aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs')
-rw-r--r--Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs169
1 files changed, 111 insertions, 58 deletions
diff --git a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs
index bf39f13a7..6f2d2a107 100644
--- a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs
+++ b/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;
@@ -14,7 +15,6 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.IO;
-using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Trickplay;
using MediaBrowser.Model.Configuration;
@@ -34,7 +34,6 @@ public class TrickplayManager : ITrickplayManager
private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem;
private readonly EncodingHelper _encodingHelper;
- private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _config;
private readonly IImageEncoder _imageEncoder;
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
@@ -51,7 +50,6 @@ public class TrickplayManager : ITrickplayManager
/// <param name="mediaEncoder">The media encoder.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="encodingHelper">The encoding helper.</param>
- /// <param name="libraryManager">The library manager.</param>
/// <param name="config">The server configuration manager.</param>
/// <param name="imageEncoder">The image encoder.</param>
/// <param name="dbProvider">The database provider.</param>
@@ -62,7 +60,6 @@ public class TrickplayManager : ITrickplayManager
IMediaEncoder mediaEncoder,
IFileSystem fileSystem,
EncodingHelper encodingHelper,
- ILibraryManager libraryManager,
IServerConfigurationManager config,
IImageEncoder imageEncoder,
IDbContextFactory<JellyfinDbContext> dbProvider,
@@ -73,7 +70,6 @@ public class TrickplayManager : ITrickplayManager
_mediaEncoder = mediaEncoder;
_fileSystem = fileSystem;
_encodingHelper = encodingHelper;
- _libraryManager = libraryManager;
_config = config;
_imageEncoder = imageEncoder;
_dbProvider = dbProvider;
@@ -82,10 +78,10 @@ public class TrickplayManager : ITrickplayManager
}
/// <inheritdoc />
- public async Task MoveGeneratedTrickplayDataAsync(Video video, LibraryOptions? libraryOptions, CancellationToken cancellationToken)
+ public async Task MoveGeneratedTrickplayDataAsync(Video video, LibraryOptions libraryOptions, CancellationToken cancellationToken)
{
var options = _config.Configuration.TrickplayOptions;
- if (!CanGenerateTrickplay(video, options.Interval))
+ if (libraryOptions is null || !libraryOptions.EnableTrickplayImageExtraction || !CanGenerateTrickplay(video, options.Interval))
{
return;
}
@@ -97,28 +93,28 @@ public class TrickplayManager : ITrickplayManager
var existingResolution = resolution.Key;
var tileWidth = resolution.Value.TileWidth;
var tileHeight = resolution.Value.TileHeight;
- var shouldBeSavedWithMedia = libraryOptions is null ? false : libraryOptions.SaveTrickplayWithMedia;
- var localOutputDir = GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, false);
- var mediaOutputDir = GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, true);
- if (shouldBeSavedWithMedia && Directory.Exists(localOutputDir))
+ var shouldBeSavedWithMedia = libraryOptions is not null && libraryOptions.SaveTrickplayWithMedia;
+ var localOutputDir = new DirectoryInfo(GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, false));
+ var mediaOutputDir = new DirectoryInfo(GetTrickplayDirectory(video, tileWidth, tileHeight, existingResolution, true));
+ if (shouldBeSavedWithMedia && localOutputDir.Exists)
{
- var localDirFiles = Directory.GetFiles(localOutputDir);
- var mediaDirExists = Directory.Exists(mediaOutputDir);
- if (localDirFiles.Length > 0 && ((mediaDirExists && Directory.GetFiles(mediaOutputDir).Length == 0) || !mediaDirExists))
+ var localDirFiles = localOutputDir.EnumerateFiles();
+ var mediaDirExists = mediaOutputDir.Exists;
+ if (localDirFiles.Any() && ((mediaDirExists && mediaOutputDir.EnumerateFiles().Any()) || !mediaDirExists))
{
// Move images from local dir to media dir
- MoveContent(localOutputDir, mediaOutputDir);
+ MoveContent(localOutputDir.FullName, mediaOutputDir.FullName);
_logger.LogInformation("Moved trickplay images for {ItemName} to {Location}", video.Name, mediaOutputDir);
}
}
- else if (!shouldBeSavedWithMedia && Directory.Exists(mediaOutputDir))
+ else if (!shouldBeSavedWithMedia && mediaOutputDir.Exists)
{
- var mediaDirFiles = Directory.GetFiles(mediaOutputDir);
- var localDirExists = Directory.Exists(localOutputDir);
- if (mediaDirFiles.Length > 0 && ((localDirExists && Directory.GetFiles(localOutputDir).Length == 0) || !localDirExists))
+ var mediaDirFiles = mediaOutputDir.EnumerateFiles();
+ var localDirExists = localOutputDir.Exists;
+ if (mediaDirFiles.Any() && ((localDirExists && localOutputDir.EnumerateFiles().Any()) || !localDirExists))
{
// Move images from media dir to local dir
- MoveContent(mediaOutputDir, localOutputDir);
+ MoveContent(mediaOutputDir.FullName, localOutputDir.FullName);
_logger.LogInformation("Moved trickplay images for {ItemName} to {Location}", video.Name, localOutputDir);
}
}
@@ -131,36 +127,98 @@ public class TrickplayManager : ITrickplayManager
var parent = Directory.GetParent(sourceFolder);
if (parent is not null)
{
- var parentContent = Directory.GetDirectories(parent.FullName);
- if (parentContent.Length == 0)
+ var parentContent = parent.EnumerateDirectories();
+ if (!parentContent.Any())
{
- Directory.Delete(parent.FullName);
+ parent.Delete();
}
}
}
/// <inheritdoc />
- public async Task RefreshTrickplayDataAsync(Video video, bool replace, LibraryOptions? libraryOptions, CancellationToken cancellationToken)
+ 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
+ if (Directory.Exists(trickplayDirectory))
+ {
+ 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);
+ }
+ }
+ }
}
}
@@ -169,14 +227,9 @@ public class TrickplayManager : ITrickplayManager
bool replace,
int width,
TrickplayOptions options,
- LibraryOptions? libraryOptions,
+ bool saveWithMedia,
CancellationToken cancellationToken)
{
- if (!CanGenerateTrickplay(video, options.Interval))
- {
- return;
- }
-
var imgTempDir = string.Empty;
using (await _resourcePool.LockAsync(cancellationToken).ConfigureAwait(false))
@@ -220,13 +273,12 @@ public class TrickplayManager : ITrickplayManager
var tileWidth = options.TileWidth;
var tileHeight = options.TileHeight;
- var saveWithMedia = libraryOptions is null ? false : libraryOptions.SaveTrickplayWithMedia;
- var outputDir = GetTrickplayDirectory(video, tileWidth, tileHeight, actualWidth, saveWithMedia);
+ var outputDir = new DirectoryInfo(GetTrickplayDirectory(video, tileWidth, tileHeight, actualWidth, saveWithMedia));
// Import existing trickplay tiles
- if (!replace && Directory.Exists(outputDir))
+ if (!replace && outputDir.Exists)
{
- var existingFiles = Directory.GetFiles(outputDir);
+ var existingFiles = outputDir.GetFiles();
if (existingFiles.Length > 0)
{
var hasTrickplayResolution = await HasTrickplayResolutionAsync(video.Id, actualWidth).ConfigureAwait(false);
@@ -251,9 +303,9 @@ public class TrickplayManager : ITrickplayManager
foreach (var tile in existingFiles)
{
- var image = _imageEncoder.GetImageSize(tile);
+ var image = _imageEncoder.GetImageSize(tile.FullName);
localTrickplayInfo.Height = Math.Max(localTrickplayInfo.Height, (int)Math.Ceiling((double)image.Height / localTrickplayInfo.TileHeight));
- var bitrate = (int)Math.Ceiling((decimal)new FileInfo(tile).Length * 8 / localTrickplayInfo.TileWidth / localTrickplayInfo.TileHeight / (localTrickplayInfo.Interval / 1000));
+ var bitrate = (int)Math.Ceiling((decimal)tile.Length * 8 / localTrickplayInfo.TileWidth / localTrickplayInfo.TileHeight / (localTrickplayInfo.Interval / 1000));
localTrickplayInfo.Bandwidth = Math.Max(localTrickplayInfo.Bandwidth, bitrate);
}
@@ -296,7 +348,7 @@ public class TrickplayManager : ITrickplayManager
.ToList();
// Create tiles
- var trickplayInfo = CreateTiles(images, actualWidth, options, outputDir);
+ var trickplayInfo = CreateTiles(images, actualWidth, options, outputDir.FullName);
// Save tiles info
try
@@ -319,7 +371,7 @@ public class TrickplayManager : ITrickplayManager
// Make sure no files stay in metadata folders on failure
// if tiles info wasn't saved.
- Directory.Delete(outputDir, true);
+ outputDir.Delete(true);
}
}
catch (Exception ex)
@@ -435,12 +487,6 @@ public class TrickplayManager : ITrickplayManager
return false;
}
- var libraryOptions = _libraryManager.GetLibraryOptions(video);
- if (libraryOptions is null || !libraryOptions.EnableTrickplayImageExtraction)
- {
- return false;
- }
-
// Can't extract images if there are no video streams
return video.GetMediaStreams().Count > 0;
}
@@ -507,6 +553,13 @@ public class TrickplayManager : ITrickplayManager
}
/// <inheritdoc />
+ public async Task DeleteTrickplayDataAsync(Guid itemId, CancellationToken cancellationToken)
+ {
+ var dbContext = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
+ await dbContext.TrickplayInfos.Where(i => i.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ /// <inheritdoc />
public async Task<Dictionary<string, Dictionary<int, TrickplayInfo>>> GetTrickplayManifest(BaseItem item)
{
var trickplayManifest = new Dictionary<string, Dictionary<int, TrickplayInfo>>();