diff options
Diffstat (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs')
| -rw-r--r-- | MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 110 |
1 files changed, 83 insertions, 27 deletions
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 39431a9fc..de1b65482 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -30,10 +30,8 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -using Microsoft.AspNetCore.Components.Forms; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using static Nikse.SubtitleEdit.Core.Common.IfoParser; namespace MediaBrowser.MediaEncoding.Encoder { @@ -458,9 +456,9 @@ namespace MediaBrowser.MediaEncoding.Encoder extraArgs += " -probesize " + ffmpegProbeSize; } - if (request.MediaSource.RequiredHttpHeaders.TryGetValue("user_agent", out var userAgent)) + if (request.MediaSource.RequiredHttpHeaders.TryGetValue("User-Agent", out var userAgent)) { - extraArgs += " -user_agent " + userAgent; + extraArgs += $" -user_agent \"{userAgent}\""; } if (request.MediaSource.Protocol == MediaProtocol.Rtsp) @@ -621,7 +619,7 @@ namespace MediaBrowser.MediaEncoding.Encoder ImageFormat? targetFormat, CancellationToken cancellationToken) { - var inputArgument = GetInputArgument(inputFile, mediaSource); + var inputArgument = GetInputPathArgument(inputFile, mediaSource); if (!isAudio) { @@ -710,16 +708,22 @@ namespace MediaBrowser.MediaEncoding.Encoder filters.Add("thumbnail=n=" + (useLargerBatchSize ? "50" : "24")); } - // Use SW tonemap on HDR10/HLG video stream only when the zscale filter is available. + // Use SW tonemap on HDR10/HLG video stream only when the zscale or tonemapx filter is available. var enableHdrExtraction = false; - if ((string.Equals(videoStream?.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + if (string.Equals(videoStream?.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream?.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) - && SupportsFilter("zscale")) { - 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"); + 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")) + { + 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"); + } } var vf = string.Join(',', filters); @@ -809,12 +813,28 @@ namespace MediaBrowser.MediaEncoding.Encoder int? threads, int? qualityScale, ProcessPriorityClass? priority, + bool enableKeyFrameOnlyExtraction, EncodingHelper encodingHelper, CancellationToken cancellationToken) { var options = allowHwAccel ? _configurationManager.GetEncodingOptions() : new EncodingOptions(); threads ??= _threads; + if (allowHwAccel && enableKeyFrameOnlyExtraction) + { + var supportsKeyFrameOnly = (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdecDecoder) + || (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && OperatingSystem.IsWindows()) + || (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwDecoder) + || string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase); + if (!supportsKeyFrameOnly) + { + // Disable hardware acceleration when the hardware decoder does not support keyframe only mode. + allowHwAccel = false; + options = new EncodingOptions(); + } + } + // A new EncodingOptions instance must be used as to not disable HW acceleration for all of Jellyfin. // Additionally, we must set a few fields without defaults to prevent null pointer exceptions. if (!allowHwAccel) @@ -824,6 +844,22 @@ namespace MediaBrowser.MediaEncoding.Encoder options.EnableTonemapping = false; } + if (imageStream.Width is not null && imageStream.Height is not null && !string.IsNullOrEmpty(imageStream.AspectRatio)) + { + // For hardware trickplay encoders, we need to re-calculate the size because they used fixed scale dimensions + var darParts = imageStream.AspectRatio.Split(':'); + var (wa, ha) = (double.Parse(darParts[0], CultureInfo.InvariantCulture), double.Parse(darParts[1], CultureInfo.InvariantCulture)); + // When dimension / DAR does not equal to 1:1, then the frames are most likely stored stretched. + // Note: this might be incorrect for 3D videos as the SAR stored might be per eye instead of per video, but we really can do little about it. + var shouldResetHeight = Math.Abs((imageStream.Width.Value * ha) - (imageStream.Height.Value * wa)) > .05; + if (shouldResetHeight) + { + // SAR = DAR * Height / Width + // RealHeight = Height / SAR = Height / (DAR * Height / Width) = Width / DAR + imageStream.Height = Convert.ToInt32(imageStream.Width.Value * ha / wa); + } + } + var baseRequest = new BaseEncodingJobOptions { MaxWidth = maxWidth, MaxFramerate = (float)(1.0 / interval.TotalSeconds) }; var jobState = new EncodingJobInfo(TranscodingJobType.Progressive) { @@ -848,6 +884,11 @@ namespace MediaBrowser.MediaEncoding.Encoder inputArg = "-threads " + threads + " " + inputArg; // HW accel args set a different input thread count, only set if disabled } + if (enableKeyFrameOnlyExtraction) + { + inputArg = "-skip_frame nokey " + inputArg; + } + var filterParam = encodingHelper.GetVideoProcessingFilterParam(jobState, options, vidEncoder).Trim(); if (string.IsNullOrWhiteSpace(filterParam)) { @@ -871,6 +912,15 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new InvalidOperationException("Empty or invalid input argument."); } + float? encoderQuality = qualityScale; + if (vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase)) + { + // vaapi's mjpeg encoder uses jpeg quality divided by QP2LAMBDA (118) as input, instead of ffmpeg defined qscale + // ffmpeg qscale is a value from 1-31, with 1 being best quality and 31 being worst + // jpeg quality is a value from 0-100, with 0 being worst quality and 100 being best + encoderQuality = (100 - ((qualityScale - 1) * (100 / 30))) / 118; + } + // Output arguments var targetDirectory = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N")); Directory.CreateDirectory(targetDirectory); @@ -884,7 +934,7 @@ namespace MediaBrowser.MediaEncoding.Encoder filterParam, outputThreads.GetValueOrDefault(_threads), vidEncoder, - qualityScale.HasValue ? "-qscale:v " + qualityScale.Value.ToString(CultureInfo.InvariantCulture) + " " : string.Empty, + qualityScale.HasValue ? "-qscale:v " + encoderQuality.Value.ToString(CultureInfo.InvariantCulture) + " " : string.Empty, "image2", outputPath); @@ -936,7 +986,7 @@ namespace MediaBrowser.MediaEncoding.Encoder var timeoutMs = _configurationManager.Configuration.ImageExtractionTimeoutMs; timeoutMs = timeoutMs <= 0 ? DefaultHdrImageExtractionTimeout : timeoutMs; - while (isResponsive) + while (isResponsive && !cancellationToken.IsCancellationRequested) { try { @@ -950,8 +1000,6 @@ namespace MediaBrowser.MediaEncoding.Encoder // We don't actually expect the process to be finished in one timeout span, just that one image has been generated. } - cancellationToken.ThrowIfCancellationRequested(); - var jpegCount = _fileSystem.GetFilePaths(targetDirectory).Count(); isResponsive = jpegCount > lastCount; @@ -960,7 +1008,12 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!ranToCompletion) { - _logger.LogInformation("Stopping trickplay extraction due to process inactivity."); + if (!isResponsive) + { + _logger.LogInformation("Trickplay process unresponsive."); + } + + _logger.LogInformation("Stopping trickplay extraction."); StopProcess(processWrapper, 1000); } } @@ -1123,19 +1176,21 @@ namespace MediaBrowser.MediaEncoding.Encoder /// <inheritdoc /> public IReadOnlyList<string> GetPrimaryPlaylistM2tsFiles(string path) - { - // Get all playable .m2ts files - var validPlaybackFiles = _blurayExaminer.GetDiscInfo(path).Files; + => _blurayExaminer.GetDiscInfo(path).Files; - // Get all files from the BDMV/STREAMING directory - var directoryFiles = _fileSystem.GetFiles(Path.Join(path, "BDMV", "STREAM")); + /// <inheritdoc /> + public string GetInputPathArgument(EncodingJobInfo state) + => GetInputPathArgument(state.MediaPath, state.MediaSource); - // Only return playable local .m2ts files - return directoryFiles - .Where(f => validPlaybackFiles.Contains(f.Name, StringComparer.OrdinalIgnoreCase)) - .Select(f => f.FullName) - .Order() - .ToList(); + /// <inheritdoc /> + public string GetInputPathArgument(string path, MediaSourceInfo mediaSource) + { + return mediaSource.VideoType switch + { + VideoType.Dvd => GetInputArgument(GetPrimaryPlaylistVobFiles(path, null), mediaSource), + VideoType.BluRay => GetInputArgument(GetPrimaryPlaylistM2tsFiles(path), mediaSource), + _ => GetInputArgument(path, mediaSource) + }; } /// <inheritdoc /> @@ -1158,6 +1213,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } // Generate concat configuration entries for each file and write to file + Directory.CreateDirectory(Path.GetDirectoryName(concatFilePath)); using StreamWriter sw = new StreamWriter(concatFilePath); foreach (var path in files) { |
