@ -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 ) ;
}
}
}
}