diff options
Diffstat (limited to 'MediaBrowser.MediaEncoding/Encoder')
| -rw-r--r-- | MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs | 21 | ||||
| -rw-r--r-- | MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs | 2 | ||||
| -rw-r--r-- | MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 119 |
3 files changed, 57 insertions, 85 deletions
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index a865b0e4c..6c43be3ab 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -62,10 +62,6 @@ namespace MediaBrowser.MediaEncoding.Encoder "libx264", "libx265", "libsvtav1", - "mpeg4", - "msmpeg4", - "libvpx", - "libvpx-vp9", "aac", "aac_at", "libfdk_aac", @@ -116,25 +112,31 @@ namespace MediaBrowser.MediaEncoding.Encoder "yadif_cuda", "tonemap_cuda", "overlay_cuda", + "transpose_cuda", "hwupload_cuda", // opencl "scale_opencl", "tonemap_opencl", "overlay_opencl", + "transpose_opencl", // vaapi "scale_vaapi", "deinterlace_vaapi", "tonemap_vaapi", "procamp_vaapi", "overlay_vaapi", + "transpose_vaapi", "hwupload_vaapi", // vulkan "libplacebo", "scale_vulkan", "overlay_vulkan", + "transpose_vulkan", + "flip_vulkan", // videotoolbox "yadif_videotoolbox", "scale_vt", + "transpose_vt", "overlay_videotoolbox", "tonemap_videotoolbox", // rkrga @@ -150,7 +152,8 @@ namespace MediaBrowser.MediaEncoding.Encoder { 2, new string[] { "tonemap_opencl", "bt2390" } }, { 3, new string[] { "overlay_opencl", "Action to take when encountering EOF from secondary input" } }, { 4, new string[] { "overlay_vaapi", "Action to take when encountering EOF from secondary input" } }, - { 5, new string[] { "overlay_vulkan", "Action to take when encountering EOF from secondary input" } } + { 5, new string[] { "overlay_vulkan", "Action to take when encountering EOF from secondary input" } }, + { 6, new string[] { "transpose_opencl", "rotate by half-turn" } } }; // These are the library versions that corresponds to our minimum ffmpeg version 4.4 according to the version table below @@ -171,6 +174,8 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly string _encoderPath; + private readonly Version _minFFmpegMultiThreadedCli = new Version(7, 0); + public EncoderValidator(ILogger logger, string encoderPath) { _logger = logger; @@ -480,7 +485,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return false; } - public bool CheckSupportedRuntimeKey(string keyDesc) + public bool CheckSupportedRuntimeKey(string keyDesc, Version? ffmpegVersion) { if (string.IsNullOrEmpty(keyDesc)) { @@ -490,7 +495,9 @@ namespace MediaBrowser.MediaEncoding.Encoder string output; try { - output = GetProcessOutput(_encoderPath, "-hide_banner -f lavfi -i nullsrc=s=1x1:d=500 -f null -", true, "?"); + // With multi-threaded cli support, FFmpeg 7 is less sensitive to keyboard input + var duration = ffmpegVersion >= _minFFmpegMultiThreadedCli ? 10000 : 1000; + output = GetProcessOutput(_encoderPath, $"-hide_banner -f lavfi -i nullsrc=s=1x1:d={duration} -f null -", true, "?"); } catch (Exception ex) { diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs index c5f500e76..2daeac734 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // If there's more than one we'll need to use the concat command if (inputFiles.Count > 1) { - var files = string.Join("|", inputFiles.Select(NormalizePath)); + var files = string.Join('|', inputFiles.Select(NormalizePath)); return string.Format(CultureInfo.InvariantCulture, "concat:\"{0}\"", files); } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 5cfead502..764230feb 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -12,6 +12,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using AsyncKeyedLock; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json.Converters; @@ -79,8 +80,14 @@ namespace MediaBrowser.MediaEncoding.Encoder private bool _isVaapiDeviceAmd = false; private bool _isVaapiDeviceInteliHD = false; private bool _isVaapiDeviceInteli965 = false; + private bool _isVaapiDeviceSupportVulkanDrmModifier = false; private bool _isVaapiDeviceSupportVulkanDrmInterop = false; + private static string[] _vulkanImageDrmFmtModifierExts = + { + "VK_EXT_image_drm_format_modifier", + }; + private static string[] _vulkanExternalMemoryDmaBufExts = { "VK_KHR_external_memory_fd", @@ -141,34 +148,50 @@ namespace MediaBrowser.MediaEncoding.Encoder public bool IsVaapiDeviceInteli965 => _isVaapiDeviceInteli965; /// <inheritdoc /> + public bool IsVaapiDeviceSupportVulkanDrmModifier => _isVaapiDeviceSupportVulkanDrmModifier; + + /// <inheritdoc /> public bool IsVaapiDeviceSupportVulkanDrmInterop => _isVaapiDeviceSupportVulkanDrmInterop; [GeneratedRegex(@"[^\/\\]+?(\.[^\/\\\n.]+)?$")] private static partial Regex FfprobePathRegex(); /// <summary> - /// 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. /// </summary> - public void SetFFmpegPath() + /// <returns>bool indicates whether a valid ffmpeg is found.</returns> + 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 <EncoderAppPath> 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 <EncoderAppPathDisplay> so it appears in UI @@ -194,7 +217,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _threads = EncodingHelper.GetNumberOfThreads(null, options, null); - _isPkeyPauseSupported = validator.CheckSupportedRuntimeKey("p pause transcoding"); + _isPkeyPauseSupported = validator.CheckSupportedRuntimeKey("p pause transcoding", _ffmpegVersion); _isLowPriorityHwDecodeSupported = validator.CheckSupportedHwaccelFlag("low_priority"); // Check the Vaapi device vendor @@ -206,6 +229,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _isVaapiDeviceAmd = validator.CheckVaapiDeviceByDriverName("Mesa Gallium driver", options.VaapiDevice); _isVaapiDeviceInteliHD = validator.CheckVaapiDeviceByDriverName("Intel iHD driver", options.VaapiDevice); _isVaapiDeviceInteli965 = validator.CheckVaapiDeviceByDriverName("Intel i965 driver", options.VaapiDevice); + _isVaapiDeviceSupportVulkanDrmModifier = validator.CheckVulkanDrmDeviceByExtensionName(options.VaapiDevice, _vulkanImageDrmFmtModifierExts); _isVaapiDeviceSupportVulkanDrmInterop = validator.CheckVulkanDrmDeviceByExtensionName(options.VaapiDevice, _vulkanExternalMemoryDmaBufExts); if (_isVaapiDeviceAmd) @@ -221,6 +245,11 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger.LogInformation("VAAPI device {RenderNodePath} is Intel GPU (i965)", options.VaapiDevice); } + if (_isVaapiDeviceSupportVulkanDrmModifier) + { + _logger.LogInformation("VAAPI device {RenderNodePath} supports Vulkan DRM modifier", options.VaapiDevice); + } + if (_isVaapiDeviceSupportVulkanDrmInterop) { _logger.LogInformation("VAAPI device {RenderNodePath} supports Vulkan DRM interop", options.VaapiDevice); @@ -229,65 +258,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } _logger.LogInformation("FFmpeg: {FfmpegPath}", _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">The path.</param> - /// <param name="pathType">The path type.</param> - 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 <EncoderAppPath> - // 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); } /// <summary> @@ -306,7 +277,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; } @@ -710,18 +681,18 @@ namespace MediaBrowser.MediaEncoding.Encoder filters.Add("thumbnail=n=" + (useLargerBatchSize ? "50" : "24")); } - // Use SW tonemap on HDR10/HLG video stream only when the zscale or tonemapx filter is available. + // Use SW tonemap on HDR video stream only when the zscale or tonemapx filter is available. + // Only enable Dolby Vision tonemap when tonemapx is available var enableHdrExtraction = false; - if (string.Equals(videoStream?.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoStream?.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) + if (videoStream?.VideoRange == VideoRange.HDR) { if (SupportsFilter("tonemapx")) { enableHdrExtraction = true; filters.Add("tonemapx=tonemap=bt2390:desat=0:peak=100:t=bt709:m=bt709:p=bt709:format=yuv420p"); } - else if (SupportsFilter("zscale")) + else if (SupportsFilter("zscale") && videoStream.VideoRangeType != VideoRangeType.DOVI) { enableHdrExtraction = true; filters.Add("zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p"); @@ -764,8 +735,6 @@ namespace MediaBrowser.MediaEncoding.Encoder using (var processWrapper = new ProcessWrapper(process, this)) { - bool ranToCompletion; - using (await _thumbnailResourcePool.LockAsync(cancellationToken).ConfigureAwait(false)) { StartProcess(processWrapper); @@ -779,22 +748,18 @@ namespace MediaBrowser.MediaEncoding.Encoder try { await process.WaitForExitAsync(TimeSpan.FromMilliseconds(timeoutMs)).ConfigureAwait(false); - ranToCompletion = true; } - catch (OperationCanceledException) + catch (OperationCanceledException ex) { process.Kill(true); - ranToCompletion = false; + throw new FfmpegException(string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction timed out for {0} after {1}ms", inputPath, timeoutMs), ex); } } - var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; var file = _fileSystem.GetFileInfo(tempExtractPath); - if (exitCode == -1 || !file.Exists || file.Length == 0) + if (processWrapper.ExitCode > 0 || !file.Exists || file.Length == 0) { - _logger.LogError("ffmpeg image extraction failed for {Path}", inputPath); - throw new FfmpegException(string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", inputPath)); } |
