diff options
Diffstat (limited to 'MediaBrowser.MediaEncoding/Encoder')
8 files changed, 262 insertions, 185 deletions
diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs index f42582270..5a554d26f 100644 --- a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs @@ -7,13 +7,13 @@ using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; using System.Threading.Tasks; -using CommonIO; +using MediaBrowser.Model.Diagnostics; namespace MediaBrowser.MediaEncoding.Encoder { public class AudioEncoder : BaseEncoder { - public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager) + public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager, processFactory) { } @@ -114,5 +114,6 @@ namespace MediaBrowser.MediaEncoding.Encoder { get { return false; } } + } } diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs index c7b78aae3..b8087fded 100644 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs @@ -11,13 +11,12 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; -using CommonIO; +using MediaBrowser.Model.Diagnostics; using MediaBrowser.Model.Dlna; namespace MediaBrowser.MediaEncoding.Encoder @@ -33,6 +32,7 @@ namespace MediaBrowser.MediaEncoding.Encoder protected readonly ISessionManager SessionManager; protected readonly ISubtitleEncoder SubtitleEncoder; protected readonly IMediaSourceManager MediaSourceManager; + protected IProcessFactory ProcessFactory; protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); @@ -44,7 +44,7 @@ namespace MediaBrowser.MediaEncoding.Encoder ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, - IMediaSourceManager mediaSourceManager) + IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) { MediaEncoder = mediaEncoder; Logger = logger; @@ -55,6 +55,7 @@ namespace MediaBrowser.MediaEncoding.Encoder SessionManager = sessionManager; SubtitleEncoder = subtitleEncoder; MediaSourceManager = mediaSourceManager; + ProcessFactory = processFactory; } public async Task<EncodingJob> Start(EncodingJobOptions options, @@ -73,27 +74,23 @@ namespace MediaBrowser.MediaEncoding.Encoder var commandLineArgs = await GetCommandLineArguments(encodingJob).ConfigureAwait(false); - var process = new Process + var process = ProcessFactory.Create(new ProcessOptions { - StartInfo = new ProcessStartInfo - { - CreateNoWindow = true, - UseShellExecute = false, - - // Must consume both stdout and stderr or deadlocks may occur - //RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, + CreateNoWindow = true, + UseShellExecute = false, - FileName = MediaEncoder.EncoderPath, - Arguments = commandLineArgs, + // Must consume both stdout and stderr or deadlocks may occur + //RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false - }, + FileName = MediaEncoder.EncoderPath, + Arguments = commandLineArgs, + IsHidden = true, + ErrorDialog = false, EnableRaisingEvents = true - }; + }); var workingDirectory = GetWorkingDirectory(options); if (!string.IsNullOrWhiteSpace(workingDirectory)) @@ -110,7 +107,7 @@ namespace MediaBrowser.MediaEncoding.Encoder FileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath)); // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - encodingJob.LogFileStream = FileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true); + encodingJob.LogFileStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true); var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine); await encodingJob.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationToken).ConfigureAwait(false); @@ -147,7 +144,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return encodingJob; } - private void Cancel(Process process, EncodingJob job) + private void Cancel(IProcess process, EncodingJob job) { Logger.Info("Killing ffmpeg process for {0}", job.OutputFilePath); @@ -162,7 +159,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// </summary> /// <param name="process">The process.</param> /// <param name="job">The job.</param> - private void OnFfMpegProcessExited(Process process, EncodingJob job) + private void OnFfMpegProcessExited(IProcess process, EncodingJob job) { job.HasExited = true; @@ -215,7 +212,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } try { - job.TaskCompletionSource.TrySetException(new ApplicationException("Encoding failed")); + job.TaskCompletionSource.TrySetException(new Exception("Encoding failed")); } catch { @@ -773,7 +770,6 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) { param = "-pix_fmt yuv420p " + param; diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 7acff8fc8..b37e783b8 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; +using MediaBrowser.Model.Diagnostics; using MediaBrowser.Model.Logging; namespace MediaBrowser.MediaEncoding.Encoder @@ -8,10 +10,12 @@ namespace MediaBrowser.MediaEncoding.Encoder public class EncoderValidator { private readonly ILogger _logger; + private readonly IProcessFactory _processFactory; - public EncoderValidator(ILogger logger) + public EncoderValidator(ILogger logger, IProcessFactory processFactory) { _logger = logger; + _processFactory = processFactory; } public Tuple<List<string>, List<string>> Validate(string encoderPath) @@ -26,15 +30,19 @@ namespace MediaBrowser.MediaEncoding.Encoder return new Tuple<List<string>, List<string>>(decoders, encoders); } - public bool ValidateVersion(string encoderAppPath) + public bool ValidateVersion(string encoderAppPath, bool logOutput) { string output = string.Empty; try { output = GetProcessOutput(encoderAppPath, "-version"); } - catch + catch (Exception ex) { + if (logOutput) + { + _logger.ErrorException("Error validating encoder", ex); + } } if (string.IsNullOrWhiteSpace(output)) @@ -42,11 +50,27 @@ namespace MediaBrowser.MediaEncoding.Encoder return false; } + if (logOutput) + { + _logger.Info("ffmpeg info: {0}", output); + } + if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1) { return false; } + output = " " + output + " "; + + for (var i = 2013; i <= 2015; i++) + { + var yearString = i.ToString(CultureInfo.InvariantCulture); + if (output.IndexOf(" " + yearString + " ", StringComparison.OrdinalIgnoreCase) != -1) + { + return false; + } + } + return true; } @@ -57,8 +81,9 @@ namespace MediaBrowser.MediaEncoding.Encoder { output = GetProcessOutput(encoderAppPath, "-decoders"); } - catch + catch (Exception ) { + //_logger.ErrorException("Error detecting available decoders", ex); } var found = new List<string>(); @@ -140,19 +165,16 @@ namespace MediaBrowser.MediaEncoding.Encoder private string GetProcessOutput(string path, string arguments) { - var process = new Process + var process = _processFactory.Create(new ProcessOptions { - StartInfo = new ProcessStartInfo - { - CreateNoWindow = true, - UseShellExecute = false, - FileName = path, - Arguments = arguments, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false, - RedirectStandardOutput = true - } - }; + CreateNoWindow = true, + UseShellExecute = false, + FileName = path, + Arguments = arguments, + IsHidden = true, + ErrorDialog = false, + RedirectStandardOutput = true + }); _logger.Info("Running {0} {1}", path, arguments); diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs index 0c7ff1b76..b5ff5cbb6 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs @@ -11,6 +11,7 @@ using MediaBrowser.Model.Net; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs index f811a8d48..e197bdb6f 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs @@ -606,9 +606,12 @@ namespace MediaBrowser.MediaEncoding.Encoder { // vaapi will throw an error with this input // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99. - if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase) && videoStream.Level == -99) + if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase)) { - return false; + if (videoStream.Level == -99 || videoStream.Level == 15) + { + return false; + } } } return true; diff --git a/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs b/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs index 71306e0ec..42048ab9e 100644 --- a/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs +++ b/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs @@ -3,9 +3,11 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; -using CommonIO; +using MediaBrowser.Model.IO; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; +using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; @@ -86,9 +88,9 @@ namespace MediaBrowser.MediaEncoding.Encoder /// <returns>Task.</returns> private async Task DownloadFontFile(string fontsDirectory, string fontFilename, IProgress<double> progress) { - var existingFile = Directory - .EnumerateFiles(_appPaths.ProgramDataPath, fontFilename, SearchOption.AllDirectories) - .FirstOrDefault(); + var existingFile = _fileSystem + .GetFilePaths(_appPaths.ProgramDataPath, true) + .FirstOrDefault(i => string.Equals(fontFilename, Path.GetFileName(i), StringComparison.OrdinalIgnoreCase)); if (existingFile != null) { @@ -168,8 +170,8 @@ namespace MediaBrowser.MediaEncoding.Encoder var bytes = Encoding.UTF8.GetBytes(contents); - using (var fileStream = _fileSystem.GetFileStream(fontConfigFile, FileMode.Create, FileAccess.Write, - FileShare.Read, true)) + using (var fileStream = _fileSystem.GetFileStream(fontConfigFile, FileOpenMode.Create, FileAccessMode.Write, + FileShareMode.Read, true)) { await fileStream.WriteAsync(bytes, 0, bytes.Length); } 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 { diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs index 457fbe2c2..cbbca479a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs @@ -8,13 +8,13 @@ using MediaBrowser.Model.Logging; using System; using System.IO; using System.Threading.Tasks; -using CommonIO; +using MediaBrowser.Model.Diagnostics; namespace MediaBrowser.MediaEncoding.Encoder { public class VideoEncoder : BaseEncoder { - public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager) + public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager, processFactory) { } @@ -191,5 +191,6 @@ namespace MediaBrowser.MediaEncoding.Encoder { get { return true; } } + } }
\ No newline at end of file |
