#429 - Extract ffmpeg from core product

This commit is contained in:
Luke Pulverenti 2013-09-23 11:37:50 -04:00
parent 28e788e435
commit 946a5c49d0
7 changed files with 233 additions and 226 deletions

View File

@ -254,10 +254,8 @@
<EmbeddedResource Include="Localization\Ratings\kz.txt" /> <EmbeddedResource Include="Localization\Ratings\kz.txt" />
<EmbeddedResource Include="Localization\Ratings\nz.txt" /> <EmbeddedResource Include="Localization\Ratings\nz.txt" />
<EmbeddedResource Include="Localization\Ratings\ru.txt" /> <EmbeddedResource Include="Localization\Ratings\ru.txt" />
<EmbeddedResource Include="MediaEncoder\readme.txt" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="MediaEncoder\ffmpeg20130904.zip" />
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,9 +1,7 @@
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo; using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using System; using System;
@ -13,7 +11,6 @@ using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@ -26,12 +23,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// </summary> /// </summary>
public class MediaEncoder : IMediaEncoder, IDisposable public class MediaEncoder : IMediaEncoder, IDisposable
{ {
/// <summary>
/// Gets or sets the zip client.
/// </summary>
/// <value>The zip client.</value>
private readonly IZipClient _zipClient;
/// <summary> /// <summary>
/// The _logger /// The _logger
/// </summary> /// </summary>
@ -48,8 +39,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// <value>The json serializer.</value> /// <value>The json serializer.</value>
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;
/// <summary> /// <summary>
/// The video image resource pool /// The video image resource pool
/// </summary> /// </summary>
@ -70,50 +59,25 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// </summary> /// </summary>
private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim(2, 2); private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim(2, 2);
/// <summary> public string FFMpegPath { get; private set; }
/// Gets or sets the versioned directory path.
/// </summary>
/// <value>The versioned directory path.</value>
private string VersionedDirectoryPath { get; set; }
/// <summary> public string FFProbePath { get; private set; }
/// Initializes a new instance of the <see cref="MediaEncoder" /> class.
/// </summary> public string Version { get; private set; }
/// <param name="logger">The logger.</param>
/// <param name="zipClient">The zip client.</param> public MediaEncoder(ILogger logger, IApplicationPaths appPaths,
/// <param name="appPaths">The app paths.</param> IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, string version)
/// <param name="jsonSerializer">The json serializer.</param>
public MediaEncoder(ILogger logger, IZipClient zipClient, IApplicationPaths appPaths,
IJsonSerializer jsonSerializer, IHttpClient httpClient)
{ {
_logger = logger; _logger = logger;
_zipClient = zipClient;
_appPaths = appPaths; _appPaths = appPaths;
_jsonSerializer = jsonSerializer; _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 // Not crazy about this but it's the only way to suppress ffmpeg crash dialog boxes
SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS | ErrorModes.SEM_NOALIGNMENTFAULTEXCEPT | SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS | ErrorModes.SEM_NOALIGNMENTFAULTEXCEPT |
ErrorModes.SEM_NOGPFAULTERRORBOX | ErrorModes.SEM_NOOPENFILEERRORBOX); 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> /// <summary>
@ -125,182 +89,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
get { return FFMpegPath; } 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> /// <summary>
/// Gets the media info. /// Gets the media info.
/// </summary> /// </summary>

View File

@ -284,8 +284,7 @@ namespace MediaBrowser.ServerApplication
RegisterSingleInstance<ILibrarySearchEngine>(() => new LuceneSearchEngine(ApplicationPaths, LogManager, LibraryManager)); RegisterSingleInstance<ILibrarySearchEngine>(() => new LuceneSearchEngine(ApplicationPaths, LogManager, LibraryManager));
MediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), ZipClient, ApplicationPaths, JsonSerializer, HttpClient); await RegisterMediaEncoder().ConfigureAwait(false);
RegisterSingleInstance(MediaEncoder);
var clientConnectionManager = new SessionManager(UserDataRepository, ServerConfigurationManager, Logger, UserRepository); var clientConnectionManager = new SessionManager(UserDataRepository, ServerConfigurationManager, Logger, UserRepository);
RegisterSingleInstance<ISessionManager>(clientConnectionManager); RegisterSingleInstance<ISessionManager>(clientConnectionManager);
@ -316,6 +315,18 @@ namespace MediaBrowser.ServerApplication
SetKernelProperties(); SetKernelProperties();
} }
/// <summary>
/// Registers the media encoder.
/// </summary>
/// <returns>Task.</returns>
private async Task RegisterMediaEncoder()
{
var info = await new FFMpegDownloader(Logger, ApplicationPaths, HttpClient, ZipClient).GetFFMpegInfo().ConfigureAwait(false);
MediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), ApplicationPaths, JsonSerializer, info.Path, info.ProbePath, info.Version);
RegisterSingleInstance(MediaEncoder);
}
/// <summary> /// <summary>
/// Sets the kernel properties. /// Sets the kernel properties.
/// </summary> /// </summary>

View File

@ -0,0 +1,205 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace MediaBrowser.ServerApplication.Implementations
{
public class FFMpegDownloader
{
private readonly IZipClient _zipClient;
private readonly IHttpClient _httpClient;
private readonly IApplicationPaths _appPaths;
private readonly ILogger _logger;
public FFMpegDownloader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient)
{
_logger = logger;
_appPaths = appPaths;
_httpClient = httpClient;
_zipClient = zipClient;
}
public async Task<FFMpegInfo> GetFFMpegInfo()
{
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);
}
await ExtractTools(assembly, resource, versionedDirectoryPath).ConfigureAwait(false);
return new FFMpegInfo
{
ProbePath = Path.Combine(versionedDirectoryPath, "ffprobe.exe"),
Path = Path.Combine(versionedDirectoryPath, "ffmpeg.exe"),
Version = Path.GetFileNameWithoutExtension(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 Task 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);
}
/// <summary>
/// Downloads the font file.
/// </summary>
/// <param name="fontsDirectory">The fonts directory.</param>
/// <param name="fontFilename">The font filename.</param>
/// <returns>Task.</returns>
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);
}
}
/// <summary>
/// Writes the font config file.
/// </summary>
/// <param name="fontsDirectory">The fonts directory.</param>
/// <returns>Task.</returns>
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 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;
}
}
public class FFMpegInfo
{
public string Path { get; set; }
public string ProbePath { get; set; }
public string Version { get; set; }
}
}

View File

@ -210,6 +210,7 @@
</Compile> </Compile>
<Compile Include="EntryPoints\StartupWizard.cs" /> <Compile Include="EntryPoints\StartupWizard.cs" />
<Compile Include="EntryPoints\UdpServerEntryPoint.cs" /> <Compile Include="EntryPoints\UdpServerEntryPoint.cs" />
<Compile Include="Implementations\FFMpegDownloader.cs" />
<Compile Include="MainStartup.cs" /> <Compile Include="MainStartup.cs" />
<Compile Include="BackgroundServiceInstaller.cs"> <Compile Include="BackgroundServiceInstaller.cs">
<SubType>Component</SubType> <SubType>Component</SubType>
@ -277,6 +278,7 @@
<LastGenOutput>Resources.Designer.cs</LastGenOutput> <LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource> </EmbeddedResource>
<None Include="app.manifest" /> <None Include="app.manifest" />
<EmbeddedResource Include="Implementations\ffmpeg20130904.zip" />
<None Include="packages.config" /> <None Include="packages.config" />
<None Include="Properties\Settings.settings"> <None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator> <Generator>SettingsSingleFileGenerator</Generator>
@ -388,6 +390,9 @@
<ItemGroup> <ItemGroup>
<Resource Include="Resources\Images\mb3logo800.png" /> <Resource Include="Resources\Images\mb3logo800.png" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Implementations\readme.txt" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>
<PostBuildEvent>if $(ConfigurationName) == Release ( <PostBuildEvent>if $(ConfigurationName) == Release (