diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ceda0bd64..5292003f0 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -402,7 +402,12 @@ namespace Emby.Server.Implementations ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated; ConfigurationManager.NamedConfigurationUpdated += OnConfigurationUpdated; - Resolve().SetFFmpegPath(); + var ffmpegValid = Resolve().SetFFmpegPath(); + + if (!ffmpegValid) + { + throw new FfmpegException("Failed to find valid ffmpeg"); + } Logger.LogInformation("ServerId: {ServerId}", SystemId); Logger.LogInformation("Core startup complete"); diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index f0c267627..e86010513 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -19,7 +19,8 @@ namespace Emby.Server.Implementations { FfmpegAnalyzeDurationKey, "200M" }, { PlaylistsAllowDuplicatesKey, bool.FalseString }, { BindToUnixSocketKey, bool.FalseString }, - { SqliteCacheSizeKey, "20000" } + { SqliteCacheSizeKey, "20000" }, + { FfmpegSkipValidationKey, bool.FalseString } }; } } diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 6c58064ce..0aaf4fcd9 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -29,6 +29,11 @@ namespace MediaBrowser.Controller.Extensions /// public const string FfmpegProbeSizeKey = "FFmpeg:probesize"; + /// + /// The key for the skipping FFmpeg validation. + /// + public const string FfmpegSkipValidationKey = "FFmpeg:novalidation"; + /// /// The key for the FFmpeg analyze duration option. /// @@ -89,6 +94,14 @@ namespace MediaBrowser.Controller.Extensions public static string? GetFFmpegAnalyzeDuration(this IConfiguration configuration) => configuration[FfmpegAnalyzeDurationKey]; + /// + /// Gets a value indicating whether the server should validate FFmpeg during startup. + /// + /// The configuration to read the setting from. + /// true if the server should validate FFmpeg during startup, otherwise false. + public static bool GetFFmpegSkipValidation(this IConfiguration configuration) + => configuration.GetValue(FfmpegSkipValidationKey); + /// /// Gets a value indicating whether playlists should allow duplicate entries from the . /// diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 038c6c7f6..e36106e52 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -223,14 +223,8 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// Sets the path to find FFmpeg. /// - void SetFFmpegPath(); - - /// - /// Updates the encoder path. - /// - /// The path. - /// The type of path. - void UpdateEncoderPath(string path, string pathType); + /// bool indicates whether a valid ffmpeg is found. + bool SetFFmpegPath(); /// /// Gets the primary playlist of .vob files. diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index e8461e77f..d9fe0594f 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -147,28 +147,41 @@ namespace MediaBrowser.MediaEncoding.Encoder private static partial Regex FfprobePathRegex(); /// - /// Run at startup or if the user removes a Custom path from transcode page. + /// Run at startup to validate ffmpeg. /// Sets global variables FFmpegPath. - /// Precedence is: Config > CLI > $PATH. + /// Precedence is: CLI/Env var > Config > $PATH. /// - public void SetFFmpegPath() + /// bool indicates whether a valid ffmpeg is found. + public bool SetFFmpegPath() { + var skipValidation = _config.GetFFmpegSkipValidation(); + if (skipValidation) + { + _logger.LogWarning("FFmpeg: Skipping FFmpeg Validation due to FFmpeg:novalidation set to true"); + return true; + } + // 1) Check if the --ffmpeg CLI switch has been given var ffmpegPath = _startupOptionFFmpegPath; + string ffmpegPathSetMethodText = "command line or environment variable"; if (string.IsNullOrEmpty(ffmpegPath)) { // 2) Custom path stored in config/encoding xml file under tag should be used as a fallback ffmpegPath = _configurationManager.GetEncodingOptions().EncoderAppPath; + ffmpegPathSetMethodText = "encoding.xml config file"; if (string.IsNullOrEmpty(ffmpegPath)) { // 3) Check "ffmpeg" ffmpegPath = "ffmpeg"; + ffmpegPathSetMethodText = "system $PATH"; } } if (!ValidatePath(ffmpegPath)) { _ffmpegPath = null; + _logger.LogError("FFmpeg: Path set by {FfmpegPathSetMethodText} is invalid", ffmpegPathSetMethodText); + return false; } // Write the FFmpeg path to the config/encoding.xml file as so it appears in UI @@ -229,65 +242,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } _logger.LogInformation("FFmpeg: {FfmpegPath}", _ffmpegPath ?? string.Empty); - } - - /// - /// 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. - /// - /// The path. - /// The path type. - public void UpdateEncoderPath(string path, string pathType) - { - var config = _configurationManager.GetEncodingOptions(); - - // Filesystem may not be case insensitive, but EncoderAppPathDisplay should always point to a valid file? - if (string.IsNullOrEmpty(config.EncoderAppPath) - && string.Equals(config.EncoderAppPathDisplay, path, StringComparison.OrdinalIgnoreCase)) - { - _logger.LogDebug("Existing ffmpeg path is empty and the new path is the same as {EncoderAppPathDisplay}. Skipping", nameof(config.EncoderAppPathDisplay)); - return; - } - - string newPath; - - _logger.LogInformation("Attempting to update encoder path to {Path}. pathType: {PathType}", path ?? string.Empty, pathType ?? string.Empty); - - if (!string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase)) - { - throw new ArgumentException("Unexpected pathType value"); - } - - if (string.IsNullOrWhiteSpace(path)) - { - // User had cleared the custom path in UI - newPath = string.Empty; - } - else - { - if (Directory.Exists(path)) - { - // Given path is directory, so resolve down to filename - newPath = GetEncoderPathFromDirectory(path, "ffmpeg"); - } - else - { - newPath = path; - } - - if (!new EncoderValidator(_logger, newPath).ValidateVersion()) - { - throw new ResourceNotFoundException(); - } - } - - // Write the new ffmpeg path to the xml as - // This ensures its not lost on next startup - config.EncoderAppPath = newPath; - _configurationManager.SaveConfiguration("encoding", config); - - // Trigger SetFFmpegPath so we validate the new path and setup probe path - SetFFmpegPath(); + return !string.IsNullOrWhiteSpace(ffmpegPath); } /// @@ -306,7 +261,7 @@ namespace MediaBrowser.MediaEncoding.Encoder bool rc = new EncoderValidator(_logger, path).ValidateVersion(); if (!rc) { - _logger.LogWarning("FFmpeg: Failed version check: {Path}", path); + _logger.LogError("FFmpeg: Failed version check: {Path}", path); return false; } diff --git a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs index a078eff77..78b32d278 100644 --- a/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs +++ b/tests/Jellyfin.Server.Integration.Tests/JellyfinApplicationFactory.cs @@ -47,6 +47,8 @@ namespace Jellyfin.Server.Integration.Tests /// protected override void ConfigureWebHost(IWebHostBuilder builder) { + // Skip ffmpeg check for testing + Environment.SetEnvironmentVariable("JELLYFIN_FFMPEG__NOVALIDATION", "true"); // Specify the startup command line options var commandLineOpts = new StartupOptions();