aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs')
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs385
1 files changed, 131 insertions, 254 deletions
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index d922f1068..292457788 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -3,17 +3,14 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Session;
using MediaBrowser.MediaEncoding.Probing;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Diagnostics;
@@ -22,6 +19,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.MediaEncoding.Encoder
@@ -32,340 +30,223 @@ namespace MediaBrowser.MediaEncoding.Encoder
public class MediaEncoder : IMediaEncoder, IDisposable
{
/// <summary>
- /// The _logger
- /// </summary>
- private readonly ILogger _logger;
-
- /// <summary>
- /// Gets the json serializer.
+ /// Gets the encoder path.
/// </summary>
- /// <value>The json serializer.</value>
- private readonly IJsonSerializer _jsonSerializer;
+ /// <value>The encoder path.</value>
+ public string EncoderPath => FFmpegPath;
/// <summary>
- /// The _thumbnail resource pool
+ /// The location of the discovered FFmpeg tool.
/// </summary>
- private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1);
-
- public string FFMpegPath { get; private set; }
-
- public string FFProbePath { get; private set; }
+ public FFmpegLocation EncoderLocation { get; private set; }
+ private readonly ILogger _logger;
+ private readonly IJsonSerializer _jsonSerializer;
+ private string FFmpegPath;
+ private string FFprobePath;
protected readonly IServerConfigurationManager ConfigurationManager;
protected readonly IFileSystem FileSystem;
- protected readonly ILiveTvManager LiveTvManager;
- protected readonly IIsoManager IsoManager;
- protected readonly ILibraryManager LibraryManager;
- protected readonly IChannelManager ChannelManager;
- protected readonly ISessionManager SessionManager;
protected readonly Func<ISubtitleEncoder> SubtitleEncoder;
protected readonly Func<IMediaSourceManager> MediaSourceManager;
- private readonly IHttpClient _httpClient;
- private readonly IZipClient _zipClient;
private readonly IProcessFactory _processFactory;
+ private readonly int DefaultImageExtractionTimeoutMs;
+ private readonly string StartupOptionFFmpegPath;
+ private readonly string StartupOptionFFprobePath;
+ private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1);
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
- private readonly bool _hasExternalEncoder;
- private readonly string _originalFFMpegPath;
- private readonly string _originalFFProbePath;
- private readonly int DefaultImageExtractionTimeoutMs;
public MediaEncoder(
ILoggerFactory loggerFactory,
IJsonSerializer jsonSerializer,
- string ffMpegPath,
- string ffProbePath,
- bool hasExternalEncoder,
+ string startupOptionsFFmpegPath,
+ string startupOptionsFFprobePath,
IServerConfigurationManager configurationManager,
IFileSystem fileSystem,
- ILiveTvManager liveTvManager,
- IIsoManager isoManager,
- ILibraryManager libraryManager,
- IChannelManager channelManager,
- ISessionManager sessionManager,
Func<ISubtitleEncoder> subtitleEncoder,
Func<IMediaSourceManager> mediaSourceManager,
- IHttpClient httpClient,
- IZipClient zipClient,
IProcessFactory processFactory,
int defaultImageExtractionTimeoutMs)
{
_logger = loggerFactory.CreateLogger(nameof(MediaEncoder));
_jsonSerializer = jsonSerializer;
+ StartupOptionFFmpegPath = startupOptionsFFmpegPath;
+ StartupOptionFFprobePath = startupOptionsFFprobePath;
ConfigurationManager = configurationManager;
FileSystem = fileSystem;
- LiveTvManager = liveTvManager;
- IsoManager = isoManager;
- LibraryManager = libraryManager;
- ChannelManager = channelManager;
- SessionManager = sessionManager;
SubtitleEncoder = subtitleEncoder;
- MediaSourceManager = mediaSourceManager;
- _httpClient = httpClient;
- _zipClient = zipClient;
_processFactory = processFactory;
DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs;
- FFProbePath = ffProbePath;
- FFMpegPath = ffMpegPath;
- _originalFFProbePath = ffProbePath;
- _originalFFMpegPath = ffMpegPath;
- _hasExternalEncoder = hasExternalEncoder;
}
- public string EncoderLocationType
+ /// <summary>
+ /// Run at startup or if the user removes a Custom path from transcode page.
+ /// Sets global variables FFmpegPath.
+ /// Precedence is: Config > CLI > $PATH
+ /// </summary>
+ public void SetFFmpegPath()
{
- get
+ // ToDo - Finalise removal of the --ffprobe switch
+ if (!string.IsNullOrEmpty(StartupOptionFFprobePath))
{
- if (_hasExternalEncoder)
- {
- return "External";
- }
-
- if (string.IsNullOrWhiteSpace(FFMpegPath))
- {
- return null;
- }
-
- if (IsSystemInstalledPath(FFMpegPath))
- {
- return "System";
- }
-
- return "Custom";
+ _logger.LogWarning("--ffprobe switch is deprecated and shall be removed in the next release");
}
- }
- private bool IsSystemInstalledPath(string path)
- {
- if (path.IndexOf("/", StringComparison.Ordinal) == -1 && path.IndexOf("\\", StringComparison.Ordinal) == -1)
+ // 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
+ if (!ValidatePath(ConfigurationManager.GetConfiguration<EncodingOptions>("encoding").EncoderAppPath, FFmpegLocation.Custom))
{
- return true;
+ // 2) Check if the --ffmpeg CLI switch has been given
+ if (!ValidatePath(StartupOptionFFmpegPath, FFmpegLocation.SetByArgument))
+ {
+ // 3) Search system $PATH environment variable for valid FFmpeg
+ if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System))
+ {
+ EncoderLocation = FFmpegLocation.NotFound;
+ FFmpegPath = null;
+ }
+ }
}
- return false;
- }
-
- public void Init()
- {
- InitPaths();
+ // Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
+ var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
+ config.EncoderAppPathDisplay = FFmpegPath ?? string.Empty;
+ ConfigurationManager.SaveConfiguration("encoding", config);
- if (!string.IsNullOrWhiteSpace(FFMpegPath))
+ // Only if mpeg path is set, try and set path to probe
+ if (FFmpegPath != null)
{
- var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath);
+ // Determine a probe path from the mpeg path
+ FFprobePath = Regex.Replace(FFmpegPath, @"[^\/\\]+?(\.[^\/\\\n.]+)?$", @"ffprobe$1");
+
+ // Interrogate to understand what coders are supported
+ var result = new EncoderValidator(_logger, _processFactory).GetAvailableCoders(FFmpegPath);
SetAvailableDecoders(result.decoders);
SetAvailableEncoders(result.encoders);
}
- }
-
- private void InitPaths()
- {
- ConfigureEncoderPaths();
-
- if (_hasExternalEncoder)
- {
- LogPaths();
- return;
- }
-
- // If the path was passed in, save it into config now.
- var encodingOptions = GetEncodingOptions();
- var appPath = encodingOptions.EncoderAppPath;
-
- var valueToSave = FFMpegPath;
-
- if (!string.IsNullOrWhiteSpace(valueToSave))
- {
- // if using system variable, don't save this.
- if (IsSystemInstalledPath(valueToSave) || _hasExternalEncoder)
- {
- valueToSave = null;
- }
- }
- if (!string.Equals(valueToSave, appPath, StringComparison.Ordinal))
- {
- encodingOptions.EncoderAppPath = valueToSave;
- ConfigurationManager.SaveConfiguration("encoding", encodingOptions);
- }
+ _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation.ToString(), FFmpegPath ?? string.Empty);
}
+ /// <summary>
+ /// Triggered from the Settings > Transcoding UI page when users submits Custom FFmpeg path to use.
+ /// Only write the new path to xml if it exists. Do not perform validation checks on ffmpeg here.
+ /// </summary>
+ /// <param name="path"></param>
+ /// <param name="pathType"></param>
public void UpdateEncoderPath(string path, string pathType)
{
- if (_hasExternalEncoder)
- {
- return;
- }
+ string newPath;
_logger.LogInformation("Attempting to update encoder path to {0}. pathType: {1}", path ?? string.Empty, pathType ?? string.Empty);
- Tuple<string, string> newPaths;
-
- if (string.Equals(pathType, "system", StringComparison.OrdinalIgnoreCase))
- {
- path = "ffmpeg";
-
- newPaths = TestForInstalledVersions();
- }
- else if (string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase))
+ if (!string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase))
{
- if (string.IsNullOrWhiteSpace(path))
- {
- throw new ArgumentNullException(nameof(path));
- }
-
- if (!File.Exists(path) && !Directory.Exists(path))
- {
- throw new ResourceNotFoundException();
- }
- newPaths = GetEncoderPaths(path);
+ throw new ArgumentException("Unexpected pathType value");
}
- else
+ else if (string.IsNullOrWhiteSpace(path))
{
- throw new ArgumentException("Unexpected pathType value");
+ // User had cleared the custom path in UI
+ newPath = string.Empty;
}
-
- if (string.IsNullOrWhiteSpace(newPaths.Item1))
+ else if (File.Exists(path))
{
- throw new ResourceNotFoundException("ffmpeg not found");
+ newPath = path;
}
- if (string.IsNullOrWhiteSpace(newPaths.Item2))
+ else if (Directory.Exists(path))
{
- throw new ResourceNotFoundException("ffprobe not found");
+ // Given path is directory, so resolve down to filename
+ newPath = GetEncoderPathFromDirectory(path, "ffmpeg");
}
-
- path = newPaths.Item1;
-
- if (!ValidateVersion(path, true))
+ else
{
- throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required.");
+ throw new ResourceNotFoundException();
}
- var config = GetEncodingOptions();
- config.EncoderAppPath = path;
+ // Write the new ffmpeg path to the xml as <EncoderAppPath>
+ // This ensures its not lost on next startup
+ var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
+ config.EncoderAppPath = newPath;
ConfigurationManager.SaveConfiguration("encoding", config);
- Init();
+ // Trigger SetFFmpegPath so we validate the new path and setup probe path
+ SetFFmpegPath();
}
- private bool ValidateVersion(string path, bool logOutput)
- {
- return new EncoderValidator(_logger, _processFactory).ValidateVersion(path, logOutput);
- }
-
- private void ConfigureEncoderPaths()
+ /// <summary>
+ /// Validates the supplied FQPN to ensure it is a ffmpeg utility.
+ /// If checks pass, global variable FFmpegPath and EncoderLocation are updated.
+ /// </summary>
+ /// <param name="path">FQPN to test</param>
+ /// <param name="location">Location (External, Custom, System) of tool</param>
+ /// <returns></returns>
+ private bool ValidatePath(string path, FFmpegLocation location)
{
- if (_hasExternalEncoder)
- {
- return;
- }
-
- var appPath = GetEncodingOptions().EncoderAppPath;
+ bool rc = false;
- if (string.IsNullOrWhiteSpace(appPath))
+ if (!string.IsNullOrEmpty(path))
{
- appPath = Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "ffmpeg");
- }
-
- var newPaths = GetEncoderPaths(appPath);
- if (string.IsNullOrWhiteSpace(newPaths.Item1) || string.IsNullOrWhiteSpace(newPaths.Item2) || IsSystemInstalledPath(appPath))
- {
- newPaths = TestForInstalledVersions();
- }
-
- if (!string.IsNullOrWhiteSpace(newPaths.Item1) && !string.IsNullOrWhiteSpace(newPaths.Item2))
- {
- FFMpegPath = newPaths.Item1;
- FFProbePath = newPaths.Item2;
- }
+ if (File.Exists(path))
+ {
+ rc = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, true);
- LogPaths();
- }
+ if (!rc)
+ {
+ _logger.LogWarning("FFmpeg: {0}: Failed version check: {1}", location.ToString(), path);
+ }
- private Tuple<string, string> GetEncoderPaths(string configuredPath)
- {
- var appPath = configuredPath;
+ // ToDo - Enable the ffmpeg validator. At the moment any version can be used.
+ rc = true;
- if (!string.IsNullOrWhiteSpace(appPath))
- {
- if (Directory.Exists(appPath))
- {
- return GetPathsFromDirectory(appPath);
+ FFmpegPath = path;
+ EncoderLocation = location;
}
-
- if (File.Exists(appPath))
+ else
{
- return new Tuple<string, string>(appPath, GetProbePathFromEncoderPath(appPath));
+ _logger.LogWarning("FFmpeg: {0}: File not found: {1}", location.ToString(), path);
}
}
- return new Tuple<string, string>(null, null);
+ return rc;
}
- private Tuple<string, string> TestForInstalledVersions()
+ private string GetEncoderPathFromDirectory(string path, string filename)
{
- string encoderPath = null;
- string probePath = null;
-
- if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath, true))
+ try
{
- encoderPath = _originalFFMpegPath;
- probePath = _originalFFProbePath;
- }
+ var files = FileSystem.GetFilePaths(path);
- if (string.IsNullOrWhiteSpace(encoderPath))
+ var excludeExtensions = new[] { ".c" };
+
+ return files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), filename, StringComparison.OrdinalIgnoreCase)
+ && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
+ }
+ catch (Exception)
{
- if (ValidateVersion("ffmpeg", true) && ValidateVersion("ffprobe", false))
- {
- encoderPath = "ffmpeg";
- probePath = "ffprobe";
- }
+ // Trap all exceptions, like DirNotExists, and return null
+ return null;
}
-
- return new Tuple<string, string>(encoderPath, probePath);
}
- private Tuple<string, string> GetPathsFromDirectory(string path)
+ /// <summary>
+ /// Search the system $PATH environment variable looking for given filename.
+ /// </summary>
+ /// <param name="fileName"></param>
+ /// <returns></returns>
+ private string ExistsOnSystemPath(string filename)
{
- // 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 = FileSystem.GetFilePaths(path);
-
- var excludeExtensions = new[] { ".c" };
+ var values = Environment.GetEnvironmentVariable("PATH");
- 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))
+ foreach (var path in values.Split(Path.PathSeparator))
{
- files = FileSystem.GetFilePaths(path, true);
-
- ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
+ var candidatePath = GetEncoderPathFromDirectory(path, filename);
- if (!string.IsNullOrWhiteSpace(ffmpegPath))
+ if (!string.IsNullOrEmpty(candidatePath))
{
- ffprobePath = GetProbePathFromEncoderPath(ffmpegPath);
+ return candidatePath;
}
}
-
- return new Tuple<string, string>(ffmpegPath, ffprobePath);
- }
-
- private string GetProbePathFromEncoderPath(string appPath)
- {
- return FileSystem.GetFilePaths(Path.GetDirectoryName(appPath))
- .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase));
- }
-
- private void LogPaths()
- {
- _logger.LogInformation("FFMpeg: {0}", FFMpegPath ?? "not found");
- _logger.LogInformation("FFProbe: {0}", FFProbePath ?? "not found");
- }
-
- private EncodingOptions GetEncodingOptions()
- {
- return ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
+ return null;
}
private List<string> _encoders = new List<string>();
@@ -413,12 +294,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
/// <summary>
- /// Gets the encoder path.
- /// </summary>
- /// <value>The encoder path.</value>
- public string EncoderPath => FFMpegPath;
-
- /// <summary>
/// Gets the media info.
/// </summary>
/// <param name="request">The request.</param>
@@ -489,7 +364,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
RedirectStandardOutput = true,
- FileName = FFProbePath,
+ FileName = FFprobePath,
Arguments = string.Format(args, probeSizeArgument, inputPath).Trim(),
IsHidden = true,
@@ -691,10 +566,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
CreateNoWindow = true,
UseShellExecute = false,
- FileName = FFMpegPath,
+ FileName = FFmpegPath,
Arguments = args,
IsHidden = true,
- ErrorDialog = false
+ ErrorDialog = false,
+ EnableRaisingEvents = true
});
_logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
@@ -813,10 +689,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
CreateNoWindow = true,
UseShellExecute = false,
- FileName = FFMpegPath,
+ FileName = FFmpegPath,
Arguments = args,
IsHidden = true,
- ErrorDialog = false
+ ErrorDialog = false,
+ EnableRaisingEvents = true
});
_logger.LogInformation(process.StartInfo.FileName + " " + process.StartInfo.Arguments);