@ -1,7 +1,7 @@
#pragma warning disable CS1591
using System ;
using System.Collections. Concurrent ;
using System.Collections. Generic ;
using System.Diagnostics ;
using System.Globalization ;
using System.IO ;
@ -9,6 +9,7 @@ using System.Linq;
using System.Threading ;
using System.Threading.Tasks ;
using AsyncKeyedLock ;
using MediaBrowser.Common ;
using MediaBrowser.Common.Configuration ;
using MediaBrowser.Common.Extensions ;
using MediaBrowser.Controller.Entities ;
@ -230,6 +231,8 @@ namespace MediaBrowser.MediaEncoding.Attachments
MediaAttachment mediaAttachment ,
CancellationToken cancellationToken )
{
await CacheAllAttachments ( mediaPath , inputFile , mediaSource , cancellationToken ) . ConfigureAwait ( false ) ;
var outputPath = GetAttachmentCachePath ( mediaPath , mediaSource , mediaAttachment . Index ) ;
await ExtractAttachment ( inputFile , mediaSource , mediaAttachment . Index , outputPath , cancellationToken )
. ConfigureAwait ( false ) ;
@ -237,6 +240,159 @@ namespace MediaBrowser.MediaEncoding.Attachments
return outputPath ;
}
private async Task CacheAllAttachments (
string mediaPath ,
string inputFile ,
MediaSourceInfo mediaSource ,
CancellationToken cancellationToken )
{
var outputFileLocks = new List < AsyncKeyedLockReleaser < string > > ( ) ;
var extractableAttachmentIds = new List < int > ( ) ;
try
{
foreach ( var attachment in mediaSource . MediaAttachments )
{
var outputPath = GetAttachmentCachePath ( mediaPath , mediaSource , attachment . Index ) ;
var @outputFileLock = _semaphoreLocks . GetOrAdd ( outputPath ) ;
await @outputFileLock . SemaphoreSlim . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
if ( File . Exists ( outputPath ) )
{
@outputFileLock . Dispose ( ) ;
continue ;
}
outputFileLocks . Add ( @outputFileLock ) ;
extractableAttachmentIds . Add ( attachment . Index ) ;
}
if ( extractableAttachmentIds . Count > 0 )
{
await CacheAllAttachmentsInternal ( mediaPath , inputFile , mediaSource , extractableAttachmentIds , cancellationToken ) . ConfigureAwait ( false ) ;
}
}
catch ( Exception ex )
{
_logger . LogWarning ( ex , "Unable to cache media attachments for File:{File}" , mediaPath ) ;
}
finally
{
foreach ( var @outputFileLock in outputFileLocks )
{
@outputFileLock . Dispose ( ) ;
}
}
}
private async Task CacheAllAttachmentsInternal (
string mediaPath ,
string inputFile ,
MediaSourceInfo mediaSource ,
List < int > extractableAttachmentIds ,
CancellationToken cancellationToken )
{
var outputPaths = new List < string > ( ) ;
var processArgs = string . Empty ;
foreach ( var attachmentId in extractableAttachmentIds )
{
var outputPath = GetAttachmentCachePath ( mediaPath , mediaSource , attachmentId ) ;
Directory . CreateDirectory ( Path . GetDirectoryName ( outputPath ) ? ? throw new FileNotFoundException ( $"Calculated path ({outputPath}) is not valid." ) ) ;
outputPaths . Add ( outputPath ) ;
processArgs + = string . Format (
CultureInfo . InvariantCulture ,
" -dump_attachment:{0} \"{1}\"" ,
attachmentId ,
EncodingUtils . NormalizePath ( outputPath ) ) ;
}
processArgs + = string . Format (
CultureInfo . InvariantCulture ,
" -i \"{0}\" -t 0 -f null null" ,
inputFile ) ;
int exitCode ;
using ( var process = new Process
{
StartInfo = new ProcessStartInfo
{
Arguments = processArgs ,
FileName = _mediaEncoder . EncoderPath ,
UseShellExecute = false ,
CreateNoWindow = true ,
WindowStyle = ProcessWindowStyle . Hidden ,
ErrorDialog = false
} ,
EnableRaisingEvents = true
} )
{
_logger . LogInformation ( "{File} {Arguments}" , process . StartInfo . FileName , process . StartInfo . Arguments ) ;
process . Start ( ) ;
try
{
await process . WaitForExitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
exitCode = process . ExitCode ;
}
catch ( OperationCanceledException )
{
process . Kill ( true ) ;
exitCode = - 1 ;
}
}
var failed = false ;
if ( exitCode = = - 1 )
{
failed = true ;
foreach ( var outputPath in outputPaths )
{
try
{
_logger . LogWarning ( "Deleting extracted media attachment due to failure: {Path}" , outputPath ) ;
_fileSystem . DeleteFile ( outputPath ) ;
}
catch ( FileNotFoundException )
{
// ffmpeg failed, so it is normal that one or more expected output files do not exist.
// There is no need to log anything for the user here.
}
catch ( IOException ex )
{
_logger . LogError ( ex , "Error deleting extracted media attachment {Path}" , outputPath ) ;
}
}
}
else
{
foreach ( var outputPath in outputPaths )
{
if ( ! File . Exists ( outputPath ) )
{
_logger . LogError ( "ffmpeg media attachment extraction failed for {InputPath} to {OutputPath}" , inputFile , outputPath ) ;
failed = true ;
continue ;
}
_logger . LogInformation ( "ffmpeg media attachment extraction completed for {InputPath} to {OutputPath}" , inputFile , outputPath ) ;
}
}
if ( failed )
{
throw new FfmpegException (
string . Format ( CultureInfo . InvariantCulture , "ffmpeg media attachment extraction failed for {0}" , inputFile ) ) ;
}
}
private async Task ExtractAttachment (
string inputFile ,
MediaSourceInfo mediaSource ,