ImageEncoder.cs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. using MediaBrowser.Controller.MediaEncoding;
  2. using MediaBrowser.Model.Logging;
  3. using System;
  4. using System.Diagnostics;
  5. using System.Globalization;
  6. using System.IO;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. namespace MediaBrowser.MediaEncoding.Encoder
  10. {
  11. public class ImageEncoder
  12. {
  13. private readonly string _ffmpegPath;
  14. private readonly ILogger _logger;
  15. private readonly CultureInfo _usCulture = new CultureInfo("en-US");
  16. private static readonly SemaphoreSlim ResourcePool = new SemaphoreSlim(5, 5);
  17. public ImageEncoder(string ffmpegPath, ILogger logger)
  18. {
  19. _ffmpegPath = ffmpegPath;
  20. _logger = logger;
  21. }
  22. public async Task<Stream> EncodeImage(ImageEncodingOptions options, CancellationToken cancellationToken)
  23. {
  24. ValidateInput(options);
  25. var process = new Process
  26. {
  27. StartInfo = new ProcessStartInfo
  28. {
  29. CreateNoWindow = true,
  30. UseShellExecute = false,
  31. FileName = _ffmpegPath,
  32. Arguments = GetArguments(options),
  33. WindowStyle = ProcessWindowStyle.Hidden,
  34. ErrorDialog = false,
  35. RedirectStandardOutput = true,
  36. RedirectStandardError = true
  37. }
  38. };
  39. await ResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
  40. process.Start();
  41. var memoryStream = new MemoryStream();
  42. #pragma warning disable 4014
  43. // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
  44. process.StandardOutput.BaseStream.CopyToAsync(memoryStream);
  45. #pragma warning restore 4014
  46. // MUST read both stdout and stderr asynchronously or a deadlock may occurr
  47. process.BeginErrorReadLine();
  48. var ranToCompletion = process.WaitForExit(5000);
  49. if (!ranToCompletion)
  50. {
  51. try
  52. {
  53. _logger.Info("Killing ffmpeg process");
  54. process.Kill();
  55. process.WaitForExit(1000);
  56. }
  57. catch (Exception ex)
  58. {
  59. _logger.ErrorException("Error killing process", ex);
  60. }
  61. }
  62. ResourcePool.Release();
  63. var exitCode = ranToCompletion ? process.ExitCode : -1;
  64. process.Dispose();
  65. if (exitCode == -1 || memoryStream.Length == 0)
  66. {
  67. memoryStream.Dispose();
  68. var msg = string.Format("ffmpeg image encoding failed for {0}", options.InputPath);
  69. _logger.Error(msg);
  70. throw new ApplicationException(msg);
  71. }
  72. memoryStream.Position = 0;
  73. return memoryStream;
  74. }
  75. private string GetArguments(ImageEncodingOptions options)
  76. {
  77. var vfScale = GetFilterGraph(options);
  78. var outputFormat = GetOutputFormat(options);
  79. return string.Format("-i file:\"{0}\" {1} -f {2}",
  80. options.InputPath,
  81. vfScale,
  82. outputFormat);
  83. }
  84. private string GetFilterGraph(ImageEncodingOptions options)
  85. {
  86. if (!options.Width.HasValue &&
  87. !options.Height.HasValue &&
  88. !options.MaxHeight.HasValue &&
  89. !options.MaxWidth.HasValue)
  90. {
  91. return string.Empty;
  92. }
  93. var widthScale = "-1";
  94. var heightScale = "-1";
  95. if (options.MaxWidth.HasValue)
  96. {
  97. widthScale = "min(iw," + options.MaxWidth.Value.ToString(_usCulture) + ")";
  98. }
  99. else if (options.Width.HasValue)
  100. {
  101. widthScale = options.Width.Value.ToString(_usCulture);
  102. }
  103. if (options.MaxHeight.HasValue)
  104. {
  105. heightScale = "min(ih," + options.MaxHeight.Value.ToString(_usCulture) + ")";
  106. }
  107. else if (options.Height.HasValue)
  108. {
  109. heightScale = options.Height.Value.ToString(_usCulture);
  110. }
  111. var scaleMethod = "lanczos";
  112. return string.Format("-vf scale=\"{0}:{1}\" -sws_flags {2}",
  113. widthScale,
  114. heightScale,
  115. scaleMethod);
  116. }
  117. private string GetOutputFormat(ImageEncodingOptions options)
  118. {
  119. return options.Format;
  120. }
  121. private void ValidateInput(ImageEncodingOptions options)
  122. {
  123. }
  124. }
  125. }