From eba488a0f8d8783386f12e656d8dbd625acc557b Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 19 Jun 2020 20:24:13 +0200 Subject: Enable TreatWarningsAsErrors in Release for MediaBrowser.Providers --- MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs') diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs index 8532c4df3..cb7c0362e 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs @@ -1,4 +1,6 @@ -using System; +#pragma warning disable CS1591 + +using System; using System.Collections.Generic; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; -- cgit v1.2.3 From 939b95907b97bf0c10d06964ef0ab9e3cd21af71 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 22 Jul 2020 08:19:26 -0600 Subject: Force plugin config location --- MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs | 3 +++ MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs | 3 +++ MediaBrowser.Providers/Plugins/Omdb/Plugin.cs | 3 +++ MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs | 3 +++ 4 files changed, 12 insertions(+) (limited to 'MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs') diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs index cb7c0362e..54054d015 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs @@ -19,6 +19,9 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public override string Description => "Get artist and album metadata or images from AudioDB."; + // TODO remove when plugin removed from server. + public override string ConfigurationFileName => "Jellyfin.Plugin.AudioDb.xml"; + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer) { diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs index a7e6267da..90266e440 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs @@ -23,6 +23,9 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz public const long DefaultRateLimit = 2000u; + // TODO remove when plugin removed from server. + public override string ConfigurationFileName => "Jellyfin.Plugin.MusicBrainz.xml"; + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer) { diff --git a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs index 4cf5f4ce6..41ca56164 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs @@ -19,6 +19,9 @@ namespace MediaBrowser.Providers.Plugins.Omdb public override string Description => "Get metadata for movies and other video content from OMDb."; + // TODO remove when plugin removed from server. + public override string ConfigurationFileName => "Jellyfin.Plugin.Omdb.xml"; + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer) { diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs b/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs index aa5f819f0..e7079ed3c 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs @@ -17,6 +17,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public override string Description => "Get metadata for movies and other video content from TheTVDB."; + // TODO remove when plugin removed from server. + public override string ConfigurationFileName => "Jellyfin.Plugin.TheTvdb.xml"; + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer) { -- cgit v1.2.3 From 384ab39f5b3dfb940cb9f612f5b64541c9ec935b Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 7 Sep 2020 13:20:39 +0200 Subject: Fix some warnings --- .../LiveTv/Listings/SchedulesDirect.cs | 8 +- .../Folders/CollectionFolderMetadataService.cs | 1 - .../LiveTv/LiveTvMetadataService.cs | 32 + .../LiveTv/ProgramMetadataService.cs | 32 - MediaBrowser.Providers/Manager/ImageSaver.cs | 32 +- .../Manager/ItemImageProvider.cs | 56 +- MediaBrowser.Providers/Manager/MetadataService.cs | 61 +- MediaBrowser.Providers/Manager/ProviderManager.cs | 4 +- MediaBrowser.Providers/Manager/ProviderUtils.cs | 4 +- MediaBrowser.Providers/Manager/RefreshResult.cs | 15 + .../MediaInfo/AudioImageProvider.cs | 16 +- .../MediaInfo/FFProbeAudioInfo.cs | 29 +- .../MediaInfo/FFProbeProvider.cs | 72 +- .../MediaInfo/FFProbeVideoInfo.cs | 23 +- .../MediaInfo/SubtitleDownloader.cs | 31 +- .../MediaInfo/SubtitleResolver.cs | 18 +- .../MediaInfo/SubtitleScheduledTask.cs | 104 ++- .../MediaInfo/VideoImageProvider.cs | 9 +- MediaBrowser.Providers/Movies/ImdbExternalId.cs | 39 + .../Movies/ImdbPersonExternalId.cs | 27 + MediaBrowser.Providers/Movies/MovieExternalIds.cs | 57 -- .../Music/AlbumInfoExtensions.cs | 81 +++ MediaBrowser.Providers/Music/Extensions.cs | 81 --- MediaBrowser.Providers/Music/ImvdbId.cs | 28 + MediaBrowser.Providers/Music/MusicExternalIds.cs | 28 - .../Playlists/PlaylistItemsProvider.cs | 10 +- .../Plugins/AudioDb/ArtistProvider.cs | 10 +- .../Plugins/AudioDb/AudioDbAlbumExternalId.cs | 27 + .../Plugins/AudioDb/AudioDbArtistExternalId.cs | 27 + .../Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs | 27 + .../AudioDb/AudioDbOtherArtistExternalId.cs | 27 + .../Plugins/AudioDb/ExternalIds.cs | 81 --- MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs | 12 +- .../Plugins/MusicBrainz/AlbumProvider.cs | 790 -------------------- .../MusicBrainz/MusicBrainzAlbumProvider.cs | 791 +++++++++++++++++++++ .../Plugins/Omdb/OmdbImageProvider.cs | 12 +- .../Plugins/Omdb/OmdbItemProvider.cs | 10 +- MediaBrowser.Providers/TV/DummySeasonProvider.cs | 14 +- .../TV/MissingEpisodeProvider.cs | 31 +- MediaBrowser.Providers/TV/TvExternalIds.cs | 82 --- MediaBrowser.Providers/TV/TvdbEpisodeExternalId.cs | 28 + MediaBrowser.Providers/TV/TvdbExternalId.cs | 28 + MediaBrowser.Providers/TV/TvdbSeasonExternalId.cs | 28 + MediaBrowser.Providers/TV/Zap2ItExternalId.cs | 28 + 44 files changed, 1528 insertions(+), 1423 deletions(-) create mode 100644 MediaBrowser.Providers/LiveTv/LiveTvMetadataService.cs delete mode 100644 MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs create mode 100644 MediaBrowser.Providers/Manager/RefreshResult.cs create mode 100644 MediaBrowser.Providers/Movies/ImdbExternalId.cs create mode 100644 MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs delete mode 100644 MediaBrowser.Providers/Movies/MovieExternalIds.cs create mode 100644 MediaBrowser.Providers/Music/AlbumInfoExtensions.cs delete mode 100644 MediaBrowser.Providers/Music/Extensions.cs create mode 100644 MediaBrowser.Providers/Music/ImvdbId.cs delete mode 100644 MediaBrowser.Providers/Music/MusicExternalIds.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs delete mode 100644 MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs delete mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs delete mode 100644 MediaBrowser.Providers/TV/TvExternalIds.cs create mode 100644 MediaBrowser.Providers/TV/TvdbEpisodeExternalId.cs create mode 100644 MediaBrowser.Providers/TV/TvdbExternalId.cs create mode 100644 MediaBrowser.Providers/TV/TvdbSeasonExternalId.cs create mode 100644 MediaBrowser.Providers/TV/Zap2ItExternalId.cs (limited to 'MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs') diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index f9ae55af8..655ff5853 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -468,13 +468,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings imageIdString = imageIdString.TrimEnd(',') + "]"; - using var message = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/metadata/programs"); - message.Content = new StringContent(imageIdString, Encoding.UTF8, MediaTypeNames.Application.Json); + using var message = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/metadata/programs") + { + Content = new StringContent(imageIdString, Encoding.UTF8, MediaTypeNames.Application.Json) + }; try { using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false); - await using var response = await innerResponse2.Content.ReadAsStreamAsync(); + await using var response = await innerResponse2.Content.ReadAsStreamAsync().ConfigureAwait(false); return await _jsonSerializer.DeserializeFromStreamAsync>( response).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs index 46f368f72..e0f3131fd 100644 --- a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs +++ b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; diff --git a/MediaBrowser.Providers/LiveTv/LiveTvMetadataService.cs b/MediaBrowser.Providers/LiveTv/LiveTvMetadataService.cs new file mode 100644 index 000000000..2e6cf4530 --- /dev/null +++ b/MediaBrowser.Providers/LiveTv/LiveTvMetadataService.cs @@ -0,0 +1,32 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Providers.Manager; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Providers.LiveTv +{ + public class LiveTvMetadataService : MetadataService + { + public LiveTvMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) + { + } + + /// + protected override void MergeData(MetadataResult source, MetadataResult target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + } + } +} diff --git a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs deleted file mode 100644 index 2e6cf4530..000000000 --- a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs +++ /dev/null @@ -1,32 +0,0 @@ -#pragma warning disable CS1591 - -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Providers.Manager; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Providers.LiveTv -{ - public class LiveTvMetadataService : MetadataService - { - public LiveTvMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } - - /// - protected override void MergeData(MetadataResult source, MetadataResult target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); - } - } -} diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 413d297cb..19a42d506 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Data.Entities; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -59,6 +58,16 @@ namespace MediaBrowser.Providers.Manager _logger = logger; } + private bool EnableExtraThumbsDuplication + { + get + { + var config = _config.GetConfiguration("xbmcmetadata"); + + return config.EnableExtraThumbsDuplication; + } + } + /// /// Saves the image. /// @@ -69,7 +78,7 @@ namespace MediaBrowser.Providers.Manager /// Index of the image. /// The cancellation token. /// Task. - /// mimeType + /// mimeType. public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken) { return SaveImage(item, source, mimeType, type, imageIndex, null, cancellationToken); @@ -312,7 +321,7 @@ namespace MediaBrowser.Providers.Manager /// /// imageIndex /// or - /// imageIndex + /// imageIndex. /// private ItemImageInfo GetCurrentImage(BaseItem item, ImageType type, int imageIndex) { @@ -328,7 +337,8 @@ namespace MediaBrowser.Providers.Manager /// The path. /// imageIndex /// or - /// imageIndex + /// imageIndex. + /// private void SetImagePath(BaseItem item, ImageType type, int? imageIndex, string path) { item.SetImagePath(type, imageIndex ?? 0, _fileSystem.GetFileInfo(path)); @@ -346,7 +356,7 @@ namespace MediaBrowser.Providers.Manager /// /// imageIndex /// or - /// imageIndex + /// imageIndex. /// private string GetStandardSavePath(BaseItem item, ImageType type, int? imageIndex, string mimeType, bool saveLocally) { @@ -500,7 +510,7 @@ namespace MediaBrowser.Providers.Manager /// Index of the image. /// Type of the MIME. /// IEnumerable{System.String}. - /// imageIndex + /// imageIndex. private string[] GetCompatibleSavePaths(BaseItem item, ImageType type, int? imageIndex, string mimeType) { var season = item as Season; @@ -604,16 +614,6 @@ namespace MediaBrowser.Providers.Manager return new[] { GetStandardSavePath(item, type, imageIndex, mimeType, true) }; } - private bool EnableExtraThumbsDuplication - { - get - { - var config = _config.GetConfiguration("xbmcmetadata"); - - return config.EnableExtraThumbsDuplication; - } - } - /// /// Gets the save path for item in mixed folder. /// diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 9227b6d93..d0bdbd7c9 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -28,6 +28,22 @@ namespace MediaBrowser.Providers.Manager private readonly IProviderManager _providerManager; private readonly IFileSystem _fileSystem; + /// + /// Image types that are only one per item. + /// + private readonly ImageType[] _singularImages = + { + ImageType.Primary, + ImageType.Art, + ImageType.Banner, + ImageType.Box, + ImageType.BoxRear, + ImageType.Disc, + ImageType.Logo, + ImageType.Menu, + ImageType.Thumb + }; + public ItemImageProvider(ILogger logger, IProviderManager providerManager, IFileSystem fileSystem) { _logger = logger; @@ -175,22 +191,6 @@ namespace MediaBrowser.Providers.Manager } } - /// - /// Image types that are only one per item. - /// - private readonly ImageType[] _singularImages = - { - ImageType.Primary, - ImageType.Art, - ImageType.Banner, - ImageType.Box, - ImageType.BoxRear, - ImageType.Disc, - ImageType.Logo, - ImageType.Menu, - ImageType.Thumb - }; - private bool HasImage(BaseItem item, ImageType type) { return item.HasImage(type); @@ -378,7 +378,6 @@ namespace MediaBrowser.Providers.Manager } else { - var newDateModified = _fileSystem.GetLastWriteTimeUtc(image.FileInfo); // If date changed then we need to reset saved image dimensions @@ -441,7 +440,9 @@ namespace MediaBrowser.Providers.Manager return changed; } - private async Task DownloadImage(BaseItem item, LibraryOptions libraryOptions, + private async Task DownloadImage( + BaseItem item, + LibraryOptions libraryOptions, IRemoteImageProvider provider, RefreshResult result, IEnumerable images, @@ -522,11 +523,6 @@ namespace MediaBrowser.Providers.Manager return false; } - // if (!item.IsSaveLocalMetadataEnabled()) - //{ - // return true; - //} - return true; } @@ -539,13 +535,15 @@ namespace MediaBrowser.Providers.Manager private void SaveImageStub(BaseItem item, ImageType imageType, IEnumerable urls, int newIndex) { - var path = string.Join("|", urls.Take(1).ToArray()); + var path = string.Join('|', urls.Take(1)); - item.SetImage(new ItemImageInfo - { - Path = path, - Type = imageType - }, newIndex); + item.SetImage( + new ItemImageInfo + { + Path = path, + Type = imageType + }, + newIndex); } private async Task DownloadBackdrops(BaseItem item, LibraryOptions libraryOptions, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable images, int minWidth, CancellationToken cancellationToken) diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index d0de58427..42785b057 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -21,12 +21,6 @@ namespace MediaBrowser.Providers.Manager where TItemType : BaseItem, IHasLookupInfo, new() where TIdType : ItemLookupInfo, new() { - protected readonly IServerConfigurationManager ServerConfigurationManager; - protected readonly ILogger> Logger; - protected readonly IProviderManager ProviderManager; - protected readonly IFileSystem FileSystem; - protected readonly ILibraryManager LibraryManager; - protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger> logger, IProviderManager providerManager, IFileSystem fileSystem, ILibraryManager libraryManager) { ServerConfigurationManager = serverConfigurationManager; @@ -36,6 +30,26 @@ namespace MediaBrowser.Providers.Manager LibraryManager = libraryManager; } + protected IServerConfigurationManager ServerConfigurationManager { get; } + + protected ILogger> Logger { get; } + + protected IProviderManager ProviderManager { get; } + + protected IFileSystem FileSystem { get; } + + protected ILibraryManager LibraryManager { get; } + + protected virtual bool EnableUpdatingPremiereDateFromChildren => false; + + protected virtual bool EnableUpdatingGenresFromChildren => false; + + protected virtual bool EnableUpdatingStudiosFromChildren => false; + + protected virtual bool EnableUpdatingOfficialRatingFromChildren => false; + + public virtual int Order => 0; + private FileSystemMetadata TryGetFile(string path, IDirectoryService directoryService) { try @@ -442,14 +456,6 @@ namespace MediaBrowser.Providers.Manager return updateType; } - protected virtual bool EnableUpdatingPremiereDateFromChildren => false; - - protected virtual bool EnableUpdatingGenresFromChildren => false; - - protected virtual bool EnableUpdatingStudiosFromChildren => false; - - protected virtual bool EnableUpdatingOfficialRatingFromChildren => false; - private ItemUpdateType UpdatePremiereDate(TItemType item, IList children) { var updateType = ItemUpdateType.None; @@ -658,7 +664,8 @@ namespace MediaBrowser.Providers.Manager return type == typeof(TItemType); } - protected virtual async Task RefreshWithProviders(MetadataResult metadata, + protected virtual async Task RefreshWithProviders( + MetadataResult metadata, TIdType id, MetadataRefreshOptions options, List providers, @@ -773,7 +780,7 @@ namespace MediaBrowser.Providers.Manager else { // TODO: If the new metadata from above has some blank data, this can cause old data to get filled into those empty fields - MergeData(metadata, temp, new MetadataField[] { }, false, false); + MergeData(metadata, temp, Array.Empty(), false, false); MergeData(temp, metadata, item.LockedFields, true, false); } } @@ -900,24 +907,23 @@ namespace MediaBrowser.Providers.Manager } } - protected abstract void MergeData(MetadataResult source, + protected abstract void MergeData( + MetadataResult source, MetadataResult target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings); - public virtual int Order => 0; - private bool HasChanged(BaseItem item, IHasItemChangeMonitor changeMonitor, IDirectoryService directoryService) { try { var hasChanged = changeMonitor.HasChanged(item, directoryService); - // if (hasChanged) - //{ - // logger.LogDebug("{0} reports change to {1}", changeMonitor.GetType().Name, item.Path ?? item.Name); - //} + if (hasChanged) + { + Logger.LogDebug("{0} reports change to {1}", changeMonitor.GetType().Name, item.Path ?? item.Name); + } return hasChanged; } @@ -928,13 +934,4 @@ namespace MediaBrowser.Providers.Manager } } } - - public class RefreshResult - { - public ItemUpdateType UpdateType { get; set; } - - public string ErrorMessage { get; set; } - - public int Failures { get; set; } - } } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 171b824ca..b6fb4267f 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -9,7 +9,6 @@ using System.Net.Http; using System.Net.Mime; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Data.Entities; using Jellyfin.Data.Events; using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; @@ -905,8 +904,7 @@ namespace MediaBrowser.Providers.Manager return provider.GetImageResponse(url, cancellationToken); } - /// - public IEnumerable GetExternalIds(IHasProviderIds item) + private IEnumerable GetExternalIds(IHasProviderIds item) { return _externalIds.Where(i => { diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index a4fd6ca84..70a5a6ac1 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -26,12 +26,12 @@ namespace MediaBrowser.Providers.Manager if (source == null) { - throw new ArgumentNullException(nameof(source)); + throw new ArgumentException("Item cannot be null.", nameof(sourceResult)); } if (target == null) { - throw new ArgumentNullException(nameof(target)); + throw new ArgumentException("Item cannot be null.", nameof(targetResult)); } if (!lockedFields.Contains(MetadataField.Name)) diff --git a/MediaBrowser.Providers/Manager/RefreshResult.cs b/MediaBrowser.Providers/Manager/RefreshResult.cs new file mode 100644 index 000000000..72fc61e42 --- /dev/null +++ b/MediaBrowser.Providers/Manager/RefreshResult.cs @@ -0,0 +1,15 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Library; + +namespace MediaBrowser.Providers.Manager +{ + public class RefreshResult + { + public ItemUpdateType UpdateType { get; set; } + + public string ErrorMessage { get; set; } + + public int Failures { get; set; } + } +} diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index f69ec9744..64ad1bddf 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -34,6 +34,10 @@ namespace MediaBrowser.Providers.MediaInfo _fileSystem = fileSystem; } + public string AudioImagesPath => Path.Combine(_config.ApplicationPaths.CachePath, "extracted-audio-images"); + + public string Name => "Image Extractor"; + public IEnumerable GetSupportedImages(BaseItem item) { return new List { ImageType.Primary }; @@ -97,11 +101,11 @@ namespace MediaBrowser.Providers.MediaInfo if (item.GetType() == typeof(Audio)) { - var albumArtist = item.AlbumArtists.FirstOrDefault(); - - if (!string.IsNullOrWhiteSpace(item.Album) && !string.IsNullOrWhiteSpace(albumArtist)) + if (item.AlbumArtists.Count > 0 + && !string.IsNullOrWhiteSpace(item.Album) + && !string.IsNullOrWhiteSpace(item.AlbumArtists[0])) { - filename = (item.Album + "-" + albumArtist).GetMD5().ToString("N", CultureInfo.InvariantCulture); + filename = (item.Album + "-" + item.AlbumArtists[0]).GetMD5().ToString("N", CultureInfo.InvariantCulture); } else { @@ -121,10 +125,6 @@ namespace MediaBrowser.Providers.MediaInfo return Path.Join(AudioImagesPath, prefix, filename); } - public string AudioImagesPath => Path.Combine(_config.ApplicationPaths.CachePath, "extracted-audio-images"); - - public string Name => "Image Extractor"; - public bool Supports(BaseItem item) { if (item.IsShortcut) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs index 77f03580a..945463666 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs @@ -37,7 +37,9 @@ namespace MediaBrowser.Providers.MediaInfo _mediaSourceManager = mediaSourceManager; } - public async Task Probe(T item, MetadataRefreshOptions options, + public async Task Probe( + T item, + MetadataRefreshOptions options, CancellationToken cancellationToken) where T : Audio { @@ -52,19 +54,21 @@ namespace MediaBrowser.Providers.MediaInfo protocol = _mediaSourceManager.GetPathProtocol(path); } - var result = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest - { - MediaType = DlnaProfileType.Audio, - MediaSource = new MediaSourceInfo + var result = await _mediaEncoder.GetMediaInfo( + new MediaInfoRequest { - Path = path, - Protocol = protocol - } - }, cancellationToken).ConfigureAwait(false); + MediaType = DlnaProfileType.Audio, + MediaSource = new MediaSourceInfo + { + Path = path, + Protocol = protocol + } + }, + cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); - Fetch(item, cancellationToken, result); + Fetch(item, result, cancellationToken); } return ItemUpdateType.MetadataImport; @@ -74,10 +78,9 @@ namespace MediaBrowser.Providers.MediaInfo /// Fetches the specified audio. /// /// The audio. - /// The cancellation token. /// The media information. - /// Task. - protected void Fetch(Audio audio, CancellationToken cancellationToken, Model.MediaInfo.MediaInfo mediaInfo) + /// The cancellation token. + protected void Fetch(Audio audio, Model.MediaInfo.MediaInfo mediaInfo, CancellationToken cancellationToken) { var mediaStreams = mediaInfo.MediaStreams; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 9926275ae..c61187fdf 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -5,8 +5,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -20,9 +18,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.MediaInfo @@ -50,9 +46,43 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IChapterManager _chapterManager; private readonly ILibraryManager _libraryManager; private readonly IMediaSourceManager _mediaSourceManager; + private readonly SubtitleResolver _subtitleResolver; + + private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); + + public FFProbeProvider( + ILogger logger, + IMediaSourceManager mediaSourceManager, + IMediaEncoder mediaEncoder, + IItemRepository itemRepo, + IBlurayExaminer blurayExaminer, + ILocalizationManager localization, + IEncodingManager encodingManager, + IServerConfigurationManager config, + ISubtitleManager subtitleManager, + IChapterManager chapterManager, + ILibraryManager libraryManager) + { + _logger = logger; + _mediaEncoder = mediaEncoder; + _itemRepo = itemRepo; + _blurayExaminer = blurayExaminer; + _localization = localization; + _encodingManager = encodingManager; + _config = config; + _subtitleManager = subtitleManager; + _chapterManager = chapterManager; + _libraryManager = libraryManager; + _mediaSourceManager = mediaSourceManager; + + _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager); + } public string Name => "ffprobe"; + // Run last + public int Order => 100; + public bool HasChanged(BaseItem item, IDirectoryService directoryService) { var video = item as Video; @@ -117,37 +147,6 @@ namespace MediaBrowser.Providers.MediaInfo return FetchAudioInfo(item, options, cancellationToken); } - private SubtitleResolver _subtitleResolver; - - public FFProbeProvider( - ILogger logger, - IMediaSourceManager mediaSourceManager, - IMediaEncoder mediaEncoder, - IItemRepository itemRepo, - IBlurayExaminer blurayExaminer, - ILocalizationManager localization, - IEncodingManager encodingManager, - IServerConfigurationManager config, - ISubtitleManager subtitleManager, - IChapterManager chapterManager, - ILibraryManager libraryManager) - { - _logger = logger; - _mediaEncoder = mediaEncoder; - _itemRepo = itemRepo; - _blurayExaminer = blurayExaminer; - _localization = localization; - _encodingManager = encodingManager; - _config = config; - _subtitleManager = subtitleManager; - _chapterManager = chapterManager; - _libraryManager = libraryManager; - _mediaSourceManager = mediaSourceManager; - - _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager); - } - - private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); public Task FetchVideoInfo(T item, MetadataRefreshOptions options, CancellationToken cancellationToken) where T : Video { @@ -234,8 +233,5 @@ namespace MediaBrowser.Providers.MediaInfo return prober.Probe(item, options, cancellationToken); } - - // Run last - public int Order => 100; } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 53a6bb619..776dee780 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -539,17 +539,18 @@ namespace MediaBrowser.Providers.MediaInfo if (enableSubtitleDownloading && enabled) { - var downloadedLanguages = await new SubtitleDownloader(_logger, - _subtitleManager) - .DownloadSubtitles(video, - currentStreams.Concat(externalSubtitleStreams).ToList(), - skipIfEmbeddedSubtitlesPresent, - skipIfAudioTrackMatches, - requirePerfectMatch, - subtitleDownloadLanguages, - libraryOptions.DisabledSubtitleFetchers, - libraryOptions.SubtitleFetcherOrder, - cancellationToken).ConfigureAwait(false); + var downloadedLanguages = await new SubtitleDownloader( + _logger, + _subtitleManager).DownloadSubtitles( + video, + currentStreams.Concat(externalSubtitleStreams).ToList(), + skipIfEmbeddedSubtitlesPresent, + skipIfAudioTrackMatches, + requirePerfectMatch, + subtitleDownloadLanguages, + libraryOptions.DisabledSubtitleFetchers, + libraryOptions.SubtitleFetcherOrder, + cancellationToken).ConfigureAwait(false); // Rescan if (downloadedLanguages.Count > 0) diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs index acddb73d0..912aedb0d 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs @@ -42,8 +42,16 @@ namespace MediaBrowser.Providers.MediaInfo foreach (var lang in languages) { - var downloaded = await DownloadSubtitles(video, mediaStreams, skipIfEmbeddedSubtitlesPresent, - skipIfAudioTrackMatches, requirePerfectMatch, lang, disabledSubtitleFetchers, subtitleFetcherOrder, cancellationToken).ConfigureAwait(false); + var downloaded = await DownloadSubtitles( + video, + mediaStreams, + skipIfEmbeddedSubtitlesPresent, + skipIfAudioTrackMatches, + requirePerfectMatch, + lang, + disabledSubtitleFetchers, + subtitleFetcherOrder, + cancellationToken).ConfigureAwait(false); if (downloaded) { @@ -54,7 +62,8 @@ namespace MediaBrowser.Providers.MediaInfo return downloadedLanguages; } - public Task DownloadSubtitles(Video video, + public Task DownloadSubtitles( + Video video, List mediaStreams, bool skipIfEmbeddedSubtitlesPresent, bool skipIfAudioTrackMatches, @@ -90,11 +99,21 @@ namespace MediaBrowser.Providers.MediaInfo return Task.FromResult(false); } - return DownloadSubtitles(video, mediaStreams, skipIfEmbeddedSubtitlesPresent, skipIfAudioTrackMatches, - requirePerfectMatch, lang, disabledSubtitleFetchers, subtitleFetcherOrder, mediaType, cancellationToken); + return DownloadSubtitles( + video, + mediaStreams, + skipIfEmbeddedSubtitlesPresent, + skipIfAudioTrackMatches, + requirePerfectMatch, + lang, + disabledSubtitleFetchers, + subtitleFetcherOrder, + mediaType, + cancellationToken); } - private async Task DownloadSubtitles(Video video, + private async Task DownloadSubtitles( + Video video, List mediaStreams, bool skipIfEmbeddedSubtitlesPresent, bool skipIfAudioTrackMatches, diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index 43659b68c..e9f999c6d 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -66,9 +66,10 @@ namespace MediaBrowser.Providers.MediaInfo return streams; } - public List GetExternalSubtitleFiles(Video video, - IDirectoryService directoryService, - bool clearCache) + public List GetExternalSubtitleFiles( + Video video, + IDirectoryService directoryService, + bool clearCache) { var list = new List(); @@ -87,7 +88,9 @@ namespace MediaBrowser.Providers.MediaInfo return list; } - private void AddExternalSubtitleStreams(List streams, string folder, + private void AddExternalSubtitleStreams( + List streams, + string folder, string videoPath, int startIndex, IDirectoryService directoryService, @@ -98,7 +101,8 @@ namespace MediaBrowser.Providers.MediaInfo AddExternalSubtitleStreams(streams, videoPath, startIndex, files); } - public void AddExternalSubtitleStreams(List streams, + public void AddExternalSubtitleStreams( + List streams, string videoPath, int startIndex, string[] files) @@ -185,8 +189,8 @@ namespace MediaBrowser.Providers.MediaInfo private string NormalizeFilenameForSubtitleComparison(string filename) { // Try to account for sloppy file naming - filename = filename.Replace("_", string.Empty); - filename = filename.Replace(" ", string.Empty); + filename = filename.Replace("_", string.Empty, StringComparison.Ordinal); + filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal); // can't normalize this due to languages such as pt-br // filename = filename.Replace("-", string.Empty); diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 91ab7b4ac..d231bfa2f 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -12,11 +12,10 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; -using MediaBrowser.Model.Globalization; namespace MediaBrowser.Providers.MediaInfo { @@ -25,29 +24,37 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ILibraryManager _libraryManager; private readonly IServerConfigurationManager _config; private readonly ISubtitleManager _subtitleManager; - private readonly IMediaSourceManager _mediaSourceManager; private readonly ILogger _logger; - private readonly IJsonSerializer _json; private readonly ILocalizationManager _localization; public SubtitleScheduledTask( ILibraryManager libraryManager, - IJsonSerializer json, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger, - IMediaSourceManager mediaSourceManager, ILocalizationManager localization) { _libraryManager = libraryManager; _config = config; _subtitleManager = subtitleManager; _logger = logger; - _mediaSourceManager = mediaSourceManager; - _json = json; _localization = localization; } + public string Name => _localization.GetLocalizedString("TaskDownloadMissingSubtitles"); + + public string Description => _localization.GetLocalizedString("TaskDownloadMissingSubtitlesDescription"); + + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + + public string Key => "DownloadSubtitles"; + + public bool IsHidden => false; + + public bool IsEnabled => true; + + public bool IsLogged => true; + private SubtitleOptions GetOptions() { return _config.GetConfiguration("subtitles"); @@ -66,23 +73,23 @@ namespace MediaBrowser.Providers.MediaInfo var libraryOptions = _libraryManager.GetLibraryOptions(library); string[] subtitleDownloadLanguages; - bool SkipIfEmbeddedSubtitlesPresent; - bool SkipIfAudioTrackMatches; - bool RequirePerfectMatch; + bool skipIfEmbeddedSubtitlesPresent; + bool skipIfAudioTrackMatches; + bool requirePerfectMatch; if (libraryOptions.SubtitleDownloadLanguages == null) { subtitleDownloadLanguages = options.DownloadLanguages; - SkipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent; - SkipIfAudioTrackMatches = options.SkipIfAudioTrackMatches; - RequirePerfectMatch = options.RequirePerfectMatch; + skipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent; + skipIfAudioTrackMatches = options.SkipIfAudioTrackMatches; + requirePerfectMatch = options.RequirePerfectMatch; } else { subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages; - SkipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; - SkipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; - RequirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; + skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; + skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; + requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; } foreach (var lang in subtitleDownloadLanguages) @@ -98,12 +105,12 @@ namespace MediaBrowser.Providers.MediaInfo Recursive = true }; - if (SkipIfAudioTrackMatches) + if (skipIfAudioTrackMatches) { query.HasNoAudioTrackWithLanguage = lang; } - if (SkipIfEmbeddedSubtitlesPresent) + if (skipIfEmbeddedSubtitlesPresent) { // Exclude if it already has any subtitles of the same language query.HasNoSubtitleTrackWithLanguage = lang; @@ -160,36 +167,37 @@ namespace MediaBrowser.Providers.MediaInfo var libraryOptions = _libraryManager.GetLibraryOptions(video); string[] subtitleDownloadLanguages; - bool SkipIfEmbeddedSubtitlesPresent; - bool SkipIfAudioTrackMatches; - bool RequirePerfectMatch; + bool skipIfEmbeddedSubtitlesPresent; + bool skipIfAudioTrackMatches; + bool requirePerfectMatch; if (libraryOptions.SubtitleDownloadLanguages == null) { subtitleDownloadLanguages = options.DownloadLanguages; - SkipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent; - SkipIfAudioTrackMatches = options.SkipIfAudioTrackMatches; - RequirePerfectMatch = options.RequirePerfectMatch; + skipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent; + skipIfAudioTrackMatches = options.SkipIfAudioTrackMatches; + requirePerfectMatch = options.RequirePerfectMatch; } else { subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages; - SkipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; - SkipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; - RequirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; + skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent; + skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches; + requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch; } - var downloadedLanguages = await new SubtitleDownloader(_logger, - _subtitleManager) - .DownloadSubtitles(video, - mediaStreams, - SkipIfEmbeddedSubtitlesPresent, - SkipIfAudioTrackMatches, - RequirePerfectMatch, - subtitleDownloadLanguages, - libraryOptions.DisabledSubtitleFetchers, - libraryOptions.SubtitleFetcherOrder, - cancellationToken).ConfigureAwait(false); + var downloadedLanguages = await new SubtitleDownloader( + _logger, + _subtitleManager).DownloadSubtitles( + video, + mediaStreams, + skipIfEmbeddedSubtitlesPresent, + skipIfAudioTrackMatches, + requirePerfectMatch, + subtitleDownloadLanguages, + libraryOptions.DisabledSubtitleFetchers, + libraryOptions.SubtitleFetcherOrder, + cancellationToken).ConfigureAwait(false); // Rescan if (downloadedLanguages.Count > 0) @@ -203,25 +211,11 @@ namespace MediaBrowser.Providers.MediaInfo public IEnumerable GetDefaultTriggers() { - return new[] { - + return new[] + { // Every so often new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} }; } - - public string Name => _localization.GetLocalizedString("TaskDownloadMissingSubtitles"); - - public string Description => _localization.GetLocalizedString("TaskDownloadMissingSubtitlesDescription"); - - public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); - - public string Key => "DownloadSubtitles"; - - public bool IsHidden => false; - - public bool IsEnabled => true; - - public bool IsLogged => true; } } diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index e23854d90..fc38d3832 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -29,6 +29,11 @@ namespace MediaBrowser.Providers.MediaInfo _fileSystem = fileSystem; } + public string Name => "Screen Grabber"; + + // Make sure this comes after internet image providers + public int Order => 100; + public IEnumerable GetSupportedImages(BaseItem item) { return new List { ImageType.Primary }; @@ -127,8 +132,6 @@ namespace MediaBrowser.Providers.MediaInfo }; } - public string Name => "Screen Grabber"; - public bool Supports(BaseItem item) { if (item.IsShortcut) @@ -150,7 +153,5 @@ namespace MediaBrowser.Providers.MediaInfo return false; } - // Make sure this comes after internet image providers - public int Order => 100; } } diff --git a/MediaBrowser.Providers/Movies/ImdbExternalId.cs b/MediaBrowser.Providers/Movies/ImdbExternalId.cs new file mode 100644 index 000000000..a8d74aa0b --- /dev/null +++ b/MediaBrowser.Providers/Movies/ImdbExternalId.cs @@ -0,0 +1,39 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Movies +{ + public class ImdbExternalId : IExternalId + { + /// + public string ProviderName => "IMDb"; + + /// + public string Key => MetadataProvider.Imdb.ToString(); + + /// + public ExternalIdMediaType? Type => null; + + /// + public string UrlFormatString => "https://www.imdb.com/title/{0}"; + + /// + public bool Supports(IHasProviderIds item) + { + // Supports images for tv movies + if (item is LiveTvProgram tvProgram && tvProgram.IsMovie) + { + return true; + } + + return item is Movie || item is MusicVideo || item is Series || item is Episode || item is Trailer; + } + } +} diff --git a/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs b/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs new file mode 100644 index 000000000..8151ab471 --- /dev/null +++ b/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs @@ -0,0 +1,27 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Movies +{ + public class ImdbPersonExternalId : IExternalId + { + /// + public string ProviderName => "IMDb"; + + /// + public string Key => MetadataProvider.Imdb.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.Person; + + /// + public string UrlFormatString => "https://www.imdb.com/name/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Person; + } +} diff --git a/MediaBrowser.Providers/Movies/MovieExternalIds.cs b/MediaBrowser.Providers/Movies/MovieExternalIds.cs deleted file mode 100644 index 14080841c..000000000 --- a/MediaBrowser.Providers/Movies/MovieExternalIds.cs +++ /dev/null @@ -1,57 +0,0 @@ -#pragma warning disable CS1591 - -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace MediaBrowser.Providers.Movies -{ - public class ImdbExternalId : IExternalId - { - /// - public string ProviderName => "IMDb"; - - /// - public string Key => MetadataProvider.Imdb.ToString(); - - /// - public ExternalIdMediaType? Type => null; - - /// - public string UrlFormatString => "https://www.imdb.com/title/{0}"; - - /// - public bool Supports(IHasProviderIds item) - { - // Supports images for tv movies - if (item is LiveTvProgram tvProgram && tvProgram.IsMovie) - { - return true; - } - - return item is Movie || item is MusicVideo || item is Series || item is Episode || item is Trailer; - } - } - - public class ImdbPersonExternalId : IExternalId - { - /// - public string ProviderName => "IMDb"; - - /// - public string Key => MetadataProvider.Imdb.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Person; - - /// - public string UrlFormatString => "https://www.imdb.com/name/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Person; - } -} diff --git a/MediaBrowser.Providers/Music/AlbumInfoExtensions.cs b/MediaBrowser.Providers/Music/AlbumInfoExtensions.cs new file mode 100644 index 000000000..dddfd02e4 --- /dev/null +++ b/MediaBrowser.Providers/Music/AlbumInfoExtensions.cs @@ -0,0 +1,81 @@ +#pragma warning disable CS1591 + +using System.Linq; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Music +{ + public static class AlbumInfoExtensions + { + public static string GetAlbumArtist(this AlbumInfo info) + { + var id = info.SongInfos.SelectMany(i => i.AlbumArtists) + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + + if (!string.IsNullOrEmpty(id)) + { + return id; + } + + return info.AlbumArtists.Count > 0 ? info.AlbumArtists[0] : default; + } + + public static string GetReleaseGroupId(this AlbumInfo info) + { + var id = info.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup); + + if (string.IsNullOrEmpty(id)) + { + return info.SongInfos.Select(i => i.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup)) + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + } + + return id; + } + + public static string GetReleaseId(this AlbumInfo info) + { + var id = info.GetProviderId(MetadataProvider.MusicBrainzAlbum); + + if (string.IsNullOrEmpty(id)) + { + return info.SongInfos.Select(i => i.GetProviderId(MetadataProvider.MusicBrainzAlbum)) + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + } + + return id; + } + + public static string GetMusicBrainzArtistId(this AlbumInfo info) + { + info.ProviderIds.TryGetValue(MetadataProvider.MusicBrainzAlbumArtist.ToString(), out string id); + + if (string.IsNullOrEmpty(id)) + { + info.ArtistProviderIds.TryGetValue(MetadataProvider.MusicBrainzArtist.ToString(), out id); + } + + if (string.IsNullOrEmpty(id)) + { + return info.SongInfos.Select(i => i.GetProviderId(MetadataProvider.MusicBrainzAlbumArtist)) + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + } + + return id; + } + + public static string GetMusicBrainzArtistId(this ArtistInfo info) + { + info.ProviderIds.TryGetValue(MetadataProvider.MusicBrainzArtist.ToString(), out var id); + + if (string.IsNullOrEmpty(id)) + { + return info.SongInfos.Select(i => i.GetProviderId(MetadataProvider.MusicBrainzAlbumArtist)) + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + } + + return id; + } + } +} diff --git a/MediaBrowser.Providers/Music/Extensions.cs b/MediaBrowser.Providers/Music/Extensions.cs deleted file mode 100644 index dddfd02e4..000000000 --- a/MediaBrowser.Providers/Music/Extensions.cs +++ /dev/null @@ -1,81 +0,0 @@ -#pragma warning disable CS1591 - -using System.Linq; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; - -namespace MediaBrowser.Providers.Music -{ - public static class AlbumInfoExtensions - { - public static string GetAlbumArtist(this AlbumInfo info) - { - var id = info.SongInfos.SelectMany(i => i.AlbumArtists) - .FirstOrDefault(i => !string.IsNullOrEmpty(i)); - - if (!string.IsNullOrEmpty(id)) - { - return id; - } - - return info.AlbumArtists.Count > 0 ? info.AlbumArtists[0] : default; - } - - public static string GetReleaseGroupId(this AlbumInfo info) - { - var id = info.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup); - - if (string.IsNullOrEmpty(id)) - { - return info.SongInfos.Select(i => i.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup)) - .FirstOrDefault(i => !string.IsNullOrEmpty(i)); - } - - return id; - } - - public static string GetReleaseId(this AlbumInfo info) - { - var id = info.GetProviderId(MetadataProvider.MusicBrainzAlbum); - - if (string.IsNullOrEmpty(id)) - { - return info.SongInfos.Select(i => i.GetProviderId(MetadataProvider.MusicBrainzAlbum)) - .FirstOrDefault(i => !string.IsNullOrEmpty(i)); - } - - return id; - } - - public static string GetMusicBrainzArtistId(this AlbumInfo info) - { - info.ProviderIds.TryGetValue(MetadataProvider.MusicBrainzAlbumArtist.ToString(), out string id); - - if (string.IsNullOrEmpty(id)) - { - info.ArtistProviderIds.TryGetValue(MetadataProvider.MusicBrainzArtist.ToString(), out id); - } - - if (string.IsNullOrEmpty(id)) - { - return info.SongInfos.Select(i => i.GetProviderId(MetadataProvider.MusicBrainzAlbumArtist)) - .FirstOrDefault(i => !string.IsNullOrEmpty(i)); - } - - return id; - } - - public static string GetMusicBrainzArtistId(this ArtistInfo info) - { - info.ProviderIds.TryGetValue(MetadataProvider.MusicBrainzArtist.ToString(), out var id); - - if (string.IsNullOrEmpty(id)) - { - return info.SongInfos.Select(i => i.GetProviderId(MetadataProvider.MusicBrainzAlbumArtist)) - .FirstOrDefault(i => !string.IsNullOrEmpty(i)); - } - - return id; - } - } -} diff --git a/MediaBrowser.Providers/Music/ImvdbId.cs b/MediaBrowser.Providers/Music/ImvdbId.cs new file mode 100644 index 000000000..a1726b996 --- /dev/null +++ b/MediaBrowser.Providers/Music/ImvdbId.cs @@ -0,0 +1,28 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Music +{ + public class ImvdbId : IExternalId + { + /// + public string ProviderName => "IMVDb"; + + /// + public string Key => "IMVDb"; + + /// + public ExternalIdMediaType? Type => null; + + /// + public string UrlFormatString => null; + + /// + public bool Supports(IHasProviderIds item) + => item is MusicVideo; + } +} diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/MusicExternalIds.cs deleted file mode 100644 index a1726b996..000000000 --- a/MediaBrowser.Providers/Music/MusicExternalIds.cs +++ /dev/null @@ -1,28 +0,0 @@ -#pragma warning disable CS1591 - -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace MediaBrowser.Providers.Music -{ - public class ImvdbId : IExternalId - { - /// - public string ProviderName => "IMVDb"; - - /// - public string Key => "IMVDb"; - - /// - public ExternalIdMediaType? Type => null; - - /// - public string UrlFormatString => null; - - /// - public bool Supports(IHasProviderIds item) - => item is MusicVideo; - } -} diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs index 5cc0a527e..067d585cb 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs @@ -10,7 +10,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; using PlaylistsNET.Content; @@ -23,16 +22,17 @@ namespace MediaBrowser.Providers.Playlists IHasItemChangeMonitor { private readonly ILogger _logger; - private IFileSystem _fileSystem; - public PlaylistItemsProvider(IFileSystem fileSystem, ILogger logger) + public PlaylistItemsProvider(ILogger logger) { - _fileSystem = fileSystem; _logger = logger; } public string Name => "Playlist Reader"; + // Run last + public int Order => 100; + public Task FetchAsync(Playlist item, MetadataRefreshOptions options, CancellationToken cancellationToken) { var path = item.Path; @@ -163,7 +163,5 @@ namespace MediaBrowser.Providers.Playlists return false; } - // Run last - public int Order => 100; } } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs index 670c0cd05..72dad8a25 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs @@ -23,16 +23,14 @@ 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 IJsonSerializer _json; - public static AudioDbArtistProvider Current; - - private const string ApiKey = "195003"; - public const string BaseUrl = "https://www.theaudiodb.com/api/v1/json/" + ApiKey; - public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory, IJsonSerializer json) { _config = config; @@ -42,6 +40,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb Current = this; } + public static AudioDbArtistProvider Current { get; private set; } + /// public string Name => "TheAudioDB"; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs new file mode 100644 index 000000000..138cfef19 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs @@ -0,0 +1,27 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.AudioDb +{ + public class AudioDbAlbumExternalId : IExternalId + { + /// + public string ProviderName => "TheAudioDb"; + + /// + public string Key => MetadataProvider.AudioDbAlbum.ToString(); + + /// + public ExternalIdMediaType? Type => null; + + /// + public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is MusicAlbum; + } +} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs new file mode 100644 index 000000000..8aceb48c0 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs @@ -0,0 +1,27 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.AudioDb +{ + public class AudioDbArtistExternalId : IExternalId + { + /// + public string ProviderName => "TheAudioDb"; + + /// + public string Key => MetadataProvider.AudioDbArtist.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.Artist; + + /// + public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is MusicArtist; + } +} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs new file mode 100644 index 000000000..014481da2 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs @@ -0,0 +1,27 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.AudioDb +{ + public class AudioDbOtherAlbumExternalId : IExternalId + { + /// + public string ProviderName => "TheAudioDb"; + + /// + public string Key => MetadataProvider.AudioDbAlbum.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.Album; + + /// + public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Audio; + } +} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs new file mode 100644 index 000000000..787539104 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs @@ -0,0 +1,27 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.AudioDb +{ + public class AudioDbOtherArtistExternalId : IExternalId + { + /// + public string ProviderName => "TheAudioDb"; + + /// + public string Key => MetadataProvider.AudioDbArtist.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist; + + /// + public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum; + } +} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs deleted file mode 100644 index 1cc1f0fa1..000000000 --- a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs +++ /dev/null @@ -1,81 +0,0 @@ -#pragma warning disable CS1591 - -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; - -namespace MediaBrowser.Providers.Plugins.AudioDb -{ - public class AudioDbAlbumExternalId : IExternalId - { - /// - public string ProviderName => "TheAudioDb"; - - /// - public string Key => MetadataProvider.AudioDbAlbum.ToString(); - - /// - public ExternalIdMediaType? Type => null; - - /// - public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is MusicAlbum; - } - - public class AudioDbOtherAlbumExternalId : IExternalId - { - /// - public string ProviderName => "TheAudioDb"; - - /// - public string Key => MetadataProvider.AudioDbAlbum.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Album; - - /// - public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Audio; - } - - public class AudioDbArtistExternalId : IExternalId - { - /// - public string ProviderName => "TheAudioDb"; - - /// - public string Key => MetadataProvider.AudioDbArtist.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Artist; - - /// - public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is MusicArtist; - } - - public class AudioDbOtherArtistExternalId : IExternalId - { - /// - public string ProviderName => "TheAudioDb"; - - /// - public string Key => MetadataProvider.AudioDbArtist.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist; - - /// - public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum; - } -} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs index 54054d015..b5bd72ff0 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs @@ -11,6 +11,12 @@ namespace MediaBrowser.Providers.Plugins.AudioDb { public class Plugin : BasePlugin, IHasWebPages { + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + public static Plugin Instance { get; private set; } public override Guid Id => new Guid("a629c0da-fac5-4c7e-931a-7174223f14c8"); @@ -22,12 +28,6 @@ namespace MediaBrowser.Providers.Plugins.AudioDb // TODO remove when plugin removed from server. public override string ConfigurationFileName => "Jellyfin.Plugin.AudioDb.xml"; - public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) - : base(applicationPaths, xmlSerializer) - { - Instance = this; - } - public IEnumerable GetPages() { yield return new PluginPageInfo diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs deleted file mode 100644 index 8414c9328..000000000 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs +++ /dev/null @@ -1,790 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using MediaBrowser.Common; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; -using MediaBrowser.Providers.Plugins.MusicBrainz; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Providers.Music -{ - public class MusicBrainzAlbumProvider : IRemoteMetadataProvider, IHasOrder - { - /// - /// 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 - /// - private readonly long _musicBrainzQueryIntervalMs; - - /// - /// For each single MB lookup/search, this is the maximum number of - /// attempts that shall be made whilst receiving a 503 Server - /// Unavailable (indicating throttled) response. - /// - private const uint MusicBrainzQueryAttempts = 5u; - - internal static MusicBrainzAlbumProvider Current; - - private readonly IHttpClientFactory _httpClientFactory; - private readonly IApplicationHost _appHost; - private readonly ILogger _logger; - - private readonly string _musicBrainzBaseUrl; - - private Stopwatch _stopWatchMusicBrainz = new Stopwatch(); - - public MusicBrainzAlbumProvider( - IHttpClientFactory httpClientFactory, - IApplicationHost appHost, - ILogger logger) - { - _httpClientFactory = httpClientFactory; - _appHost = appHost; - _logger = logger; - - _musicBrainzBaseUrl = Plugin.Instance.Configuration.Server; - _musicBrainzQueryIntervalMs = Plugin.Instance.Configuration.RateLimit; - - // Use a stopwatch to ensure we don't exceed the MusicBrainz rate limit - _stopWatchMusicBrainz.Start(); - - Current = this; - } - - /// - public string Name => "MusicBrainz"; - - /// - public int Order => 0; - - /// - public async Task> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken) - { - // TODO maybe remove when artist metadata can be disabled - if (!Plugin.Instance.Configuration.Enable) - { - return Enumerable.Empty(); - } - - var releaseId = searchInfo.GetReleaseId(); - var releaseGroupId = searchInfo.GetReleaseGroupId(); - - string url; - - if (!string.IsNullOrEmpty(releaseId)) - { - url = "/ws/2/release/?query=reid:" + releaseId.ToString(CultureInfo.InvariantCulture); - } - else if (!string.IsNullOrEmpty(releaseGroupId)) - { - url = "/ws/2/release?release-group=" + releaseGroupId.ToString(CultureInfo.InvariantCulture); - } - else - { - var artistMusicBrainzId = searchInfo.GetMusicBrainzArtistId(); - - if (!string.IsNullOrWhiteSpace(artistMusicBrainzId)) - { - url = string.Format( - CultureInfo.InvariantCulture, - "/ws/2/release/?query=\"{0}\" AND arid:{1}", - WebUtility.UrlEncode(searchInfo.Name), - artistMusicBrainzId); - } - else - { - // I'm sure there is a better way but for now it resolves search for 12" Mixes - var queryName = searchInfo.Name.Replace("\"", string.Empty); - - url = string.Format( - CultureInfo.InvariantCulture, - "/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"", - WebUtility.UrlEncode(queryName), - WebUtility.UrlEncode(searchInfo.GetAlbumArtist())); - } - } - - if (!string.IsNullOrWhiteSpace(url)) - { - using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().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)) - { - var results = ReleaseResult.Parse(reader); - - return results.Select(i => - { - var result = new RemoteSearchResult - { - Name = i.Title, - ProductionYear = i.Year - }; - - if (i.Artists.Count > 0) - { - result.AlbumArtist = new RemoteSearchResult - { - SearchProviderName = Name, - Name = i.Artists[0].Item1 - }; - - result.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, i.Artists[0].Item2); - } - - if (!string.IsNullOrWhiteSpace(i.ReleaseId)) - { - result.SetProviderId(MetadataProvider.MusicBrainzAlbum, i.ReleaseId); - } - - if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId)) - { - result.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, i.ReleaseGroupId); - } - - return result; - }); - } - } - } - - /// - public async Task> GetMetadata(AlbumInfo id, CancellationToken cancellationToken) - { - var releaseId = id.GetReleaseId(); - var releaseGroupId = id.GetReleaseGroupId(); - - var result = new MetadataResult - { - Item = new MusicAlbum() - }; - - // TODO maybe remove when artist metadata can be disabled - if (!Plugin.Instance.Configuration.Enable) - { - return result; - } - - // If we have a release group Id but not a release Id... - if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId)) - { - releaseId = await GetReleaseIdFromReleaseGroupId(releaseGroupId, cancellationToken).ConfigureAwait(false); - result.HasMetadata = true; - } - - if (string.IsNullOrWhiteSpace(releaseId)) - { - var artistMusicBrainzId = id.GetMusicBrainzArtistId(); - - var releaseResult = await GetReleaseResult(artistMusicBrainzId, id.GetAlbumArtist(), id.Name, cancellationToken).ConfigureAwait(false); - - if (releaseResult != null) - { - if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseId)) - { - releaseId = releaseResult.ReleaseId; - result.HasMetadata = true; - } - - if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseGroupId)) - { - releaseGroupId = releaseResult.ReleaseGroupId; - result.HasMetadata = true; - } - - result.Item.ProductionYear = releaseResult.Year; - result.Item.Overview = releaseResult.Overview; - } - } - - // If we have a release Id but not a release group Id... - if (!string.IsNullOrWhiteSpace(releaseId) && string.IsNullOrWhiteSpace(releaseGroupId)) - { - releaseGroupId = await GetReleaseGroupFromReleaseId(releaseId, cancellationToken).ConfigureAwait(false); - result.HasMetadata = true; - } - - if (!string.IsNullOrWhiteSpace(releaseId) || !string.IsNullOrWhiteSpace(releaseGroupId)) - { - result.HasMetadata = true; - } - - if (result.HasMetadata) - { - if (!string.IsNullOrEmpty(releaseId)) - { - result.Item.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseId); - } - - if (!string.IsNullOrEmpty(releaseGroupId)) - { - result.Item.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, releaseGroupId); - } - } - - return result; - } - - private Task GetReleaseResult(string artistMusicBrainId, string artistName, string albumName, CancellationToken cancellationToken) - { - if (!string.IsNullOrEmpty(artistMusicBrainId)) - { - return GetReleaseResult(albumName, artistMusicBrainId, cancellationToken); - } - - if (string.IsNullOrWhiteSpace(artistName)) - { - return Task.FromResult(new ReleaseResult()); - } - - return GetReleaseResultByArtistName(albumName, artistName, cancellationToken); - } - - private async Task GetReleaseResult(string albumName, string artistId, CancellationToken cancellationToken) - { - var url = string.Format(CultureInfo.InvariantCulture, "/ws/2/release/?query=\"{0}\" AND arid:{1}", - WebUtility.UrlEncode(albumName), - artistId); - - using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - 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); - return ReleaseResult.Parse(reader).FirstOrDefault(); - } - - private async Task GetReleaseResultByArtistName(string albumName, string artistName, CancellationToken cancellationToken) - { - var url = string.Format( - CultureInfo.InvariantCulture, - "/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"", - WebUtility.UrlEncode(albumName), - WebUtility.UrlEncode(artistName)); - - using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - 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); - 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 ValueTuple ParseArtistCredit(XmlReader reader) - { - 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-credit": - { - using (var subReader = reader.ReadSubtree()) - { - return ParseArtistNameCredit(subReader); - } - } - - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - - return new ValueTuple(); - } - - private static (string, string) ParseArtistNameCredit(XmlReader reader) - { - 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 "artist": - { - var id = reader.GetAttribute("id"); - using (var subReader = reader.ReadSubtree()) - { - return ParseArtistArtistCredit(subReader, id); - } - } - - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - - return (null, null); - } - - private static (string name, string id) ParseArtistArtistCredit(XmlReader reader, string artistId) - { - reader.MoveToContent(); - reader.Read(); - - string name = null; - - // 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": - { - name = reader.ReadElementContentAsString(); - break; - } - - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - - return (name, artistId); - } - - private async Task GetReleaseIdFromReleaseGroupId(string releaseGroupId, CancellationToken cancellationToken) - { - var url = "/ws/2/release?release-group=" + releaseGroupId.ToString(CultureInfo.InvariantCulture); - - using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - 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); - var result = ReleaseResult.Parse(reader).FirstOrDefault(); - - return result?.ReleaseId; - } - - /// - /// Gets the release group id internal. - /// - /// The release entry id. - /// The cancellation token. - /// Task{System.String}. - private async Task GetReleaseGroupFromReleaseId(string releaseEntryId, CancellationToken cancellationToken) - { - var url = "/ws/2/release-group/?query=reid:" + releaseEntryId.ToString(CultureInfo.InvariantCulture); - - using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - 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 "release-group-list": - { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - - using (var subReader = reader.ReadSubtree()) - { - return GetFirstReleaseGroupId(subReader); - } - } - - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - - return null; - } - } - - private string GetFirstReleaseGroupId(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-group": - { - return reader.GetAttribute("id"); - } - - default: - { - reader.Skip(); - break; - } - } - } - else - { - reader.Read(); - } - } - - return null; - } - - /// - /// Makes request to MusicBrainz server and awaits a response. - /// A 503 Service Unavailable response indicates throttling to maintain a rate limit. - /// A number of retries shall be made in order to try and satisfy the request before - /// giving up and returning null. - /// - internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken) - { - using var options = new HttpRequestMessage(HttpMethod.Get, _musicBrainzBaseUrl.TrimEnd('/') + url); - - // MusicBrainz request a contact email address is supplied, as comment, in user agent field: - // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent - options.Headers.UserAgent.ParseAdd(string.Format( - CultureInfo.InvariantCulture, - "{0} ( {1} )", - _appHost.ApplicationUserAgent, - _appHost.ApplicationUserAgentAddress)); - - HttpResponseMessage response; - var attempts = 0u; - - do - { - attempts++; - - if (_stopWatchMusicBrainz.ElapsedMilliseconds < _musicBrainzQueryIntervalMs) - { - // MusicBrainz is extremely adamant about limiting to one request per second - var delayMs = _musicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds; - await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false); - } - - // Write time since last request to debug log as evidence we're meeting rate limit - // requirement, before resetting stopwatch back to zero. - _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds); - _stopWatchMusicBrainz.Restart(); - - response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options).ConfigureAwait(false); - - // We retry a finite number of times, and only whilst MB is indicating 503 (throttling) - } - while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable); - - // Log error if unable to query MB database due to throttling - if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable) - { - _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, options.RequestUri); - } - - return response; - } - - /// - public Task GetImageResponse(string url, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - } -} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs new file mode 100644 index 000000000..46f8988f2 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -0,0 +1,791 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Diagnostics; +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 MediaBrowser.Common; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Plugins.MusicBrainz; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Providers.Music +{ + public class MusicBrainzAlbumProvider : IRemoteMetadataProvider, IHasOrder + { + /// + /// For each single MB lookup/search, this is the maximum number of + /// attempts that shall be made whilst receiving a 503 Server + /// Unavailable (indicating throttled) response. + /// + private const uint MusicBrainzQueryAttempts = 5u; + + /// + /// 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 + /// + private readonly long _musicBrainzQueryIntervalMs; + + private readonly IHttpClientFactory _httpClientFactory; + private readonly IApplicationHost _appHost; + private readonly ILogger _logger; + + private readonly string _musicBrainzBaseUrl; + + private Stopwatch _stopWatchMusicBrainz = new Stopwatch(); + + public MusicBrainzAlbumProvider( + IHttpClientFactory httpClientFactory, + IApplicationHost appHost, + ILogger logger) + { + _httpClientFactory = httpClientFactory; + _appHost = appHost; + _logger = logger; + + _musicBrainzBaseUrl = Plugin.Instance.Configuration.Server; + _musicBrainzQueryIntervalMs = Plugin.Instance.Configuration.RateLimit; + + // Use a stopwatch to ensure we don't exceed the MusicBrainz rate limit + _stopWatchMusicBrainz.Start(); + + Current = this; + } + + internal static MusicBrainzAlbumProvider Current { get; private set; } + + /// + public string Name => "MusicBrainz"; + + /// + public int Order => 0; + + /// + public async Task> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken) + { + // TODO maybe remove when artist metadata can be disabled + if (!Plugin.Instance.Configuration.Enable) + { + return Enumerable.Empty(); + } + + var releaseId = searchInfo.GetReleaseId(); + var releaseGroupId = searchInfo.GetReleaseGroupId(); + + string url; + + if (!string.IsNullOrEmpty(releaseId)) + { + url = "/ws/2/release/?query=reid:" + releaseId.ToString(CultureInfo.InvariantCulture); + } + else if (!string.IsNullOrEmpty(releaseGroupId)) + { + url = "/ws/2/release?release-group=" + releaseGroupId.ToString(CultureInfo.InvariantCulture); + } + else + { + var artistMusicBrainzId = searchInfo.GetMusicBrainzArtistId(); + + if (!string.IsNullOrWhiteSpace(artistMusicBrainzId)) + { + url = string.Format( + CultureInfo.InvariantCulture, + "/ws/2/release/?query=\"{0}\" AND arid:{1}", + WebUtility.UrlEncode(searchInfo.Name), + artistMusicBrainzId); + } + else + { + // I'm sure there is a better way but for now it resolves search for 12" Mixes + var queryName = searchInfo.Name.Replace("\"", string.Empty, StringComparison.Ordinal); + + url = string.Format( + CultureInfo.InvariantCulture, + "/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"", + WebUtility.UrlEncode(queryName), + WebUtility.UrlEncode(searchInfo.GetAlbumArtist())); + } + } + + if (!string.IsNullOrWhiteSpace(url)) + { + using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync().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)) + { + var results = ReleaseResult.Parse(reader); + + return results.Select(i => + { + var result = new RemoteSearchResult + { + Name = i.Title, + ProductionYear = i.Year + }; + + if (i.Artists.Count > 0) + { + result.AlbumArtist = new RemoteSearchResult + { + SearchProviderName = Name, + Name = i.Artists[0].Item1 + }; + + result.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, i.Artists[0].Item2); + } + + if (!string.IsNullOrWhiteSpace(i.ReleaseId)) + { + result.SetProviderId(MetadataProvider.MusicBrainzAlbum, i.ReleaseId); + } + + if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId)) + { + result.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, i.ReleaseGroupId); + } + + return result; + }); + } + } + } + + /// + public async Task> GetMetadata(AlbumInfo id, CancellationToken cancellationToken) + { + var releaseId = id.GetReleaseId(); + var releaseGroupId = id.GetReleaseGroupId(); + + var result = new MetadataResult + { + Item = new MusicAlbum() + }; + + // TODO maybe remove when artist metadata can be disabled + if (!Plugin.Instance.Configuration.Enable) + { + return result; + } + + // If we have a release group Id but not a release Id... + if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId)) + { + releaseId = await GetReleaseIdFromReleaseGroupId(releaseGroupId, cancellationToken).ConfigureAwait(false); + result.HasMetadata = true; + } + + if (string.IsNullOrWhiteSpace(releaseId)) + { + var artistMusicBrainzId = id.GetMusicBrainzArtistId(); + + var releaseResult = await GetReleaseResult(artistMusicBrainzId, id.GetAlbumArtist(), id.Name, cancellationToken).ConfigureAwait(false); + + if (releaseResult != null) + { + if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseId)) + { + releaseId = releaseResult.ReleaseId; + result.HasMetadata = true; + } + + if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseGroupId)) + { + releaseGroupId = releaseResult.ReleaseGroupId; + result.HasMetadata = true; + } + + result.Item.ProductionYear = releaseResult.Year; + result.Item.Overview = releaseResult.Overview; + } + } + + // If we have a release Id but not a release group Id... + if (!string.IsNullOrWhiteSpace(releaseId) && string.IsNullOrWhiteSpace(releaseGroupId)) + { + releaseGroupId = await GetReleaseGroupFromReleaseId(releaseId, cancellationToken).ConfigureAwait(false); + result.HasMetadata = true; + } + + if (!string.IsNullOrWhiteSpace(releaseId) || !string.IsNullOrWhiteSpace(releaseGroupId)) + { + result.HasMetadata = true; + } + + if (result.HasMetadata) + { + if (!string.IsNullOrEmpty(releaseId)) + { + result.Item.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseId); + } + + if (!string.IsNullOrEmpty(releaseGroupId)) + { + result.Item.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, releaseGroupId); + } + } + + return result; + } + + private Task GetReleaseResult(string artistMusicBrainId, string artistName, string albumName, CancellationToken cancellationToken) + { + if (!string.IsNullOrEmpty(artistMusicBrainId)) + { + return GetReleaseResult(albumName, artistMusicBrainId, cancellationToken); + } + + if (string.IsNullOrWhiteSpace(artistName)) + { + return Task.FromResult(new ReleaseResult()); + } + + return GetReleaseResultByArtistName(albumName, artistName, cancellationToken); + } + + private async Task GetReleaseResult(string albumName, string artistId, CancellationToken cancellationToken) + { + var url = string.Format( + CultureInfo.InvariantCulture, + "/ws/2/release/?query=\"{0}\" AND arid:{1}", + WebUtility.UrlEncode(albumName), + artistId); + + using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + 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); + return ReleaseResult.Parse(reader).FirstOrDefault(); + } + + private async Task GetReleaseResultByArtistName(string albumName, string artistName, CancellationToken cancellationToken) + { + var url = string.Format( + CultureInfo.InvariantCulture, + "/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"", + WebUtility.UrlEncode(albumName), + WebUtility.UrlEncode(artistName)); + + using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + 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); + 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(); + 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-credit": + { + using (var subReader = reader.ReadSubtree()) + { + return ParseArtistNameCredit(subReader); + } + } + + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return default; + } + + private static (string, string) ParseArtistNameCredit(XmlReader reader) + { + 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 "artist": + { + var id = reader.GetAttribute("id"); + using (var subReader = reader.ReadSubtree()) + { + return ParseArtistArtistCredit(subReader, id); + } + } + + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return (null, null); + } + + private static (string name, string id) ParseArtistArtistCredit(XmlReader reader, string artistId) + { + reader.MoveToContent(); + reader.Read(); + + string name = null; + + // 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": + { + name = reader.ReadElementContentAsString(); + break; + } + + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return (name, artistId); + } + + private async Task GetReleaseIdFromReleaseGroupId(string releaseGroupId, CancellationToken cancellationToken) + { + var url = "/ws/2/release?release-group=" + releaseGroupId.ToString(CultureInfo.InvariantCulture); + + using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + 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); + var result = ReleaseResult.Parse(reader).FirstOrDefault(); + + return result?.ReleaseId; + } + + /// + /// Gets the release group id internal. + /// + /// The release entry id. + /// The cancellation token. + /// Task{System.String}. + private async Task GetReleaseGroupFromReleaseId(string releaseEntryId, CancellationToken cancellationToken) + { + var url = "/ws/2/release-group/?query=reid:" + releaseEntryId.ToString(CultureInfo.InvariantCulture); + + using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + 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 "release-group-list": + { + if (reader.IsEmptyElement) + { + reader.Read(); + continue; + } + + using (var subReader = reader.ReadSubtree()) + { + return GetFirstReleaseGroupId(subReader); + } + } + + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return null; + } + } + + private string GetFirstReleaseGroupId(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-group": + { + return reader.GetAttribute("id"); + } + + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return null; + } + + /// + /// Makes request to MusicBrainz server and awaits a response. + /// A 503 Service Unavailable response indicates throttling to maintain a rate limit. + /// A number of retries shall be made in order to try and satisfy the request before + /// giving up and returning null. + /// + internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken) + { + using var options = new HttpRequestMessage(HttpMethod.Get, _musicBrainzBaseUrl.TrimEnd('/') + url); + + // MusicBrainz request a contact email address is supplied, as comment, in user agent field: + // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent + options.Headers.UserAgent.ParseAdd(string.Format( + CultureInfo.InvariantCulture, + "{0} ( {1} )", + _appHost.ApplicationUserAgent, + _appHost.ApplicationUserAgentAddress)); + + HttpResponseMessage response; + var attempts = 0u; + + do + { + attempts++; + + if (_stopWatchMusicBrainz.ElapsedMilliseconds < _musicBrainzQueryIntervalMs) + { + // MusicBrainz is extremely adamant about limiting to one request per second + var delayMs = _musicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds; + await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false); + } + + // Write time since last request to debug log as evidence we're meeting rate limit + // requirement, before resetting stopwatch back to zero. + _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds); + _stopWatchMusicBrainz.Restart(); + + response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options).ConfigureAwait(false); + + // We retry a finite number of times, and only whilst MB is indicating 503 (throttling) + } + while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable); + + // Log error if unable to query MB database due to throttling + if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable) + { + _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, options.RequestUri); + } + + return response; + } + + /// + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs index 8b8fea09e..8f4240dc1 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs @@ -36,6 +36,12 @@ namespace MediaBrowser.Providers.Plugins.Omdb _appHost = appHost; } + public string Name => "The Open Movie Database"; + + // After other internet providers, because they're better + // But before fallback providers like screengrab + public int Order => 90; + public IEnumerable GetSupportedImages(BaseItem item) { return new List @@ -86,15 +92,9 @@ namespace MediaBrowser.Providers.Plugins.Omdb return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } - public string Name => "The Open Movie Database"; - public bool Supports(BaseItem item) { return item is Movie || item is Trailer || item is Episode; } - - // After other internet providers, because they're better - // But before fallback providers like screengrab - public int Order => 90; } } diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs index d53eba7e9..705359d2c 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs @@ -49,6 +49,8 @@ namespace MediaBrowser.Providers.Plugins.Omdb _appHost = appHost; } + public string Name => "The Open Movie Database"; + // After primary option public int Order => 2; @@ -199,8 +201,6 @@ namespace MediaBrowser.Providers.Plugins.Omdb return GetSearchResults(searchInfo, "movie", cancellationToken); } - public string Name => "The Open Movie Database"; - public async Task> GetMetadata(SeriesInfo info, CancellationToken cancellationToken) { var result = new MetadataResult @@ -263,14 +263,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb { var results = await GetSearchResultsInternal(info, "movie", false, cancellationToken).ConfigureAwait(false); var first = results.FirstOrDefault(); - return first == null ? null : first.GetProviderId(MetadataProvider.Imdb); + return first?.GetProviderId(MetadataProvider.Imdb); } private async Task GetSeriesImdbId(SeriesInfo info, CancellationToken cancellationToken) { var results = await GetSearchResultsInternal(info, "series", false, cancellationToken).ConfigureAwait(false); var first = results.FirstOrDefault(); - return first == null ? null : first.GetProviderId(MetadataProvider.Imdb); + return first?.GetProviderId(MetadataProvider.Imdb); } public Task GetImageResponse(string url, CancellationToken cancellationToken) @@ -278,7 +278,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } - class SearchResult + private class SearchResult { public string Title { get; set; } diff --git a/MediaBrowser.Providers/TV/DummySeasonProvider.cs b/MediaBrowser.Providers/TV/DummySeasonProvider.cs index 0c09cdef6..a0f7f6cfd 100644 --- a/MediaBrowser.Providers/TV/DummySeasonProvider.cs +++ b/MediaBrowser.Providers/TV/DummySeasonProvider.cs @@ -124,7 +124,8 @@ namespace MediaBrowser.Providers.TV /// /// Adds the season. /// - public async Task AddSeason(Series series, + public async Task AddSeason( + Series series, int? seasonNumber, bool isVirtualItem, CancellationToken cancellationToken) @@ -211,11 +212,14 @@ namespace MediaBrowser.Providers.TV { _logger.LogInformation("Removing virtual season {0} {1}", series.Name, seasonToRemove.IndexNumber); - _libraryManager.DeleteItem(seasonToRemove, new DeleteOptions - { - DeleteFileLocation = true + _libraryManager.DeleteItem( + seasonToRemove, + new DeleteOptions + { + DeleteFileLocation = true - }, false); + }, + false); hasChanges = true; } diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs index 09850beb0..616c61ec0 100644 --- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs @@ -159,7 +159,7 @@ namespace MediaBrowser.Providers.TV var now = DateTime.UtcNow.AddDays(-UnairedEpisodeThresholdDays); - if (airDate < now && addMissingEpisodes || airDate > now) + if ((airDate < now && addMissingEpisodes) || airDate > now) { // tvdb has a lot of nearly blank episodes _logger.LogInformation("Creating virtual missing/unaired episode {0} {1}x{2}", series.Name, tuple.seasonNumber, tuple.episodenumber); @@ -232,10 +232,13 @@ namespace MediaBrowser.Providers.TV foreach (var episodeToRemove in episodesToRemove) { - _libraryManager.DeleteItem(episodeToRemove, new DeleteOptions - { - DeleteFileLocation = true - }, false); + _libraryManager.DeleteItem( + episodeToRemove, + new DeleteOptions + { + DeleteFileLocation = true + }, + false); hasChanges = true; } @@ -246,7 +249,7 @@ namespace MediaBrowser.Providers.TV /// /// Removes the obsolete or missing seasons. /// - /// + /// All recursive children. /// The episode lookup. /// . private bool RemoveObsoleteOrMissingSeasons( @@ -297,10 +300,13 @@ namespace MediaBrowser.Providers.TV foreach (var seasonToRemove in seasonsToRemove) { - _libraryManager.DeleteItem(seasonToRemove, new DeleteOptions - { - DeleteFileLocation = true - }, false); + _libraryManager.DeleteItem( + seasonToRemove, + new DeleteOptions + { + DeleteFileLocation = true + }, + false); hasChanges = true; } @@ -354,7 +360,10 @@ namespace MediaBrowser.Providers.TV /// /// /// Episode. - private Episode GetExistingEpisode(IEnumerable existingEpisodes, IReadOnlyDictionary seasonCounts, (int seasonNumber, int episodeNumber, DateTime firstAired) episodeTuple) + private Episode GetExistingEpisode( + IEnumerable existingEpisodes, + IReadOnlyDictionary seasonCounts, + (int seasonNumber, int episodeNumber, DateTime firstAired) episodeTuple) { var seasonNumber = episodeTuple.seasonNumber; var episodeNumber = episodeTuple.episodeNumber; diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs deleted file mode 100644 index a6040edd1..000000000 --- a/MediaBrowser.Providers/TV/TvExternalIds.cs +++ /dev/null @@ -1,82 +0,0 @@ -#pragma warning disable CS1591 - -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Providers; -using MediaBrowser.Providers.Plugins.TheTvdb; - -namespace MediaBrowser.Providers.TV -{ - public class Zap2ItExternalId : IExternalId - { - /// - public string ProviderName => "Zap2It"; - - /// - public string Key => MetadataProvider.Zap2It.ToString(); - - /// - public ExternalIdMediaType? Type => null; - - /// - public string UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Series; - } - - public class TvdbExternalId : IExternalId - { - /// - public string ProviderName => "TheTVDB"; - - /// - public string Key => MetadataProvider.Tvdb.ToString(); - - /// - public ExternalIdMediaType? Type => null; - - /// - public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=series&id={0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Series; - } - - public class TvdbSeasonExternalId : IExternalId - { - /// - public string ProviderName => "TheTVDB"; - - /// - public string Key => MetadataProvider.Tvdb.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Season; - - /// - public string UrlFormatString => null; - - /// - public bool Supports(IHasProviderIds item) => item is Season; - } - - public class TvdbEpisodeExternalId : IExternalId - { - /// - public string ProviderName => "TheTVDB"; - - /// - public string Key => MetadataProvider.Tvdb.ToString(); - - /// - public ExternalIdMediaType? Type => ExternalIdMediaType.Episode; - - /// - public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=episode&id={0}"; - - /// - public bool Supports(IHasProviderIds item) => item is Episode; - } -} diff --git a/MediaBrowser.Providers/TV/TvdbEpisodeExternalId.cs b/MediaBrowser.Providers/TV/TvdbEpisodeExternalId.cs new file mode 100644 index 000000000..40c5f2d78 --- /dev/null +++ b/MediaBrowser.Providers/TV/TvdbEpisodeExternalId.cs @@ -0,0 +1,28 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Plugins.TheTvdb; + +namespace MediaBrowser.Providers.TV +{ + public class TvdbEpisodeExternalId : IExternalId + { + /// + public string ProviderName => "TheTVDB"; + + /// + public string Key => MetadataProvider.Tvdb.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.Episode; + + /// + public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=episode&id={0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Episode; + } +} diff --git a/MediaBrowser.Providers/TV/TvdbExternalId.cs b/MediaBrowser.Providers/TV/TvdbExternalId.cs new file mode 100644 index 000000000..4c54de9f8 --- /dev/null +++ b/MediaBrowser.Providers/TV/TvdbExternalId.cs @@ -0,0 +1,28 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Plugins.TheTvdb; + +namespace MediaBrowser.Providers.TV +{ + public class TvdbExternalId : IExternalId + { + /// + public string ProviderName => "TheTVDB"; + + /// + public string Key => MetadataProvider.Tvdb.ToString(); + + /// + public ExternalIdMediaType? Type => null; + + /// + public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=series&id={0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Series; + } +} diff --git a/MediaBrowser.Providers/TV/TvdbSeasonExternalId.cs b/MediaBrowser.Providers/TV/TvdbSeasonExternalId.cs new file mode 100644 index 000000000..807ebb3ee --- /dev/null +++ b/MediaBrowser.Providers/TV/TvdbSeasonExternalId.cs @@ -0,0 +1,28 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Plugins.TheTvdb; + +namespace MediaBrowser.Providers.TV +{ + public class TvdbSeasonExternalId : IExternalId + { + /// + public string ProviderName => "TheTVDB"; + + /// + public string Key => MetadataProvider.Tvdb.ToString(); + + /// + public ExternalIdMediaType? Type => ExternalIdMediaType.Season; + + /// + public string UrlFormatString => null; + + /// + public bool Supports(IHasProviderIds item) => item is Season; + } +} diff --git a/MediaBrowser.Providers/TV/Zap2ItExternalId.cs b/MediaBrowser.Providers/TV/Zap2ItExternalId.cs new file mode 100644 index 000000000..c9f314af9 --- /dev/null +++ b/MediaBrowser.Providers/TV/Zap2ItExternalId.cs @@ -0,0 +1,28 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Plugins.TheTvdb; + +namespace MediaBrowser.Providers.TV +{ + public class Zap2ItExternalId : IExternalId + { + /// + public string ProviderName => "Zap2It"; + + /// + public string Key => MetadataProvider.Zap2It.ToString(); + + /// + public ExternalIdMediaType? Type => null; + + /// + public string UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}"; + + /// + public bool Supports(IHasProviderIds item) => item is Series; + } +} -- cgit v1.2.3