Răsfoiți Sursa

Merge pull request #9352 from Shadowghost/musibrainz-fix

Cody Robibero 2 ani în urmă
părinte
comite
26297d26af

+ 2 - 1
Jellyfin.Server/Migrations/MigrationRunner.cs

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

+ 89 - 0
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
+
+}

+ 0 - 1
MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs

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

+ 11 - 15
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">
                     <div class="inputContainer">
                         <input is="emby-input" type="text" id="server" required label="Server" />
                         <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 class="fieldDescription">This can be a mirror of the official server or even a custom server.</div>
                     </div>
                     </div>
                     <div class="inputContainer">
                     <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>
                     </div>
                     <label class="checkboxContainer">
                     <label class="checkboxContainer">
                         <input is="emby-checkbox" type="checkbox" id="replaceArtistName" />
                         <input is="emby-checkbox" type="checkbox" id="replaceArtistName" />
@@ -32,7 +28,7 @@
                 uniquePluginId: "8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a"
                 uniquePluginId: "8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a"
             };
             };
 
 
-            document.querySelector('.musicBrainzConfigPage')
+            document.querySelector('.musicBrainzConfigurationPage')
                 .addEventListener('pageshow', function () {
                 .addEventListener('pageshow', function () {
                     Dashboard.showLoadingMsg();
                     Dashboard.showLoadingMsg();
                     ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
                     ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
@@ -49,14 +45,14 @@
                             bubbles: true,
                             bubbles: true,
                             cancelable: false
                             cancelable: false
                         }));
                         }));
-                        
+
                         document.querySelector('#replaceArtistName').checked = config.ReplaceArtistName;
                         document.querySelector('#replaceArtistName').checked = config.ReplaceArtistName;
 
 
                         Dashboard.hideLoadingMsg();
                         Dashboard.hideLoadingMsg();
                     });
                     });
                 });
                 });
 
 
-            document.querySelector('.musicBrainzConfigForm')
+            document.querySelector('.musicBrainzConfigurationForm')
                 .addEventListener('submit', function (e) {
                 .addEventListener('submit', function (e) {
                     Dashboard.showLoadingMsg();
                     Dashboard.showLoadingMsg();
 
 

+ 54 - 36
MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs

@@ -8,8 +8,10 @@ using Jellyfin.Extensions;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Plugins;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Providers.Music;
 using MediaBrowser.Providers.Music;
+using MediaBrowser.Providers.Plugins.MusicBrainz.Configuration;
 using MetaBrainz.MusicBrainz;
 using MetaBrainz.MusicBrainz;
 using MetaBrainz.MusicBrainz.Interfaces.Entities;
 using MetaBrainz.MusicBrainz.Interfaces.Entities;
 using MetaBrainz.MusicBrainz.Interfaces.Searches;
 using MetaBrainz.MusicBrainz.Interfaces.Searches;
@@ -23,8 +25,7 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz;
 public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, AlbumInfo>, IHasOrder, IDisposable
 public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, AlbumInfo>, IHasOrder, IDisposable
 {
 {
     private readonly ILogger<MusicBrainzAlbumProvider> _logger;
     private readonly ILogger<MusicBrainzAlbumProvider> _logger;
-    private readonly Query _musicBrainzQuery;
-    private readonly string _musicBrainzDefaultUri = "https://musicbrainz.org";
+    private Query _musicBrainzQuery;
 
 
     /// <summary>
     /// <summary>
     /// Initializes a new instance of the <see cref="MusicBrainzAlbumProvider"/> class.
     /// 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)
     public MusicBrainzAlbumProvider(ILogger<MusicBrainzAlbumProvider> logger)
     {
     {
         _logger = 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();
         _musicBrainzQuery = new Query();
+        ReloadConfig(null, MusicBrainz.Plugin.Instance!.Configuration);
+        MusicBrainz.Plugin.Instance!.ConfigurationChanged += ReloadConfig;
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
@@ -64,6 +45,29 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu
     /// <inheritdoc />
     /// <inheritdoc />
     public int Order => 0;
     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 />
     /// <inheritdoc />
     public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken)
     public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken)
     {
     {
@@ -72,13 +76,13 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu
 
 
         if (!string.IsNullOrEmpty(releaseId))
         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();
             return GetReleaseResult(releaseResult).SingleItemAsEnumerable();
         }
         }
 
 
         if (!string.IsNullOrEmpty(releaseGroupId))
         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);
             return GetReleaseGroupResult(releaseGroupResult.Releases);
         }
         }
 
 
@@ -133,7 +137,9 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu
 
 
         foreach (var result in releaseSearchResults)
         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,
             Name = releaseSearchResult.Title,
             ProductionYear = releaseSearchResult.Date?.Year,
             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());
         searchResult.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseSearchResult.Id.ToString());

+ 30 - 25
MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs

@@ -8,8 +8,10 @@ using Jellyfin.Extensions;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Plugins;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Providers;
 using MediaBrowser.Providers.Music;
 using MediaBrowser.Providers.Music;
+using MediaBrowser.Providers.Plugins.MusicBrainz.Configuration;
 using MetaBrainz.MusicBrainz;
 using MetaBrainz.MusicBrainz;
 using MetaBrainz.MusicBrainz.Interfaces.Entities;
 using MetaBrainz.MusicBrainz.Interfaces.Entities;
 using MetaBrainz.MusicBrainz.Interfaces.Searches;
 using MetaBrainz.MusicBrainz.Interfaces.Searches;
@@ -23,8 +25,7 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz;
 public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IDisposable
 public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IDisposable
 {
 {
     private readonly ILogger<MusicBrainzArtistProvider> _logger;
     private readonly ILogger<MusicBrainzArtistProvider> _logger;
-    private readonly Query _musicBrainzQuery;
-    private readonly string _musicBrainzDefaultUri = "https://musicbrainz.org";
+    private Query _musicBrainzQuery;
 
 
     /// <summary>
     /// <summary>
     /// Initializes a new instance of the <see cref="MusicBrainzArtistProvider"/> class.
     /// 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)
     public MusicBrainzArtistProvider(ILogger<MusicBrainzArtistProvider> logger)
     {
     {
         _logger = 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();
         _musicBrainzQuery = new Query();
+        ReloadConfig(null, MusicBrainz.Plugin.Instance!.Configuration);
+        MusicBrainz.Plugin.Instance!.ConfigurationChanged += ReloadConfig;
     }
     }
 
 
     /// <inheritdoc />
     /// <inheritdoc />
     public string Name => "MusicBrainz";
     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 />
     /// <inheritdoc />
     public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
     public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
     {
     {
@@ -112,7 +116,8 @@ public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, Ar
         {
         {
             Name = artist.Name,
             Name = artist.Name,
             ProductionYear = artist.LifeSpan?.Begin?.Year,
             ProductionYear = artist.LifeSpan?.Begin?.Year,
-            PremiereDate = artist.LifeSpan?.Begin?.NearestDate
+            PremiereDate = artist.LifeSpan?.Begin?.NearestDate,
+            SearchProviderName = Name,
         };
         };
 
 
         searchResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Id.ToString());
         searchResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Id.ToString());