@@ -0,0 +1,930 @@
using MediaBrowser.Common.Configuration ;
using MediaBrowser.Common.IO ;
using MediaBrowser.Common.MediaInfo ;
using MediaBrowser.Model.Entities ;
using MediaBrowser.Model.IO ;
using MediaBrowser.Model.Logging ;
using MediaBrowser.Model.Serialization ;
using System ;
using System.Collections.Generic ;
using System.ComponentModel ;
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
using System.Reflection ;
using System.Runtime.InteropServices ;
using System.Text ;
using System.Threading ;
using System.Threading.Tasks ;
namespace MediaBrowser.Server.Implementations.MediaEncoder
{
/// <summary>
/// Class MediaEncoder
/// </summary>
public class MediaEncoder : IMediaEncoder , IDisposable
{
/// <summary>
/// Gets or sets the zip client.
/// </summary>
/// <value>The zip client.</value>
private readonly IZipClient _zipClient ;
/// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger ;
/// <summary>
/// The _app paths
/// </summary>
private readonly IApplicationPaths _appPaths ;
/// <summary>
/// Gets the json serializer.
/// </summary>
/// <value>The json serializer.</value>
private readonly IJsonSerializer _jsonSerializer ;
/// <summary>
/// The video image resource pool
/// </summary>
private readonly SemaphoreSlim _videoImageResourcePool = new SemaphoreSlim ( 2 , 2 ) ;
/// <summary>
/// The audio image resource pool
/// </summary>
private readonly SemaphoreSlim _audioImageResourcePool = new SemaphoreSlim ( 3 , 3 ) ;
/// <summary>
/// The _subtitle extraction resource pool
/// </summary>
private readonly SemaphoreSlim _subtitleExtractionResourcePool = new SemaphoreSlim ( 2 , 2 ) ;
/// <summary>
/// The FF probe resource pool
/// </summary>
private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim ( 3 , 3 ) ;
/// <summary>
/// Gets or sets the versioned directory path.
/// </summary>
/// <value>The versioned directory path.</value>
private string VersionedDirectoryPath { get ; set ; }
/// <summary>
/// Initializes a new instance of the <see cref="MediaEncoder" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="zipClient">The zip client.</param>
/// <param name="appPaths">The app paths.</param>
/// <param name="jsonSerializer">The json serializer.</param>
public MediaEncoder ( ILogger logger , IZipClient zipClient , IApplicationPaths appPaths , IJsonSerializer jsonSerializer )
{
_logger = logger ;
_zipClient = zipClient ;
_appPaths = appPaths ;
_jsonSerializer = jsonSerializer ;
// Not crazy about this but it's the only way to suppress ffmpeg crash dialog boxes
SetErrorMode ( ErrorModes . SEM_FAILCRITICALERRORS | ErrorModes . SEM_NOALIGNMENTFAULTEXCEPT | ErrorModes . SEM_NOGPFAULTERRORBOX | ErrorModes . SEM_NOOPENFILEERRORBOX ) ;
Task . Run ( ( ) = > VersionedDirectoryPath = GetVersionedDirectoryPath ( ) ) ;
}
/// <summary>
/// The _media tools path
/// </summary>
private string _mediaToolsPath ;
/// <summary>
/// Gets the folder path to tools
/// </summary>
/// <value>The media tools path.</value>
private string MediaToolsPath
{
get
{
if ( _mediaToolsPath = = null )
{
_mediaToolsPath = Path . Combine ( _appPaths . ProgramDataPath , "ffmpeg" ) ;
if ( ! Directory . Exists ( _mediaToolsPath ) )
{
Directory . CreateDirectory ( _mediaToolsPath ) ;
}
}
return _mediaToolsPath ;
}
}
/// <summary>
/// Gets the encoder path.
/// </summary>
/// <value>The encoder path.</value>
public string EncoderPath
{
get { return FFMpegPath ; }
}
/// <summary>
/// The _ FF MPEG path
/// </summary>
private string _FFMpegPath ;
/// <summary>
/// Gets the path to ffmpeg.exe
/// </summary>
/// <value>The FF MPEG path.</value>
public string FFMpegPath
{
get
{
return _FFMpegPath ? ? ( _FFMpegPath = Path . Combine ( VersionedDirectoryPath , "ffmpeg.exe" ) ) ;
}
}
/// <summary>
/// The _ FF probe path
/// </summary>
private string _FFProbePath ;
/// <summary>
/// Gets the path to ffprobe.exe
/// </summary>
/// <value>The FF probe path.</value>
private string FFProbePath
{
get
{
return _FFProbePath ? ? ( _FFProbePath = Path . Combine ( VersionedDirectoryPath , "ffprobe.exe" ) ) ;
}
}
/// <summary>
/// Gets the version.
/// </summary>
/// <value>The version.</value>
public string Version
{
get { return Path . GetFileNameWithoutExtension ( VersionedDirectoryPath ) ; }
}
/// <summary>
/// Gets the versioned directory path.
/// </summary>
/// <returns>System.String.</returns>
private string GetVersionedDirectoryPath ( )
{
var assembly = GetType ( ) . Assembly ;
var prefix = GetType ( ) . Namespace + "." ;
var srch = prefix + "ffmpeg" ;
var resource = assembly . GetManifestResourceNames ( ) . First ( r = > r . StartsWith ( srch ) ) ;
var filename = resource . Substring ( resource . IndexOf ( prefix , StringComparison . OrdinalIgnoreCase ) + prefix . Length ) ;
var versionedDirectoryPath = Path . Combine ( MediaToolsPath , Path . GetFileNameWithoutExtension ( filename ) ) ;
if ( ! Directory . Exists ( versionedDirectoryPath ) )
{
Directory . CreateDirectory ( versionedDirectoryPath ) ;
}
ExtractTools ( assembly , resource , versionedDirectoryPath ) ;
return versionedDirectoryPath ;
}
/// <summary>
/// Extracts the tools.
/// </summary>
/// <param name="assembly">The assembly.</param>
/// <param name="zipFileResourcePath">The zip file resource path.</param>
/// <param name="targetPath">The target path.</param>
private void ExtractTools ( Assembly assembly , string zipFileResourcePath , string targetPath )
{
using ( var resourceStream = assembly . GetManifestResourceStream ( zipFileResourcePath ) )
{
_zipClient . ExtractAll ( resourceStream , targetPath , false ) ;
}
ExtractFonts ( assembly , targetPath ) ;
}
/// <summary>
/// Extracts the fonts.
/// </summary>
/// <param name="assembly">The assembly.</param>
/// <param name="targetPath">The target path.</param>
private async void ExtractFonts ( Assembly assembly , string targetPath )
{
var fontsDirectory = Path . Combine ( targetPath , "fonts" ) ;
if ( ! Directory . Exists ( fontsDirectory ) )
{
Directory . CreateDirectory ( fontsDirectory ) ;
}
const string fontFilename = "ARIALUNI.TTF" ;
var fontFile = Path . Combine ( fontsDirectory , fontFilename ) ;
if ( ! File . Exists ( fontFile ) )
{
using ( var stream = assembly . GetManifestResourceStream ( GetType ( ) . Namespace + ".fonts." + fontFilename ) )
{
using ( var fileStream = new FileStream ( fontFile , FileMode . Create , FileAccess . Write , FileShare . Read , StreamDefaults . DefaultFileStreamBufferSize , FileOptions . Asynchronous ) )
{
await stream . CopyToAsync ( fileStream ) . ConfigureAwait ( false ) ;
}
}
}
await ExtractFontConfigFile ( assembly , fontsDirectory ) . ConfigureAwait ( false ) ;
}
/// <summary>
/// Extracts the font config file.
/// </summary>
/// <param name="assembly">The assembly.</param>
/// <param name="fontsDirectory">The fonts directory.</param>
/// <returns>Task.</returns>
private async Task ExtractFontConfigFile ( Assembly assembly , string fontsDirectory )
{
const string fontConfigFilename = "fonts.conf" ;
var fontConfigFile = Path . Combine ( fontsDirectory , fontConfigFilename ) ;
if ( ! File . Exists ( fontConfigFile ) )
{
using ( var stream = assembly . GetManifestResourceStream ( GetType ( ) . Namespace + ".fonts." + fontConfigFilename ) )
{
using ( var streamReader = new StreamReader ( stream ) )
{
var contents = await streamReader . ReadToEndAsync ( ) . ConfigureAwait ( false ) ;
contents = contents . Replace ( "<dir></dir>" , "<dir>" + fontsDirectory + "</dir>" ) ;
var bytes = Encoding . UTF8 . GetBytes ( contents ) ;
using ( var fileStream = new FileStream ( fontConfigFile , FileMode . Create , FileAccess . Write , FileShare . Read , StreamDefaults . DefaultFileStreamBufferSize , FileOptions . Asynchronous ) )
{
await fileStream . WriteAsync ( bytes , 0 , bytes . Length ) ;
}
}
}
}
}
/// <summary>
/// Gets the media info.
/// </summary>
/// <param name="inputFiles">The input files.</param>
/// <param name="type">The type.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task < MediaInfoResult > GetMediaInfo ( string [ ] inputFiles , InputType type , CancellationToken cancellationToken )
{
return GetMediaInfoInternal ( GetInputArgument ( inputFiles , type ) , type ! = InputType . AudioFile , GetProbeSizeArgument ( type ) , cancellationToken ) ;
}
/// <summary>
/// Gets the input argument.
/// </summary>
/// <param name="inputFiles">The input files.</param>
/// <param name="type">The type.</param>
/// <returns>System.String.</returns>
/// <exception cref="System.ArgumentException">Unrecognized InputType</exception>
public string GetInputArgument ( string [ ] inputFiles , InputType type )
{
string inputPath = null ;
switch ( type )
{
case InputType . Dvd :
case InputType . VideoFile :
case InputType . AudioFile :
inputPath = GetConcatInputArgument ( inputFiles ) ;
break ;
case InputType . Bluray :
inputPath = GetBlurayInputArgument ( inputFiles [ 0 ] ) ;
break ;
default :
throw new ArgumentException ( "Unrecognized InputType" ) ;
}
return inputPath ;
}
/// <summary>
/// Gets the probe size argument.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>System.String.</returns>
public string GetProbeSizeArgument ( InputType type )
{
return type = = InputType . Dvd ? "-probesize 1G -analyzeduration 200M" : string . Empty ;
}
/// <summary>
/// Gets the media info internal.
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="extractChapters">if set to <c>true</c> [extract chapters].</param>
/// <param name="probeSizeArgument">The probe size argument.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{MediaInfoResult}.</returns>
/// <exception cref="System.ApplicationException"></exception>
private async Task < MediaInfoResult > GetMediaInfoInternal ( string inputPath , bool extractChapters , string probeSizeArgument , CancellationToken cancellationToken )
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
CreateNoWindow = true ,
UseShellExecute = false ,
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
RedirectStandardOutput = true ,
RedirectStandardError = true ,
FileName = FFProbePath ,
Arguments = string . Format ( "{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_format" , probeSizeArgument , inputPath ) . Trim ( ) ,
WindowStyle = ProcessWindowStyle . Hidden ,
ErrorDialog = false
} ,
EnableRaisingEvents = true
} ;
_logger . Debug ( "{0} {1}" , process . StartInfo . FileName , process . StartInfo . Arguments ) ;
process . Exited + = ProcessExited ;
await _ffProbeResourcePool . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
MediaInfoResult result ;
string standardError = null ;
try
{
process . Start ( ) ;
Task < string > standardErrorReadTask = null ;
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
if ( extractChapters )
{
standardErrorReadTask = process . StandardError . ReadToEndAsync ( ) ;
}
else
{
process . BeginErrorReadLine ( ) ;
}
result = _jsonSerializer . DeserializeFromStream < MediaInfoResult > ( process . StandardOutput . BaseStream ) ;
if ( extractChapters )
{
standardError = await standardErrorReadTask . ConfigureAwait ( false ) ;
}
}
catch
{
// Hate having to do this
try
{
process . Kill ( ) ;
}
catch ( InvalidOperationException ex1 )
{
_logger . ErrorException ( "Error killing ffprobe" , ex1 ) ;
}
catch ( Win32Exception ex1 )
{
_logger . ErrorException ( "Error killing ffprobe" , ex1 ) ;
}
throw ;
}
finally
{
_ffProbeResourcePool . Release ( ) ;
}
if ( result = = null )
{
throw new ApplicationException ( string . Format ( "FFProbe failed for {0}" , inputPath ) ) ;
}
cancellationToken . ThrowIfCancellationRequested ( ) ;
if ( extractChapters & & ! string . IsNullOrEmpty ( standardError ) )
{
AddChapters ( result , standardError ) ;
}
return result ;
}
/// <summary>
/// Adds the chapters.
/// </summary>
/// <param name="result">The result.</param>
/// <param name="standardError">The standard error.</param>
private void AddChapters ( MediaInfoResult result , string standardError )
{
var lines = standardError . Split ( '\n' ) . Select ( l = > l . TrimStart ( ) ) ;
var chapters = new List < ChapterInfo > { } ;
ChapterInfo lastChapter = null ;
foreach ( var line in lines )
{
if ( line . StartsWith ( "Chapter" , StringComparison . OrdinalIgnoreCase ) )
{
// Example:
// Chapter #0.2: start 400.534, end 4565.435
const string srch = "start " ;
var start = line . IndexOf ( srch , StringComparison . OrdinalIgnoreCase ) ;
if ( start = = - 1 )
{
continue ;
}
var subString = line . Substring ( start + srch . Length ) ;
subString = subString . Substring ( 0 , subString . IndexOf ( ',' ) ) ;
double seconds ;
if ( double . TryParse ( subString , out seconds ) )
{
lastChapter = new ChapterInfo
{
StartPositionTicks = TimeSpan . FromSeconds ( seconds ) . Ticks
} ;
chapters . Add ( lastChapter ) ;
}
}
else if ( line . StartsWith ( "title" , StringComparison . OrdinalIgnoreCase ) )
{
if ( lastChapter ! = null & & string . IsNullOrEmpty ( lastChapter . Name ) )
{
var index = line . IndexOf ( ':' ) ;
if ( index ! = - 1 )
{
lastChapter . Name = line . Substring ( index + 1 ) . Trim ( ) . TrimEnd ( '\r' ) ;
}
}
}
}
result . Chapters = chapters ;
}
/// <summary>
/// Processes the exited.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
void ProcessExited ( object sender , EventArgs e )
{
( ( Process ) sender ) . Dispose ( ) ;
}
/// <summary>
/// Converts the text subtitle to ass.
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException">inputPath
/// or
/// outputPath</exception>
/// <exception cref="System.ApplicationException"></exception>
public async Task ConvertTextSubtitleToAss ( string inputPath , string outputPath , CancellationToken cancellationToken )
{
if ( string . IsNullOrEmpty ( inputPath ) )
{
throw new ArgumentNullException ( "inputPath" ) ;
}
if ( string . IsNullOrEmpty ( outputPath ) )
{
throw new ArgumentNullException ( "outputPath" ) ;
}
var process = new Process
{
StartInfo = new ProcessStartInfo
{
CreateNoWindow = true ,
UseShellExecute = false ,
FileName = FFMpegPath ,
Arguments = string . Format ( "-i \"{0}\" \"{1}\"" , inputPath , outputPath ) ,
WindowStyle = ProcessWindowStyle . Hidden ,
ErrorDialog = false
}
} ;
_logger . Debug ( "{0} {1}" , process . StartInfo . FileName , process . StartInfo . Arguments ) ;
await _subtitleExtractionResourcePool . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
var ranToCompletion = StartAndWaitForProcess ( process ) ;
_subtitleExtractionResourcePool . Release ( ) ;
var exitCode = ranToCompletion ? process . ExitCode : - 1 ;
process . Dispose ( ) ;
var failed = false ;
if ( exitCode = = - 1 )
{
failed = true ;
if ( File . Exists ( outputPath ) )
{
try
{
_logger . Info ( "Deleting converted subtitle due to failure: " , outputPath ) ;
File . Delete ( outputPath ) ;
}
catch ( IOException ex )
{
_logger . ErrorException ( "Error deleting converted subtitle {0}" , ex , outputPath ) ;
}
}
}
else if ( ! File . Exists ( outputPath ) )
{
failed = true ;
}
if ( failed )
{
var msg = string . Format ( "ffmpeg subtitle conversion failed for {0}" , inputPath ) ;
_logger . Error ( msg ) ;
throw new ApplicationException ( msg ) ;
}
}
/// <summary>
/// Extracts the text subtitle.
/// </summary>
/// <param name="inputFiles">The input files.</param>
/// <param name="type">The type.</param>
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentException">Must use inputPath list overload</exception>
public Task ExtractTextSubtitle ( string [ ] inputFiles , InputType type , int subtitleStreamIndex , string outputPath , CancellationToken cancellationToken )
{
return ExtractTextSubtitleInternal ( GetInputArgument ( inputFiles , type ) , subtitleStreamIndex , outputPath , cancellationToken ) ;
}
/// <summary>
/// Extracts the text subtitle.
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException">inputPath
/// or
/// outputPath
/// or
/// cancellationToken</exception>
/// <exception cref="System.ApplicationException"></exception>
private async Task ExtractTextSubtitleInternal ( string inputPath , int subtitleStreamIndex , string outputPath , CancellationToken cancellationToken )
{
if ( string . IsNullOrEmpty ( inputPath ) )
{
throw new ArgumentNullException ( "inputPath" ) ;
}
if ( string . IsNullOrEmpty ( outputPath ) )
{
throw new ArgumentNullException ( "outputPath" ) ;
}
if ( cancellationToken = = null )
{
throw new ArgumentNullException ( "cancellationToken" ) ;
}
var process = new Process
{
StartInfo = new ProcessStartInfo
{
CreateNoWindow = true ,
UseShellExecute = false ,
FileName = FFMpegPath ,
Arguments = string . Format ( "-i {0} -map 0:{1} -an -vn -c:s ass \"{2}\"" , inputPath , subtitleStreamIndex , outputPath ) ,
WindowStyle = ProcessWindowStyle . Hidden ,
ErrorDialog = false
}
} ;
_logger . Debug ( "{0} {1}" , process . StartInfo . FileName , process . StartInfo . Arguments ) ;
await _subtitleExtractionResourcePool . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
var ranToCompletion = StartAndWaitForProcess ( process ) ;
_subtitleExtractionResourcePool . Release ( ) ;
var exitCode = ranToCompletion ? process . ExitCode : - 1 ;
process . Dispose ( ) ;
var failed = false ;
if ( exitCode = = - 1 )
{
failed = true ;
if ( File . Exists ( outputPath ) )
{
try
{
_logger . Info ( "Deleting extracted subtitle due to failure: " , outputPath ) ;
File . Delete ( outputPath ) ;
}
catch ( IOException ex )
{
_logger . ErrorException ( "Error deleting extracted subtitle {0}" , ex , outputPath ) ;
}
}
}
else if ( ! File . Exists ( outputPath ) )
{
failed = true ;
}
if ( failed )
{
var msg = string . Format ( "ffmpeg subtitle extraction failed for {0}" , inputPath ) ;
_logger . Error ( msg ) ;
throw new ApplicationException ( msg ) ;
}
}
/// <summary>
/// Extracts the image.
/// </summary>
/// <param name="inputFiles">The input files.</param>
/// <param name="type">The type.</param>
/// <param name="offset">The offset.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentException">Must use inputPath list overload</exception>
public Task ExtractImage ( string [ ] inputFiles , InputType type , TimeSpan ? offset , string outputPath , CancellationToken cancellationToken )
{
var resourcePool = type = = InputType . AudioFile ? _audioImageResourcePool : _videoImageResourcePool ;
return ExtractImageInternal ( GetInputArgument ( inputFiles , type ) , offset , outputPath , resourcePool , cancellationToken ) ;
}
/// <summary>
/// Extracts the image.
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="offset">The offset.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="resourcePool">The resource pool.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException">inputPath
/// or
/// outputPath</exception>
/// <exception cref="System.ApplicationException"></exception>
private async Task ExtractImageInternal ( string inputPath , TimeSpan ? offset , string outputPath , SemaphoreSlim resourcePool , CancellationToken cancellationToken )
{
if ( string . IsNullOrEmpty ( inputPath ) )
{
throw new ArgumentNullException ( "inputPath" ) ;
}
if ( string . IsNullOrEmpty ( outputPath ) )
{
throw new ArgumentNullException ( "outputPath" ) ;
}
var args = string . Format ( "-i {0} -threads 0 -v quiet -vframes 1 -filter:v select=\\'eq(pict_type\\,I)\\' -f image2 \"{1}\"" , inputPath , outputPath ) ;
if ( offset . HasValue )
{
args = string . Format ( "-ss {0} " , Convert . ToInt32 ( offset . Value . TotalSeconds ) ) + args ;
}
var process = new Process
{
StartInfo = new ProcessStartInfo
{
CreateNoWindow = true ,
UseShellExecute = false ,
FileName = FFMpegPath ,
Arguments = args ,
WindowStyle = ProcessWindowStyle . Hidden ,
ErrorDialog = false
}
} ;
await resourcePool . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
var ranToCompletion = StartAndWaitForProcess ( process ) ;
resourcePool . Release ( ) ;
var exitCode = ranToCompletion ? process . ExitCode : - 1 ;
process . Dispose ( ) ;
var failed = false ;
if ( exitCode = = - 1 )
{
failed = true ;
if ( File . Exists ( outputPath ) )
{
try
{
_logger . Info ( "Deleting extracted image due to failure: " , outputPath ) ;
File . Delete ( outputPath ) ;
}
catch ( IOException ex )
{
_logger . ErrorException ( "Error deleting extracted image {0}" , ex , outputPath ) ;
}
}
}
else if ( ! File . Exists ( outputPath ) )
{
failed = true ;
}
if ( failed )
{
var msg = string . Format ( "ffmpeg image extraction failed for {0}" , inputPath ) ;
_logger . Error ( msg ) ;
throw new ApplicationException ( msg ) ;
}
}
/// <summary>
/// Starts the and wait for process.
/// </summary>
/// <param name="process">The process.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
private bool StartAndWaitForProcess ( Process process )
{
process . Start ( ) ;
var ranToCompletion = process . WaitForExit ( 10000 ) ;
if ( ! ranToCompletion )
{
try
{
_logger . Info ( "Killing ffmpeg process" ) ;
process . Kill ( ) ;
process . WaitForExit ( 1000 ) ;
}
catch ( Win32Exception ex )
{
_logger . ErrorException ( "Error killing process" , ex ) ;
}
catch ( InvalidOperationException ex )
{
_logger . ErrorException ( "Error killing process" , ex ) ;
}
catch ( NotSupportedException ex )
{
_logger . ErrorException ( "Error killing process" , ex ) ;
}
}
return ranToCompletion ;
}
/// <summary>
/// Gets the file input argument.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.String.</returns>
public string GetFileInputArgument ( string path )
{
return string . Format ( "file:\"{0}\"" , path ) ;
}
/// <summary>
/// Gets the concat input argument.
/// </summary>
/// <param name="playableStreamFiles">The playable stream files.</param>
/// <returns>System.String.</returns>
public string GetConcatInputArgument ( string [ ] playableStreamFiles )
{
// Get all streams
// If there's more than one we'll need to use the concat command
if ( playableStreamFiles . Length > 1 )
{
var files = string . Join ( "|" , playableStreamFiles ) ;
return string . Format ( "concat:\"{0}\"" , files ) ;
}
// Determine the input path for video files
return string . Format ( "file:\"{0}\"" , playableStreamFiles [ 0 ] ) ;
}
/// <summary>
/// Gets the bluray input argument.
/// </summary>
/// <param name="blurayRoot">The bluray root.</param>
/// <returns>System.String.</returns>
public string GetBlurayInputArgument ( string blurayRoot )
{
return string . Format ( "bluray:\"{0}\"" , blurayRoot ) ;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose ( )
{
Dispose ( true ) ;
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose ( bool dispose )
{
if ( dispose )
{
_videoImageResourcePool . Dispose ( ) ;
}
SetErrorMode ( ErrorModes . SYSTEM_DEFAULT ) ;
}
/// <summary>
/// Sets the error mode.
/// </summary>
/// <param name="uMode">The u mode.</param>
/// <returns>ErrorModes.</returns>
[DllImport("kernel32.dll")]
static extern ErrorModes SetErrorMode ( ErrorModes uMode ) ;
/// <summary>
/// Enum ErrorModes
/// </summary>
[Flags]
public enum ErrorModes : uint
{
/// <summary>
/// The SYSTE m_ DEFAULT
/// </summary>
SYSTEM_DEFAULT = 0x0 ,
/// <summary>
/// The SE m_ FAILCRITICALERRORS
/// </summary>
SEM_FAILCRITICALERRORS = 0x0001 ,
/// <summary>
/// The SE m_ NOALIGNMENTFAULTEXCEPT
/// </summary>
SEM_NOALIGNMENTFAULTEXCEPT = 0x0004 ,
/// <summary>
/// The SE m_ NOGPFAULTERRORBOX
/// </summary>
SEM_NOGPFAULTERRORBOX = 0x0002 ,
/// <summary>
/// The SE m_ NOOPENFILEERRORBOX
/// </summary>
SEM_NOOPENFILEERRORBOX = 0x8000
}
}
}