diff options
Diffstat (limited to 'MediaBrowser.Providers')
83 files changed, 2388 insertions, 1348 deletions
diff --git a/MediaBrowser.Providers/Books/AudioBookMetadataService.cs b/MediaBrowser.Providers/Books/AudioBookMetadataService.cs index 96e1165b6..845f53493 100644 --- a/MediaBrowser.Providers/Books/AudioBookMetadataService.cs +++ b/MediaBrowser.Providers/Books/AudioBookMetadataService.cs @@ -1,50 +1,64 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Books +namespace MediaBrowser.Providers.Books; + +/// <summary> +/// Service to manage audiobook metadata. +/// </summary> +public class AudioBookMetadataService : MetadataService<AudioBook, SongInfo> { - public class AudioBookMetadataService : MetadataService<AudioBook, SongInfo> + /// <summary> + /// Initializes a new instance of the <see cref="AudioBookMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public AudioBookMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<AudioBookMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public AudioBookMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<AudioBookMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } + } - /// <inheritdoc /> - protected override void MergeData( - MetadataResult<AudioBook> source, - MetadataResult<AudioBook> target, - MetadataField[] lockedFields, - bool replaceData, - bool mergeMetadataSettings) - { - base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + /// <inheritdoc /> + protected override void MergeData( + MetadataResult<AudioBook> source, + MetadataResult<AudioBook> target, + MetadataField[] lockedFields, + bool replaceData, + bool mergeMetadataSettings) + { + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); - var sourceItem = source.Item; - var targetItem = target.Item; + var sourceItem = source.Item; + var targetItem = target.Item; - if (replaceData || targetItem.Artists.Count == 0) - { - targetItem.Artists = sourceItem.Artists; - } + if (replaceData || targetItem.Artists.Count == 0) + { + targetItem.Artists = sourceItem.Artists; + } - if (replaceData || string.IsNullOrEmpty(targetItem.Album)) - { - targetItem.Album = sourceItem.Album; - } + if (replaceData || string.IsNullOrEmpty(targetItem.Album)) + { + targetItem.Album = sourceItem.Album; } } } diff --git a/MediaBrowser.Providers/Books/BookMetadataService.cs b/MediaBrowser.Providers/Books/BookMetadataService.cs index 50b9922c6..3b8dbfa7b 100644 --- a/MediaBrowser.Providers/Books/BookMetadataService.cs +++ b/MediaBrowser.Providers/Books/BookMetadataService.cs @@ -1,37 +1,51 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Books +namespace MediaBrowser.Providers.Books; + +/// <summary> +/// Service to manage book metadata. +/// </summary> +public class BookMetadataService : MetadataService<Book, BookInfo> { - public class BookMetadataService : MetadataService<Book, BookInfo> + /// <summary> + /// Initializes a new instance of the <see cref="BookMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public BookMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<BookMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public BookMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<BookMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } + } - /// <inheritdoc /> - protected override void MergeData(MetadataResult<Book> source, MetadataResult<Book> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + /// <inheritdoc /> + protected override void MergeData(MetadataResult<Book> source, MetadataResult<Book> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) + { + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); - if (replaceData || string.IsNullOrEmpty(target.Item.SeriesName)) - { - target.Item.SeriesName = source.Item.SeriesName; - } + if (replaceData || string.IsNullOrEmpty(target.Item.SeriesName)) + { + target.Item.SeriesName = source.Item.SeriesName; } } } diff --git a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs index b51ab4c08..1cb6bf234 100644 --- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs +++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs @@ -1,85 +1,99 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using System.Linq; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.BoxSets +namespace MediaBrowser.Providers.BoxSets; + +/// <summary> +/// Service to manage boxset metadata. +/// </summary> +public class BoxSetMetadataService : MetadataService<BoxSet, BoxSetInfo> { - public class BoxSetMetadataService : MetadataService<BoxSet, BoxSetInfo> + /// <summary> + /// Initializes a new instance of the <see cref="BoxSetMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public BoxSetMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<BoxSetMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public BoxSetMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<BoxSetMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } + } - /// <inheritdoc /> - protected override bool EnableUpdatingGenresFromChildren => true; + /// <inheritdoc /> + protected override bool EnableUpdatingGenresFromChildren => true; - /// <inheritdoc /> - protected override bool EnableUpdatingOfficialRatingFromChildren => true; + /// <inheritdoc /> + protected override bool EnableUpdatingOfficialRatingFromChildren => true; - /// <inheritdoc /> - protected override bool EnableUpdatingStudiosFromChildren => true; + /// <inheritdoc /> + protected override bool EnableUpdatingStudiosFromChildren => true; - /// <inheritdoc /> - protected override bool EnableUpdatingPremiereDateFromChildren => true; + /// <inheritdoc /> + protected override bool EnableUpdatingPremiereDateFromChildren => true; - /// <inheritdoc /> - protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(BoxSet item) - { - return item.GetLinkedChildren(); - } + /// <inheritdoc /> + protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(BoxSet item) + { + return item.GetLinkedChildren(); + } - /// <inheritdoc /> - protected override void MergeData(MetadataResult<BoxSet> source, MetadataResult<BoxSet> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + /// <inheritdoc /> + protected override void MergeData(MetadataResult<BoxSet> source, MetadataResult<BoxSet> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) + { + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); - var sourceItem = source.Item; - var targetItem = target.Item; + var sourceItem = source.Item; + var targetItem = target.Item; - if (mergeMetadataSettings) + if (mergeMetadataSettings) + { + if (replaceData || targetItem.LinkedChildren.Length == 0) + { + targetItem.LinkedChildren = sourceItem.LinkedChildren; + } + else { - if (replaceData || targetItem.LinkedChildren.Length == 0) - { - targetItem.LinkedChildren = sourceItem.LinkedChildren; - } - else - { - targetItem.LinkedChildren = sourceItem.LinkedChildren.Concat(targetItem.LinkedChildren).Distinct().ToArray(); - } + targetItem.LinkedChildren = sourceItem.LinkedChildren.Concat(targetItem.LinkedChildren).Distinct().ToArray(); } } + } - /// <inheritdoc /> - protected override ItemUpdateType BeforeSaveInternal(BoxSet item, bool isFullRefresh, ItemUpdateType updateType) - { - var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType); - - var libraryFolderIds = item.GetLibraryFolderIds(); + /// <inheritdoc /> + protected override ItemUpdateType BeforeSaveInternal(BoxSet item, bool isFullRefresh, ItemUpdateType updateType) + { + var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType); - var itemLibraryFolderIds = item.LibraryFolderIds; - if (itemLibraryFolderIds is null || !libraryFolderIds.SequenceEqual(itemLibraryFolderIds)) - { - item.LibraryFolderIds = libraryFolderIds; - updatedType |= ItemUpdateType.MetadataImport; - } + var libraryFolderIds = item.GetLibraryFolderIds(); - return updatedType; + var itemLibraryFolderIds = item.LibraryFolderIds; + if (itemLibraryFolderIds is null || !libraryFolderIds.SequenceEqual(itemLibraryFolderIds)) + { + item.LibraryFolderIds = libraryFolderIds; + updatedType |= ItemUpdateType.MetadataImport; } + + return updatedType; } } diff --git a/MediaBrowser.Providers/Channels/ChannelMetadataService.cs b/MediaBrowser.Providers/Channels/ChannelMetadataService.cs index 0267fa13f..7e9b694b3 100644 --- a/MediaBrowser.Providers/Channels/ChannelMetadataService.cs +++ b/MediaBrowser.Providers/Channels/ChannelMetadataService.cs @@ -1,25 +1,39 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Channels +namespace MediaBrowser.Providers.Channels; + +/// <summary> +/// Service to manage channel metadata. +/// </summary> +public class ChannelMetadataService : MetadataService<Channel, ItemLookupInfo> { - public class ChannelMetadataService : MetadataService<Channel, ItemLookupInfo> + /// <summary> + /// Initializes a new instance of the <see cref="ChannelMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public ChannelMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<ChannelMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public ChannelMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<ChannelMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } } } diff --git a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs index 0629824d3..9efef60a1 100644 --- a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs +++ b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs @@ -1,25 +1,39 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Folders +namespace MediaBrowser.Providers.Folders; + +/// <summary> +/// Service to manage collection folder metadata. +/// </summary> +public class CollectionFolderMetadataService : MetadataService<CollectionFolder, ItemLookupInfo> { - public class CollectionFolderMetadataService : MetadataService<CollectionFolder, ItemLookupInfo> + /// <summary> + /// Initializes a new instance of the <see cref="CollectionFolderMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public CollectionFolderMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<CollectionFolderMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public CollectionFolderMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<CollectionFolderMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } } } diff --git a/MediaBrowser.Providers/Folders/FolderMetadataService.cs b/MediaBrowser.Providers/Folders/FolderMetadataService.cs index 79d52991a..272bb31e3 100644 --- a/MediaBrowser.Providers/Folders/FolderMetadataService.cs +++ b/MediaBrowser.Providers/Folders/FolderMetadataService.cs @@ -1,29 +1,43 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Folders +namespace MediaBrowser.Providers.Folders; + +/// <summary> +/// Service to manage folder metadata. +/// </summary> +public class FolderMetadataService : MetadataService<Folder, ItemLookupInfo> { - public class FolderMetadataService : MetadataService<Folder, ItemLookupInfo> + /// <summary> + /// Initializes a new instance of the <see cref="FolderMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public FolderMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<FolderMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public FolderMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<FolderMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } - - /// <inheritdoc /> - // Make sure the type-specific services get picked first - public override int Order => 10; } + + /// <inheritdoc /> + // Make sure the type-specific services get picked first + public override int Order => 10; } diff --git a/MediaBrowser.Providers/Folders/UserViewMetadataService.cs b/MediaBrowser.Providers/Folders/UserViewMetadataService.cs index 79c5597e5..ab4bc917d 100644 --- a/MediaBrowser.Providers/Folders/UserViewMetadataService.cs +++ b/MediaBrowser.Providers/Folders/UserViewMetadataService.cs @@ -1,25 +1,39 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Folders +namespace MediaBrowser.Providers.Folders; + +/// <summary> +/// Service to manage user view metadata. +/// </summary> +public class UserViewMetadataService : MetadataService<UserView, ItemLookupInfo> { - public class UserViewMetadataService : MetadataService<UserView, ItemLookupInfo> + /// <summary> + /// Initializes a new instance of the <see cref="UserViewMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public UserViewMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<UserViewMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public UserViewMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<UserViewMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } } } diff --git a/MediaBrowser.Providers/Genres/GenreMetadataService.cs b/MediaBrowser.Providers/Genres/GenreMetadataService.cs index 4d10d8987..0dd0384dc 100644 --- a/MediaBrowser.Providers/Genres/GenreMetadataService.cs +++ b/MediaBrowser.Providers/Genres/GenreMetadataService.cs @@ -1,25 +1,39 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Genres +namespace MediaBrowser.Providers.Genres; + +/// <summary> +/// Service to manage genre metadata. +/// </summary> +public class GenreMetadataService : MetadataService<Genre, ItemLookupInfo> { - public class GenreMetadataService : MetadataService<Genre, ItemLookupInfo> + /// <summary> + /// Initializes a new instance of the <see cref="GenreMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public GenreMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<GenreMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public GenreMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<GenreMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } } } diff --git a/MediaBrowser.Providers/LiveTv/LiveTvMetadataService.cs b/MediaBrowser.Providers/LiveTv/LiveTvMetadataService.cs index c94d36530..83f9984ea 100644 --- a/MediaBrowser.Providers/LiveTv/LiveTvMetadataService.cs +++ b/MediaBrowser.Providers/LiveTv/LiveTvMetadataService.cs @@ -1,25 +1,39 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.LiveTv +namespace MediaBrowser.Providers.LiveTv; + +/// <summary> +/// Service to manage live TV metadata. +/// </summary> +public class LiveTvMetadataService : MetadataService<LiveTvChannel, ItemLookupInfo> { - public class LiveTvMetadataService : MetadataService<LiveTvChannel, ItemLookupInfo> + /// <summary> + /// Initializes a new instance of the <see cref="LiveTvMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public LiveTvMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<LiveTvMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public LiveTvMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<LiveTvMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } } } diff --git a/MediaBrowser.Providers/Lyric/LrcLyricParser.cs b/MediaBrowser.Providers/Lyric/LrcLyricParser.cs index fffdf4887..fa711eb28 100644 --- a/MediaBrowser.Providers/Lyric/LrcLyricParser.cs +++ b/MediaBrowser.Providers/Lyric/LrcLyricParser.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; +using System.Text.RegularExpressions; using Jellyfin.Extensions; using LrcParser.Model; using LrcParser.Parser; @@ -14,7 +16,7 @@ namespace MediaBrowser.Providers.Lyric; /// <summary> /// LRC Lyric Parser. /// </summary> -public class LrcLyricParser : ILyricParser +public partial class LrcLyricParser : ILyricParser { private readonly LyricParser _lrcLyricParser; @@ -65,11 +67,54 @@ public class LrcLyricParser : ILyricParser } List<LyricLine> lyricList = []; - - for (int i = 0; i < sortedLyricData.Count; i++) + for (var lineIndex = 0; lineIndex < sortedLyricData.Count; lineIndex++) { - long ticks = TimeSpan.FromMilliseconds(sortedLyricData[i].StartTime).Ticks; - lyricList.Add(new LyricLine(sortedLyricData[i].Text.Trim(), ticks)); + var lyric = sortedLyricData[lineIndex]; + + // Extract cues from time tags + var cues = new List<LyricLineCue>(); + if (lyric.TimeTags.Count > 0) + { + var keys = lyric.TimeTags.Keys.ToList(); + for (var tagIndex = 0; tagIndex < keys.Count - 1; tagIndex++) + { + var currentKey = keys[tagIndex]; + var nextKey = keys[tagIndex + 1]; + + var currentPos = currentKey.State == IndexState.End ? currentKey.Index + 1 : currentKey.Index; + var nextPos = nextKey.State == IndexState.End ? nextKey.Index + 1 : nextKey.Index; + var currentMs = lyric.TimeTags[currentKey] ?? 0; + var nextMs = lyric.TimeTags[keys[tagIndex + 1]] ?? 0; + var currentSlice = lyric.Text[currentPos..nextPos]; + var currentSliceTrimmed = currentSlice.Trim(); + if (currentSliceTrimmed.Length > 0) + { + cues.Add(new LyricLineCue( + position: currentPos, + endPosition: nextPos, + start: TimeSpan.FromMilliseconds(currentMs).Ticks, + end: TimeSpan.FromMilliseconds(nextMs).Ticks)); + } + } + + var lastKey = keys[^1]; + var lastPos = lastKey.State == IndexState.End ? lastKey.Index + 1 : lastKey.Index; + var lastMs = lyric.TimeTags[lastKey] ?? 0; + var lastSlice = lyric.Text[lastPos..]; + var lastSliceTrimmed = lastSlice.Trim(); + + if (lastSliceTrimmed.Length > 0) + { + cues.Add(new LyricLineCue( + position: lastPos, + endPosition: lyric.Text.Length, + start: TimeSpan.FromMilliseconds(lastMs).Ticks, + end: lineIndex + 1 < sortedLyricData.Count ? TimeSpan.FromMilliseconds(sortedLyricData[lineIndex + 1].StartTime).Ticks : null)); + } + } + + long lyricStartTicks = TimeSpan.FromMilliseconds(lyric.StartTime).Ticks; + lyricList.Add(new LyricLine(lyric.Text, lyricStartTicks, cues)); } return new LyricDto { Lyrics = lyricList }; diff --git a/MediaBrowser.Providers/Lyric/LyricManager.cs b/MediaBrowser.Providers/Lyric/LyricManager.cs index f4b18a8c1..913a104a0 100644 --- a/MediaBrowser.Providers/Lyric/LyricManager.cs +++ b/MediaBrowser.Providers/Lyric/LyricManager.cs @@ -78,7 +78,8 @@ public class LyricManager : ILyricManager MediaPath = audio.Path, SongName = audio.Name, AlbumName = audio.Album, - ArtistNames = audio.GetAllArtists().ToList(), + AlbumArtistsNames = audio.AlbumArtists, + ArtistNames = audio.Artists, Duration = audio.RunTimeTicks, IsAutomated = isAutomated }; diff --git a/MediaBrowser.Providers/Lyric/LyricScheduledTask.cs b/MediaBrowser.Providers/Lyric/LyricScheduledTask.cs index 73912b579..b8861ee5e 100644 --- a/MediaBrowser.Providers/Lyric/LyricScheduledTask.cs +++ b/MediaBrowser.Providers/Lyric/LyricScheduledTask.cs @@ -117,7 +117,8 @@ public class LyricScheduledTask : IScheduledTask MediaPath = audioItem.Path, SongName = audioItem.Name, AlbumName = audioItem.Album, - ArtistNames = audioItem.GetAllArtists().ToList(), + AlbumArtistsNames = audioItem.AlbumArtists, + ArtistNames = audioItem.Artists, Duration = audioItem.RunTimeTicks, IsAutomated = true, DisabledLyricFetchers = libraryOptions.DisabledLyricFetchers, diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 8f6aa2db3..d9a8c044b 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -483,6 +483,22 @@ namespace MediaBrowser.Providers.Manager } } + if (type == ImageType.Logo && saveLocally) + { + if (season is not null && season.IndexNumber.HasValue) + { + var seriesFolder = season.SeriesPath; + + var seasonMarker = season.IndexNumber.Value == 0 + ? "-specials" + : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); + + var imageFilename = "season" + seasonMarker + "-logo" + extension; + + return Path.Combine(seriesFolder, imageFilename); + } + } + string filename; var folderName = item is MusicAlbum || item is MusicArtist || diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index ee22b4bc6..75882a088 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -377,6 +377,10 @@ namespace MediaBrowser.Providers.Manager { // Nothing to do, already gone } + catch (DirectoryNotFoundException) + { + // Nothing to do, already gone + } catch (UnauthorizedAccessException ex) { _logger.LogWarning(ex, "Unable to delete {Image}", image.Path); diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 778fbc712..0f2188aa8 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -12,7 +12,9 @@ using Jellyfin.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; @@ -26,13 +28,22 @@ namespace MediaBrowser.Providers.Manager where TItemType : BaseItem, IHasLookupInfo<TIdType>, new() where TIdType : ItemLookupInfo, new() { - protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger<MetadataService<TItemType, TIdType>> logger, IProviderManager providerManager, IFileSystem fileSystem, ILibraryManager libraryManager) + protected MetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<MetadataService<TItemType, TIdType>> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) { ServerConfigurationManager = serverConfigurationManager; Logger = logger; ProviderManager = providerManager; FileSystem = fileSystem; LibraryManager = libraryManager; + ExternalDataManager = externalDataManager; + ItemRepository = itemRepository; ImageProvider = new ItemImageProvider(Logger, ProviderManager, FileSystem); } @@ -48,6 +59,10 @@ namespace MediaBrowser.Providers.Manager protected ILibraryManager LibraryManager { get; } + protected IExternalDataManager ExternalDataManager { get; } + + protected IItemRepository ItemRepository { get; } + protected virtual bool EnableUpdatingPremiereDateFromChildren => false; protected virtual bool EnableUpdatingGenresFromChildren => false; @@ -58,11 +73,11 @@ namespace MediaBrowser.Providers.Manager public virtual int Order => 0; - private FileSystemMetadata TryGetFile(string path, IDirectoryService directoryService) + private FileSystemMetadata TryGetFileSystemMetadata(string path, IDirectoryService directoryService) { try { - return directoryService.GetFile(path); + return directoryService.GetFileSystemEntry(path); } catch (Exception ex) { @@ -75,8 +90,9 @@ namespace MediaBrowser.Providers.Manager { var itemOfType = (TItemType)item; var updateType = ItemUpdateType.None; + var libraryOptions = LibraryManager.GetLibraryOptions(item); - var isFirstRefresh = item.DateLastRefreshed == default; + var isFirstRefresh = item.DateLastRefreshed == DateTime.MinValue; var hasRefreshedMetadata = true; var hasRefreshedImages = true; @@ -128,11 +144,11 @@ namespace MediaBrowser.Providers.Manager var metadataResult = new MetadataResult<TItemType> { - Item = itemOfType, - People = LibraryManager.GetPeople(item) + Item = itemOfType }; - var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh || refreshOptions.ForceSave, updateType); + var beforeSaveResult = await BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh || refreshOptions.ForceSave, updateType) + .ConfigureAwait(false); updateType |= beforeSaveResult; updateType = await SaveInternal(item, refreshOptions, updateType, isFirstRefresh, requiresRefresh, metadataResult, cancellationToken).ConfigureAwait(false); @@ -193,6 +209,7 @@ namespace MediaBrowser.Providers.Manager if (hasRefreshedMetadata && hasRefreshedImages) { item.DateLastRefreshed = DateTime.UtcNow; + updateType |= item.OnMetadataChanged(); } updateType = await SaveInternal(item, refreshOptions, updateType, isFirstRefresh, requiresRefresh, metadataResult, cancellationToken).ConfigureAwait(false); @@ -208,7 +225,7 @@ namespace MediaBrowser.Providers.Manager { if (item.IsFileProtocol) { - var file = TryGetFile(item.Path, refreshOptions.DirectoryService); + var file = TryGetFileSystemMetadata(item.Path, refreshOptions.DirectoryService); if (file is not null) { item.DateModified = file.LastWriteTimeUtc; @@ -252,14 +269,13 @@ namespace MediaBrowser.Providers.Manager protected async Task SaveItemAsync(MetadataResult<TItemType> result, ItemUpdateType reason, CancellationToken cancellationToken) { - if (result.Item.SupportsPeople) + await result.Item.UpdateToRepositoryAsync(reason, cancellationToken).ConfigureAwait(false); + if (result.Item.SupportsPeople && result.People is not null) { var baseItem = result.Item; await LibraryManager.UpdatePeopleAsync(baseItem, result.People, cancellationToken).ConfigureAwait(false); } - - await result.Item.UpdateToRepositoryAsync(reason, cancellationToken).ConfigureAwait(false); } protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) @@ -275,12 +291,20 @@ namespace MediaBrowser.Providers.Manager /// <param name="isFullRefresh">if set to <c>true</c> [is full refresh].</param> /// <param name="currentUpdateType">Type of the current update.</param> /// <returns>ItemUpdateType.</returns> - private ItemUpdateType BeforeSave(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType) + private async Task<ItemUpdateType> BeforeSave(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType) { var updateType = BeforeSaveInternal(item, isFullRefresh, currentUpdateType); updateType |= item.OnMetadataChanged(); + if (updateType == ItemUpdateType.None) + { + if (!await ItemRepository.ItemExistsAsync(item.Id).ConfigureAwait(false)) + { + return ItemUpdateType.MetadataImport; + } + } + return updateType; } @@ -303,6 +327,31 @@ namespace MediaBrowser.Providers.Manager updateType |= ItemUpdateType.MetadataImport; } + // Cleanup extracted files if source file was modified + var itemPath = item.Path; + if (!string.IsNullOrEmpty(itemPath)) + { + var info = FileSystem.GetFileSystemInfo(itemPath); + if (info.Exists && item.HasChanged(info.LastWriteTimeUtc)) + { + Logger.LogDebug("File modification time changed from {Then} to {Now}: {Path}", item.DateModified, info.LastWriteTimeUtc, itemPath); + + item.DateModified = info.LastWriteTimeUtc; + if (ServerConfigurationManager.GetMetadataConfiguration().UseFileCreationTimeForDateAdded) + { + item.DateCreated = info.CreationTimeUtc; + } + + if (item is Video video) + { + Logger.LogInformation("File changed, pruning extracted data: {Path}", item.Path); + ExternalDataManager.DeleteExternalItemDataAsync(video, CancellationToken.None).GetAwaiter().GetResult(); + } + + updateType |= ItemUpdateType.MetadataImport; + } + } + return updateType; } @@ -613,7 +662,7 @@ namespace MediaBrowser.Providers.Manager var dateLastImageRefresh = item.DateLastRefreshed; // Run all if either of these flags are true - var runAllProviders = options.ImageRefreshMode == MetadataRefreshMode.FullRefresh || dateLastImageRefresh == default(DateTime); + var runAllProviders = options.ImageRefreshMode == MetadataRefreshMode.FullRefresh || dateLastImageRefresh.Date == DateTime.MinValue.Date; if (!runAllProviders) { @@ -780,7 +829,9 @@ namespace MediaBrowser.Providers.Manager } else { - var shouldReplace = options.MetadataRefreshMode > MetadataRefreshMode.ValidationOnly || options.ReplaceAllMetadata; + var shouldReplace = (options.MetadataRefreshMode > MetadataRefreshMode.ValidationOnly && options.ReplaceAllMetadata) + // Case for Scan for new and updated files + || (options.MetadataRefreshMode == MetadataRefreshMode.Default && !options.ReplaceAllMetadata); MergeData(temp, metadata, item.LockedFields, shouldReplace, true); } } @@ -1041,7 +1092,7 @@ namespace MediaBrowser.Providers.Manager } else { - target.Studios = target.Studios.Concat(source.Studios).Distinct().ToArray(); + target.Studios = target.Studios.Concat(source.Studios).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); } } @@ -1053,7 +1104,7 @@ namespace MediaBrowser.Providers.Manager } else { - target.Tags = target.Tags.Concat(source.Tags).Distinct().ToArray(); + target.Tags = target.Tags.Concat(source.Tags).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); } } @@ -1065,7 +1116,7 @@ namespace MediaBrowser.Providers.Manager } else { - target.ProductionLocations = target.ProductionLocations.Concat(source.ProductionLocations).Distinct().ToArray(); + target.ProductionLocations = target.ProductionLocations.Concat(source.ProductionLocations).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); } } @@ -1127,11 +1178,16 @@ namespace MediaBrowser.Providers.Manager target.LockedFields = target.LockedFields.Concat(source.LockedFields).Distinct().ToArray(); } - if (source.DateCreated != default) + if (source.DateCreated != DateTime.MinValue) { target.DateCreated = source.DateCreated; } + if (replaceData || source.DateModified != DateTime.MinValue) + { + target.DateModified = source.DateModified; + } + if (replaceData || string.IsNullOrEmpty(target.PreferredMetadataCountryCode)) { target.PreferredMetadataCountryCode = source.PreferredMetadataCountryCode; @@ -1146,13 +1202,24 @@ namespace MediaBrowser.Providers.Manager private static void MergePeople(IReadOnlyList<PersonInfo> source, IReadOnlyList<PersonInfo> target) { - foreach (var person in target) + var sourceByName = source.ToLookup(p => p.Name.RemoveDiacritics(), StringComparer.OrdinalIgnoreCase); + var targetByName = target.ToLookup(p => p.Name.RemoveDiacritics(), StringComparer.OrdinalIgnoreCase); + + foreach (var name in targetByName.Select(g => g.Key)) { - var normalizedName = person.Name.RemoveDiacritics(); - var personInSource = source.FirstOrDefault(i => string.Equals(i.Name.RemoveDiacritics(), normalizedName, StringComparison.OrdinalIgnoreCase)); + var targetPeople = targetByName[name].ToArray(); + var sourcePeople = sourceByName[name].ToArray(); - if (personInSource is not null) + if (sourcePeople.Length == 0) { + continue; + } + + for (int i = 0; i < targetPeople.Length; i++) + { + var person = targetPeople[i]; + var personInSource = i < sourcePeople.Length ? sourcePeople[i] : sourcePeople[0]; + foreach (var providerId in personInSource.ProviderIds) { person.ProviderIds.TryAdd(providerId.Key, providerId.Value); @@ -1162,6 +1229,16 @@ namespace MediaBrowser.Providers.Manager { person.ImageUrl = personInSource.ImageUrl; } + + if (!string.IsNullOrWhiteSpace(personInSource.Role) && string.IsNullOrWhiteSpace(person.Role)) + { + person.Role = personInSource.Role; + } + + if (personInSource.SortOrder.HasValue && !person.SortOrder.HasValue) + { + person.SortOrder = personInSource.SortOrder; + } } } } @@ -1193,7 +1270,7 @@ namespace MediaBrowser.Providers.Manager } else if (sourceHasAlbumArtist.AlbumArtists.Count > 0) { - targetHasAlbumArtist.AlbumArtists = targetHasAlbumArtist.AlbumArtists.Concat(sourceHasAlbumArtist.AlbumArtists).Distinct().ToArray(); + targetHasAlbumArtist.AlbumArtists = targetHasAlbumArtist.AlbumArtists.Concat(sourceHasAlbumArtist.AlbumArtists).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); } } } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 8c45abe25..43f0746ba 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -1,13 +1,11 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Mime; -using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using AsyncKeyedLock; @@ -24,6 +22,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Lyrics; +using MediaBrowser.Controller.MediaSegments; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Configuration; @@ -670,8 +669,13 @@ namespace MediaBrowser.Providers.Manager private async Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> savers) { var libraryOptions = _libraryManager.GetLibraryOptions(item); + var applicableSavers = savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false)).ToList(); + if (applicableSavers.Count == 0) + { + return; + } - foreach (var saver in savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false))) + foreach (var saver in applicableSavers) { _logger.LogDebug("Saving {Item} to {Saver}", item.Path ?? item.Name, saver.Name); @@ -693,6 +697,7 @@ namespace MediaBrowser.Providers.Manager { _libraryMonitor.ReportFileSystemChangeBeginning(path); await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false); + item.DateLastSaved = DateTime.UtcNow; } catch (Exception ex) { @@ -708,6 +713,7 @@ namespace MediaBrowser.Providers.Manager try { await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false); + item.DateLastSaved = DateTime.UtcNow; } catch (Exception ex) { @@ -715,6 +721,8 @@ namespace MediaBrowser.Providers.Manager } } } + + _libraryManager.CreateItem(item, null); } /// <summary> @@ -899,35 +907,10 @@ namespace MediaBrowser.Providers.Manager /// <inheritdoc/> public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item) { -#pragma warning disable CS0618 // Type or member is obsolete - Remove 10.11 - var legacyExternalIdUrls = GetExternalIds(item) - .Select(i => - { - var urlFormatString = i.UrlFormatString; - if (string.IsNullOrEmpty(urlFormatString) - || !item.TryGetProviderId(i.Key, out var providerId)) - { - return null; - } - - return new ExternalUrl - { - Name = i.ProviderName, - Url = string.Format( - CultureInfo.InvariantCulture, - urlFormatString, - providerId) - }; - }) - .OfType<ExternalUrl>(); -#pragma warning restore CS0618 // Type or member is obsolete - - var externalUrls = _externalUrlProviders + return _externalUrlProviders .SelectMany(p => p .GetExternalUrls(item) .Select(externalUrl => new ExternalUrl { Name = p.Name, Url = externalUrl })); - - return legacyExternalIdUrls.Concat(externalUrls).OrderBy(u => u.Name); } /// <inheritdoc/> @@ -937,10 +920,7 @@ namespace MediaBrowser.Providers.Manager .Select(i => new ExternalIdInfo( name: i.ProviderName, key: i.Key, - type: i.Type, -#pragma warning disable CS0618 // Type or member is obsolete - Remove 10.11 - urlFormatString: i.UrlFormatString)); -#pragma warning restore CS0618 // Type or member is obsolete + type: i.Type)); } /// <inheritdoc/> diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs index a0481a642..c0680b901 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using ATL; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; @@ -19,6 +20,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging; +using static Jellyfin.Extensions.StringExtensions; namespace MediaBrowser.Providers.MediaInfo { @@ -30,7 +32,6 @@ namespace MediaBrowser.Providers.MediaInfo private const char InternalValueSeparator = '\u001F'; private readonly IMediaEncoder _mediaEncoder; - private readonly IItemRepository _itemRepo; private readonly ILibraryManager _libraryManager; private readonly ILogger<AudioFileProber> _logger; private readonly IMediaSourceManager _mediaSourceManager; @@ -44,7 +45,6 @@ namespace MediaBrowser.Providers.MediaInfo /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> - /// <param name="itemRepo">Instance of the <see cref="IItemRepository"/> interface.</param> /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> /// <param name="lyricResolver">Instance of the <see cref="LyricResolver"/> interface.</param> /// <param name="lyricManager">Instance of the <see cref="ILyricManager"/> interface.</param> @@ -53,14 +53,12 @@ namespace MediaBrowser.Providers.MediaInfo ILogger<AudioFileProber> logger, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, - IItemRepository itemRepo, ILibraryManager libraryManager, LyricResolver lyricResolver, ILyricManager lyricManager, IMediaStreamRepository mediaStreamRepository) { _mediaEncoder = mediaEncoder; - _itemRepo = itemRepo; _libraryManager = libraryManager; _logger = logger; _mediaSourceManager = mediaSourceManager; @@ -135,7 +133,6 @@ namespace MediaBrowser.Providers.MediaInfo audio.TotalBitrate = mediaInfo.Bitrate; audio.RunTimeTicks = mediaInfo.RunTimeTicks; - audio.Size = mediaInfo.Size; // Add external lyrics first to prevent the lrc file get overwritten on first scan var mediaStreams = new List<MediaStream>(mediaInfo.MediaStreams); @@ -174,16 +171,41 @@ namespace MediaBrowser.Providers.MediaInfo _logger.LogWarning("File {File} only has ID3v1 tags, some fields may be truncated", audio.Path); } - track.Title = string.IsNullOrEmpty(track.Title) ? mediaInfo.Name : track.Title; - track.Album = string.IsNullOrEmpty(track.Album) ? mediaInfo.Album : track.Album; - track.Year = track.Year is null or 0 ? mediaInfo.ProductionYear : track.Year; - track.TrackNumber = track.TrackNumber is null or 0 ? mediaInfo.IndexNumber : track.TrackNumber; - track.DiscNumber = track.DiscNumber is null or 0 ? mediaInfo.ParentIndexNumber : track.DiscNumber; + // We should never use the property setter of the ATL.Track class. + // That setter is meant for its own tag parser and external editor usage and will have unwanted side effects + // For example, setting the Year property will also set the Date property, which is not what we want here. + // To properly handle fallback values, we make a clone of those fields when valid. + var trackTitle = (string.IsNullOrEmpty(track.Title) ? mediaInfo.Name : track.Title)?.Trim(); + var trackAlbum = (string.IsNullOrEmpty(track.Album) ? mediaInfo.Album : track.Album)?.Trim(); + var trackYear = track.Year is null or 0 ? mediaInfo.ProductionYear : track.Year; + var trackTrackNumber = track.TrackNumber is null or 0 ? mediaInfo.IndexNumber : track.TrackNumber; + var trackDiscNumber = track.DiscNumber is null or 0 ? mediaInfo.ParentIndexNumber : track.DiscNumber; + + // Some users may use a misbehaved tag editor that writes a null character in the tag when not allowed by the standard. + trackTitle = GetSanitizedStringTag(trackTitle, audio.Path); + trackAlbum = GetSanitizedStringTag(trackAlbum, audio.Path); + var trackAlbumArtist = GetSanitizedStringTag(track.AlbumArtist, audio.Path); + var trackArist = GetSanitizedStringTag(track.Artist, audio.Path); + var trackComposer = GetSanitizedStringTag(track.Composer, audio.Path); + var trackGenre = GetSanitizedStringTag(track.Genre, audio.Path); if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast)) { var people = new List<PersonInfo>(); - var albumArtists = string.IsNullOrEmpty(track.AlbumArtist) ? [] : track.AlbumArtist.Split(InternalValueSeparator); + string[]? albumArtists = null; + if (libraryOptions.PreferNonstandardArtistsTag) + { + TryGetSanitizedAdditionalFields(track, "ALBUMARTISTS", out var albumArtistsTagString); + if (albumArtistsTagString is not null) + { + albumArtists = albumArtistsTagString.Split(InternalValueSeparator); + } + } + + if (albumArtists is null || albumArtists.Length == 0) + { + albumArtists = string.IsNullOrEmpty(trackAlbumArtist) ? [] : trackAlbumArtist.Split(InternalValueSeparator); + } if (libraryOptions.UseCustomTagDelimiters) { @@ -192,7 +214,7 @@ namespace MediaBrowser.Providers.MediaInfo foreach (var albumArtist in albumArtists) { - if (!string.IsNullOrEmpty(albumArtist)) + if (!string.IsNullOrWhiteSpace(albumArtist)) { PeopleHelper.AddPerson(people, new PersonInfo { @@ -205,7 +227,7 @@ namespace MediaBrowser.Providers.MediaInfo string[]? performers = null; if (libraryOptions.PreferNonstandardArtistsTag) { - track.AdditionalFields.TryGetValue("ARTISTS", out var artistsTagString); + TryGetSanitizedAdditionalFields(track, "ARTISTS", out var artistsTagString); if (artistsTagString is not null) { performers = artistsTagString.Split(InternalValueSeparator); @@ -214,7 +236,7 @@ namespace MediaBrowser.Providers.MediaInfo if (performers is null || performers.Length == 0) { - performers = string.IsNullOrEmpty(track.Artist) ? [] : track.Artist.Split(InternalValueSeparator); + performers = string.IsNullOrEmpty(trackArist) ? [] : trackArist.Split(InternalValueSeparator); } if (libraryOptions.UseCustomTagDelimiters) @@ -224,7 +246,7 @@ namespace MediaBrowser.Providers.MediaInfo foreach (var performer in performers) { - if (!string.IsNullOrEmpty(performer)) + if (!string.IsNullOrWhiteSpace(performer)) { PeopleHelper.AddPerson(people, new PersonInfo { @@ -234,15 +256,18 @@ namespace MediaBrowser.Providers.MediaInfo } } - foreach (var composer in track.Composer.Split(InternalValueSeparator)) + if (!string.IsNullOrWhiteSpace(trackComposer)) { - if (!string.IsNullOrEmpty(composer)) + foreach (var composer in trackComposer.Split(InternalValueSeparator)) { - PeopleHelper.AddPerson(people, new PersonInfo + if (!string.IsNullOrWhiteSpace(composer)) { - Name = composer, - Type = PersonKind.Composer - }); + PeopleHelper.AddPerson(people, new PersonInfo + { + Name = composer, + Type = PersonKind.Composer + }); + } } } @@ -275,22 +300,22 @@ namespace MediaBrowser.Providers.MediaInfo } } - if (!audio.LockedFields.Contains(MetadataField.Name) && !string.IsNullOrEmpty(track.Title)) + if (!audio.LockedFields.Contains(MetadataField.Name) && !string.IsNullOrEmpty(trackTitle)) { - audio.Name = track.Title; + audio.Name = trackTitle; } if (options.ReplaceAllMetadata) { - audio.Album = track.Album; - audio.IndexNumber = track.TrackNumber; - audio.ParentIndexNumber = track.DiscNumber; + audio.Album = trackAlbum; + audio.IndexNumber = trackTrackNumber; + audio.ParentIndexNumber = trackDiscNumber; } else { - audio.Album ??= track.Album; - audio.IndexNumber ??= track.TrackNumber; - audio.ParentIndexNumber ??= track.DiscNumber; + audio.Album ??= trackAlbum; + audio.IndexNumber ??= trackTrackNumber; + audio.ParentIndexNumber ??= trackDiscNumber; } if (track.Date.HasValue) @@ -298,11 +323,12 @@ namespace MediaBrowser.Providers.MediaInfo audio.PremiereDate = track.Date; } - if (track.Year.HasValue) + if (trackYear.HasValue) { - var year = track.Year.Value; + var year = trackYear.Value; audio.ProductionYear = year; + // ATL library handles such fallback this with its own internal logic, but we also need to handle it here for the ffprobe fallbacks. if (!audio.PremiereDate.HasValue) { try @@ -311,26 +337,29 @@ namespace MediaBrowser.Providers.MediaInfo } catch (ArgumentOutOfRangeException ex) { - _logger.LogError(ex, "Error parsing YEAR tag in {File}. '{TagValue}' is an invalid year", audio.Path, track.Year); + _logger.LogError(ex, "Error parsing YEAR tag in {File}. '{TagValue}' is an invalid year", audio.Path, trackYear); } } } if (!audio.LockedFields.Contains(MetadataField.Genres)) { - var genres = string.IsNullOrEmpty(track.Genre) ? [] : track.Genre.Split(InternalValueSeparator).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); + var genres = string.IsNullOrEmpty(trackGenre) ? [] : trackGenre.Split(InternalValueSeparator).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); if (libraryOptions.UseCustomTagDelimiters) { genres = genres.SelectMany(g => SplitWithCustomDelimiter(g, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist)).ToArray(); } - audio.Genres = options.ReplaceAllMetadata || audio.Genres is null || audio.Genres.Length == 0 - ? genres - : audio.Genres; + genres = genres.Trimmed().Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); + + if (options.ReplaceAllMetadata || audio.Genres is null || audio.Genres.Length == 0 || audio.Genres.All(string.IsNullOrWhiteSpace)) + { + audio.Genres = genres; + } } - track.AdditionalFields.TryGetValue("REPLAYGAIN_TRACK_GAIN", out var trackGainTag); + TryGetSanitizedAdditionalFields(track, "REPLAYGAIN_TRACK_GAIN", out var trackGainTag); if (trackGainTag is not null) { @@ -339,7 +368,7 @@ namespace MediaBrowser.Providers.MediaInfo trackGainTag = trackGainTag[..^2].Trim(); } - if (float.TryParse(trackGainTag, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) + if (float.TryParse(trackGainTag, NumberStyles.Float, CultureInfo.InvariantCulture, out var value) && float.IsFinite(value)) { audio.NormalizationGain = value; } @@ -347,8 +376,8 @@ namespace MediaBrowser.Providers.MediaInfo if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out _)) { - if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_ARTISTID", out var musicBrainzArtistTag) - || track.AdditionalFields.TryGetValue("MusicBrainz Artist Id", out musicBrainzArtistTag)) + if ((TryGetSanitizedAdditionalFields(track, "MUSICBRAINZ_ARTISTID", out var musicBrainzArtistTag) + || TryGetSanitizedAdditionalFields(track, "MusicBrainz Artist Id", out musicBrainzArtistTag)) && !string.IsNullOrEmpty(musicBrainzArtistTag)) { var id = GetFirstMusicBrainzId(musicBrainzArtistTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist); @@ -358,8 +387,8 @@ namespace MediaBrowser.Providers.MediaInfo if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbumArtist, out _)) { - if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_ALBUMARTISTID", out var musicBrainzReleaseArtistIdTag) - || track.AdditionalFields.TryGetValue("MusicBrainz Album Artist Id", out musicBrainzReleaseArtistIdTag)) + if ((TryGetSanitizedAdditionalFields(track, "MUSICBRAINZ_ALBUMARTISTID", out var musicBrainzReleaseArtistIdTag) + || TryGetSanitizedAdditionalFields(track, "MusicBrainz Album Artist Id", out musicBrainzReleaseArtistIdTag)) && !string.IsNullOrEmpty(musicBrainzReleaseArtistIdTag)) { var id = GetFirstMusicBrainzId(musicBrainzReleaseArtistIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist); @@ -369,8 +398,8 @@ namespace MediaBrowser.Providers.MediaInfo if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbum, out _)) { - if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_ALBUMID", out var musicBrainzReleaseIdTag) - || track.AdditionalFields.TryGetValue("MusicBrainz Album Id", out musicBrainzReleaseIdTag)) + if ((TryGetSanitizedAdditionalFields(track, "MUSICBRAINZ_ALBUMID", out var musicBrainzReleaseIdTag) + || TryGetSanitizedAdditionalFields(track, "MusicBrainz Album Id", out musicBrainzReleaseIdTag)) && !string.IsNullOrEmpty(musicBrainzReleaseIdTag)) { var id = GetFirstMusicBrainzId(musicBrainzReleaseIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist); @@ -380,8 +409,8 @@ namespace MediaBrowser.Providers.MediaInfo if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzReleaseGroup, out _)) { - if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_RELEASEGROUPID", out var musicBrainzReleaseGroupIdTag) - || track.AdditionalFields.TryGetValue("MusicBrainz Release Group Id", out musicBrainzReleaseGroupIdTag)) + if ((TryGetSanitizedAdditionalFields(track, "MUSICBRAINZ_RELEASEGROUPID", out var musicBrainzReleaseGroupIdTag) + || TryGetSanitizedAdditionalFields(track, "MusicBrainz Release Group Id", out musicBrainzReleaseGroupIdTag)) && !string.IsNullOrEmpty(musicBrainzReleaseGroupIdTag)) { var id = GetFirstMusicBrainzId(musicBrainzReleaseGroupIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist); @@ -391,8 +420,8 @@ namespace MediaBrowser.Providers.MediaInfo if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzTrack, out _)) { - if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_RELEASETRACKID", out var trackMbId) - || track.AdditionalFields.TryGetValue("MusicBrainz Release Track Id", out trackMbId)) + if ((TryGetSanitizedAdditionalFields(track, "MUSICBRAINZ_RELEASETRACKID", out var trackMbId) + || TryGetSanitizedAdditionalFields(track, "MusicBrainz Release Track Id", out trackMbId)) && !string.IsNullOrEmpty(trackMbId)) { var id = GetFirstMusicBrainzId(trackMbId, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist); @@ -400,9 +429,31 @@ namespace MediaBrowser.Providers.MediaInfo } } + if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzRecording, out _)) + { + if ((TryGetSanitizedAdditionalFields(track, "MUSICBRAINZ_TRACKID", out var recordingMbId) + || TryGetSanitizedAdditionalFields(track, "MusicBrainz Track Id", out recordingMbId)) + && !string.IsNullOrEmpty(recordingMbId)) + { + audio.TrySetProviderId(MetadataProvider.MusicBrainzRecording, recordingMbId); + } + else if (TryGetSanitizedAdditionalFields(track, "UFID", out var ufIdValue) && !string.IsNullOrEmpty(ufIdValue)) + { + // If tagged with MB Picard, the format is 'http://musicbrainz.org\0<recording MBID>' + if (ufIdValue.Contains("musicbrainz.org", StringComparison.OrdinalIgnoreCase)) + { + audio.TrySetProviderId(MetadataProvider.MusicBrainzRecording, ufIdValue.AsSpan().RightPart('\0').ToString()); + } + } + } + // Save extracted lyrics if they exist, // and if the audio doesn't yet have lyrics. - var lyrics = track.Lyrics.SynchronizedLyrics.Count > 0 ? track.Lyrics.FormatSynchToLRC() : track.Lyrics.UnsynchronizedLyrics; + // ATL supports both SRT and LRC formats as synchronized lyrics, but we only want to save LRC format. + var supportedLyrics = track.Lyrics.Where(l => l.Format != LyricsInfo.LyricsFormat.SRT).ToList(); + var candidateSynchronizedLyric = supportedLyrics.FirstOrDefault(l => l.Format is not LyricsInfo.LyricsFormat.UNSYNCHRONIZED and not LyricsInfo.LyricsFormat.OTHER && l.SynchronizedLyrics is not null); + var candidateUnsynchronizedLyric = supportedLyrics.FirstOrDefault(l => l.Format is LyricsInfo.LyricsFormat.UNSYNCHRONIZED or LyricsInfo.LyricsFormat.OTHER && l.UnsynchronizedLyrics is not null); + var lyrics = candidateSynchronizedLyric is not null ? candidateSynchronizedLyric.FormatSynch() : candidateUnsynchronizedLyric?.UnsynchronizedLyrics; if (!string.IsNullOrWhiteSpace(lyrics) && tryExtractEmbeddedLyrics) { @@ -463,5 +514,28 @@ namespace MediaBrowser.Providers.MediaInfo return val; } + + private string? GetSanitizedStringTag(string? tag, string filePath) + { + if (string.IsNullOrEmpty(tag)) + { + return null; + } + + var result = tag.TruncateAtNull(); + if (result.Length != tag.Length) + { + _logger.LogWarning("Audio file {File} contains a null character in its tag, but this is not allowed by its tagging standard. All characters after the null char will be discarded. Please fix your file", filePath); + } + + return result; + } + + private bool TryGetSanitizedAdditionalFields(Track track, string field, out string? value) + { + var hasField = track.AdditionalFields.TryGetValue(field, out value); + value = GetSanitizedStringTag(value, track.Path); + return hasField; + } } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 16f3175d2..bdb6b93be 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -6,6 +6,8 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; @@ -33,13 +35,11 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ILogger<FFProbeVideoInfo> _logger; private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaEncoder _mediaEncoder; - private readonly IItemRepository _itemRepo; private readonly IBlurayExaminer _blurayExaminer; private readonly ILocalizationManager _localization; - private readonly IEncodingManager _encodingManager; + private readonly IChapterManager _chapterManager; private readonly IServerConfigurationManager _config; private readonly ISubtitleManager _subtitleManager; - private readonly IChapterRepository _chapterManager; private readonly ILibraryManager _libraryManager; private readonly AudioResolver _audioResolver; private readonly SubtitleResolver _subtitleResolver; @@ -50,13 +50,11 @@ namespace MediaBrowser.Providers.MediaInfo ILogger<FFProbeVideoInfo> logger, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, - IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, - IEncodingManager encodingManager, + IChapterManager chapterManager, IServerConfigurationManager config, ISubtitleManager subtitleManager, - IChapterRepository chapterManager, ILibraryManager libraryManager, AudioResolver audioResolver, SubtitleResolver subtitleResolver, @@ -66,13 +64,11 @@ namespace MediaBrowser.Providers.MediaInfo _logger = logger; _mediaSourceManager = mediaSourceManager; _mediaEncoder = mediaEncoder; - _itemRepo = itemRepo; _blurayExaminer = blurayExaminer; _localization = localization; - _encodingManager = encodingManager; + _chapterManager = chapterManager; _config = config; _subtitleManager = subtitleManager; - _chapterManager = chapterManager; _libraryManager = libraryManager; _audioResolver = audioResolver; _subtitleResolver = subtitleResolver; @@ -219,10 +215,14 @@ namespace MediaBrowser.Providers.MediaInfo mediaAttachments = mediaInfo.MediaAttachments; video.TotalBitrate = mediaInfo.Bitrate; video.RunTimeTicks = mediaInfo.RunTimeTicks; - video.Size = mediaInfo.Size; video.Container = mediaInfo.Container; + var videoType = video.VideoType; + if (videoType == VideoType.BluRay || videoType == VideoType.Dvd) + { + video.Size = mediaInfo.Size; + } - chapters = mediaInfo.Chapters ?? Array.Empty<ChapterInfo>(); + chapters = mediaInfo.Chapters ?? []; if (blurayInfo is not null) { FetchBdInfo(video, ref chapters, mediaStreams, blurayInfo); @@ -239,8 +239,8 @@ namespace MediaBrowser.Providers.MediaInfo } } - mediaAttachments = Array.Empty<MediaAttachment>(); - chapters = Array.Empty<ChapterInfo>(); + mediaAttachments = []; + chapters = []; } var libraryOptions = _libraryManager.GetLibraryOptions(video); @@ -276,10 +276,7 @@ namespace MediaBrowser.Providers.MediaInfo _mediaStreamRepository.SaveMediaStreams(video.Id, mediaStreams, cancellationToken); - if (mediaAttachments.Any()) - { - _mediaAttachmentRepository.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken); - } + _mediaAttachmentRepository.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken); if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || options.MetadataRefreshMode == MetadataRefreshMode.Default) @@ -297,9 +294,9 @@ namespace MediaBrowser.Providers.MediaInfo extractDuringScan = libraryOptions.ExtractChapterImagesDuringLibraryScan; } - await _encodingManager.RefreshChapterImages(video, options.DirectoryService, chapters, extractDuringScan, false, cancellationToken).ConfigureAwait(false); + await _chapterManager.RefreshChapterImages(video, options.DirectoryService, chapters, extractDuringScan, false, cancellationToken).ConfigureAwait(false); - _chapterManager.SaveChapters(video.Id, chapters); + _chapterManager.SaveChapters(video, chapters); } } @@ -323,16 +320,19 @@ namespace MediaBrowser.Providers.MediaInfo private void FetchBdInfo(Video video, ref ChapterInfo[] chapters, List<MediaStream> mediaStreams, BlurayDiscInfo blurayInfo) { - if (blurayInfo.Files.Length <= 1) - { - return; - } - var ffmpegVideoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video); + var externalStreams = mediaStreams.Where(s => s.IsExternal).ToList(); // Fill video properties from the BDInfo result mediaStreams.Clear(); - mediaStreams.AddRange(blurayInfo.MediaStreams); + + // Rebuild the list with external streams first + int index = 0; + foreach (var stream in externalStreams.Concat(blurayInfo.MediaStreams)) + { + stream.Index = index++; + mediaStreams.Add(stream); + } if (blurayInfo.RunTimeTicks.HasValue && blurayInfo.RunTimeTicks.Value > 0) { @@ -405,9 +405,9 @@ namespace MediaBrowser.Providers.MediaInfo { if (video.Genres.Length == 0 || replaceData) { - video.Genres = Array.Empty<string>(); + video.Genres = []; - foreach (var genre in data.Genres) + foreach (var genre in data.Genres.Trimmed()) { video.AddGenre(genre); } @@ -514,12 +514,15 @@ namespace MediaBrowser.Providers.MediaInfo foreach (var person in data.People) { - PeopleHelper.AddPerson(people, new PersonInfo + if (!string.IsNullOrWhiteSpace(person.Name)) { - Name = person.Name, - Type = person.Type, - Role = person.Role - }); + PeopleHelper.AddPerson(people, new PersonInfo + { + Name = person.Name, + Type = person.Type, + Role = person.Role.Trim() + }); + } } _libraryManager.UpdatePeople(video, people); @@ -648,7 +651,7 @@ namespace MediaBrowser.Providers.MediaInfo long dummyChapterDuration = TimeSpan.FromSeconds(_config.Configuration.DummyChapterDuration).Ticks; if (runtime <= dummyChapterDuration) { - return Array.Empty<ChapterInfo>(); + return []; } int chapterCount = (int)(runtime / dummyChapterDuration); diff --git a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs index 1c2f8b913..bd6b36458 100644 --- a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs @@ -55,13 +55,11 @@ namespace MediaBrowser.Providers.MediaInfo /// </summary> /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> - /// <param name="itemRepo">Instance of the <see cref="IItemRepository"/> interface.</param> /// <param name="blurayExaminer">Instance of the <see cref="IBlurayExaminer"/> interface.</param> /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> - /// <param name="encodingManager">Instance of the <see cref="IEncodingManager"/> interface.</param> + /// <param name="chapterManager">Instance of the <see cref="IChapterManager"/> interface.</param> /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> /// <param name="subtitleManager">Instance of the <see cref="ISubtitleManager"/> interface.</param> - /// <param name="chapterManager">Instance of the <see cref="IChapterRepository"/> interface.</param> /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/>.</param> /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> @@ -72,13 +70,11 @@ namespace MediaBrowser.Providers.MediaInfo public ProbeProvider( IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, - IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, - IEncodingManager encodingManager, + IChapterManager chapterManager, IServerConfigurationManager config, ISubtitleManager subtitleManager, - IChapterRepository chapterManager, ILibraryManager libraryManager, IFileSystem fileSystem, ILoggerFactory loggerFactory, @@ -96,13 +92,11 @@ namespace MediaBrowser.Providers.MediaInfo loggerFactory.CreateLogger<FFProbeVideoInfo>(), mediaSourceManager, mediaEncoder, - itemRepo, blurayExaminer, localization, - encodingManager, + chapterManager, config, subtitleManager, - chapterManager, libraryManager, _audioResolver, _subtitleResolver, @@ -113,7 +107,6 @@ namespace MediaBrowser.Providers.MediaInfo loggerFactory.CreateLogger<AudioFileProber>(), mediaSourceManager, mediaEncoder, - itemRepo, libraryManager, _lyricResolver, lyricManager, @@ -137,9 +130,9 @@ namespace MediaBrowser.Providers.MediaInfo if (!string.IsNullOrWhiteSpace(path) && item.IsFileProtocol) { var file = directoryService.GetFile(path); - if (file is not null && file.LastWriteTimeUtc != item.DateModified) + if (file is not null && item.HasChanged(file.LastWriteTimeUtc) && file.Length != item.Size) { - _logger.LogDebug("Refreshing {ItemPath} due to date modified timestamp change.", path); + _logger.LogDebug("Refreshing {ItemPath} due to file system modification.", path); return true; } } diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 938f3cb32..1134baf92 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -14,7 +14,6 @@ using MediaBrowser.Controller.Dto; 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.Tasks; diff --git a/MediaBrowser.Providers/Movies/ImdbExternalId.cs b/MediaBrowser.Providers/Movies/ImdbExternalId.cs index a8d74aa0b..def0b13c0 100644 --- a/MediaBrowser.Providers/Movies/ImdbExternalId.cs +++ b/MediaBrowser.Providers/Movies/ImdbExternalId.cs @@ -22,9 +22,6 @@ namespace MediaBrowser.Providers.Movies public ExternalIdMediaType? Type => null; /// <inheritdoc /> - public string UrlFormatString => "https://www.imdb.com/title/{0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) { // Supports images for tv movies diff --git a/MediaBrowser.Providers/Movies/ImdbExternalUrlProvider.cs b/MediaBrowser.Providers/Movies/ImdbExternalUrlProvider.cs new file mode 100644 index 000000000..980bac102 --- /dev/null +++ b/MediaBrowser.Providers/Movies/ImdbExternalUrlProvider.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Movies; + +/// <summary> +/// External URLs for IMDb. +/// </summary> +public class ImdbExternalUrlProvider : IExternalUrlProvider +{ + /// <inheritdoc/> + public string Name => "IMDb"; + + /// <inheritdoc/> + public IEnumerable<string> GetExternalUrls(BaseItem item) + { + var baseUrl = "https://www.imdb.com/"; + if (item.TryGetProviderId(MetadataProvider.Imdb, out var externalId)) + { + if (item is Person) + { + yield return baseUrl + $"name/{externalId}"; + } + else + { + yield return baseUrl + $"title/{externalId}"; + } + } + } +} diff --git a/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs b/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs index 8151ab471..aa2b2fae9 100644 --- a/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs +++ b/MediaBrowser.Providers/Movies/ImdbPersonExternalId.cs @@ -19,9 +19,6 @@ namespace MediaBrowser.Providers.Movies public ExternalIdMediaType? Type => ExternalIdMediaType.Person; /// <inheritdoc /> - public string UrlFormatString => "https://www.imdb.com/name/{0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) => item is Person; } } diff --git a/MediaBrowser.Providers/Movies/MovieMetadataService.cs b/MediaBrowser.Providers/Movies/MovieMetadataService.cs index 8997ddc64..8c169a7b6 100644 --- a/MediaBrowser.Providers/Movies/MovieMetadataService.cs +++ b/MediaBrowser.Providers/Movies/MovieMetadataService.cs @@ -1,40 +1,54 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Movies +namespace MediaBrowser.Providers.Movies; + +/// <summary> +/// Service to manage movie metadata. +/// </summary> +public class MovieMetadataService : MetadataService<Movie, MovieInfo> { - public class MovieMetadataService : MetadataService<Movie, MovieInfo> + /// <summary> + /// Initializes a new instance of the <see cref="MovieMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public MovieMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<MovieMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public MovieMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<MovieMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } + } - /// <inheritdoc /> - protected override void MergeData(MetadataResult<Movie> source, MetadataResult<Movie> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + /// <inheritdoc /> + protected override void MergeData(MetadataResult<Movie> source, MetadataResult<Movie> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) + { + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); - var sourceItem = source.Item; - var targetItem = target.Item; + var sourceItem = source.Item; + var targetItem = target.Item; - if (replaceData || string.IsNullOrEmpty(targetItem.CollectionName)) - { - targetItem.CollectionName = sourceItem.CollectionName; - } + if (replaceData || string.IsNullOrEmpty(targetItem.CollectionName)) + { + targetItem.CollectionName = sourceItem.CollectionName; } } } diff --git a/MediaBrowser.Providers/Movies/TrailerMetadataService.cs b/MediaBrowser.Providers/Movies/TrailerMetadataService.cs index e77d2fa8a..fa2442932 100644 --- a/MediaBrowser.Providers/Movies/TrailerMetadataService.cs +++ b/MediaBrowser.Providers/Movies/TrailerMetadataService.cs @@ -1,42 +1,56 @@ -#pragma warning disable CS1591 - using System.Linq; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Movies +namespace MediaBrowser.Providers.Movies; + +/// <summary> +/// Service to manage trailer metadata. +/// </summary> +public class TrailerMetadataService : MetadataService<Trailer, TrailerInfo> { - public class TrailerMetadataService : MetadataService<Trailer, TrailerInfo> + /// <summary> + /// Initializes a new instance of the <see cref="TrailerMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public TrailerMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<TrailerMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) + { + } + + /// <inheritdoc /> + protected override void MergeData(MetadataResult<Trailer> source, MetadataResult<Trailer> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) { - public TrailerMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<TrailerMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + + if (replaceData || target.Item.TrailerTypes.Length == 0) { + target.Item.TrailerTypes = source.Item.TrailerTypes; } - - /// <inheritdoc /> - protected override void MergeData(MetadataResult<Trailer> source, MetadataResult<Trailer> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) + else { - base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); - - if (replaceData || target.Item.TrailerTypes.Length == 0) - { - target.Item.TrailerTypes = source.Item.TrailerTypes; - } - else - { - target.Item.TrailerTypes = target.Item.TrailerTypes.Concat(source.Item.TrailerTypes).Distinct().ToArray(); - } + target.Item.TrailerTypes = target.Item.TrailerTypes.Concat(source.Item.TrailerTypes).Distinct().ToArray(); } } } diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs index 25698d8cb..7c193b4d5 100644 --- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs +++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs @@ -1,189 +1,206 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Music +namespace MediaBrowser.Providers.Music; + +/// <summary> +/// The album metadata service. +/// </summary> +public class AlbumMetadataService : MetadataService<MusicAlbum, AlbumInfo> { /// <summary> - /// The album metadata service. + /// Initializes a new instance of the <see cref="AlbumMetadataService"/> class. /// </summary> - public class AlbumMetadataService : MetadataService<MusicAlbum, AlbumInfo> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public AlbumMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<AlbumMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - /// <summary> - /// Initializes a new instance of the <see cref="AlbumMetadataService"/> class. - /// </summary> - /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> - /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> - /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> - /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> - /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> - public AlbumMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<AlbumMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } + } + + /// <inheritdoc /> + protected override bool EnableUpdatingPremiereDateFromChildren => true; - /// <inheritdoc /> - protected override bool EnableUpdatingPremiereDateFromChildren => true; + /// <inheritdoc /> + protected override bool EnableUpdatingGenresFromChildren => true; - /// <inheritdoc /> - protected override bool EnableUpdatingGenresFromChildren => true; + /// <inheritdoc /> + protected override bool EnableUpdatingStudiosFromChildren => true; - /// <inheritdoc /> - protected override bool EnableUpdatingStudiosFromChildren => true; + /// <inheritdoc /> + protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(MusicAlbum item) + => item.GetRecursiveChildren(i => i is Audio); - /// <inheritdoc /> - protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(MusicAlbum item) - => item.GetRecursiveChildren(i => i is Audio); + /// <inheritdoc /> + protected override Task AfterMetadataRefresh(MusicAlbum item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) + { + base.AfterMetadataRefresh(item, refreshOptions, cancellationToken); + + SetPeople(item); + + return Task.CompletedTask; + } + + /// <inheritdoc /> + protected override ItemUpdateType UpdateMetadataFromChildren(MusicAlbum item, IReadOnlyList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType) + { + var updateType = base.UpdateMetadataFromChildren(item, children, isFullRefresh, currentUpdateType); - /// <inheritdoc /> - protected override ItemUpdateType UpdateMetadataFromChildren(MusicAlbum item, IReadOnlyList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType) + // don't update user-changeable metadata for locked items + if (item.IsLocked) { - var updateType = base.UpdateMetadataFromChildren(item, children, isFullRefresh, currentUpdateType); + return updateType; + } - // don't update user-changeable metadata for locked items - if (item.IsLocked) + if (isFullRefresh || currentUpdateType > ItemUpdateType.None) + { + if (!item.LockedFields.Contains(MetadataField.Name)) { - return updateType; - } + var name = children.Select(i => i.Album).FirstOrDefault(i => !string.IsNullOrEmpty(i)); - if (isFullRefresh || currentUpdateType > ItemUpdateType.None) - { - if (!item.LockedFields.Contains(MetadataField.Name)) + if (!string.IsNullOrEmpty(name) + && !string.Equals(item.Name, name, StringComparison.Ordinal)) { - var name = children.Select(i => i.Album).FirstOrDefault(i => !string.IsNullOrEmpty(i)); - - if (!string.IsNullOrEmpty(name) - && !string.Equals(item.Name, name, StringComparison.Ordinal)) - { - item.Name = name; - updateType |= ItemUpdateType.MetadataEdit; - } + item.Name = name; + updateType |= ItemUpdateType.MetadataEdit; } - - var songs = children.Cast<Audio>().ToArray(); - - updateType |= SetArtistsFromSongs(item, songs); - updateType |= SetAlbumArtistFromSongs(item, songs); - updateType |= SetAlbumFromSongs(item, songs); - updateType |= SetPeople(item); } - return updateType; + var songs = children.Cast<Audio>().ToArray(); + + updateType |= SetArtistsFromSongs(item, songs); + updateType |= SetAlbumArtistFromSongs(item, songs); + updateType |= SetAlbumFromSongs(item, songs); } - private ItemUpdateType SetAlbumArtistFromSongs(MusicAlbum item, IReadOnlyList<Audio> songs) - { - var updateType = ItemUpdateType.None; + return updateType; + } - var albumArtists = songs - .SelectMany(i => i.AlbumArtists) - .GroupBy(i => i) - .OrderByDescending(g => g.Count()) - .Select(g => g.Key) - .ToArray(); + private ItemUpdateType SetAlbumArtistFromSongs(MusicAlbum item, IReadOnlyList<Audio> songs) + { + var updateType = ItemUpdateType.None; - updateType |= SetProviderIdFromSongs(item, songs, MetadataProvider.MusicBrainzAlbumArtist); + var albumArtists = songs + .SelectMany(i => i.AlbumArtists) + .GroupBy(i => i) + .OrderByDescending(g => g.Count()) + .Select(g => g.Key) + .ToArray(); - if (!item.AlbumArtists.SequenceEqual(albumArtists, StringComparer.OrdinalIgnoreCase)) - { - item.AlbumArtists = albumArtists; - updateType |= ItemUpdateType.MetadataEdit; - } + updateType |= SetProviderIdFromSongs(item, songs, MetadataProvider.MusicBrainzAlbumArtist); - return updateType; + if (!item.AlbumArtists.SequenceEqual(albumArtists, StringComparer.OrdinalIgnoreCase)) + { + item.AlbumArtists = albumArtists; + updateType |= ItemUpdateType.MetadataEdit; } - private ItemUpdateType SetArtistsFromSongs(MusicAlbum item, IReadOnlyList<Audio> songs) - { - var updateType = ItemUpdateType.None; + return updateType; + } - var artists = songs - .SelectMany(i => i.Artists) - .GroupBy(i => i) - .OrderByDescending(g => g.Count()) - .Select(g => g.Key) - .ToArray(); + private ItemUpdateType SetArtistsFromSongs(MusicAlbum item, IReadOnlyList<Audio> songs) + { + var updateType = ItemUpdateType.None; - if (!item.Artists.SequenceEqual(artists, StringComparer.OrdinalIgnoreCase)) - { - item.Artists = artists; - updateType |= ItemUpdateType.MetadataEdit; - } + var artists = songs + .SelectMany(i => i.Artists) + .GroupBy(i => i) + .OrderByDescending(g => g.Count()) + .Select(g => g.Key) + .ToArray(); - return updateType; + if (!item.Artists.SequenceEqual(artists, StringComparer.OrdinalIgnoreCase)) + { + item.Artists = artists; + updateType |= ItemUpdateType.MetadataEdit; } - private ItemUpdateType SetAlbumFromSongs(MusicAlbum item, IReadOnlyList<Audio> songs) - { - var updateType = ItemUpdateType.None; + return updateType; + } - updateType |= SetProviderIdFromSongs(item, songs, MetadataProvider.MusicBrainzAlbum); - updateType |= SetProviderIdFromSongs(item, songs, MetadataProvider.MusicBrainzReleaseGroup); + private ItemUpdateType SetAlbumFromSongs(MusicAlbum item, IReadOnlyList<Audio> songs) + { + var updateType = ItemUpdateType.None; - return updateType; - } + updateType |= SetProviderIdFromSongs(item, songs, MetadataProvider.MusicBrainzAlbum); + updateType |= SetProviderIdFromSongs(item, songs, MetadataProvider.MusicBrainzReleaseGroup); - private ItemUpdateType SetProviderIdFromSongs(BaseItem item, IReadOnlyList<Audio> songs, MetadataProvider provider) + return updateType; + } + + private ItemUpdateType SetProviderIdFromSongs(BaseItem item, IReadOnlyList<Audio> songs, MetadataProvider provider) + { + var ids = songs + .Select(i => i.GetProviderId(provider)) + .GroupBy(i => i) + .OrderByDescending(g => g.Count()) + .Select(g => g.Key) + .ToArray(); + + var id = item.GetProviderId(provider); + if (ids.Length != 0) { - var ids = songs - .Select(i => i.GetProviderId(provider)) - .GroupBy(i => i) - .OrderByDescending(g => g.Count()) - .Select(g => g.Key) - .ToArray(); - - var id = item.GetProviderId(provider); - if (ids.Length != 0) + var firstId = ids[0]; + if (!string.IsNullOrEmpty(firstId) + && (string.IsNullOrEmpty(id) + || !id.Equals(firstId, StringComparison.OrdinalIgnoreCase))) { - var firstId = ids[0]; - if (!string.IsNullOrEmpty(firstId) - && (string.IsNullOrEmpty(id) - || !id.Equals(firstId, StringComparison.OrdinalIgnoreCase))) - { - item.SetProviderId(provider, firstId); - return ItemUpdateType.MetadataEdit; - } + item.SetProviderId(provider, firstId); + return ItemUpdateType.MetadataEdit; } - - return ItemUpdateType.None; } - private void SetProviderId(MusicAlbum sourceItem, MusicAlbum targetItem, MetadataProvider provider) + return ItemUpdateType.None; + } + + private void SetProviderId(MusicAlbum sourceItem, MusicAlbum targetItem, MetadataProvider provider) + { + var source = sourceItem.GetProviderId(provider); + var target = targetItem.GetProviderId(provider); + if (!string.IsNullOrEmpty(source) + && (string.IsNullOrEmpty(target) + || !target.Equals(source, StringComparison.Ordinal))) { - var source = sourceItem.GetProviderId(provider); - var target = targetItem.GetProviderId(provider); - if (!string.IsNullOrEmpty(source) - && (string.IsNullOrEmpty(target) - || !target.Equals(source, StringComparison.Ordinal))) - { - targetItem.SetProviderId(provider, source); - } + targetItem.SetProviderId(provider, source); } + } - private ItemUpdateType SetPeople(MusicAlbum item) + private void SetPeople(MusicAlbum item) + { + if (item.AlbumArtists.Any() || item.Artists.Any()) { - var updateType = ItemUpdateType.None; + var people = new List<PersonInfo>(); - if (item.AlbumArtists.Any() || item.Artists.Any()) + foreach (var albumArtist in item.AlbumArtists) { - var people = new List<PersonInfo>(); - - foreach (var albumArtist in item.AlbumArtists) + if (!string.IsNullOrWhiteSpace(albumArtist)) { PeopleHelper.AddPerson(people, new PersonInfo { @@ -191,8 +208,11 @@ namespace MediaBrowser.Providers.Music Type = PersonKind.AlbumArtist }); } + } - foreach (var artist in item.Artists) + foreach (var artist in item.Artists) + { + if (!string.IsNullOrWhiteSpace(artist)) { PeopleHelper.AddPerson(people, new PersonInfo { @@ -200,50 +220,47 @@ namespace MediaBrowser.Providers.Music Type = PersonKind.Artist }); } - - LibraryManager.UpdatePeople(item, people); - updateType |= ItemUpdateType.MetadataEdit; } - return updateType; + LibraryManager.UpdatePeople(item, people); } + } - /// <inheritdoc /> - protected override void MergeData( - MetadataResult<MusicAlbum> source, - MetadataResult<MusicAlbum> target, - MetadataField[] lockedFields, - bool replaceData, - bool mergeMetadataSettings) - { - base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + /// <inheritdoc /> + protected override void MergeData( + MetadataResult<MusicAlbum> source, + MetadataResult<MusicAlbum> target, + MetadataField[] lockedFields, + bool replaceData, + bool mergeMetadataSettings) + { + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); - var sourceItem = source.Item; - var targetItem = target.Item; + var sourceItem = source.Item; + var targetItem = target.Item; - if (replaceData || targetItem.Artists.Count == 0) - { - targetItem.Artists = sourceItem.Artists; - } - else - { - targetItem.Artists = targetItem.Artists.Concat(sourceItem.Artists).Distinct().ToArray(); - } + if (replaceData || targetItem.Artists.Count == 0) + { + targetItem.Artists = sourceItem.Artists; + } + else + { + targetItem.Artists = targetItem.Artists.Concat(sourceItem.Artists).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); + } - if (replaceData || string.IsNullOrEmpty(targetItem.GetProviderId(MetadataProvider.MusicBrainzAlbumArtist))) - { - SetProviderId(sourceItem, targetItem, MetadataProvider.MusicBrainzAlbumArtist); - } + if (replaceData || string.IsNullOrEmpty(targetItem.GetProviderId(MetadataProvider.MusicBrainzAlbumArtist))) + { + SetProviderId(sourceItem, targetItem, MetadataProvider.MusicBrainzAlbumArtist); + } - if (replaceData || string.IsNullOrEmpty(targetItem.GetProviderId(MetadataProvider.MusicBrainzAlbum))) - { - SetProviderId(sourceItem, targetItem, MetadataProvider.MusicBrainzAlbum); - } + if (replaceData || string.IsNullOrEmpty(targetItem.GetProviderId(MetadataProvider.MusicBrainzAlbum))) + { + SetProviderId(sourceItem, targetItem, MetadataProvider.MusicBrainzAlbum); + } - if (replaceData || string.IsNullOrEmpty(targetItem.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup))) - { - SetProviderId(sourceItem, targetItem, MetadataProvider.MusicBrainzReleaseGroup); - } + if (replaceData || string.IsNullOrEmpty(targetItem.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup))) + { + SetProviderId(sourceItem, targetItem, MetadataProvider.MusicBrainzReleaseGroup); } } } diff --git a/MediaBrowser.Providers/Music/ArtistMetadataService.cs b/MediaBrowser.Providers/Music/ArtistMetadataService.cs index c47f9a500..22999077b 100644 --- a/MediaBrowser.Providers/Music/ArtistMetadataService.cs +++ b/MediaBrowser.Providers/Music/ArtistMetadataService.cs @@ -1,43 +1,56 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; -using System.Collections.Immutable; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Music +namespace MediaBrowser.Providers.Music; + +/// <summary> +/// Service to manage artist metadata. +/// </summary> +public class ArtistMetadataService : MetadataService<MusicArtist, ArtistInfo> { - public class ArtistMetadataService : MetadataService<MusicArtist, ArtistInfo> + /// <summary> + /// Initializes a new instance of the <see cref="ArtistMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public ArtistMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<ArtistMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public ArtistMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<ArtistMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } + } - /// <inheritdoc /> - protected override bool EnableUpdatingGenresFromChildren => true; + /// <inheritdoc /> + protected override bool EnableUpdatingGenresFromChildren => true; - /// <inheritdoc /> - protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(MusicArtist item) - { - return item.IsAccessedByName - ? item.GetTaggedItems(new InternalItemsQuery - { - Recursive = true, - IsFolder = false - }) - : item.GetRecursiveChildren(i => i is IHasArtist && !i.IsFolder); - } + /// <inheritdoc /> + protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(MusicArtist item) + { + return item.IsAccessedByName + ? item.GetTaggedItems(new InternalItemsQuery + { + Recursive = true, + IsFolder = false + }) + : item.GetRecursiveChildren(i => i is IHasArtist && !i.IsFolder); } } diff --git a/MediaBrowser.Providers/Music/AudioMetadataService.cs b/MediaBrowser.Providers/Music/AudioMetadataService.cs index 7b25bc0e4..f4d17686f 100644 --- a/MediaBrowser.Providers/Music/AudioMetadataService.cs +++ b/MediaBrowser.Providers/Music/AudioMetadataService.cs @@ -2,78 +2,83 @@ using System; using System.Linq; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Music +namespace MediaBrowser.Providers.Music; + +/// <summary> +/// The audio metadata service. +/// </summary> +public class AudioMetadataService : MetadataService<Audio, SongInfo> { /// <summary> - /// The audio metadata service. + /// Initializes a new instance of the <see cref="AudioMetadataService"/> class. /// </summary> - public class AudioMetadataService : MetadataService<Audio, SongInfo> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public AudioMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<AudioMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - /// <summary> - /// Initializes a new instance of the <see cref="AudioMetadataService"/> class. - /// </summary> - /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> - /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> - /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> - /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> - /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> - public AudioMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<AudioMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } + } - private void SetProviderId(Audio sourceItem, Audio targetItem, bool replaceData, MetadataProvider provider) + private void SetProviderId(Audio sourceItem, Audio targetItem, bool replaceData, MetadataProvider provider) + { + var target = targetItem.GetProviderId(provider); + if (replaceData || string.IsNullOrEmpty(target)) { - var target = targetItem.GetProviderId(provider); - if (replaceData || string.IsNullOrEmpty(target)) + var source = sourceItem.GetProviderId(provider); + if (!string.IsNullOrEmpty(source) + && (string.IsNullOrEmpty(target) + || !target.Equals(source, StringComparison.Ordinal))) { - var source = sourceItem.GetProviderId(provider); - if (!string.IsNullOrEmpty(source) - && (string.IsNullOrEmpty(target) - || !target.Equals(source, StringComparison.Ordinal))) - { - targetItem.SetProviderId(provider, source); - } + targetItem.SetProviderId(provider, source); } } + } - /// <inheritdoc /> - protected override void MergeData(MetadataResult<Audio> source, MetadataResult<Audio> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + /// <inheritdoc /> + protected override void MergeData(MetadataResult<Audio> source, MetadataResult<Audio> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) + { + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); - var sourceItem = source.Item; - var targetItem = target.Item; + var sourceItem = source.Item; + var targetItem = target.Item; - if (replaceData || targetItem.Artists.Count == 0) - { - targetItem.Artists = sourceItem.Artists; - } - else - { - targetItem.Artists = targetItem.Artists.Concat(sourceItem.Artists).Distinct().ToArray(); - } - - if (replaceData || string.IsNullOrEmpty(targetItem.Album)) - { - targetItem.Album = sourceItem.Album; - } + if (replaceData || targetItem.Artists.Count == 0) + { + targetItem.Artists = sourceItem.Artists; + } + else + { + targetItem.Artists = targetItem.Artists.Concat(sourceItem.Artists).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); + } - SetProviderId(sourceItem, targetItem, replaceData, MetadataProvider.MusicBrainzAlbumArtist); - SetProviderId(sourceItem, targetItem, replaceData, MetadataProvider.MusicBrainzAlbum); - SetProviderId(sourceItem, targetItem, replaceData, MetadataProvider.MusicBrainzReleaseGroup); + if (replaceData || string.IsNullOrEmpty(targetItem.Album)) + { + targetItem.Album = sourceItem.Album; } + + SetProviderId(sourceItem, targetItem, replaceData, MetadataProvider.MusicBrainzAlbumArtist); + SetProviderId(sourceItem, targetItem, replaceData, MetadataProvider.MusicBrainzAlbum); + SetProviderId(sourceItem, targetItem, replaceData, MetadataProvider.MusicBrainzReleaseGroup); } } diff --git a/MediaBrowser.Providers/Music/ImvdbId.cs b/MediaBrowser.Providers/Music/ImvdbId.cs index ed69f369c..b2c0b7019 100644 --- a/MediaBrowser.Providers/Music/ImvdbId.cs +++ b/MediaBrowser.Providers/Music/ImvdbId.cs @@ -19,9 +19,6 @@ namespace MediaBrowser.Providers.Music public ExternalIdMediaType? Type => null; /// <inheritdoc /> - public string? UrlFormatString => null; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) => item is MusicVideo; } diff --git a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs index 24c4b5501..345e13460 100644 --- a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs +++ b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs @@ -1,55 +1,70 @@ -#pragma warning disable CS1591 - +using System; using System.Linq; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Music +namespace MediaBrowser.Providers.Music; + +/// <summary> +/// Service to manage music video metadata. +/// </summary> +public class MusicVideoMetadataService : MetadataService<MusicVideo, MusicVideoInfo> { - public class MusicVideoMetadataService : MetadataService<MusicVideo, MusicVideoInfo> + /// <summary> + /// Initializes a new instance of the <see cref="MusicVideoMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public MusicVideoMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<MusicVideoMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public MusicVideoMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<MusicVideoMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } + } - /// <inheritdoc /> - protected override void MergeData( - MetadataResult<MusicVideo> source, - MetadataResult<MusicVideo> target, - MetadataField[] lockedFields, - bool replaceData, - bool mergeMetadataSettings) - { - base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + /// <inheritdoc /> + protected override void MergeData( + MetadataResult<MusicVideo> source, + MetadataResult<MusicVideo> target, + MetadataField[] lockedFields, + bool replaceData, + bool mergeMetadataSettings) + { + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); - var sourceItem = source.Item; - var targetItem = target.Item; + var sourceItem = source.Item; + var targetItem = target.Item; - if (replaceData || string.IsNullOrEmpty(targetItem.Album)) - { - targetItem.Album = sourceItem.Album; - } + if (replaceData || string.IsNullOrEmpty(targetItem.Album)) + { + targetItem.Album = sourceItem.Album; + } - if (replaceData || targetItem.Artists.Count == 0) - { - targetItem.Artists = sourceItem.Artists; - } - else - { - targetItem.Artists = targetItem.Artists.Concat(sourceItem.Artists).Distinct().ToArray(); - } + if (replaceData || targetItem.Artists.Count == 0) + { + targetItem.Artists = sourceItem.Artists; + } + else + { + targetItem.Artists = targetItem.Artists.Concat(sourceItem.Artists).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); } } } diff --git a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs index 46eb546c2..4b0044dcf 100644 --- a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs +++ b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs @@ -1,25 +1,39 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.MusicGenres +namespace MediaBrowser.Providers.MusicGenres; + +/// <summary> +/// Service to manage music genre metadata. +/// </summary> +public class MusicGenreMetadataService : MetadataService<MusicGenre, ItemLookupInfo> { - public class MusicGenreMetadataService : MetadataService<MusicGenre, ItemLookupInfo> + /// <summary> + /// Initializes a new instance of the <see cref="MusicGenreMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public MusicGenreMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<MusicGenreMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public MusicGenreMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<MusicGenreMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } } } diff --git a/MediaBrowser.Providers/People/PersonMetadataService.cs b/MediaBrowser.Providers/People/PersonMetadataService.cs index 59bf7e4e6..23aff246e 100644 --- a/MediaBrowser.Providers/People/PersonMetadataService.cs +++ b/MediaBrowser.Providers/People/PersonMetadataService.cs @@ -1,25 +1,39 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.People +namespace MediaBrowser.Providers.People; + +/// <summary> +/// Service to manage person metadata. +/// </summary> +public class PersonMetadataService : MetadataService<Person, PersonLookupInfo> { - public class PersonMetadataService : MetadataService<Person, PersonLookupInfo> + /// <summary> + /// Initializes a new instance of the <see cref="PersonMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public PersonMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<PersonMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public PersonMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<PersonMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } } } diff --git a/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs b/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs index f2cccb90f..f05ed904f 100644 --- a/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs +++ b/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs @@ -1,25 +1,39 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Photos +namespace MediaBrowser.Providers.Photos; + +/// <summary> +/// Service to manage photo album metadata. +/// </summary> +public class PhotoAlbumMetadataService : MetadataService<PhotoAlbum, ItemLookupInfo> { - public class PhotoAlbumMetadataService : MetadataService<PhotoAlbum, ItemLookupInfo> + /// <summary> + /// Initializes a new instance of the <see cref="PhotoAlbumMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public PhotoAlbumMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<PhotoAlbumMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public PhotoAlbumMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<PhotoAlbumMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } } } diff --git a/MediaBrowser.Providers/Photos/PhotoMetadataService.cs b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs index 6941401e0..0f7a33560 100644 --- a/MediaBrowser.Providers/Photos/PhotoMetadataService.cs +++ b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs @@ -1,25 +1,39 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Photos +namespace MediaBrowser.Providers.Photos; + +/// <summary> +/// Service to manage photo metadata. +/// </summary> +public class PhotoMetadataService : MetadataService<Photo, ItemLookupInfo> { - public class PhotoMetadataService : MetadataService<Photo, ItemLookupInfo> + /// <summary> + /// Initializes a new instance of the <see cref="PhotoMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public PhotoMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<PhotoMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public PhotoMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<PhotoMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } } } diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs index 51a3ba0c7..4c10fe3f1 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs @@ -99,7 +99,7 @@ public class PlaylistItemsProvider : ILocalMetadataProvider<Playlist>, .OfType<CollectionFolder>() .Where(f => f.CollectionType.HasValue && !_ignoredCollections.Contains(f.CollectionType.Value)) .SelectMany(f => f.PhysicalLocations) - .Distinct(StringComparer.OrdinalIgnoreCase) + .Distinct() .ToList(); using (var stream = File.OpenRead(path)) @@ -215,7 +215,7 @@ public class PlaylistItemsProvider : ILocalMetadataProvider<Playlist>, if (!string.IsNullOrWhiteSpace(path) && item.IsFileProtocol) { var file = directoryService.GetFile(path); - if (file is not null && file.LastWriteTimeUtc != item.DateModified) + if (file is not null && item.HasChanged(file.LastWriteTimeUtc)) { _logger.LogDebug("Refreshing {Path} due to date modified timestamp change.", path); return true; diff --git a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs index 7be54453f..8df15e440 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs @@ -1,10 +1,10 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using System.Linq; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; @@ -12,62 +12,76 @@ using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Playlists +namespace MediaBrowser.Providers.Playlists; + +/// <summary> +/// Service to manage playlist metadata. +/// </summary> +public class PlaylistMetadataService : MetadataService<Playlist, ItemLookupInfo> { - public class PlaylistMetadataService : MetadataService<Playlist, ItemLookupInfo> + /// <summary> + /// Initializes a new instance of the <see cref="PlaylistMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public PlaylistMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<PlaylistMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public PlaylistMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<PlaylistMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } + } - /// <inheritdoc /> - protected override bool EnableUpdatingGenresFromChildren => true; + /// <inheritdoc /> + protected override bool EnableUpdatingGenresFromChildren => true; - /// <inheritdoc /> - protected override bool EnableUpdatingOfficialRatingFromChildren => true; + /// <inheritdoc /> + protected override bool EnableUpdatingOfficialRatingFromChildren => true; - /// <inheritdoc /> - protected override bool EnableUpdatingStudiosFromChildren => true; + /// <inheritdoc /> + protected override bool EnableUpdatingStudiosFromChildren => true; - /// <inheritdoc /> - protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(Playlist item) - => item.GetLinkedChildren(); + /// <inheritdoc /> + protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(Playlist item) + => item.GetLinkedChildren(); - /// <inheritdoc /> - protected override void MergeData(MetadataResult<Playlist> source, MetadataResult<Playlist> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + /// <inheritdoc /> + protected override void MergeData(MetadataResult<Playlist> source, MetadataResult<Playlist> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) + { + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); - var sourceItem = source.Item; - var targetItem = target.Item; + var sourceItem = source.Item; + var targetItem = target.Item; - if (mergeMetadataSettings) - { - targetItem.PlaylistMediaType = sourceItem.PlaylistMediaType; + if (mergeMetadataSettings) + { + targetItem.PlaylistMediaType = sourceItem.PlaylistMediaType; - if (replaceData || targetItem.LinkedChildren.Length == 0) - { - targetItem.LinkedChildren = sourceItem.LinkedChildren; - } - else - { - targetItem.LinkedChildren = sourceItem.LinkedChildren.Concat(targetItem.LinkedChildren).Distinct().ToArray(); - } + if (replaceData || targetItem.LinkedChildren.Length == 0) + { + targetItem.LinkedChildren = sourceItem.LinkedChildren; + } + else + { + targetItem.LinkedChildren = sourceItem.LinkedChildren.Concat(targetItem.LinkedChildren).Distinct().ToArray(); + } - if (replaceData || targetItem.Shares.Count == 0) - { - targetItem.Shares = sourceItem.Shares; - } - else - { - targetItem.Shares = sourceItem.Shares.Concat(targetItem.Shares).DistinctBy(s => s.UserId).ToArray(); - } + if (replaceData || targetItem.Shares.Count == 0) + { + targetItem.Shares = sourceItem.Shares; + } + else + { + targetItem.Shares = sourceItem.Shares.Concat(targetItem.Shares).DistinctBy(s => s.UserId).ToArray(); } } } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs index 138cfef19..622bb1dba 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalId.cs @@ -19,9 +19,6 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public ExternalIdMediaType? Type => null; /// <inheritdoc /> - public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) => item is MusicAlbum; } } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalUrlProvider.cs new file mode 100644 index 000000000..01d284105 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumExternalUrlProvider.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Plugins.AudioDb; + +/// <summary> +/// External artist URLs for AudioDb. +/// </summary> +public class AudioDbAlbumExternalUrlProvider : IExternalUrlProvider +{ + /// <inheritdoc/> + public string Name => "TheAudioDb Album"; + + /// <inheritdoc/> + public IEnumerable<string> GetExternalUrls(BaseItem item) + { + if (item.TryGetProviderId(MetadataProvider.AudioDbAlbum, out var externalId)) + { + var baseUrl = "https://www.theaudiodb.com/"; + switch (item) + { + case MusicAlbum: + yield return baseUrl + $"album/{externalId}"; + break; + } + } + } +} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs index 8a516e1ce..d2eeb7f07 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.IO; -using System.Linq; using System.Net.Http; using System.Text.Json; using System.Threading; @@ -50,9 +49,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb /// <inheritdoc /> public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { - var id = item.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup); - - if (!string.IsNullOrWhiteSpace(id)) + if (item.TryGetProviderId(MetadataProvider.MusicBrainzReleaseGroup, out var id)) { await AudioDbAlbumProvider.Current.EnsureInfo(id, cancellationToken).ConfigureAwait(false); @@ -70,7 +67,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb } } - return Enumerable.Empty<RemoteImageInfo>(); + return []; } private List<RemoteImageInfo> GetImages(AudioDbAlbumProvider.Album item) diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs index ff30af879..49ece22a9 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs @@ -94,7 +94,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb if (!string.IsNullOrWhiteSpace(result.strArtist)) { - item.AlbumArtists = new string[] { result.strArtist }; + item.AlbumArtists = [result.strArtist]; } if (!string.IsNullOrEmpty(result.intYearReleased)) @@ -104,7 +104,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb if (!string.IsNullOrEmpty(result.strGenre)) { - item.Genres = new[] { result.strGenre }; + item.Genres = [result.strGenre]; } item.SetProviderId(MetadataProvider.AudioDbArtist, result.idArtist); @@ -170,6 +170,11 @@ namespace MediaBrowser.Providers.Plugins.AudioDb var url = AudioDbArtistProvider.BaseUrl + "/album-mb.php?i=" + musicBrainzReleaseGroupId; var path = GetAlbumInfoPath(_config.ApplicationPaths, musicBrainzReleaseGroupId); + var fileInfo = _fileSystem.GetFileSystemInfo(path); + if (fileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) + { + return; + } Directory.CreateDirectory(Path.GetDirectoryName(path)); diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs index 8aceb48c0..3b5955b5b 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalId.cs @@ -19,9 +19,6 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public ExternalIdMediaType? Type => ExternalIdMediaType.Artist; /// <inheritdoc /> - public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) => item is MusicArtist; } } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalUrlProvider.cs new file mode 100644 index 000000000..56b0d9bcb --- /dev/null +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalUrlProvider.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Plugins.AudioDb; + +/// <summary> +/// External artist URLs for AudioDb. +/// </summary> +public class AudioDbArtistExternalUrlProvider : IExternalUrlProvider +{ + /// <inheritdoc/> + public string Name => "TheAudioDb Artist"; + + /// <inheritdoc/> + public IEnumerable<string> GetExternalUrls(BaseItem item) + { + if (item.TryGetProviderId(MetadataProvider.AudioDbArtist, out var externalId)) + { + var baseUrl = "https://www.theaudiodb.com/"; + switch (item) + { + case MusicAlbum: + case Person: + yield return baseUrl + $"artist/{externalId}"; + break; + } + } + } +} diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs index 4e7757cd2..88730f34d 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.IO; -using System.Linq; using System.Net.Http; using System.Text.Json; using System.Threading; @@ -43,21 +42,19 @@ namespace MediaBrowser.Providers.Plugins.AudioDb /// <inheritdoc /> public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { - return new ImageType[] - { + return + [ ImageType.Primary, ImageType.Logo, ImageType.Banner, ImageType.Backdrop - }; + ]; } /// <inheritdoc /> public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { - var id = item.GetProviderId(MetadataProvider.MusicBrainzArtist); - - if (!string.IsNullOrWhiteSpace(id)) + if (item.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out var id)) { await AudioDbArtistProvider.Current.EnsureArtistInfo(id, cancellationToken).ConfigureAwait(false); @@ -75,7 +72,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb } } - return Enumerable.Empty<RemoteImageInfo>(); + return []; } private List<RemoteImageInfo> GetImages(AudioDbArtistProvider.Artist item) diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs index 014481da2..fdfd330cd 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherAlbumExternalId.cs @@ -19,9 +19,6 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public ExternalIdMediaType? Type => ExternalIdMediaType.Album; /// <inheritdoc /> - public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) => item is Audio; } } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs index 787539104..5a39ec1cd 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbOtherArtistExternalId.cs @@ -19,9 +19,6 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist; /// <inheritdoc /> - public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum; } } diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs index 825fe32fa..f1fc4a137 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs @@ -20,8 +20,5 @@ public class MusicBrainzAlbumArtistExternalId : IExternalId public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist; /// <inheritdoc /> - public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) => item is Audio; } diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalUrlProvider.cs new file mode 100644 index 000000000..f4b3f4f8c --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalUrlProvider.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Plugins.MusicBrainz; + +/// <summary> +/// External album artist URLs for MusicBrainz. +/// </summary> +public class MusicBrainzAlbumArtistExternalUrlProvider : IExternalUrlProvider +{ + /// <inheritdoc/> + public string Name => "MusicBrainz Album Artist"; + + /// <inheritdoc/> + public IEnumerable<string> GetExternalUrls(BaseItem item) + { + if (item is MusicAlbum) + { + if (item.TryGetProviderId(MetadataProvider.MusicBrainzAlbumArtist, out var externalId)) + { + yield return Plugin.Instance!.Configuration.Server + $"/artist/{externalId}"; + } + } + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs index b7d53984c..48784e0ec 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs @@ -20,8 +20,5 @@ public class MusicBrainzAlbumExternalId : IExternalId public ExternalIdMediaType? Type => ExternalIdMediaType.Album; /// <inheritdoc /> - public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/release/{0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum; } diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalUrlProvider.cs new file mode 100644 index 000000000..b9d3b4835 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalUrlProvider.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Plugins.MusicBrainz; + +/// <summary> +/// External album URLs for MusicBrainz. +/// </summary> +public class MusicBrainzAlbumExternalUrlProvider : IExternalUrlProvider +{ + /// <inheritdoc/> + public string Name => "MusicBrainz Album"; + + /// <inheritdoc/> + public IEnumerable<string> GetExternalUrls(BaseItem item) + { + if (item is MusicAlbum) + { + if (item.TryGetProviderId(MetadataProvider.MusicBrainzAlbum, out var externalId)) + { + yield return Plugin.Instance!.Configuration.Server + $"/release/{externalId}"; + } + } + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs index b3f001618..bd5d67ed1 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs @@ -20,8 +20,5 @@ public class MusicBrainzArtistExternalId : IExternalId public ExternalIdMediaType? Type => ExternalIdMediaType.Artist; /// <inheritdoc /> - public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) => item is MusicArtist; } diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalUrlProvider.cs new file mode 100644 index 000000000..ee5a597c6 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalUrlProvider.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Plugins.MusicBrainz; + +/// <summary> +/// External artist URLs for MusicBrainz. +/// </summary> +public class MusicBrainzArtistExternalUrlProvider : IExternalUrlProvider +{ + /// <inheritdoc/> + public string Name => "MusicBrainz Artist"; + + /// <inheritdoc/> + public IEnumerable<string> GetExternalUrls(BaseItem item) + { + if (item.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out var externalId)) + { + switch (item) + { + case MusicAlbum: + case Person: + yield return Plugin.Instance!.Configuration.Server + $"/artist/{externalId}"; + + break; + } + } + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs index a0a922293..470cdad66 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs @@ -20,8 +20,5 @@ public class MusicBrainzOtherArtistExternalId : IExternalId public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist; /// <inheritdoc /> - public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) => item is Audio or MusicAlbum; } diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzRecordingId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzRecordingId.cs new file mode 100644 index 000000000..89d8b9b99 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzRecordingId.cs @@ -0,0 +1,24 @@ +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Providers.Plugins.MusicBrainz; + +/// <summary> +/// MusicBrainz recording id. +/// </summary> +public class MusicBrainzRecordingId : IExternalId +{ + /// <inheritdoc /> + public string ProviderName => "MusicBrainz"; + + /// <inheritdoc /> + public string Key => MetadataProvider.MusicBrainzRecording.ToString(); + + /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.Recording; + + /// <inheritdoc /> + public bool Supports(IHasProviderIds item) => item is Audio; +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs index 47b6d6963..c19b62abf 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs @@ -20,8 +20,5 @@ public class MusicBrainzReleaseGroupExternalId : IExternalId public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup; /// <inheritdoc /> - public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/release-group/{0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) => item is Audio or MusicAlbum; } diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalUrlProvider.cs new file mode 100644 index 000000000..dd0a939f7 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalUrlProvider.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Plugins.MusicBrainz; + +/// <summary> +/// External release group URLs for MusicBrainz. +/// </summary> +public class MusicBrainzReleaseGroupExternalUrlProvider : IExternalUrlProvider +{ + /// <inheritdoc/> + public string Name => "MusicBrainz Release Group"; + + /// <inheritdoc/> + public IEnumerable<string> GetExternalUrls(BaseItem item) + { + if (item is MusicAlbum) + { + if (item.TryGetProviderId(MetadataProvider.MusicBrainzReleaseGroup, out var externalId)) + { + yield return Plugin.Instance!.Configuration.Server + $"/release-group/{externalId}"; + } + } + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackExternalUrlProvider.cs new file mode 100644 index 000000000..59e6f42b1 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackExternalUrlProvider.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Plugins.MusicBrainz; + +/// <summary> +/// External track URLs for MusicBrainz. +/// </summary> +public class MusicBrainzTrackExternalUrlProvider : IExternalUrlProvider +{ + /// <inheritdoc/> + public string Name => "MusicBrainz Track"; + + /// <inheritdoc/> + public IEnumerable<string> GetExternalUrls(BaseItem item) + { + if (item is Audio) + { + if (item.TryGetProviderId(MetadataProvider.MusicBrainzTrack, out var externalId)) + { + yield return Plugin.Instance!.Configuration.Server + $"/track/{externalId}"; + } + } + } +} diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs index cb4345660..6a7b6f541 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs @@ -20,8 +20,5 @@ public class MusicBrainzTrackId : IExternalId public ExternalIdMediaType? Type => ExternalIdMediaType.Track; /// <inheritdoc /> - public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/track/{0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) => item is Audio; } diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs index d8b33a799..ccff31eba 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs @@ -55,13 +55,12 @@ namespace MediaBrowser.Providers.Plugins.Omdb if (info.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out string? seriesImdbId) && !string.IsNullOrEmpty(seriesImdbId) - && info.IndexNumber.HasValue - && info.ParentIndexNumber.HasValue) + && info.IndexNumber.HasValue) { result.HasMetadata = await _omdbProvider.FetchEpisodeData( result, info.IndexNumber.Value, - info.ParentIndexNumber.Value, + info.ParentIndexNumber ?? 1, info.GetProviderId(MetadataProvider.Imdb), seriesImdbId, info.MetadataLanguage, diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index de0da7f7b..ad9edb031 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -421,7 +421,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb { var person = new PersonInfo { - Name = result.Director, + Name = result.Director.Trim(), Type = PersonKind.Director }; @@ -432,7 +432,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb { var person = new PersonInfo { - Name = result.Writer, + Name = result.Writer.Trim(), Type = PersonKind.Writer }; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs index d453a4ff4..2076589d3 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs @@ -21,9 +21,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets public ExternalIdMediaType? Type => ExternalIdMediaType.BoxSet; /// <inheritdoc /> - public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "collection/{0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) { return item is Movie || item is MusicVideo || item is Trailer; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs index 99b759ae2..f11b1d95a 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs @@ -39,6 +39,21 @@ namespace MediaBrowser.Providers.Plugins.Tmdb public int MaxCastMembers { get; set; } = 15; /// <summary> + /// Gets or sets a value indicating the maximum number of crew members to fetch for an item. + /// </summary> + public int MaxCrewMembers { get; set; } = 15; + + /// <summary> + /// Gets or sets a value indicating whether to hide cast members without profile images. + /// </summary> + public bool HideMissingCastMembers { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to hide crew members without profile images. + /// </summary> + public bool HideMissingCrewMembers { get; set; } + + /// <summary> /// Gets or sets a value indicating the poster image size to fetch. /// </summary> public string? PosterSize { get; set; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html index f3c24e7b4..89d380ec1 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html @@ -25,9 +25,24 @@ <input is="emby-checkbox" type="checkbox" id="importSeasonName" /> <span>Import season name from metadata fetched for series.</span> </label> - <div class="inputContainer"> - <input is="emby-input" type="number" id="maxCastMembers" pattern="[0-9]*" required min="0" max="1000" label="Max Cast Members" /> - <div class="fieldDescription">The maximum number of cast members to fetch for an item.</div> + <div class="verticalSection"> + <h2>Cast & Crew Settings</h2> + <div class="inputContainer"> + <input is="emby-input" type="number" id="maxCastMembers" pattern="[0-9]*" required min="0" max="1000" label="Max Cast Members" /> + <div class="fieldDescription">The maximum number of cast members to fetch for an item.</div> + </div> + <div class="inputContainer"> + <input is="emby-input" type="number" id="maxCrewMembers" pattern="[0-9]*" required min="0" max="1000" label="Max Crew Members" /> + <div class="fieldDescription">The maximum number of crew members to fetch for an item.</div> + </div> + <label class="checkboxContainer"> + <input is="emby-checkbox" type="checkbox" id="hideMissingCastMembers" /> + <span>Hide cast members without profile images.</span> + </label> + <label class="checkboxContainer"> + <input is="emby-checkbox" type="checkbox" id="hideMissingCrewMembers" /> + <span>Hide crew members without profile images.</span> + </label> </div> <div class="verticalSection verticalSection-extrabottompadding"> <h2>Image Scaling</h2> @@ -129,6 +144,8 @@ document.querySelector('#excludeTagsSeries').checked = config.ExcludeTagsSeries; document.querySelector('#excludeTagsMovies').checked = config.ExcludeTagsMovies; document.querySelector('#importSeasonName').checked = config.ImportSeasonName; + document.querySelector('#hideMissingCastMembers').checked = config.HideMissingCastMembers; + document.querySelector('#hideMissingCrewMembers').checked = config.HideMissingCrewMembers; var maxCastMembers = document.querySelector('#maxCastMembers'); maxCastMembers.value = config.MaxCastMembers; @@ -137,12 +154,18 @@ cancelable: false })); + var maxCrewMembers = document.querySelector('#maxCrewMembers'); + maxCrewMembers.value = config.MaxCrewMembers; + maxCrewMembers.dispatchEvent(new Event('change', { + bubbles: true, + cancelable: false + })); + pluginConfig = config; configureImageScaling(); }); }); - document.querySelector('.configForm') .addEventListener('submit', function (e) { Dashboard.showLoadingMsg(); @@ -153,6 +176,9 @@ config.ExcludeTagsMovies = document.querySelector('#excludeTagsMovies').checked; config.ImportSeasonName = document.querySelector('#importSeasonName').checked; config.MaxCastMembers = document.querySelector('#maxCastMembers').value; + config.MaxCrewMembers = document.querySelector('#maxCrewMembers').value; + config.HideMissingCastMembers = document.querySelector('#hideMissingCastMembers').checked; + config.HideMissingCrewMembers = document.querySelector('#hideMissingCrewMembers').checked; config.PosterSize = document.querySelector('#selectPosterSize').value; config.BackdropSize = document.querySelector('#selectBackdropSize').value; config.LogoSize = document.querySelector('#selectLogoSize').value; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs index 6d6032e8f..9a1d872ec 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs @@ -21,9 +21,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies public ExternalIdMediaType? Type => ExternalIdMediaType.Movie; /// <inheritdoc /> - public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "movie/{0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) { // Supports images for tv movies diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index eef08b251..2f8cb68ef 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -144,6 +144,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies { var tmdbId = info.GetProviderId(MetadataProvider.Tmdb); var imdbId = info.GetProviderId(MetadataProvider.Imdb); + var config = Plugin.Instance.Configuration; if (string.IsNullOrEmpty(tmdbId) && string.IsNullOrEmpty(imdbId)) { @@ -234,7 +235,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies var genres = movieResult.Genres; - foreach (var genre in genres.Select(g => g.Name)) + foreach (var genre in genres.Select(g => g.Name).Trimmed()) { movie.AddGenre(genre); } @@ -249,12 +250,26 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies if (movieResult.Credits?.Cast is not null) { - foreach (var actor in movieResult.Credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) + var castQuery = movieResult.Credits.Cast.AsEnumerable(); + + if (config.HideMissingCastMembers) { + castQuery = castQuery.Where(a => !string.IsNullOrEmpty(a.ProfilePath)); + } + + castQuery = castQuery.OrderBy(a => a.Order).Take(config.MaxCastMembers); + + foreach (var actor in castQuery) + { + if (string.IsNullOrWhiteSpace(actor.Name)) + { + continue; + } + var personInfo = new PersonInfo { Name = actor.Name.Trim(), - Role = actor.Character, + Role = actor.Character?.Trim() ?? string.Empty, Type = PersonKind.Actor, SortOrder = actor.Order }; @@ -275,32 +290,47 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies if (movieResult.Credits?.Crew is not null) { - foreach (var person in movieResult.Credits.Crew) + var crewQuery = movieResult.Credits.Crew + .Select(crewMember => new + { + CrewMember = crewMember, + PersonType = TmdbUtils.MapCrewToPersonType(crewMember) + }) + .Where(entry => + TmdbUtils.WantedCrewKinds.Contains(entry.PersonType) || + TmdbUtils.WantedCrewTypes.Contains(entry.CrewMember.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)); + + if (config.HideMissingCrewMembers) + { + crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath)); + } + + crewQuery = crewQuery.Take(config.MaxCrewMembers); + + foreach (var entry in crewQuery) { - // Normalize this - var type = TmdbUtils.MapCrewToPersonType(person); + var crewMember = entry.CrewMember; - if (!TmdbUtils.WantedCrewKinds.Contains(type) - && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrWhiteSpace(crewMember.Name)) { continue; } var personInfo = new PersonInfo { - Name = person.Name.Trim(), - Role = person.Job, - Type = type + Name = crewMember.Name.Trim(), + Role = crewMember.Job?.Trim() ?? string.Empty, + Type = entry.PersonType }; - if (!string.IsNullOrWhiteSpace(person.ProfilePath)) + if (!string.IsNullOrWhiteSpace(crewMember.ProfilePath)) { - personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(person.ProfilePath); + personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath); } - if (person.Id > 0) + if (crewMember.Id > 0) { - personInfo.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture)); + personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCulture)); } metadataResult.AddPerson(personInfo); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs index d26a70028..2c0787b15 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs @@ -20,9 +20,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People public ExternalIdMediaType? Type => ExternalIdMediaType.Person; /// <inheritdoc /> - public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "person/{0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) { return item is Person; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs index d1fec7cb1..7de0e430f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs @@ -63,10 +63,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return Enumerable.Empty<RemoteImageInfo>(); } - var seasonNumber = episode.ParentIndexNumber; + var seasonNumber = episode.ParentIndexNumber ?? 1; var episodeNumber = episode.IndexNumber; - if (!seasonNumber.HasValue || !episodeNumber.HasValue) + if (!episodeNumber.HasValue) { return Enumerable.Empty<RemoteImageInfo>(); } @@ -75,7 +75,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV // TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here var episodeResult = await _tmdbClientManager - .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, series.DisplayOrder, null, null, cancellationToken) + .GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, series.DisplayOrder, null, null, cancellationToken) .ConfigureAwait(false); var stills = episodeResult?.Images?.Stills; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index e628abde5..7d0900cfd 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs @@ -47,7 +47,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) { // The search query must either provide an episode number or date - if (!searchInfo.IndexNumber.HasValue || !searchInfo.ParentIndexNumber.HasValue) + if (!searchInfo.IndexNumber.HasValue) { return Enumerable.Empty<RemoteSearchResult>(); } @@ -81,6 +81,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken) { var metadataResult = new MetadataResult<Episode>(); + var config = Plugin.Instance.Configuration; // Allowing this will dramatically increase scan times if (info.IsMissingEpisode) @@ -96,10 +97,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return metadataResult; } - var seasonNumber = info.ParentIndexNumber; + var seasonNumber = info.ParentIndexNumber ?? 1; var episodeNumber = info.IndexNumber; - if (!seasonNumber.HasValue || !episodeNumber.HasValue) + if (!episodeNumber.HasValue) { return metadataResult; } @@ -112,7 +113,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV List<TvEpisode>? result = null; for (int? episode = startindex; episode <= endindex; episode++) { - var episodeInfo = await _tmdbClientManager.GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episode.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken).ConfigureAwait(false); + var episodeInfo = await _tmdbClientManager.GetEpisodeAsync(seriesTmdbId, seasonNumber, episode.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken).ConfigureAwait(false); if (episodeInfo is not null) { (result ??= new List<TvEpisode>()).Add(episodeInfo); @@ -156,7 +157,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV else { episodeResult = await _tmdbClientManager - .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) + .GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) .ConfigureAwait(false); } @@ -206,52 +207,106 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV if (credits?.Cast is not null) { - foreach (var actor in credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) + var castQuery = config.HideMissingCastMembers + ? credits.Cast.Where(a => !string.IsNullOrEmpty(a.ProfilePath)).OrderBy(a => a.Order) + : credits.Cast.OrderBy(a => a.Order); + + foreach (var actor in castQuery.Take(config.MaxCastMembers)) { - metadataResult.AddPerson(new PersonInfo + if (string.IsNullOrWhiteSpace(actor.Name)) + { + continue; + } + + var personInfo = new PersonInfo { Name = actor.Name.Trim(), - Role = actor.Character, + Role = actor.Character?.Trim() ?? string.Empty, Type = PersonKind.Actor, - SortOrder = actor.Order - }); + SortOrder = actor.Order, + ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath) + }; + + if (actor.Id > 0) + { + personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture)); + } + + metadataResult.AddPerson(personInfo); } } if (credits?.GuestStars is not null) { - foreach (var guest in credits.GuestStars.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) + var guestQuery = config.HideMissingCastMembers + ? credits.GuestStars.Where(a => !string.IsNullOrEmpty(a.ProfilePath)).OrderBy(a => a.Order) + : credits.GuestStars.OrderBy(a => a.Order); + + foreach (var guest in guestQuery.Take(config.MaxCastMembers)) { - metadataResult.AddPerson(new PersonInfo + if (string.IsNullOrWhiteSpace(guest.Name)) + { + continue; + } + + var personInfo = new PersonInfo { Name = guest.Name.Trim(), - Role = guest.Character, + Role = guest.Character?.Trim() ?? string.Empty, Type = PersonKind.GuestStar, - SortOrder = guest.Order - }); + SortOrder = guest.Order, + ImageUrl = _tmdbClientManager.GetProfileUrl(guest.ProfilePath) + }; + + if (guest.Id > 0) + { + personInfo.SetProviderId(MetadataProvider.Tmdb, guest.Id.ToString(CultureInfo.InvariantCulture)); + } + + metadataResult.AddPerson(personInfo); } } - // and the rest from crew if (credits?.Crew is not null) { - foreach (var person in credits.Crew) + var crewQuery = credits.Crew + .Select(crewMember => new + { + CrewMember = crewMember, + PersonType = TmdbUtils.MapCrewToPersonType(crewMember) + }) + .Where(entry => + TmdbUtils.WantedCrewKinds.Contains(entry.PersonType) || + TmdbUtils.WantedCrewTypes.Contains(entry.CrewMember.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)); + + if (config.HideMissingCrewMembers) + { + crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath)); + } + + foreach (var entry in crewQuery.Take(config.MaxCrewMembers)) { - // Normalize this - var type = TmdbUtils.MapCrewToPersonType(person); + var crewMember = entry.CrewMember; - if (!TmdbUtils.WantedCrewKinds.Contains(type) - && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrWhiteSpace(crewMember.Name)) { continue; } - metadataResult.AddPerson(new PersonInfo + var personInfo = new PersonInfo { - Name = person.Name.Trim(), - Role = person.Job, - Type = type - }); + Name = crewMember.Name.Trim(), + Role = crewMember.Job?.Trim() ?? string.Empty, + Type = entry.PersonType, + ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath) + }; + + if (crewMember.Id > 0) + { + personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCulture)); + } + + metadataResult.AddPerson(personInfo); } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs index 3f208b599..cfef0d656 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs @@ -42,6 +42,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV public async Task<MetadataResult<Season>> GetMetadata(SeasonInfo info, CancellationToken cancellationToken) { var result = new MetadataResult<Season>(); + var config = Plugin.Instance.Configuration; info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string? seriesTmdbId); @@ -65,10 +66,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV result.Item = new Season { IndexNumber = seasonNumber, - Overview = seasonResult.Overview + Overview = seasonResult.Overview, + PremiereDate = seasonResult.AirDate, + ProductionYear = seasonResult.AirDate?.Year }; - if (Plugin.Instance.Configuration.ImportSeasonName) + if (config.ImportSeasonName) { result.Item.Name = seasonResult.Name; } @@ -77,46 +80,81 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV // TODO why was this disabled? var credits = seasonResult.Credits; + if (credits?.Cast is not null) { - var cast = credits.Cast.OrderBy(c => c.Order).Take(Plugin.Instance.Configuration.MaxCastMembers).ToList(); - for (var i = 0; i < cast.Count; i++) + var castQuery = config.HideMissingCastMembers + ? credits.Cast.Where(a => !string.IsNullOrEmpty(a.ProfilePath)).OrderBy(a => a.Order) + : credits.Cast.OrderBy(a => a.Order); + + foreach (var actor in castQuery.Take(config.MaxCastMembers)) { - result.AddPerson(new PersonInfo + if (string.IsNullOrWhiteSpace(actor.Name)) + { + continue; + } + + var personInfo = new PersonInfo { - Name = cast[i].Name.Trim(), - Role = cast[i].Character, + Name = actor.Name.Trim(), + Role = actor.Character?.Trim() ?? string.Empty, Type = PersonKind.Actor, - SortOrder = cast[i].Order - }); + SortOrder = actor.Order, + ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath) + }; + + if (actor.Id > 0) + { + personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture)); + } + + result.AddPerson(personInfo); } } if (credits?.Crew is not null) { - foreach (var person in credits.Crew) + var crewQuery = credits.Crew + .Select(crewMember => new + { + CrewMember = crewMember, + PersonType = TmdbUtils.MapCrewToPersonType(crewMember) + }) + .Where(entry => + TmdbUtils.WantedCrewKinds.Contains(entry.PersonType) || + TmdbUtils.WantedCrewTypes.Contains(entry.CrewMember.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)); + + if (config.HideMissingCrewMembers) { - // Normalize this - var type = TmdbUtils.MapCrewToPersonType(person); + crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath)); + } - if (!TmdbUtils.WantedCrewKinds.Contains(type) - && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + foreach (var entry in crewQuery.Take(config.MaxCrewMembers)) + { + var crewMember = entry.CrewMember; + + if (string.IsNullOrWhiteSpace(crewMember.Name)) { continue; } - result.AddPerson(new PersonInfo + var personInfo = new PersonInfo { - Name = person.Name.Trim(), - Role = person.Job, - Type = type - }); + Name = crewMember.Name.Trim(), + Role = crewMember.Job?.Trim() ?? string.Empty, + Type = entry.PersonType, + ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath) + }; + + if (crewMember.Id > 0) + { + personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCulture)); + } + + result.AddPerson(personInfo); } } - result.Item.PremiereDate = seasonResult.AirDate; - result.Item.ProductionYear = seasonResult.AirDate?.Year; - return result; } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs index 5f2d7909a..840cec984 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs @@ -20,9 +20,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV public ExternalIdMediaType? Type => ExternalIdMediaType.Series; /// <inheritdoc /> - public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "tv/{0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) { return item is Series; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index e4062740f..8791712c7 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -323,17 +323,31 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV private IEnumerable<PersonInfo> GetPersons(TvShow seriesResult) { + var config = Plugin.Instance.Configuration; + if (seriesResult.Credits?.Cast is not null) { - foreach (var actor in seriesResult.Credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) + IEnumerable<Cast> castQuery = seriesResult.Credits.Cast.OrderBy(a => a.Order); + + if (config.HideMissingCastMembers) + { + castQuery = castQuery.Where(a => !string.IsNullOrEmpty(a.ProfilePath)); + } + + foreach (var actor in castQuery.Take(config.MaxCastMembers)) { + if (string.IsNullOrWhiteSpace(actor.Name)) + { + continue; + } + var personInfo = new PersonInfo { Name = actor.Name.Trim(), - Role = actor.Character, + Role = actor.Character?.Trim() ?? string.Empty, Type = PersonKind.Actor, SortOrder = actor.Order, - ImageUrl = _tmdbClientManager.GetPosterUrl(actor.ProfilePath) + ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath) }; if (actor.Id > 0) @@ -347,30 +361,44 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV if (seriesResult.Credits?.Crew is not null) { - var keepTypes = new[] + var crewQuery = seriesResult.Credits.Crew + .Select(crewMember => new + { + CrewMember = crewMember, + PersonType = TmdbUtils.MapCrewToPersonType(crewMember) + }) + .Where(entry => + TmdbUtils.WantedCrewKinds.Contains(entry.PersonType) || + TmdbUtils.WantedCrewTypes.Contains(entry.CrewMember.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)); + + if (config.HideMissingCrewMembers) { - PersonType.Director, - PersonType.Writer, - PersonType.Producer - }; + crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath)); + } - foreach (var person in seriesResult.Credits.Crew) + foreach (var entry in crewQuery.Take(config.MaxCrewMembers)) { - // Normalize this - var type = TmdbUtils.MapCrewToPersonType(person); + var crewMember = entry.CrewMember; - if (!TmdbUtils.WantedCrewKinds.Contains(type) - && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrWhiteSpace(crewMember.Name)) { continue; } - yield return new PersonInfo + var personInfo = new PersonInfo { - Name = person.Name.Trim(), - Role = person.Job, - Type = type + Name = crewMember.Name.Trim(), + Role = crewMember.Job?.Trim() ?? string.Empty, + Type = entry.PersonType, + ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath) }; + + if (crewMember.Id > 0) + { + personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCulture)); + } + + yield return personInfo; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index 4916a95d9..767004c9e 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -374,7 +374,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <returns>The TMDb tv show information.</returns> public async Task<IReadOnlyList<SearchTv>> SearchSeriesAsync(string name, string language, int year = 0, CancellationToken cancellationToken = default) { - var key = $"searchseries-{name}-{language}"; + var key = $"searchseries-{name}-{year.ToString(CultureInfo.InvariantCulture)}-{language}"; if (_memoryCache.TryGetValue(key, out SearchContainer<SearchTv>? series) && series is not null) { return series.Results; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs new file mode 100644 index 000000000..8d9ec10c1 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using TMDbLib.Objects.TvShows; + +namespace MediaBrowser.Providers.Plugins.Tmdb; + +/// <summary> +/// External URLs for TMDb. +/// </summary> +public class TmdbExternalUrlProvider : IExternalUrlProvider +{ + /// <inheritdoc/> + public string Name => "TMDB"; + + /// <inheritdoc/> + public IEnumerable<string> GetExternalUrls(BaseItem item) + { + switch (item) + { + case Series: + if (item.TryGetProviderId(MetadataProvider.Tmdb, out var externalId)) + { + yield return TmdbUtils.BaseTmdbUrl + $"tv/{externalId}"; + } + + break; + case Season season: + if (season.Series?.TryGetProviderId(MetadataProvider.Tmdb, out var seriesExternalId) == true) + { + var orderString = season.Series.DisplayOrder; + var seasonNumber = season.IndexNumber; + if (string.IsNullOrEmpty(orderString) && seasonNumber is not null) + { + // Default order is airdate + yield return TmdbUtils.BaseTmdbUrl + $"tv/{seriesExternalId}/season/{seasonNumber}"; + } + + if (Enum.TryParse<TvGroupType>(season.Series.DisplayOrder, out var order)) + { + if (order.Equals(TvGroupType.OriginalAirDate) && seasonNumber is not null) + { + yield return TmdbUtils.BaseTmdbUrl + $"tv/{seriesExternalId}/season/{seasonNumber}"; + } + } + } + + break; + case Episode episode: + if (episode.Series?.TryGetProviderId(MetadataProvider.Tmdb, out seriesExternalId) == true) + { + var orderString = episode.Series.DisplayOrder; + var seasonNumber = episode.Season?.IndexNumber; + var episodeNumber = episode.IndexNumber; + if (string.IsNullOrEmpty(orderString) && seasonNumber is not null && episodeNumber is not null) + { + // Default order is airdate + yield return TmdbUtils.BaseTmdbUrl + $"tv/{seriesExternalId}/season/{seasonNumber}/episode/{episodeNumber}"; + } + + if (Enum.TryParse<TvGroupType>(orderString, out var order)) + { + if (order.Equals(TvGroupType.OriginalAirDate) && seasonNumber is not null && episodeNumber is not null) + { + yield return TmdbUtils.BaseTmdbUrl + $"tv/{seriesExternalId}/season/{seasonNumber}/episode/{episodeNumber}"; + } + } + } + + break; + case Movie: + if (item.TryGetProviderId(MetadataProvider.Tmdb, out externalId)) + { + yield return TmdbUtils.BaseTmdbUrl + $"movie/{externalId}"; + } + + break; + case Person: + if (item.TryGetProviderId(MetadataProvider.Tmdb, out externalId)) + { + yield return TmdbUtils.BaseTmdbUrl + $"person/{externalId}"; + } + + break; + case BoxSet: + if (item.TryGetProviderId(MetadataProvider.Tmdb, out externalId)) + { + yield return TmdbUtils.BaseTmdbUrl + $"collection/{externalId}"; + } + + break; + } + } +} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index a7c93ac4c..afbada3b3 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb PersonKind.Producer }; - [GeneratedRegex(@"[\W_]+")] + [GeneratedRegex(@"[\W_-[·]]+")] private static partial Regex NonWordRegex(); /// <summary> diff --git a/MediaBrowser.Providers/Studios/StudioMetadataService.cs b/MediaBrowser.Providers/Studios/StudioMetadataService.cs index df938325f..fb8cd36c4 100644 --- a/MediaBrowser.Providers/Studios/StudioMetadataService.cs +++ b/MediaBrowser.Providers/Studios/StudioMetadataService.cs @@ -1,25 +1,39 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Studios +namespace MediaBrowser.Providers.Studios; + +/// <summary> +/// Service to manage studio metadata. +/// </summary> +public class StudioMetadataService : MetadataService<Studio, ItemLookupInfo> { - public class StudioMetadataService : MetadataService<Studio, ItemLookupInfo> + /// <summary> + /// Initializes a new instance of the <see cref="StudioMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public StudioMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<StudioMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public StudioMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<StudioMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } } } diff --git a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs index 9b4793ee6..31f068711 100644 --- a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs +++ b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs @@ -1,113 +1,113 @@ using System; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV; + +/// <summary> +/// Service to manage episode metadata. +/// </summary> +public class EpisodeMetadataService : MetadataService<Episode, EpisodeInfo> { /// <summary> - /// Service to manage episode metadata. + /// Initializes a new instance of the <see cref="EpisodeMetadataService"/> class. /// </summary> - public class EpisodeMetadataService : MetadataService<Episode, EpisodeInfo> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public EpisodeMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<EpisodeMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) + { + } + + /// <inheritdoc /> + protected override ItemUpdateType BeforeSaveInternal(Episode item, bool isFullRefresh, ItemUpdateType updateType) { - /// <summary> - /// Initializes a new instance of the <see cref="EpisodeMetadataService"/> class. - /// </summary> - /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> - /// <param name="logger">Instance of the <see cref="ILogger{SeasonMetadataService}"/> interface.</param> - /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> - /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> - /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> - public EpisodeMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<EpisodeMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) + var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType); + + var seriesName = item.FindSeriesName(); + if (!string.Equals(item.SeriesName, seriesName, StringComparison.Ordinal)) { + item.SeriesName = seriesName; + updatedType |= ItemUpdateType.MetadataImport; } - /// <inheritdoc /> - protected override ItemUpdateType BeforeSaveInternal(Episode item, bool isFullRefresh, ItemUpdateType updateType) + var seasonName = item.FindSeasonName(); + if (!string.Equals(item.SeasonName, seasonName, StringComparison.Ordinal)) { - var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType); - - var seriesName = item.FindSeriesName(); - if (!string.Equals(item.SeriesName, seriesName, StringComparison.Ordinal)) - { - item.SeriesName = seriesName; - updatedType |= ItemUpdateType.MetadataImport; - } - - var seasonName = item.FindSeasonName(); - if (!string.Equals(item.SeasonName, seasonName, StringComparison.Ordinal)) - { - item.SeasonName = seasonName; - updatedType |= ItemUpdateType.MetadataImport; - } - - var seriesId = item.FindSeriesId(); - if (!item.SeriesId.Equals(seriesId)) - { - item.SeriesId = seriesId; - updatedType |= ItemUpdateType.MetadataImport; - } - - var seasonId = item.FindSeasonId(); - if (!item.SeasonId.Equals(seasonId)) - { - item.SeasonId = seasonId; - updatedType |= ItemUpdateType.MetadataImport; - } + item.SeasonName = seasonName; + updatedType |= ItemUpdateType.MetadataImport; + } - var seriesPresentationUniqueKey = item.FindSeriesPresentationUniqueKey(); - if (!string.Equals(item.SeriesPresentationUniqueKey, seriesPresentationUniqueKey, StringComparison.Ordinal)) - { - item.SeriesPresentationUniqueKey = seriesPresentationUniqueKey; - updatedType |= ItemUpdateType.MetadataImport; - } + var seriesId = item.FindSeriesId(); + if (!item.SeriesId.Equals(seriesId)) + { + item.SeriesId = seriesId; + updatedType |= ItemUpdateType.MetadataImport; + } - return updatedType; + var seasonId = item.FindSeasonId(); + if (!item.SeasonId.Equals(seasonId)) + { + item.SeasonId = seasonId; + updatedType |= ItemUpdateType.MetadataImport; } - /// <inheritdoc /> - protected override void MergeData(MetadataResult<Episode> source, MetadataResult<Episode> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) + var seriesPresentationUniqueKey = item.FindSeriesPresentationUniqueKey(); + if (!string.Equals(item.SeriesPresentationUniqueKey, seriesPresentationUniqueKey, StringComparison.Ordinal)) { - base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + item.SeriesPresentationUniqueKey = seriesPresentationUniqueKey; + updatedType |= ItemUpdateType.MetadataImport; + } + + return updatedType; + } - var sourceItem = source.Item; - var targetItem = target.Item; + /// <inheritdoc /> + protected override void MergeData(MetadataResult<Episode> source, MetadataResult<Episode> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) + { + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); - if (replaceData || !targetItem.AirsBeforeSeasonNumber.HasValue) - { - targetItem.AirsBeforeSeasonNumber = sourceItem.AirsBeforeSeasonNumber; - } + var sourceItem = source.Item; + var targetItem = target.Item; - if (replaceData || !targetItem.AirsAfterSeasonNumber.HasValue) - { - targetItem.AirsAfterSeasonNumber = sourceItem.AirsAfterSeasonNumber; - } + if (replaceData || !targetItem.AirsBeforeSeasonNumber.HasValue) + { + targetItem.AirsBeforeSeasonNumber = sourceItem.AirsBeforeSeasonNumber; + } - if (replaceData || !targetItem.AirsBeforeEpisodeNumber.HasValue) - { - targetItem.AirsBeforeEpisodeNumber = sourceItem.AirsBeforeEpisodeNumber; - } + if (replaceData || !targetItem.AirsAfterSeasonNumber.HasValue) + { + targetItem.AirsAfterSeasonNumber = sourceItem.AirsAfterSeasonNumber; + } - if (replaceData || !targetItem.IndexNumberEnd.HasValue) - { - targetItem.IndexNumberEnd = sourceItem.IndexNumberEnd; - } + if (replaceData || !targetItem.AirsBeforeEpisodeNumber.HasValue) + { + targetItem.AirsBeforeEpisodeNumber = sourceItem.AirsBeforeEpisodeNumber; + } - if (replaceData || !targetItem.ParentIndexNumber.HasValue) - { - targetItem.ParentIndexNumber = sourceItem.ParentIndexNumber; - } + if (replaceData || !targetItem.IndexNumberEnd.HasValue) + { + targetItem.IndexNumberEnd = sourceItem.IndexNumberEnd; } } } diff --git a/MediaBrowser.Providers/TV/SeasonMetadataService.cs b/MediaBrowser.Providers/TV/SeasonMetadataService.cs index b27ccaa6a..886175dea 100644 --- a/MediaBrowser.Providers/TV/SeasonMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeasonMetadataService.cs @@ -4,109 +4,114 @@ using System.Linq; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV; + +/// <summary> +/// Service to manage season metadata. +/// </summary> +public class SeasonMetadataService : MetadataService<Season, SeasonInfo> { /// <summary> - /// Service to manage season metadata. + /// Initializes a new instance of the <see cref="SeasonMetadataService"/> class. /// </summary> - public class SeasonMetadataService : MetadataService<Season, SeasonInfo> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public SeasonMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<SeasonMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - /// <summary> - /// Initializes a new instance of the <see cref="SeasonMetadataService"/> class. - /// </summary> - /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> - /// <param name="logger">Instance of the <see cref="ILogger{SeasonMetadataService}"/> interface.</param> - /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> - /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> - /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> - public SeasonMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<SeasonMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } + } - /// <inheritdoc /> - protected override bool EnableUpdatingPremiereDateFromChildren => true; + /// <inheritdoc /> + protected override bool EnableUpdatingPremiereDateFromChildren => true; - /// <inheritdoc /> - protected override ItemUpdateType BeforeSaveInternal(Season item, bool isFullRefresh, ItemUpdateType updateType) - { - var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType); - - if (item.IndexNumber == 0 && !item.IsLocked && !item.LockedFields.Contains(MetadataField.Name)) - { - var seasonZeroDisplayName = LibraryManager.GetLibraryOptions(item).SeasonZeroDisplayName; + /// <inheritdoc /> + protected override ItemUpdateType BeforeSaveInternal(Season item, bool isFullRefresh, ItemUpdateType updateType) + { + var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType); - if (!string.Equals(item.Name, seasonZeroDisplayName, StringComparison.OrdinalIgnoreCase)) - { - item.Name = seasonZeroDisplayName; - updatedType |= ItemUpdateType.MetadataEdit; - } - } + if (item.IndexNumber == 0 && !item.IsLocked && !item.LockedFields.Contains(MetadataField.Name)) + { + var seasonZeroDisplayName = LibraryManager.GetLibraryOptions(item).SeasonZeroDisplayName; - var seriesName = item.FindSeriesName(); - if (!string.Equals(item.SeriesName, seriesName, StringComparison.Ordinal)) + if (!string.Equals(item.Name, seasonZeroDisplayName, StringComparison.OrdinalIgnoreCase)) { - item.SeriesName = seriesName; - updatedType |= ItemUpdateType.MetadataImport; + item.Name = seasonZeroDisplayName; + updatedType |= ItemUpdateType.MetadataEdit; } + } - var seriesPresentationUniqueKey = item.FindSeriesPresentationUniqueKey(); - if (!string.Equals(item.SeriesPresentationUniqueKey, seriesPresentationUniqueKey, StringComparison.Ordinal)) - { - item.SeriesPresentationUniqueKey = seriesPresentationUniqueKey; - updatedType |= ItemUpdateType.MetadataImport; - } + var seriesName = item.FindSeriesName(); + if (!string.Equals(item.SeriesName, seriesName, StringComparison.Ordinal)) + { + item.SeriesName = seriesName; + updatedType |= ItemUpdateType.MetadataImport; + } - var seriesId = item.FindSeriesId(); - if (!item.SeriesId.Equals(seriesId)) - { - item.SeriesId = seriesId; - updatedType |= ItemUpdateType.MetadataImport; - } + var seriesPresentationUniqueKey = item.FindSeriesPresentationUniqueKey(); + if (!string.Equals(item.SeriesPresentationUniqueKey, seriesPresentationUniqueKey, StringComparison.Ordinal)) + { + item.SeriesPresentationUniqueKey = seriesPresentationUniqueKey; + updatedType |= ItemUpdateType.MetadataImport; + } - return updatedType; + var seriesId = item.FindSeriesId(); + if (!item.SeriesId.Equals(seriesId)) + { + item.SeriesId = seriesId; + updatedType |= ItemUpdateType.MetadataImport; } - /// <inheritdoc /> - protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(Season item) - => item.GetEpisodes(); + return updatedType; + } - /// <inheritdoc /> - protected override ItemUpdateType UpdateMetadataFromChildren(Season item, IReadOnlyList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType) - { - var updateType = base.UpdateMetadataFromChildren(item, children, isFullRefresh, currentUpdateType); + /// <inheritdoc /> + protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(Season item) + => item.GetEpisodes(); - if (isFullRefresh || currentUpdateType > ItemUpdateType.None) - { - updateType |= SaveIsVirtualItem(item, children); - } + /// <inheritdoc /> + protected override ItemUpdateType UpdateMetadataFromChildren(Season item, IReadOnlyList<BaseItem> children, bool isFullRefresh, ItemUpdateType currentUpdateType) + { + var updateType = base.UpdateMetadataFromChildren(item, children, isFullRefresh, currentUpdateType); - return updateType; + if (isFullRefresh || currentUpdateType > ItemUpdateType.None) + { + updateType |= SaveIsVirtualItem(item, children); } - private ItemUpdateType SaveIsVirtualItem(Season item, IReadOnlyList<BaseItem> episodes) - { - var isVirtualItem = item.LocationType == LocationType.Virtual && (episodes.Count == 0 || episodes.All(i => i.LocationType == LocationType.Virtual)); + return updateType; + } - if (item.IsVirtualItem != isVirtualItem) - { - item.IsVirtualItem = isVirtualItem; - return ItemUpdateType.MetadataEdit; - } + private ItemUpdateType SaveIsVirtualItem(Season item, IReadOnlyList<BaseItem> episodes) + { + var isVirtualItem = item.LocationType == LocationType.Virtual && (episodes.Count == 0 || episodes.All(i => i.LocationType == LocationType.Virtual)); - return ItemUpdateType.None; + if (item.IsVirtualItem != isVirtualItem) + { + item.IsVirtualItem = isVirtualItem; + return ItemUpdateType.MetadataEdit; } + + return ItemUpdateType.None; } } diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index 284415dce..c3a6ddd6a 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -8,7 +8,9 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; @@ -16,269 +18,272 @@ using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV; + +/// <summary> +/// Service to manage series metadata. +/// </summary> +public class SeriesMetadataService : MetadataService<Series, SeriesInfo> { + private readonly ILocalizationManager _localizationManager; + /// <summary> - /// Service to manage series metadata. + /// Initializes a new instance of the <see cref="SeriesMetadataService"/> class. /// </summary> - public class SeriesMetadataService : MetadataService<Series, SeriesInfo> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public SeriesMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<SeriesMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + ILocalizationManager localizationManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - private readonly ILocalizationManager _localizationManager; + _localizationManager = localizationManager; + } - /// <summary> - /// Initializes a new instance of the <see cref="SeriesMetadataService"/> class. - /// </summary> - /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> - /// <param name="logger">Instance of the <see cref="ILogger{SeasonMetadataService}"/> interface.</param> - /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> - /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> - /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> - /// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param> - public SeriesMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<SeriesMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager, - ILocalizationManager localizationManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) + /// <inheritdoc /> + public override async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) + { + if (item is Series series) { - _localizationManager = localizationManager; - } + var seasons = series.GetRecursiveChildren(i => i is Season).ToList(); - /// <inheritdoc /> - public override async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) - { - if (item is Series series) + foreach (var season in seasons) { - var seasons = series.GetRecursiveChildren(i => i is Season).ToList(); - - foreach (var season in seasons) + var hasUpdate = refreshOptions is not null && season.BeforeMetadataRefresh(refreshOptions.ReplaceAllMetadata); + if (hasUpdate) { - var hasUpdate = refreshOptions != null && season.BeforeMetadataRefresh(refreshOptions.ReplaceAllMetadata); - if (hasUpdate) - { - await season.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false); - } + await season.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false); } } - - return await base.RefreshMetadata(item, refreshOptions, cancellationToken).ConfigureAwait(false); } - /// <inheritdoc /> - protected override async Task AfterMetadataRefresh(Series item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) - { - await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false); + return await base.RefreshMetadata(item, refreshOptions, cancellationToken).ConfigureAwait(false); + } - RemoveObsoleteEpisodes(item); - RemoveObsoleteSeasons(item); - await CreateSeasonsAsync(item, cancellationToken).ConfigureAwait(false); + /// <inheritdoc /> + protected override async Task AfterMetadataRefresh(Series item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) + { + await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false); + + RemoveObsoleteEpisodes(item); + RemoveObsoleteSeasons(item); + await CreateSeasonsAsync(item, cancellationToken).ConfigureAwait(false); + } + + /// <inheritdoc /> + protected override void MergeData(MetadataResult<Series> source, MetadataResult<Series> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) + { + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + + var sourceItem = source.Item; + var targetItem = target.Item; + + if (replaceData || string.IsNullOrEmpty(targetItem.AirTime)) + { + targetItem.AirTime = sourceItem.AirTime; } - /// <inheritdoc /> - protected override void MergeData(MetadataResult<Series> source, MetadataResult<Series> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) + if (replaceData || !targetItem.Status.HasValue) { - base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + targetItem.Status = sourceItem.Status; + } - var sourceItem = source.Item; - var targetItem = target.Item; + if (replaceData || targetItem.AirDays is null || targetItem.AirDays.Length == 0) + { + targetItem.AirDays = sourceItem.AirDays; + } + } - if (replaceData || string.IsNullOrEmpty(targetItem.AirTime)) + private void RemoveObsoleteSeasons(Series series) + { + // TODO Legacy. It's not really "physical" seasons as any virtual seasons are always converted to non-virtual in CreateSeasonsAsync. + var physicalSeasonNumbers = new HashSet<int>(); + var virtualSeasons = new List<Season>(); + foreach (var existingSeason in series.Children.OfType<Season>()) + { + if (existingSeason.LocationType != LocationType.Virtual && existingSeason.IndexNumber.HasValue) { - targetItem.AirTime = sourceItem.AirTime; + physicalSeasonNumbers.Add(existingSeason.IndexNumber.Value); } - - if (replaceData || !targetItem.Status.HasValue) + else if (existingSeason.LocationType == LocationType.Virtual) { - targetItem.Status = sourceItem.Status; + virtualSeasons.Add(existingSeason); } + } - if (replaceData || targetItem.AirDays is null || targetItem.AirDays.Length == 0) + foreach (var virtualSeason in virtualSeasons) + { + var seasonNumber = virtualSeason.IndexNumber; + // If there's a physical season with the same number or no episodes in the season, delete it + if ((seasonNumber.HasValue && physicalSeasonNumbers.Contains(seasonNumber.Value)) + || virtualSeason.GetEpisodes().Count == 0) { - targetItem.AirDays = sourceItem.AirDays; + Logger.LogInformation("Removing virtual season {SeasonNumber} in series {SeriesName}", virtualSeason.IndexNumber, series.Name); + + LibraryManager.DeleteItem( + virtualSeason, + new DeleteOptions + { + // Internal metadata paths are removed regardless of this. + DeleteFileLocation = false + }, + false); } } + } - private void RemoveObsoleteSeasons(Series series) + private void RemoveObsoleteEpisodes(Series series) + { + var episodesBySeason = series.GetEpisodes(null, new DtoOptions(), true) + .OfType<Episode>() + .GroupBy(e => e.ParentIndexNumber) + .ToList(); + + foreach (var seasonEpisodes in episodesBySeason) { - // TODO Legacy. It's not really "physical" seasons as any virtual seasons are always converted to non-virtual in CreateSeasonsAsync. - var physicalSeasonNumbers = new HashSet<int>(); - var virtualSeasons = new List<Season>(); - foreach (var existingSeason in series.Children.OfType<Season>()) + List<Episode> nonPhysicalEpisodes = []; + List<Episode> physicalEpisodes = []; + foreach (var episode in seasonEpisodes) { - if (existingSeason.LocationType != LocationType.Virtual && existingSeason.IndexNumber.HasValue) + if (episode.IsVirtualItem || episode.IsMissingEpisode) { - physicalSeasonNumbers.Add(existingSeason.IndexNumber.Value); - } - else if (existingSeason.LocationType == LocationType.Virtual) - { - virtualSeasons.Add(existingSeason); + nonPhysicalEpisodes.Add(episode); + continue; } + + physicalEpisodes.Add(episode); } - foreach (var virtualSeason in virtualSeasons) + // Only consider non-physical episodes + foreach (var episode in nonPhysicalEpisodes) { - var seasonNumber = virtualSeason.IndexNumber; - // If there's a physical season with the same number or no episodes in the season, delete it - if ((seasonNumber.HasValue && physicalSeasonNumbers.Contains(seasonNumber.Value)) - || virtualSeason.GetEpisodes().Count == 0) - { - Logger.LogInformation("Removing virtual season {SeasonNumber} in series {SeriesName}", virtualSeason.IndexNumber, series.Name); + // Episodes without an episode number are practically orphaned and should be deleted + // Episodes with a physical equivalent should be deleted (they are no longer missing) + var shouldKeep = episode.IndexNumber.HasValue && !physicalEpisodes.Any(e => e.ContainsEpisodeNumber(episode.IndexNumber.Value)); - LibraryManager.DeleteItem( - virtualSeason, - new DeleteOptions - { - // Internal metadata paths are removed regardless of this. - DeleteFileLocation = false - }, - false); + if (shouldKeep) + { + continue; } + + DeleteEpisode(episode); } } + } - private void RemoveObsoleteEpisodes(Series series) - { - var episodesBySeason = series.GetEpisodes(null, new DtoOptions(), true) - .OfType<Episode>() - .GroupBy(e => e.ParentIndexNumber) - .ToList(); + private void DeleteEpisode(Episode episode) + { + Logger.LogInformation( + "Removing virtual episode S{SeasonNumber}E{EpisodeNumber} in series {SeriesName}", + episode.ParentIndexNumber, + episode.IndexNumber, + episode.SeriesName); - foreach (var seasonEpisodes in episodesBySeason) + LibraryManager.DeleteItem( + episode, + new DeleteOptions { - List<Episode> nonPhysicalEpisodes = []; - List<Episode> physicalEpisodes = []; - foreach (var episode in seasonEpisodes) - { - if (episode.IsVirtualItem || episode.IsMissingEpisode) - { - nonPhysicalEpisodes.Add(episode); - continue; - } + // Internal metadata paths are removed regardless of this. + DeleteFileLocation = false + }, + false); + } - physicalEpisodes.Add(episode); - } + /// <summary> + /// Creates seasons for all episodes if they don't exist. + /// If no season number can be determined, a dummy season will be created. + /// </summary> + /// <param name="series">The series.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>The async task.</returns> + private async Task CreateSeasonsAsync(Series series, CancellationToken cancellationToken) + { + var seriesChildren = series.GetRecursiveChildren(i => i is Episode || i is Season); + var seasons = seriesChildren.OfType<Season>().ToList(); + var uniqueSeasonNumbers = seriesChildren + .OfType<Episode>() + .Select(e => e.ParentIndexNumber >= 0 ? e.ParentIndexNumber : null) + .Distinct(); - // Only consider non-physical episodes - foreach (var episode in nonPhysicalEpisodes) + // Loop through the unique season numbers + foreach (var seasonNumber in uniqueSeasonNumbers) + { + // Null season numbers will have a 'dummy' season created because seasons are always required. + var existingSeason = seasons.FirstOrDefault(i => i.IndexNumber == seasonNumber); + if (existingSeason is null) + { + var seasonName = GetValidSeasonNameForSeries(series, null, seasonNumber); + await CreateSeasonAsync(series, seasonName, seasonNumber, cancellationToken).ConfigureAwait(false); + } + else if (existingSeason.IsVirtualItem) + { + var episodeCount = seriesChildren.OfType<Episode>().Count(e => e.ParentIndexNumber == seasonNumber && !e.IsMissingEpisode); + if (episodeCount > 0) { - // Episodes without an episode number are practically orphaned and should be deleted - // Episodes with a physical equivalent should be deleted (they are no longer missing) - var shouldKeep = episode.IndexNumber.HasValue && !physicalEpisodes.Any(e => e.ContainsEpisodeNumber(episode.IndexNumber.Value)); - - if (shouldKeep) - { - continue; - } - - DeleteEpisode(episode); + existingSeason.IsVirtualItem = false; + await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false); } } } + } - private void DeleteEpisode(Episode episode) - { - Logger.LogInformation( - "Removing virtual episode S{SeasonNumber}E{EpisodeNumber} in series {SeriesName}", - episode.ParentIndexNumber, - episode.IndexNumber, - episode.SeriesName); - - LibraryManager.DeleteItem( - episode, - new DeleteOptions - { - // Internal metadata paths are removed regardless of this. - DeleteFileLocation = false - }, - false); - } + /// <summary> + /// Creates a new season, adds it to the database by linking it to the [series] and refreshes the metadata. + /// </summary> + /// <param name="series">The series.</param> + /// <param name="seasonName">The season name.</param> + /// <param name="seasonNumber">The season number.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>The newly created season.</returns> + private async Task CreateSeasonAsync( + Series series, + string? seasonName, + int? seasonNumber, + CancellationToken cancellationToken) + { + Logger.LogInformation("Creating Season {SeasonName} entry for {SeriesName}", seasonName, series.Name); - /// <summary> - /// Creates seasons for all episodes if they don't exist. - /// If no season number can be determined, a dummy season will be created. - /// </summary> - /// <param name="series">The series.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>The async task.</returns> - private async Task CreateSeasonsAsync(Series series, CancellationToken cancellationToken) + var season = new Season { - var seriesChildren = series.GetRecursiveChildren(i => i is Episode || i is Season); - var seasons = seriesChildren.OfType<Season>().ToList(); - var uniqueSeasonNumbers = seriesChildren - .OfType<Episode>() - .Select(e => e.ParentIndexNumber >= 0 ? e.ParentIndexNumber : null) - .Distinct(); + Name = seasonName, + IndexNumber = seasonNumber, + Id = LibraryManager.GetNewItemId( + series.Id + (seasonNumber ?? -1).ToString(CultureInfo.InvariantCulture) + seasonName, + typeof(Season)), + IsVirtualItem = false, + SeriesId = series.Id, + SeriesName = series.Name, + SeriesPresentationUniqueKey = series.GetPresentationUniqueKey() + }; - // Loop through the unique season numbers - foreach (var seasonNumber in uniqueSeasonNumbers) - { - // Null season numbers will have a 'dummy' season created because seasons are always required. - var existingSeason = seasons.FirstOrDefault(i => i.IndexNumber == seasonNumber); - if (existingSeason is null) - { - var seasonName = GetValidSeasonNameForSeries(series, null, seasonNumber); - await CreateSeasonAsync(series, seasonName, seasonNumber, cancellationToken).ConfigureAwait(false); - } - else if (existingSeason.IsVirtualItem) - { - var episodeCount = seriesChildren.OfType<Episode>().Count(e => e.ParentIndexNumber == seasonNumber && !e.IsMissingEpisode); - if (episodeCount > 0) - { - existingSeason.IsVirtualItem = false; - await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false); - } - } - } - } + series.AddChild(season); + await season.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellationToken).ConfigureAwait(false); + } - /// <summary> - /// Creates a new season, adds it to the database by linking it to the [series] and refreshes the metadata. - /// </summary> - /// <param name="series">The series.</param> - /// <param name="seasonName">The season name.</param> - /// <param name="seasonNumber">The season number.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>The newly created season.</returns> - private async Task CreateSeasonAsync( - Series series, - string? seasonName, - int? seasonNumber, - CancellationToken cancellationToken) + private string GetValidSeasonNameForSeries(Series series, string? seasonName, int? seasonNumber) + { + if (string.IsNullOrEmpty(seasonName)) { - Logger.LogInformation("Creating Season {SeasonName} entry for {SeriesName}", seasonName, series.Name); - - var season = new Season + seasonName = seasonNumber switch { - Name = seasonName, - IndexNumber = seasonNumber, - Id = LibraryManager.GetNewItemId( - series.Id + (seasonNumber ?? -1).ToString(CultureInfo.InvariantCulture) + seasonName, - typeof(Season)), - IsVirtualItem = false, - SeriesId = series.Id, - SeriesName = series.Name, - SeriesPresentationUniqueKey = series.GetPresentationUniqueKey() + null => _localizationManager.GetLocalizedString("NameSeasonUnknown"), + 0 => LibraryManager.GetLibraryOptions(series).SeasonZeroDisplayName, + _ => string.Format(CultureInfo.InvariantCulture, _localizationManager.GetLocalizedString("NameSeasonNumber"), seasonNumber.Value) }; - - series.AddChild(season); - await season.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellationToken).ConfigureAwait(false); } - private string GetValidSeasonNameForSeries(Series series, string? seasonName, int? seasonNumber) - { - if (string.IsNullOrEmpty(seasonName)) - { - seasonName = seasonNumber switch - { - null => _localizationManager.GetLocalizedString("NameSeasonUnknown"), - 0 => LibraryManager.GetLibraryOptions(series).SeasonZeroDisplayName, - _ => string.Format(CultureInfo.InvariantCulture, _localizationManager.GetLocalizedString("NameSeasonNumber"), seasonNumber.Value) - }; - } - - return seasonName; - } + return seasonName; } } diff --git a/MediaBrowser.Providers/TV/Zap2ItExternalId.cs b/MediaBrowser.Providers/TV/Zap2ItExternalId.cs index 3cb18e424..8907d7744 100644 --- a/MediaBrowser.Providers/TV/Zap2ItExternalId.cs +++ b/MediaBrowser.Providers/TV/Zap2ItExternalId.cs @@ -19,9 +19,6 @@ namespace MediaBrowser.Providers.TV public ExternalIdMediaType? Type => null; /// <inheritdoc /> - public string UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}"; - - /// <inheritdoc /> public bool Supports(IHasProviderIds item) => item is Series; } } diff --git a/MediaBrowser.Providers/TV/Zap2ItExternalUrlProvider.cs b/MediaBrowser.Providers/TV/Zap2ItExternalUrlProvider.cs new file mode 100644 index 000000000..52b0583e5 --- /dev/null +++ b/MediaBrowser.Providers/TV/Zap2ItExternalUrlProvider.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.TV; + +/// <summary> +/// External URLs for TMDb. +/// </summary> +public class Zap2ItExternalUrlProvider : IExternalUrlProvider +{ + /// <inheritdoc/> + public string Name => "Zap2It"; + + /// <inheritdoc/> + public IEnumerable<string> GetExternalUrls(BaseItem item) + { + if (item.TryGetProviderId(MetadataProvider.Zap2It, out var externalId)) + { + yield return $"http://tvlistings.zap2it.com/overview.html?programSeriesId={externalId}"; + } + } +} diff --git a/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs b/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs index 4310f93d4..81dcbf893 100644 --- a/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs +++ b/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs @@ -59,14 +59,14 @@ public class TrickplayImagesTask : IScheduledTask /// <inheritdoc /> public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() { - return new[] - { + return + [ new TaskTriggerInfo { Type = TaskTriggerInfoType.DailyTrigger, TimeOfDayTicks = TimeSpan.FromHours(3).Ticks } - }; + ]; } /// <inheritdoc /> @@ -74,8 +74,8 @@ public class TrickplayImagesTask : IScheduledTask { var query = new InternalItemsQuery { - MediaTypes = new[] { MediaType.Video }, - SourceTypes = new[] { SourceType.Library }, + MediaTypes = [MediaType.Video], + SourceTypes = [SourceType.Library], IsVirtualItem = false, IsFolder = false, Recursive = true, diff --git a/MediaBrowser.Providers/Trickplay/TrickplayProvider.cs b/MediaBrowser.Providers/Trickplay/TrickplayProvider.cs index 2c74e5f70..926a962e2 100644 --- a/MediaBrowser.Providers/Trickplay/TrickplayProvider.cs +++ b/MediaBrowser.Providers/Trickplay/TrickplayProvider.cs @@ -56,7 +56,7 @@ public class TrickplayProvider : ICustomMetadataProvider<Episode>, if (item.IsFileProtocol) { var file = directoryService.GetFile(item.Path); - if (file is not null && item.DateModified != file.LastWriteTimeUtc) + if (file is not null && item.HasChanged(file.LastWriteTimeUtc)) { return true; } @@ -101,7 +101,7 @@ public class TrickplayProvider : ICustomMetadataProvider<Episode>, bool? enableDuringScan = libraryOptions?.ExtractTrickplayImagesDuringLibraryScan; bool replace = options.RegenerateTrickplay && options.MetadataRefreshMode > MetadataRefreshMode.Default; - if (!enableDuringScan.GetValueOrDefault(false)) + if (libraryOptions is null || !enableDuringScan.GetValueOrDefault(false)) { return ItemUpdateType.None; } diff --git a/MediaBrowser.Providers/Videos/VideoMetadataService.cs b/MediaBrowser.Providers/Videos/VideoMetadataService.cs index caa6d6e1f..464b337ff 100644 --- a/MediaBrowser.Providers/Videos/VideoMetadataService.cs +++ b/MediaBrowser.Providers/Videos/VideoMetadataService.cs @@ -1,29 +1,43 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Videos +namespace MediaBrowser.Providers.Videos; + +/// <summary> +/// Service to manage video metadata. +/// </summary> +public class VideoMetadataService : MetadataService<Video, ItemLookupInfo> { - public class VideoMetadataService : MetadataService<Video, ItemLookupInfo> + /// <summary> + /// Initializes a new instance of the <see cref="VideoMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public VideoMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<VideoMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public VideoMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<VideoMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } - - /// <inheritdoc /> - // Make sure the type-specific services get picked first - public override int Order => 10; } + + /// <inheritdoc /> + // Make sure the type-specific services get picked first + public override int Order => 10; } diff --git a/MediaBrowser.Providers/Years/YearMetadataService.cs b/MediaBrowser.Providers/Years/YearMetadataService.cs index 689e8661b..cc403e7c9 100644 --- a/MediaBrowser.Providers/Years/YearMetadataService.cs +++ b/MediaBrowser.Providers/Years/YearMetadataService.cs @@ -1,25 +1,39 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; using MediaBrowser.Providers.Manager; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Years +namespace MediaBrowser.Providers.Years; + +/// <summary> +/// Service to manage year metadata. +/// </summary> +public class YearMetadataService : MetadataService<Year, ItemLookupInfo> { - public class YearMetadataService : MetadataService<Year, ItemLookupInfo> + /// <summary> + /// Initializes a new instance of the <see cref="YearMetadataService"/> class. + /// </summary> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/>.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="externalDataManager">Instance of the <see cref="IExternalDataManager"/> interface.</param> + /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param> + public YearMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<YearMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IExternalDataManager externalDataManager, + IItemRepository itemRepository) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, externalDataManager, itemRepository) { - public YearMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<YearMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } } } |
