EncoderValidator.cs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Linq;
  5. using System.Text.RegularExpressions;
  6. using MediaBrowser.Model.Diagnostics;
  7. using Microsoft.Extensions.Logging;
  8. namespace MediaBrowser.MediaEncoding.Encoder
  9. {
  10. public class EncoderValidator
  11. {
  12. private readonly ILogger _logger;
  13. private readonly IProcessFactory _processFactory;
  14. public EncoderValidator(ILogger logger, IProcessFactory processFactory)
  15. {
  16. _logger = logger;
  17. _processFactory = processFactory;
  18. }
  19. public (IEnumerable<string> decoders, IEnumerable<string> encoders) Validate(string encoderPath)
  20. {
  21. _logger.LogInformation("Validating media encoder at {EncoderPath}", encoderPath);
  22. var decoders = GetCodecs(encoderPath, Codec.Decoder);
  23. var encoders = GetCodecs(encoderPath, Codec.Encoder);
  24. _logger.LogInformation("Encoder validation complete");
  25. return (decoders, encoders);
  26. }
  27. public bool ValidateVersion(string encoderAppPath, bool logOutput)
  28. {
  29. string output = null;
  30. try
  31. {
  32. output = GetProcessOutput(encoderAppPath, "-version");
  33. }
  34. catch (Exception ex)
  35. {
  36. if (logOutput)
  37. {
  38. _logger.LogError(ex, "Error validating encoder");
  39. }
  40. }
  41. if (string.IsNullOrWhiteSpace(output))
  42. {
  43. return false;
  44. }
  45. _logger.LogDebug("ffmpeg output: {Output}", output);
  46. if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1)
  47. {
  48. return false;
  49. }
  50. output = " " + output + " ";
  51. for (var i = 2013; i <= 2015; i++)
  52. {
  53. var yearString = i.ToString(CultureInfo.InvariantCulture);
  54. if (output.IndexOf(" " + yearString + " ", StringComparison.OrdinalIgnoreCase) != -1)
  55. {
  56. return false;
  57. }
  58. }
  59. return true;
  60. }
  61. private static readonly string[] requiredDecoders = new[]
  62. {
  63. "mpeg2video",
  64. "h264_qsv",
  65. "hevc_qsv",
  66. "mpeg2_qsv",
  67. "vc1_qsv",
  68. "h264_cuvid",
  69. "hevc_cuvid",
  70. "dts",
  71. "ac3",
  72. "aac",
  73. "mp3",
  74. "h264",
  75. "hevc"
  76. };
  77. private static readonly string[] requiredEncoders = new[]
  78. {
  79. "libx264",
  80. "libx265",
  81. "mpeg4",
  82. "msmpeg4",
  83. "libvpx",
  84. "libvpx-vp9",
  85. "aac",
  86. "libmp3lame",
  87. "libopus",
  88. "libvorbis",
  89. "srt",
  90. "h264_nvenc",
  91. "hevc_nvenc",
  92. "h264_qsv",
  93. "hevc_qsv",
  94. "h264_omx",
  95. "hevc_omx",
  96. "h264_vaapi",
  97. "hevc_vaapi",
  98. "ac3"
  99. };
  100. private enum Codec
  101. {
  102. Encoder,
  103. Decoder
  104. }
  105. private IEnumerable<string> GetCodecs(string encoderAppPath, Codec codec)
  106. {
  107. string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
  108. string output = null;
  109. try
  110. {
  111. output = GetProcessOutput(encoderAppPath, "-" + codecstr);
  112. }
  113. catch (Exception ex)
  114. {
  115. _logger.LogError(ex, "Error detecting available {Codec}", codecstr);
  116. }
  117. if (string.IsNullOrWhiteSpace(output))
  118. {
  119. return Enumerable.Empty<string>();
  120. }
  121. var required = codec == Codec.Encoder ? requiredEncoders : requiredDecoders;
  122. var found = Regex
  123. .Matches(output, @"^\s\S{6}\s(?<codec>[\w|-]+)\s+.+$", RegexOptions.Multiline)
  124. .Cast<Match>()
  125. .Select(x => x.Groups["codec"].Value)
  126. .Where(x => required.Contains(x));
  127. _logger.LogInformation("Available {Codec}: {Codecs}", codecstr, found);
  128. return found;
  129. }
  130. private string GetProcessOutput(string path, string arguments)
  131. {
  132. IProcess process = _processFactory.Create(new ProcessOptions
  133. {
  134. CreateNoWindow = true,
  135. UseShellExecute = false,
  136. FileName = path,
  137. Arguments = arguments,
  138. IsHidden = true,
  139. ErrorDialog = false,
  140. RedirectStandardOutput = true,
  141. // ffmpeg uses stderr to log info, don't show this
  142. RedirectStandardError = true
  143. });
  144. _logger.LogDebug("Running {Path} {Arguments}", path, arguments);
  145. using (process)
  146. {
  147. process.Start();
  148. try
  149. {
  150. return process.StandardOutput.ReadToEnd();
  151. }
  152. catch
  153. {
  154. _logger.LogWarning("Killing process {Path} {Arguments}", path, arguments);
  155. // Hate having to do this
  156. try
  157. {
  158. process.Kill();
  159. }
  160. catch (Exception ex)
  161. {
  162. _logger.LogError(ex, "Error killing process");
  163. }
  164. throw;
  165. }
  166. }
  167. }
  168. }
  169. }