FFMpegProcess.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. using MediaBrowser.Common.Configuration;
  2. using MediaBrowser.Common.IO;
  3. using MediaBrowser.Controller.LiveTv;
  4. using MediaBrowser.Model.Entities;
  5. using MediaBrowser.Model.IO;
  6. using MediaBrowser.Model.Logging;
  7. using System;
  8. using System.Diagnostics;
  9. using System.IO;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. namespace MediaBrowser.MediaEncoding.Encoder
  13. {
  14. public class FFMpegProcess : IDisposable
  15. {
  16. private readonly string _ffmpegPath;
  17. private readonly ILogger _logger;
  18. private readonly IFileSystem _fileSystem;
  19. private readonly IApplicationPaths _appPaths;
  20. private readonly IIsoManager _isoManager;
  21. private readonly ILiveTvManager _liveTvManager;
  22. private Stream _logFileStream;
  23. private InternalEncodingTask _task;
  24. private IIsoMount _isoMount;
  25. public FFMpegProcess(string ffmpegPath, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths, IIsoManager isoManager, ILiveTvManager liveTvManager)
  26. {
  27. _ffmpegPath = ffmpegPath;
  28. _logger = logger;
  29. _fileSystem = fileSystem;
  30. _appPaths = appPaths;
  31. _isoManager = isoManager;
  32. _liveTvManager = liveTvManager;
  33. }
  34. public async Task Start(InternalEncodingTask task, Func<InternalEncodingTask,string,string> argumentsFactory)
  35. {
  36. _task = task;
  37. if (!File.Exists(_ffmpegPath))
  38. {
  39. throw new InvalidOperationException("ffmpeg was not found at " + _ffmpegPath);
  40. }
  41. Directory.CreateDirectory(Path.GetDirectoryName(task.Request.OutputPath));
  42. string mountedPath = null;
  43. if (task.InputVideoType.HasValue && task.InputVideoType == VideoType.Iso && task.IsoType.HasValue)
  44. {
  45. if (_isoManager.CanMount(task.MediaPath))
  46. {
  47. _isoMount = await _isoManager.Mount(task.MediaPath, CancellationToken.None).ConfigureAwait(false);
  48. mountedPath = _isoMount.MountedPath;
  49. }
  50. }
  51. var process = new Process
  52. {
  53. StartInfo = new ProcessStartInfo
  54. {
  55. CreateNoWindow = true,
  56. UseShellExecute = false,
  57. // Must consume both stdout and stderr or deadlocks may occur
  58. RedirectStandardOutput = true,
  59. RedirectStandardError = true,
  60. FileName = _ffmpegPath,
  61. WorkingDirectory = Path.GetDirectoryName(_ffmpegPath),
  62. Arguments = argumentsFactory(task, mountedPath),
  63. WindowStyle = ProcessWindowStyle.Hidden,
  64. ErrorDialog = false
  65. },
  66. EnableRaisingEvents = true
  67. };
  68. _logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
  69. var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-" + task.Id + ".txt");
  70. Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
  71. // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
  72. _logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
  73. process.Exited += process_Exited;
  74. try
  75. {
  76. process.Start();
  77. }
  78. catch (Exception ex)
  79. {
  80. _logger.ErrorException("Error starting ffmpeg", ex);
  81. task.OnError();
  82. DisposeLogFileStream();
  83. process.Dispose();
  84. throw;
  85. }
  86. task.OnBegin();
  87. // MUST read both stdout and stderr asynchronously or a deadlock may occurr
  88. process.BeginOutputReadLine();
  89. #pragma warning disable 4014
  90. // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
  91. process.StandardError.BaseStream.CopyToAsync(_logFileStream);
  92. #pragma warning restore 4014
  93. }
  94. async void process_Exited(object sender, EventArgs e)
  95. {
  96. var process = (Process)sender;
  97. if (_isoMount != null)
  98. {
  99. _isoMount.Dispose();
  100. _isoMount = null;
  101. }
  102. DisposeLogFileStream();
  103. try
  104. {
  105. _logger.Info("FFMpeg exited with code {0} for {1}", process.ExitCode, _task.Request.OutputPath);
  106. }
  107. catch
  108. {
  109. _logger.Info("FFMpeg exited with an error for {0}", _task.Request.OutputPath);
  110. }
  111. _task.OnCompleted();
  112. if (!string.IsNullOrEmpty(_task.LiveTvStreamId))
  113. {
  114. try
  115. {
  116. await _liveTvManager.CloseLiveStream(_task.LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
  117. }
  118. catch (Exception ex)
  119. {
  120. _logger.ErrorException("Error closing live tv stream", ex);
  121. }
  122. }
  123. }
  124. public void Dispose()
  125. {
  126. DisposeLogFileStream();
  127. }
  128. private void DisposeLogFileStream()
  129. {
  130. if (_logFileStream != null)
  131. {
  132. _logFileStream.Dispose();
  133. _logFileStream = null;
  134. }
  135. }
  136. }
  137. }