From fb92eab69b419ce13742c8ae4c43cd47ae398db4 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Thu, 22 Jul 2021 17:33:19 -0700 Subject: Fix analysis issues --- .../Manager/ItemImageProvider.cs | 1 + MediaBrowser.Providers/Manager/MetadataService.cs | 5 + .../MediaBrowser.Providers.csproj | 2 +- .../Plugins/AudioDb/AlbumImageProvider.cs | 110 ------- .../Plugins/AudioDb/AlbumProvider.cs | 289 ----------------- .../Plugins/AudioDb/ArtistImageProvider.cs | 151 --------- .../Plugins/AudioDb/ArtistProvider.cs | 282 ---------------- .../Plugins/AudioDb/AudioDbAlbumImageProvider.cs | 110 +++++++ .../Plugins/AudioDb/AudioDbAlbumProvider.cs | 291 +++++++++++++++++ .../Plugins/AudioDb/AudioDbArtistImageProvider.cs | 151 +++++++++ .../Plugins/AudioDb/AudioDbArtistProvider.cs | 282 ++++++++++++++++ .../Plugins/MusicBrainz/ArtistProvider.cs | 272 ---------------- .../Plugins/MusicBrainz/ExternalIds.cs | 119 ------- .../MusicBrainzAlbumArtistExternalId.cs | 28 ++ .../MusicBrainz/MusicBrainzAlbumExternalId.cs | 28 ++ .../MusicBrainz/MusicBrainzAlbumProvider.cs | 358 +++++++++++---------- .../MusicBrainz/MusicBrainzArtistExternalId.cs | 28 ++ .../MusicBrainz/MusicBrainzArtistProvider.cs | 272 ++++++++++++++++ .../MusicBrainzOtherArtistExternalId.cs | 28 ++ .../MusicBrainzReleaseGroupExternalId.cs | 28 ++ .../Plugins/MusicBrainz/MusicBrainzTrackId.cs | 28 ++ .../Plugins/MusicBrainz/Plugin.cs | 8 +- .../Plugins/Omdb/OmdbItemProvider.cs | 2 +- .../Plugins/Omdb/OmdbProvider.cs | 44 ++- .../Studios/StudioMetadataService.cs | 3 +- 25 files changed, 1512 insertions(+), 1408 deletions(-) delete mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs delete mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs delete mode 100644 MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs delete mode 100644 MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs delete mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs delete mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 416723d49..fd6d7937b 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -536,6 +536,7 @@ namespace MediaBrowser.Providers.Manager return true; } } + // We always want to use prefetched images return false; } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 333f47f87..3a42eb4c1 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -505,6 +505,11 @@ namespace MediaBrowser.Providers.Manager /// /// Gets the providers. /// + /// A media item. + /// The LibraryOptions to use. + /// The MetadataRefreshOptions to use. + /// Specifies first refresh mode. + /// Specifies refresh mode. /// IEnumerable{`0}. protected IEnumerable GetProviders(BaseItem item, LibraryOptions libraryOptions, MetadataRefreshOptions options, bool isFirstRefresh, bool requiresRefresh) { diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index cdb07a15d..c167b3473 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -29,7 +29,7 @@ net5.0 false true - true + true AllEnabledByDefault ../jellyfin.ruleset diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs deleted file mode 100644 index 36d8eeb40..000000000 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs +++ /dev/null @@ -1,110 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Jellyfin.Extensions.Json; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace MediaBrowser.Providers.Plugins.AudioDb -{ - public class AudioDbAlbumImageProvider : IRemoteImageProvider, IHasOrder - { - private readonly IServerConfigurationManager _config; - private readonly IHttpClientFactory _httpClientFactory; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; - - public AudioDbAlbumImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory) - { - _config = config; - _httpClientFactory = httpClientFactory; - } - - /// - public string Name => "TheAudioDB"; - - /// - // After embedded and fanart - public int Order => 2; - - /// - public IEnumerable GetSupportedImages(BaseItem item) - { - return new List - { - ImageType.Primary, - ImageType.Disc - }; - } - - /// - public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) - { - var id = item.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup); - - if (!string.IsNullOrWhiteSpace(id)) - { - await AudioDbAlbumProvider.Current.EnsureInfo(id, cancellationToken).ConfigureAwait(false); - - var path = AudioDbAlbumProvider.GetAlbumInfoPath(_config.ApplicationPaths, id); - - await using FileStream jsonStream = File.OpenRead(path); - var obj = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); - - if (obj != null && obj.album != null && obj.album.Count > 0) - { - return GetImages(obj.album[0]); - } - } - - return new List(); - } - - private IEnumerable GetImages(AudioDbAlbumProvider.Album item) - { - var list = new List(); - - if (!string.IsNullOrWhiteSpace(item.strAlbumThumb)) - { - list.Add(new RemoteImageInfo - { - ProviderName = Name, - Url = item.strAlbumThumb, - Type = ImageType.Primary - }); - } - - if (!string.IsNullOrWhiteSpace(item.strAlbumCDart)) - { - list.Add(new RemoteImageInfo - { - ProviderName = Name, - Url = item.strAlbumCDart, - Type = ImageType.Disc - }); - } - - return list; - } - - /// - public Task GetImageResponse(string url, CancellationToken cancellationToken) - { - var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); - return httpClient.GetAsync(url, cancellationToken); - } - - /// - public bool Supports(BaseItem item) - => item is MusicAlbum; - } -} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs deleted file mode 100644 index 9539c396d..000000000 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs +++ /dev/null @@ -1,289 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using Jellyfin.Extensions.Json; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Providers; -using MediaBrowser.Providers.Music; - -namespace MediaBrowser.Providers.Plugins.AudioDb -{ - public class AudioDbAlbumProvider : IRemoteMetadataProvider, IHasOrder - { - private readonly IServerConfigurationManager _config; - private readonly IFileSystem _fileSystem; - private readonly IHttpClientFactory _httpClientFactory; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; - - public static AudioDbAlbumProvider Current; - - public AudioDbAlbumProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory) - { - _config = config; - _fileSystem = fileSystem; - _httpClientFactory = httpClientFactory; - - Current = this; - } - - /// - public string Name => "TheAudioDB"; - - /// - // After music brainz - public int Order => 1; - - /// - public Task> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken) - => Task.FromResult(Enumerable.Empty()); - - /// - public async Task> GetMetadata(AlbumInfo info, CancellationToken cancellationToken) - { - var result = new MetadataResult(); - var id = info.GetReleaseGroupId(); - - if (!string.IsNullOrWhiteSpace(id)) - { - await EnsureInfo(id, cancellationToken).ConfigureAwait(false); - - var path = GetAlbumInfoPath(_config.ApplicationPaths, id); - - await using FileStream jsonStream = File.OpenRead(path); - var obj = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); - - if (obj != null && obj.album != null && obj.album.Count > 0) - { - result.Item = new MusicAlbum(); - result.HasMetadata = true; - ProcessResult(result.Item, obj.album[0], info.MetadataLanguage); - } - } - - return result; - } - - private void ProcessResult(MusicAlbum item, Album result, string preferredLanguage) - { - if (Plugin.Instance.Configuration.ReplaceAlbumName && !string.IsNullOrWhiteSpace(result.strAlbum)) - { - item.Album = result.strAlbum; - } - - if (!string.IsNullOrWhiteSpace(result.strArtist)) - { - item.AlbumArtists = new string[] { result.strArtist }; - } - - if (!string.IsNullOrEmpty(result.intYearReleased)) - { - item.ProductionYear = int.Parse(result.intYearReleased, CultureInfo.InvariantCulture); - } - - if (!string.IsNullOrEmpty(result.strGenre)) - { - item.Genres = new[] { result.strGenre }; - } - - item.SetProviderId(MetadataProvider.AudioDbArtist, result.idArtist); - item.SetProviderId(MetadataProvider.AudioDbAlbum, result.idAlbum); - - item.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, result.strMusicBrainzArtistID); - item.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, result.strMusicBrainzID); - - string overview = null; - - if (string.Equals(preferredLanguage, "de", StringComparison.OrdinalIgnoreCase)) - { - overview = result.strDescriptionDE; - } - else if (string.Equals(preferredLanguage, "fr", StringComparison.OrdinalIgnoreCase)) - { - overview = result.strDescriptionFR; - } - else if (string.Equals(preferredLanguage, "nl", StringComparison.OrdinalIgnoreCase)) - { - overview = result.strDescriptionNL; - } - else if (string.Equals(preferredLanguage, "ru", StringComparison.OrdinalIgnoreCase)) - { - overview = result.strDescriptionRU; - } - else if (string.Equals(preferredLanguage, "it", StringComparison.OrdinalIgnoreCase)) - { - overview = result.strDescriptionIT; - } - else if ((preferredLanguage ?? string.Empty).StartsWith("pt", StringComparison.OrdinalIgnoreCase)) - { - overview = result.strDescriptionPT; - } - - if (string.IsNullOrWhiteSpace(overview)) - { - overview = result.strDescriptionEN; - } - - item.Overview = (overview ?? string.Empty).StripHtml(); - } - - internal Task EnsureInfo(string musicBrainzReleaseGroupId, CancellationToken cancellationToken) - { - var xmlPath = GetAlbumInfoPath(_config.ApplicationPaths, musicBrainzReleaseGroupId); - - var fileInfo = _fileSystem.GetFileSystemInfo(xmlPath); - - if (fileInfo.Exists) - { - if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) - { - return Task.CompletedTask; - } - } - - return DownloadInfo(musicBrainzReleaseGroupId, cancellationToken); - } - - internal async Task DownloadInfo(string musicBrainzReleaseGroupId, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var url = AudioDbArtistProvider.BaseUrl + "/album-mb.php?i=" + musicBrainzReleaseGroupId; - - var path = GetAlbumInfoPath(_config.ApplicationPaths, musicBrainzReleaseGroupId); - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true); - await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); - } - - private static string GetAlbumDataPath(IApplicationPaths appPaths, string musicBrainzReleaseGroupId) - { - var dataPath = Path.Combine(GetAlbumDataPath(appPaths), musicBrainzReleaseGroupId); - - return dataPath; - } - - private static string GetAlbumDataPath(IApplicationPaths appPaths) - { - var dataPath = Path.Combine(appPaths.CachePath, "audiodb-album"); - - return dataPath; - } - - internal static string GetAlbumInfoPath(IApplicationPaths appPaths, string musicBrainzReleaseGroupId) - { - var dataPath = GetAlbumDataPath(appPaths, musicBrainzReleaseGroupId); - - return Path.Combine(dataPath, "album.json"); - } - - public class Album - { - public string idAlbum { get; set; } - - public string idArtist { get; set; } - - public string strAlbum { get; set; } - - public string strArtist { get; set; } - - public string intYearReleased { get; set; } - - public string strGenre { get; set; } - - public string strSubGenre { get; set; } - - public string strReleaseFormat { get; set; } - - public string intSales { get; set; } - - public string strAlbumThumb { get; set; } - - public string strAlbumCDart { get; set; } - - public string strDescriptionEN { get; set; } - - public string strDescriptionDE { get; set; } - - public string strDescriptionFR { get; set; } - - public string strDescriptionCN { get; set; } - - public string strDescriptionIT { get; set; } - - public string strDescriptionJP { get; set; } - - public string strDescriptionRU { get; set; } - - public string strDescriptionES { get; set; } - - public string strDescriptionPT { get; set; } - - public string strDescriptionSE { get; set; } - - public string strDescriptionNL { get; set; } - - public string strDescriptionHU { get; set; } - - public string strDescriptionNO { get; set; } - - public string strDescriptionIL { get; set; } - - public string strDescriptionPL { get; set; } - - public object intLoved { get; set; } - - public object intScore { get; set; } - - public string strReview { get; set; } - - public object strMood { get; set; } - - public object strTheme { get; set; } - - public object strSpeed { get; set; } - - public object strLocation { get; set; } - - public string strMusicBrainzID { get; set; } - - public string strMusicBrainzArtistID { get; set; } - - public object strItunesID { get; set; } - - public object strAmazonID { get; set; } - - public string strLocked { get; set; } - } - - public class RootObject - { - public List album { get; set; } - } - - /// - public Task GetImageResponse(string url, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - } -} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs deleted file mode 100644 index aa61a56f6..000000000 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs +++ /dev/null @@ -1,151 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Jellyfin.Extensions.Json; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace MediaBrowser.Providers.Plugins.AudioDb -{ - public class AudioDbArtistImageProvider : IRemoteImageProvider, IHasOrder - { - private readonly IServerConfigurationManager _config; - private readonly IHttpClientFactory _httpClientFactory; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; - - public AudioDbArtistImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory) - { - _config = config; - _httpClientFactory = httpClientFactory; - } - - /// - public string Name => "TheAudioDB"; - - /// - // After fanart - public int Order => 1; - - /// - public IEnumerable GetSupportedImages(BaseItem item) - { - return new List - { - ImageType.Primary, - ImageType.Logo, - ImageType.Banner, - ImageType.Backdrop - }; - } - - /// - public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) - { - var id = item.GetProviderId(MetadataProvider.MusicBrainzArtist); - - if (!string.IsNullOrWhiteSpace(id)) - { - await AudioDbArtistProvider.Current.EnsureArtistInfo(id, cancellationToken).ConfigureAwait(false); - - var path = AudioDbArtistProvider.GetArtistInfoPath(_config.ApplicationPaths, id); - - await using FileStream jsonStream = File.OpenRead(path); - var obj = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); - - if (obj != null && obj.artists != null && obj.artists.Count > 0) - { - return GetImages(obj.artists[0]); - } - } - - return new List(); - } - - private IEnumerable GetImages(AudioDbArtistProvider.Artist item) - { - var list = new List(); - - if (!string.IsNullOrWhiteSpace(item.strArtistThumb)) - { - list.Add(new RemoteImageInfo - { - ProviderName = Name, - Url = item.strArtistThumb, - Type = ImageType.Primary - }); - } - - if (!string.IsNullOrWhiteSpace(item.strArtistLogo)) - { - list.Add(new RemoteImageInfo - { - ProviderName = Name, - Url = item.strArtistLogo, - Type = ImageType.Logo - }); - } - - if (!string.IsNullOrWhiteSpace(item.strArtistBanner)) - { - list.Add(new RemoteImageInfo - { - ProviderName = Name, - Url = item.strArtistBanner, - Type = ImageType.Banner - }); - } - - if (!string.IsNullOrWhiteSpace(item.strArtistFanart)) - { - list.Add(new RemoteImageInfo - { - ProviderName = Name, - Url = item.strArtistFanart, - Type = ImageType.Backdrop - }); - } - - if (!string.IsNullOrWhiteSpace(item.strArtistFanart2)) - { - list.Add(new RemoteImageInfo - { - ProviderName = Name, - Url = item.strArtistFanart2, - Type = ImageType.Backdrop - }); - } - - if (!string.IsNullOrWhiteSpace(item.strArtistFanart3)) - { - list.Add(new RemoteImageInfo - { - ProviderName = Name, - Url = item.strArtistFanart3, - Type = ImageType.Backdrop - }); - } - - return list; - } - - public Task GetImageResponse(string url, CancellationToken cancellationToken) - { - var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); - return httpClient.GetAsync(url, cancellationToken); - } - - /// - public bool Supports(BaseItem item) - => item is MusicArtist; - } -} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs deleted file mode 100644 index b2f05d76d..000000000 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs +++ /dev/null @@ -1,282 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using Jellyfin.Extensions.Json; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Providers; -using MediaBrowser.Providers.Music; - -namespace MediaBrowser.Providers.Plugins.AudioDb -{ - public class AudioDbArtistProvider : IRemoteMetadataProvider, IHasOrder - { - private const string ApiKey = "195003"; - public const string BaseUrl = "https://www.theaudiodb.com/api/v1/json/" + ApiKey; - - private readonly IServerConfigurationManager _config; - private readonly IFileSystem _fileSystem; - private readonly IHttpClientFactory _httpClientFactory; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; - - public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory) - { - _config = config; - _fileSystem = fileSystem; - _httpClientFactory = httpClientFactory; - Current = this; - } - - public static AudioDbArtistProvider Current { get; private set; } - - /// - public string Name => "TheAudioDB"; - - /// - // After musicbrainz - public int Order => 1; - - /// - public Task> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken) - => Task.FromResult(Enumerable.Empty()); - - /// - public async Task> GetMetadata(ArtistInfo info, CancellationToken cancellationToken) - { - var result = new MetadataResult(); - var id = info.GetMusicBrainzArtistId(); - - if (!string.IsNullOrWhiteSpace(id)) - { - await EnsureArtistInfo(id, cancellationToken).ConfigureAwait(false); - - var path = GetArtistInfoPath(_config.ApplicationPaths, id); - - await using FileStream jsonStream = File.OpenRead(path); - var obj = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); - - if (obj != null && obj.artists != null && obj.artists.Count > 0) - { - result.Item = new MusicArtist(); - result.HasMetadata = true; - ProcessResult(result.Item, obj.artists[0], info.MetadataLanguage); - } - } - - return result; - } - - private void ProcessResult(MusicArtist item, Artist result, string preferredLanguage) - { - // item.HomePageUrl = result.strWebsite; - - if (!string.IsNullOrEmpty(result.strGenre)) - { - item.Genres = new[] { result.strGenre }; - } - - item.SetProviderId(MetadataProvider.AudioDbArtist, result.idArtist); - item.SetProviderId(MetadataProvider.MusicBrainzArtist, result.strMusicBrainzID); - - string overview = null; - - if (string.Equals(preferredLanguage, "de", StringComparison.OrdinalIgnoreCase)) - { - overview = result.strBiographyDE; - } - else if (string.Equals(preferredLanguage, "fr", StringComparison.OrdinalIgnoreCase)) - { - overview = result.strBiographyFR; - } - else if (string.Equals(preferredLanguage, "nl", StringComparison.OrdinalIgnoreCase)) - { - overview = result.strBiographyNL; - } - else if (string.Equals(preferredLanguage, "ru", StringComparison.OrdinalIgnoreCase)) - { - overview = result.strBiographyRU; - } - else if (string.Equals(preferredLanguage, "it", StringComparison.OrdinalIgnoreCase)) - { - overview = result.strBiographyIT; - } - else if ((preferredLanguage ?? string.Empty).StartsWith("pt", StringComparison.OrdinalIgnoreCase)) - { - overview = result.strBiographyPT; - } - - if (string.IsNullOrWhiteSpace(overview)) - { - overview = result.strBiographyEN; - } - - item.Overview = (overview ?? string.Empty).StripHtml(); - } - - internal Task EnsureArtistInfo(string musicBrainzId, CancellationToken cancellationToken) - { - var xmlPath = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId); - - var fileInfo = _fileSystem.GetFileSystemInfo(xmlPath); - - if (fileInfo.Exists - && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) - { - return Task.CompletedTask; - } - - return DownloadArtistInfo(musicBrainzId, cancellationToken); - } - - internal async Task DownloadArtistInfo(string musicBrainzId, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var url = BaseUrl + "/artist-mb.php?i=" + musicBrainzId; - - var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId); - - using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true); - await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); - } - - /// - /// Gets the artist data path. - /// - /// The application paths. - /// The music brainz artist identifier. - /// System.String. - private static string GetArtistDataPath(IApplicationPaths appPaths, string musicBrainzArtistId) - => Path.Combine(GetArtistDataPath(appPaths), musicBrainzArtistId); - - /// - /// Gets the artist data path. - /// - /// The application paths. - /// System.String. - private static string GetArtistDataPath(IApplicationPaths appPaths) - => Path.Combine(appPaths.CachePath, "audiodb-artist"); - - internal static string GetArtistInfoPath(IApplicationPaths appPaths, string musicBrainzArtistId) - { - var dataPath = GetArtistDataPath(appPaths, musicBrainzArtistId); - - return Path.Combine(dataPath, "artist.json"); - } - - public class Artist - { - public string idArtist { get; set; } - - public string strArtist { get; set; } - - public string strArtistAlternate { get; set; } - - public object idLabel { get; set; } - - public string intFormedYear { get; set; } - - public string intBornYear { get; set; } - - public object intDiedYear { get; set; } - - public object strDisbanded { get; set; } - - public string strGenre { get; set; } - - public string strSubGenre { get; set; } - - public string strWebsite { get; set; } - - public string strFacebook { get; set; } - - public string strTwitter { get; set; } - - public string strBiographyEN { get; set; } - - public string strBiographyDE { get; set; } - - public string strBiographyFR { get; set; } - - public string strBiographyCN { get; set; } - - public string strBiographyIT { get; set; } - - public string strBiographyJP { get; set; } - - public string strBiographyRU { get; set; } - - public string strBiographyES { get; set; } - - public string strBiographyPT { get; set; } - - public string strBiographySE { get; set; } - - public string strBiographyNL { get; set; } - - public string strBiographyHU { get; set; } - - public string strBiographyNO { get; set; } - - public string strBiographyIL { get; set; } - - public string strBiographyPL { get; set; } - - public string strGender { get; set; } - - public string intMembers { get; set; } - - public string strCountry { get; set; } - - public string strCountryCode { get; set; } - - public string strArtistThumb { get; set; } - - public string strArtistLogo { get; set; } - - public string strArtistFanart { get; set; } - - public string strArtistFanart2 { get; set; } - - public string strArtistFanart3 { get; set; } - - public string strArtistBanner { get; set; } - - public string strMusicBrainzID { get; set; } - - public object strLastFMChart { get; set; } - - public string strLocked { get; set; } - } - - public class RootObject - { - public List artists { get; set; } - } - - /// - public Task GetImageResponse(string url, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - } -} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs new file mode 100644 index 000000000..36d8eeb40 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs @@ -0,0 +1,110 @@ +#pragma warning disable CS1591 + +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Extensions.Json; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.AudioDb +{ + public class AudioDbAlbumImageProvider : IRemoteImageProvider, IHasOrder + { + private readonly IServerConfigurationManager _config; + private readonly IHttpClientFactory _httpClientFactory; + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + + public AudioDbAlbumImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory) + { + _config = config; + _httpClientFactory = httpClientFactory; + } + + /// + public string Name => "TheAudioDB"; + + /// + // After embedded and fanart + public int Order => 2; + + /// + public IEnumerable GetSupportedImages(BaseItem item) + { + return new List + { + ImageType.Primary, + ImageType.Disc + }; + } + + /// + public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) + { + var id = item.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup); + + if (!string.IsNullOrWhiteSpace(id)) + { + await AudioDbAlbumProvider.Current.EnsureInfo(id, cancellationToken).ConfigureAwait(false); + + var path = AudioDbAlbumProvider.GetAlbumInfoPath(_config.ApplicationPaths, id); + + await using FileStream jsonStream = File.OpenRead(path); + var obj = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + + if (obj != null && obj.album != null && obj.album.Count > 0) + { + return GetImages(obj.album[0]); + } + } + + return new List(); + } + + private IEnumerable GetImages(AudioDbAlbumProvider.Album item) + { + var list = new List(); + + if (!string.IsNullOrWhiteSpace(item.strAlbumThumb)) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = item.strAlbumThumb, + Type = ImageType.Primary + }); + } + + if (!string.IsNullOrWhiteSpace(item.strAlbumCDart)) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = item.strAlbumCDart, + Type = ImageType.Disc + }); + } + + return list; + } + + /// + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); + return httpClient.GetAsync(url, cancellationToken); + } + + /// + public bool Supports(BaseItem item) + => item is MusicAlbum; + } +} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs new file mode 100644 index 000000000..ccf9501be --- /dev/null +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs @@ -0,0 +1,291 @@ +#pragma warning disable CS1591, SA1300 + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Extensions.Json; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Music; + +namespace MediaBrowser.Providers.Plugins.AudioDb +{ + public class AudioDbAlbumProvider : IRemoteMetadataProvider, IHasOrder + { + private readonly IServerConfigurationManager _config; + private readonly IFileSystem _fileSystem; + private readonly IHttpClientFactory _httpClientFactory; + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + +#pragma warning disable SA1401 + public static AudioDbAlbumProvider Current; +#pragma warning restore SA1401 + + public AudioDbAlbumProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory) + { + _config = config; + _fileSystem = fileSystem; + _httpClientFactory = httpClientFactory; + + Current = this; + } + + /// + public string Name => "TheAudioDB"; + + /// + // After music brainz + public int Order => 1; + + /// + public Task> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken) + => Task.FromResult(Enumerable.Empty()); + + /// + public async Task> GetMetadata(AlbumInfo info, CancellationToken cancellationToken) + { + var result = new MetadataResult(); + var id = info.GetReleaseGroupId(); + + if (!string.IsNullOrWhiteSpace(id)) + { + await EnsureInfo(id, cancellationToken).ConfigureAwait(false); + + var path = GetAlbumInfoPath(_config.ApplicationPaths, id); + + await using FileStream jsonStream = File.OpenRead(path); + var obj = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + + if (obj != null && obj.album != null && obj.album.Count > 0) + { + result.Item = new MusicAlbum(); + result.HasMetadata = true; + ProcessResult(result.Item, obj.album[0], info.MetadataLanguage); + } + } + + return result; + } + + private void ProcessResult(MusicAlbum item, Album result, string preferredLanguage) + { + if (Plugin.Instance.Configuration.ReplaceAlbumName && !string.IsNullOrWhiteSpace(result.strAlbum)) + { + item.Album = result.strAlbum; + } + + if (!string.IsNullOrWhiteSpace(result.strArtist)) + { + item.AlbumArtists = new string[] { result.strArtist }; + } + + if (!string.IsNullOrEmpty(result.intYearReleased)) + { + item.ProductionYear = int.Parse(result.intYearReleased, CultureInfo.InvariantCulture); + } + + if (!string.IsNullOrEmpty(result.strGenre)) + { + item.Genres = new[] { result.strGenre }; + } + + item.SetProviderId(MetadataProvider.AudioDbArtist, result.idArtist); + item.SetProviderId(MetadataProvider.AudioDbAlbum, result.idAlbum); + + item.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, result.strMusicBrainzArtistID); + item.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, result.strMusicBrainzID); + + string overview = null; + + if (string.Equals(preferredLanguage, "de", StringComparison.OrdinalIgnoreCase)) + { + overview = result.strDescriptionDE; + } + else if (string.Equals(preferredLanguage, "fr", StringComparison.OrdinalIgnoreCase)) + { + overview = result.strDescriptionFR; + } + else if (string.Equals(preferredLanguage, "nl", StringComparison.OrdinalIgnoreCase)) + { + overview = result.strDescriptionNL; + } + else if (string.Equals(preferredLanguage, "ru", StringComparison.OrdinalIgnoreCase)) + { + overview = result.strDescriptionRU; + } + else if (string.Equals(preferredLanguage, "it", StringComparison.OrdinalIgnoreCase)) + { + overview = result.strDescriptionIT; + } + else if ((preferredLanguage ?? string.Empty).StartsWith("pt", StringComparison.OrdinalIgnoreCase)) + { + overview = result.strDescriptionPT; + } + + if (string.IsNullOrWhiteSpace(overview)) + { + overview = result.strDescriptionEN; + } + + item.Overview = (overview ?? string.Empty).StripHtml(); + } + + internal Task EnsureInfo(string musicBrainzReleaseGroupId, CancellationToken cancellationToken) + { + var xmlPath = GetAlbumInfoPath(_config.ApplicationPaths, musicBrainzReleaseGroupId); + + var fileInfo = _fileSystem.GetFileSystemInfo(xmlPath); + + if (fileInfo.Exists) + { + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) + { + return Task.CompletedTask; + } + } + + return DownloadInfo(musicBrainzReleaseGroupId, cancellationToken); + } + + internal async Task DownloadInfo(string musicBrainzReleaseGroupId, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var url = AudioDbArtistProvider.BaseUrl + "/album-mb.php?i=" + musicBrainzReleaseGroupId; + + var path = GetAlbumInfoPath(_config.ApplicationPaths, musicBrainzReleaseGroupId); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true); + await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); + } + + private static string GetAlbumDataPath(IApplicationPaths appPaths, string musicBrainzReleaseGroupId) + { + var dataPath = Path.Combine(GetAlbumDataPath(appPaths), musicBrainzReleaseGroupId); + + return dataPath; + } + + private static string GetAlbumDataPath(IApplicationPaths appPaths) + { + var dataPath = Path.Combine(appPaths.CachePath, "audiodb-album"); + + return dataPath; + } + + internal static string GetAlbumInfoPath(IApplicationPaths appPaths, string musicBrainzReleaseGroupId) + { + var dataPath = GetAlbumDataPath(appPaths, musicBrainzReleaseGroupId); + + return Path.Combine(dataPath, "album.json"); + } + + /// + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public class Album + { + public string idAlbum { get; set; } + + public string idArtist { get; set; } + + public string strAlbum { get; set; } + + public string strArtist { get; set; } + + public string intYearReleased { get; set; } + + public string strGenre { get; set; } + + public string strSubGenre { get; set; } + + public string strReleaseFormat { get; set; } + + public string intSales { get; set; } + + public string strAlbumThumb { get; set; } + + public string strAlbumCDart { get; set; } + + public string strDescriptionEN { get; set; } + + public string strDescriptionDE { get; set; } + + public string strDescriptionFR { get; set; } + + public string strDescriptionCN { get; set; } + + public string strDescriptionIT { get; set; } + + public string strDescriptionJP { get; set; } + + public string strDescriptionRU { get; set; } + + public string strDescriptionES { get; set; } + + public string strDescriptionPT { get; set; } + + public string strDescriptionSE { get; set; } + + public string strDescriptionNL { get; set; } + + public string strDescriptionHU { get; set; } + + public string strDescriptionNO { get; set; } + + public string strDescriptionIL { get; set; } + + public string strDescriptionPL { get; set; } + + public object intLoved { get; set; } + + public object intScore { get; set; } + + public string strReview { get; set; } + + public object strMood { get; set; } + + public object strTheme { get; set; } + + public object strSpeed { get; set; } + + public object strLocation { get; set; } + + public string strMusicBrainzID { get; set; } + + public string strMusicBrainzArtistID { get; set; } + + public object strItunesID { get; set; } + + public object strAmazonID { get; set; } + + public string strLocked { get; set; } + } + + public class RootObject + { + public List album { get; set; } + } + } +} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs new file mode 100644 index 000000000..aa61a56f6 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs @@ -0,0 +1,151 @@ +#pragma warning disable CS1591 + +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Extensions.Json; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.AudioDb +{ + public class AudioDbArtistImageProvider : IRemoteImageProvider, IHasOrder + { + private readonly IServerConfigurationManager _config; + private readonly IHttpClientFactory _httpClientFactory; + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + + public AudioDbArtistImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory) + { + _config = config; + _httpClientFactory = httpClientFactory; + } + + /// + public string Name => "TheAudioDB"; + + /// + // After fanart + public int Order => 1; + + /// + public IEnumerable GetSupportedImages(BaseItem item) + { + return new List + { + ImageType.Primary, + ImageType.Logo, + ImageType.Banner, + ImageType.Backdrop + }; + } + + /// + public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) + { + var id = item.GetProviderId(MetadataProvider.MusicBrainzArtist); + + if (!string.IsNullOrWhiteSpace(id)) + { + await AudioDbArtistProvider.Current.EnsureArtistInfo(id, cancellationToken).ConfigureAwait(false); + + var path = AudioDbArtistProvider.GetArtistInfoPath(_config.ApplicationPaths, id); + + await using FileStream jsonStream = File.OpenRead(path); + var obj = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + + if (obj != null && obj.artists != null && obj.artists.Count > 0) + { + return GetImages(obj.artists[0]); + } + } + + return new List(); + } + + private IEnumerable GetImages(AudioDbArtistProvider.Artist item) + { + var list = new List(); + + if (!string.IsNullOrWhiteSpace(item.strArtistThumb)) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = item.strArtistThumb, + Type = ImageType.Primary + }); + } + + if (!string.IsNullOrWhiteSpace(item.strArtistLogo)) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = item.strArtistLogo, + Type = ImageType.Logo + }); + } + + if (!string.IsNullOrWhiteSpace(item.strArtistBanner)) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = item.strArtistBanner, + Type = ImageType.Banner + }); + } + + if (!string.IsNullOrWhiteSpace(item.strArtistFanart)) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = item.strArtistFanart, + Type = ImageType.Backdrop + }); + } + + if (!string.IsNullOrWhiteSpace(item.strArtistFanart2)) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = item.strArtistFanart2, + Type = ImageType.Backdrop + }); + } + + if (!string.IsNullOrWhiteSpace(item.strArtistFanart3)) + { + list.Add(new RemoteImageInfo + { + ProviderName = Name, + Url = item.strArtistFanart3, + Type = ImageType.Backdrop + }); + } + + return list; + } + + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); + return httpClient.GetAsync(url, cancellationToken); + } + + /// + public bool Supports(BaseItem item) + => item is MusicArtist; + } +} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs new file mode 100644 index 000000000..c11e7a7ff --- /dev/null +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs @@ -0,0 +1,282 @@ +#pragma warning disable CS1591, SA1300 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Extensions.Json; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Music; + +namespace MediaBrowser.Providers.Plugins.AudioDb +{ + public class AudioDbArtistProvider : IRemoteMetadataProvider, IHasOrder + { + private const string ApiKey = "195003"; + public const string BaseUrl = "https://www.theaudiodb.com/api/v1/json/" + ApiKey; + + private readonly IServerConfigurationManager _config; + private readonly IFileSystem _fileSystem; + private readonly IHttpClientFactory _httpClientFactory; + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; + + public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory) + { + _config = config; + _fileSystem = fileSystem; + _httpClientFactory = httpClientFactory; + Current = this; + } + + public static AudioDbArtistProvider Current { get; private set; } + + /// + public string Name => "TheAudioDB"; + + /// + // After musicbrainz + public int Order => 1; + + /// + public Task> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken) + => Task.FromResult(Enumerable.Empty()); + + /// + public async Task> GetMetadata(ArtistInfo info, CancellationToken cancellationToken) + { + var result = new MetadataResult(); + var id = info.GetMusicBrainzArtistId(); + + if (!string.IsNullOrWhiteSpace(id)) + { + await EnsureArtistInfo(id, cancellationToken).ConfigureAwait(false); + + var path = GetArtistInfoPath(_config.ApplicationPaths, id); + + await using FileStream jsonStream = File.OpenRead(path); + var obj = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + + if (obj != null && obj.artists != null && obj.artists.Count > 0) + { + result.Item = new MusicArtist(); + result.HasMetadata = true; + ProcessResult(result.Item, obj.artists[0], info.MetadataLanguage); + } + } + + return result; + } + + private void ProcessResult(MusicArtist item, Artist result, string preferredLanguage) + { + // item.HomePageUrl = result.strWebsite; + + if (!string.IsNullOrEmpty(result.strGenre)) + { + item.Genres = new[] { result.strGenre }; + } + + item.SetProviderId(MetadataProvider.AudioDbArtist, result.idArtist); + item.SetProviderId(MetadataProvider.MusicBrainzArtist, result.strMusicBrainzID); + + string overview = null; + + if (string.Equals(preferredLanguage, "de", StringComparison.OrdinalIgnoreCase)) + { + overview = result.strBiographyDE; + } + else if (string.Equals(preferredLanguage, "fr", StringComparison.OrdinalIgnoreCase)) + { + overview = result.strBiographyFR; + } + else if (string.Equals(preferredLanguage, "nl", StringComparison.OrdinalIgnoreCase)) + { + overview = result.strBiographyNL; + } + else if (string.Equals(preferredLanguage, "ru", StringComparison.OrdinalIgnoreCase)) + { + overview = result.strBiographyRU; + } + else if (string.Equals(preferredLanguage, "it", StringComparison.OrdinalIgnoreCase)) + { + overview = result.strBiographyIT; + } + else if ((preferredLanguage ?? string.Empty).StartsWith("pt", StringComparison.OrdinalIgnoreCase)) + { + overview = result.strBiographyPT; + } + + if (string.IsNullOrWhiteSpace(overview)) + { + overview = result.strBiographyEN; + } + + item.Overview = (overview ?? string.Empty).StripHtml(); + } + + internal Task EnsureArtistInfo(string musicBrainzId, CancellationToken cancellationToken) + { + var xmlPath = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId); + + var fileInfo = _fileSystem.GetFileSystemInfo(xmlPath); + + if (fileInfo.Exists + && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) + { + return Task.CompletedTask; + } + + return DownloadArtistInfo(musicBrainzId, cancellationToken); + } + + internal async Task DownloadArtistInfo(string musicBrainzId, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var url = BaseUrl + "/artist-mb.php?i=" + musicBrainzId; + + var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId); + + using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true); + await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); + } + + /// + /// Gets the artist data path. + /// + /// The application paths. + /// The music brainz artist identifier. + /// System.String. + private static string GetArtistDataPath(IApplicationPaths appPaths, string musicBrainzArtistId) + => Path.Combine(GetArtistDataPath(appPaths), musicBrainzArtistId); + + /// + /// Gets the artist data path. + /// + /// The application paths. + /// System.String. + private static string GetArtistDataPath(IApplicationPaths appPaths) + => Path.Combine(appPaths.CachePath, "audiodb-artist"); + + internal static string GetArtistInfoPath(IApplicationPaths appPaths, string musicBrainzArtistId) + { + var dataPath = GetArtistDataPath(appPaths, musicBrainzArtistId); + + return Path.Combine(dataPath, "artist.json"); + } + + /// + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public class Artist + { + public string idArtist { get; set; } + + public string strArtist { get; set; } + + public string strArtistAlternate { get; set; } + + public object idLabel { get; set; } + + public string intFormedYear { get; set; } + + public string intBornYear { get; set; } + + public object intDiedYear { get; set; } + + public object strDisbanded { get; set; } + + public string strGenre { get; set; } + + public string strSubGenre { get; set; } + + public string strWebsite { get; set; } + + public string strFacebook { get; set; } + + public string strTwitter { get; set; } + + public string strBiographyEN { get; set; } + + public string strBiographyDE { get; set; } + + public string strBiographyFR { get; set; } + + public string strBiographyCN { get; set; } + + public string strBiographyIT { get; set; } + + public string strBiographyJP { get; set; } + + public string strBiographyRU { get; set; } + + public string strBiographyES { get; set; } + + public string strBiographyPT { get; set; } + + public string strBiographySE { get; set; } + + public string strBiographyNL { get; set; } + + public string strBiographyHU { get; set; } + + public string strBiographyNO { get; set; } + + public string strBiographyIL { get; set; } + + public string strBiographyPL { get; set; } + + public string strGender { get; set; } + + public string intMembers { get; set; } + + public string strCountry { get; set; } + + public string strCountryCode { get; set; } + + public string strArtistThumb { get; set; } + + public string strArtistLogo { get; set; } + + public string strArtistFanart { get; set; } + + public string strArtistFanart2 { get; set; } + + public string strArtistFanart3 { get; set; } + + public string strArtistBanner { get; set; } + + public string strMusicBrainzID { get; set; } + + public object strLastFMChart { get; set; } + + public string strLocked { get; set; } + } + + public class RootObject + { + public List artists { get; set; } + } + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs deleted file mode 100644 index 7a9379af7..000000000 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs +++ /dev/null @@ -1,272 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using Diacritics.Extensions; -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 MusicBrainzArtistProvider : IRemoteMetadataProvider - { - /// - public async Task> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken) - { - var musicBrainzId = searchInfo.GetMusicBrainzArtistId(); - - if (!string.IsNullOrWhiteSpace(musicBrainzId)) - { - var url = "/ws/2/artist/?query=arid:{0}" + musicBrainzId.ToString(CultureInfo.InvariantCulture); - - using var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - return GetResultsFromResponse(stream); - } - else - { - // They seem to throw bad request failures on any term with a slash - var nameToSearch = searchInfo.Name.Replace('/', ' '); - - var url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=\"{0}\"&dismax=true", UrlEncode(nameToSearch)); - - using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) - await using (var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false)) - { - var results = GetResultsFromResponse(stream).ToList(); - - if (results.Count > 0) - { - return results; - } - } - - if (searchInfo.Name.HasDiacritics()) - { - // Try again using the search with accent characters url - url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch)); - - using var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - return GetResultsFromResponse(stream); - } - } - - return Enumerable.Empty(); - } - - private IEnumerable GetResultsFromResponse(Stream stream) - { - using var oReader = new StreamReader(stream, Encoding.UTF8); - var settings = new XmlReaderSettings() - { - ValidationType = ValidationType.None, - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true - }; - - using var reader = XmlReader.Create(oReader, settings); - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "artist-list": - { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - - using var subReader = reader.ReadSubtree(); - return ParseArtistList(subReader).ToList(); - } - - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - - return Enumerable.Empty(); - } - - private IEnumerable ParseArtistList(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 "artist": - { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - - var mbzId = reader.GetAttribute("id"); - - using var subReader = reader.ReadSubtree(); - var artist = ParseArtist(subReader, mbzId); - if (artist != null) - { - yield return artist; - } - - break; - } - - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - } - - private RemoteSearchResult ParseArtist(XmlReader reader, string artistId) - { - var result = new RemoteSearchResult(); - - 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 "name": - { - result.Name = reader.ReadElementContentAsString(); - break; - } - - case "annotation": - { - result.Overview = reader.ReadElementContentAsString(); - break; - } - - default: - { - // there is sort-name if ever needed - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - - result.SetProviderId(MetadataProvider.MusicBrainzArtist, artistId); - - if (string.IsNullOrWhiteSpace(artistId) || string.IsNullOrWhiteSpace(result.Name)) - { - return null; - } - - return result; - } - - /// - public async Task> GetMetadata(ArtistInfo info, CancellationToken cancellationToken) - { - var result = new MetadataResult - { - Item = new MusicArtist() - }; - - var musicBrainzId = info.GetMusicBrainzArtistId(); - - if (string.IsNullOrWhiteSpace(musicBrainzId)) - { - var searchResults = await GetSearchResults(info, cancellationToken).ConfigureAwait(false); - - var singleResult = searchResults.FirstOrDefault(); - - if (singleResult != null) - { - musicBrainzId = singleResult.GetProviderId(MetadataProvider.MusicBrainzArtist); - result.Item.Overview = singleResult.Overview; - - if (Plugin.Instance.Configuration.ReplaceArtistName) - { - result.Item.Name = singleResult.Name; - } - } - } - - if (!string.IsNullOrWhiteSpace(musicBrainzId)) - { - result.HasMetadata = true; - result.Item.SetProviderId(MetadataProvider.MusicBrainzArtist, musicBrainzId); - } - - return result; - } - - /// - /// Encodes an URL. - /// - /// The name. - /// System.String. - private static string UrlEncode(string name) - { - return WebUtility.UrlEncode(name); - } - - public string Name => "MusicBrainz"; - - public Task GetImageResponse(string url, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - } -} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs deleted file mode 100644 index 5600c389c..000000000 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs +++ /dev/null @@ -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 - { - /// - public string ProviderName => "MusicBrainz"; - - /// - public string Key => MetadataProvider.MusicBrainzReleaseGroup.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup; - - /// - public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum; - } - - public class MusicBrainzAlbumArtistExternalId : IExternalId - { - /// - public string ProviderName => "MusicBrainz"; - - /// - public string Key => MetadataProvider.MusicBrainzAlbumArtist.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist; - - /// - public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Audio; - } - - public class MusicBrainzAlbumExternalId : IExternalId - { - /// - public string ProviderName => "MusicBrainz"; - - /// - public string Key => MetadataProvider.MusicBrainzAlbum.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Album; - - /// - public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum; - } - - public class MusicBrainzArtistExternalId : IExternalId - { - /// - public string ProviderName => "MusicBrainz"; - - /// - public string Key => MetadataProvider.MusicBrainzArtist.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Artist; - - /// - public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is MusicArtist; - } - - public class MusicBrainzOtherArtistExternalId : IExternalId - { - /// - public string ProviderName => "MusicBrainz"; - - /// - - public string Key => MetadataProvider.MusicBrainzArtist.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist; - - /// - public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum; - } - - public class MusicBrainzTrackId : IExternalId - { - /// - public string ProviderName => "MusicBrainz"; - - /// - public string Key => MetadataProvider.MusicBrainzTrack.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Track; - - /// - public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Audio; - } -} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs new file mode 100644 index 000000000..1b37e2a60 --- /dev/null +++ b/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 + { + /// + public string ProviderName => "MusicBrainz"; + + /// + public string Key => MetadataProvider.MusicBrainzAlbumArtist.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist; + + /// + public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Audio; + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs new file mode 100644 index 000000000..ef095111a --- /dev/null +++ b/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 + { + /// + public string ProviderName => "MusicBrainz"; + + /// + public string Key => MetadataProvider.MusicBrainzAlbum.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.Album; + + /// + public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum; + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index 8db3c391e..9148e726f 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591, SA1401 using System; using System.Collections.Generic; @@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Music /// The Jellyfin user-agent is unrestricted but source IP must not exceed /// one request per second, therefore we rate limit to avoid throttling. /// 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. /// private readonly long _musicBrainzQueryIntervalMs; @@ -302,181 +302,6 @@ namespace MediaBrowser.Providers.Music 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> Artists = new List>(); - - public static IEnumerable 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(); - } - - private static IEnumerable 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) { reader.MoveToContent(); @@ -496,6 +321,7 @@ namespace MediaBrowser.Providers.Music using var subReader = reader.ReadSubtree(); return ParseArtistNameCredit(subReader); } + default: { 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 /// giving up and returning null. /// + /// Address of MusicBrainz server. + /// CancellationToken to use for method. + /// Returns response from MusicBrainz service. internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken) { await _apiRequestLock.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -762,5 +591,180 @@ namespace MediaBrowser.Providers.Music { throw new NotImplementedException(); } + + private class ReleaseResult + { + public string ReleaseId; + public string ReleaseGroupId; + public string Title; + public string Overview; + public int? Year; + + public List> Artists = new List>(); + + public static IEnumerable 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(); + } + + private static IEnumerable 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; + } + } } } diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs new file mode 100644 index 000000000..d654e1372 --- /dev/null +++ b/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 + { + /// + public string ProviderName => "MusicBrainz"; + + /// + public string Key => MetadataProvider.MusicBrainzArtist.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.Artist; + + /// + public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is MusicArtist; + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs new file mode 100644 index 000000000..7cff5f595 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs @@ -0,0 +1,272 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using Diacritics.Extensions; +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 MusicBrainzArtistProvider : IRemoteMetadataProvider + { + public string Name => "MusicBrainz"; + + /// + public async Task> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken) + { + var musicBrainzId = searchInfo.GetMusicBrainzArtistId(); + + if (!string.IsNullOrWhiteSpace(musicBrainzId)) + { + var url = "/ws/2/artist/?query=arid:{0}" + musicBrainzId.ToString(CultureInfo.InvariantCulture); + + using var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + return GetResultsFromResponse(stream); + } + else + { + // They seem to throw bad request failures on any term with a slash + var nameToSearch = searchInfo.Name.Replace('/', ' '); + + var url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=\"{0}\"&dismax=true", UrlEncode(nameToSearch)); + + using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) + await using (var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false)) + { + var results = GetResultsFromResponse(stream).ToList(); + + if (results.Count > 0) + { + return results; + } + } + + if (searchInfo.Name.HasDiacritics()) + { + // Try again using the search with accent characters url + url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch)); + + using var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + return GetResultsFromResponse(stream); + } + } + + return Enumerable.Empty(); + } + + private IEnumerable GetResultsFromResponse(Stream stream) + { + using var oReader = new StreamReader(stream, Encoding.UTF8); + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + + using var reader = XmlReader.Create(oReader, settings); + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "artist-list": + { + if (reader.IsEmptyElement) + { + reader.Read(); + continue; + } + + using var subReader = reader.ReadSubtree(); + return ParseArtistList(subReader).ToList(); + } + + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return Enumerable.Empty(); + } + + private IEnumerable ParseArtistList(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 "artist": + { + if (reader.IsEmptyElement) + { + reader.Read(); + continue; + } + + var mbzId = reader.GetAttribute("id"); + + using var subReader = reader.ReadSubtree(); + var artist = ParseArtist(subReader, mbzId); + if (artist != null) + { + yield return artist; + } + + break; + } + + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + } + + private RemoteSearchResult ParseArtist(XmlReader reader, string artistId) + { + var result = new RemoteSearchResult(); + + 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 "name": + { + result.Name = reader.ReadElementContentAsString(); + break; + } + + case "annotation": + { + result.Overview = reader.ReadElementContentAsString(); + break; + } + + default: + { + // there is sort-name if ever needed + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + result.SetProviderId(MetadataProvider.MusicBrainzArtist, artistId); + + if (string.IsNullOrWhiteSpace(artistId) || string.IsNullOrWhiteSpace(result.Name)) + { + return null; + } + + return result; + } + + /// + public async Task> GetMetadata(ArtistInfo info, CancellationToken cancellationToken) + { + var result = new MetadataResult + { + Item = new MusicArtist() + }; + + var musicBrainzId = info.GetMusicBrainzArtistId(); + + if (string.IsNullOrWhiteSpace(musicBrainzId)) + { + var searchResults = await GetSearchResults(info, cancellationToken).ConfigureAwait(false); + + var singleResult = searchResults.FirstOrDefault(); + + if (singleResult != null) + { + musicBrainzId = singleResult.GetProviderId(MetadataProvider.MusicBrainzArtist); + result.Item.Overview = singleResult.Overview; + + if (Plugin.Instance.Configuration.ReplaceArtistName) + { + result.Item.Name = singleResult.Name; + } + } + } + + if (!string.IsNullOrWhiteSpace(musicBrainzId)) + { + result.HasMetadata = true; + result.Item.SetProviderId(MetadataProvider.MusicBrainzArtist, musicBrainzId); + } + + return result; + } + + /// + /// Encodes an URL. + /// + /// The name. + /// System.String. + private static string UrlEncode(string name) + { + return WebUtility.UrlEncode(name); + } + + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs new file mode 100644 index 000000000..f889a34b5 --- /dev/null +++ b/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 + { + /// + public string ProviderName => "MusicBrainz"; + + /// + public string Key => MetadataProvider.MusicBrainzArtist.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist; + + /// + public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum; + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs new file mode 100644 index 000000000..53783d2c0 --- /dev/null +++ b/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 + { + /// + public string ProviderName => "MusicBrainz"; + + /// + public string Key => MetadataProvider.MusicBrainzReleaseGroup.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup; + + /// + public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum; + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs new file mode 100644 index 000000000..627f8f098 --- /dev/null +++ b/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 + { + /// + public string ProviderName => "MusicBrainz"; + + /// + public string Key => MetadataProvider.MusicBrainzTrack.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.Track; + + /// + public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Audio; + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs index 9eeb4750b..69b69be42 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs @@ -11,6 +11,10 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz { public class Plugin : BasePlugin, IHasWebPages { + public const string DefaultServer = "https://musicbrainz.org"; + + public const long DefaultRateLimit = 2000u; + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer 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 const string DefaultServer = "https://musicbrainz.org"; - - public const long DefaultRateLimit = 2000u; - // TODO remove when plugin removed from server. public override string ConfigurationFileName => "Jellyfin.Plugin.MusicBrainz.xml"; diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs index d9b0600c3..02e696de5 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591, SA1300 using System; using System.Collections.Generic; diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index eafcae4ac..88435e2d4 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS159, SA1300 using System; using System.Collections.Generic; @@ -20,6 +20,7 @@ using MediaBrowser.Model.IO; namespace MediaBrowser.Providers.Plugins.Omdb { + /// Provider for OMDB service. public class OmdbProvider { private readonly IFileSystem _fileSystem; @@ -29,6 +30,11 @@ namespace MediaBrowser.Providers.Plugins.Omdb private readonly IApplicationHost _appHost; private readonly JsonSerializerOptions _jsonOptions; + /// Initializes a new instance of the class. + /// HttpClientFactory to use for calls to OMDB service. + /// IFileSystem to use for store OMDB data. + /// IApplicationHost to use. + /// IServerConfigurationManager to use. public OmdbProvider(IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IApplicationHost appHost, IServerConfigurationManager configurationManager) { _httpClientFactory = httpClientFactory; @@ -41,6 +47,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb _jsonOptions.Converters.Add(new JsonOmdbNotAvailableInt32Converter()); } + /// Fetches data from OMDB service. + /// Metadata about media item. + /// IMDB ID for media. + /// Media language. + /// Country of origin. + /// CancellationToken to use for operation. + /// The first generic type parameter. + /// Returns a Task object that can be awaited. public async Task Fetch(MetadataResult itemResult, string imdbId, string language, string country, CancellationToken cancellationToken) where T : BaseItem { @@ -105,6 +119,17 @@ namespace MediaBrowser.Providers.Plugins.Omdb ParseAdditionalMetadata(itemResult, result); } + /// Gets data about an episode. + /// Metadata about episode. + /// Episode number. + /// Season number. + /// Episode ID. + /// Season ID. + /// Episode language. + /// Country of origin. + /// CancellationToken to use for operation. + /// The first generic type parameter. + /// Whether operation was successful. public async Task FetchEpisodeData(MetadataResult itemResult, int episodeNumber, int seasonNumber, string episodeImdbId, string seriesImdbId, string language, string country, CancellationToken cancellationToken) where T : BaseItem { @@ -236,6 +261,9 @@ namespace MediaBrowser.Providers.Plugins.Omdb return false; } + /// Gets OMDB URL. + /// Appends query string to URL. + /// OMDB URL with optional query string. public static string GetOmdbUrl(string query) { const string Url = "https://www.omdbapi.com?apikey=2c9d9507"; @@ -327,6 +355,12 @@ namespace MediaBrowser.Providers.Plugins.Omdb return path; } + /// Gets response from OMDB service as type T. + /// HttpClient instance to use for service call. + /// Http URL to use for service call. + /// CancellationToken to use for service call. + /// The first generic type parameter. + /// OMDB service response as type T. public async Task GetDeserializedOmdbResponse(HttpClient httpClient, string url, CancellationToken cancellationToken) { using var response = await GetOmdbResponse(httpClient, url, cancellationToken).ConfigureAwait(false); @@ -335,6 +369,11 @@ namespace MediaBrowser.Providers.Plugins.Omdb return await JsonSerializer.DeserializeAsync(content, _jsonOptions, cancellationToken).ConfigureAwait(false); } + /// Gets response from OMDB service. + /// HttpClient instance to use for service call. + /// Http URL to use for service call. + /// CancellationToken to use for service call. + /// OMDB service response as HttpResponseMessage. public static Task GetOmdbResponse(HttpClient httpClient, string url, CancellationToken cancellationToken) { return httpClient.GetAsync(url, cancellationToken); @@ -538,10 +577,13 @@ namespace MediaBrowser.Providers.Plugins.Omdb } } + /// Describes OMDB rating. public class OmdbRating { + /// Gets or sets rating source. public string Source { get; set; } + /// Gets or sets rating value. public string Value { get; set; } } } diff --git a/MediaBrowser.Providers/Studios/StudioMetadataService.cs b/MediaBrowser.Providers/Studios/StudioMetadataService.cs index 78042b40d..091b33ce0 100644 --- a/MediaBrowser.Providers/Studios/StudioMetadataService.cs +++ b/MediaBrowser.Providers/Studios/StudioMetadataService.cs @@ -17,7 +17,8 @@ namespace MediaBrowser.Providers.Studios IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, - IFileSystem fileSystem, ILibraryManager libraryManager) + IFileSystem fileSystem, + ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) { } -- cgit v1.2.3