Selaa lähdekoodia

Prune trickplay data on regenerate and scan (#14085)

Tim Eisele 2 päivää sitten
vanhempi
sitoutus
e1a5c16404

+ 77 - 28
Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs

@@ -7,6 +7,7 @@ using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using AsyncKeyedLock;
+using J2N.Collections.Generic.Extensions;
 using Jellyfin.Database.Implementations;
 using Jellyfin.Database.Implementations.Entities;
 using MediaBrowser.Common.Configuration;
@@ -80,7 +81,7 @@ public class TrickplayManager : ITrickplayManager
     public async Task MoveGeneratedTrickplayDataAsync(Video video, LibraryOptions libraryOptions, CancellationToken cancellationToken)
     {
         var options = _config.Configuration.TrickplayOptions;
-        if (!CanGenerateTrickplay(video, options.Interval, libraryOptions))
+        if (libraryOptions is null || !libraryOptions.EnableTrickplayImageExtraction || !CanGenerateTrickplay(video, options.Interval))
         {
             return;
         }
@@ -137,25 +138,84 @@ public class TrickplayManager : ITrickplayManager
     /// <inheritdoc />
     public async Task RefreshTrickplayDataAsync(Video video, bool replace, LibraryOptions libraryOptions, CancellationToken cancellationToken)
     {
-        _logger.LogDebug("Trickplay refresh for {ItemId} (replace existing: {Replace})", video.Id, replace);
-
         var options = _config.Configuration.TrickplayOptions;
-        if (options.Interval < 1000)
+        if (!CanGenerateTrickplay(video, options.Interval) || libraryOptions is null)
         {
-            _logger.LogWarning("Trickplay image interval {Interval} is too small, reset to the minimum valid value of 1000", options.Interval);
-            options.Interval = 1000;
+            return;
         }
 
-        foreach (var width in options.WidthResolutions)
+        var dbContext = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
+        await using (dbContext.ConfigureAwait(false))
         {
-            cancellationToken.ThrowIfCancellationRequested();
-            await RefreshTrickplayDataInternal(
-                video,
-                replace,
-                width,
-                options,
-                libraryOptions,
-                cancellationToken).ConfigureAwait(false);
+            var saveWithMedia = libraryOptions.SaveTrickplayWithMedia;
+            var trickplayDirectory = _pathManager.GetTrickplayDirectory(video, saveWithMedia);
+            if (!libraryOptions.EnableTrickplayImageExtraction || replace)
+            {
+                // Prune existing data
+                if (Directory.Exists(trickplayDirectory))
+                {
+                    try
+                    {
+                        Directory.Delete(trickplayDirectory, true);
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.LogWarning("Unable to clear trickplay directory: {Directory}: {Exception}", trickplayDirectory, ex);
+                    }
+                }
+
+                await dbContext.TrickplayInfos
+                        .Where(i => i.ItemId.Equals(video.Id))
+                        .ExecuteDeleteAsync(cancellationToken)
+                        .ConfigureAwait(false);
+
+                if (!replace)
+                {
+                    return;
+                }
+            }
+
+            _logger.LogDebug("Trickplay refresh for {ItemId} (replace existing: {Replace})", video.Id, replace);
+
+            if (options.Interval < 1000)
+            {
+                _logger.LogWarning("Trickplay image interval {Interval} is too small, reset to the minimum valid value of 1000", options.Interval);
+                options.Interval = 1000;
+            }
+
+            foreach (var width in options.WidthResolutions)
+            {
+                cancellationToken.ThrowIfCancellationRequested();
+                await RefreshTrickplayDataInternal(
+                    video,
+                    replace,
+                    width,
+                    options,
+                    saveWithMedia,
+                    cancellationToken).ConfigureAwait(false);
+            }
+
+            // Cleanup old trickplay files
+            var existingFolders = Directory.GetDirectories(trickplayDirectory).ToList();
+            var trickplayInfos = await dbContext.TrickplayInfos
+                    .AsNoTracking()
+                    .Where(i => i.ItemId.Equals(video.Id))
+                    .ToListAsync(cancellationToken)
+                    .ConfigureAwait(false);
+            var expectedFolders = trickplayInfos.Select(i => GetTrickplayDirectory(video, i.TileWidth, i.TileHeight, i.Width, saveWithMedia)).ToList();
+            var foldersToRemove = existingFolders.Except(expectedFolders);
+            foreach (var folder in foldersToRemove)
+            {
+                try
+                {
+                    _logger.LogWarning("Pruning trickplay files for {Item}", video.Path);
+                    Directory.Delete(folder, true);
+                }
+                catch (Exception ex)
+                {
+                    _logger.LogWarning("Unable to remove trickplay directory: {Directory}: {Exception}", folder, ex);
+                }
+            }
         }
     }
 
@@ -164,14 +224,9 @@ public class TrickplayManager : ITrickplayManager
         bool replace,
         int width,
         TrickplayOptions options,
-        LibraryOptions libraryOptions,
+        bool saveWithMedia,
         CancellationToken cancellationToken)
     {
-        if (!CanGenerateTrickplay(video, options.Interval, libraryOptions))
-        {
-            return;
-        }
-
         var imgTempDir = string.Empty;
 
         using (await _resourcePool.LockAsync(cancellationToken).ConfigureAwait(false))
@@ -215,7 +270,6 @@ public class TrickplayManager : ITrickplayManager
 
                 var tileWidth = options.TileWidth;
                 var tileHeight = options.TileHeight;
-                var saveWithMedia = libraryOptions is not null && libraryOptions.SaveTrickplayWithMedia;
                 var outputDir = new DirectoryInfo(GetTrickplayDirectory(video, tileWidth, tileHeight, actualWidth, saveWithMedia));
 
                 // Import existing trickplay tiles
@@ -402,7 +456,7 @@ public class TrickplayManager : ITrickplayManager
         return trickplayInfo;
     }
 
-    private bool CanGenerateTrickplay(Video video, int interval, LibraryOptions libraryOptions)
+    private bool CanGenerateTrickplay(Video video, int interval)
     {
         var videoType = video.VideoType;
         if (videoType == VideoType.Iso || videoType == VideoType.Dvd || videoType == VideoType.BluRay)
@@ -430,11 +484,6 @@ public class TrickplayManager : ITrickplayManager
             return false;
         }
 
-        if (libraryOptions is null || !libraryOptions.EnableTrickplayImageExtraction)
-        {
-            return false;
-        }
-
         // Can't extract images if there are no video streams
         return video.GetMediaStreams().Count > 0;
     }

+ 5 - 5
MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs

@@ -59,14 +59,14 @@ public class TrickplayImagesTask : IScheduledTask
     /// <inheritdoc />
     public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
     {
-        return new[]
-        {
+        return
+        [
             new TaskTriggerInfo
             {
                 Type = TaskTriggerInfoType.DailyTrigger,
                 TimeOfDayTicks = TimeSpan.FromHours(3).Ticks
             }
-        };
+        ];
     }
 
     /// <inheritdoc />
@@ -74,8 +74,8 @@ public class TrickplayImagesTask : IScheduledTask
     {
         var query = new InternalItemsQuery
         {
-            MediaTypes = new[] { MediaType.Video },
-            SourceTypes = new[] { SourceType.Library },
+            MediaTypes = [MediaType.Video],
+            SourceTypes = [SourceType.Library],
             IsVirtualItem = false,
             IsFolder = false,
             Recursive = true,