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.
This commit is contained in:
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
|
||||
/// Gets the encoder path.
|
||||
/// </summary>
|
||||
/// <value>The encoder path.</value>
|
||||
public string EncoderPath => FFmpegPath;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public string EncoderLocationType { get; private set; }
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the json serializer.
|
||||
/// </summary>
|
||||
/// <value>The json serializer.</value>
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// The _thumbnail resource pool
|
||||
/// </summary>
|
||||
private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1);
|
||||
|
||||
public string FFMpegPath { get; private set; }
|
||||
|
||||
public string FFProbePath { get; private set; }
|
||||
|
||||
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 List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
|
||||
private readonly bool _hasExternalEncoder;
|
||||
private readonly string _originalFFMpegPath;
|
||||
private readonly string _originalFFProbePath;
|
||||
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>();
|
||||
|
||||
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)
|
||||
_logger.LogInformation("FFmpeg: Using path from command line switch --ffmpeg");
|
||||
EncoderLocationType = "External";
|
||||
}
|
||||
|
||||
// 2) Try Custom path stroed in config/encoding xml file under tag <EncoderAppPathCustom>
|
||||
else if (ValidatePathFFmpeg("From Config File", ConfigurationManager.GetConfiguration<EncodingOptions>("encoding").EncoderAppPathCustom))
|
||||
{
|
||||
_logger.LogInformation("FFmpeg: Using path from config/encoding.xml file");
|
||||
EncoderLocationType = "Custom";
|
||||
}
|
||||
|
||||
// 3) Search system $PATH environment variable for valid FFmpeg
|
||||
else if (ValidatePathFFmpeg("From $PATH", ExistsOnSystemPath("ffmpeg")))
|
||||
{
|
||||
_logger.LogInformation("FFmpeg: Using system $PATH for FFmpeg");
|
||||
EncoderLocationType = "System";
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("FFmpeg: No suitable executable found");
|
||||
FFmpegPath = null;
|
||||
EncoderLocationType = null;
|
||||
}
|
||||
|
||||
// If given, use the --ffprobe CLI switch
|
||||
if (ValidatePathFFprobe("CLI Switch", StartupOptionFFprobePath))
|
||||
{
|
||||
_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;
|
||||
}
|
||||
|
||||
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 so it appears in UI
|
||||
var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
|
||||
config.EncoderAppPath = FFmpegPath ?? string.Empty;
|
||||
ConfigurationManager.SaveConfiguration("encoding", config);
|
||||
|
||||
// Only if mpeg path is set, try and set path to probe
|
||||
if (FFmpegPath != null)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
return "External";
|
||||
// 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.IsNullOrWhiteSpace(FFMpegPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// Interrogate to understand what coders it supports
|
||||
var result = new EncoderValidator(_logger, _processFactory).GetAvailableCoders(FFmpegPath);
|
||||
|
||||
if (IsSystemInstalledPath(FFMpegPath))
|
||||
{
|
||||
return "System";
|
||||
}
|
||||
SetAvailableDecoders(result.decoders);
|
||||
SetAvailableEncoders(result.encoders);
|
||||
}
|
||||
|
||||
return "Custom";
|
||||
// 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)
|
||||
{
|
||||
_logger.LogInformation("Attempting to update encoder path to {0}. pathType: {1}", path ?? string.Empty, pathType ?? string.Empty);
|
||||
|
||||
if (!string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new ArgumentException("Unexpected pathType value");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(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);
|
||||
|
||||
Init();
|
||||
}
|
||||
else if (!File.Exists(path) && !Directory.Exists(path))
|
||||
{
|
||||
// Given path is neither file or folder
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Supplied path could be either file path or folder path.
|
||||
// Resolve down to file path and validate
|
||||
path = GetEncoderPath(path);
|
||||
|
||||
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";
|
||||
|
||||
// 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);
|
||||
|
||||
FFprobePath = null; // Clear probe path so it gets relooked in ReInit()
|
||||
|
||||
ReInit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSystemInstalledPath(string path)
|
||||
private bool ValidatePath(string type, string path)
|
||||
{
|
||||
if (path.IndexOf("/", StringComparison.Ordinal) == -1 && path.IndexOf("\\", StringComparison.Ordinal) == -1)
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var valid = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, true);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool ValidatePathFFmpeg(string comment, string path)
|
||||
{
|
||||
if (ValidatePath("FFmpeg: " + comment, path) == true)
|
||||
{
|
||||
FFmpegPath = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Init()
|
||||
private bool ValidatePathFFprobe(string comment, string path)
|
||||
{
|
||||
InitPaths();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(FFMpegPath))
|
||||
if (ValidatePath("FFprobe: " + comment, path) == true)
|
||||
{
|
||||
var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath);
|
||||
|
||||
SetAvailableDecoders(result.decoders);
|
||||
SetAvailableEncoders(result.encoders);
|
||||
FFprobePath = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void InitPaths()
|
||||
private string GetEncoderPath(string path)
|
||||
{
|
||||
ConfigureEncoderPaths();
|
||||
|
||||
if (_hasExternalEncoder)
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
LogPaths();
|
||||
return;
|
||||
return GetEncoderPathFromDirectory(path);
|
||||
}
|
||||
|
||||
// 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 (File.Exists(path))
|
||||
{
|
||||
// if using system variable, don't save this.
|
||||
if (IsSystemInstalledPath(valueToSave) || _hasExternalEncoder)
|
||||
{
|
||||
valueToSave = null;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
if (!string.Equals(valueToSave, appPath, StringComparison.Ordinal))
|
||||
{
|
||||
encodingOptions.EncoderAppPath = valueToSave;
|
||||
ConfigurationManager.SaveConfiguration("encoding", encodingOptions);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void UpdateEncoderPath(string path, string pathType)
|
||||
private string GetEncoderPathFromDirectory(string path)
|
||||
{
|
||||
if (_hasExternalEncoder)
|
||||
try
|
||||
{
|
||||
return;
|
||||
var files = FileSystem.GetFilePaths(path);
|
||||
|
||||
var excludeExtensions = new[] { ".c" };
|
||||
|
||||
return files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
|
||||
}
|
||||
|
||||
_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))
|
||||
catch (Exception)
|
||||
{
|
||||
path = "ffmpeg";
|
||||
|
||||
newPaths = TestForInstalledVersions();
|
||||
// Trap all exceptions, like DirNotExists, and return null
|
||||
return null;
|
||||
}
|
||||
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);
|
||||
}
|
||||
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;
|
||||
|
||||
if (!ValidateVersion(path, true))
|
||||
{
|
||||
throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required.");
|
||||
}
|
||||
|
||||
var config = GetEncodingOptions();
|
||||
config.EncoderAppPath = path;
|
||||
ConfigurationManager.SaveConfiguration("encoding", config);
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
private bool ValidateVersion(string path, bool logOutput)
|
||||
{
|
||||
return new EncoderValidator(_logger, _processFactory).ValidateVersion(path, logOutput);
|
||||
}
|
||||
|
||||
private void ConfigureEncoderPaths()
|
||||
{
|
||||
if (_hasExternalEncoder)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
LogPaths();
|
||||
}
|
||||
|
||||
private Tuple<string, string> GetEncoderPaths(string configuredPath)
|
||||
{
|
||||
var appPath = configuredPath;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(appPath))
|
||||
{
|
||||
if (Directory.Exists(appPath))
|
||||
{
|
||||
return GetPathsFromDirectory(appPath);
|
||||
}
|
||||
|
||||
if (File.Exists(appPath))
|
||||
{
|
||||
return new Tuple<string, string>(appPath, GetProbePathFromEncoderPath(appPath));
|
||||
}
|
||||
}
|
||||
|
||||
return new Tuple<string, string>(null, null);
|
||||
}
|
||||
|
||||
private Tuple<string, string> TestForInstalledVersions()
|
||||
{
|
||||
string encoderPath = null;
|
||||
string probePath = null;
|
||||
|
||||
if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath, true))
|
||||
{
|
||||
encoderPath = _originalFFMpegPath;
|
||||
probePath = _originalFFProbePath;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(encoderPath))
|
||||
{
|
||||
if (ValidateVersion("ffmpeg", true) && ValidateVersion("ffprobe", false))
|
||||
{
|
||||
encoderPath = "ffmpeg";
|
||||
probePath = "ffprobe";
|
||||
}
|
||||
}
|
||||
|
||||
return new Tuple<string, string>(encoderPath, probePath);
|
||||
}
|
||||
|
||||
private Tuple<string, string> GetPathsFromDirectory(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))
|
||||
{
|
||||
files = FileSystem.GetFilePaths(path, true);
|
||||
|
||||
ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(ffmpegPath))
|
||||
{
|
||||
ffprobePath = GetProbePathFromEncoderPath(ffmpegPath);
|
||||
}
|
||||
}
|
||||
|
||||
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…
Reference in New Issue
Block a user