Rich Lander 3 роки тому
батько
коміт
fb92eab69b
20 змінених файлів з 426 додано та 322 видалено
  1. 1 0
      MediaBrowser.Providers/Manager/ItemImageProvider.cs
  2. 5 0
      MediaBrowser.Providers/Manager/MetadataService.cs
  3. 1 1
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  4. 0 0
      MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs
  5. 10 8
      MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs
  6. 0 0
      MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs
  7. 8 8
      MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
  8. 0 119
      MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs
  9. 28 0
      MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs
  10. 28 0
      MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs
  11. 181 177
      MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
  12. 28 0
      MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs
  13. 2 2
      MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
  14. 28 0
      MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs
  15. 28 0
      MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs
  16. 28 0
      MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs
  17. 4 4
      MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
  18. 1 1
      MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
  19. 43 1
      MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
  20. 2 1
      MediaBrowser.Providers/Studios/StudioMetadataService.cs

+ 1 - 0
MediaBrowser.Providers/Manager/ItemImageProvider.cs

@@ -536,6 +536,7 @@ namespace MediaBrowser.Providers.Manager
                     return true;
                     return true;
                 }
                 }
             }
             }
+
             // We always want to use prefetched images
             // We always want to use prefetched images
             return false;
             return false;
         }
         }

+ 5 - 0
MediaBrowser.Providers/Manager/MetadataService.cs

@@ -505,6 +505,11 @@ namespace MediaBrowser.Providers.Manager
         /// <summary>
         /// <summary>
         /// Gets the providers.
         /// Gets the providers.
         /// </summary>
         /// </summary>
+        /// <param name="item">A media item.</param>
+        /// <param name="libraryOptions">The LibraryOptions to use.</param>
+        /// <param name="options">The MetadataRefreshOptions to use.</param>
+        /// <param name="isFirstRefresh">Specifies first refresh mode.</param>
+        /// <param name="requiresRefresh">Specifies refresh mode.</param>
         /// <returns>IEnumerable{`0}.</returns>
         /// <returns>IEnumerable{`0}.</returns>
         protected IEnumerable<IMetadataProvider> GetProviders(BaseItem item, LibraryOptions libraryOptions, MetadataRefreshOptions options, bool isFirstRefresh, bool requiresRefresh)
         protected IEnumerable<IMetadataProvider> GetProviders(BaseItem item, LibraryOptions libraryOptions, MetadataRefreshOptions options, bool isFirstRefresh, bool requiresRefresh)
         {
         {

+ 1 - 1
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -29,7 +29,7 @@
     <TargetFramework>net5.0</TargetFramework>
     <TargetFramework>net5.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
-    <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
+    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <AnalysisMode Condition=" '$(Configuration)' == 'Debug'">AllEnabledByDefault</AnalysisMode>
     <AnalysisMode Condition=" '$(Configuration)' == 'Debug'">AllEnabledByDefault</AnalysisMode>
     <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
     <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
   </PropertyGroup>
   </PropertyGroup>

+ 0 - 0
MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs → MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs


+ 10 - 8
MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs → MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs

@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591, SA1300
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -9,9 +9,9 @@ using System.Net.Http;
 using System.Text.Json;
 using System.Text.Json;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
-using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
@@ -30,7 +30,9 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
         private readonly IHttpClientFactory _httpClientFactory;
         private readonly IHttpClientFactory _httpClientFactory;
         private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
         private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 
 
+#pragma warning disable SA1401
         public static AudioDbAlbumProvider Current;
         public static AudioDbAlbumProvider Current;
+#pragma warning restore SA1401
 
 
         public AudioDbAlbumProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory)
         public AudioDbAlbumProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory)
         {
         {
@@ -196,6 +198,12 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
             return Path.Combine(dataPath, "album.json");
             return Path.Combine(dataPath, "album.json");
         }
         }
 
 
+        /// <inheritdoc />
+        public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
+        {
+            throw new NotImplementedException();
+        }
+
         public class Album
         public class Album
         {
         {
             public string idAlbum { get; set; }
             public string idAlbum { get; set; }
@@ -279,11 +287,5 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
         {
         {
             public List<Album> album { get; set; }
             public List<Album> album { get; set; }
         }
         }
-
-        /// <inheritdoc />
-        public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
-        {
-            throw new NotImplementedException();
-        }
     }
     }
 }
 }

+ 0 - 0
MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs → MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs


+ 8 - 8
MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs → MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs

@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591, SA1300
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -8,9 +8,9 @@ using System.Net.Http;
 using System.Text.Json;
 using System.Text.Json;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
-using Jellyfin.Extensions.Json;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
@@ -183,6 +183,12 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
             return Path.Combine(dataPath, "artist.json");
             return Path.Combine(dataPath, "artist.json");
         }
         }
 
 
+        /// <inheritdoc />
+        public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
+        {
+            throw new NotImplementedException();
+        }
+
         public class Artist
         public class Artist
         {
         {
             public string idArtist { get; set; }
             public string idArtist { get; set; }
@@ -272,11 +278,5 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
         {
         {
             public List<Artist> artists { get; set; }
             public List<Artist> artists { get; set; }
         }
         }
-
-        /// <inheritdoc />
-        public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
-        {
-            throw new NotImplementedException();
-        }
     }
     }
 }
 }

+ 0 - 119
MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs

@@ -1,119 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-
-namespace MediaBrowser.Providers.Music
-{
-    public class MusicBrainzReleaseGroupExternalId : IExternalId
-    {
-        /// <inheritdoc />
-        public string ProviderName => "MusicBrainz";
-
-        /// <inheritdoc />
-        public string Key => MetadataProvider.MusicBrainzReleaseGroup.ToString();
-
-        /// <inheritdoc />
-        public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup;
-
-        /// <inheritdoc />
-        public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}";
-
-        /// <inheritdoc />
-        public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
-    }
-
-    public class MusicBrainzAlbumArtistExternalId : IExternalId
-    {
-        /// <inheritdoc />
-        public string ProviderName => "MusicBrainz";
-
-        /// <inheritdoc />
-        public string Key => MetadataProvider.MusicBrainzAlbumArtist.ToString();
-
-        /// <inheritdoc />
-        public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist;
-
-        /// <inheritdoc />
-        public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
-
-        /// <inheritdoc />
-        public bool Supports(IHasProviderIds item) => item is Audio;
-    }
-
-    public class MusicBrainzAlbumExternalId : IExternalId
-    {
-        /// <inheritdoc />
-        public string ProviderName => "MusicBrainz";
-
-        /// <inheritdoc />
-        public string Key => MetadataProvider.MusicBrainzAlbum.ToString();
-
-        /// <inheritdoc />
-        public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
-
-        /// <inheritdoc />
-        public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}";
-
-        /// <inheritdoc />
-        public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
-    }
-
-    public class MusicBrainzArtistExternalId : IExternalId
-    {
-        /// <inheritdoc />
-        public string ProviderName => "MusicBrainz";
-
-        /// <inheritdoc />
-        public string Key => MetadataProvider.MusicBrainzArtist.ToString();
-
-        /// <inheritdoc />
-        public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
-
-        /// <inheritdoc />
-        public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
-
-        /// <inheritdoc />
-        public bool Supports(IHasProviderIds item) => item is MusicArtist;
-    }
-
-    public class MusicBrainzOtherArtistExternalId : IExternalId
-    {
-        /// <inheritdoc />
-        public string ProviderName => "MusicBrainz";
-
-        /// <inheritdoc />
-
-        public string Key => MetadataProvider.MusicBrainzArtist.ToString();
-
-        /// <inheritdoc />
-        public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
-
-        /// <inheritdoc />
-        public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
-
-        /// <inheritdoc />
-        public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
-    }
-
-    public class MusicBrainzTrackId : IExternalId
-    {
-        /// <inheritdoc />
-        public string ProviderName => "MusicBrainz";
-
-        /// <inheritdoc />
-        public string Key => MetadataProvider.MusicBrainzTrack.ToString();
-
-        /// <inheritdoc />
-        public ExternalIdMediaType? Type => ExternalIdMediaType.Track;
-
-        /// <inheritdoc />
-        public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}";
-
-        /// <inheritdoc />
-        public bool Supports(IHasProviderIds item) => item is Audio;
-    }
-}

+ 28 - 0
MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs

@@ -0,0 +1,28 @@
+#pragma warning disable CS1591
+
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
+
+namespace MediaBrowser.Providers.Music
+{
+    public class MusicBrainzAlbumArtistExternalId : IExternalId
+    {
+        /// <inheritdoc />
+        public string ProviderName => "MusicBrainz";
+
+        /// <inheritdoc />
+        public string Key => MetadataProvider.MusicBrainzAlbumArtist.ToString();
+
+        /// <inheritdoc />
+        public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist;
+
+        /// <inheritdoc />
+        public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+
+        /// <inheritdoc />
+        public bool Supports(IHasProviderIds item) => item is Audio;
+    }
+}

+ 28 - 0
MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs

@@ -0,0 +1,28 @@
+#pragma warning disable CS1591
+
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
+
+namespace MediaBrowser.Providers.Music
+{
+    public class MusicBrainzAlbumExternalId : IExternalId
+    {
+        /// <inheritdoc />
+        public string ProviderName => "MusicBrainz";
+
+        /// <inheritdoc />
+        public string Key => MetadataProvider.MusicBrainzAlbum.ToString();
+
+        /// <inheritdoc />
+        public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
+
+        /// <inheritdoc />
+        public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}";
+
+        /// <inheritdoc />
+        public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
+    }
+}

+ 181 - 177
MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs

@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591, SA1401
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Music
         /// The Jellyfin user-agent is unrestricted but source IP must not exceed
         /// The Jellyfin user-agent is unrestricted but source IP must not exceed
         /// one request per second, therefore we rate limit to avoid throttling.
         /// one request per second, therefore we rate limit to avoid throttling.
         /// Be prudent, use a value slightly above the minimun required.
         /// Be prudent, use a value slightly above the minimun required.
-        /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting
+        /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting.
         /// </summary>
         /// </summary>
         private readonly long _musicBrainzQueryIntervalMs;
         private readonly long _musicBrainzQueryIntervalMs;
 
 
@@ -302,181 +302,6 @@ namespace MediaBrowser.Providers.Music
             return ReleaseResult.Parse(reader).FirstOrDefault();
             return ReleaseResult.Parse(reader).FirstOrDefault();
         }
         }
 
 
-        private class ReleaseResult
-        {
-            public string ReleaseId;
-            public string ReleaseGroupId;
-            public string Title;
-            public string Overview;
-            public int? Year;
-
-            public List<ValueTuple<string, string>> Artists = new List<ValueTuple<string, string>>();
-
-            public static IEnumerable<ReleaseResult> Parse(XmlReader reader)
-            {
-                reader.MoveToContent();
-                reader.Read();
-
-                // Loop through each element
-                while (!reader.EOF && reader.ReadState == ReadState.Interactive)
-                {
-                    if (reader.NodeType == XmlNodeType.Element)
-                    {
-                        switch (reader.Name)
-                        {
-                            case "release-list":
-                                {
-                                    if (reader.IsEmptyElement)
-                                    {
-                                        reader.Read();
-                                        continue;
-                                    }
-
-                                    using var subReader = reader.ReadSubtree();
-                                    return ParseReleaseList(subReader).ToList();
-                                }
-
-                            default:
-                                {
-                                    reader.Skip();
-                                    break;
-                                }
-                        }
-                    }
-                    else
-                    {
-                        reader.Read();
-                    }
-                }
-
-                return Enumerable.Empty<ReleaseResult>();
-            }
-
-            private static IEnumerable<ReleaseResult> ParseReleaseList(XmlReader reader)
-            {
-                reader.MoveToContent();
-                reader.Read();
-
-                // Loop through each element
-                while (!reader.EOF && reader.ReadState == ReadState.Interactive)
-                {
-                    if (reader.NodeType == XmlNodeType.Element)
-                    {
-                        switch (reader.Name)
-                        {
-                            case "release":
-                                {
-                                    if (reader.IsEmptyElement)
-                                    {
-                                        reader.Read();
-                                        continue;
-                                    }
-
-                                    var releaseId = reader.GetAttribute("id");
-
-                                    using var subReader = reader.ReadSubtree();
-                                    var release = ParseRelease(subReader, releaseId);
-                                    if (release != null)
-                                    {
-                                        yield return release;
-                                    }
-
-                                    break;
-                                }
-
-                            default:
-                                {
-                                    reader.Skip();
-                                    break;
-                                }
-                        }
-                    }
-                    else
-                    {
-                        reader.Read();
-                    }
-                }
-            }
-
-            private static ReleaseResult ParseRelease(XmlReader reader, string releaseId)
-            {
-                var result = new ReleaseResult
-                {
-                    ReleaseId = releaseId
-                };
-
-                reader.MoveToContent();
-                reader.Read();
-
-                // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator
-
-                // Loop through each element
-                while (!reader.EOF && reader.ReadState == ReadState.Interactive)
-                {
-                    if (reader.NodeType == XmlNodeType.Element)
-                    {
-                        switch (reader.Name)
-                        {
-                            case "title":
-                                {
-                                    result.Title = reader.ReadElementContentAsString();
-                                    break;
-                                }
-
-                            case "date":
-                                {
-                                    var val = reader.ReadElementContentAsString();
-                                    if (DateTime.TryParse(val, out var date))
-                                    {
-                                        result.Year = date.Year;
-                                    }
-
-                                    break;
-                                }
-
-                            case "annotation":
-                                {
-                                    result.Overview = reader.ReadElementContentAsString();
-                                    break;
-                                }
-
-                            case "release-group":
-                                {
-                                    result.ReleaseGroupId = reader.GetAttribute("id");
-                                    reader.Skip();
-                                    break;
-                                }
-
-                            case "artist-credit":
-                                {
-                                    using var subReader = reader.ReadSubtree();
-                                    var artist = ParseArtistCredit(subReader);
-
-                                    if (!string.IsNullOrEmpty(artist.Item1))
-                                    {
-                                        result.Artists.Add(artist);
-                                    }
-
-                                    break;
-                                }
-
-                            default:
-                                {
-                                    reader.Skip();
-                                    break;
-                                }
-                        }
-                    }
-                    else
-                    {
-                        reader.Read();
-                    }
-                }
-
-                return result;
-            }
-        }
-
         private static (string, string) ParseArtistCredit(XmlReader reader)
         private static (string, string) ParseArtistCredit(XmlReader reader)
         {
         {
             reader.MoveToContent();
             reader.MoveToContent();
@@ -496,6 +321,7 @@ namespace MediaBrowser.Providers.Music
                             using var subReader = reader.ReadSubtree();
                             using var subReader = reader.ReadSubtree();
                             return ParseArtistNameCredit(subReader);
                             return ParseArtistNameCredit(subReader);
                         }
                         }
+
                         default:
                         default:
                         {
                         {
                             reader.Skip();
                             reader.Skip();
@@ -707,6 +533,9 @@ namespace MediaBrowser.Providers.Music
         /// A number of retries shall be made in order to try and satisfy the request before
         /// A number of retries shall be made in order to try and satisfy the request before
         /// giving up and returning null.
         /// giving up and returning null.
         /// </summary>
         /// </summary>
+        /// <param name="url">Address of MusicBrainz server.</param>
+        /// <param name="cancellationToken">CancellationToken to use for method.</param>
+        /// <returns>Returns response from MusicBrainz service.</returns>
         internal async Task<HttpResponseMessage> GetMusicBrainzResponse(string url, CancellationToken cancellationToken)
         internal async Task<HttpResponseMessage> GetMusicBrainzResponse(string url, CancellationToken cancellationToken)
         {
         {
             await _apiRequestLock.WaitAsync(cancellationToken).ConfigureAwait(false);
             await _apiRequestLock.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -762,5 +591,180 @@ namespace MediaBrowser.Providers.Music
         {
         {
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
+
+        private class ReleaseResult
+        {
+            public string ReleaseId;
+            public string ReleaseGroupId;
+            public string Title;
+            public string Overview;
+            public int? Year;
+
+            public List<ValueTuple<string, string>> Artists = new List<ValueTuple<string, string>>();
+
+            public static IEnumerable<ReleaseResult> Parse(XmlReader reader)
+            {
+                reader.MoveToContent();
+                reader.Read();
+
+                // Loop through each element
+                while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+                {
+                    if (reader.NodeType == XmlNodeType.Element)
+                    {
+                        switch (reader.Name)
+                        {
+                            case "release-list":
+                                {
+                                    if (reader.IsEmptyElement)
+                                    {
+                                        reader.Read();
+                                        continue;
+                                    }
+
+                                    using var subReader = reader.ReadSubtree();
+                                    return ParseReleaseList(subReader).ToList();
+                                }
+
+                            default:
+                                {
+                                    reader.Skip();
+                                    break;
+                                }
+                        }
+                    }
+                    else
+                    {
+                        reader.Read();
+                    }
+                }
+
+                return Enumerable.Empty<ReleaseResult>();
+            }
+
+            private static IEnumerable<ReleaseResult> ParseReleaseList(XmlReader reader)
+            {
+                reader.MoveToContent();
+                reader.Read();
+
+                // Loop through each element
+                while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+                {
+                    if (reader.NodeType == XmlNodeType.Element)
+                    {
+                        switch (reader.Name)
+                        {
+                            case "release":
+                                {
+                                    if (reader.IsEmptyElement)
+                                    {
+                                        reader.Read();
+                                        continue;
+                                    }
+
+                                    var releaseId = reader.GetAttribute("id");
+
+                                    using var subReader = reader.ReadSubtree();
+                                    var release = ParseRelease(subReader, releaseId);
+                                    if (release != null)
+                                    {
+                                        yield return release;
+                                    }
+
+                                    break;
+                                }
+
+                            default:
+                                {
+                                    reader.Skip();
+                                    break;
+                                }
+                        }
+                    }
+                    else
+                    {
+                        reader.Read();
+                    }
+                }
+            }
+
+            private static ReleaseResult ParseRelease(XmlReader reader, string releaseId)
+            {
+                var result = new ReleaseResult
+                {
+                    ReleaseId = releaseId
+                };
+
+                reader.MoveToContent();
+                reader.Read();
+
+                // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator
+
+                // Loop through each element
+                while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+                {
+                    if (reader.NodeType == XmlNodeType.Element)
+                    {
+                        switch (reader.Name)
+                        {
+                            case "title":
+                                {
+                                    result.Title = reader.ReadElementContentAsString();
+                                    break;
+                                }
+
+                            case "date":
+                                {
+                                    var val = reader.ReadElementContentAsString();
+                                    if (DateTime.TryParse(val, out var date))
+                                    {
+                                        result.Year = date.Year;
+                                    }
+
+                                    break;
+                                }
+
+                            case "annotation":
+                                {
+                                    result.Overview = reader.ReadElementContentAsString();
+                                    break;
+                                }
+
+                            case "release-group":
+                                {
+                                    result.ReleaseGroupId = reader.GetAttribute("id");
+                                    reader.Skip();
+                                    break;
+                                }
+
+                            case "artist-credit":
+                                {
+                                    using var subReader = reader.ReadSubtree();
+                                    var artist = ParseArtistCredit(subReader);
+
+                                    if (!string.IsNullOrEmpty(artist.Item1))
+                                    {
+                                        result.Artists.Add(artist);
+                                    }
+
+                                    break;
+                                }
+
+                            default:
+                                {
+                                    reader.Skip();
+                                    break;
+                                }
+                        }
+                    }
+                    else
+                    {
+                        reader.Read();
+                    }
+                }
+
+                return result;
+            }
+        }
     }
     }
 }
 }

+ 28 - 0
MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs

@@ -0,0 +1,28 @@
+#pragma warning disable CS1591
+
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
+
+namespace MediaBrowser.Providers.Music
+{
+    public class MusicBrainzArtistExternalId : IExternalId
+    {
+        /// <inheritdoc />
+        public string ProviderName => "MusicBrainz";
+
+        /// <inheritdoc />
+        public string Key => MetadataProvider.MusicBrainzArtist.ToString();
+
+        /// <inheritdoc />
+        public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
+
+        /// <inheritdoc />
+        public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+
+        /// <inheritdoc />
+        public bool Supports(IHasProviderIds item) => item is MusicArtist;
+    }
+}

+ 2 - 2
MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs → MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs

@@ -22,6 +22,8 @@ namespace MediaBrowser.Providers.Music
 {
 {
     public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>
     public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>
     {
     {
+        public string Name => "MusicBrainz";
+
         /// <inheritdoc />
         /// <inheritdoc />
         public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
         public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
         {
         {
@@ -262,8 +264,6 @@ namespace MediaBrowser.Providers.Music
             return WebUtility.UrlEncode(name);
             return WebUtility.UrlEncode(name);
         }
         }
 
 
-        public string Name => "MusicBrainz";
-
         public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
         public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
         {
         {
             throw new NotImplementedException();
             throw new NotImplementedException();

+ 28 - 0
MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs

@@ -0,0 +1,28 @@
+#pragma warning disable CS1591
+
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
+
+namespace MediaBrowser.Providers.Music
+{
+    public class MusicBrainzOtherArtistExternalId : IExternalId
+    {
+        /// <inheritdoc />
+        public string ProviderName => "MusicBrainz";
+
+        /// <inheritdoc />
+        public string Key => MetadataProvider.MusicBrainzArtist.ToString();
+
+        /// <inheritdoc />
+        public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
+
+        /// <inheritdoc />
+        public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+
+        /// <inheritdoc />
+        public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
+    }
+}

+ 28 - 0
MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs

@@ -0,0 +1,28 @@
+#pragma warning disable CS1591
+
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
+
+namespace MediaBrowser.Providers.Music
+{
+    public class MusicBrainzReleaseGroupExternalId : IExternalId
+    {
+        /// <inheritdoc />
+        public string ProviderName => "MusicBrainz";
+
+        /// <inheritdoc />
+        public string Key => MetadataProvider.MusicBrainzReleaseGroup.ToString();
+
+        /// <inheritdoc />
+        public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup;
+
+        /// <inheritdoc />
+        public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}";
+
+        /// <inheritdoc />
+        public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
+    }
+}

+ 28 - 0
MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs

@@ -0,0 +1,28 @@
+#pragma warning disable CS1591
+
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
+
+namespace MediaBrowser.Providers.Music
+{
+    public class MusicBrainzTrackId : IExternalId
+    {
+        /// <inheritdoc />
+        public string ProviderName => "MusicBrainz";
+
+        /// <inheritdoc />
+        public string Key => MetadataProvider.MusicBrainzTrack.ToString();
+
+        /// <inheritdoc />
+        public ExternalIdMediaType? Type => ExternalIdMediaType.Track;
+
+        /// <inheritdoc />
+        public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}";
+
+        /// <inheritdoc />
+        public bool Supports(IHasProviderIds item) => item is Audio;
+    }
+}

+ 4 - 4
MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs

@@ -11,6 +11,10 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz
 {
 {
     public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
     public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
     {
     {
+        public const string DefaultServer = "https://musicbrainz.org";
+
+        public const long DefaultRateLimit = 2000u;
+
         public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
         public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
             : base(applicationPaths, xmlSerializer)
             : base(applicationPaths, xmlSerializer)
         {
         {
@@ -25,10 +29,6 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz
 
 
         public override string Description => "Get artist and album metadata from any MusicBrainz server.";
         public override string Description => "Get artist and album metadata from any MusicBrainz server.";
 
 
-        public const string DefaultServer = "https://musicbrainz.org";
-
-        public const long DefaultRateLimit = 2000u;
-
         // TODO remove when plugin removed from server.
         // TODO remove when plugin removed from server.
         public override string ConfigurationFileName => "Jellyfin.Plugin.MusicBrainz.xml";
         public override string ConfigurationFileName => "Jellyfin.Plugin.MusicBrainz.xml";
 
 

+ 1 - 1
MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs

@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591, SA1300
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;

+ 43 - 1
MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs

@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS159, SA1300
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -20,6 +20,7 @@ using MediaBrowser.Model.IO;
 
 
 namespace MediaBrowser.Providers.Plugins.Omdb
 namespace MediaBrowser.Providers.Plugins.Omdb
 {
 {
+    /// <summary>Provider for OMDB service.</summary>
     public class OmdbProvider
     public class OmdbProvider
     {
     {
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
@@ -29,6 +30,11 @@ namespace MediaBrowser.Providers.Plugins.Omdb
         private readonly IApplicationHost _appHost;
         private readonly IApplicationHost _appHost;
         private readonly JsonSerializerOptions _jsonOptions;
         private readonly JsonSerializerOptions _jsonOptions;
 
 
+        /// <summary>Initializes a new instance of the <see cref="OmdbProvider"/> class.</summary>
+        /// <param name="httpClientFactory">HttpClientFactory to use for calls to OMDB service.</param>
+        /// <param name="fileSystem">IFileSystem to use for store OMDB data.</param>
+        /// <param name="appHost">IApplicationHost to use.</param>
+        /// <param name="configurationManager">IServerConfigurationManager to use.</param>
         public OmdbProvider(IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IApplicationHost appHost, IServerConfigurationManager configurationManager)
         public OmdbProvider(IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IApplicationHost appHost, IServerConfigurationManager configurationManager)
         {
         {
             _httpClientFactory = httpClientFactory;
             _httpClientFactory = httpClientFactory;
@@ -41,6 +47,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb
             _jsonOptions.Converters.Add(new JsonOmdbNotAvailableInt32Converter());
             _jsonOptions.Converters.Add(new JsonOmdbNotAvailableInt32Converter());
         }
         }
 
 
+        /// <summary>Fetches data from OMDB service.</summary>
+        /// <param name="itemResult">Metadata about media item.</param>
+        /// <param name="imdbId">IMDB ID for media.</param>
+        /// <param name="language">Media language.</param>
+        /// <param name="country">Country of origin.</param>
+        /// <param name="cancellationToken">CancellationToken to use for operation.</param>
+        /// <typeparam name="T">The first generic type parameter.</typeparam>
+        /// <returns>Returns a Task object that can be awaited.</returns>
         public async Task Fetch<T>(MetadataResult<T> itemResult, string imdbId, string language, string country, CancellationToken cancellationToken)
         public async Task Fetch<T>(MetadataResult<T> itemResult, string imdbId, string language, string country, CancellationToken cancellationToken)
             where T : BaseItem
             where T : BaseItem
         {
         {
@@ -105,6 +119,17 @@ namespace MediaBrowser.Providers.Plugins.Omdb
             ParseAdditionalMetadata(itemResult, result);
             ParseAdditionalMetadata(itemResult, result);
         }
         }
 
 
+        /// <summary>Gets data about an episode.</summary>
+        /// <param name="itemResult">Metadata about episode.</param>
+        /// <param name="episodeNumber">Episode number.</param>
+        /// <param name="seasonNumber">Season number.</param>
+        /// <param name="episodeImdbId">Episode ID.</param>
+        /// <param name="seriesImdbId">Season ID.</param>
+        /// <param name="language">Episode language.</param>
+        /// <param name="country">Country of origin.</param>
+        /// <param name="cancellationToken">CancellationToken to use for operation.</param>
+        /// <typeparam name="T">The first generic type parameter.</typeparam>
+        /// <returns>Whether operation was successful.</returns>
         public async Task<bool> FetchEpisodeData<T>(MetadataResult<T> itemResult, int episodeNumber, int seasonNumber, string episodeImdbId, string seriesImdbId, string language, string country, CancellationToken cancellationToken)
         public async Task<bool> FetchEpisodeData<T>(MetadataResult<T> itemResult, int episodeNumber, int seasonNumber, string episodeImdbId, string seriesImdbId, string language, string country, CancellationToken cancellationToken)
             where T : BaseItem
             where T : BaseItem
         {
         {
@@ -236,6 +261,9 @@ namespace MediaBrowser.Providers.Plugins.Omdb
             return false;
             return false;
         }
         }
 
 
+        /// <summary>Gets OMDB URL.</summary>
+        /// <param name="query">Appends query string to URL.</param>
+        /// <returns>OMDB URL with optional query string.</returns>
         public static string GetOmdbUrl(string query)
         public static string GetOmdbUrl(string query)
         {
         {
             const string Url = "https://www.omdbapi.com?apikey=2c9d9507";
             const string Url = "https://www.omdbapi.com?apikey=2c9d9507";
@@ -327,6 +355,12 @@ namespace MediaBrowser.Providers.Plugins.Omdb
             return path;
             return path;
         }
         }
 
 
+        /// <summary>Gets response from OMDB service as type T.</summary>
+        /// <param name="httpClient">HttpClient instance to use for service call.</param>
+        /// <param name="url">Http URL to use for service call.</param>
+        /// <param name="cancellationToken">CancellationToken to use for service call.</param>
+        /// <typeparam name="T">The first generic type parameter.</typeparam>
+        /// <returns>OMDB service response as type T.</returns>
         public async Task<T> GetDeserializedOmdbResponse<T>(HttpClient httpClient, string url, CancellationToken cancellationToken)
         public async Task<T> GetDeserializedOmdbResponse<T>(HttpClient httpClient, string url, CancellationToken cancellationToken)
         {
         {
             using var response = await GetOmdbResponse(httpClient, url, cancellationToken).ConfigureAwait(false);
             using var response = await GetOmdbResponse(httpClient, url, cancellationToken).ConfigureAwait(false);
@@ -335,6 +369,11 @@ namespace MediaBrowser.Providers.Plugins.Omdb
             return await JsonSerializer.DeserializeAsync<T>(content, _jsonOptions, cancellationToken).ConfigureAwait(false);
             return await JsonSerializer.DeserializeAsync<T>(content, _jsonOptions, cancellationToken).ConfigureAwait(false);
         }
         }
 
 
+        /// <summary>Gets response from OMDB service.</summary>
+        /// <param name="httpClient">HttpClient instance to use for service call.</param>
+        /// <param name="url">Http URL to use for service call.</param>
+        /// <param name="cancellationToken">CancellationToken to use for service call.</param>
+        /// <returns>OMDB service response as HttpResponseMessage.</returns>
         public static Task<HttpResponseMessage> GetOmdbResponse(HttpClient httpClient, string url, CancellationToken cancellationToken)
         public static Task<HttpResponseMessage> GetOmdbResponse(HttpClient httpClient, string url, CancellationToken cancellationToken)
         {
         {
             return httpClient.GetAsync(url, cancellationToken);
             return httpClient.GetAsync(url, cancellationToken);
@@ -538,10 +577,13 @@ namespace MediaBrowser.Providers.Plugins.Omdb
             }
             }
         }
         }
 
 
+        /// <summary>Describes OMDB rating.</summary>
         public class OmdbRating
         public class OmdbRating
         {
         {
+            /// <summary>Gets or sets rating source.</summary>
             public string Source { get; set; }
             public string Source { get; set; }
 
 
+            /// <summary>Gets or sets rating value.</summary>
             public string Value { get; set; }
             public string Value { get; set; }
         }
         }
     }
     }

+ 2 - 1
MediaBrowser.Providers/Studios/StudioMetadataService.cs

@@ -17,7 +17,8 @@ namespace MediaBrowser.Providers.Studios
             IServerConfigurationManager serverConfigurationManager,
             IServerConfigurationManager serverConfigurationManager,
             ILogger<StudioMetadataService> logger,
             ILogger<StudioMetadataService> logger,
             IProviderManager providerManager,
             IProviderManager providerManager,
-            IFileSystem fileSystem, ILibraryManager libraryManager)
+            IFileSystem fileSystem,
+            ILibraryManager libraryManager)
             : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager)
             : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager)
         {
         {
         }
         }