@ -1,9 +1,7 @@
using MediaBrowser.Common.Configuration ;
using MediaBrowser.Common.IO ;
using MediaBrowser.Common.MediaInfo ;
using MediaBrowser.Common.Net ;
using MediaBrowser.Model.Entities ;
using MediaBrowser.Model.IO ;
using MediaBrowser.Model.Logging ;
using MediaBrowser.Model.Serialization ;
using System ;
@ -13,7 +11,6 @@ using System.Diagnostics;
using System.Globalization ;
using System.IO ;
using System.Linq ;
using System.Reflection ;
using System.Runtime.InteropServices ;
using System.Text ;
using System.Threading ;
@ -26,12 +23,6 @@ namespace MediaBrowser.Server.Implementations.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>
@ -48,8 +39,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// <value>The json serializer.</value>
private readonly IJsonSerializer _jsonSerializer ;
private readonly IHttpClient _httpClient ;
/// <summary>
/// The video image resource pool
/// </summary>
@ -70,50 +59,25 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// </summary>
private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim ( 2 , 2 ) ;
/// <summary>
/// Gets or sets the versioned directory path.
/// </summary>
/// <value>The versioned directory path.</value>
private string VersionedDirectoryPath { get ; set ; }
public string FFMpegPath { get ; private 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 , IHttpClient httpClient )
public string FFProbePath { get ; private set ; }
public string Version { get ; private set ; }
public MediaEncoder ( ILogger logger , IApplicationPaths appPaths ,
IJsonSerializer jsonSerializer , string ffMpegPath , string ffProbePath , string version )
{
_logger = logger ;
_zipClient = zipClient ;
_appPaths = appPaths ;
_jsonSerializer = jsonSerializer ;
_httpClient = httpClient ;
Version = version ;
FFProbePath = ffProbePath ;
FFMpegPath = ffMpegPath ;
// 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>
/// Gets the media tools path.
/// </summary>
/// <param name="create">if set to <c>true</c> [create].</param>
/// <returns>System.String.</returns>
private string GetMediaToolsPath ( bool create )
{
var path = Path . Combine ( _appPaths . ProgramDataPath , "ffmpeg" ) ;
if ( create & & ! Directory . Exists ( path ) )
{
Directory . CreateDirectory ( path ) ;
}
return path ;
}
/// <summary>
@ -125,182 +89,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
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 ( GetMediaToolsPath ( true ) ,
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 async void ExtractTools ( Assembly assembly , string zipFileResourcePath , string targetPath )
{
using ( var resourceStream = assembly . GetManifestResourceStream ( zipFileResourcePath ) )
{
_zipClient . ExtractAll ( resourceStream , targetPath , false ) ;
}
try
{
await DownloadFonts ( targetPath ) . ConfigureAwait ( false ) ;
}
catch ( Exception ex )
{
_logger . ErrorException ( "Error getting ffmpeg font files" , ex ) ;
}
}
private const string FontUrl = "https://www.dropbox.com/s/9nb76tybcsw5xrk/ARIALUNI.zip?dl=1" ;
/// <summary>
/// Extracts the fonts.
/// </summary>
/// <param name="targetPath">The target path.</param>
private async Task DownloadFonts ( 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 ) )
{
await DownloadFontFile ( fontsDirectory , fontFilename ) . ConfigureAwait ( false ) ;
}
await WriteFontConfigFile ( fontsDirectory ) . ConfigureAwait ( false ) ;
}
private async Task DownloadFontFile ( string fontsDirectory , string fontFilename )
{
var existingFile = Directory
. EnumerateFiles ( _appPaths . ProgramDataPath , fontFilename , SearchOption . AllDirectories )
. FirstOrDefault ( ) ;
if ( existingFile ! = null )
{
try
{
File . Copy ( existingFile , Path . Combine ( fontsDirectory , fontFilename ) , true ) ;
return ;
}
catch ( IOException ex )
{
// Log this, but don't let it fail the operation
_logger . ErrorException ( "Error copying file" , ex ) ;
}
}
var tempFile = await _httpClient . GetTempFile ( new HttpRequestOptions
{
Url = FontUrl ,
Progress = new Progress < double > ( )
} ) ;
_zipClient . ExtractAll ( tempFile , fontsDirectory , true ) ;
try
{
File . Delete ( tempFile ) ;
}
catch ( IOException ex )
{
// Log this, but don't let it fail the operation
_logger . ErrorException ( "Error deleting temp file {0}" , ex , tempFile ) ;
}
}
private async Task WriteFontConfigFile ( string fontsDirectory )
{
const string fontConfigFilename = "fonts.conf" ;
var fontConfigFile = Path . Combine ( fontsDirectory , fontConfigFilename ) ;
if ( ! File . Exists ( fontConfigFile ) )
{
var contents = string . Format ( "<?xml version=\"1.0\"?><fontconfig><dir>{0}</dir><alias><family>Arial</family><prefer>Arial Unicode MS</prefer></alias></fontconfig>" , fontsDirectory ) ;
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>