Merge pull request #9352 from Shadowghost/musibrainz-fix

This commit is contained in:
Cody Robibero 2023-02-19 16:55:54 -07:00 committed by GitHub
commit 26297d26af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 186 additions and 78 deletions

View File

@ -21,7 +21,8 @@ namespace Jellyfin.Server.Migrations
/// </summary>
private static readonly Type[] _preStartupMigrationTypes =
{
typeof(PreStartupRoutines.CreateNetworkConfiguration)
typeof(PreStartupRoutines.CreateNetworkConfiguration),
typeof(PreStartupRoutines.MigrateMusicBrainzTimeout)
};
/// <summary>

View File

@ -0,0 +1,89 @@
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using Emby.Server.Implementations;
using MediaBrowser.Providers.Plugins.MusicBrainz.Configuration;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Migrations.PreStartupRoutines;
/// <inheritdoc />
public class MigrateMusicBrainzTimeout : IMigrationRoutine
{
private readonly ServerApplicationPaths _applicationPaths;
private readonly ILogger<MigrateMusicBrainzTimeout> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="MigrateMusicBrainzTimeout"/> class.
/// </summary>
/// <param name="applicationPaths">An instance of <see cref="ServerApplicationPaths"/>.</param>
/// <param name="loggerFactory">An instance of the <see cref="ILoggerFactory"/> interface.</param>
public MigrateMusicBrainzTimeout(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory)
{
_applicationPaths = applicationPaths;
_logger = loggerFactory.CreateLogger<MigrateMusicBrainzTimeout>();
}
/// <inheritdoc />
public Guid Id => Guid.Parse("A6DCACF4-C057-4Ef9-80D3-61CEF9DDB4F0");
/// <inheritdoc />
public string Name => nameof(MigrateMusicBrainzTimeout);
/// <inheritdoc />
public bool PerformOnNewInstall => false;
/// <inheritdoc />
public void Perform()
{
string path = Path.Combine(_applicationPaths.PluginConfigurationsPath, "Jellyfin.Plugin.MusicBrainz.xml");
if (!File.Exists(path))
{
_logger.LogDebug("No MusicBrainz plugin configuration file found, skipping");
return;
}
var serverConfigSerializer = new XmlSerializer(typeof(OldMusicBrainzConfiguration), new XmlRootAttribute("PluginConfiguration"));
using var xmlReader = XmlReader.Create(path);
var oldPluginConfiguration = serverConfigSerializer.Deserialize(xmlReader) as OldMusicBrainzConfiguration;
if (oldPluginConfiguration is not null)
{
var newPluginConfiguration = new PluginConfiguration();
newPluginConfiguration.Server = oldPluginConfiguration.Server;
newPluginConfiguration.ReplaceArtistName = oldPluginConfiguration.ReplaceArtistName;
var newRateLimit = oldPluginConfiguration.RateLimit / 1000.0;
newPluginConfiguration.RateLimit = newRateLimit < 1.0 ? 1.0 : newRateLimit;
var pluginConfigurationSerializer = new XmlSerializer(typeof(PluginConfiguration), new XmlRootAttribute("PluginConfiguration"));
var xmlWriterSettings = new XmlWriterSettings { Indent = true };
using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings);
pluginConfigurationSerializer.Serialize(xmlWriter, newPluginConfiguration);
}
}
#pragma warning disable
public sealed class OldMusicBrainzConfiguration
{
private string _server = string.Empty;
private long _rateLimit = 0L;
public string Server
{
get => _server;
set => _server = value.TrimEnd('/');
}
public long RateLimit
{
get => _rateLimit;
set => _rateLimit = value;
}
public bool ReplaceArtistName { get; set; }
}
#pragma warning restore
}

View File

@ -1,5 +1,4 @@
using MediaBrowser.Model.Plugins;
using MetaBrainz.MusicBrainz;
namespace MediaBrowser.Providers.Plugins.MusicBrainz.Configuration;

View File

@ -1,20 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<title>MusicBrainz</title>
</head>
<body>
<div data-role="page" class="page type-interior pluginConfigurationPage musicBrainzConfigPage" data-require="emby-input,emby-button,emby-checkbox">
<div data-role="content">
<div class="content-primary">
<form class="musicBrainzConfigForm">
<div id="musicBrainzConfigurationPage" data-role="page"
class="page type-interior pluginConfigurationPage musicBrainzConfigurationPage" data-require="emby-input,emby-button,emby-checkbox">
<div data-role="content">
<div class="content-primary">
<h1>MusicBrainz</h1>
<form class="musicBrainzConfigurationForm">
<div class="inputContainer">
<input is="emby-input" type="text" id="server" required label="Server" />
<div class="fieldDescription">This can be a mirror of the official server or even a custom server.</div>
</div>
<div class="inputContainer">
<input is="emby-input" type="number" id="rateLimit" pattern="[0-9]*" required min="0" max="10000" label="Rate Limit" />
<div class="fieldDescription">Span of time between requests in milliseconds. The official server is limited to one request every two seconds.</div>
<input is="emby-input" type="number" id="rateLimit" required pattern="[0-9]*" min="0" max="10" step=".01" label="Rate Limit" />
<div class="fieldDescription">Span of time between requests in seconds. The official server is limited to one request every seconds.</div>
</div>
<label class="checkboxContainer">
<input is="emby-checkbox" type="checkbox" id="replaceArtistName" />
@ -32,7 +28,7 @@
uniquePluginId: "8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a"
};
document.querySelector('.musicBrainzConfigPage')
document.querySelector('.musicBrainzConfigurationPage')
.addEventListener('pageshow', function () {
Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
@ -49,14 +45,14 @@
bubbles: true,
cancelable: false
}));
document.querySelector('#replaceArtistName').checked = config.ReplaceArtistName;
Dashboard.hideLoadingMsg();
});
});
document.querySelector('.musicBrainzConfigForm')
document.querySelector('.musicBrainzConfigurationForm')
.addEventListener('submit', function (e) {
Dashboard.showLoadingMsg();

View File

@ -8,8 +8,10 @@ using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Providers;
using MediaBrowser.Providers.Music;
using MediaBrowser.Providers.Plugins.MusicBrainz.Configuration;
using MetaBrainz.MusicBrainz;
using MetaBrainz.MusicBrainz.Interfaces.Entities;
using MetaBrainz.MusicBrainz.Interfaces.Searches;
@ -23,8 +25,7 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz;
public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, AlbumInfo>, IHasOrder, IDisposable
{
private readonly ILogger<MusicBrainzAlbumProvider> _logger;
private readonly Query _musicBrainzQuery;
private readonly string _musicBrainzDefaultUri = "https://musicbrainz.org";
private Query _musicBrainzQuery;
/// <summary>
/// Initializes a new instance of the <see cref="MusicBrainzAlbumProvider"/> class.
@ -33,29 +34,9 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu
public MusicBrainzAlbumProvider(ILogger<MusicBrainzAlbumProvider> logger)
{
_logger = logger;
MusicBrainz.Plugin.Instance!.ConfigurationChanged += (_, _) =>
{
if (Uri.TryCreate(MusicBrainz.Plugin.Instance.Configuration.Server, UriKind.Absolute, out var server))
{
Query.DefaultServer = server.Host;
Query.DefaultPort = server.Port;
Query.DefaultUrlScheme = server.Scheme;
}
else
{
// Fallback to official server
_logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server");
var defaultServer = new Uri(_musicBrainzDefaultUri);
Query.DefaultServer = defaultServer.Host;
Query.DefaultPort = defaultServer.Port;
Query.DefaultUrlScheme = defaultServer.Scheme;
}
Query.DelayBetweenRequests = MusicBrainz.Plugin.Instance.Configuration.RateLimit;
};
_musicBrainzQuery = new Query();
ReloadConfig(null, MusicBrainz.Plugin.Instance!.Configuration);
MusicBrainz.Plugin.Instance!.ConfigurationChanged += ReloadConfig;
}
/// <inheritdoc />
@ -64,6 +45,29 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu
/// <inheritdoc />
public int Order => 0;
private void ReloadConfig(object? sender, BasePluginConfiguration e)
{
var configuration = (PluginConfiguration)e;
if (Uri.TryCreate(configuration.Server, UriKind.Absolute, out var server))
{
Query.DefaultServer = server.DnsSafeHost;
Query.DefaultPort = server.Port;
Query.DefaultUrlScheme = server.Scheme;
}
else
{
// Fallback to official server
_logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server");
var defaultServer = new Uri(configuration.Server);
Query.DefaultServer = defaultServer.Host;
Query.DefaultPort = defaultServer.Port;
Query.DefaultUrlScheme = defaultServer.Scheme;
}
Query.DelayBetweenRequests = configuration.RateLimit;
_musicBrainzQuery = new Query();
}
/// <inheritdoc />
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken)
{
@ -72,13 +76,13 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu
if (!string.IsNullOrEmpty(releaseId))
{
var releaseResult = await _musicBrainzQuery.LookupReleaseAsync(new Guid(releaseId), Include.ReleaseGroups, cancellationToken).ConfigureAwait(false);
var releaseResult = await _musicBrainzQuery.LookupReleaseAsync(new Guid(releaseId), Include.Artists | Include.ReleaseGroups, cancellationToken).ConfigureAwait(false);
return GetReleaseResult(releaseResult).SingleItemAsEnumerable();
}
if (!string.IsNullOrEmpty(releaseGroupId))
{
var releaseGroupResult = await _musicBrainzQuery.LookupReleaseGroupAsync(new Guid(releaseGroupId), Include.None, null, cancellationToken).ConfigureAwait(false);
var releaseGroupResult = await _musicBrainzQuery.LookupReleaseGroupAsync(new Guid(releaseGroupId), Include.Releases, null, cancellationToken).ConfigureAwait(false);
return GetReleaseGroupResult(releaseGroupResult.Releases);
}
@ -133,7 +137,9 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu
foreach (var result in releaseSearchResults)
{
yield return GetReleaseResult(result);
// Fetch full release info, otherwise artists are missing
var fullResult = _musicBrainzQuery.LookupRelease(result.Id, Include.Artists | Include.ReleaseGroups);
yield return GetReleaseResult(fullResult);
}
}
@ -143,21 +149,33 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu
{
Name = releaseSearchResult.Title,
ProductionYear = releaseSearchResult.Date?.Year,
PremiereDate = releaseSearchResult.Date?.NearestDate
PremiereDate = releaseSearchResult.Date?.NearestDate,
SearchProviderName = Name
};
if (releaseSearchResult.ArtistCredit?.Count > 0)
// Add artists and use first as album artist
var artists = releaseSearchResult.ArtistCredit;
if (artists is not null && artists.Count > 0)
{
searchResult.AlbumArtist = new RemoteSearchResult
{
SearchProviderName = Name,
Name = releaseSearchResult.ArtistCredit[0].Name
};
var artistResults = new List<RemoteSearchResult>();
if (releaseSearchResult.ArtistCredit[0].Artist?.Id is not null)
foreach (var artist in artists)
{
searchResult.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, releaseSearchResult.ArtistCredit[0].Artist!.Id.ToString());
var artistResult = new RemoteSearchResult
{
Name = artist.Name
};
if (artist.Artist?.Id is not null)
{
artistResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Artist!.Id.ToString());
}
artistResults.Add(artistResult);
}
searchResult.AlbumArtist = artistResults[0];
searchResult.Artists = artistResults.ToArray();
}
searchResult.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseSearchResult.Id.ToString());

View File

@ -8,8 +8,10 @@ using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Providers;
using MediaBrowser.Providers.Music;
using MediaBrowser.Providers.Plugins.MusicBrainz.Configuration;
using MetaBrainz.MusicBrainz;
using MetaBrainz.MusicBrainz.Interfaces.Entities;
using MetaBrainz.MusicBrainz.Interfaces.Searches;
@ -23,8 +25,7 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz;
public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IDisposable
{
private readonly ILogger<MusicBrainzArtistProvider> _logger;
private readonly Query _musicBrainzQuery;
private readonly string _musicBrainzDefaultUri = "https://musicbrainz.org";
private Query _musicBrainzQuery;
/// <summary>
/// Initializes a new instance of the <see cref="MusicBrainzArtistProvider"/> class.
@ -33,34 +34,37 @@ public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, Ar
public MusicBrainzArtistProvider(ILogger<MusicBrainzArtistProvider> logger)
{
_logger = logger;
MusicBrainz.Plugin.Instance!.ConfigurationChanged += (_, _) =>
{
if (Uri.TryCreate(MusicBrainz.Plugin.Instance.Configuration.Server, UriKind.Absolute, out var server))
{
Query.DefaultServer = server.Host;
Query.DefaultPort = server.Port;
Query.DefaultUrlScheme = server.Scheme;
}
else
{
// Fallback to official server
_logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server");
var defaultServer = new Uri(_musicBrainzDefaultUri);
Query.DefaultServer = defaultServer.Host;
Query.DefaultPort = defaultServer.Port;
Query.DefaultUrlScheme = defaultServer.Scheme;
}
Query.DelayBetweenRequests = MusicBrainz.Plugin.Instance.Configuration.RateLimit;
};
_musicBrainzQuery = new Query();
ReloadConfig(null, MusicBrainz.Plugin.Instance!.Configuration);
MusicBrainz.Plugin.Instance!.ConfigurationChanged += ReloadConfig;
}
/// <inheritdoc />
public string Name => "MusicBrainz";
private void ReloadConfig(object? sender, BasePluginConfiguration e)
{
var configuration = (PluginConfiguration)e;
if (Uri.TryCreate(configuration.Server, UriKind.Absolute, out var server))
{
Query.DefaultServer = server.DnsSafeHost;
Query.DefaultPort = server.Port;
Query.DefaultUrlScheme = server.Scheme;
}
else
{
// Fallback to official server
_logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server");
var defaultServer = new Uri(configuration.Server);
Query.DefaultServer = defaultServer.Host;
Query.DefaultPort = defaultServer.Port;
Query.DefaultUrlScheme = defaultServer.Scheme;
}
Query.DelayBetweenRequests = configuration.RateLimit;
_musicBrainzQuery = new Query();
}
/// <inheritdoc />
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
{
@ -112,7 +116,8 @@ public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, Ar
{
Name = artist.Name,
ProductionYear = artist.LifeSpan?.Begin?.Year,
PremiereDate = artist.LifeSpan?.Begin?.NearestDate
PremiereDate = artist.LifeSpan?.Begin?.NearestDate,
SearchProviderName = Name,
};
searchResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Id.ToString());