|
@@ -6,6 +6,7 @@ using System.Linq;
|
|
|
using System.Text;
|
|
|
using System.Threading;
|
|
|
using System.Threading.Tasks;
|
|
|
+using AsyncKeyedLock;
|
|
|
using Jellyfin.Data.Entities;
|
|
|
using MediaBrowser.Common.Configuration;
|
|
|
using MediaBrowser.Controller.Configuration;
|
|
@@ -37,7 +38,7 @@ public class TrickplayManager : ITrickplayManager
|
|
|
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
|
|
|
private readonly IApplicationPaths _appPaths;
|
|
|
|
|
|
- private static readonly SemaphoreSlim _resourcePool = new(1, 1);
|
|
|
+ private static readonly AsyncNonKeyedLocker _resourcePool = new(1);
|
|
|
private static readonly string[] _trickplayImgExtensions = { ".jpg" };
|
|
|
|
|
|
/// <summary>
|
|
@@ -107,93 +108,92 @@ public class TrickplayManager : ITrickplayManager
|
|
|
var imgTempDir = string.Empty;
|
|
|
var outputDir = GetTrickplayDirectory(video, width);
|
|
|
|
|
|
- await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
|
-
|
|
|
- try
|
|
|
+ using (await _resourcePool.LockAsync(cancellationToken).ConfigureAwait(false))
|
|
|
{
|
|
|
- if (!replace && Directory.Exists(outputDir) && (await GetTrickplayResolutions(video.Id).ConfigureAwait(false)).ContainsKey(width))
|
|
|
- {
|
|
|
- _logger.LogDebug("Found existing trickplay files for {ItemId}. Exiting.", video.Id);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // Extract images
|
|
|
- // Note: Media sources under parent items exist as their own video/item as well. Only use this video stream for trickplay.
|
|
|
- var mediaSource = video.GetMediaSources(false).Find(source => Guid.Parse(source.Id).Equals(video.Id));
|
|
|
-
|
|
|
- if (mediaSource is null)
|
|
|
+ try
|
|
|
{
|
|
|
- _logger.LogDebug("Found no matching media source for item {ItemId}", video.Id);
|
|
|
- return;
|
|
|
- }
|
|
|
+ if (!replace && Directory.Exists(outputDir) && (await GetTrickplayResolutions(video.Id).ConfigureAwait(false)).ContainsKey(width))
|
|
|
+ {
|
|
|
+ _logger.LogDebug("Found existing trickplay files for {ItemId}. Exiting.", video.Id);
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- var mediaPath = mediaSource.Path;
|
|
|
- var mediaStream = mediaSource.VideoStream;
|
|
|
- var container = mediaSource.Container;
|
|
|
+ // Extract images
|
|
|
+ // Note: Media sources under parent items exist as their own video/item as well. Only use this video stream for trickplay.
|
|
|
+ var mediaSource = video.GetMediaSources(false).Find(source => Guid.Parse(source.Id).Equals(video.Id));
|
|
|
|
|
|
- _logger.LogInformation("Creating trickplay files at {Width} width, for {Path} [ID: {ItemId}]", width, mediaPath, video.Id);
|
|
|
- imgTempDir = await _mediaEncoder.ExtractVideoImagesOnIntervalAccelerated(
|
|
|
- mediaPath,
|
|
|
- container,
|
|
|
- mediaSource,
|
|
|
- mediaStream,
|
|
|
- width,
|
|
|
- TimeSpan.FromMilliseconds(options.Interval),
|
|
|
- options.EnableHwAcceleration,
|
|
|
- options.ProcessThreads,
|
|
|
- options.Qscale,
|
|
|
- options.ProcessPriority,
|
|
|
- _encodingHelper,
|
|
|
- cancellationToken).ConfigureAwait(false);
|
|
|
+ if (mediaSource is null)
|
|
|
+ {
|
|
|
+ _logger.LogDebug("Found no matching media source for item {ItemId}", video.Id);
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- if (string.IsNullOrEmpty(imgTempDir) || !Directory.Exists(imgTempDir))
|
|
|
- {
|
|
|
- throw new InvalidOperationException("Null or invalid directory from media encoder.");
|
|
|
- }
|
|
|
+ var mediaPath = mediaSource.Path;
|
|
|
+ var mediaStream = mediaSource.VideoStream;
|
|
|
+ var container = mediaSource.Container;
|
|
|
+
|
|
|
+ _logger.LogInformation("Creating trickplay files at {Width} width, for {Path} [ID: {ItemId}]", width, mediaPath, video.Id);
|
|
|
+ imgTempDir = await _mediaEncoder.ExtractVideoImagesOnIntervalAccelerated(
|
|
|
+ mediaPath,
|
|
|
+ container,
|
|
|
+ mediaSource,
|
|
|
+ mediaStream,
|
|
|
+ width,
|
|
|
+ TimeSpan.FromMilliseconds(options.Interval),
|
|
|
+ options.EnableHwAcceleration,
|
|
|
+ options.ProcessThreads,
|
|
|
+ options.Qscale,
|
|
|
+ options.ProcessPriority,
|
|
|
+ _encodingHelper,
|
|
|
+ cancellationToken).ConfigureAwait(false);
|
|
|
+
|
|
|
+ if (string.IsNullOrEmpty(imgTempDir) || !Directory.Exists(imgTempDir))
|
|
|
+ {
|
|
|
+ throw new InvalidOperationException("Null or invalid directory from media encoder.");
|
|
|
+ }
|
|
|
|
|
|
- var images = _fileSystem.GetFiles(imgTempDir, _trickplayImgExtensions, false, false)
|
|
|
- .Select(i => i.FullName)
|
|
|
- .OrderBy(i => i)
|
|
|
- .ToList();
|
|
|
+ var images = _fileSystem.GetFiles(imgTempDir, _trickplayImgExtensions, false, false)
|
|
|
+ .Select(i => i.FullName)
|
|
|
+ .OrderBy(i => i)
|
|
|
+ .ToList();
|
|
|
|
|
|
- // Create tiles
|
|
|
- var trickplayInfo = CreateTiles(images, width, options, outputDir);
|
|
|
+ // Create tiles
|
|
|
+ var trickplayInfo = CreateTiles(images, width, options, outputDir);
|
|
|
|
|
|
- // Save tiles info
|
|
|
- try
|
|
|
- {
|
|
|
- if (trickplayInfo is not null)
|
|
|
+ // Save tiles info
|
|
|
+ try
|
|
|
{
|
|
|
- trickplayInfo.ItemId = video.Id;
|
|
|
- await SaveTrickplayInfo(trickplayInfo).ConfigureAwait(false);
|
|
|
+ if (trickplayInfo is not null)
|
|
|
+ {
|
|
|
+ trickplayInfo.ItemId = video.Id;
|
|
|
+ await SaveTrickplayInfo(trickplayInfo).ConfigureAwait(false);
|
|
|
|
|
|
- _logger.LogInformation("Finished creation of trickplay files for {0}", mediaPath);
|
|
|
+ _logger.LogInformation("Finished creation of trickplay files for {0}", mediaPath);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ throw new InvalidOperationException("Null trickplay tiles info from CreateTiles.");
|
|
|
+ }
|
|
|
}
|
|
|
- else
|
|
|
+ catch (Exception ex)
|
|
|
{
|
|
|
- throw new InvalidOperationException("Null trickplay tiles info from CreateTiles.");
|
|
|
+ _logger.LogError(ex, "Error while saving trickplay tiles info.");
|
|
|
+
|
|
|
+ // Make sure no files stay in metadata folders on failure
|
|
|
+ // if tiles info wasn't saved.
|
|
|
+ Directory.Delete(outputDir, true);
|
|
|
}
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
- _logger.LogError(ex, "Error while saving trickplay tiles info.");
|
|
|
-
|
|
|
- // Make sure no files stay in metadata folders on failure
|
|
|
- // if tiles info wasn't saved.
|
|
|
- Directory.Delete(outputDir, true);
|
|
|
+ _logger.LogError(ex, "Error creating trickplay images.");
|
|
|
}
|
|
|
- }
|
|
|
- catch (Exception ex)
|
|
|
- {
|
|
|
- _logger.LogError(ex, "Error creating trickplay images.");
|
|
|
- }
|
|
|
- finally
|
|
|
- {
|
|
|
- _resourcePool.Release();
|
|
|
-
|
|
|
- if (!string.IsNullOrEmpty(imgTempDir))
|
|
|
+ finally
|
|
|
{
|
|
|
- Directory.Delete(imgTempDir, true);
|
|
|
+ if (!string.IsNullOrEmpty(imgTempDir))
|
|
|
+ {
|
|
|
+ Directory.Delete(imgTempDir, true);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|