FFMpegLoader.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. using MediaBrowser.Common.Configuration;
  2. using MediaBrowser.Common.Net;
  3. using MediaBrowser.Model.IO;
  4. using MediaBrowser.Model.Logging;
  5. using Mono.Unix.Native;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. using Emby.Server.Core;
  13. using Emby.Server.Core.FFMpeg;
  14. namespace MediaBrowser.Server.Startup.Common.FFMpeg
  15. {
  16. public class FFMpegLoader
  17. {
  18. private readonly IHttpClient _httpClient;
  19. private readonly IApplicationPaths _appPaths;
  20. private readonly ILogger _logger;
  21. private readonly IZipClient _zipClient;
  22. private readonly IFileSystem _fileSystem;
  23. private readonly NativeEnvironment _environment;
  24. private readonly FFMpegInstallInfo _ffmpegInstallInfo;
  25. public FFMpegLoader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem, NativeEnvironment environment, FFMpegInstallInfo ffmpegInstallInfo)
  26. {
  27. _logger = logger;
  28. _appPaths = appPaths;
  29. _httpClient = httpClient;
  30. _zipClient = zipClient;
  31. _fileSystem = fileSystem;
  32. _environment = environment;
  33. _ffmpegInstallInfo = ffmpegInstallInfo;
  34. }
  35. public async Task<FFMpegInfo> GetFFMpegInfo(NativeEnvironment environment, StartupOptions options, IProgress<double> progress)
  36. {
  37. var customffMpegPath = options.GetOption("-ffmpeg");
  38. var customffProbePath = options.GetOption("-ffprobe");
  39. if (!string.IsNullOrWhiteSpace(customffMpegPath) && !string.IsNullOrWhiteSpace(customffProbePath))
  40. {
  41. return new FFMpegInfo
  42. {
  43. ProbePath = customffProbePath,
  44. EncoderPath = customffMpegPath,
  45. Version = "external"
  46. };
  47. }
  48. var downloadInfo = _ffmpegInstallInfo;
  49. var version = downloadInfo.Version;
  50. if (string.Equals(version, "path", StringComparison.OrdinalIgnoreCase))
  51. {
  52. return new FFMpegInfo
  53. {
  54. ProbePath = downloadInfo.FFProbeFilename,
  55. EncoderPath = downloadInfo.FFMpegFilename,
  56. Version = version
  57. };
  58. }
  59. if (string.Equals(version, "0", StringComparison.OrdinalIgnoreCase))
  60. {
  61. return new FFMpegInfo();
  62. }
  63. var rootEncoderPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
  64. var versionedDirectoryPath = Path.Combine(rootEncoderPath, version);
  65. var info = new FFMpegInfo
  66. {
  67. ProbePath = Path.Combine(versionedDirectoryPath, downloadInfo.FFProbeFilename),
  68. EncoderPath = Path.Combine(versionedDirectoryPath, downloadInfo.FFMpegFilename),
  69. Version = version
  70. };
  71. _fileSystem.CreateDirectory(versionedDirectoryPath);
  72. var excludeFromDeletions = new List<string> { versionedDirectoryPath };
  73. if (!_fileSystem.FileExists(info.ProbePath) || !_fileSystem.FileExists(info.EncoderPath))
  74. {
  75. // ffmpeg not present. See if there's an older version we can start with
  76. var existingVersion = GetExistingVersion(info, rootEncoderPath);
  77. // No older version. Need to download and block until complete
  78. if (existingVersion == null)
  79. {
  80. var success = await DownloadFFMpeg(downloadInfo, versionedDirectoryPath, progress).ConfigureAwait(false);
  81. if (!success)
  82. {
  83. return new FFMpegInfo();
  84. }
  85. }
  86. else
  87. {
  88. info = existingVersion;
  89. versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath);
  90. excludeFromDeletions.Add(versionedDirectoryPath);
  91. }
  92. }
  93. // Allow just one of these to be overridden, if desired.
  94. if (!string.IsNullOrWhiteSpace(customffMpegPath))
  95. {
  96. info.EncoderPath = customffMpegPath;
  97. }
  98. if (!string.IsNullOrWhiteSpace(customffProbePath))
  99. {
  100. info.EncoderPath = customffProbePath;
  101. }
  102. return info;
  103. }
  104. private FFMpegInfo GetExistingVersion(FFMpegInfo info, string rootEncoderPath)
  105. {
  106. var encoderFilename = Path.GetFileName(info.EncoderPath);
  107. var probeFilename = Path.GetFileName(info.ProbePath);
  108. foreach (var directory in Directory.EnumerateDirectories(rootEncoderPath, "*", SearchOption.TopDirectoryOnly)
  109. .ToList())
  110. {
  111. var allFiles = Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories).ToList();
  112. var encoder = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), encoderFilename, StringComparison.OrdinalIgnoreCase));
  113. var probe = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), probeFilename, StringComparison.OrdinalIgnoreCase));
  114. if (!string.IsNullOrWhiteSpace(encoder) &&
  115. !string.IsNullOrWhiteSpace(probe))
  116. {
  117. return new FFMpegInfo
  118. {
  119. EncoderPath = encoder,
  120. ProbePath = probe,
  121. Version = Path.GetFileName(Path.GetDirectoryName(probe))
  122. };
  123. }
  124. }
  125. return null;
  126. }
  127. private async Task<bool> DownloadFFMpeg(FFMpegInstallInfo downloadinfo, string directory, IProgress<double> progress)
  128. {
  129. foreach (var url in downloadinfo.DownloadUrls)
  130. {
  131. progress.Report(0);
  132. try
  133. {
  134. var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
  135. {
  136. Url = url,
  137. CancellationToken = CancellationToken.None,
  138. Progress = progress
  139. }).ConfigureAwait(false);
  140. ExtractFFMpeg(downloadinfo, tempFile, directory);
  141. return true;
  142. }
  143. catch (Exception ex)
  144. {
  145. _logger.ErrorException("Error downloading {0}", ex, url);
  146. }
  147. }
  148. return false;
  149. }
  150. private void ExtractFFMpeg(FFMpegInstallInfo downloadinfo, string tempFile, string targetFolder)
  151. {
  152. _logger.Info("Extracting ffmpeg from {0}", tempFile);
  153. var tempFolder = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString());
  154. _fileSystem.CreateDirectory(tempFolder);
  155. try
  156. {
  157. ExtractArchive(downloadinfo, tempFile, tempFolder);
  158. var files = Directory.EnumerateFiles(tempFolder, "*", SearchOption.AllDirectories)
  159. .ToList();
  160. foreach (var file in files.Where(i =>
  161. {
  162. var filename = Path.GetFileName(i);
  163. return
  164. string.Equals(filename, downloadinfo.FFProbeFilename, StringComparison.OrdinalIgnoreCase) ||
  165. string.Equals(filename, downloadinfo.FFMpegFilename, StringComparison.OrdinalIgnoreCase);
  166. }))
  167. {
  168. var targetFile = Path.Combine(targetFolder, Path.GetFileName(file));
  169. _fileSystem.CopyFile(file, targetFile, true);
  170. SetFilePermissions(targetFile);
  171. }
  172. }
  173. finally
  174. {
  175. DeleteFile(tempFile);
  176. }
  177. }
  178. private void SetFilePermissions(string path)
  179. {
  180. // Linux: File permission to 666, and user's execute bit
  181. if (_environment.OperatingSystem == OperatingSystem.Bsd || _environment.OperatingSystem == OperatingSystem.Linux || _environment.OperatingSystem == OperatingSystem.Osx)
  182. {
  183. _logger.Info("Syscall.chmod {0} FilePermissions.DEFFILEMODE | FilePermissions.S_IRWXU | FilePermissions.S_IXGRP | FilePermissions.S_IXOTH", path);
  184. Syscall.chmod(path, FilePermissions.DEFFILEMODE | FilePermissions.S_IRWXU | FilePermissions.S_IXGRP | FilePermissions.S_IXOTH);
  185. }
  186. }
  187. private void ExtractArchive(FFMpegInstallInfo downloadinfo, string archivePath, string targetPath)
  188. {
  189. _logger.Info("Extracting {0} to {1}", archivePath, targetPath);
  190. if (string.Equals(downloadinfo.ArchiveType, "7z", StringComparison.OrdinalIgnoreCase))
  191. {
  192. _zipClient.ExtractAllFrom7z(archivePath, targetPath, true);
  193. }
  194. else if (string.Equals(downloadinfo.ArchiveType, "gz", StringComparison.OrdinalIgnoreCase))
  195. {
  196. _zipClient.ExtractAllFromTar(archivePath, targetPath, true);
  197. }
  198. }
  199. private void DeleteFile(string path)
  200. {
  201. try
  202. {
  203. _fileSystem.DeleteFile(path);
  204. }
  205. catch (IOException ex)
  206. {
  207. _logger.ErrorException("Error deleting temp file {0}", ex, path);
  208. }
  209. }
  210. }
  211. }