diff options
Diffstat (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs')
| -rw-r--r-- | MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 317 |
1 files changed, 184 insertions, 133 deletions
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 340990373..23e63dad0 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -14,18 +14,17 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using CommonIO; using MediaBrowser.Model.Configuration; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; +using MediaBrowser.Model.Diagnostics; +using MediaBrowser.Model.System; namespace MediaBrowser.MediaEncoding.Encoder { @@ -80,14 +79,21 @@ namespace MediaBrowser.MediaEncoding.Encoder protected readonly Func<IMediaSourceManager> MediaSourceManager; private readonly IHttpClient _httpClient; private readonly IZipClient _zipClient; - private readonly IMemoryStreamProvider _memoryStreamProvider; + private readonly IProcessFactory _processFactory; + private readonly IMemoryStreamFactory _memoryStreamProvider; private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>(); private readonly bool _hasExternalEncoder; - private string _originalFFMpegPath; - private string _originalFFProbePath; + private readonly string _originalFFMpegPath; + private readonly string _originalFFProbePath; + private readonly int DefaultImageExtractionTimeoutMs; + private readonly bool EnableEncoderFontFile; - public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func<ISubtitleEncoder> subtitleEncoder, Func<IMediaSourceManager> mediaSourceManager, IHttpClient httpClient, IZipClient zipClient, IMemoryStreamProvider memoryStreamProvider) + private readonly IEnvironmentInfo _environmentInfo; + + public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func<ISubtitleEncoder> subtitleEncoder, Func<IMediaSourceManager> mediaSourceManager, IHttpClient httpClient, IZipClient zipClient, IMemoryStreamFactory memoryStreamProvider, IProcessFactory processFactory, + int defaultImageExtractionTimeoutMs, + bool enableEncoderFontFile, IEnvironmentInfo environmentInfo) { _logger = logger; _jsonSerializer = jsonSerializer; @@ -103,18 +109,80 @@ namespace MediaBrowser.MediaEncoding.Encoder _httpClient = httpClient; _zipClient = zipClient; _memoryStreamProvider = memoryStreamProvider; + _processFactory = processFactory; + DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs; + EnableEncoderFontFile = enableEncoderFontFile; + _environmentInfo = environmentInfo; FFProbePath = ffProbePath; FFMpegPath = ffMpegPath; _originalFFProbePath = ffProbePath; _originalFFMpegPath = ffMpegPath; _hasExternalEncoder = hasExternalEncoder; + + SetEnvironmentVariable(); + } + + private readonly object _logLock = new object(); + public void SetLogFilename(string name) + { + lock (_logLock) + { + try + { + _environmentInfo.SetProcessEnvironmentVariable("FFREPORT", "file=" + name + ":level=32"); + } + catch (Exception ex) + { + _logger.ErrorException("Error setting FFREPORT environment variable", ex); + } + } + } + + public void ClearLogFilename() + { + lock (_logLock) + { + try + { + _environmentInfo.SetProcessEnvironmentVariable("FFREPORT", null); + } + catch (Exception ex) + { + //_logger.ErrorException("Error setting FFREPORT environment variable", ex); + } + } + } + + private void SetEnvironmentVariable() + { + try + { + //_environmentInfo.SetProcessEnvironmentVariable("FFREPORT", "file=program-YYYYMMDD-HHMMSS.txt:level=32"); + } + catch (Exception ex) + { + _logger.ErrorException("Error setting FFREPORT environment variable", ex); + } + try + { + //_environmentInfo.SetUserEnvironmentVariable("FFREPORT", "file=program-YYYYMMDD-HHMMSS.txt:level=32"); + } + catch (Exception ex) + { + _logger.ErrorException("Error setting FFREPORT environment variable", ex); + } } public string EncoderLocationType { get { + if (_hasExternalEncoder) + { + return "External"; + } + if (string.IsNullOrWhiteSpace(FFMpegPath)) { return null; @@ -157,12 +225,12 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!string.IsNullOrWhiteSpace(FFMpegPath)) { - var result = new EncoderValidator(_logger).Validate(FFMpegPath); + var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath); SetAvailableDecoders(result.Item1); SetAvailableEncoders(result.Item2); - if (Environment.OSVersion.Platform == PlatformID.Win32NT) + if (EnableEncoderFontFile) { var directory = Path.GetDirectoryName(FFMpegPath); @@ -179,6 +247,12 @@ namespace MediaBrowser.MediaEncoding.Encoder { ConfigureEncoderPaths(); + if (_hasExternalEncoder) + { + LogPaths(); + return; + } + // If the path was passed in, save it into config now. var encodingOptions = GetEncodingOptions(); var appPath = encodingOptions.EncoderAppPath; @@ -188,7 +262,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!string.IsNullOrWhiteSpace(valueToSave)) { // if using system variable, don't save this. - if (IsSystemInstalledPath(valueToSave)) + if (IsSystemInstalledPath(valueToSave) || _hasExternalEncoder) { valueToSave = null; } @@ -203,6 +277,11 @@ namespace MediaBrowser.MediaEncoding.Encoder public async Task UpdateEncoderPath(string path, string pathType) { + if (_hasExternalEncoder) + { + return; + } + Tuple<string, string> newPaths; if (string.Equals(pathType, "system", StringComparison.OrdinalIgnoreCase)) @@ -218,7 +297,7 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new ArgumentNullException("path"); } - if (!File.Exists(path) && !Directory.Exists(path)) + if (!FileSystem.FileExists(path) && !FileSystem.DirectoryExists(path)) { throw new ResourceNotFoundException(); } @@ -240,7 +319,7 @@ namespace MediaBrowser.MediaEncoding.Encoder path = newPaths.Item1; - if (!ValidateVersion(path)) + if (!ValidateVersion(path, true)) { throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required."); } @@ -252,13 +331,18 @@ namespace MediaBrowser.MediaEncoding.Encoder Init(); } - private bool ValidateVersion(string path) + private bool ValidateVersion(string path, bool logOutput) { - return new EncoderValidator(_logger).ValidateVersion(path); + return new EncoderValidator(_logger, _processFactory).ValidateVersion(path, logOutput); } private void ConfigureEncoderPaths() { + if (_hasExternalEncoder) + { + return; + } + var appPath = GetEncodingOptions().EncoderAppPath; if (string.IsNullOrWhiteSpace(appPath)) @@ -287,12 +371,12 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!string.IsNullOrWhiteSpace(appPath)) { - if (Directory.Exists(appPath)) + if (FileSystem.DirectoryExists(appPath)) { return GetPathsFromDirectory(appPath); } - if (File.Exists(appPath)) + if (FileSystem.FileExists(appPath)) { return new Tuple<string, string>(appPath, GetProbePathFromEncoderPath(appPath)); } @@ -306,7 +390,7 @@ namespace MediaBrowser.MediaEncoding.Encoder string encoderPath = null; string probePath = null; - if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath)) + if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath, true)) { encoderPath = _originalFFMpegPath; probePath = _originalFFProbePath; @@ -314,7 +398,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (string.IsNullOrWhiteSpace(encoderPath)) { - if (ValidateVersion("ffmpeg") && ValidateVersion("ffprobe")) + if (ValidateVersion("ffmpeg", true) && ValidateVersion("ffprobe", false)) { encoderPath = "ffmpeg"; probePath = "ffprobe"; @@ -328,16 +412,16 @@ namespace MediaBrowser.MediaEncoding.Encoder { // Since we can't predict the file extension, first try directly within the folder // If that doesn't pan out, then do a recursive search - var files = Directory.GetFiles(path); + var files = FileSystem.GetFilePaths(path); var excludeExtensions = new[] { ".c" }; var ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); var ffprobePath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); - if (string.IsNullOrWhiteSpace(ffmpegPath) || !File.Exists(ffmpegPath)) + if (string.IsNullOrWhiteSpace(ffmpegPath) || !FileSystem.FileExists(ffmpegPath)) { - files = Directory.GetFiles(path, "*", SearchOption.AllDirectories); + files = FileSystem.GetFilePaths(path, true); ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); @@ -352,7 +436,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private string GetProbePathFromEncoderPath(string appPath) { - return Directory.GetFiles(Path.GetDirectoryName(appPath)) + return FileSystem.GetFilePaths(Path.GetDirectoryName(appPath)) .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase)); } @@ -432,7 +516,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (request.AnalyzeDurationSections > 0) { analyzeDuration = "-analyzeduration " + - (request.AnalyzeDurationSections*1000000).ToString(CultureInfo.InvariantCulture); + (request.AnalyzeDurationSections * 1000000).ToString(CultureInfo.InvariantCulture); } else { @@ -495,7 +579,6 @@ namespace MediaBrowser.MediaEncoding.Encoder /// <param name="videoType">Type of the video.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{MediaInfoResult}.</returns> - /// <exception cref="System.ApplicationException">ffprobe failed - streams and format are both null.</exception> private async Task<MediaInfo> GetMediaInfoInternal(string inputPath, string primaryPath, MediaProtocol protocol, @@ -509,31 +592,24 @@ namespace MediaBrowser.MediaEncoding.Encoder ? "{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_chapters -show_format" : "{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_format"; - var process = new Process + var process = _processFactory.Create(new ProcessOptions { - StartInfo = new ProcessStartInfo - { - CreateNoWindow = true, - UseShellExecute = false, - - // Must consume both or ffmpeg may hang due to deadlocks. See comments below. - RedirectStandardOutput = true, - //RedirectStandardError = true, - RedirectStandardInput = false, - FileName = FFProbePath, - Arguments = string.Format(args, - probeSizeArgument, inputPath).Trim(), + CreateNoWindow = true, + UseShellExecute = false, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false - }, + // Must consume both or ffmpeg may hang due to deadlocks. See comments below. + RedirectStandardOutput = true, + FileName = FFProbePath, + Arguments = string.Format(args, probeSizeArgument, inputPath).Trim(), + IsHidden = true, + ErrorDialog = false, EnableRaisingEvents = true - }; + }); _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - using (var processWrapper = new ProcessWrapper(process, this, _logger, false)) + using (var processWrapper = new ProcessWrapper(process, this, _logger)) { await _ffProbeResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -558,7 +634,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (result.streams == null && result.format == null) { - throw new ApplicationException("ffprobe failed - streams and format are both null."); + throw new Exception("ffprobe failed - streams and format are both null."); } if (result.streams != null) @@ -595,7 +671,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } catch { - StopProcess(processWrapper, 100, true); + StopProcess(processWrapper, 100); throw; } @@ -644,31 +720,25 @@ namespace MediaBrowser.MediaEncoding.Encoder var args = "{0} -i {1} -map 0:v:{2} -an -filter:v idet -frames:v 500 -an -f null /dev/null"; - var process = new Process + var process = _processFactory.Create(new ProcessOptions { - StartInfo = new ProcessStartInfo - { - CreateNoWindow = true, - UseShellExecute = false, - - // Must consume both or ffmpeg may hang due to deadlocks. See comments below. - //RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = false, - FileName = FFMpegPath, - Arguments = string.Format(args, probeSizeArgument, inputPath, videoStream.Index.ToString(CultureInfo.InvariantCulture)).Trim(), + CreateNoWindow = true, + UseShellExecute = false, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false - }, + // Must consume both or ffmpeg may hang due to deadlocks. See comments below. + RedirectStandardError = true, + FileName = FFMpegPath, + Arguments = string.Format(args, probeSizeArgument, inputPath, videoStream.Index.ToString(CultureInfo.InvariantCulture)).Trim(), + IsHidden = true, + ErrorDialog = false, EnableRaisingEvents = true - }; + }); _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); var idetFoundInterlaced = false; - using (var processWrapper = new ProcessWrapper(process, this, _logger, false)) + using (var processWrapper = new ProcessWrapper(process, this, _logger)) { try { @@ -711,7 +781,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } catch { - StopProcess(processWrapper, 100, true); + StopProcess(processWrapper, 100); throw; } @@ -864,7 +934,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } var tempExtractPath = Path.Combine(ConfigurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg"); - Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath)); + FileSystem.CreateDirectory(Path.GetDirectoryName(tempExtractPath)); // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600. // This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar @@ -898,7 +968,7 @@ namespace MediaBrowser.MediaEncoding.Encoder var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty; var enableThumbnail = !new List<string> { "wtv" }.Contains(container ?? string.Empty, StringComparer.OrdinalIgnoreCase); - var thumbnail = enableThumbnail ? ",thumbnail=30" : string.Empty; + var thumbnail = enableThumbnail ? ",thumbnail=24" : string.Empty; // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case. var args = useIFrame ? string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}{4}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail) : @@ -916,22 +986,19 @@ namespace MediaBrowser.MediaEncoding.Encoder args = string.Format("-ss {0} ", GetTimeParameter(offset.Value)) + args; } - var process = new Process + var process = _processFactory.Create(new ProcessOptions { - StartInfo = new ProcessStartInfo - { - CreateNoWindow = true, - UseShellExecute = false, - FileName = FFMpegPath, - Arguments = args, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false - } - }; + CreateNoWindow = true, + UseShellExecute = false, + FileName = FFMpegPath, + Arguments = args, + IsHidden = true, + ErrorDialog = false + }); _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - using (var processWrapper = new ProcessWrapper(process, this, _logger, false)) + using (var processWrapper = new ProcessWrapper(process, this, _logger)) { await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -944,14 +1011,14 @@ namespace MediaBrowser.MediaEncoding.Encoder var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs; if (timeoutMs <= 0) { - timeoutMs = Environment.Is64BitOperatingSystem ? (Environment.ProcessorCount > 2 ? 14000 : 20000) : 40000; + timeoutMs = DefaultImageExtractionTimeoutMs; } ranToCompletion = process.WaitForExit(timeoutMs); if (!ranToCompletion) { - StopProcess(processWrapper, 1000, false); + StopProcess(processWrapper, 1000); } } @@ -961,7 +1028,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; - var file = new FileInfo(tempExtractPath); + var file = FileSystem.GetFileInfo(tempExtractPath); if (exitCode == -1 || !file.Exists || file.Length == 0) { @@ -969,7 +1036,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger.Error(msg); - throw new ApplicationException(msg); + throw new Exception(msg); } return tempExtractPath; @@ -1022,19 +1089,15 @@ namespace MediaBrowser.MediaEncoding.Encoder args = probeSize + " " + args; } - var process = new Process + var process = _processFactory.Create(new ProcessOptions { - StartInfo = new ProcessStartInfo - { - CreateNoWindow = true, - UseShellExecute = false, - FileName = FFMpegPath, - Arguments = args, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false, - RedirectStandardInput = true - } - }; + CreateNoWindow = true, + UseShellExecute = false, + FileName = FFMpegPath, + Arguments = args, + IsHidden = true, + ErrorDialog = false + }); _logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments); @@ -1042,7 +1105,7 @@ namespace MediaBrowser.MediaEncoding.Encoder bool ranToCompletion = false; - using (var processWrapper = new ProcessWrapper(process, this, _logger, true)) + using (var processWrapper = new ProcessWrapper(process, this, _logger)) { try { @@ -1065,7 +1128,7 @@ namespace MediaBrowser.MediaEncoding.Encoder cancellationToken.ThrowIfCancellationRequested(); - var jpegCount = Directory.GetFiles(targetDirectory) + var jpegCount = FileSystem.GetFilePaths(targetDirectory) .Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase)); isResponsive = (jpegCount > lastCount); @@ -1074,7 +1137,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!ranToCompletion) { - StopProcess(processWrapper, 1000, false); + StopProcess(processWrapper, 1000); } } finally @@ -1090,7 +1153,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger.Error(msg); - throw new ApplicationException(msg); + throw new Exception(msg); } } } @@ -1107,7 +1170,8 @@ namespace MediaBrowser.MediaEncoding.Encoder LibraryManager, SessionManager, SubtitleEncoder(), - MediaSourceManager()) + MediaSourceManager(), + _processFactory) .Start(options, progress, cancellationToken).ConfigureAwait(false); await job.TaskCompletionSource.Task.ConfigureAwait(false); @@ -1127,7 +1191,8 @@ namespace MediaBrowser.MediaEncoding.Encoder LibraryManager, SessionManager, SubtitleEncoder(), - MediaSourceManager()) + MediaSourceManager(), + _processFactory) .Start(options, progress, cancellationToken).ConfigureAwait(false); await job.TaskCompletionSource.Task.ConfigureAwait(false); @@ -1144,40 +1209,25 @@ namespace MediaBrowser.MediaEncoding.Encoder _runningProcesses.Add(process); } } - private void StopProcess(ProcessWrapper process, int waitTimeMs, bool enableForceKill) + private void StopProcess(ProcessWrapper process, int waitTimeMs) { try { - _logger.Info("Killing ffmpeg process"); - - if (process.IsRedirectingStdin) + if (process.Process.WaitForExit(waitTimeMs)) { - try - { - process.Process.StandardInput.WriteLine("q"); - } - catch (Exception) - { - _logger.Error("Error sending q command to process"); - } + return; } + } + catch (Exception ex) + { + _logger.Error("Error in WaitForExit", ex); + } - try - { - if (process.Process.WaitForExit(waitTimeMs)) - { - return; - } - } - catch (Exception ex) - { - _logger.Error("Error in WaitForExit", ex); - } + try + { + _logger.Info("Killing ffmpeg process"); - if (enableForceKill) - { - process.Process.Kill(); - } + process.Process.Kill(); } catch (Exception ex) { @@ -1198,14 +1248,17 @@ namespace MediaBrowser.MediaEncoding.Encoder { if (!process.HasExited) { - StopProcess(process, 500, true); + StopProcess(process, 500); } } } public string EscapeSubtitleFilterPath(string path) { - return path.Replace('\\', '/').Replace(":/", "\\:/").Replace("'", "'\\\\\\''"); + // https://ffmpeg.org/ffmpeg-filters.html#Notes-on-filtergraph-escaping + // We need to double escape + + return path.Replace('\\', '/').Replace(":", "\\:").Replace("'", "'\\\\\\''"); } /// <summary> @@ -1231,25 +1284,23 @@ namespace MediaBrowser.MediaEncoding.Encoder private class ProcessWrapper : IDisposable { - public readonly Process Process; + public readonly IProcess Process; public bool HasExited; public int? ExitCode; private readonly MediaEncoder _mediaEncoder; private readonly ILogger _logger; - public bool IsRedirectingStdin { get; private set; } - public ProcessWrapper(Process process, MediaEncoder mediaEncoder, ILogger logger, bool isRedirectingStdin) + public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder, ILogger logger) { Process = process; _mediaEncoder = mediaEncoder; _logger = logger; Process.Exited += Process_Exited; - IsRedirectingStdin = isRedirectingStdin; } void Process_Exited(object sender, EventArgs e) { - var process = (Process)sender; + var process = (IProcess)sender; HasExited = true; @@ -1269,7 +1320,7 @@ namespace MediaBrowser.MediaEncoding.Encoder DisposeProcess(process); } - private void DisposeProcess(Process process) + private void DisposeProcess(IProcess process) { try { |
