Reworked FFmpeg path discovery and always display to user

1) Reworked FFmpeg and FFprobe path discovery (CLI switch, Custom xml, system $PATH, UI update trigger).  Removed FFMpeg folder from Emby.Server.Implementations.  All path discovery now in MediaEncoder.

2) Always display FFmpeg path to user in Transcode page.

3) Allow user to remove a Custome FFmpeg path and return to using system $PATH (or --ffmpeg if available).

4) Remove unused code associated with 'prebuilt' FFmpeg.

5) Much improved logging during path discovery.
pull/844/head
PloughPuff 5 years ago committed by Ploughpuff
parent 5587dd8bfb
commit 20775116f7

@ -28,7 +28,6 @@ using Emby.Server.Implementations.Data;
using Emby.Server.Implementations.Devices;
using Emby.Server.Implementations.Diagnostics;
using Emby.Server.Implementations.Dto;
using Emby.Server.Implementations.FFMpeg;
using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.HttpServer.Security;
using Emby.Server.Implementations.IO;
@ -792,7 +791,8 @@ namespace Emby.Server.Implementations
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
serviceCollection.AddSingleton(ChapterManager);
RegisterMediaEncoder(serviceCollection);
MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(LoggerFactory, JsonSerializer, StartupOptions.FFmpegPath, StartupOptions.FFprobePath, ServerConfigurationManager, FileSystemManager, () => SubtitleEncoder, () => MediaSourceManager, ProcessFactory, 5000);
serviceCollection.AddSingleton(MediaEncoder);
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
serviceCollection.AddSingleton(EncodingManager);
@ -908,83 +908,25 @@ namespace Emby.Server.Implementations
return new ImageProcessor(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder);
}
protected virtual FFMpegInstallInfo GetFfmpegInstallInfo()
{
var info = new FFMpegInstallInfo();
// Windows builds: http://ffmpeg.zeranoe.com/builds/
// Linux builds: http://johnvansickle.com/ffmpeg/
// OS X builds: http://ffmpegmac.net/
// OS X x64: http://www.evermeet.cx/ffmpeg/
if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Linux)
{
info.FFMpegFilename = "ffmpeg";
info.FFProbeFilename = "ffprobe";
info.ArchiveType = "7z";
info.Version = "20170308";
}
else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows)
{
info.FFMpegFilename = "ffmpeg.exe";
info.FFProbeFilename = "ffprobe.exe";
info.Version = "20170308";
info.ArchiveType = "7z";
}
else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX)
{
info.FFMpegFilename = "ffmpeg";
info.FFProbeFilename = "ffprobe";
info.ArchiveType = "7z";
info.Version = "20170308";
}
return info;
}
protected virtual FFMpegInfo GetFFMpegInfo()
{
return new FFMpegLoader(ApplicationPaths, FileSystemManager, GetFfmpegInstallInfo())
.GetFFMpegInfo(StartupOptions);
}
/// <summary>
/// Registers the media encoder.
/// </summary>
/// <returns>Task.</returns>
private void RegisterMediaEncoder(IServiceCollection serviceCollection)
private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo)
{
string encoderPath = null;
string probePath = null;
var info = GetFFMpegInfo();
encoderPath = info.EncoderPath;
probePath = info.ProbePath;
var hasExternalEncoder = string.Equals(info.Version, "external", StringComparison.OrdinalIgnoreCase);
var mediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(
MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(
LoggerFactory,
JsonSerializer,
encoderPath,
probePath,
hasExternalEncoder,
StartupOptions.FFmpegPath,
StartupOptions.FFprobePath,
ServerConfigurationManager,
FileSystemManager,
LiveTvManager,
IsoManager,
LibraryManager,
ChannelManager,
SessionManager,
() => SubtitleEncoder,
() => MediaSourceManager,
HttpClient,
ZipClient,
ProcessFactory,
5000);
MediaEncoder = mediaEncoder;
serviceCollection.AddSingleton(MediaEncoder);
RegisterSingleInstance(MediaEncoder);
}
/// <summary>

@ -1,24 +0,0 @@
namespace Emby.Server.Implementations.FFMpeg
{
/// <summary>
/// Class FFMpegInfo
/// </summary>
public class FFMpegInfo
{
/// <summary>
/// Gets or sets the path.
/// </summary>
/// <value>The path.</value>
public string EncoderPath { get; set; }
/// <summary>
/// Gets or sets the probe path.
/// </summary>
/// <value>The probe path.</value>
public string ProbePath { get; set; }
/// <summary>
/// Gets or sets the version.
/// </summary>
/// <value>The version.</value>
public string Version { get; set; }
}
}

@ -1,17 +0,0 @@
namespace Emby.Server.Implementations.FFMpeg
{
public class FFMpegInstallInfo
{
public string Version { get; set; }
public string FFMpegFilename { get; set; }
public string FFProbeFilename { get; set; }
public string ArchiveType { get; set; }
public FFMpegInstallInfo()
{
Version = "Path";
FFMpegFilename = "ffmpeg";
FFProbeFilename = "ffprobe";
}
}
}

@ -1,132 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.FFMpeg
{
public class FFMpegLoader
{
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
private readonly FFMpegInstallInfo _ffmpegInstallInfo;
public FFMpegLoader(IApplicationPaths appPaths, IFileSystem fileSystem, FFMpegInstallInfo ffmpegInstallInfo)
{
_appPaths = appPaths;
_fileSystem = fileSystem;
_ffmpegInstallInfo = ffmpegInstallInfo;
}
public FFMpegInfo GetFFMpegInfo(IStartupOptions options)
{
var customffMpegPath = options.FFmpegPath;
var customffProbePath = options.FFprobePath;
if (!string.IsNullOrWhiteSpace(customffMpegPath) && !string.IsNullOrWhiteSpace(customffProbePath))
{
return new FFMpegInfo
{
ProbePath = customffProbePath,
EncoderPath = customffMpegPath,
Version = "external"
};
}
var downloadInfo = _ffmpegInstallInfo;
var prebuiltFolder = _appPaths.ProgramSystemPath;
var prebuiltffmpeg = Path.Combine(prebuiltFolder, downloadInfo.FFMpegFilename);
var prebuiltffprobe = Path.Combine(prebuiltFolder, downloadInfo.FFProbeFilename);
if (File.Exists(prebuiltffmpeg) && File.Exists(prebuiltffprobe))
{
return new FFMpegInfo
{
ProbePath = prebuiltffprobe,
EncoderPath = prebuiltffmpeg,
Version = "external"
};
}
var version = downloadInfo.Version;
if (string.Equals(version, "0", StringComparison.OrdinalIgnoreCase))
{
return new FFMpegInfo();
}
var rootEncoderPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
var versionedDirectoryPath = Path.Combine(rootEncoderPath, version);
var info = new FFMpegInfo
{
ProbePath = Path.Combine(versionedDirectoryPath, downloadInfo.FFProbeFilename),
EncoderPath = Path.Combine(versionedDirectoryPath, downloadInfo.FFMpegFilename),
Version = version
};
Directory.CreateDirectory(versionedDirectoryPath);
var excludeFromDeletions = new List<string> { versionedDirectoryPath };
if (!File.Exists(info.ProbePath) || !File.Exists(info.EncoderPath))
{
// ffmpeg not present. See if there's an older version we can start with
var existingVersion = GetExistingVersion(info, rootEncoderPath);
// No older version. Need to download and block until complete
if (existingVersion == null)
{
return new FFMpegInfo();
}
else
{
info = existingVersion;
versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath);
excludeFromDeletions.Add(versionedDirectoryPath);
}
}
// Allow just one of these to be overridden, if desired.
if (!string.IsNullOrWhiteSpace(customffMpegPath))
{
info.EncoderPath = customffMpegPath;
}
if (!string.IsNullOrWhiteSpace(customffProbePath))
{
info.ProbePath = customffProbePath;
}
return info;
}
private FFMpegInfo GetExistingVersion(FFMpegInfo info, string rootEncoderPath)
{
var encoderFilename = Path.GetFileName(info.EncoderPath);
var probeFilename = Path.GetFileName(info.ProbePath);
foreach (var directory in _fileSystem.GetDirectoryPaths(rootEncoderPath))
{
var allFiles = _fileSystem.GetFilePaths(directory, true).ToList();
var encoder = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), encoderFilename, StringComparison.OrdinalIgnoreCase));
var probe = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), probeFilename, StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrWhiteSpace(encoder) &&
!string.IsNullOrWhiteSpace(probe))
{
return new FFMpegInfo
{
EncoderPath = encoder,
ProbePath = probe,
Version = Path.GetFileName(Path.GetDirectoryName(probe))
};
}
}
return null;
}
}
}

@ -19,7 +19,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_processFactory = processFactory;
}
public (IEnumerable<string> decoders, IEnumerable<string> encoders) Validate(string encoderPath)
public (IEnumerable<string> decoders, IEnumerable<string> encoders) GetAvailableCoders(string encoderPath)
{
_logger.LogInformation("Validating media encoder at {EncoderPath}", encoderPath);

@ -7,13 +7,9 @@ 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;
@ -32,323 +28,288 @@ 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
/// External: path supplied via command line
/// Custom: coming from UI or config/encoding.xml file
/// System: FFmpeg found in system $PATH
/// null: No FFmpeg found
/// </summary>
private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1);
public string FFMpegPath { get; private set; }
public string FFProbePath { get; private set; }
public string EncoderLocationType { get; private set; }
private readonly ILogger _logger;
private readonly IJsonSerializer _jsonSerializer;
private string FFmpegPath { get; set; }
private string FFprobePath { get; set; }
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 and EncoderLocationType.
/// If startup options --ffprobe is given then FFprobePath is set too.
/// </summary>
public void Init()
{
get
// 1) If given, use the --ffmpeg CLI switch
if (ValidatePathFFmpeg("From CLI Switch", StartupOptionFFmpegPath))
{
if (_hasExternalEncoder)
{
return "External";
}
if (string.IsNullOrWhiteSpace(FFMpegPath))
{
return null;
}
if (IsSystemInstalledPath(FFMpegPath))
{
return "System";
}
return "Custom";
_logger.LogInformation("FFmpeg: Using path from command line switch --ffmpeg");
EncoderLocationType = "External";
}
}
private bool IsSystemInstalledPath(string path)
{
if (path.IndexOf("/", StringComparison.Ordinal) == -1 && path.IndexOf("\\", StringComparison.Ordinal) == -1)
// 2) Try Custom path stroed in config/encoding xml file under tag <EncoderAppPathCustom>
else if (ValidatePathFFmpeg("From Config File", ConfigurationManager.GetConfiguration<EncodingOptions>("encoding").EncoderAppPathCustom))
{
return true;
_logger.LogInformation("FFmpeg: Using path from config/encoding.xml file");
EncoderLocationType = "Custom";
}
return false;
}
public void Init()
{
InitPaths();
if (!string.IsNullOrWhiteSpace(FFMpegPath))
// 3) Search system $PATH environment variable for valid FFmpeg
else if (ValidatePathFFmpeg("From $PATH", ExistsOnSystemPath("ffmpeg")))
{
var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath);
SetAvailableDecoders(result.decoders);
SetAvailableEncoders(result.encoders);
_logger.LogInformation("FFmpeg: Using system $PATH for FFmpeg");
EncoderLocationType = "System";
}
else
{
_logger.LogError("FFmpeg: No suitable executable found");
FFmpegPath = null;
EncoderLocationType = null;
}
}
private void InitPaths()
{
ConfigureEncoderPaths();
if (_hasExternalEncoder)
// If given, use the --ffprobe CLI switch
if (ValidatePathFFprobe("CLI Switch", StartupOptionFFprobePath))
{
LogPaths();
return;
_logger.LogInformation("FFprobe: Using path from command line switch --ffprobe");
}
else
{
// FFprobe path from command line is no good, so set to null and let ReInit() try
// and set using the FFmpeg path.
FFprobePath = null;
}
// If the path was passed in, save it into config now.
var encodingOptions = GetEncodingOptions();
var appPath = encodingOptions.EncoderAppPath;
ReInit();
}
var valueToSave = FFMpegPath;
/// <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 so it appears in UI
var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
config.EncoderAppPath = FFmpegPath ?? string.Empty;
ConfigurationManager.SaveConfiguration("encoding", config);
if (!string.IsNullOrWhiteSpace(valueToSave))
// Only if mpeg path is set, try and set path to probe
if (FFmpegPath != null)
{
// if using system variable, don't save this.
if (IsSystemInstalledPath(valueToSave) || _hasExternalEncoder)
// Probe would be null here if no valid --ffprobe path was given
// at startup, or we're performing ReInit following mpeg path update from UI
if (FFprobePath == null)
{
valueToSave = null;
// Use the mpeg path to create a probe path
if (ValidatePathFFprobe("Copied from FFmpeg:", GetProbePathFromEncoderPath(FFmpegPath)))
{
_logger.LogInformation("FFprobe: Using FFprobe in same folders as FFmpeg");
}
else
{
_logger.LogError("FFprobe: No suitable executable found");
}
}
}
if (!string.Equals(valueToSave, appPath, StringComparison.Ordinal))
{
encodingOptions.EncoderAppPath = valueToSave;
ConfigurationManager.SaveConfiguration("encoding", encodingOptions);
// Interrogate to understand what coders it supports
var result = new EncoderValidator(_logger, _processFactory).GetAvailableCoders(FFmpegPath);
SetAvailableDecoders(result.decoders);
SetAvailableEncoders(result.encoders);
}
// Stamp FFmpeg paths to the log file
LogPaths();
}
/// <summary>
/// Triggered from the Settings > Trascoding UI page when users sumits Custom FFmpeg path to use.
/// </summary>
/// <param name="path"></param>
/// <param name="pathType"></param>
public void UpdateEncoderPath(string path, string pathType)
{
if (_hasExternalEncoder)
{
return;
}
_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))
if (!string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase))
{
path = "ffmpeg";
newPaths = TestForInstalledVersions();
throw new ArgumentException("Unexpected pathType value");
}
else if (string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase))
else
{
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentNullException(nameof(path));
}
// User had cleared the cutom path in UI. Clear the Custom config
// setting and peform full Init to relook any CLI switches and system $PATH
var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
config.EncoderAppPathCustom = string.Empty;
ConfigurationManager.SaveConfiguration("encoding", config);
if (!File.Exists(path) && !Directory.Exists(path))
Init();
}
else if (!File.Exists(path) && !Directory.Exists(path))
{
// Given path is neither file or folder
throw new ResourceNotFoundException();
}
newPaths = GetEncoderPaths(path);
}
else
{
throw new ArgumentException("Unexpected pathType value");
}
if (string.IsNullOrWhiteSpace(newPaths.Item1))
{
throw new ResourceNotFoundException("ffmpeg not found");
}
if (string.IsNullOrWhiteSpace(newPaths.Item2))
{
throw new ResourceNotFoundException("ffprobe not found");
}
path = newPaths.Item1;
else
{
// Supplied path could be either file path or folder path.
// Resolve down to file path and validate
path = GetEncoderPath(path);
if (!ValidateVersion(path, true))
{
throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required.");
}
if (path == null)
{
throw new ResourceNotFoundException("FFmpeg not found");
}
else if (!ValidatePathFFmpeg("New From UI", path))
{
throw new ResourceNotFoundException("Failed validation checks. Version 4.0 or greater is required");
}
else
{
EncoderLocationType = "Custom";
var config = GetEncodingOptions();
config.EncoderAppPath = path;
ConfigurationManager.SaveConfiguration("encoding", config);
// 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);
Init();
}
FFprobePath = null; // Clear probe path so it gets relooked in ReInit()
private bool ValidateVersion(string path, bool logOutput)
{
return new EncoderValidator(_logger, _processFactory).ValidateVersion(path, logOutput);
ReInit();
}
}
}
}
private void ConfigureEncoderPaths()
private bool ValidatePath(string type, string path)
{
if (_hasExternalEncoder)
if (!string.IsNullOrEmpty(path))
{
return;
}
var appPath = GetEncodingOptions().EncoderAppPath;
if (File.Exists(path))
{
var valid = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, true);
if (string.IsNullOrWhiteSpace(appPath))
{
appPath = Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "ffmpeg");
if (valid == true)
{
return true;
}
else
{
_logger.LogError("{0}: Failed validation checks. Version 4.0 or greater is required: {1}", type, path);
}
}
else
{
_logger.LogError("{0}: File not found: {1}", type, path);
}
}
var newPaths = GetEncoderPaths(appPath);
if (string.IsNullOrWhiteSpace(newPaths.Item1) || string.IsNullOrWhiteSpace(newPaths.Item2) || IsSystemInstalledPath(appPath))
{
newPaths = TestForInstalledVersions();
}
return false;
}
if (!string.IsNullOrWhiteSpace(newPaths.Item1) && !string.IsNullOrWhiteSpace(newPaths.Item2))
private bool ValidatePathFFmpeg(string comment, string path)
{
if (ValidatePath("FFmpeg: " + comment, path) == true)
{
FFMpegPath = newPaths.Item1;
FFProbePath = newPaths.Item2;
FFmpegPath = path;
return true;
}
LogPaths();
return false;
}
private Tuple<string, string> GetEncoderPaths(string configuredPath)
private bool ValidatePathFFprobe(string comment, string path)
{
var appPath = configuredPath;
if (!string.IsNullOrWhiteSpace(appPath))
if (ValidatePath("FFprobe: " + comment, path) == true)
{
if (Directory.Exists(appPath))
{
return GetPathsFromDirectory(appPath);
}
if (File.Exists(appPath))
{
return new Tuple<string, string>(appPath, GetProbePathFromEncoderPath(appPath));
}
FFprobePath = path;
return true;
}
return new Tuple<string, string>(null, null);
return false;
}
private Tuple<string, string> TestForInstalledVersions()
private string GetEncoderPath(string path)
{
string encoderPath = null;
string probePath = null;
if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath, true))
if (Directory.Exists(path))
{
encoderPath = _originalFFMpegPath;
probePath = _originalFFProbePath;
return GetEncoderPathFromDirectory(path);
}
if (string.IsNullOrWhiteSpace(encoderPath))
if (File.Exists(path))
{
if (ValidateVersion("ffmpeg", true) && ValidateVersion("ffprobe", false))
{
encoderPath = "ffmpeg";
probePath = "ffprobe";
}
return path;
}
return new Tuple<string, string>(encoderPath, probePath);
return null;
}
private Tuple<string, string> GetPathsFromDirectory(string path)
private string GetEncoderPathFromDirectory(string path)
{
// 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 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))
try
{
files = FileSystem.GetFilePaths(path, true);
var files = FileSystem.GetFilePaths(path);
ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
var excludeExtensions = new[] { ".c" };
if (!string.IsNullOrWhiteSpace(ffmpegPath))
{
ffprobePath = GetProbePathFromEncoderPath(ffmpegPath);
}
return files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
}
catch (Exception)
{
// Trap all exceptions, like DirNotExists, and return null
return null;
}
return new Tuple<string, string>(ffmpegPath, ffprobePath);
}
private string GetProbePathFromEncoderPath(string appPath)
@ -357,15 +318,31 @@ namespace MediaBrowser.MediaEncoding.Encoder
.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase));
}
private void LogPaths()
/// <summary>
/// Search the system $PATH environment variable looking for given filename.
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
private string ExistsOnSystemPath(string fileName)
{
_logger.LogInformation("FFMpeg: {0}", FFMpegPath ?? "not found");
_logger.LogInformation("FFProbe: {0}", FFProbePath ?? "not found");
var values = Environment.GetEnvironmentVariable("PATH");
foreach (var path in values.Split(Path.PathSeparator))
{
var candidatePath = GetEncoderPathFromDirectory(path);
if (ValidatePath("Found on PATH", candidatePath))
{
return candidatePath;
}
}
return null;
}
private EncodingOptions GetEncodingOptions()
private void LogPaths()
{
return ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
_logger.LogInformation("FFMpeg: {0}", FFmpegPath ?? "not found");
_logger.LogInformation("FFProbe: {0}", FFprobePath ?? "not found");
}
private List<string> _encoders = new List<string>();
@ -412,12 +389,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
return true;
}
/// <summary>
/// Gets the encoder path.
/// </summary>
/// <value>The encoder path.</value>
public string EncoderPath => FFMpegPath;
/// <summary>
/// Gets the media info.
/// </summary>
@ -489,7 +460,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,7 +662,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
CreateNoWindow = true,
UseShellExecute = false,
FileName = FFMpegPath,
FileName = FFmpegPath,
Arguments = args,
IsHidden = true,
ErrorDialog = false,
@ -814,7 +785,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
CreateNoWindow = true,
UseShellExecute = false,
FileName = FFMpegPath,
FileName = FFmpegPath,
Arguments = args,
IsHidden = true,
ErrorDialog = false,

@ -8,7 +8,8 @@ namespace MediaBrowser.Model.Configuration
public bool EnableThrottling { get; set; }
public int ThrottleDelaySeconds { get; set; }
public string HardwareAccelerationType { get; set; }
public string EncoderAppPath { get; set; }
public string EncoderAppPathCustom { get; set; } // FFmpeg path as set by the user via the UI
public string EncoderAppPath { get; set; } // The current FFmpeg path being used by the system
public string VaapiDevice { get; set; }
public int H264Crf { get; set; }
public string H264Preset { get; set; }

Loading…
Cancel
Save