FFMpegDownloader.cs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. using MediaBrowser.Common.Configuration;
  2. using MediaBrowser.Common.IO;
  3. using MediaBrowser.Common.Net;
  4. using MediaBrowser.Model.IO;
  5. using MediaBrowser.Model.Logging;
  6. using System;
  7. using System.IO;
  8. using System.Linq;
  9. using System.Reflection;
  10. using System.Text;
  11. using System.Threading.Tasks;
  12. namespace MediaBrowser.ServerApplication.Implementations
  13. {
  14. public class FFMpegDownloader
  15. {
  16. private readonly IZipClient _zipClient;
  17. private readonly IHttpClient _httpClient;
  18. private readonly IApplicationPaths _appPaths;
  19. private readonly ILogger _logger;
  20. public FFMpegDownloader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient)
  21. {
  22. _logger = logger;
  23. _appPaths = appPaths;
  24. _httpClient = httpClient;
  25. _zipClient = zipClient;
  26. }
  27. public async Task<FFMpegInfo> GetFFMpegInfo()
  28. {
  29. var assembly = GetType().Assembly;
  30. var prefix = GetType().Namespace + ".";
  31. var srch = prefix + "ffmpeg";
  32. var resource = assembly.GetManifestResourceNames().First(r => r.StartsWith(srch));
  33. var filename =
  34. resource.Substring(resource.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) + prefix.Length);
  35. var versionedDirectoryPath = Path.Combine(GetMediaToolsPath(true),
  36. Path.GetFileNameWithoutExtension(filename));
  37. if (!Directory.Exists(versionedDirectoryPath))
  38. {
  39. Directory.CreateDirectory(versionedDirectoryPath);
  40. }
  41. await ExtractTools(assembly, resource, versionedDirectoryPath).ConfigureAwait(false);
  42. return new FFMpegInfo
  43. {
  44. ProbePath = Path.Combine(versionedDirectoryPath, "ffprobe.exe"),
  45. Path = Path.Combine(versionedDirectoryPath, "ffmpeg.exe"),
  46. Version = Path.GetFileNameWithoutExtension(versionedDirectoryPath)
  47. };
  48. }
  49. /// <summary>
  50. /// Extracts the tools.
  51. /// </summary>
  52. /// <param name="assembly">The assembly.</param>
  53. /// <param name="zipFileResourcePath">The zip file resource path.</param>
  54. /// <param name="targetPath">The target path.</param>
  55. private async Task ExtractTools(Assembly assembly, string zipFileResourcePath, string targetPath)
  56. {
  57. using (var resourceStream = assembly.GetManifestResourceStream(zipFileResourcePath))
  58. {
  59. _zipClient.ExtractAll(resourceStream, targetPath, false);
  60. }
  61. try
  62. {
  63. await DownloadFonts(targetPath).ConfigureAwait(false);
  64. }
  65. catch (Exception ex)
  66. {
  67. _logger.ErrorException("Error getting ffmpeg font files", ex);
  68. }
  69. }
  70. private const string FontUrl = "https://www.dropbox.com/s/9nb76tybcsw5xrk/ARIALUNI.zip?dl=1";
  71. /// <summary>
  72. /// Extracts the fonts.
  73. /// </summary>
  74. /// <param name="targetPath">The target path.</param>
  75. private async Task DownloadFonts(string targetPath)
  76. {
  77. var fontsDirectory = Path.Combine(targetPath, "fonts");
  78. if (!Directory.Exists(fontsDirectory))
  79. {
  80. Directory.CreateDirectory(fontsDirectory);
  81. }
  82. const string fontFilename = "ARIALUNI.TTF";
  83. var fontFile = Path.Combine(fontsDirectory, fontFilename);
  84. if (!File.Exists(fontFile))
  85. {
  86. await DownloadFontFile(fontsDirectory, fontFilename).ConfigureAwait(false);
  87. }
  88. await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
  89. }
  90. /// <summary>
  91. /// Downloads the font file.
  92. /// </summary>
  93. /// <param name="fontsDirectory">The fonts directory.</param>
  94. /// <param name="fontFilename">The font filename.</param>
  95. /// <returns>Task.</returns>
  96. private async Task DownloadFontFile(string fontsDirectory, string fontFilename)
  97. {
  98. var existingFile = Directory
  99. .EnumerateFiles(_appPaths.ProgramDataPath, fontFilename, SearchOption.AllDirectories)
  100. .FirstOrDefault();
  101. if (existingFile != null)
  102. {
  103. try
  104. {
  105. File.Copy(existingFile, Path.Combine(fontsDirectory, fontFilename), true);
  106. return;
  107. }
  108. catch (IOException ex)
  109. {
  110. // Log this, but don't let it fail the operation
  111. _logger.ErrorException("Error copying file", ex);
  112. }
  113. }
  114. var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
  115. {
  116. Url = FontUrl,
  117. Progress = new Progress<double>()
  118. });
  119. _zipClient.ExtractAll(tempFile, fontsDirectory, true);
  120. try
  121. {
  122. File.Delete(tempFile);
  123. }
  124. catch (IOException ex)
  125. {
  126. // Log this, but don't let it fail the operation
  127. _logger.ErrorException("Error deleting temp file {0}", ex, tempFile);
  128. }
  129. }
  130. /// <summary>
  131. /// Writes the font config file.
  132. /// </summary>
  133. /// <param name="fontsDirectory">The fonts directory.</param>
  134. /// <returns>Task.</returns>
  135. private async Task WriteFontConfigFile(string fontsDirectory)
  136. {
  137. const string fontConfigFilename = "fonts.conf";
  138. var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename);
  139. if (!File.Exists(fontConfigFile))
  140. {
  141. var contents = string.Format("<?xml version=\"1.0\"?><fontconfig><dir>{0}</dir><alias><family>Arial</family><prefer>Arial Unicode MS</prefer></alias></fontconfig>", fontsDirectory);
  142. var bytes = Encoding.UTF8.GetBytes(contents);
  143. using (var fileStream = new FileStream(fontConfigFile, FileMode.Create, FileAccess.Write,
  144. FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize,
  145. FileOptions.Asynchronous))
  146. {
  147. await fileStream.WriteAsync(bytes, 0, bytes.Length);
  148. }
  149. }
  150. }
  151. /// <summary>
  152. /// Gets the media tools path.
  153. /// </summary>
  154. /// <param name="create">if set to <c>true</c> [create].</param>
  155. /// <returns>System.String.</returns>
  156. private string GetMediaToolsPath(bool create)
  157. {
  158. var path = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
  159. if (create && !Directory.Exists(path))
  160. {
  161. Directory.CreateDirectory(path);
  162. }
  163. return path;
  164. }
  165. }
  166. public class FFMpegInfo
  167. {
  168. public string Path { get; set; }
  169. public string ProbePath { get; set; }
  170. public string Version { get; set; }
  171. }
  172. }