diff options
57 files changed, 1914 insertions, 1124 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index fa6e9ff97..987ce8b84 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -36,7 +36,6 @@ using Emby.Server.Implementations.SyncPlay; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; using Jellyfin.Api.Helpers; -using Jellyfin.Database.Implementations; using Jellyfin.Drawing; using Jellyfin.MediaEncoding.Hls.Playlist; using Jellyfin.Networking.Manager; @@ -63,6 +62,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Lyrics; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.MediaSegments; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Playlists; @@ -93,7 +93,6 @@ using MediaBrowser.Providers.Subtitles; using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -560,6 +559,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton<ISubtitleParser, SubtitleEditParser>(); serviceCollection.AddSingleton<ISubtitleEncoder, SubtitleEncoder>(); + serviceCollection.AddSingleton<IKeyframeManager, KeyframeManager>(); serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>(); diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 60f515f24..0eb387ffd 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.Collections var libraryOptions = new LibraryOptions { - PathInfos = new[] { new MediaPathInfo(path) }, + PathInfos = [new MediaPathInfo(path)], EnableRealtimeMonitor = false, SaveLocalMetadata = true }; @@ -150,15 +150,15 @@ namespace Emby.Server.Implementations.Collections try { - Directory.CreateDirectory(path); - + var info = Directory.CreateDirectory(path); var collection = new BoxSet { Name = name, Path = path, IsLocked = options.IsLocked, ProviderIds = options.ProviderIds, - DateCreated = DateTime.UtcNow + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc }; parentFolder.AddChild(collection); diff --git a/Emby.Server.Implementations/Library/KeyframeManager.cs b/Emby.Server.Implementations/Library/KeyframeManager.cs new file mode 100644 index 000000000..18f4ce047 --- /dev/null +++ b/Emby.Server.Implementations/Library/KeyframeManager.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.MediaEncoding.Keyframes; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Persistence; + +namespace Emby.Server.Implementations.Library; + +/// <summary> +/// Manager for Keyframe data. +/// </summary> +public class KeyframeManager : IKeyframeManager +{ + private readonly IKeyframeRepository _repository; + + /// <summary> + /// Initializes a new instance of the <see cref="KeyframeManager"/> class. + /// </summary> + /// <param name="repository">The keyframe repository.</param> + public KeyframeManager(IKeyframeRepository repository) + { + _repository = repository; + } + + /// <inheritdoc /> + public IReadOnlyList<KeyframeData> GetKeyframeData(Guid itemId) + { + return _repository.GetKeyframeData(itemId); + } + + /// <inheritdoc /> + public async Task SaveKeyframeDataAsync(Guid itemId, KeyframeData data, CancellationToken cancellationToken) + { + await _repository.SaveKeyframeDataAsync(itemId, data, cancellationToken).ConfigureAwait(false); + } + + /// <inheritdoc /> + public async Task DeleteKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken) + { + await _repository.DeleteKeyframeDataAsync(itemId, cancellationToken).ConfigureAwait(false); + } +} diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 51f330746..1fdd80bd8 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -208,7 +208,7 @@ namespace Emby.Server.Implementations.Library /// Gets or sets the postscan tasks. /// </summary> /// <value>The postscan tasks.</value> - private ILibraryPostScanTask[] PostscanTasks { get; set; } = []; + private ILibraryPostScanTask[] PostScanTasks { get; set; } = []; /// <summary> /// Gets or sets the intro providers. @@ -245,20 +245,20 @@ namespace Emby.Server.Implementations.Library /// <param name="resolvers">The resolvers.</param> /// <param name="introProviders">The intro providers.</param> /// <param name="itemComparers">The item comparers.</param> - /// <param name="postscanTasks">The post scan tasks.</param> + /// <param name="postScanTasks">The post scan tasks.</param> public void AddParts( IEnumerable<IResolverIgnoreRule> rules, IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders, IEnumerable<IBaseItemComparer> itemComparers, - IEnumerable<ILibraryPostScanTask> postscanTasks) + IEnumerable<ILibraryPostScanTask> postScanTasks) { EntityResolutionIgnoreRules = rules.ToArray(); EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray(); MultiItemResolvers = EntityResolvers.OfType<IMultiItemResolver>().ToArray(); IntroProviders = introProviders.ToArray(); Comparers = itemComparers.ToArray(); - PostscanTasks = postscanTasks.ToArray(); + PostScanTasks = postScanTasks.ToArray(); } /// <summary> @@ -393,7 +393,7 @@ namespace Emby.Server.Implementations.Library } } - if (options.DeleteFileLocation && item.IsFileProtocol) + if ((options.DeleteFileLocation && item.IsFileProtocol) || IsInternalItem(item)) { // Assume only the first is required // Add this flag to GetDeletePaths if required in the future @@ -472,6 +472,36 @@ namespace Emby.Server.Implementations.Library ReportItemRemoved(item, parent); } + private bool IsInternalItem(BaseItem item) + { + if (!item.IsFileProtocol) + { + return false; + } + + var pathToCheck = item switch + { + Genre => _configurationManager.ApplicationPaths.GenrePath, + MusicArtist => _configurationManager.ApplicationPaths.ArtistsPath, + MusicGenre => _configurationManager.ApplicationPaths.GenrePath, + Person => _configurationManager.ApplicationPaths.PeoplePath, + Studio => _configurationManager.ApplicationPaths.StudioPath, + Year => _configurationManager.ApplicationPaths.YearPath, + _ => null + }; + + var itemPath = item.Path; + if (!string.IsNullOrEmpty(pathToCheck) && !string.IsNullOrEmpty(itemPath)) + { + var cleanPath = _fileSystem.GetValidFilename(itemPath); + var cleanCheckPath = _fileSystem.GetValidFilename(pathToCheck); + + return cleanPath.StartsWith(cleanCheckPath, StringComparison.Ordinal); + } + + return false; + } + private List<string> GetMetadataPaths(BaseItem item, IEnumerable<BaseItem> children) { var list = GetInternalMetadataPaths(item); @@ -639,7 +669,7 @@ namespace Emby.Server.Implementations.Library } } - // Need to remove subpaths that may have been resolved from shortcuts + // Need to remove sub-paths that may have been resolved from shortcuts // Example: if \\server\movies exists, then strip out \\server\movies\action if (isPhysicalRoot) { @@ -772,11 +802,12 @@ namespace Emby.Server.Implementations.Library // Add in the plug-in folders var path = Path.Combine(_configurationManager.ApplicationPaths.DataPath, "playlists"); - Directory.CreateDirectory(path); - + var info = Directory.CreateDirectory(path); Folder folder = new PlaylistsFolder { - Path = path + Path = path, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, }; if (folder.Id.IsEmpty()) @@ -862,7 +893,7 @@ namespace Emby.Server.Implementations.Library { Path = path, IsFolder = isFolder, - OrderBy = new[] { (ItemSortBy.DateCreated, SortOrder.Descending) }, + OrderBy = [(ItemSortBy.DateCreated, SortOrder.Descending)], Limit = 1, DtoOptions = new DtoOptions(true) }; @@ -968,7 +999,7 @@ namespace Emby.Server.Implementations.Library { var existing = GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { BaseItemKind.MusicArtist }, + IncludeItemTypes = [BaseItemKind.MusicArtist], Name = name, DtoOptions = options }).Cast<MusicArtist>() @@ -987,12 +1018,13 @@ namespace Emby.Server.Implementations.Library var item = GetItemById(id) as T; if (item is null) { + var info = Directory.CreateDirectory(path); item = new T { Name = name, Id = id, - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, Path = path }; @@ -1118,7 +1150,7 @@ namespace Emby.Server.Implementations.Library /// <returns>Task.</returns> private async Task RunPostScanTasks(IProgress<double> progress, CancellationToken cancellationToken) { - var tasks = PostscanTasks.ToList(); + var tasks = PostScanTasks.ToList(); var numComplete = 0; var numTasks = tasks.Count; @@ -1241,7 +1273,7 @@ namespace Emby.Server.Implementations.Library private CollectionTypeOptions? GetCollectionType(string path) { - var files = _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false); + var files = _fileSystem.GetFilePaths(path, [".collection"], true, false); foreach (ReadOnlySpan<char> file in files) { if (Enum.TryParse<CollectionTypeOptions>(Path.GetFileNameWithoutExtension(file), true, out var res)) @@ -1312,7 +1344,7 @@ namespace Emby.Server.Implementations.Library var parent = GetItemById(query.ParentId); if (parent is not null) { - SetTopParentIdsOrAncestors(query, new[] { parent }); + SetTopParentIdsOrAncestors(query, [parent]); } } @@ -1343,7 +1375,7 @@ namespace Emby.Server.Implementations.Library var parent = GetItemById(query.ParentId); if (parent is not null) { - SetTopParentIdsOrAncestors(query, new[] { parent }); + SetTopParentIdsOrAncestors(query, [parent]); } } @@ -1531,7 +1563,7 @@ namespace Emby.Server.Implementations.Library var parent = GetItemById(query.ParentId); if (parent is not null) { - SetTopParentIdsOrAncestors(query, new[] { parent }); + SetTopParentIdsOrAncestors(query, [parent]); } } @@ -1561,7 +1593,7 @@ namespace Emby.Server.Implementations.Library // Prevent searching in all libraries due to empty filter if (query.TopParentIds.Length == 0) { - query.TopParentIds = new[] { Guid.NewGuid() }; + query.TopParentIds = [Guid.NewGuid()]; } } else @@ -1572,7 +1604,7 @@ namespace Emby.Server.Implementations.Library // Prevent searching in all libraries due to empty filter if (query.AncestorIds.Length == 0) { - query.AncestorIds = new[] { Guid.NewGuid() }; + query.AncestorIds = [Guid.NewGuid()]; } } @@ -1601,7 +1633,7 @@ namespace Emby.Server.Implementations.Library // Prevent searching in all libraries due to empty filter if (query.TopParentIds.Length == 0) { - query.TopParentIds = new[] { Guid.NewGuid() }; + query.TopParentIds = [Guid.NewGuid()]; } } } @@ -1612,7 +1644,7 @@ namespace Emby.Server.Implementations.Library { if (view.ViewType == CollectionType.livetv) { - return new[] { view.Id }; + return [view.Id]; } // Translate view into folders @@ -1661,7 +1693,7 @@ namespace Emby.Server.Implementations.Library var topParent = item.GetTopParent(); if (topParent is not null) { - return new[] { topParent.Id }; + return [topParent.Id]; } return []; @@ -1868,7 +1900,7 @@ namespace Emby.Server.Implementations.Library /// <inheritdoc /> public void CreateItem(BaseItem item, BaseItem? parent) { - CreateItems(new[] { item }, parent, CancellationToken.None); + CreateItems([item], parent, CancellationToken.None); } /// <inheritdoc /> @@ -2054,7 +2086,7 @@ namespace Emby.Server.Implementations.Library /// <inheritdoc /> public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) - => UpdateItemsAsync(new[] { item }, parent, updateReason, cancellationToken); + => UpdateItemsAsync([item], parent, updateReason, cancellationToken); public async Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason) { @@ -2283,13 +2315,13 @@ namespace Emby.Server.Implementations.Library if (item is null || !string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase)) { - Directory.CreateDirectory(path); - + var info = Directory.CreateDirectory(path); item = new UserView { Path = path, Id = id, - DateCreated = DateTime.UtcNow, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, Name = name, ViewType = viewType, ForcedSortName = sortName @@ -2331,13 +2363,13 @@ namespace Emby.Server.Implementations.Library if (item is null) { - Directory.CreateDirectory(path); - + var info = Directory.CreateDirectory(path); item = new UserView { Path = path, Id = id, - DateCreated = DateTime.UtcNow, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, Name = name, ViewType = viewType, ForcedSortName = sortName, @@ -2395,20 +2427,19 @@ namespace Emby.Server.Implementations.Library if (item is null) { - Directory.CreateDirectory(path); - + var info = Directory.CreateDirectory(path); item = new UserView { Path = path, Id = id, - DateCreated = DateTime.UtcNow, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, Name = name, ViewType = viewType, - ForcedSortName = sortName + ForcedSortName = sortName, + DisplayParentId = parentId }; - item.DisplayParentId = parentId; - CreateItem(item, null); isNew = true; @@ -2465,20 +2496,19 @@ namespace Emby.Server.Implementations.Library if (item is null) { - Directory.CreateDirectory(path); - + var info = Directory.CreateDirectory(path); item = new UserView { Path = path, Id = id, - DateCreated = DateTime.UtcNow, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc, Name = name, ViewType = viewType, - ForcedSortName = sortName + ForcedSortName = sortName, + DisplayParentId = parentId }; - item.DisplayParentId = parentId; - CreateItem(item, null); isNew = true; @@ -2989,12 +3019,14 @@ namespace Emby.Server.Implementations.Library if (personEntity is null) { var path = Person.GetPath(person.Name); + var info = Directory.CreateDirectory(path); + var lastWriteTime = info.LastWriteTimeUtc; personEntity = new Person() { Name = person.Name, Id = GetItemByNameId<Person>(path), - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, + DateCreated = info.CreationTimeUtc, + DateModified = lastWriteTime, Path = path }; diff --git a/Emby.Server.Implementations/Library/PathManager.cs b/Emby.Server.Implementations/Library/PathManager.cs index 853d85e5e..a9b7a1274 100644 --- a/Emby.Server.Implementations/Library/PathManager.cs +++ b/Emby.Server.Implementations/Library/PathManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using MediaBrowser.Common.Configuration; @@ -84,4 +85,17 @@ public class PathManager : IPathManager return Path.Combine(GetChapterImageFolderPath(item), filename); } + + /// <inheritdoc/> + public IReadOnlyList<string> GetExtractedDataPaths(BaseItem item) + { + var mediaSourceId = item.Id.ToString("N", CultureInfo.InvariantCulture); + return [ + GetAttachmentFolderPath(mediaSourceId), + GetSubtitleFolderPath(mediaSourceId), + GetTrickplayDirectory(item, false), + GetTrickplayDirectory(item, true), + GetChapterImageFolderPath(item) + ]; + } } diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index c9e3a4daf..ab6bc4907 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -136,23 +136,33 @@ namespace Emby.Server.Implementations.Library if (config.UseFileCreationTimeForDateAdded) { - // directoryService.getFile may return null - if (info is not null) + var fileCreationDate = info?.CreationTimeUtc; + if (fileCreationDate is not null) { - var dateCreated = info.CreationTimeUtc; - + var dateCreated = fileCreationDate; if (dateCreated.Equals(DateTime.MinValue)) { dateCreated = DateTime.UtcNow; } - item.DateCreated = dateCreated; + item.DateCreated = dateCreated.Value; } } else { item.DateCreated = DateTime.UtcNow; } + + if (info is not null && !info.IsDirectory) + { + item.Size = info.Length; + } + + var fileModificationDate = info?.LastWriteTimeUtc; + if (fileModificationDate.HasValue) + { + item.DateModified = fileModificationDate.Value; + } } } } diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 98a43b6c9..1ce363de5 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -134,14 +134,16 @@ namespace Emby.Server.Implementations.Playlists try { - Directory.CreateDirectory(path); + var info = Directory.CreateDirectory(path); var playlist = new Playlist { Name = name, Path = path, OwnerUserId = request.UserId, Shares = request.Users ?? [], - OpenAccess = request.Public ?? false + OpenAccess = request.Public ?? false, + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc }; playlist.SetMediaType(request.MediaType); diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/MediaSegmentExtractionTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/MediaSegmentExtractionTask.cs index b8c944d3d..c3f17c2ae 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/MediaSegmentExtractionTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/MediaSegmentExtractionTask.cs @@ -4,10 +4,10 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; -using MediaBrowser.Controller; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Tasks; diff --git a/Jellyfin.Api/Controllers/MediaSegmentsController.cs b/Jellyfin.Api/Controllers/MediaSegmentsController.cs index e30e2b54e..2a91a8455 100644 --- a/Jellyfin.Api/Controllers/MediaSegmentsController.cs +++ b/Jellyfin.Api/Controllers/MediaSegmentsController.cs @@ -5,9 +5,9 @@ using System.Linq; using System.Threading.Tasks; using Jellyfin.Api.Extensions; using Jellyfin.Database.Implementations.Enums; -using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; using MediaBrowser.Model.MediaSegments; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; diff --git a/Jellyfin.Server.Implementations/Item/KeyframeRepository.cs b/Jellyfin.Server.Implementations/Item/KeyframeRepository.cs index a2267700f..93c6f472e 100644 --- a/Jellyfin.Server.Implementations/Item/KeyframeRepository.cs +++ b/Jellyfin.Server.Implementations/Item/KeyframeRepository.cs @@ -61,4 +61,12 @@ public class KeyframeRepository : IKeyframeRepository await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); await transaction.CommitAsync(cancellationToken).ConfigureAwait(false); } + + /// <inheritdoc /> + public async Task DeleteKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken) + { + using var context = _dbProvider.CreateDbContext(); + await context.KeyframeData.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false); + await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + } } diff --git a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs index d6eeafacc..5a2032c1f 100644 --- a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs +++ b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs @@ -10,10 +10,10 @@ using Jellyfin.Database.Implementations.Entities; using Jellyfin.Database.Implementations.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; using MediaBrowser.Controller.Providers; using MediaBrowser.Model; using MediaBrowser.Model.MediaSegments; @@ -140,6 +140,13 @@ public class MediaSegmentManager : IMediaSegmentManager } /// <inheritdoc /> + public async Task DeleteSegmentsAsync(Guid itemId) + { + using var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); + await db.MediaSegments.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync().ConfigureAwait(false); + } + + /// <inheritdoc /> public async Task<IEnumerable<MediaSegmentDto>> GetSegmentsAsync(Guid itemId, IEnumerable<MediaSegmentType>? typeFilter, bool filterByProvider = true) { var baseItem = _libraryManager.GetItemById(itemId); diff --git a/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs b/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs index c5bc70278..1ee4c41c3 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs @@ -73,6 +73,11 @@ public class MigrateKeyframeData : IDatabaseMigrationRoutine } offset += Limit; + if (offset > records) + { + offset = records; + } + _logger.LogInformation("Checked: {Count} - Imported: {Items} - Time: {Time}", offset, itemCount, sw.Elapsed); } while (offset < records); diff --git a/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs b/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs index 9031f2fdc..c6471b24c 100644 --- a/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs +++ b/Jellyfin.Server/Migrations/Routines/MoveExtractedFiles.cs @@ -95,6 +95,11 @@ public class MoveExtractedFiles : IMigrationRoutine } offset += Limit; + if (offset > records) + { + offset = records; + } + _logger.LogInformation("Checked: {Count} - Moved: {Items} - Time: {Time}", offset, itemCount, sw.Elapsed); } while (offset < records); diff --git a/Jellyfin.Server/Migrations/Routines/RefreshInternalDateModified.cs b/Jellyfin.Server/Migrations/Routines/RefreshInternalDateModified.cs new file mode 100644 index 000000000..9a95757a8 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/RefreshInternalDateModified.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Jellyfin.Database.Implementations; +using Jellyfin.Database.Implementations.Entities; +using Jellyfin.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.IO; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Migrations.Routines; + +/// <summary> +/// Migration to re-read creation dates for library items with internal metadata paths. +/// </summary> +[JellyfinMigration("2025-04-20T23:00:00", nameof(RefreshInternalDateModified), "32E762EB-4918-45CE-A44C-C801F66B877D", RunMigrationOnSetup = false)] +public class RefreshInternalDateModified : IDatabaseMigrationRoutine +{ + private readonly ILogger<RefreshInternalDateModified> _logger; + private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; + private readonly IFileSystem _fileSystem; + private readonly IServerApplicationHost _applicationHost; + private readonly bool _useFileCreationTimeForDateAdded; + + private IReadOnlyList<string> _internalTypes = [ + typeof(Genre).FullName!, + typeof(MusicGenre).FullName!, + typeof(MusicArtist).FullName!, + typeof(People).FullName!, + typeof(Studio).FullName! + ]; + + private IReadOnlyList<string> _internalPaths; + + /// <summary> + /// Initializes a new instance of the <see cref="RefreshInternalDateModified"/> class. + /// </summary> + /// <param name="applicationHost">Instance of the <see cref="IServerApplicationHost"/> interface.</param> + /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param> + /// <param name="configurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="dbProvider">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</param> + /// <param name="logger">The logger.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + public RefreshInternalDateModified( + IServerApplicationHost applicationHost, + IServerApplicationPaths applicationPaths, + IServerConfigurationManager configurationManager, + IDbContextFactory<JellyfinDbContext> dbProvider, + ILogger<RefreshInternalDateModified> logger, + IFileSystem fileSystem) + { + _dbProvider = dbProvider; + _logger = logger; + _fileSystem = fileSystem; + _applicationHost = applicationHost; + _internalPaths = [ + applicationPaths.ArtistsPath, + applicationPaths.GenrePath, + applicationPaths.MusicGenrePath, + applicationPaths.StudioPath, + applicationPaths.PeoplePath + ]; + _useFileCreationTimeForDateAdded = configurationManager.GetMetadataConfiguration().UseFileCreationTimeForDateAdded; + } + + /// <inheritdoc /> + public void Perform() + { + const int Limit = 5000; + int itemCount = 0, offset = 0; + + var sw = Stopwatch.StartNew(); + + using var context = _dbProvider.CreateDbContext(); + var records = context.BaseItems.Count(b => _internalTypes.Contains(b.Type)); + _logger.LogInformation("Checking if {Count} potentially internal items require refreshed DateModified", records); + + do + { + var results = context.BaseItems + .Where(b => _internalTypes.Contains(b.Type)) + .OrderBy(e => e.Id) + .Skip(offset) + .Take(Limit) + .ToList(); + + foreach (var item in results) + { + var itemPath = item.Path; + if (itemPath is not null) + { + var realPath = _applicationHost.ExpandVirtualPath(item.Path); + if (_internalPaths.Any(path => realPath.StartsWith(path, StringComparison.Ordinal))) + { + var writeTime = _fileSystem.GetLastWriteTimeUtc(realPath); + var itemModificationTime = item.DateModified; + if (writeTime != itemModificationTime) + { + _logger.LogDebug("Reset file modification date: Old: {Old} - New: {New} - Path: {Path}", itemModificationTime, writeTime, realPath); + item.DateModified = writeTime; + if (_useFileCreationTimeForDateAdded) + { + item.DateCreated = _fileSystem.GetCreationTimeUtc(realPath); + } + + itemCount++; + } + } + } + } + + offset += Limit; + if (offset > records) + { + offset = records; + } + + _logger.LogInformation("Checked: {Count} - Refreshed: {Items} - Time: {Time}", offset, itemCount, sw.Elapsed); + } while (offset < records); + + context.SaveChanges(); + + _logger.LogInformation("Refreshed DateModified for {Count} items in {Time}", itemCount, sw.Elapsed); + } +} diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index b90ec8222..16fde9440 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -25,6 +25,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; @@ -1265,7 +1266,7 @@ namespace MediaBrowser.Controller.Entities } /// <summary> - /// Overrides the base implementation to refresh metadata for local trailers. + /// The base implementation to refresh metadata. /// </summary> /// <param name="options">The options.</param> /// <param name="cancellationToken">The cancellation token.</param> @@ -1362,9 +1363,7 @@ namespace MediaBrowser.Controller.Entities protected virtual FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService) { - var path = ContainingFolderPath; - - return directoryService.GetFileSystemEntries(path); + return directoryService.GetFileSystemEntries(ContainingFolderPath); } private async Task<bool> RefreshExtras(BaseItem item, MetadataRefreshOptions options, IReadOnlyList<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) @@ -1393,6 +1392,23 @@ namespace MediaBrowser.Controller.Entities return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken); }); + // Cleanup removed extras + var removedExtraIds = item.ExtraIds.Where(e => !newExtraIds.Contains(e)).ToArray(); + if (removedExtraIds.Length > 0) + { + var removedExtras = LibraryManager.GetItemList(new InternalItemsQuery() + { + ItemIds = removedExtraIds + }); + foreach (var removedExtra in removedExtras) + { + LibraryManager.DeleteItem(removedExtra, new DeleteOptions() + { + DeleteFileLocation = false + }); + } + } + await Task.WhenAll(tasks).ConfigureAwait(false); item.ExtraIds = newExtraIds; @@ -1407,6 +1423,22 @@ namespace MediaBrowser.Controller.Entities public virtual bool RequiresRefresh() { + if (string.IsNullOrEmpty(Path) || DateModified == default) + { + return false; + } + + var info = FileSystem.GetFileSystemInfo(Path); + if (info.Exists) + { + if (info.IsDirectory) + { + return info.LastWriteTimeUtc != DateModified; + } + + return info.LastWriteTimeUtc != DateModified && info.Length != (Size ?? 0); + } + return false; } diff --git a/MediaBrowser.Controller/IO/IPathManager.cs b/MediaBrowser.Controller/IO/IPathManager.cs index 4e4eb514e..eb6743754 100644 --- a/MediaBrowser.Controller/IO/IPathManager.cs +++ b/MediaBrowser.Controller/IO/IPathManager.cs @@ -1,9 +1,10 @@ +using System.Collections.Generic; using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.IO; /// <summary> -/// Interface ITrickplayManager. +/// Interface IPathManager. /// </summary> public interface IPathManager { @@ -60,4 +61,11 @@ public interface IPathManager /// <param name="chapterPositionTicks">The chapter position.</param> /// <returns>The chapter images data path.</returns> public string GetChapterImagePath(BaseItem item, long chapterPositionTicks); + + /// <summary> + /// Gets the paths of extracted data folders. + /// </summary> + /// <param name="item">The base item.</param> + /// <returns>The absolute paths.</returns> + public IReadOnlyList<string> GetExtractedDataPaths(BaseItem item); } diff --git a/MediaBrowser.Controller/Library/IKeyframeManager.cs b/MediaBrowser.Controller/Library/IKeyframeManager.cs new file mode 100644 index 000000000..b0155efdd --- /dev/null +++ b/MediaBrowser.Controller/Library/IKeyframeManager.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.MediaEncoding.Keyframes; + +namespace MediaBrowser.Controller.IO; + +/// <summary> +/// Interface IKeyframeManager. +/// </summary> +public interface IKeyframeManager +{ + /// <summary> + /// Gets the keyframe data. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <returns>The keyframe data.</returns> + IReadOnlyList<KeyframeData> GetKeyframeData(Guid itemId); + + /// <summary> + /// Saves the keyframe data. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="data">The keyframe data.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>The task object representing the asynchronous operation.</returns> + Task SaveKeyframeDataAsync(Guid itemId, KeyframeData data, CancellationToken cancellationToken); + + /// <summary> + /// Deletes the keyframe data. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>The task object representing the asynchronous operation.</returns> + Task DeleteKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken); +} diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index df90f546c..98ed15eb6 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -220,13 +220,13 @@ namespace MediaBrowser.Controller.Library /// <param name="resolvers">The resolvers.</param> /// <param name="introProviders">The intro providers.</param> /// <param name="itemComparers">The item comparers.</param> - /// <param name="postscanTasks">The postscan tasks.</param> + /// <param name="postScanTasks">The post scan tasks.</param> void AddParts( IEnumerable<IResolverIgnoreRule> rules, IEnumerable<IItemResolver> resolvers, IEnumerable<IIntroProvider> introProviders, IEnumerable<IBaseItemComparer> itemComparers, - IEnumerable<ILibraryPostScanTask> postscanTasks); + IEnumerable<ILibraryPostScanTask> postScanTasks); /// <summary> /// Sorts the specified items. @@ -593,11 +593,11 @@ namespace MediaBrowser.Controller.Library QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query); /// <summary> - /// Ignores the file. + /// Checks if the file is ignored. /// </summary> /// <param name="file">The file.</param> /// <param name="parent">The parent.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> + /// <returns><c>true</c> if ignored, <c>false</c> otherwise.</returns> bool IgnoreFile(FileSystemMetadata file, BaseItem parent); Guid GetStudioId(string name); diff --git a/MediaBrowser.Controller/MediaSegments/IMediaSegmentManager.cs b/MediaBrowser.Controller/MediaSegments/IMediaSegmentManager.cs index 456977b88..6cd6474f7 100644 --- a/MediaBrowser.Controller/MediaSegments/IMediaSegmentManager.cs +++ b/MediaBrowser.Controller/MediaSegments/IMediaSegmentManager.cs @@ -7,7 +7,7 @@ using Jellyfin.Database.Implementations.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.MediaSegments; -namespace MediaBrowser.Controller; +namespace MediaBrowser.Controller.MediaSegments; /// <summary> /// Defines methods for interacting with media segments. @@ -46,6 +46,13 @@ public interface IMediaSegmentManager Task DeleteSegmentAsync(Guid segmentId); /// <summary> + /// Deletes all media segments of an item. + /// </summary> + /// <param name="itemId">The <see cref="BaseItem.Id"/> to delete all segments for.</param> + /// <returns>a task.</returns> + Task DeleteSegmentsAsync(Guid itemId); + + /// <summary> /// Obtains all segments associated with the itemId. /// </summary> /// <param name="itemId">The id of the <see cref="BaseItem"/>.</param> diff --git a/MediaBrowser.Controller/MediaSegments/IMediaSegmentProvider.cs b/MediaBrowser.Controller/MediaSegments/IMediaSegmentProvider.cs index 39bb58bef..5a6d15d78 100644 --- a/MediaBrowser.Controller/MediaSegments/IMediaSegmentProvider.cs +++ b/MediaBrowser.Controller/MediaSegments/IMediaSegmentProvider.cs @@ -1,13 +1,11 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities; using MediaBrowser.Model; using MediaBrowser.Model.MediaSegments; -namespace MediaBrowser.Controller; +namespace MediaBrowser.Controller.MediaSegments; /// <summary> /// Provides methods for Obtaining the Media Segments from an Item. diff --git a/MediaBrowser.Controller/Persistence/IKeyframeRepository.cs b/MediaBrowser.Controller/Persistence/IKeyframeRepository.cs index 4930434a7..2596784ba 100644 --- a/MediaBrowser.Controller/Persistence/IKeyframeRepository.cs +++ b/MediaBrowser.Controller/Persistence/IKeyframeRepository.cs @@ -26,4 +26,12 @@ public interface IKeyframeRepository /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task object representing the asynchronous operation.</returns> Task SaveKeyframeDataAsync(Guid itemId, KeyframeData data, CancellationToken cancellationToken); + + /// <summary> + /// Deletes the keyframe data. + /// </summary> + /// <param name="itemId">The item id.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>The task object representing the asynchronous operation.</returns> + Task DeleteKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken); } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 897652fcd..2eb647e26 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -537,7 +537,7 @@ namespace MediaBrowser.MediaEncoding.Encoder EnableRaisingEvents = true }; - _logger.LogInformation("Starting {ProcessFileName} with args {ProcessArgs}", _ffprobePath, args); + _logger.LogDebug("Starting {ProcessFileName} with args {ProcessArgs}", _ffprobePath, args); var memoryStream = new MemoryStream(); await using (memoryStream.ConfigureAwait(false)) @@ -637,7 +637,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } catch (Exception ex) { - _logger.LogError(ex, "I-frame image extraction failed, will attempt standard way. Input: {Arguments}", inputArgument); + _logger.LogWarning(ex, "I-frame image extraction failed, will attempt standard way. Input: {Arguments}", inputArgument); } } diff --git a/MediaBrowser.Providers/Books/AudioBookMetadataService.cs b/MediaBrowser.Providers/Books/AudioBookMetadataService.cs index 96e1165b6..79cd33aa0 100644 --- a/MediaBrowser.Providers/Books/AudioBookMetadataService.cs +++ b/MediaBrowser.Providers/Books/AudioBookMetadataService.cs @@ -1,50 +1,66 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public AudioBookMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<AudioBookMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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..6df8feab8 100644 --- a/MediaBrowser.Providers/Books/BookMetadataService.cs +++ b/MediaBrowser.Providers/Books/BookMetadataService.cs @@ -1,37 +1,53 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public BookMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<BookMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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..83f0f2485 100644 --- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs +++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs @@ -1,85 +1,101 @@ -#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.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public BoxSetMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<BoxSetMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - public BoxSetMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<BoxSetMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } + } - /// <inheritdoc /> - protected override bool EnableUpdatingGenresFromChildren => true; + /// <inheritdoc /> + protected override bool EnableUpdatingGenresFromChildren => true; - /// <inheritdoc /> - protected override bool EnableUpdatingOfficialRatingFromChildren => true; + /// <inheritdoc /> + protected override bool EnableUpdatingOfficialRatingFromChildren => true; - /// <inheritdoc /> - protected override bool EnableUpdatingStudiosFromChildren => true; + /// <inheritdoc /> + protected override bool EnableUpdatingStudiosFromChildren => true; - /// <inheritdoc /> - protected override bool EnableUpdatingPremiereDateFromChildren => true; + /// <inheritdoc /> + protected override bool EnableUpdatingPremiereDateFromChildren => true; - /// <inheritdoc /> - protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(BoxSet item) - { - return item.GetLinkedChildren(); - } + /// <inheritdoc /> + protected override IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(BoxSet item) + { + return item.GetLinkedChildren(); + } - /// <inheritdoc /> - protected override void MergeData(MetadataResult<BoxSet> source, MetadataResult<BoxSet> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + /// <inheritdoc /> + protected override void MergeData(MetadataResult<BoxSet> source, MetadataResult<BoxSet> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) + { + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); - var sourceItem = source.Item; - var targetItem = target.Item; + var sourceItem = source.Item; + var targetItem = target.Item; - if (mergeMetadataSettings) + if (mergeMetadataSettings) + { + if (replaceData || targetItem.LinkedChildren.Length == 0) + { + targetItem.LinkedChildren = sourceItem.LinkedChildren; + } + else { - if (replaceData || targetItem.LinkedChildren.Length == 0) - { - targetItem.LinkedChildren = sourceItem.LinkedChildren; - } - else - { - targetItem.LinkedChildren = sourceItem.LinkedChildren.Concat(targetItem.LinkedChildren).Distinct().ToArray(); - } + targetItem.LinkedChildren = sourceItem.LinkedChildren.Concat(targetItem.LinkedChildren).Distinct().ToArray(); } } + } - /// <inheritdoc /> - protected override ItemUpdateType BeforeSaveInternal(BoxSet item, bool isFullRefresh, ItemUpdateType updateType) - { - var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType); - - var libraryFolderIds = item.GetLibraryFolderIds(); + /// <inheritdoc /> + protected override ItemUpdateType BeforeSaveInternal(BoxSet item, bool isFullRefresh, ItemUpdateType updateType) + { + var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType); - var itemLibraryFolderIds = item.LibraryFolderIds; - if (itemLibraryFolderIds is null || !libraryFolderIds.SequenceEqual(itemLibraryFolderIds)) - { - item.LibraryFolderIds = libraryFolderIds; - updatedType |= ItemUpdateType.MetadataImport; - } + var libraryFolderIds = item.GetLibraryFolderIds(); - return updatedType; + var itemLibraryFolderIds = item.LibraryFolderIds; + if (itemLibraryFolderIds is null || !libraryFolderIds.SequenceEqual(itemLibraryFolderIds)) + { + item.LibraryFolderIds = libraryFolderIds; + updatedType |= ItemUpdateType.MetadataImport; } + + return updatedType; } } diff --git a/MediaBrowser.Providers/Channels/ChannelMetadataService.cs b/MediaBrowser.Providers/Channels/ChannelMetadataService.cs index 0267fa13f..a1f77e0a8 100644 --- a/MediaBrowser.Providers/Channels/ChannelMetadataService.cs +++ b/MediaBrowser.Providers/Channels/ChannelMetadataService.cs @@ -1,25 +1,41 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public ChannelMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<ChannelMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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..6407b1a61 100644 --- a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs +++ b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs @@ -1,25 +1,41 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public CollectionFolderMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<CollectionFolderMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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..7843f729d 100644 --- a/MediaBrowser.Providers/Folders/FolderMetadataService.cs +++ b/MediaBrowser.Providers/Folders/FolderMetadataService.cs @@ -1,29 +1,45 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public FolderMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<FolderMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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..834fba458 100644 --- a/MediaBrowser.Providers/Folders/UserViewMetadataService.cs +++ b/MediaBrowser.Providers/Folders/UserViewMetadataService.cs @@ -1,25 +1,41 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public UserViewMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<UserViewMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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..2a2a0bf50 100644 --- a/MediaBrowser.Providers/Genres/GenreMetadataService.cs +++ b/MediaBrowser.Providers/Genres/GenreMetadataService.cs @@ -1,25 +1,41 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public GenreMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<GenreMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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..9e4d91019 100644 --- a/MediaBrowser.Providers/LiveTv/LiveTvMetadataService.cs +++ b/MediaBrowser.Providers/LiveTv/LiveTvMetadataService.cs @@ -1,25 +1,41 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public LiveTvMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<LiveTvMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - public LiveTvMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<LiveTvMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } } } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 50bbf0974..c4d4e775a 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net.Http; using System.Threading; @@ -12,7 +13,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.MediaSegments; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; @@ -26,13 +29,24 @@ 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, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) { ServerConfigurationManager = serverConfigurationManager; Logger = logger; ProviderManager = providerManager; FileSystem = fileSystem; LibraryManager = libraryManager; + PathManager = pathManager; + KeyframeManager = keyframeManager; + MediaSegmentManager = mediaSegmentManager; ImageProvider = new ItemImageProvider(Logger, ProviderManager, FileSystem); } @@ -48,6 +62,12 @@ namespace MediaBrowser.Providers.Manager protected ILibraryManager LibraryManager { get; } + protected IPathManager PathManager { get; } + + protected IKeyframeManager KeyframeManager { get; } + + protected IMediaSegmentManager MediaSegmentManager { get; } + protected virtual bool EnableUpdatingPremiereDateFromChildren => false; protected virtual bool EnableUpdatingGenresFromChildren => false; @@ -303,6 +323,55 @@ 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); + var modificationDate = info.LastWriteTimeUtc; + var itemLastModifiedFileSystem = item.DateModified; + if (info.Exists && itemLastModifiedFileSystem != modificationDate) + { + Logger.LogDebug("File modification time changed from {Then} to {Now}: {Path}", itemLastModifiedFileSystem, modificationDate, itemPath); + + item.DateModified = modificationDate; + if (ServerConfigurationManager.GetMetadataConfiguration().UseFileCreationTimeForDateAdded) + { + item.DateCreated = info.CreationTimeUtc; + } + + var size = info.Length; + if (item is Video video) + { + var videoType = video.VideoType; + var sizeChanged = size != (video.Size ?? 0); + if (videoType == VideoType.BluRay || video.VideoType == VideoType.Dvd || sizeChanged) + { + if (sizeChanged) + { + item.Size = size; + Logger.LogDebug("File size changed from {Then} to {Now}: {Path}", video.Size, size, itemPath); + } + + var validPaths = PathManager.GetExtractedDataPaths(video).Where(Directory.Exists).ToList(); + if (validPaths.Count > 0) + { + Logger.LogInformation("File changed, pruning extracted data: {Path}", itemPath); + foreach (var path in validPaths) + { + Directory.Delete(path, true); + } + } + + KeyframeManager.DeleteKeyframeDataAsync(video.Id, CancellationToken.None).GetAwaiter().GetResult(); + MediaSegmentManager.DeleteSegmentsAsync(item.Id).GetAwaiter().GetResult(); + } + } + + updateType |= ItemUpdateType.MetadataImport; + } + } + return updateType; } @@ -1132,6 +1201,11 @@ namespace MediaBrowser.Providers.Manager target.DateCreated = source.DateCreated; } + if (replaceData || source.DateModified != default) + { + target.DateModified = source.DateModified; + } + if (replaceData || string.IsNullOrEmpty(target.PreferredMetadataCountryCode)) { target.PreferredMetadataCountryCode = source.PreferredMetadataCountryCode; diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 856f33b49..1a29548f2 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; diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs index 286ba0de0..4fd3ab973 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs @@ -133,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); diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 7947ba921..7c88a0e7d 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -214,10 +214,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); @@ -234,8 +238,8 @@ namespace MediaBrowser.Providers.MediaInfo } } - mediaAttachments = Array.Empty<MediaAttachment>(); - chapters = Array.Empty<ChapterInfo>(); + mediaAttachments = []; + chapters = []; } var libraryOptions = _libraryManager.GetLibraryOptions(video); @@ -400,7 +404,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()) { @@ -643,7 +647,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 ba6034ec1..8c673350d 100644 --- a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs @@ -130,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 && file.LastWriteTimeUtc != item.DateModified && file.Length != item.Size) { - _logger.LogDebug("Refreshing {ItemPath} due to date modified timestamp change.", path); + _logger.LogDebug("Refreshing {ItemPath} due to file system modification.", path); return true; } } diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 938f3cb32..1134baf92 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -14,7 +14,6 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Subtitles; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Tasks; diff --git a/MediaBrowser.Providers/Movies/MovieMetadataService.cs b/MediaBrowser.Providers/Movies/MovieMetadataService.cs index 8997ddc64..0779e17bd 100644 --- a/MediaBrowser.Providers/Movies/MovieMetadataService.cs +++ b/MediaBrowser.Providers/Movies/MovieMetadataService.cs @@ -1,40 +1,56 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public MovieMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<MovieMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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..bf8735ad4 100644 --- a/MediaBrowser.Providers/Movies/TrailerMetadataService.cs +++ b/MediaBrowser.Providers/Movies/TrailerMetadataService.cs @@ -1,42 +1,58 @@ -#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.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public TrailerMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<TrailerMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) + { + } + + /// <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 64b627367..cc6d7953d 100644 --- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs +++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs @@ -5,245 +5,252 @@ 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.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public AlbumMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<AlbumMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - /// <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 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); + updateType |= SetPeople(item); } - private ItemUpdateType SetAlbumArtistFromSongs(MusicAlbum item, IReadOnlyList<Audio> songs) - { - var updateType = ItemUpdateType.None; + return updateType; + } - var albumArtists = songs - .SelectMany(i => i.AlbumArtists) - .GroupBy(i => i) - .OrderByDescending(g => g.Count()) - .Select(g => g.Key) - .ToArray(); + private ItemUpdateType SetAlbumArtistFromSongs(MusicAlbum item, IReadOnlyList<Audio> songs) + { + var updateType = ItemUpdateType.None; - updateType |= SetProviderIdFromSongs(item, songs, MetadataProvider.MusicBrainzAlbumArtist); + var albumArtists = songs + .SelectMany(i => i.AlbumArtists) + .GroupBy(i => i) + .OrderByDescending(g => g.Count()) + .Select(g => g.Key) + .ToArray(); - if (!item.AlbumArtists.SequenceEqual(albumArtists, StringComparer.OrdinalIgnoreCase)) - { - item.AlbumArtists = albumArtists; - updateType |= ItemUpdateType.MetadataEdit; - } + updateType |= SetProviderIdFromSongs(item, songs, MetadataProvider.MusicBrainzAlbumArtist); - return updateType; + if (!item.AlbumArtists.SequenceEqual(albumArtists, StringComparer.OrdinalIgnoreCase)) + { + item.AlbumArtists = albumArtists; + updateType |= ItemUpdateType.MetadataEdit; } - private ItemUpdateType SetArtistsFromSongs(MusicAlbum item, IReadOnlyList<Audio> songs) - { - var updateType = ItemUpdateType.None; + return updateType; + } - var artists = songs - .SelectMany(i => i.Artists) - .GroupBy(i => i) - .OrderByDescending(g => g.Count()) - .Select(g => g.Key) - .ToArray(); + private ItemUpdateType SetArtistsFromSongs(MusicAlbum item, IReadOnlyList<Audio> songs) + { + var updateType = ItemUpdateType.None; - if (!item.Artists.SequenceEqual(artists, StringComparer.OrdinalIgnoreCase)) - { - item.Artists = artists; - updateType |= ItemUpdateType.MetadataEdit; - } + var artists = songs + .SelectMany(i => i.Artists) + .GroupBy(i => i) + .OrderByDescending(g => g.Count()) + .Select(g => g.Key) + .ToArray(); - return updateType; + if (!item.Artists.SequenceEqual(artists, StringComparer.OrdinalIgnoreCase)) + { + item.Artists = artists; + updateType |= ItemUpdateType.MetadataEdit; } - private ItemUpdateType SetAlbumFromSongs(MusicAlbum item, IReadOnlyList<Audio> songs) - { - var updateType = ItemUpdateType.None; + return updateType; + } - updateType |= SetProviderIdFromSongs(item, songs, MetadataProvider.MusicBrainzAlbum); - updateType |= SetProviderIdFromSongs(item, songs, MetadataProvider.MusicBrainzReleaseGroup); + private ItemUpdateType SetAlbumFromSongs(MusicAlbum item, IReadOnlyList<Audio> songs) + { + var updateType = ItemUpdateType.None; - return updateType; - } + updateType |= SetProviderIdFromSongs(item, songs, MetadataProvider.MusicBrainzAlbum); + updateType |= SetProviderIdFromSongs(item, songs, MetadataProvider.MusicBrainzReleaseGroup); - private ItemUpdateType SetProviderIdFromSongs(BaseItem item, IReadOnlyList<Audio> songs, MetadataProvider provider) + return updateType; + } + + private ItemUpdateType SetProviderIdFromSongs(BaseItem item, IReadOnlyList<Audio> songs, MetadataProvider provider) + { + var ids = songs + .Select(i => i.GetProviderId(provider)) + .GroupBy(i => i) + .OrderByDescending(g => g.Count()) + .Select(g => g.Key) + .ToArray(); + + var id = item.GetProviderId(provider); + if (ids.Length != 0) { - var ids = songs - .Select(i => i.GetProviderId(provider)) - .GroupBy(i => i) - .OrderByDescending(g => g.Count()) - .Select(g => g.Key) - .ToArray(); - - var id = item.GetProviderId(provider); - if (ids.Length != 0) + var firstId = ids[0]; + if (!string.IsNullOrEmpty(firstId) + && (string.IsNullOrEmpty(id) + || !id.Equals(firstId, StringComparison.OrdinalIgnoreCase))) { - var firstId = ids[0]; - if (!string.IsNullOrEmpty(firstId) - && (string.IsNullOrEmpty(id) - || !id.Equals(firstId, StringComparison.OrdinalIgnoreCase))) - { - item.SetProviderId(provider, firstId); - return ItemUpdateType.MetadataEdit; - } + item.SetProviderId(provider, firstId); + return ItemUpdateType.MetadataEdit; } - - return ItemUpdateType.None; } - private void SetProviderId(MusicAlbum sourceItem, MusicAlbum targetItem, MetadataProvider provider) + return ItemUpdateType.None; + } + + private void SetProviderId(MusicAlbum sourceItem, MusicAlbum targetItem, MetadataProvider provider) + { + var source = sourceItem.GetProviderId(provider); + var target = targetItem.GetProviderId(provider); + if (!string.IsNullOrEmpty(source) + && (string.IsNullOrEmpty(target) + || !target.Equals(source, StringComparison.Ordinal))) { - var source = sourceItem.GetProviderId(provider); - var target = targetItem.GetProviderId(provider); - if (!string.IsNullOrEmpty(source) - && (string.IsNullOrEmpty(target) - || !target.Equals(source, StringComparison.Ordinal))) - { - targetItem.SetProviderId(provider, source); - } + targetItem.SetProviderId(provider, source); } + } - private ItemUpdateType SetPeople(MusicAlbum item) + private ItemUpdateType SetPeople(MusicAlbum item) + { + var updateType = ItemUpdateType.None; + + 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) + PeopleHelper.AddPerson(people, new PersonInfo { - PeopleHelper.AddPerson(people, new PersonInfo - { - Name = albumArtist.Trim(), - Type = PersonKind.AlbumArtist - }); - } + Name = albumArtist.Trim(), + Type = PersonKind.AlbumArtist + }); + } - foreach (var artist in item.Artists) + foreach (var artist in item.Artists) + { + PeopleHelper.AddPerson(people, new PersonInfo { - PeopleHelper.AddPerson(people, new PersonInfo - { - Name = artist.Trim(), - Type = PersonKind.Artist - }); - } - - LibraryManager.UpdatePeople(item, people); - updateType |= ItemUpdateType.MetadataEdit; + Name = artist.Trim(), + Type = PersonKind.Artist + }); } - return updateType; + LibraryManager.UpdatePeople(item, people); + updateType |= ItemUpdateType.MetadataEdit; } - /// <inheritdoc /> - protected override void MergeData( - MetadataResult<MusicAlbum> source, - MetadataResult<MusicAlbum> target, - MetadataField[] lockedFields, - bool replaceData, - bool mergeMetadataSettings) - { - base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + return updateType; + } - var sourceItem = source.Item; - var targetItem = target.Item; + /// <inheritdoc /> + protected override void MergeData( + MetadataResult<MusicAlbum> source, + MetadataResult<MusicAlbum> target, + MetadataField[] lockedFields, + bool replaceData, + bool mergeMetadataSettings) + { + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); - if (replaceData || targetItem.Artists.Count == 0) - { - targetItem.Artists = sourceItem.Artists; - } - else - { - targetItem.Artists = targetItem.Artists.Concat(sourceItem.Artists).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); - } + var sourceItem = source.Item; + var targetItem = target.Item; - if (replaceData || string.IsNullOrEmpty(targetItem.GetProviderId(MetadataProvider.MusicBrainzAlbumArtist))) - { - SetProviderId(sourceItem, targetItem, MetadataProvider.MusicBrainzAlbumArtist); - } + 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.MusicBrainzAlbum))) - { - SetProviderId(sourceItem, targetItem, MetadataProvider.MusicBrainzAlbum); - } + if (replaceData || string.IsNullOrEmpty(targetItem.GetProviderId(MetadataProvider.MusicBrainzAlbumArtist))) + { + SetProviderId(sourceItem, targetItem, MetadataProvider.MusicBrainzAlbumArtist); + } - if (replaceData || string.IsNullOrEmpty(targetItem.GetProviderId(MetadataProvider.MusicBrainzReleaseGroup))) - { - SetProviderId(sourceItem, targetItem, MetadataProvider.MusicBrainzReleaseGroup); - } + 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); } } } diff --git a/MediaBrowser.Providers/Music/ArtistMetadataService.cs b/MediaBrowser.Providers/Music/ArtistMetadataService.cs index c47f9a500..3764893a6 100644 --- a/MediaBrowser.Providers/Music/ArtistMetadataService.cs +++ b/MediaBrowser.Providers/Music/ArtistMetadataService.cs @@ -1,43 +1,58 @@ -#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.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public ArtistMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<ArtistMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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 71962d952..b632f9a51 100644 --- a/MediaBrowser.Providers/Music/AudioMetadataService.cs +++ b/MediaBrowser.Providers/Music/AudioMetadataService.cs @@ -2,78 +2,85 @@ 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.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public AudioMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<AudioMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - /// <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(StringComparer.OrdinalIgnoreCase).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 4022bedc1..f4df1d78d 100644 --- a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs +++ b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs @@ -1,56 +1,72 @@ -#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.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public MusicVideoMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<MusicVideoMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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(StringComparer.OrdinalIgnoreCase).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..a27698e37 100644 --- a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs +++ b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs @@ -1,25 +1,41 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public MusicGenreMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<MusicGenreMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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..03a396489 100644 --- a/MediaBrowser.Providers/People/PersonMetadataService.cs +++ b/MediaBrowser.Providers/People/PersonMetadataService.cs @@ -1,25 +1,41 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public PersonMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<PersonMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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..dfa981adb 100644 --- a/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs +++ b/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs @@ -1,25 +1,41 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public PhotoAlbumMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<PhotoAlbumMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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..9043d7b11 100644 --- a/MediaBrowser.Providers/Photos/PhotoMetadataService.cs +++ b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs @@ -1,25 +1,41 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public PhotoMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<PhotoMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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/PlaylistMetadataService.cs b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs index 7be54453f..8c9f8bab7 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.MediaSegments; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; @@ -12,62 +12,78 @@ 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public PlaylistMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<PlaylistMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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/Studios/StudioMetadataService.cs b/MediaBrowser.Providers/Studios/StudioMetadataService.cs index df938325f..9d43b50d9 100644 --- a/MediaBrowser.Providers/Studios/StudioMetadataService.cs +++ b/MediaBrowser.Providers/Studios/StudioMetadataService.cs @@ -1,25 +1,41 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public StudioMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<StudioMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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..3921d6a9a 100644 --- a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs +++ b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs @@ -1,113 +1,115 @@ using System; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public EpisodeMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<EpisodeMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) + { + } + + /// <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..65aa4a963 100644 --- a/MediaBrowser.Providers/TV/SeasonMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeasonMetadataService.cs @@ -4,109 +4,116 @@ 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.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public SeasonMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<SeasonMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - /// <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..f0d21cf1a 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.MediaSegments; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; @@ -16,269 +18,274 @@ 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public SeriesMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<SeriesMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + ILocalizationManager localizationManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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/Videos/VideoMetadataService.cs b/MediaBrowser.Providers/Videos/VideoMetadataService.cs index caa6d6e1f..fb7899957 100644 --- a/MediaBrowser.Providers/Videos/VideoMetadataService.cs +++ b/MediaBrowser.Providers/Videos/VideoMetadataService.cs @@ -1,29 +1,45 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public VideoMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<VideoMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - 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..4d12fc32f 100644 --- a/MediaBrowser.Providers/Years/YearMetadataService.cs +++ b/MediaBrowser.Providers/Years/YearMetadataService.cs @@ -1,25 +1,41 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaSegments; 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="pathManager">Instance of the <see cref="IPathManager"/> interface.</param> + /// <param name="keyframeManager">Instance of the <see cref="IKeyframeManager"/> interface.</param> + /// <param name="mediaSegmentManager">Instance of the <see cref="IMediaSegmentManager"/> interface.</param> + public YearMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger<YearMetadataService> logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - public YearMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger<YearMetadataService> logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } } } diff --git a/src/Jellyfin.Drawing/ImageProcessor.cs b/src/Jellyfin.Drawing/ImageProcessor.cs index 7718f6c6a..e0380a99b 100644 --- a/src/Jellyfin.Drawing/ImageProcessor.cs +++ b/src/Jellyfin.Drawing/ImageProcessor.cs @@ -524,11 +524,11 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable /// <inheritdoc /> public void CreateImageCollage(ImageCollageOptions options, string? libraryName) { - _logger.LogInformation("Creating image collage and saving to {Path}", options.OutputPath); + _logger.LogDebug("Creating image collage and saving to {Path}", options.OutputPath); _imageEncoder.CreateImageCollage(options, libraryName); - _logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath); + _logger.LogDebug("Completed creation of image collage and saved to {Path}", options.OutputPath); } /// <inheritdoc /> diff --git a/src/Jellyfin.LiveTv/Channels/ChannelManager.cs b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs index 5addcd26e..74182171f 100644 --- a/src/Jellyfin.LiveTv/Channels/ChannelManager.cs +++ b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs @@ -445,12 +445,13 @@ namespace Jellyfin.LiveTv.Channels if (item is null) { + var info = Directory.CreateDirectory(path); item = new Channel { Name = channelInfo.Name, Id = id, - DateCreated = _fileSystem.GetCreationTimeUtc(path), - DateModified = _fileSystem.GetLastWriteTimeUtc(path) + DateCreated = info.CreationTimeUtc, + DateModified = info.LastWriteTimeUtc }; isNew = true; diff --git a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs index c227883b5..87e7a4b56 100644 --- a/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs +++ b/tests/Jellyfin.Providers.Tests/Manager/ProviderManagerTests.cs @@ -12,6 +12,7 @@ using MediaBrowser.Controller.Entities; 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; |
