FFMpegLoader.cs 8.7 KB

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