|
@@ -40,8 +40,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
/// </summary>
|
|
/// </summary>
|
|
public FFmpegLocation EncoderLocation { get; private set; }
|
|
public FFmpegLocation EncoderLocation { get; private set; }
|
|
|
|
|
|
- private FFmpegLocation ProbeLocation;
|
|
|
|
-
|
|
|
|
private readonly ILogger _logger;
|
|
private readonly ILogger _logger;
|
|
private readonly IJsonSerializer _jsonSerializer;
|
|
private readonly IJsonSerializer _jsonSerializer;
|
|
private string FFmpegPath;
|
|
private string FFmpegPath;
|
|
@@ -55,11 +53,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
private readonly string StartupOptionFFmpegPath;
|
|
private readonly string StartupOptionFFmpegPath;
|
|
private readonly string StartupOptionFFprobePath;
|
|
private readonly string StartupOptionFFprobePath;
|
|
|
|
|
|
- /// <summary>
|
|
|
|
- /// Enum to identify the two types of FF utilities of interest.
|
|
|
|
- /// </summary>
|
|
|
|
- private enum FFtype { Mpeg, Probe };
|
|
|
|
-
|
|
|
|
private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1);
|
|
private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1);
|
|
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
|
|
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
|
|
|
|
|
|
@@ -93,14 +86,20 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
/// </summary>
|
|
/// </summary>
|
|
public void Init()
|
|
public void Init()
|
|
{
|
|
{
|
|
- // 1) Custom path stored in config/encoding xml file under tag <EncoderAppPathCustom> takes precedence
|
|
|
|
- if (!ValidatePath(FFtype.Mpeg, ConfigurationManager.GetConfiguration<EncodingOptions>("encoding").EncoderAppPathCustom, FFmpegLocation.Custom))
|
|
|
|
|
|
+ // ToDo - Finalise removal of the --ffprobe switch
|
|
|
|
+ if (!string.IsNullOrEmpty(StartupOptionFFprobePath))
|
|
|
|
+ {
|
|
|
|
+ _logger.LogWarning("--ffprobe switch is deprecated and shall be removed in the next release");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
|
|
|
|
+ if (!ValidatePath(ConfigurationManager.GetConfiguration<EncodingOptions>("encoding").EncoderAppPath, FFmpegLocation.Custom))
|
|
{
|
|
{
|
|
// 2) Check if the --ffmpeg CLI switch has been given
|
|
// 2) Check if the --ffmpeg CLI switch has been given
|
|
- if (!ValidatePath(FFtype.Mpeg, StartupOptionFFmpegPath, FFmpegLocation.SetByArgument))
|
|
|
|
|
|
+ if (!ValidatePath(StartupOptionFFmpegPath, FFmpegLocation.SetByArgument))
|
|
{
|
|
{
|
|
// 3) Search system $PATH environment variable for valid FFmpeg
|
|
// 3) Search system $PATH environment variable for valid FFmpeg
|
|
- if (!ValidatePath(FFtype.Mpeg, ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System))
|
|
|
|
|
|
+ if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System))
|
|
{
|
|
{
|
|
EncoderLocation = FFmpegLocation.NotFound;
|
|
EncoderLocation = FFmpegLocation.NotFound;
|
|
FFmpegPath = null;
|
|
FFmpegPath = null;
|
|
@@ -108,110 +107,80 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- ReInit();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /// <summary>
|
|
|
|
- /// Writes the currently used FFmpeg to config/encoding.xml file.
|
|
|
|
- /// Sets the FFprobe path if not currently set.
|
|
|
|
- /// Interrogates the FFmpeg tool to identify what encoders/decodres are available.
|
|
|
|
- /// </summary>
|
|
|
|
- private void ReInit()
|
|
|
|
- {
|
|
|
|
- // Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPath> so it appears in UI
|
|
|
|
|
|
+ // Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
|
|
var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
|
|
var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
|
|
- config.EncoderAppPath = FFmpegPath ?? string.Empty;
|
|
|
|
|
|
+ config.EncoderAppPathDisplay = FFmpegPath ?? string.Empty;
|
|
ConfigurationManager.SaveConfiguration("encoding", config);
|
|
ConfigurationManager.SaveConfiguration("encoding", config);
|
|
|
|
|
|
- // Clear probe settings in case probe validation fails
|
|
|
|
- ProbeLocation = FFmpegLocation.NotFound;
|
|
|
|
- FFprobePath = null;
|
|
|
|
-
|
|
|
|
// Only if mpeg path is set, try and set path to probe
|
|
// Only if mpeg path is set, try and set path to probe
|
|
if (FFmpegPath != null)
|
|
if (FFmpegPath != null)
|
|
{
|
|
{
|
|
- if (EncoderLocation == FFmpegLocation.Custom || StartupOptionFFprobePath == null)
|
|
|
|
- {
|
|
|
|
- // If mpeg was read from config, or CLI switch not given, try and set probe from mpeg path
|
|
|
|
- ValidatePath(FFtype.Probe, GetProbePathFromEncoderPath(FFmpegPath), EncoderLocation);
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- // Else try and set probe path from CLI switch
|
|
|
|
- ValidatePath(FFtype.Probe, StartupOptionFFmpegPath, FFmpegLocation.SetByArgument);
|
|
|
|
- }
|
|
|
|
|
|
+ // Determine a probe path from the mpeg path
|
|
|
|
+ FFprobePath = Regex.Replace(FFmpegPath, @"[^\/\\]+?(\.[^\/\\\n.]+)?$", @"ffprobe$1");
|
|
|
|
|
|
- // Interrogate to understand what coders it supports
|
|
|
|
|
|
+ // Interrogate to understand what coders are supported
|
|
var result = new EncoderValidator(_logger, _processFactory).GetAvailableCoders(FFmpegPath);
|
|
var result = new EncoderValidator(_logger, _processFactory).GetAvailableCoders(FFmpegPath);
|
|
|
|
|
|
SetAvailableDecoders(result.decoders);
|
|
SetAvailableDecoders(result.decoders);
|
|
SetAvailableEncoders(result.encoders);
|
|
SetAvailableEncoders(result.encoders);
|
|
}
|
|
}
|
|
|
|
|
|
- // Stamp FFmpeg paths to the log file
|
|
|
|
- LogPaths();
|
|
|
|
|
|
+ _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation.ToString(), FFmpegPath ?? string.Empty);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
- /// Triggered from the Settings > Trascoding UI page when users sumits Custom FFmpeg path to use.
|
|
|
|
|
|
+ /// 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>
|
|
/// </summary>
|
|
/// <param name="path"></param>
|
|
/// <param name="path"></param>
|
|
/// <param name="pathType"></param>
|
|
/// <param name="pathType"></param>
|
|
public void UpdateEncoderPath(string path, string pathType)
|
|
public void UpdateEncoderPath(string path, string pathType)
|
|
{
|
|
{
|
|
|
|
+ string newPath;
|
|
|
|
+
|
|
_logger.LogInformation("Attempting to update encoder path to {0}. pathType: {1}", path ?? string.Empty, pathType ?? string.Empty);
|
|
_logger.LogInformation("Attempting to update encoder path to {0}. pathType: {1}", path ?? string.Empty, pathType ?? string.Empty);
|
|
|
|
|
|
if (!string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase))
|
|
if (!string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
{
|
|
throw new ArgumentException("Unexpected pathType value");
|
|
throw new ArgumentException("Unexpected pathType value");
|
|
}
|
|
}
|
|
-
|
|
|
|
- if (string.IsNullOrWhiteSpace(path))
|
|
|
|
|
|
+ else if (string.IsNullOrWhiteSpace(path))
|
|
{
|
|
{
|
|
- // User had cleared the custom path in UI. Clear the Custom config
|
|
|
|
- // setting and perform full Init to reinspect any CLI switches and system $PATH
|
|
|
|
- var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
|
|
|
|
- config.EncoderAppPathCustom = string.Empty;
|
|
|
|
- ConfigurationManager.SaveConfiguration("encoding", config);
|
|
|
|
-
|
|
|
|
- Init();
|
|
|
|
|
|
+ // User had cleared the custom path in UI
|
|
|
|
+ newPath = string.Empty;
|
|
}
|
|
}
|
|
- else if (!File.Exists(path) && !Directory.Exists(path))
|
|
|
|
|
|
+ else if (File.Exists(path))
|
|
{
|
|
{
|
|
- // Given path is neither file or folder
|
|
|
|
- throw new ResourceNotFoundException();
|
|
|
|
|
|
+ newPath = path;
|
|
|
|
+ }
|
|
|
|
+ else if (Directory.Exists(path))
|
|
|
|
+ {
|
|
|
|
+ // Given path is directory, so resolve down to filename
|
|
|
|
+ newPath = GetEncoderPathFromDirectory(path, "ffmpeg");
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
- // Supplied path could be either file path or folder path.
|
|
|
|
- // Resolve down to file path and validate
|
|
|
|
- if (!ValidatePath(FFtype.Mpeg, GetEncoderPath(path), FFmpegLocation.Custom))
|
|
|
|
- {
|
|
|
|
- throw new ResourceNotFoundException("Failed validation checks.");
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- // Write the validated mpeg path to the xml as <EncoderAppPathCustom>
|
|
|
|
- // This ensures its not lost on new startup
|
|
|
|
- var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
|
|
|
|
- config.EncoderAppPathCustom = FFmpegPath;
|
|
|
|
- ConfigurationManager.SaveConfiguration("encoding", config);
|
|
|
|
-
|
|
|
|
- ReInit();
|
|
|
|
- }
|
|
|
|
|
|
+ throw new ResourceNotFoundException();
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // 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);
|
|
|
|
+
|
|
|
|
+ // Trigger Init so we validate the new path and setup probe path
|
|
|
|
+ Init();
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
- /// Validates the supplied FQPN to ensure it is a FFxxx utility.
|
|
|
|
- /// If checks pass, global variable FFmpegPath (or FFprobePath) and
|
|
|
|
- /// EncoderLocation (or ProbeLocation) are updated.
|
|
|
|
|
|
+ /// Validates the supplied FQPN to ensure it is a ffmpeg utility.
|
|
|
|
+ /// If checks pass, global variable FFmpegPath and EncoderLocation are updated.
|
|
/// </summary>
|
|
/// </summary>
|
|
- /// <param name="type">Either mpeg or probe</param>
|
|
|
|
/// <param name="path">FQPN to test</param>
|
|
/// <param name="path">FQPN to test</param>
|
|
/// <param name="location">Location (External, Custom, System) of tool</param>
|
|
/// <param name="location">Location (External, Custom, System) of tool</param>
|
|
/// <returns></returns>
|
|
/// <returns></returns>
|
|
- private bool ValidatePath(FFtype type, string path, FFmpegLocation location)
|
|
|
|
|
|
+ private bool ValidatePath(string path, FFmpegLocation location)
|
|
{
|
|
{
|
|
bool rc = false;
|
|
bool rc = false;
|
|
|
|
|
|
@@ -219,51 +188,28 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
{
|
|
{
|
|
if (File.Exists(path))
|
|
if (File.Exists(path))
|
|
{
|
|
{
|
|
- rc = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, false);
|
|
|
|
|
|
+ rc = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, true);
|
|
|
|
|
|
- // Only update the global variables if the checks passed
|
|
|
|
- if (rc)
|
|
|
|
|
|
+ if (!rc)
|
|
{
|
|
{
|
|
- if (type == FFtype.Mpeg)
|
|
|
|
- {
|
|
|
|
- FFmpegPath = path;
|
|
|
|
- EncoderLocation = location;
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- FFprobePath = path;
|
|
|
|
- ProbeLocation = location;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- _logger.LogError("{0}: {1}: Failed version check: {2}", type.ToString(), location.ToString(), path);
|
|
|
|
|
|
+ _logger.LogWarning("FFmpeg: {0}: Failed version check: {1}", location.ToString(), path);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // ToDo - Enable the ffmpeg validator. At the moment any version can be used.
|
|
|
|
+ rc = true;
|
|
|
|
+
|
|
|
|
+ FFmpegPath = path;
|
|
|
|
+ EncoderLocation = location;
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
- _logger.LogError("{0}: {1}: File not found: {2}", type.ToString(), location.ToString(), path);
|
|
|
|
|
|
+ _logger.LogWarning("FFmpeg: {0}: File not found: {1}", location.ToString(), path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return rc;
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
|
|
- private string GetEncoderPath(string path)
|
|
|
|
- {
|
|
|
|
- if (Directory.Exists(path))
|
|
|
|
- {
|
|
|
|
- return GetEncoderPathFromDirectory(path, "ffmpeg");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (File.Exists(path))
|
|
|
|
- {
|
|
|
|
- return path;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return null;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
private string GetEncoderPathFromDirectory(string path, string filename)
|
|
private string GetEncoderPathFromDirectory(string path, string filename)
|
|
{
|
|
{
|
|
try
|
|
try
|
|
@@ -282,25 +228,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
|
- /// With the given path string, replaces the filename with ffprobe, taking case
|
|
|
|
- /// of any file extension (like .exe on windows).
|
|
|
|
- /// </summary>
|
|
|
|
- /// <param name="appPath"></param>
|
|
|
|
- /// <returns></returns>
|
|
|
|
- private string GetProbePathFromEncoderPath(string appPath)
|
|
|
|
- {
|
|
|
|
- if (!string.IsNullOrEmpty(appPath))
|
|
|
|
- {
|
|
|
|
- const string pattern = @"[^\/\\]+?(\.[^\/\\\n.]+)?$";
|
|
|
|
- const string substitution = @"ffprobe$1";
|
|
|
|
-
|
|
|
|
- return Regex.Replace(appPath, pattern, substitution);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return null;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Search the system $PATH environment variable looking for given filename.
|
|
/// Search the system $PATH environment variable looking for given filename.
|
|
/// </summary>
|
|
/// </summary>
|
|
@@ -322,12 +249,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
return null;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
- private void LogPaths()
|
|
|
|
- {
|
|
|
|
- _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation.ToString(), FFmpegPath ?? string.Empty);
|
|
|
|
- _logger.LogInformation("FFprobe: {0}: {1}", ProbeLocation.ToString(), FFprobePath ?? string.Empty);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
private List<string> _encoders = new List<string>();
|
|
private List<string> _encoders = new List<string>();
|
|
public void SetAvailableEncoders(IEnumerable<string> list)
|
|
public void SetAvailableEncoders(IEnumerable<string> list)
|
|
{
|
|
{
|