diff options
6 files changed, 186 insertions, 78 deletions
diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 23fb9e370..8a6ab7932 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -21,7 +21,8 @@ namespace Jellyfin.Server.Migrations /// </summary> private static readonly Type[] _preStartupMigrationTypes = { - typeof(PreStartupRoutines.CreateNetworkConfiguration) + typeof(PreStartupRoutines.CreateNetworkConfiguration), + typeof(PreStartupRoutines.MigrateMusicBrainzTimeout) }; /// <summary> diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs new file mode 100644 index 000000000..14b51bd4c --- /dev/null +++ b/Jellyfin.Server/Migrations/PreStartupRoutines/MigrateMusicBrainzTimeout.cs @@ -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 + +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs index a97b56743..a2f3c63f0 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs @@ -1,5 +1,4 @@ using MediaBrowser.Model.Plugins; -using MetaBrainz.MusicBrainz; namespace MediaBrowser.Providers.Plugins.MusicBrainz.Configuration; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html index 6f1296bb7..62d86cd8f 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html @@ -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(); diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index 34f45f0d5..3afa90bae 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -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()); diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs index 718b5a1c4..be1d87675 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs @@ -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()); |
