Merge pull request #844 from ploughpuff/ffmpeg

Reworked FFmpeg path discovery and always display to user
pull/1060/head
Vasily 5 years ago committed by GitHub
commit a4b52b7264
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -23,6 +23,7 @@
- [fruhnow](https://github.com/fruhnow)
- [Lynxy](https://github.com/Lynxy)
- [fasheng](https://github.com/fasheng)
- [ploughpuff](https://github.com/ploughpuff)
# Emby Contributors

@ -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;
@ -535,7 +534,7 @@ namespace Emby.Server.Implementations
ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
MediaEncoder.Init();
MediaEncoder.SetFFmpegPath();
//if (string.IsNullOrWhiteSpace(MediaEncoder.EncoderPath))
//{
@ -790,7 +789,18 @@ 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);
@ -906,85 +916,6 @@ 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)
{
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(
LoggerFactory,
JsonSerializer,
encoderPath,
probePath,
hasExternalEncoder,
ServerConfigurationManager,
FileSystemManager,
LiveTvManager,
IsoManager,
LibraryManager,
ChannelManager,
SessionManager,
() => SubtitleEncoder,
() => MediaSourceManager,
HttpClient,
ZipClient,
ProcessFactory,
5000);
MediaEncoder = mediaEncoder;
serviceCollection.AddSingleton(MediaEncoder);
}
/// <summary>
/// Gets the user repository.
/// </summary>
@ -1460,7 +1391,7 @@ namespace Emby.Server.Implementations
ServerName = FriendlyName,
LocalAddress = localAddress,
SupportsLibraryMonitor = true,
EncoderLocationType = MediaEncoder.EncoderLocationType,
EncoderLocation = MediaEncoder.EncoderLocation,
SystemArchitecture = EnvironmentInfo.SystemArchitecture,
SystemUpdateLevel = SystemUpdateLevel,
PackageName = StartupOptions.PackageName

@ -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;
}
}
}

@ -20,10 +20,10 @@ namespace Jellyfin.Server
[Option('l', "logdir", Required = false, HelpText = "Path to use for writing log files.")]
public string LogDir { get; set; }
[Option("ffmpeg", Required = false, HelpText = "Path to external FFmpeg executable to use in place of default found in PATH. Must be specified along with --ffprobe.")]
[Option("ffmpeg", Required = false, HelpText = "Path to external FFmpeg executable to use in place of default found in PATH.")]
public string FFmpegPath { get; set; }
[Option("ffprobe", Required = false, HelpText = "Path to external FFprobe executable to use in place of default found in PATH. Must be specified along with --ffmpeg.")]
[Option("ffprobe", Required = false, HelpText = "(deprecated) Option has no effect and shall be removed in next release.")]
public string FFprobePath { get; set; }
[Option("service", Required = false, HelpText = "Run as headless service.")]

@ -6,6 +6,7 @@ using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.System;
namespace MediaBrowser.Controller.MediaEncoding
{
@ -14,7 +15,7 @@ namespace MediaBrowser.Controller.MediaEncoding
/// </summary>
public interface IMediaEncoder : ITranscoderSupport
{
string EncoderLocationType { get; }
FFmpegLocation EncoderLocation { get; }
/// <summary>
/// Gets the encoder path.
@ -91,7 +92,7 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <returns>System.String.</returns>
string EscapeSubtitleFilterPath(string path);
void Init();
void SetFFmpegPath();
void UpdateEncoderPath(string path, string pathType);
bool SupportsEncoder(string encoder);

@ -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);
@ -48,6 +48,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (string.IsNullOrWhiteSpace(output))
{
if (logOutput)
{
_logger.LogError("FFmpeg validation: The process returned no result");
}
return false;
}
@ -55,6 +59,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1)
{
if (logOutput)
{
_logger.LogError("FFmpeg validation: avconv instead of ffmpeg is not supported");
}
return false;
}

@ -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))
if (!string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase))
{
path = "ffmpeg";
newPaths = TestForInstalledVersions();
}
else 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)
/// <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)
{
return new EncoderValidator(_logger, _processFactory).ValidateVersion(path, logOutput);
}
bool rc = false;
private void ConfigureEncoderPaths()
{
if (_hasExternalEncoder)
if (!string.IsNullOrEmpty(path))
{
return;
}
var appPath = GetEncodingOptions().EncoderAppPath;
if (string.IsNullOrWhiteSpace(appPath))
{
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);
var excludeExtensions = new[] { ".c" };
if (string.IsNullOrWhiteSpace(encoderPath))
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 values = Environment.GetEnvironmentVariable("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))
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>();
@ -412,12 +293,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 +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,7 +566,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
CreateNoWindow = true,
UseShellExecute = false,
FileName = FFMpegPath,
FileName = FFmpegPath,
Arguments = args,
IsHidden = true,
ErrorDialog = false,
@ -814,7 +689,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
CreateNoWindow = true,
UseShellExecute = false,
FileName = FFMpegPath,
FileName = FFmpegPath,
Arguments = args,
IsHidden = true,
ErrorDialog = false,

@ -8,7 +8,14 @@ namespace MediaBrowser.Model.Configuration
public bool EnableThrottling { get; set; }
public int ThrottleDelaySeconds { get; set; }
public string HardwareAccelerationType { get; set; }
/// <summary>
/// FFmpeg path as set by the user via the UI
/// </summary>
public string EncoderAppPath { get; set; }
/// <summary>
/// The current FFmpeg path being used by the system and displayed on the transcode page
/// </summary>
public string EncoderAppPathDisplay { get; set; }
public string VaapiDevice { get; set; }
public int H264Crf { get; set; }
public string H264Preset { get; set; }

@ -4,6 +4,21 @@ using MediaBrowser.Model.Updates;
namespace MediaBrowser.Model.System
{
/// <summary>
/// Enum describing the location of the FFmpeg tool.
/// </summary>
public enum FFmpegLocation
{
/// <summary>No path to FFmpeg found.</summary>
NotFound,
/// <summary>Path supplied via command line using switch --ffmpeg.</summary>
SetByArgument,
/// <summary>User has supplied path via Transcoding UI page.</summary>
Custom,
/// <summary>FFmpeg tool found on system $PATH.</summary>
System
};
/// <summary>
/// Class SystemInfo
/// </summary>
@ -122,7 +137,7 @@ namespace MediaBrowser.Model.System
/// <value><c>true</c> if this instance has update available; otherwise, <c>false</c>.</value>
public bool HasUpdateAvailable { get; set; }
public string EncoderLocationType { get; set; }
public FFmpegLocation EncoderLocation { get; set; }
public Architecture SystemArchitecture { get; set; }

Loading…
Cancel
Save