From d976f13970e034a24c1d0f69384501e31475a127 Mon Sep 17 00:00:00 2001 From: Tim Eisele Date: Mon, 5 May 2025 05:21:44 +0200 Subject: [PATCH] Recognize file changes and remove data on change (#13839) --- .../ApplicationHost.cs | 4 +- .../Collections/CollectionManager.cs | 8 +- .../Library/KeyframeManager.cs | 44 ++ .../Library/LibraryManager.cs | 122 +++-- .../Library/PathManager.cs | 14 + .../Library/ResolverHelper.cs | 20 +- .../Playlists/PlaylistManager.cs | 6 +- .../Tasks/MediaSegmentExtractionTask.cs | 2 +- .../Controllers/MediaSegmentsController.cs | 2 +- .../Item/KeyframeRepository.cs | 8 + .../MediaSegments/MediaSegmentManager.cs | 9 +- .../Routines/MigrateKeyframeData.cs | 5 + .../Migrations/Routines/MoveExtractedFiles.cs | 5 + .../Routines/RefreshInternalDateModified.cs | 131 +++++ MediaBrowser.Controller/Entities/BaseItem.cs | 40 +- MediaBrowser.Controller/IO/IPathManager.cs | 10 +- .../Library/IKeyframeManager.cs | 37 ++ .../Library/ILibraryManager.cs | 8 +- .../MediaSegments/IMediaSegmentManager.cs | 9 +- .../MediaSegments/IMediaSegmentProvider.cs | 6 +- .../Persistence/IKeyframeRepository.cs | 8 + .../Encoder/MediaEncoder.cs | 4 +- .../Books/AudioBookMetadataService.cs | 80 +-- .../Books/BookMetadataService.cs | 58 ++- .../BoxSets/BoxSetMetadataService.cs | 142 ++--- .../Channels/ChannelMetadataService.cs | 42 +- .../CollectionFolderMetadataService.cs | 42 +- .../Folders/FolderMetadataService.cs | 52 +- .../Folders/UserViewMetadataService.cs | 42 +- .../Genres/GenreMetadataService.cs | 42 +- .../LiveTv/LiveTvMetadataService.cs | 42 +- .../Manager/MetadataService.cs | 76 ++- .../Manager/ProviderManager.cs | 3 +- .../MediaInfo/AudioFileProber.cs | 1 - .../MediaInfo/FFProbeVideoInfo.cs | 16 +- .../MediaInfo/ProbeProvider.cs | 4 +- .../MediaInfo/SubtitleScheduledTask.cs | 1 - .../Movies/MovieMetadataService.cs | 66 ++- .../Movies/TrailerMetadataService.cs | 64 ++- .../Music/AlbumMetadataService.cs | 405 +++++++------- .../Music/ArtistMetadataService.cs | 69 ++- .../Music/AudioMetadataService.cs | 123 ++--- .../Music/MusicVideoMetadataService.cs | 88 ++-- .../MusicGenres/MusicGenreMetadataService.cs | 42 +- .../People/PersonMetadataService.cs | 42 +- .../Photos/PhotoAlbumMetadataService.cs | 42 +- .../Photos/PhotoMetadataService.cs | 42 +- .../Playlists/PlaylistMetadataService.cs | 120 +++-- .../Studios/StudioMetadataService.cs | 42 +- .../TV/EpisodeMetadataService.cs | 168 +++--- .../TV/SeasonMetadataService.cs | 171 +++--- .../TV/SeriesMetadataService.cs | 493 +++++++++--------- .../Videos/VideoMetadataService.cs | 52 +- .../Years/YearMetadataService.cs | 42 +- src/Jellyfin.Drawing/ImageProcessor.cs | 4 +- .../Channels/ChannelManager.cs | 5 +- .../Manager/ProviderManagerTests.cs | 1 + 57 files changed, 2008 insertions(+), 1218 deletions(-) create mode 100644 Emby.Server.Implementations/Library/KeyframeManager.cs create mode 100644 Jellyfin.Server/Migrations/Routines/RefreshInternalDateModified.cs create mode 100644 MediaBrowser.Controller/Library/IKeyframeManager.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index fa6e9ff977..987ce8b843 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(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 60f515f24d..0eb387ffdd 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 0000000000..18f4ce0479 --- /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; + +/// +/// Manager for Keyframe data. +/// +public class KeyframeManager : IKeyframeManager +{ + private readonly IKeyframeRepository _repository; + + /// + /// Initializes a new instance of the class. + /// + /// The keyframe repository. + public KeyframeManager(IKeyframeRepository repository) + { + _repository = repository; + } + + /// + public IReadOnlyList GetKeyframeData(Guid itemId) + { + return _repository.GetKeyframeData(itemId); + } + + /// + public async Task SaveKeyframeDataAsync(Guid itemId, KeyframeData data, CancellationToken cancellationToken) + { + await _repository.SaveKeyframeDataAsync(itemId, data, cancellationToken).ConfigureAwait(false); + } + + /// + 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 51f3307465..1fdd80bd8a 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. /// /// The postscan tasks. - private ILibraryPostScanTask[] PostscanTasks { get; set; } = []; + private ILibraryPostScanTask[] PostScanTasks { get; set; } = []; /// /// Gets or sets the intro providers. @@ -245,20 +245,20 @@ namespace Emby.Server.Implementations.Library /// The resolvers. /// The intro providers. /// The item comparers. - /// The post scan tasks. + /// The post scan tasks. public void AddParts( IEnumerable rules, IEnumerable resolvers, IEnumerable introProviders, IEnumerable itemComparers, - IEnumerable postscanTasks) + IEnumerable postScanTasks) { EntityResolutionIgnoreRules = rules.ToArray(); EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray(); MultiItemResolvers = EntityResolvers.OfType().ToArray(); IntroProviders = introProviders.ToArray(); Comparers = itemComparers.ToArray(); - PostscanTasks = postscanTasks.ToArray(); + PostScanTasks = postScanTasks.ToArray(); } /// @@ -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 GetMetadataPaths(BaseItem item, IEnumerable 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() @@ -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 /// Task. private async Task RunPostScanTasks(IProgress 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 file in files) { if (Enum.TryParse(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 /// public void CreateItem(BaseItem item, BaseItem? parent) { - CreateItems(new[] { item }, parent, CancellationToken.None); + CreateItems([item], parent, CancellationToken.None); } /// @@ -2054,7 +2086,7 @@ namespace Emby.Server.Implementations.Library /// 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(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 853d85e5e5..a9b7a1274b 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); } + + /// + public IReadOnlyList 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 c9e3a4daf8..ab6bc49073 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 98a43b6c98..1ce363de5c 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 b8c944d3d9..c3f17c2aeb 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 e30e2b54e4..2a91a84557 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 a2267700fb..93c6f472e2 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); } + + /// + 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 d6eeafacc3..5a2032c1f7 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; @@ -139,6 +139,13 @@ public class MediaSegmentManager : IMediaSegmentManager await db.MediaSegments.Where(e => e.Id.Equals(segmentId)).ExecuteDeleteAsync().ConfigureAwait(false); } + /// + 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); + } + /// public async Task> GetSegmentsAsync(Guid itemId, IEnumerable? typeFilter, bool filterByProvider = true) { diff --git a/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs b/Jellyfin.Server/Migrations/Routines/MigrateKeyframeData.cs index c5bc702789..1ee4c41c37 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 9031f2fdcf..c6471b24c8 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 0000000000..9a95757a8c --- /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; + +/// +/// Migration to re-read creation dates for library items with internal metadata paths. +/// +[JellyfinMigration("2025-04-20T23:00:00", nameof(RefreshInternalDateModified), "32E762EB-4918-45CE-A44C-C801F66B877D", RunMigrationOnSetup = false)] +public class RefreshInternalDateModified : IDatabaseMigrationRoutine +{ + private readonly ILogger _logger; + private readonly IDbContextFactory _dbProvider; + private readonly IFileSystem _fileSystem; + private readonly IServerApplicationHost _applicationHost; + private readonly bool _useFileCreationTimeForDateAdded; + + private IReadOnlyList _internalTypes = [ + typeof(Genre).FullName!, + typeof(MusicGenre).FullName!, + typeof(MusicArtist).FullName!, + typeof(People).FullName!, + typeof(Studio).FullName! + ]; + + private IReadOnlyList _internalPaths; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// The logger. + /// Instance of the interface. + public RefreshInternalDateModified( + IServerApplicationHost applicationHost, + IServerApplicationPaths applicationPaths, + IServerConfigurationManager configurationManager, + IDbContextFactory dbProvider, + ILogger 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; + } + + /// + 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 b90ec8222d..16fde9440a 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 } /// - /// Overrides the base implementation to refresh metadata for local trailers. + /// The base implementation to refresh metadata. /// /// The options. /// The cancellation token. @@ -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 RefreshExtras(BaseItem item, MetadataRefreshOptions options, IReadOnlyList 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 4e4eb514e6..eb67437545 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; /// -/// Interface ITrickplayManager. +/// Interface IPathManager. /// public interface IPathManager { @@ -60,4 +61,11 @@ public interface IPathManager /// The chapter position. /// The chapter images data path. public string GetChapterImagePath(BaseItem item, long chapterPositionTicks); + + /// + /// Gets the paths of extracted data folders. + /// + /// The base item. + /// The absolute paths. + public IReadOnlyList GetExtractedDataPaths(BaseItem item); } diff --git a/MediaBrowser.Controller/Library/IKeyframeManager.cs b/MediaBrowser.Controller/Library/IKeyframeManager.cs new file mode 100644 index 0000000000..b0155efdd3 --- /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; + +/// +/// Interface IKeyframeManager. +/// +public interface IKeyframeManager +{ + /// + /// Gets the keyframe data. + /// + /// The item id. + /// The keyframe data. + IReadOnlyList GetKeyframeData(Guid itemId); + + /// + /// Saves the keyframe data. + /// + /// The item id. + /// The keyframe data. + /// The cancellation token. + /// The task object representing the asynchronous operation. + Task SaveKeyframeDataAsync(Guid itemId, KeyframeData data, CancellationToken cancellationToken); + + /// + /// Deletes the keyframe data. + /// + /// The item id. + /// The cancellation token. + /// The task object representing the asynchronous operation. + Task DeleteKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken); +} diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index df90f546cd..98ed15eb6c 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -220,13 +220,13 @@ namespace MediaBrowser.Controller.Library /// The resolvers. /// The intro providers. /// The item comparers. - /// The postscan tasks. + /// The post scan tasks. void AddParts( IEnumerable rules, IEnumerable resolvers, IEnumerable introProviders, IEnumerable itemComparers, - IEnumerable postscanTasks); + IEnumerable postScanTasks); /// /// Sorts the specified items. @@ -593,11 +593,11 @@ namespace MediaBrowser.Controller.Library QueryResult GetItemsResult(InternalItemsQuery query); /// - /// Ignores the file. + /// Checks if the file is ignored. /// /// The file. /// The parent. - /// true if XXXX, false otherwise. + /// true if ignored, false otherwise. 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 456977b88e..6cd6474f7b 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; /// /// Defines methods for interacting with media segments. @@ -45,6 +45,13 @@ public interface IMediaSegmentManager /// a task. Task DeleteSegmentAsync(Guid segmentId); + /// + /// Deletes all media segments of an item. + /// + /// The to delete all segments for. + /// a task. + Task DeleteSegmentsAsync(Guid itemId); + /// /// Obtains all segments associated with the itemId. /// diff --git a/MediaBrowser.Controller/MediaSegments/IMediaSegmentProvider.cs b/MediaBrowser.Controller/MediaSegments/IMediaSegmentProvider.cs index 39bb58bef2..5a6d15d781 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; /// /// 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 4930434a7f..2596784ba4 100644 --- a/MediaBrowser.Controller/Persistence/IKeyframeRepository.cs +++ b/MediaBrowser.Controller/Persistence/IKeyframeRepository.cs @@ -26,4 +26,12 @@ public interface IKeyframeRepository /// The cancellation token. /// The task object representing the asynchronous operation. Task SaveKeyframeDataAsync(Guid itemId, KeyframeData data, CancellationToken cancellationToken); + + /// + /// Deletes the keyframe data. + /// + /// The item id. + /// The cancellation token. + /// The task object representing the asynchronous operation. + Task DeleteKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken); } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 897652fcd6..2eb647e264 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 96e1165b69..79cd33aa06 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; + +/// +/// Service to manage audiobook metadata. +/// +public class AudioBookMetadataService : MetadataService { - public class AudioBookMetadataService : MetadataService + /// + /// Initializes a new instance of the class. + /// + /// Instance of the . + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public AudioBookMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger 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 logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) + } + + /// + protected override void MergeData( + MetadataResult source, + MetadataResult target, + MetadataField[] lockedFields, + bool replaceData, + bool mergeMetadataSettings) + { + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + + var sourceItem = source.Item; + var targetItem = target.Item; + + if (replaceData || targetItem.Artists.Count == 0) { + targetItem.Artists = sourceItem.Artists; } - /// - protected override void MergeData( - MetadataResult source, - MetadataResult target, - MetadataField[] lockedFields, - bool replaceData, - bool mergeMetadataSettings) + if (replaceData || string.IsNullOrEmpty(targetItem.Album)) { - base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); - - var sourceItem = source.Item; - var targetItem = target.Item; - - if (replaceData || targetItem.Artists.Count == 0) - { - targetItem.Artists = sourceItem.Artists; - } - - if (replaceData || string.IsNullOrEmpty(targetItem.Album)) - { - targetItem.Album = sourceItem.Album; - } + targetItem.Album = sourceItem.Album; } } } diff --git a/MediaBrowser.Providers/Books/BookMetadataService.cs b/MediaBrowser.Providers/Books/BookMetadataService.cs index 50b9922c69..6df8feab80 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; + +/// +/// Service to manage book metadata. +/// +public class BookMetadataService : MetadataService { - public class BookMetadataService : MetadataService + /// + /// Initializes a new instance of the class. + /// + /// Instance of the . + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public BookMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger 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 logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } + } - /// - protected override void MergeData(MetadataResult source, MetadataResult target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + /// + protected override void MergeData(MetadataResult source, MetadataResult 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 b51ab4c08e..83f0f2485b 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; + +/// +/// Service to manage boxset metadata. +/// +public class BoxSetMetadataService : MetadataService { - public class BoxSetMetadataService : MetadataService + /// + /// Initializes a new instance of the class. + /// + /// Instance of the . + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public BoxSetMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger 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 logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) + } + + /// + protected override bool EnableUpdatingGenresFromChildren => true; + + /// + protected override bool EnableUpdatingOfficialRatingFromChildren => true; + + /// + protected override bool EnableUpdatingStudiosFromChildren => true; + + /// + protected override bool EnableUpdatingPremiereDateFromChildren => true; + + /// + protected override IReadOnlyList GetChildrenForMetadataUpdates(BoxSet item) + { + return item.GetLinkedChildren(); + } + + /// + protected override void MergeData(MetadataResult source, MetadataResult target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) + { + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + + var sourceItem = source.Item; + var targetItem = target.Item; + + if (mergeMetadataSettings) { - } - - /// - protected override bool EnableUpdatingGenresFromChildren => true; - - /// - protected override bool EnableUpdatingOfficialRatingFromChildren => true; - - /// - protected override bool EnableUpdatingStudiosFromChildren => true; - - /// - protected override bool EnableUpdatingPremiereDateFromChildren => true; - - /// - protected override IReadOnlyList GetChildrenForMetadataUpdates(BoxSet item) - { - return item.GetLinkedChildren(); - } - - /// - protected override void MergeData(MetadataResult source, MetadataResult target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); - - var sourceItem = source.Item; - var targetItem = target.Item; - - if (mergeMetadataSettings) + if (replaceData || targetItem.LinkedChildren.Length == 0) { - if (replaceData || targetItem.LinkedChildren.Length == 0) - { - targetItem.LinkedChildren = sourceItem.LinkedChildren; - } - else - { - targetItem.LinkedChildren = sourceItem.LinkedChildren.Concat(targetItem.LinkedChildren).Distinct().ToArray(); - } + targetItem.LinkedChildren = sourceItem.LinkedChildren; } - } - - /// - protected override ItemUpdateType BeforeSaveInternal(BoxSet item, bool isFullRefresh, ItemUpdateType updateType) - { - var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType); - - var libraryFolderIds = item.GetLibraryFolderIds(); - - var itemLibraryFolderIds = item.LibraryFolderIds; - if (itemLibraryFolderIds is null || !libraryFolderIds.SequenceEqual(itemLibraryFolderIds)) + else { - item.LibraryFolderIds = libraryFolderIds; - updatedType |= ItemUpdateType.MetadataImport; + targetItem.LinkedChildren = sourceItem.LinkedChildren.Concat(targetItem.LinkedChildren).Distinct().ToArray(); } - - return updatedType; } } + + /// + protected override ItemUpdateType BeforeSaveInternal(BoxSet item, bool isFullRefresh, ItemUpdateType updateType) + { + var updatedType = base.BeforeSaveInternal(item, isFullRefresh, updateType); + + var libraryFolderIds = item.GetLibraryFolderIds(); + + 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 0267fa13f7..a1f77e0a8d 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; + +/// +/// Service to manage channel metadata. +/// +public class ChannelMetadataService : MetadataService { - public class ChannelMetadataService : MetadataService + /// + /// Initializes a new instance of the class. + /// + /// Instance of the . + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public ChannelMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger 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 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 0629824d30..6407b1a61e 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; + +/// +/// Service to manage collection folder metadata. +/// +public class CollectionFolderMetadataService : MetadataService { - public class CollectionFolderMetadataService : MetadataService + /// + /// Initializes a new instance of the class. + /// + /// Instance of the . + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public CollectionFolderMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger 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 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 79d52991a1..7843f729d1 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 -{ - public class FolderMetadataService : MetadataService - { - public FolderMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } +namespace MediaBrowser.Providers.Folders; - /// - // Make sure the type-specific services get picked first - public override int Order => 10; +/// +/// Service to manage folder metadata. +/// +public class FolderMetadataService : MetadataService +{ + /// + /// Initializes a new instance of the class. + /// + /// Instance of the . + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public FolderMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) + { } + + /// + // 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 79c5597e54..834fba4587 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; + +/// +/// Service to manage user view metadata. +/// +public class UserViewMetadataService : MetadataService { - public class UserViewMetadataService : MetadataService + /// + /// Initializes a new instance of the class. + /// + /// Instance of the . + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public UserViewMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger 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 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 4d10d89874..2a2a0bf509 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; + +/// +/// Service to manage genre metadata. +/// +public class GenreMetadataService : MetadataService { - public class GenreMetadataService : MetadataService + /// + /// Initializes a new instance of the class. + /// + /// Instance of the . + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public GenreMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger 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 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 c94d365301..9e4d910198 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; + +/// +/// Service to manage live TV metadata. +/// +public class LiveTvMetadataService : MetadataService { - public class LiveTvMetadataService : MetadataService + /// + /// Initializes a new instance of the class. + /// + /// Instance of the . + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public LiveTvMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger 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 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 50bbf0974a..c4d4e775ac 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, new() where TIdType : ItemLookupInfo, new() { - protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger> logger, IProviderManager providerManager, IFileSystem fileSystem, ILibraryManager libraryManager) + protected MetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger> 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 856f33b497..1a29548f24 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 286ba0de0d..4fd3ab9730 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(mediaInfo.MediaStreams); diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 7947ba9218..7c88a0e7d4 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(); + 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(); - chapters = Array.Empty(); + 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(); + 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(); + return []; } int chapterCount = (int)(runtime / dummyChapterDuration); diff --git a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs index ba6034ec12..8c673350da 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 938f3cb327..1134baf92d 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 8997ddc644..0779e17bda 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; + +/// +/// Service to manage movie metadata. +/// +public class MovieMetadataService : MetadataService { - public class MovieMetadataService : MetadataService + /// + /// Initializes a new instance of the class. + /// + /// Instance of the . + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public MovieMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger 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 logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) + } + + /// + protected override void MergeData(MetadataResult source, MetadataResult target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) + { + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + + var sourceItem = source.Item; + var targetItem = target.Item; + + if (replaceData || string.IsNullOrEmpty(targetItem.CollectionName)) { - } - - /// - protected override void MergeData(MetadataResult source, MetadataResult 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.CollectionName)) - { - targetItem.CollectionName = sourceItem.CollectionName; - } + targetItem.CollectionName = sourceItem.CollectionName; } } } diff --git a/MediaBrowser.Providers/Movies/TrailerMetadataService.cs b/MediaBrowser.Providers/Movies/TrailerMetadataService.cs index e77d2fa8a3..bf8735ad4b 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; + +/// +/// Service to manage trailer metadata. +/// +public class TrailerMetadataService : MetadataService { - public class TrailerMetadataService : MetadataService + /// + /// Initializes a new instance of the class. + /// + /// Instance of the . + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public TrailerMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - public TrailerMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) + } + + /// + protected override void MergeData(MetadataResult source, MetadataResult target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) + { + base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings); + + if (replaceData || target.Item.TrailerTypes.Length == 0) { + target.Item.TrailerTypes = source.Item.TrailerTypes; } - - /// - protected override void MergeData(MetadataResult source, MetadataResult 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 64b6273670..cc6d7953d5 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; + +/// +/// The album metadata service. +/// +public class AlbumMetadataService : MetadataService { /// - /// The album metadata service. + /// Initializes a new instance of the class. /// - public class AlbumMetadataService : MetadataService + /// Instance of the . + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public AlbumMetadataService( + IServerConfigurationManager serverConfigurationManager, + ILogger logger, + IProviderManager providerManager, + IFileSystem fileSystem, + ILibraryManager libraryManager, + IPathManager pathManager, + IKeyframeManager keyframeManager, + IMediaSegmentManager mediaSegmentManager) + : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager, pathManager, keyframeManager, mediaSegmentManager) { - /// - /// Initializes a new instance of the class. - /// - /// Instance of the . - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - public AlbumMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) + } + + /// + protected override bool EnableUpdatingPremiereDateFromChildren => true; + + /// + protected override bool EnableUpdatingGenresFromChildren => true; + + /// + protected override bool EnableUpdatingStudiosFromChildren => true; + + /// + protected override IReadOnlyList GetChildrenForMetadataUpdates(MusicAlbum item) + => item.GetRecursiveChildren(i => i is Audio); + + /// + protected override ItemUpdateType UpdateMetadataFromChildren(MusicAlbum item, IReadOnlyList children, bool isFullRefresh, ItemUpdateType currentUpdateType) + { + var updateType = base.UpdateMetadataFromChildren(item, children, isFullRefresh, currentUpdateType); + + // don't update user-changeable metadata for locked items + if (item.IsLocked) { + return updateType; } - /// - protected override bool EnableUpdatingPremiereDateFromChildren => true; - - /// - protected override bool EnableUpdatingGenresFromChildren => true; - - /// - protected override bool EnableUpdatingStudiosFromChildren => true; - - /// - protected override IReadOnlyList GetChildrenForMetadataUpdates(MusicAlbum item) - => item.GetRecursiveChildren(i => i is Audio); - - /// - protected override ItemUpdateType UpdateMetadataFromChildren(MusicAlbum item, IReadOnlyList children, bool isFullRefresh, ItemUpdateType currentUpdateType) + if (isFullRefresh || currentUpdateType > ItemUpdateType.None) { - var updateType = base.UpdateMetadataFromChildren(item, children, isFullRefresh, currentUpdateType); - - // don't update user-changeable metadata for locked items - if (item.IsLocked) + 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; - } - } - - var songs = children.Cast