aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Providers
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Providers')
-rw-r--r--MediaBrowser.Providers/Books/AudioBookMetadataService.cs78
-rw-r--r--MediaBrowser.Providers/Books/BookMetadataService.cs56
-rw-r--r--MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs120
-rw-r--r--MediaBrowser.Providers/Channels/ChannelMetadataService.cs40
-rw-r--r--MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs40
-rw-r--r--MediaBrowser.Providers/Folders/FolderMetadataService.cs48
-rw-r--r--MediaBrowser.Providers/Folders/UserViewMetadataService.cs40
-rw-r--r--MediaBrowser.Providers/Genres/GenreMetadataService.cs40
-rw-r--r--MediaBrowser.Providers/LiveTv/LiveTvMetadataService.cs40
-rw-r--r--MediaBrowser.Providers/Lyric/LrcLyricParser.cs62
-rw-r--r--MediaBrowser.Providers/Manager/ImageSaver.cs16
-rw-r--r--MediaBrowser.Providers/Manager/ItemImageProvider.cs4
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs182
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs14
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioFileProber.cs174
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs66
-rw-r--r--MediaBrowser.Providers/MediaInfo/ProbeProvider.cs17
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs1
-rw-r--r--MediaBrowser.Providers/Movies/MovieMetadataService.cs60
-rw-r--r--MediaBrowser.Providers/Movies/TrailerMetadataService.cs62
-rw-r--r--MediaBrowser.Providers/Music/AlbumMetadataService.cs349
-rw-r--r--MediaBrowser.Providers/Music/ArtistMetadataService.cs67
-rw-r--r--MediaBrowser.Providers/Music/AudioMetadataService.cs109
-rw-r--r--MediaBrowser.Providers/Music/MusicVideoMetadataService.cs87
-rw-r--r--MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs40
-rw-r--r--MediaBrowser.Providers/People/PersonMetadataService.cs40
-rw-r--r--MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs40
-rw-r--r--MediaBrowser.Providers/Photos/PhotoMetadataService.cs40
-rw-r--r--MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs4
-rw-r--r--MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs108
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs9
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalUrlProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalUrlProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs9
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs15
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html34
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs92
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs105
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs85
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs78
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs64
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs23
-rw-r--r--MediaBrowser.Providers/Studios/StudioMetadataService.cs40
-rw-r--r--MediaBrowser.Providers/TV/EpisodeMetadataService.cs162
-rw-r--r--MediaBrowser.Providers/TV/SeasonMetadataService.cs153
-rw-r--r--MediaBrowser.Providers/TV/SeriesMetadataService.cs417
-rw-r--r--MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs10
-rw-r--r--MediaBrowser.Providers/Trickplay/TrickplayProvider.cs4
-rw-r--r--MediaBrowser.Providers/Videos/VideoMetadataService.cs48
-rw-r--r--MediaBrowser.Providers/Years/YearMetadataService.cs40
59 files changed, 2112 insertions, 1350 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..eccf8a606 100644
--- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
+++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
@@ -1,85 +1,93 @@
-#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 EnableUpdatingOfficialRatingFromChildren => true;
+ /// <inheritdoc />
+ protected override bool EnableUpdatingGenresFromChildren => true;
- /// <inheritdoc />
- protected override bool EnableUpdatingStudiosFromChildren => true;
+ /// <inheritdoc />
+ protected override bool EnableUpdatingOfficialRatingFromChildren => true;
- /// <inheritdoc />
- protected override bool EnableUpdatingPremiereDateFromChildren => true;
+ /// <inheritdoc />
+ protected override bool EnableUpdatingStudiosFromChildren => true;
- /// <inheritdoc />
- protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(BoxSet item)
- {
- return item.GetLinkedChildren();
- }
+ /// <inheritdoc />
+ protected override bool EnableUpdatingPremiereDateFromChildren => true;
- /// <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 IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(BoxSet item)
+ {
+ return item.GetLinkedChildren();
+ }
- var sourceItem = source.Item;
- var targetItem = target.Item;
+ /// <inheritdoc />
+ protected override void MergeData(MetadataResult<BoxSet> source, MetadataResult<BoxSet> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
+ {
+ base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings);
- if (mergeMetadataSettings)
- {
- if (replaceData || targetItem.LinkedChildren.Length == 0)
- {
- targetItem.LinkedChildren = sourceItem.LinkedChildren;
- }
- else
- {
- targetItem.LinkedChildren = sourceItem.LinkedChildren.Concat(targetItem.LinkedChildren).Distinct().ToArray();
- }
- }
- }
+ var sourceItem = source.Item;
+ var targetItem = target.Item;
- /// <inheritdoc />
- protected override ItemUpdateType BeforeSaveInternal(BoxSet item, bool isFullRefresh, ItemUpdateType updateType)
+ if (mergeMetadataSettings)
{
- var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType);
+ // TODO: Change to only replace when currently empty or requested. This is currently not done because the metadata service is not handling attaching collection items based on the provider responses
+ targetItem.LinkedChildren = sourceItem.LinkedChildren.Concat(targetItem.LinkedChildren).DistinctBy(i => i.Path).ToArray();
+ }
+ }
- 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 27d17b535..fa711eb28 100644
--- a/MediaBrowser.Providers/Lyric/LrcLyricParser.cs
+++ b/MediaBrowser.Providers/Lyric/LrcLyricParser.cs
@@ -2,6 +2,7 @@ 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;
@@ -66,47 +67,56 @@ public partial class LrcLyricParser : ILyricParser
}
List<LyricLine> lyricList = [];
- for (var l = 0; l < sortedLyricData.Count; l++)
+ for (var lineIndex = 0; lineIndex < sortedLyricData.Count; lineIndex++)
{
- var cues = new List<LyricLineCue>();
- var lyric = sortedLyricData[l];
+ var lyric = sortedLyricData[lineIndex];
- if (lyric.TimeTags.Count != 0)
+ // Extract cues from time tags
+ var cues = new List<LyricLineCue>();
+ if (lyric.TimeTags.Count > 0)
{
var keys = lyric.TimeTags.Keys.ToList();
- int current = 0, next = 1;
- while (next < keys.Count)
+ for (var tagIndex = 0; tagIndex < keys.Count - 1; tagIndex++)
{
- var currentKey = keys[current];
- var currentMs = lyric.TimeTags[currentKey] ?? 0;
- var nextMs = lyric.TimeTags[keys[next]] ?? 0;
-
- cues.Add(new LyricLineCue(
- position: Math.Max(currentKey.Index, 0),
- start: TimeSpan.FromMilliseconds(currentMs).Ticks,
- end: TimeSpan.FromMilliseconds(nextMs).Ticks));
+ var currentKey = keys[tagIndex];
+ var nextKey = keys[tagIndex + 1];
- current++;
- next++;
+ 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[current];
+ 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();
- cues.Add(new LyricLineCue(
- position: Math.Max(lastKey.Index, 0),
- start: TimeSpan.FromMilliseconds(lastMs).Ticks,
- end: l + 1 < sortedLyricData.Count ? TimeSpan.FromMilliseconds(sortedLyricData[l + 1].StartTime).Ticks : null));
+ 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(WhitespaceRegex().Replace(lyric.Text.Trim(), " "), lyricStartTicks, cues));
+ lyricList.Add(new LyricLine(lyric.Text, lyricStartTicks, cues));
}
return new LyricDto { Lyrics = lyricList };
}
-
- // Replacement is required until https://github.com/karaoke-dev/LrcParser/issues/83 is resolved.
- [GeneratedRegex(@"\s+")]
- private static partial Regex WhitespaceRegex();
}
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 45f66f85f..f220ec4a1 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,14 +144,17 @@ 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);
+ if (!isFirstRefresh)
+ {
+ updateType = await SaveInternal(item, refreshOptions, updateType, isFirstRefresh, requiresRefresh, metadataResult, cancellationToken).ConfigureAwait(false);
+ }
// Next run metadata providers
if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
@@ -209,10 +228,15 @@ 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;
+
+ if (!file.IsDirectory)
+ {
+ item.Size = file.Length;
+ }
}
}
@@ -253,14 +277,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)
@@ -276,12 +299,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;
}
@@ -289,12 +320,8 @@ namespace MediaBrowser.Providers.Manager
{
if (EnableUpdateMetadataFromChildren(item, isFullRefresh, updateType))
{
- if (isFullRefresh || updateType > ItemUpdateType.None)
- {
- var children = GetChildrenForMetadataUpdates(item);
-
- updateType = UpdateMetadataFromChildren(item, children, isFullRefresh, updateType);
- }
+ var children = GetChildrenForMetadataUpdates(item);
+ updateType = UpdateMetadataFromChildren(item, children, isFullRefresh, updateType);
}
var presentationUniqueKey = item.CreatePresentationUniqueKey();
@@ -304,21 +331,57 @@ 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)
+ {
+ if (info.CreationTimeUtc > DateTime.MinValue)
+ {
+ 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;
}
protected virtual bool EnableUpdateMetadataFromChildren(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType)
{
- if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
+ if (item is Folder folder)
{
- if (EnableUpdatingPremiereDateFromChildren || EnableUpdatingGenresFromChildren || EnableUpdatingStudiosFromChildren || EnableUpdatingOfficialRatingFromChildren)
+ if (!isFullRefresh && currentUpdateType == ItemUpdateType.None)
{
- return true;
+ return folder.SupportsDateLastMediaAdded;
}
- if (item is Folder folder)
+ if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
{
- return folder.SupportsDateLastMediaAdded || folder.SupportsCumulativeRunTimeTicks;
+ if (EnableUpdatingPremiereDateFromChildren || EnableUpdatingGenresFromChildren || EnableUpdatingStudiosFromChildren || EnableUpdatingOfficialRatingFromChildren)
+ {
+ return true;
+ }
+
+ if (folder.SupportsDateLastMediaAdded || folder.SupportsCumulativeRunTimeTicks)
+ {
+ return true;
+ }
}
}
@@ -339,36 +402,42 @@ namespace MediaBrowser.Providers.Manager
{
var updateType = ItemUpdateType.None;
- if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
+ if (item is Folder folder)
{
- updateType |= UpdateCumulativeRunTimeTicks(item, children);
- updateType |= UpdateDateLastMediaAdded(item, children);
-
- // don't update user-changeable metadata for locked items
- if (item.IsLocked)
+ if (folder.SupportsDateLastMediaAdded)
{
- return updateType;
+ updateType |= UpdateDateLastMediaAdded(item, children);
}
- if (EnableUpdatingPremiereDateFromChildren)
+ if ((isFullRefresh || currentUpdateType > ItemUpdateType.None) && folder.SupportsCumulativeRunTimeTicks)
{
- updateType |= UpdatePremiereDate(item, children);
+ updateType |= UpdateCumulativeRunTimeTicks(item, children);
}
+ }
- if (EnableUpdatingGenresFromChildren)
- {
- updateType |= UpdateGenres(item, children);
- }
+ if (!(isFullRefresh || currentUpdateType > ItemUpdateType.None) || item.IsLocked)
+ {
+ return updateType;
+ }
- if (EnableUpdatingStudiosFromChildren)
- {
- updateType |= UpdateStudios(item, children);
- }
+ if (EnableUpdatingPremiereDateFromChildren)
+ {
+ updateType |= UpdatePremiereDate(item, children);
+ }
- if (EnableUpdatingOfficialRatingFromChildren)
- {
- updateType |= UpdateOfficialRating(item, children);
- }
+ if (EnableUpdatingGenresFromChildren)
+ {
+ updateType |= UpdateGenres(item, children);
+ }
+
+ if (EnableUpdatingStudiosFromChildren)
+ {
+ updateType |= UpdateStudios(item, children);
+ }
+
+ if (EnableUpdatingOfficialRatingFromChildren)
+ {
+ updateType |= UpdateOfficialRating(item, children);
}
return updateType;
@@ -614,7 +683,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)
{
@@ -781,7 +850,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);
}
}
@@ -1042,7 +1113,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();
}
}
@@ -1054,7 +1125,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();
}
}
@@ -1066,7 +1137,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();
}
}
@@ -1128,11 +1199,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;
@@ -1215,7 +1291,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();
}
}
}
@@ -1224,7 +1300,7 @@ namespace MediaBrowser.Providers.Manager
{
if (source is Video sourceCast && target is Video targetCast)
{
- if (replaceData || !targetCast.Video3DFormat.HasValue)
+ if (sourceCast.Video3DFormat.HasValue && (replaceData || !targetCast.Video3DFormat.HasValue))
{
targetCast.Video3DFormat = sourceCast.Video3DFormat;
}
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 856f33b49..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>
diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
index 0bb21b287..869e3f292 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
@@ -32,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;
@@ -46,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>
@@ -55,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;
@@ -137,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);
@@ -186,10 +181,31 @@ namespace MediaBrowser.Providers.MediaInfo
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)
{
@@ -202,7 +218,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
PeopleHelper.AddPerson(people, new PersonInfo
{
- Name = albumArtist.Trim(),
+ Name = albumArtist,
Type = PersonKind.AlbumArtist
});
}
@@ -211,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);
@@ -220,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)
@@ -234,21 +250,24 @@ namespace MediaBrowser.Providers.MediaInfo
{
PeopleHelper.AddPerson(people, new PersonInfo
{
- Name = performer.Trim(),
+ Name = performer,
Type = PersonKind.Artist
});
}
}
- foreach (var composer in track.Composer.Split(InternalValueSeparator))
+ if (!string.IsNullOrWhiteSpace(trackComposer))
{
- if (!string.IsNullOrWhiteSpace(composer))
+ foreach (var composer in trackComposer.Split(InternalValueSeparator))
{
- PeopleHelper.AddPerson(people, new PersonInfo
+ if (!string.IsNullOrWhiteSpace(composer))
{
- Name = composer.Trim(),
- Type = PersonKind.Composer
- });
+ PeopleHelper.AddPerson(people, new PersonInfo
+ {
+ Name = composer,
+ Type = PersonKind.Composer
+ });
+ }
}
}
@@ -325,7 +344,7 @@ namespace MediaBrowser.Providers.MediaInfo
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)
{
@@ -334,12 +353,13 @@ namespace MediaBrowser.Providers.MediaInfo
genres = genres.Trimmed().Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
- audio.Genres = options.ReplaceAllMetadata || audio.Genres is null || audio.Genres.Length == 0
- ? genres
- : audio.Genres;
+ 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)
{
@@ -348,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;
}
@@ -356,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);
@@ -367,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);
@@ -378,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);
@@ -389,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);
@@ -400,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);
@@ -411,25 +431,29 @@ namespace MediaBrowser.Providers.MediaInfo
if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzRecording, out _))
{
- if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_TRACKID", out var recordingMbId)
- || track.AdditionalFields.TryGetValue("MusicBrainz Track Id", out recordingMbId))
+ 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 (track.AdditionalFields.TryGetValue("UFID", out var ufIdValue) && !string.IsNullOrEmpty(ufIdValue))
+ else if (TryGetSanitizedUFIDFields(track, out var owner, out var identifier) && !string.IsNullOrEmpty(owner) && !string.IsNullOrEmpty(identifier))
{
// If tagged with MB Picard, the format is 'http://musicbrainz.org\0<recording MBID>'
- if (ufIdValue.Contains("musicbrainz.org", StringComparison.OrdinalIgnoreCase))
+ if (owner.Contains("musicbrainz.org", StringComparison.OrdinalIgnoreCase))
{
- audio.TrySetProviderId(MetadataProvider.MusicBrainzRecording, ufIdValue.AsSpan().RightPart('\0').ToString());
+ audio.TrySetProviderId(MetadataProvider.MusicBrainzRecording, identifier);
}
}
}
// 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)
{
@@ -490,5 +514,79 @@ 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 = TryGetAdditionalFieldWithFallback(track, field, out value);
+ value = GetSanitizedStringTag(value, track.Path);
+ return hasField;
+ }
+
+ private bool TryGetSanitizedUFIDFields(Track track, out string? owner, out string? identifier)
+ {
+ var hasField = TryGetAdditionalFieldWithFallback(track, "UFID", out string? value);
+ if (hasField && !string.IsNullOrEmpty(value))
+ {
+ string[] parts = value.Split('\0');
+ if (parts.Length == 2)
+ {
+ owner = GetSanitizedStringTag(parts[0], track.Path);
+ identifier = GetSanitizedStringTag(parts[1], track.Path);
+ return true;
+ }
+ }
+
+ owner = null;
+ identifier = null;
+ return false;
+ }
+
+ // Build the explicit mka-style fallback key (e.g., ARTISTS -> track.artists, "MusicBrainz Artist Id" -> track.musicbrainz_artist_id)
+ private static string GetMkaFallbackKey(string key)
+ {
+ if (string.IsNullOrWhiteSpace(key))
+ {
+ return key;
+ }
+
+ var normalized = key.Trim().Replace(' ', '_').ToLowerInvariant();
+ return "track." + normalized;
+ }
+
+ // First try the normal key exactly; if missing, try the mka-style fallback key.
+ private bool TryGetAdditionalFieldWithFallback(Track track, string key, out string? value)
+ {
+ // Prefer the normal key (as-is, case-sensitive)
+ if (track.AdditionalFields.TryGetValue(key, out value))
+ {
+ return true;
+ }
+
+ // Fallback to mka-style: "track." + lower-case(original key)
+ var fallbackKey = GetMkaFallbackKey(key);
+ if (track.AdditionalFields.TryGetValue(fallbackKey, out value))
+ {
+ return true;
+ }
+
+ value = null;
+ return false;
+ }
}
}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index 266e1861f..bde23e842 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -6,6 +6,7 @@ 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;
@@ -34,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;
@@ -51,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,
@@ -67,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;
@@ -220,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);
@@ -240,8 +239,8 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
- mediaAttachments = Array.Empty<MediaAttachment>();
- chapters = Array.Empty<ChapterInfo>();
+ mediaAttachments = [];
+ chapters = [];
}
var libraryOptions = _libraryManager.GetLibraryOptions(video);
@@ -277,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)
@@ -298,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);
}
}
@@ -324,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)
{
@@ -406,7 +405,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
if (video.Genres.Length == 0 || replaceData)
{
- video.Genres = Array.Empty<string>();
+ video.Genres = [];
foreach (var genre in data.Genres.Trimmed())
{
@@ -515,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.Trim(),
- Type = person.Type,
- Role = person.Role.Trim()
- });
+ PeopleHelper.AddPerson(people, new PersonInfo
+ {
+ Name = person.Name,
+ Type = person.Type,
+ Role = person.Role?.Trim()
+ });
+ }
}
_libraryManager.UpdatePeople(video, people);
@@ -649,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..9f5463b82 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))
{
- _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/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 0bcc301cb..13511b594 100644
--- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs
+++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
@@ -1,249 +1,266 @@
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, StringComparer.OrdinalIgnoreCase)
+ .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.Ordinal))
+ {
+ 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, StringComparer.OrdinalIgnoreCase)
+ .OrderByDescending(g => g.Count())
+ .Select(g => g.Key)
+ .ToArray();
- return updateType;
+ if (!item.Artists.SequenceEqual(artists, StringComparer.Ordinal))
+ {
+ 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
{
- Name = albumArtist.Trim(),
+ Name = albumArtist,
Type = PersonKind.AlbumArtist
});
}
+ }
- foreach (var artist in item.Artists)
+ foreach (var artist in item.Artists)
+ {
+ if (!string.IsNullOrWhiteSpace(artist))
{
PeopleHelper.AddPerson(people, new PersonInfo
{
- Name = artist.Trim(),
+ Name = artist,
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/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/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/AudioDbArtistExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalUrlProvider.cs
index 56b0d9bcb..6c1fbbeb7 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalUrlProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistExternalUrlProvider.cs
@@ -22,7 +22,7 @@ public class AudioDbArtistExternalUrlProvider : IExternalUrlProvider
var baseUrl = "https://www.theaudiodb.com/";
switch (item)
{
- case MusicAlbum:
+ case MusicArtist:
case Person:
yield return baseUrl + $"artist/{externalId}";
break;
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalUrlProvider.cs
index ee5a597c6..398ec2d20 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalUrlProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalUrlProvider.cs
@@ -21,7 +21,7 @@ public class MusicBrainzArtistExternalUrlProvider : IExternalUrlProvider
{
switch (item)
{
- case MusicAlbum:
+ case MusicArtist:
case Person:
yield return Plugin.Instance!.Configuration.Server + $"/artist/{externalId}";
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
index ad9edb031..82c6e3011 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
@@ -138,6 +138,8 @@ namespace MediaBrowser.Providers.Plugins.Omdb
}
var item = itemResult.Item;
+ item.IndexNumber = episodeNumber;
+ item.ParentIndexNumber = seasonNumber;
var seasonResult = await GetSeasonRootObject(seriesImdbId, seasonNumber, cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
index 18cdba7a0..02818a0e2 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
@@ -66,7 +66,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
var language = item.GetPreferredMetadataLanguage();
// TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here
- var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, null, null, cancellationToken).ConfigureAwait(false);
+ var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, null, null, null, cancellationToken).ConfigureAwait(false);
if (collection?.Images is null)
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
index c76c65591..34c9abae1 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
@@ -47,7 +47,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
if (tmdbId > 0)
{
- var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
+ var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language, searchInfo.MetadataCountryCode), searchInfo.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
if (collection is null)
{
@@ -70,7 +70,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
return new[] { result };
}
- var collectionSearchResults = await _tmdbClientManager.SearchCollectionAsync(searchInfo.Name, language, cancellationToken).ConfigureAwait(false);
+ var collectionSearchResults = await _tmdbClientManager.SearchCollectionAsync(searchInfo.Name, language, searchInfo.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
var collections = new RemoteSearchResult[collectionSearchResults.Count];
for (var i = 0; i < collectionSearchResults.Count; i++)
@@ -95,6 +95,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
{
var tmdbId = Convert.ToInt32(info.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
var language = info.MetadataLanguage;
+
// We don't already have an Id, need to fetch it
if (tmdbId <= 0)
{
@@ -102,7 +103,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
// Caller provides the filename with extension stripped and NOT the parsed filename
var parsedName = _libraryManager.ParseName(info.Name);
var cleanedName = TmdbUtils.CleanName(parsedName.Name);
- var searchResults = await _tmdbClientManager.SearchCollectionAsync(cleanedName, language, cancellationToken).ConfigureAwait(false);
+ var searchResults = await _tmdbClientManager.SearchCollectionAsync(cleanedName, language, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
if (searchResults is not null && searchResults.Count > 0)
{
@@ -114,7 +115,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
if (tmdbId > 0)
{
- var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
+ var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language, info.MetadataCountryCode), info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
if (collection is not null)
{
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/TmdbMovieImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs
index 1696a2c49..fcc357410 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs
@@ -59,6 +59,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{
var language = item.GetPreferredMetadataLanguage();
+ var countryCode = item.GetPreferredMetadataCountryCode();
var movieTmdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
if (movieTmdbId <= 0)
@@ -69,7 +70,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
return Enumerable.Empty<RemoteImageInfo>();
}
- var movieResult = await _tmdbClientManager.FindByExternalIdAsync(movieImdbId, FindExternalSource.Imdb, language, cancellationToken).ConfigureAwait(false);
+ var movieResult = await _tmdbClientManager.FindByExternalIdAsync(movieImdbId, FindExternalSource.Imdb, language, countryCode, cancellationToken).ConfigureAwait(false);
if (movieResult?.MovieResults is not null && movieResult.MovieResults.Count > 0)
{
movieTmdbId = movieResult.MovieResults[0].Id;
@@ -83,7 +84,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
// TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here
var movie = await _tmdbClientManager
- .GetMovieAsync(movieTmdbId, null, null, cancellationToken)
+ .GetMovieAsync(movieTmdbId, null, null, null, cancellationToken)
.ConfigureAwait(false);
if (movie?.Images is null)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
index 9bb6507fe..414a0a3c9 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
@@ -59,7 +59,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
.GetMovieAsync(
int.Parse(id, CultureInfo.InvariantCulture),
searchInfo.MetadataLanguage,
- TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
+ TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode),
+ searchInfo.MetadataCountryCode,
cancellationToken)
.ConfigureAwait(false);
@@ -93,7 +94,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
var result = await _tmdbClientManager.FindByExternalIdAsync(
id,
FindExternalSource.Imdb,
- TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
+ TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode),
+ searchInfo.MetadataCountryCode,
cancellationToken).ConfigureAwait(false);
movieResults = result?.MovieResults;
}
@@ -103,7 +105,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
var result = await _tmdbClientManager.FindByExternalIdAsync(
id,
FindExternalSource.TvDb,
- TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
+ TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode),
+ searchInfo.MetadataCountryCode,
cancellationToken).ConfigureAwait(false);
movieResults = result?.MovieResults;
}
@@ -111,7 +114,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
if (movieResults is null)
{
movieResults = await _tmdbClientManager
- .SearchMovieAsync(searchInfo.Name, searchInfo.Year ?? 0, searchInfo.MetadataLanguage, cancellationToken)
+ .SearchMovieAsync(searchInfo.Name, searchInfo.Year ?? 0, searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode, cancellationToken)
.ConfigureAwait(false);
}
@@ -144,6 +147,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))
{
@@ -151,7 +155,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
// Caller provides the filename with extension stripped and NOT the parsed filename
var parsedName = _libraryManager.ParseName(info.Name);
var cleanedName = TmdbUtils.CleanName(parsedName.Name);
- var searchResults = await _tmdbClientManager.SearchMovieAsync(cleanedName, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+
+ var searchResults = await _tmdbClientManager.SearchMovieAsync(cleanedName, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
if (searchResults.Count > 0)
{
@@ -161,7 +166,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
if (string.IsNullOrEmpty(tmdbId) && !string.IsNullOrEmpty(imdbId))
{
- var movieResultFromImdbId = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+ var movieResultFromImdbId = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
if (movieResultFromImdbId?.MovieResults.Count > 0)
{
tmdbId = movieResultFromImdbId.MovieResults[0].Id.ToString(CultureInfo.InvariantCulture);
@@ -174,7 +179,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
}
var movieResult = await _tmdbClientManager
- .GetMovieAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
+ .GetMovieAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage, info.MetadataCountryCode), info.MetadataCountryCode, cancellationToken)
.ConfigureAwait(false);
if (movieResult is null)
@@ -212,15 +217,18 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
var releases = movieResult.Releases.Countries.Where(i => !string.IsNullOrWhiteSpace(i.Certification)).ToList();
var ourRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, info.MetadataCountryCode, StringComparison.OrdinalIgnoreCase));
- var usRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, "US", StringComparison.OrdinalIgnoreCase));
if (ourRelease is not null)
{
movie.OfficialRating = TmdbUtils.BuildParentalRating(ourRelease.Iso_3166_1, ourRelease.Certification);
}
- else if (usRelease is not null)
+ else
{
- movie.OfficialRating = usRelease.Certification;
+ var usRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, "US", StringComparison.OrdinalIgnoreCase));
+ if (usRelease is not null)
+ {
+ movie.OfficialRating = usRelease.Certification;
+ }
}
}
@@ -249,12 +257,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.Trim(),
+ Role = actor.Character?.Trim() ?? string.Empty,
Type = PersonKind.Actor,
SortOrder = actor.Order
};
@@ -275,32 +297,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)
{
- // Normalize this
- var type = TmdbUtils.MapCrewToPersonType(person);
+ crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath));
+ }
+
+ crewQuery = crewQuery.Take(config.MaxCrewMembers);
- if (!TmdbUtils.WantedCrewKinds.Contains(type)
- && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase))
+ foreach (var entry in crewQuery)
+ {
+ var crewMember = entry.CrewMember;
+
+ if (string.IsNullOrWhiteSpace(crewMember.Name))
{
continue;
}
var personInfo = new PersonInfo
{
- Name = person.Name.Trim(),
- Role = person.Job?.Trim(),
- 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);
@@ -310,9 +347,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
if (movieResult.Videos?.Results is not null)
{
var trailers = new List<MediaUrl>();
- for (var i = 0; i < movieResult.Videos.Results.Count; i++)
+
+ var sortedVideos = movieResult.Videos.Results
+ .OrderByDescending(video => string.Equals(video.Type, "trailer", StringComparison.OrdinalIgnoreCase));
+
+ foreach (var video in sortedVideos)
{
- var video = movieResult.Videos.Results[i];
if (!TmdbUtils.IsTrailerType(video))
{
continue;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
index 9e5404b32..33888ddf4 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
@@ -60,7 +60,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
}
var language = item.GetPreferredMetadataLanguage();
- var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), language, cancellationToken).ConfigureAwait(false);
+ var countryCode = item.GetPreferredMetadataCountryCode();
+ var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), language, countryCode, cancellationToken).ConfigureAwait(false);
if (personResult?.Images?.Profiles is null)
{
return Enumerable.Empty<RemoteImageInfo>();
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
index 98c46895d..4b32d0f6b 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
@@ -39,7 +39,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
{
if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId))
{
- var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+ var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
if (personResult is not null)
{
@@ -101,7 +101,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
if (personTmdbId > 0)
{
- var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+ var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
if (person is null)
{
return result;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
index 7de0e430f..7ae54cdcd 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
@@ -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, episodeNumber.Value, series.DisplayOrder, null, null, cancellationToken)
+ .GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, series.DisplayOrder, null, 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 73c3b4f16..e30c555cb 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
@@ -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)
@@ -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, 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, info.MetadataCountryCode), info.MetadataCountryCode, 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, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
+ .GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage, info.MetadataCountryCode), info.MetadataCountryCode, cancellationToken)
.ConfigureAwait(false);
}
@@ -176,8 +177,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var item = new Episode
{
- IndexNumber = info.IndexNumber,
- ParentIndexNumber = info.ParentIndexNumber,
+ IndexNumber = episodeNumber,
+ ParentIndexNumber = seasonNumber,
IndexNumberEnd = info.IndexNumberEnd,
Name = episodeResult.Name,
PremiereDate = episodeResult.AirDate,
@@ -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.Trim(),
+ 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.Trim(),
+ 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?.Trim(),
- 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/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
index a743601ed..5b2f0d26e 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
@@ -68,7 +68,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 seasonResult = await _tmdbClientManager
- .GetSeasonAsync(seriesTmdbId, season.IndexNumber.Value, null, null, cancellationToken)
+ .GetSeasonAsync(seriesTmdbId, season.IndexNumber.Value, null, null, null, cancellationToken)
.ConfigureAwait(false);
var posters = seasonResult?.Images?.Posters;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
index b0a1e00df..1b429039e 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);
@@ -53,7 +54,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
}
var seasonResult = await _tmdbClientManager
- .GetSeasonAsync(Convert.ToInt32(seriesTmdbId, CultureInfo.InvariantCulture), seasonNumber.Value, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
+ .GetSeasonAsync(Convert.ToInt32(seriesTmdbId, CultureInfo.InvariantCulture), seasonNumber.Value, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage, info.MetadataCountryCode), info.MetadataCountryCode, cancellationToken)
.ConfigureAwait(false);
if (seasonResult is null)
@@ -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,47 +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))
{
- var member = cast[i];
- result.AddPerson(new PersonInfo
+ if (string.IsNullOrWhiteSpace(actor.Name))
+ {
+ continue;
+ }
+
+ var personInfo = new PersonInfo
{
- Name = member.Name.Trim(),
- Role = member.Character.Trim(),
+ Name = actor.Name.Trim(),
+ Role = actor.Character?.Trim() ?? string.Empty,
Type = PersonKind.Actor,
- SortOrder = member.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?.Trim(),
- 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/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
index 2cb4fe1c1..5cba84dcb 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
@@ -68,7 +68,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 series = await _tmdbClientManager
- .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), null, null, cancellationToken)
+ .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), null, null, null, cancellationToken)
.ConfigureAwait(false);
if (series?.Images is null)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
index 9ace9c674..f0828e826 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
@@ -57,7 +57,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var tmdbId))
{
var series = await _tmdbClientManager
- .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, searchInfo.MetadataLanguage, cancellationToken)
+ .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode, cancellationToken)
.ConfigureAwait(false);
if (series is not null)
@@ -71,7 +71,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
if (searchInfo.TryGetProviderId(MetadataProvider.Imdb, out var imdbId))
{
var findResult = await _tmdbClientManager
- .FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, searchInfo.MetadataLanguage, cancellationToken)
+ .FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode, cancellationToken)
.ConfigureAwait(false);
var tvResults = findResult?.TvResults;
@@ -92,7 +92,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
if (searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out var tvdbId))
{
var findResult = await _tmdbClientManager
- .FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, searchInfo.MetadataLanguage, cancellationToken)
+ .FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode, cancellationToken)
.ConfigureAwait(false);
var tvResults = findResult?.TvResults;
@@ -110,7 +110,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
}
}
- var tvSearchResults = await _tmdbClientManager.SearchSeriesAsync(searchInfo.Name, searchInfo.MetadataLanguage, cancellationToken: cancellationToken)
+ var tvSearchResults = await _tmdbClientManager.SearchSeriesAsync(searchInfo.Name, searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode, cancellationToken: cancellationToken)
.ConfigureAwait(false);
var remoteResults = new RemoteSearchResult[tvSearchResults.Count];
@@ -173,7 +173,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
if (string.IsNullOrEmpty(tmdbId) && info.TryGetProviderId(MetadataProvider.Imdb, out var imdbId))
{
- var searchResult = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+ var searchResult = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
if (searchResult?.TvResults.Count > 0)
{
tmdbId = searchResult.TvResults[0].Id.ToString(CultureInfo.InvariantCulture);
@@ -182,7 +182,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
if (string.IsNullOrEmpty(tmdbId) && info.TryGetProviderId(MetadataProvider.Tvdb, out var tvdbId))
{
- var searchResult = await _tmdbClientManager.FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+ var searchResult = await _tmdbClientManager.FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
if (searchResult?.TvResults.Count > 0)
{
tmdbId = searchResult.TvResults[0].Id.ToString(CultureInfo.InvariantCulture);
@@ -196,7 +196,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
// Caller provides the filename with extension stripped and NOT the parsed filename
var parsedName = _libraryManager.ParseName(info.Name);
var cleanedName = TmdbUtils.CleanName(parsedName.Name);
- var searchResults = await _tmdbClientManager.SearchSeriesAsync(cleanedName, info.MetadataLanguage, info.Year ?? parsedName.Year ?? 0, cancellationToken).ConfigureAwait(false);
+ var searchResults = await _tmdbClientManager.SearchSeriesAsync(cleanedName, info.MetadataLanguage, info.MetadataCountryCode, info.Year ?? parsedName.Year ?? 0, cancellationToken).ConfigureAwait(false);
if (searchResults.Count > 0)
{
@@ -212,7 +212,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
cancellationToken.ThrowIfCancellationRequested();
var tvShow = await _tmdbClientManager
- .GetSeriesAsync(tmdbIdInt, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
+ .GetSeriesAsync(tmdbIdInt, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage, info.MetadataCountryCode), info.MetadataCountryCode, cancellationToken)
.ConfigureAwait(false);
if (tvShow is null)
@@ -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.Trim(),
+ 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?.Trim(),
- 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..fedf34598 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
@@ -51,9 +51,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="tmdbId">The movie's TMDb id.</param>
/// <param name="language">The movie's language.</param>
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
+ /// <param name="countryCode">The country code, ISO 3166-1.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb movie or null if not found.</returns>
- public async Task<Movie?> GetMovieAsync(int tmdbId, string? language, string? imageLanguages, CancellationToken cancellationToken)
+ public async Task<Movie?> GetMovieAsync(int tmdbId, string? language, string? imageLanguages, string? countryCode, CancellationToken cancellationToken)
{
var key = $"movie-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
if (_memoryCache.TryGetValue(key, out Movie? movie))
@@ -71,7 +72,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
movie = await _tmDbClient.GetMovieAsync(
tmdbId,
- TmdbUtils.NormalizeLanguage(language),
+ TmdbUtils.NormalizeLanguage(language, countryCode),
imageLanguages,
extraMethods,
cancellationToken).ConfigureAwait(false);
@@ -90,9 +91,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="tmdbId">The collection's TMDb id.</param>
/// <param name="language">The collection's language.</param>
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
+ /// <param name="countryCode">The country code, ISO 3166-1.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb collection or null if not found.</returns>
- public async Task<Collection?> GetCollectionAsync(int tmdbId, string? language, string? imageLanguages, CancellationToken cancellationToken)
+ public async Task<Collection?> GetCollectionAsync(int tmdbId, string? language, string? imageLanguages, string? countryCode, CancellationToken cancellationToken)
{
var key = $"collection-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
if (_memoryCache.TryGetValue(key, out Collection? collection))
@@ -104,7 +106,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
collection = await _tmDbClient.GetCollectionAsync(
tmdbId,
- TmdbUtils.NormalizeLanguage(language),
+ TmdbUtils.NormalizeLanguage(language, countryCode),
imageLanguages,
CollectionMethods.Images,
cancellationToken).ConfigureAwait(false);
@@ -123,9 +125,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="tmdbId">The tv show's TMDb id.</param>
/// <param name="language">The tv show's language.</param>
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
+ /// <param name="countryCode">The country code, ISO 3166-1.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb tv show information or null if not found.</returns>
- public async Task<TvShow?> GetSeriesAsync(int tmdbId, string? language, string? imageLanguages, CancellationToken cancellationToken)
+ public async Task<TvShow?> GetSeriesAsync(int tmdbId, string? language, string? imageLanguages, string? countryCode, CancellationToken cancellationToken)
{
var key = $"series-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
if (_memoryCache.TryGetValue(key, out TvShow? series))
@@ -143,7 +146,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
series = await _tmDbClient.GetTvShowAsync(
tmdbId,
- language: TmdbUtils.NormalizeLanguage(language),
+ language: TmdbUtils.NormalizeLanguage(language, countryCode),
includeImageLanguage: imageLanguages,
extraMethods: extraMethods,
cancellationToken: cancellationToken).ConfigureAwait(false);
@@ -163,9 +166,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="displayOrder">The display order.</param>
/// <param name="language">The tv show's language.</param>
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
+ /// <param name="countryCode">The country code, ISO 3166-1.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb tv show episode group information or null if not found.</returns>
- private async Task<TvGroupCollection?> GetSeriesGroupAsync(int tvShowId, string displayOrder, string? language, string? imageLanguages, CancellationToken cancellationToken)
+ private async Task<TvGroupCollection?> GetSeriesGroupAsync(int tvShowId, string displayOrder, string? language, string? imageLanguages, string? countryCode, CancellationToken cancellationToken)
{
TvGroupType? groupType =
string.Equals(displayOrder, "originalAirDate", StringComparison.Ordinal) ? TvGroupType.OriginalAirDate :
@@ -190,7 +194,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
await EnsureClientConfigAsync().ConfigureAwait(false);
- var series = await GetSeriesAsync(tvShowId, language, imageLanguages, cancellationToken).ConfigureAwait(false);
+ var series = await GetSeriesAsync(tvShowId, language, imageLanguages, countryCode, cancellationToken).ConfigureAwait(false);
var episodeGroupId = series?.EpisodeGroups.Results.Find(g => g.Type == groupType)?.Id;
if (episodeGroupId is null)
@@ -200,7 +204,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
group = await _tmDbClient.GetTvEpisodeGroupsAsync(
episodeGroupId,
- language: TmdbUtils.NormalizeLanguage(language),
+ language: TmdbUtils.NormalizeLanguage(language, countryCode),
cancellationToken: cancellationToken).ConfigureAwait(false);
if (group is not null)
@@ -218,9 +222,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="seasonNumber">The season number.</param>
/// <param name="language">The tv season's language.</param>
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
+ /// <param name="countryCode">The country code, ISO 3166-1.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb tv season information or null if not found.</returns>
- public async Task<TvSeason?> GetSeasonAsync(int tvShowId, int seasonNumber, string? language, string? imageLanguages, CancellationToken cancellationToken)
+ public async Task<TvSeason?> GetSeasonAsync(int tvShowId, int seasonNumber, string? language, string? imageLanguages, string? countryCode, CancellationToken cancellationToken)
{
var key = $"season-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}-{language}";
if (_memoryCache.TryGetValue(key, out TvSeason? season))
@@ -233,7 +238,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
season = await _tmDbClient.GetTvSeasonAsync(
tvShowId,
seasonNumber,
- language: TmdbUtils.NormalizeLanguage(language),
+ language: TmdbUtils.NormalizeLanguage(language, countryCode),
includeImageLanguage: imageLanguages,
extraMethods: TvSeasonMethods.Credits | TvSeasonMethods.Images | TvSeasonMethods.ExternalIds | TvSeasonMethods.Videos,
cancellationToken: cancellationToken).ConfigureAwait(false);
@@ -255,9 +260,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="displayOrder">The display order.</param>
/// <param name="language">The episode's language.</param>
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
+ /// <param name="countryCode">The country code, ISO 3166-1.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb tv episode information or null if not found.</returns>
- public async Task<TvEpisode?> GetEpisodeAsync(int tvShowId, int seasonNumber, int episodeNumber, string displayOrder, string? language, string? imageLanguages, CancellationToken cancellationToken)
+ public async Task<TvEpisode?> GetEpisodeAsync(int tvShowId, int seasonNumber, int episodeNumber, string displayOrder, string? language, string? imageLanguages, string? countryCode, CancellationToken cancellationToken)
{
var key = $"episode-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}e{episodeNumber.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}";
if (_memoryCache.TryGetValue(key, out TvEpisode? episode))
@@ -267,7 +273,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
await EnsureClientConfigAsync().ConfigureAwait(false);
- var group = await GetSeriesGroupAsync(tvShowId, displayOrder, language, imageLanguages, cancellationToken).ConfigureAwait(false);
+ var group = await GetSeriesGroupAsync(tvShowId, displayOrder, language, imageLanguages, countryCode, cancellationToken).ConfigureAwait(false);
if (group is not null)
{
var season = group.Groups.Find(s => s.Order == seasonNumber);
@@ -284,7 +290,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
tvShowId,
seasonNumber,
episodeNumber,
- language: TmdbUtils.NormalizeLanguage(language),
+ language: TmdbUtils.NormalizeLanguage(language, countryCode),
includeImageLanguage: imageLanguages,
extraMethods: TvEpisodeMethods.Credits | TvEpisodeMethods.Images | TvEpisodeMethods.ExternalIds | TvEpisodeMethods.Videos,
cancellationToken: cancellationToken).ConfigureAwait(false);
@@ -301,10 +307,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// Gets a person eg. cast or crew member from the TMDb API based on its TMDb id.
/// </summary>
/// <param name="personTmdbId">The person's TMDb id.</param>
- /// <param name="language">The episode's language.</param>
+ /// <param name="language">The person's language.</param>
+ /// <param name="countryCode">The country code, ISO 3166-1.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb person information or null if not found.</returns>
- public async Task<Person?> GetPersonAsync(int personTmdbId, string language, CancellationToken cancellationToken)
+ public async Task<Person?> GetPersonAsync(int personTmdbId, string language, string? countryCode, CancellationToken cancellationToken)
{
var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
if (_memoryCache.TryGetValue(key, out Person? person))
@@ -316,7 +323,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
person = await _tmDbClient.GetPersonAsync(
personTmdbId,
- TmdbUtils.NormalizeLanguage(language),
+ TmdbUtils.NormalizeLanguage(language, countryCode),
PersonMethods.TvCredits | PersonMethods.MovieCredits | PersonMethods.Images | PersonMethods.ExternalIds,
cancellationToken).ConfigureAwait(false);
@@ -334,12 +341,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="externalId">The item's external id.</param>
/// <param name="source">The source of the id eg. IMDb.</param>
/// <param name="language">The item's language.</param>
+ /// <param name="countryCode">The country code, ISO 3166-1.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb item or null if not found.</returns>
public async Task<FindContainer?> FindByExternalIdAsync(
string externalId,
FindExternalSource source,
string language,
+ string? countryCode,
CancellationToken cancellationToken)
{
var key = $"find-{source.ToString()}-{externalId.ToString(CultureInfo.InvariantCulture)}-{language}";
@@ -353,7 +362,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
result = await _tmDbClient.FindAsync(
source,
externalId,
- TmdbUtils.NormalizeLanguage(language),
+ TmdbUtils.NormalizeLanguage(language, countryCode),
cancellationToken).ConfigureAwait(false);
if (result is not null)
@@ -369,12 +378,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// </summary>
/// <param name="name">The name of the tv show.</param>
/// <param name="language">The tv show's language.</param>
+ /// <param name="countryCode">The country code, ISO 3166-1.</param>
/// <param name="year">The year the tv show first aired.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb tv show information.</returns>
- public async Task<IReadOnlyList<SearchTv>> SearchSeriesAsync(string name, string language, int year = 0, CancellationToken cancellationToken = default)
+ public async Task<IReadOnlyList<SearchTv>> SearchSeriesAsync(string name, string language, string? countryCode, 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;
@@ -383,7 +393,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
await EnsureClientConfigAsync().ConfigureAwait(false);
var searchResults = await _tmDbClient
- .SearchTvShowAsync(name, TmdbUtils.NormalizeLanguage(language), includeAdult: Plugin.Instance.Configuration.IncludeAdult, firstAirDateYear: year, cancellationToken: cancellationToken)
+ .SearchTvShowAsync(name, TmdbUtils.NormalizeLanguage(language, countryCode), includeAdult: Plugin.Instance.Configuration.IncludeAdult, firstAirDateYear: year, cancellationToken: cancellationToken)
.ConfigureAwait(false);
if (searchResults.Results.Count > 0)
@@ -431,7 +441,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <returns>The TMDb movie information.</returns>
public Task<IReadOnlyList<SearchMovie>> SearchMovieAsync(string name, string language, CancellationToken cancellationToken)
{
- return SearchMovieAsync(name, 0, language, cancellationToken);
+ return SearchMovieAsync(name, 0, language, null, cancellationToken);
}
/// <summary>
@@ -440,9 +450,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="name">The name of the movie.</param>
/// <param name="year">The release year of the movie.</param>
/// <param name="language">The movie's language.</param>
+ /// <param name="countryCode">The country code, ISO 3166-1.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb movie information.</returns>
- public async Task<IReadOnlyList<SearchMovie>> SearchMovieAsync(string name, int year, string language, CancellationToken cancellationToken)
+ public async Task<IReadOnlyList<SearchMovie>> SearchMovieAsync(string name, int year, string language, string? countryCode, CancellationToken cancellationToken)
{
var key = $"moviesearch-{name}-{year.ToString(CultureInfo.InvariantCulture)}-{language}";
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchMovie>? movies) && movies is not null)
@@ -453,7 +464,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
await EnsureClientConfigAsync().ConfigureAwait(false);
var searchResults = await _tmDbClient
- .SearchMovieAsync(name, TmdbUtils.NormalizeLanguage(language), includeAdult: Plugin.Instance.Configuration.IncludeAdult, year: year, cancellationToken: cancellationToken)
+ .SearchMovieAsync(name, TmdbUtils.NormalizeLanguage(language, countryCode), includeAdult: Plugin.Instance.Configuration.IncludeAdult, year: year, cancellationToken: cancellationToken)
.ConfigureAwait(false);
if (searchResults.Results.Count > 0)
@@ -469,9 +480,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// </summary>
/// <param name="name">The name of the collection.</param>
/// <param name="language">The collection's language.</param>
+ /// <param name="countryCode">The country code, ISO 3166-1.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb collection information.</returns>
- public async Task<IReadOnlyList<SearchCollection>> SearchCollectionAsync(string name, string language, CancellationToken cancellationToken)
+ public async Task<IReadOnlyList<SearchCollection>> SearchCollectionAsync(string name, string language, string? countryCode, CancellationToken cancellationToken)
{
var key = $"collectionsearch-{name}-{language}";
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchCollection>? collections) && collections is not null)
@@ -482,7 +494,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
await EnsureClientConfigAsync().ConfigureAwait(false);
var searchResults = await _tmDbClient
- .SearchCollectionAsync(name, TmdbUtils.NormalizeLanguage(language), cancellationToken: cancellationToken)
+ .SearchCollectionAsync(name, TmdbUtils.NormalizeLanguage(language, countryCode), cancellationToken: cancellationToken)
.ConfigureAwait(false);
if (searchResults.Results.Count > 0)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs
index 27e3f93a3..8d9ec10c1 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbExternalUrlProvider.cs
@@ -30,7 +30,7 @@ public class TmdbExternalUrlProvider : IExternalUrlProvider
break;
case Season season:
- if (season.Series.TryGetProviderId(MetadataProvider.Tmdb, out var seriesExternalId))
+ if (season.Series?.TryGetProviderId(MetadataProvider.Tmdb, out var seriesExternalId) == true)
{
var orderString = season.Series.DisplayOrder;
var seasonNumber = season.IndexNumber;
@@ -51,7 +51,7 @@ public class TmdbExternalUrlProvider : IExternalUrlProvider
break;
case Episode episode:
- if (episode.Series.TryGetProviderId(MetadataProvider.Imdb, out seriesExternalId))
+ if (episode.Series?.TryGetProviderId(MetadataProvider.Tmdb, out seriesExternalId) == true)
{
var orderString = episode.Series.DisplayOrder;
var seasonNumber = episode.Season?.IndexNumber;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
index a7c93ac4c..f5e59a278 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>
@@ -105,14 +105,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// Normalizes a language string for use with TMDb's include image language parameter.
/// </summary>
/// <param name="preferredLanguage">The preferred language as either a 2 letter code with or without country code.</param>
+ /// <param name="countryCode">The country code, ISO 3166-1.</param>
/// <returns>The comma separated language string.</returns>
- public static string GetImageLanguagesParam(string preferredLanguage)
+ public static string GetImageLanguagesParam(string preferredLanguage, string? countryCode = null)
{
var languages = new List<string>();
if (!string.IsNullOrEmpty(preferredLanguage))
{
- preferredLanguage = NormalizeLanguage(preferredLanguage);
+ preferredLanguage = NormalizeLanguage(preferredLanguage, countryCode);
languages.Add(preferredLanguage);
@@ -140,15 +141,24 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// Normalizes a language string for use with TMDb's language parameter.
/// </summary>
/// <param name="language">The language code.</param>
+ /// <param name="countryCode">The country code.</param>
/// <returns>The normalized language code.</returns>
[return: NotNullIfNotNull(nameof(language))]
- public static string? NormalizeLanguage(string? language)
+ public static string? NormalizeLanguage(string? language, string? countryCode = null)
{
if (string.IsNullOrEmpty(language))
{
return language;
}
+ // Handle es-419 (Latin American Spanish) by converting to regional variant
+ if (string.Equals(language, "es-419", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(countryCode))
+ {
+ language = string.Equals(countryCode, "AR", StringComparison.OrdinalIgnoreCase)
+ ? "es-AR"
+ : "es-MX";
+ }
+
// TMDb requires this to be uppercase
// Everything after the hyphen must be written in uppercase due to a way TMDb wrote their API.
// See here: https://www.themoviedb.org/talk/5119221d760ee36c642af4ad?page=3#56e372a0c3a3685a9e0019ab
@@ -185,7 +195,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
return requestLanguage;
}
- return imageLanguage;
+ // TMDb now returns xx for no language instead of an empty string.
+ return string.Equals(imageLanguage, "xx", StringComparison.OrdinalIgnoreCase)
+ ? string.Empty
+ : imageLanguage;
}
/// <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 42d59d348..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 is not 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/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)
- {
- }
}
}