< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.MediaEncoding.EncodingHelper
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
Line coverage
0%
Covered lines: 26
Uncovered lines: 3701
Coverable lines: 3727
Total lines: 7798
Line coverage: 0.6%
Branch coverage
0%
Covered branches: 0
Total branches: 3713
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 9/14/2025 - 12:09:49 AM Line coverage: 0.6% (26/3722) Branch coverage: 0% (0/3701) Total lines: 778210/28/2025 - 12:11:27 AM Line coverage: 0.6% (26/3722) Branch coverage: 0% (0/3703) Total lines: 778212/4/2025 - 12:11:49 AM Line coverage: 0.6% (26/3727) Branch coverage: 0% (0/3713) Total lines: 7798 9/14/2025 - 12:09:49 AM Line coverage: 0.6% (26/3722) Branch coverage: 0% (0/3701) Total lines: 778210/28/2025 - 12:11:27 AM Line coverage: 0.6% (26/3722) Branch coverage: 0% (0/3703) Total lines: 778212/4/2025 - 12:11:49 AM Line coverage: 0.6% (26/3727) Branch coverage: 0% (0/3713) Total lines: 7798

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.cctor()100%210%
GetH264Encoder(...)100%210%
GetH265Encoder(...)100%210%
GetAv1Encoder(...)100%210%
GetH26xOrAv1Encoder(...)0%110100%
GetMjpegEncoder(...)0%210140%
IsVaapiSupported(...)0%2040%
IsVaapiFullSupported()0%272160%
IsRkmppFullSupported()0%4260%
IsOpenclFullSupported()0%4260%
IsCudaFullSupported()0%110100%
IsVulkanFullSupported()0%110100%
IsVideoToolboxFullSupported()0%7280%
IsSwTonemapAvailable(...)0%4260%
IsHwTonemapAvailable(...)0%420200%
IsVulkanHwTonemapAvailable(...)0%4260%
IsIntelVppTonemapAvailable(...)0%210140%
IsVideoToolboxTonemapAvailable(...)0%110100%
IsVideoStreamHevcRext(...)0%342180%
GetVideoEncoder(...)0%210140%
GetUserAgentParam(...)0%620%
GetRefererParam(...)0%620%
GetInputFormat(...)0%1482380%
GetDecoderFromCodec(...)0%7280%
InferAudioCodec(...)0%3422580%
InferVideoCodec(...)0%156120%
GetVideoProfileScore(...)0%4260%
GetAudioEncoder(...)0%420200%
GetRkmppDeviceArgs(...)0%620%
GetVideoToolboxDeviceArgs(...)0%620%
GetCudaDeviceArgs(...)0%2040%
GetVulkanDeviceArgs(...)0%7280%
GetOpenclDeviceArgs(...)0%7280%
GetD3d11vaDeviceArgs(...)0%4260%
GetVaapiDeviceArgs(...)0%272160%
GetDrmDeviceArgs(...)0%2040%
GetQsvDeviceArgs(...)0%7280%
GetFilterHwDeviceArgs(...)0%620%
GetGraphicalSubCanvasSize(...)0%420200%
GetInputVideoHwaccelArgs(...)0%145201200%
GetInputArgument(...)0%1190340%
IsH264(...)0%2040%
IsH265(...)0%2040%
IsAv1(...)0%620%
IsAAC(...)0%620%
IsDoviWithHdr10Bl(...)0%7280%
IsDovi(...)0%110100%
IsHdr10Plus(...)0%7280%
ShouldRemoveDynamicHdrMetadata(...)0%600240%
CanEncoderRemoveDynamicHdrMetadata(...)0%342180%
IsDoviRemoved(...)0%4260%
IsHdr10PlusRemoved(...)0%4260%
GetBitStreamArgs(...)0%702260%
GetAudioBitStreamArguments(...)0%110100%
GetSegmentFileExtension(...)0%620%
GetVideoBitrateParam(...)0%1482380%
GetEncoderParam(...)0%8010890%
NormalizeTranscodingLevel(...)0%506220%
GetTextSubtitlesFilter(...)0%210140%
GetFramerateParam(...)0%156120%
GetHlsVideoKeyFrameArguments(...)0%1806420%
GetVideoQualityParam(...)0%325801800%
CanStreamCopyVideo(...)0%175561320%
CanStreamCopyAudio(...)0%1190340%
GetVideoBitrateParamValue(...)0%506220%
GetMinBitrate(...)0%2040%
GetVideoBitrateScaleFactor(...)0%7280%
ScaleBitrate(...)0%110100%
GetAudioBitrateParam(...)100%210%
GetAudioBitrateParam(...)0%2162460%
GetAudioVbrModeParam(...)0%1190340%
GetAudioFilterParam(...)0%702260%
GetNumAudioChannelsParam(...)0%930300%
EnforceResolutionLimit(...)0%2040%
GetFastSeekCommandLineParameter(...)0%506220%
GetMapArgs(...)0%1640400%
GetNegativeMapArgsByFilters(...)0%2040%
GetMediaStream(...)0%156120%
GetFixedOutputSize(...)0%272160%
IsScaleRatioSupported(...)0%506220%
GetHwScaleFilter(...)0%600240%
GetGraphicalSubPreProcessFilters(...)0%342180%
GetAlphaSrcFilter(...)0%4260%
GetSwScaleFilter(...)0%702260%
GetFixedSwScaleFilter(...)0%132110%
GetSwDeinterlaceFilter(...)0%4260%
GetHwDeinterlaceFilter(...)0%1332360%
GetHwTonemapFilter(...)0%1332360%
GetLibplaceboFilter(...)0%506220%
GetVideoTransposeDirection(...)0%156120%
GetSwVidFilterChain(...)0%2550500%
GetNvidiaVidFilterChain(...)0%110100%
GetNvidiaVidFiltersPrefered(...)0%7832880%
GetAmdVidFilterChain(...)0%210140%
GetAmdDx11VidFiltersPrefered(...)0%7832880%
GetIntelVidFilterChain(...)0%506220%
GetIntelQsvDx11VidFiltersPrefered(...)0%208801440%
GetIntelQsvVaapiVidFiltersPrefered(...)0%175561320%
GetVaapiVidFilterChain(...)0%1190340%
GetIntelVaapiFullVidFiltersPrefered(...)0%126561120%
GetAmdVaapiFullVidFiltersPrefered(...)0%101001000%
GetVaapiLimitedVidFiltersPrefered(...)0%8190900%
GetAppleVidFilterChain(...)0%156120%
GetAppleVidFiltersPreferred(...)0%5550740%
GetRkmppVidFilterChain(...)0%272160%
GetRkmppVidFiltersPrefered(...)0%160021260%
GetVideoProcessingFilterParam(...)0%2756520%
GetOverwriteColorPropertiesParam(...)0%2040%
GetInputHdrParam(...)0%620%
GetOutputSdrParam(...)0%2040%
GetVideoColorBitDepth(...)0%600240%
GetHardwareVideoDecoder(...)0%2352480%
GetHwDecoderName(...)0%1190340%
GetHwaccelType(...)0%150061220%
GetQsvHwVidDecoder(...)0%3192560%
GetNvdecVidDecoder(...)0%2162460%
GetAmfVidDecoder(...)0%1056320%
GetVaapiVidDecoder(...)0%2756520%
GetVideotoolboxVidDecoder(...)0%2352480%
GetRkmppVidDecoder(...)0%2970540%
GetNumberOfThreads(...)0%4260%
TryStreamCopy(...)0%272160%
GetInputModifier(...)0%3906620%
AttachMediaSourceInfo(...)0%1806420%
ShiftAudioCodecsIfNeeded(...)0%110100%
ShiftVideoCodecsIfNeeded(...)0%110100%
NormalizeSubtitleEmbed(...)0%4260%
GetSubtitleEmbedArguments(...)0%7280%
GetProgressiveVideoFullCommandLine(...)0%2040%
GetOutputFFlags(...)0%2040%
GetProgressiveVideoArguments(...)0%1482380%
GetProgressiveVideoAudioArguments(...)0%600240%
GetProgressiveAudioFullCommandLine(...)0%930300%
FindIndex(...)0%4260%
IsCopyCodec(...)100%210%
ShouldEncodeSubtitle(...)0%2040%
GetVideoSyncOption(...)0%132110%

File(s)

/srv/git/jellyfin/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CS1591
 4// We need lowercase normalized string for ffmpeg
 5#pragma warning disable CA1308
 6
 7using System;
 8using System.Collections.Generic;
 9using System.Globalization;
 10using System.IO;
 11using System.Linq;
 12using System.Runtime.InteropServices;
 13using System.Text;
 14using System.Text.RegularExpressions;
 15using System.Threading;
 16using Jellyfin.Data;
 17using Jellyfin.Data.Enums;
 18using Jellyfin.Database.Implementations.Enums;
 19using Jellyfin.Extensions;
 20using MediaBrowser.Common.Configuration;
 21using MediaBrowser.Controller.Extensions;
 22using MediaBrowser.Controller.IO;
 23using MediaBrowser.Model.Configuration;
 24using MediaBrowser.Model.Dlna;
 25using MediaBrowser.Model.Dto;
 26using MediaBrowser.Model.Entities;
 27using MediaBrowser.Model.MediaInfo;
 28using Microsoft.Extensions.Configuration;
 29using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
 30
 31namespace MediaBrowser.Controller.MediaEncoding
 32{
 33    public partial class EncodingHelper
 34    {
 35        /// <summary>
 36        /// The codec validation regex.
 37        /// This regular expression matches strings that consist of alphanumeric characters, hyphens,
 38        /// periods, underscores, commas, and vertical bars, with a length between 0 and 40 characters.
 39        /// This should matches all common valid codecs.
 40        /// </summary>
 41        public const string ContainerValidationRegex = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
 42
 43        /// <summary>
 44        /// The level validation regex.
 45        /// This regular expression matches strings representing a double.
 46        /// </summary>
 47        public const string LevelValidationRegex = @"-?[0-9]+(?:\.[0-9]+)?";
 48
 49        private const string _defaultMjpegEncoder = "mjpeg";
 50
 51        private const string QsvAlias = "qs";
 52        private const string VaapiAlias = "va";
 53        private const string D3d11vaAlias = "dx11";
 54        private const string VideotoolboxAlias = "vt";
 55        private const string RkmppAlias = "rk";
 56        private const string OpenclAlias = "ocl";
 57        private const string CudaAlias = "cu";
 58        private const string DrmAlias = "dr";
 59        private const string VulkanAlias = "vk";
 60        private readonly IApplicationPaths _appPaths;
 61        private readonly IMediaEncoder _mediaEncoder;
 62        private readonly ISubtitleEncoder _subtitleEncoder;
 63        private readonly IConfiguration _config;
 64        private readonly IConfigurationManager _configurationManager;
 65        private readonly IPathManager _pathManager;
 66
 67        // i915 hang was fixed by linux 6.2 (3f882f2)
 2168        private readonly Version _minKerneli915Hang = new Version(5, 18);
 2169        private readonly Version _maxKerneli915Hang = new Version(6, 1, 3);
 2170        private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18);
 2171        private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15);
 72
 2173        private readonly Version _minFFmpegImplicitHwaccel = new Version(6, 0);
 2174        private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0);
 2175        private readonly Version _minFFmpegOclCuTonemapMode = new Version(5, 1, 3);
 2176        private readonly Version _minFFmpegSvtAv1Params = new Version(5, 1);
 2177        private readonly Version _minFFmpegVaapiH26xEncA53CcSei = new Version(6, 0);
 2178        private readonly Version _minFFmpegReadrateOption = new Version(5, 0);
 2179        private readonly Version _minFFmpegWorkingVtHwSurface = new Version(7, 0, 1);
 2180        private readonly Version _minFFmpegDisplayRotationOption = new Version(6, 0);
 2181        private readonly Version _minFFmpegAdvancedTonemapMode = new Version(7, 0, 1);
 2182        private readonly Version _minFFmpegAlteredVaVkInterop = new Version(7, 0, 1);
 2183        private readonly Version _minFFmpegQsvVppTonemapOption = new Version(7, 0, 1);
 2184        private readonly Version _minFFmpegQsvVppOutRangeOption = new Version(7, 0, 1);
 2185        private readonly Version _minFFmpegVaapiDeviceVendorId = new Version(7, 0, 1);
 2186        private readonly Version _minFFmpegQsvVppScaleModeOption = new Version(6, 0);
 2187        private readonly Version _minFFmpegRkmppHevcDecDoviRpu = new Version(7, 1, 1);
 88
 089        private static readonly Regex _containerValidationRegex = new(ContainerValidationRegex, RegexOptions.Compiled);
 90
 091        private static readonly string[] _videoProfilesH264 =
 092        [
 093            "ConstrainedBaseline",
 094            "Baseline",
 095            "Extended",
 096            "Main",
 097            "High",
 098            "ProgressiveHigh",
 099            "ConstrainedHigh",
 0100            "High10"
 0101        ];
 102
 0103        private static readonly string[] _videoProfilesH265 =
 0104        [
 0105            "Main",
 0106            "Main10"
 0107        ];
 108
 0109        private static readonly string[] _videoProfilesAv1 =
 0110        [
 0111            "Main",
 0112            "High",
 0113            "Professional",
 0114        ];
 115
 0116        private static readonly HashSet<string> _mp4ContainerNames = new(StringComparer.OrdinalIgnoreCase)
 0117        {
 0118            "mp4",
 0119            "m4a",
 0120            "m4p",
 0121            "m4b",
 0122            "m4r",
 0123            "m4v",
 0124        };
 125
 0126        private static readonly TonemappingMode[] _legacyTonemapModes = [TonemappingMode.max, TonemappingMode.rgb];
 0127        private static readonly TonemappingMode[] _advancedTonemapModes = [TonemappingMode.lum, TonemappingMode.itp];
 128
 129        // Set max transcoding channels for encoders that can't handle more than a set amount of channels
 130        // AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels
 0131        private static readonly Dictionary<string, int> _audioTranscodeChannelLookup = new(StringComparer.OrdinalIgnoreC
 0132        {
 0133            { "libmp3lame", 2 },
 0134            { "libfdk_aac", 6 },
 0135            { "ac3", 6 },
 0136            { "eac3", 6 },
 0137            { "dca", 6 },
 0138            { "mlp", 6 },
 0139            { "truehd", 6 },
 0140        };
 141
 0142        private static readonly Dictionary<HardwareAccelerationType, string> _mjpegCodecMap = new()
 0143        {
 0144            { HardwareAccelerationType.vaapi, _defaultMjpegEncoder + "_vaapi" },
 0145            { HardwareAccelerationType.qsv, _defaultMjpegEncoder + "_qsv" },
 0146            { HardwareAccelerationType.videotoolbox, _defaultMjpegEncoder + "_videotoolbox" },
 0147            { HardwareAccelerationType.rkmpp, _defaultMjpegEncoder + "_rkmpp" }
 0148        };
 149
 0150        public static readonly string[] LosslessAudioCodecs =
 0151        [
 0152            "alac",
 0153            "ape",
 0154            "flac",
 0155            "mlp",
 0156            "truehd",
 0157            "wavpack"
 0158        ];
 159
 160        public EncodingHelper(
 161            IApplicationPaths appPaths,
 162            IMediaEncoder mediaEncoder,
 163            ISubtitleEncoder subtitleEncoder,
 164            IConfiguration config,
 165            IConfigurationManager configurationManager,
 166            IPathManager pathManager)
 167        {
 21168            _appPaths = appPaths;
 21169            _mediaEncoder = mediaEncoder;
 21170            _subtitleEncoder = subtitleEncoder;
 21171            _config = config;
 21172            _configurationManager = configurationManager;
 21173            _pathManager = pathManager;
 21174        }
 175
 176        private enum DynamicHdrMetadataRemovalPlan
 177        {
 178            None,
 179            RemoveDovi,
 180            RemoveHdr10Plus,
 181        }
 182
 183        [GeneratedRegex(@"\s+")]
 184        private static partial Regex WhiteSpaceRegex();
 185
 186        public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0187            => GetH26xOrAv1Encoder("libx264", "h264", state, encodingOptions);
 188
 189        public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0190            => GetH26xOrAv1Encoder("libx265", "hevc", state, encodingOptions);
 191
 192        public string GetAv1Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 0193            => GetH26xOrAv1Encoder("libsvtav1", "av1", state, encodingOptions);
 194
 195        private string GetH26xOrAv1Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptio
 196        {
 197            // Only use alternative encoders for video files.
 198            // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying
 199            // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such 
 0200            if (state.VideoType == VideoType.VideoFile)
 201            {
 0202                var hwType = encodingOptions.HardwareAccelerationType;
 203
 0204                var codecMap = new Dictionary<HardwareAccelerationType, string>()
 0205                {
 0206                    { HardwareAccelerationType.amf,                  hwEncoder + "_amf" },
 0207                    { HardwareAccelerationType.nvenc,                hwEncoder + "_nvenc" },
 0208                    { HardwareAccelerationType.qsv,                  hwEncoder + "_qsv" },
 0209                    { HardwareAccelerationType.vaapi,                hwEncoder + "_vaapi" },
 0210                    { HardwareAccelerationType.videotoolbox,         hwEncoder + "_videotoolbox" },
 0211                    { HardwareAccelerationType.v4l2m2m,              hwEncoder + "_v4l2m2m" },
 0212                    { HardwareAccelerationType.rkmpp,                hwEncoder + "_rkmpp" },
 0213                };
 214
 0215                if (hwType != HardwareAccelerationType.none
 0216                    && encodingOptions.EnableHardwareEncoding
 0217                    && codecMap.TryGetValue(hwType, out var preferredEncoder)
 0218                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 219                {
 0220                    return preferredEncoder;
 221                }
 222            }
 223
 0224            return defaultEncoder;
 225        }
 226
 227        private string GetMjpegEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 228        {
 0229            if (state.VideoType == VideoType.VideoFile)
 230            {
 0231                var hwType = encodingOptions.HardwareAccelerationType;
 232
 233                // Only enable VA-API MJPEG encoder on Intel iHD driver.
 234                // Legacy platforms supported ONLY by i965 do not support MJPEG encoder.
 0235                if (hwType == HardwareAccelerationType.vaapi
 0236                    && !_mediaEncoder.IsVaapiDeviceInteliHD)
 237                {
 0238                    return _defaultMjpegEncoder;
 239                }
 240
 0241                if (hwType != HardwareAccelerationType.none
 0242                    && encodingOptions.EnableHardwareEncoding
 0243                    && _mjpegCodecMap.TryGetValue(hwType, out var preferredEncoder)
 0244                    && _mediaEncoder.SupportsEncoder(preferredEncoder))
 245                {
 0246                    return preferredEncoder;
 247                }
 248            }
 249
 0250            return _defaultMjpegEncoder;
 251        }
 252
 253        private bool IsVaapiSupported(EncodingJobInfo state)
 254        {
 255            // vaapi will throw an error with this input
 256            // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99.
 0257            if (string.Equals(state.VideoStream?.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 258            {
 0259                return false;
 260            }
 261
 0262            return _mediaEncoder.SupportsHwaccel("vaapi");
 263        }
 264
 265        private bool IsVaapiFullSupported()
 266        {
 0267            return _mediaEncoder.SupportsHwaccel("drm")
 0268                   && _mediaEncoder.SupportsHwaccel("vaapi")
 0269                   && _mediaEncoder.SupportsFilter("scale_vaapi")
 0270                   && _mediaEncoder.SupportsFilter("deinterlace_vaapi")
 0271                   && _mediaEncoder.SupportsFilter("tonemap_vaapi")
 0272                   && _mediaEncoder.SupportsFilter("procamp_vaapi")
 0273                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVaapiFrameSync)
 0274                   && _mediaEncoder.SupportsFilter("transpose_vaapi")
 0275                   && _mediaEncoder.SupportsFilter("hwupload_vaapi");
 276        }
 277
 278        private bool IsRkmppFullSupported()
 279        {
 0280            return _mediaEncoder.SupportsHwaccel("rkmpp")
 0281                   && _mediaEncoder.SupportsFilter("scale_rkrga")
 0282                   && _mediaEncoder.SupportsFilter("vpp_rkrga")
 0283                   && _mediaEncoder.SupportsFilter("overlay_rkrga");
 284        }
 285
 286        private bool IsOpenclFullSupported()
 287        {
 0288            return _mediaEncoder.SupportsHwaccel("opencl")
 0289                   && _mediaEncoder.SupportsFilter("scale_opencl")
 0290                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390)
 0291                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclFrameSync);
 292
 293            // Let transpose_opencl optional for the time being.
 294        }
 295
 296        private bool IsCudaFullSupported()
 297        {
 0298            return _mediaEncoder.SupportsHwaccel("cuda")
 0299                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat)
 0300                   && _mediaEncoder.SupportsFilter("yadif_cuda")
 0301                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName)
 0302                   && _mediaEncoder.SupportsFilter("overlay_cuda")
 0303                   && _mediaEncoder.SupportsFilter("hwupload_cuda");
 304
 305            // Let transpose_cuda optional for the time being.
 306        }
 307
 308        private bool IsVulkanFullSupported()
 309        {
 0310            return _mediaEncoder.SupportsHwaccel("vulkan")
 0311                   && _mediaEncoder.SupportsFilter("libplacebo")
 0312                   && _mediaEncoder.SupportsFilter("scale_vulkan")
 0313                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVulkanFrameSync)
 0314                   && _mediaEncoder.SupportsFilter("transpose_vulkan")
 0315                   && _mediaEncoder.SupportsFilter("flip_vulkan");
 316        }
 317
 318        private bool IsVideoToolboxFullSupported()
 319        {
 0320            return _mediaEncoder.SupportsHwaccel("videotoolbox")
 0321                && _mediaEncoder.SupportsFilter("yadif_videotoolbox")
 0322                && _mediaEncoder.SupportsFilter("overlay_videotoolbox")
 0323                && _mediaEncoder.SupportsFilter("tonemap_videotoolbox")
 0324                && _mediaEncoder.SupportsFilter("scale_vt");
 325
 326            // Let transpose_vt optional for the time being.
 327        }
 328
 329        private bool IsSwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 330        {
 0331            if (state.VideoStream is null
 0332                || GetVideoColorBitDepth(state) < 10
 0333                || !_mediaEncoder.SupportsFilter("tonemapx"))
 334            {
 0335                return false;
 336            }
 337
 0338            return state.VideoStream.VideoRange == VideoRange.HDR;
 339        }
 340
 341        private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 342        {
 0343            if (state.VideoStream is null
 0344                || !options.EnableTonemapping
 0345                || GetVideoColorBitDepth(state) < 10)
 346            {
 0347                return false;
 348            }
 349
 0350            if (state.VideoStream.VideoRange == VideoRange.HDR
 0351                && state.VideoStream.VideoRangeType == VideoRangeType.DOVI)
 352            {
 353                // Only native SW decoder, HW accelerator and hevc_rkmpp decoder can parse dovi rpu.
 0354                var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 355
 0356                var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 0357                if (isRkmppDecoder
 0358                    && _mediaEncoder.EncoderVersion >= _minFFmpegRkmppHevcDecDoviRpu
 0359                    && string.Equals(state.VideoStream?.Codec, "hevc", StringComparison.OrdinalIgnoreCase))
 360                {
 0361                    return true;
 362                }
 363
 0364                var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 0365                var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 0366                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 0367                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 0368                var isVideoToolBoxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 0369                return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder || isVideoToolBoxDecoder;
 370            }
 371
 372            // GPU tonemapping supports all HDR RangeTypes
 0373            return state.VideoStream.VideoRange == VideoRange.HDR;
 374        }
 375
 376        private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 377        {
 0378            if (state.VideoStream is null)
 379            {
 0380                return false;
 381            }
 382
 383            // libplacebo has partial Dolby Vision to SDR tonemapping support.
 0384            return options.EnableTonemapping
 0385                   && state.VideoStream.VideoRange == VideoRange.HDR
 0386                   && GetVideoColorBitDepth(state) == 10;
 387        }
 388
 389        private bool IsIntelVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 390        {
 0391            if (state.VideoStream is null
 0392                || !options.EnableVppTonemapping
 0393                || GetVideoColorBitDepth(state) < 10)
 394            {
 0395                return false;
 396            }
 397
 398            // prefer 'tonemap_vaapi' over 'vpp_qsv' on Linux for supporting Gen9/KBLx.
 399            // 'vpp_qsv' requires VPL, which is only supported on Gen12/TGLx and newer.
 0400            if (OperatingSystem.IsWindows()
 0401                && options.HardwareAccelerationType == HardwareAccelerationType.qsv
 0402                && _mediaEncoder.EncoderVersion < _minFFmpegQsvVppTonemapOption)
 403            {
 0404                return false;
 405            }
 406
 0407            return state.VideoStream.VideoRange == VideoRange.HDR
 0408                   && IsDoviWithHdr10Bl(state.VideoStream);
 409        }
 410
 411        private bool IsVideoToolboxTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
 412        {
 0413            if (state.VideoStream is null
 0414                || !options.EnableVideoToolboxTonemapping
 0415                || GetVideoColorBitDepth(state) < 10)
 416            {
 0417                return false;
 418            }
 419
 420            // Certain DV profile 5 video works in Safari with direct playing, but the VideoToolBox does not produce cor
 421            // All other HDR formats working.
 0422            return state.VideoStream.VideoRange == VideoRange.HDR
 0423                   && (IsDoviWithHdr10Bl(state.VideoStream)
 0424                       || state.VideoStream.VideoRangeType is VideoRangeType.HLG);
 425        }
 426
 427        private bool IsVideoStreamHevcRext(EncodingJobInfo state)
 428        {
 0429            var videoStream = state.VideoStream;
 0430            if (videoStream is null)
 431            {
 0432                return false;
 433            }
 434
 0435            return string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 0436                   && (string.Equals(videoStream.Profile, "Rext", StringComparison.OrdinalIgnoreCase)
 0437                       || string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 0438                       || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 0439                       || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 0440                       || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 0441                       || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase)
 0442                       || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)
 0443                       || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase));
 444        }
 445
 446        /// <summary>
 447        /// Gets the name of the output video codec.
 448        /// </summary>
 449        /// <param name="state">Encoding state.</param>
 450        /// <param name="encodingOptions">Encoding options.</param>
 451        /// <returns>Encoder string.</returns>
 452        public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
 453        {
 0454            var codec = state.OutputVideoCodec;
 455
 0456            if (!string.IsNullOrEmpty(codec))
 457            {
 0458                if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 459                {
 0460                    return GetAv1Encoder(state, encodingOptions);
 461                }
 462
 0463                if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 0464                    || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
 465                {
 0466                    return GetH265Encoder(state, encodingOptions);
 467                }
 468
 0469                if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
 470                {
 0471                    return GetH264Encoder(state, encodingOptions);
 472                }
 473
 0474                if (string.Equals(codec, "mjpeg", StringComparison.OrdinalIgnoreCase))
 475                {
 0476                    return GetMjpegEncoder(state, encodingOptions);
 477                }
 478
 0479                if (_containerValidationRegex.IsMatch(codec))
 480                {
 0481                    return codec.ToLowerInvariant();
 482                }
 483            }
 484
 0485            return "copy";
 486        }
 487
 488        /// <summary>
 489        /// Gets the user agent param.
 490        /// </summary>
 491        /// <param name="state">The state.</param>
 492        /// <returns>System.String.</returns>
 493        public string GetUserAgentParam(EncodingJobInfo state)
 494        {
 0495            if (state.RemoteHttpHeaders.TryGetValue("User-Agent", out string useragent))
 496            {
 0497                return "-user_agent \"" + useragent + "\"";
 498            }
 499
 0500            return string.Empty;
 501        }
 502
 503        /// <summary>
 504        /// Gets the referer param.
 505        /// </summary>
 506        /// <param name="state">The state.</param>
 507        /// <returns>System.String.</returns>
 508        public string GetRefererParam(EncodingJobInfo state)
 509        {
 0510            if (state.RemoteHttpHeaders.TryGetValue("Referer", out string referer))
 511            {
 0512                return "-referer \"" + referer + "\"";
 513            }
 514
 0515            return string.Empty;
 516        }
 517
 518        public static string GetInputFormat(string container)
 519        {
 0520            if (string.IsNullOrEmpty(container) || !_containerValidationRegex.IsMatch(container))
 521            {
 0522                return null;
 523            }
 524
 0525            container = container.Replace("mkv", "matroska", StringComparison.OrdinalIgnoreCase);
 526
 0527            if (string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
 528            {
 0529                return "mpegts";
 530            }
 531
 532            // For these need to find out the ffmpeg names
 0533            if (string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
 534            {
 0535                return null;
 536            }
 537
 0538            if (string.Equals(container, "wmv", StringComparison.OrdinalIgnoreCase))
 539            {
 0540                return null;
 541            }
 542
 0543            if (string.Equals(container, "mts", StringComparison.OrdinalIgnoreCase))
 544            {
 0545                return null;
 546            }
 547
 0548            if (string.Equals(container, "vob", StringComparison.OrdinalIgnoreCase))
 549            {
 0550                return null;
 551            }
 552
 0553            if (string.Equals(container, "mpg", StringComparison.OrdinalIgnoreCase))
 554            {
 0555                return null;
 556            }
 557
 0558            if (string.Equals(container, "mpeg", StringComparison.OrdinalIgnoreCase))
 559            {
 0560                return null;
 561            }
 562
 0563            if (string.Equals(container, "rec", StringComparison.OrdinalIgnoreCase))
 564            {
 0565                return null;
 566            }
 567
 0568            if (string.Equals(container, "dvr-ms", StringComparison.OrdinalIgnoreCase))
 569            {
 0570                return null;
 571            }
 572
 0573            if (string.Equals(container, "ogm", StringComparison.OrdinalIgnoreCase))
 574            {
 0575                return null;
 576            }
 577
 0578            if (string.Equals(container, "divx", StringComparison.OrdinalIgnoreCase))
 579            {
 0580                return null;
 581            }
 582
 0583            if (string.Equals(container, "tp", StringComparison.OrdinalIgnoreCase))
 584            {
 0585                return null;
 586            }
 587
 0588            if (string.Equals(container, "rmvb", StringComparison.OrdinalIgnoreCase))
 589            {
 0590                return null;
 591            }
 592
 0593            if (string.Equals(container, "rtp", StringComparison.OrdinalIgnoreCase))
 594            {
 0595                return null;
 596            }
 597
 598            // Seeing reported failures here, not sure yet if this is related to specifying input format
 0599            if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase))
 600            {
 0601                return null;
 602            }
 603
 604            // obviously don't do this for strm files
 0605            if (string.Equals(container, "strm", StringComparison.OrdinalIgnoreCase))
 606            {
 0607                return null;
 608            }
 609
 610            // ISO files don't have an ffmpeg format
 0611            if (string.Equals(container, "iso", StringComparison.OrdinalIgnoreCase))
 612            {
 0613                return null;
 614            }
 615
 0616            return container;
 617        }
 618
 619        /// <summary>
 620        /// Gets decoder from a codec.
 621        /// </summary>
 622        /// <param name="codec">Codec to use.</param>
 623        /// <returns>Decoder string.</returns>
 624        public string GetDecoderFromCodec(string codec)
 625        {
 626            // For these need to find out the ffmpeg names
 0627            if (string.Equals(codec, "mp2", StringComparison.OrdinalIgnoreCase))
 628            {
 0629                return null;
 630            }
 631
 0632            if (string.Equals(codec, "aac_latm", StringComparison.OrdinalIgnoreCase))
 633            {
 0634                return null;
 635            }
 636
 0637            if (string.Equals(codec, "eac3", StringComparison.OrdinalIgnoreCase))
 638            {
 0639                return null;
 640            }
 641
 0642            if (_mediaEncoder.SupportsDecoder(codec))
 643            {
 0644                return codec;
 645            }
 646
 0647            return null;
 648        }
 649
 650        /// <summary>
 651        /// Infers the audio codec based on the url.
 652        /// </summary>
 653        /// <param name="container">Container to use.</param>
 654        /// <returns>Codec string.</returns>
 655        public string InferAudioCodec(string container)
 656        {
 0657            if (string.IsNullOrWhiteSpace(container))
 658            {
 659                // this may not work, but if the client is that broken we cannot do anything better
 0660                return "aac";
 661            }
 662
 0663            var inferredCodec = container.ToLowerInvariant();
 664
 0665            return inferredCodec switch
 0666            {
 0667                "ogg" or "oga" or "ogv" or "webm" or "webma" => "opus",
 0668                "m4a" or "m4b" or "mp4" or "mov" or "mkv" or "mka" => "aac",
 0669                "ts" or "avi" or "flv" or "f4v" or "swf" => "mp3",
 0670                _ => inferredCodec
 0671            };
 672        }
 673
 674        /// <summary>
 675        /// Infers the video codec.
 676        /// </summary>
 677        /// <param name="url">The URL.</param>
 678        /// <returns>System.Nullable{VideoCodecs}.</returns>
 679        public string InferVideoCodec(string url)
 680        {
 0681            var ext = Path.GetExtension(url.AsSpan());
 682
 0683            if (ext.Equals(".asf", StringComparison.OrdinalIgnoreCase))
 684            {
 0685                return "wmv";
 686            }
 687
 0688            if (ext.Equals(".webm", StringComparison.OrdinalIgnoreCase))
 689            {
 690                // TODO: this may not always mean VP8, as the codec ages
 0691                return "vp8";
 692            }
 693
 0694            if (ext.Equals(".ogg", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ogv", StringComparison.OrdinalIgn
 695            {
 0696                return "theora";
 697            }
 698
 0699            if (ext.Equals(".m3u8", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ts", StringComparison.OrdinalIgn
 700            {
 0701                return "h264";
 702            }
 703
 0704            return "copy";
 705        }
 706
 707        public int GetVideoProfileScore(string videoCodec, string videoProfile)
 708        {
 709            // strip spaces because they may be stripped out on the query string
 0710            string profile = videoProfile.Replace(" ", string.Empty, StringComparison.Ordinal);
 0711            if (string.Equals("h264", videoCodec, StringComparison.OrdinalIgnoreCase))
 712            {
 0713                return Array.FindIndex(_videoProfilesH264, x => string.Equals(x, profile, StringComparison.OrdinalIgnore
 714            }
 715
 0716            if (string.Equals("hevc", videoCodec, StringComparison.OrdinalIgnoreCase))
 717            {
 0718                return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnore
 719            }
 720
 0721            if (string.Equals("av1", videoCodec, StringComparison.OrdinalIgnoreCase))
 722            {
 0723                return Array.FindIndex(_videoProfilesAv1, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreC
 724            }
 725
 0726            return -1;
 727        }
 728
 729        /// <summary>
 730        /// Gets the audio encoder.
 731        /// </summary>
 732        /// <param name="state">The state.</param>
 733        /// <returns>System.String.</returns>
 734        public string GetAudioEncoder(EncodingJobInfo state)
 735        {
 0736            var codec = state.OutputAudioCodec;
 737
 0738            if (!_containerValidationRegex.IsMatch(codec))
 739            {
 0740                codec = "aac";
 741            }
 742
 0743            if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
 744            {
 745                // Use Apple's aac encoder if available as it provides best audio quality
 0746                if (_mediaEncoder.SupportsEncoder("aac_at"))
 747                {
 0748                    return "aac_at";
 749                }
 750
 751                // Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
 0752                if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
 753                {
 0754                    return "libfdk_aac";
 755                }
 756
 0757                return "aac";
 758            }
 759
 0760            if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
 761            {
 0762                return "libmp3lame";
 763            }
 764
 0765            if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase))
 766            {
 0767                return "libvorbis";
 768            }
 769
 0770            if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
 771            {
 0772                return "libopus";
 773            }
 774
 0775            if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase))
 776            {
 0777                return "flac";
 778            }
 779
 0780            if (string.Equals(codec, "dts", StringComparison.OrdinalIgnoreCase))
 781            {
 0782                return "dca";
 783            }
 784
 0785            if (string.Equals(codec, "alac", StringComparison.OrdinalIgnoreCase))
 786            {
 787                // The ffmpeg upstream breaks the AudioToolbox ALAC encoder in version 6.1 but fixes it in version 7.0.
 788                // Since ALAC is lossless in quality and the AudioToolbox encoder is not faster,
 789                // its only benefit is a smaller file size.
 790                // To prevent problems, use the ffmpeg native encoder instead.
 0791                return "alac";
 792            }
 793
 0794            return codec.ToLowerInvariant();
 795        }
 796
 797        private string GetRkmppDeviceArgs(string alias)
 798        {
 0799            alias ??= RkmppAlias;
 800
 801            // device selection in rk is not supported.
 0802            return " -init_hw_device rkmpp=" + alias;
 803        }
 804
 805        private string GetVideoToolboxDeviceArgs(string alias)
 806        {
 0807            alias ??= VideotoolboxAlias;
 808
 809            // device selection in vt is not supported.
 0810            return " -init_hw_device videotoolbox=" + alias;
 811        }
 812
 813        private string GetCudaDeviceArgs(int deviceIndex, string alias)
 814        {
 0815            alias ??= CudaAlias;
 0816            deviceIndex = deviceIndex >= 0
 0817                ? deviceIndex
 0818                : 0;
 819
 0820            return string.Format(
 0821                CultureInfo.InvariantCulture,
 0822                " -init_hw_device cuda={0}:{1}",
 0823                alias,
 0824                deviceIndex);
 825        }
 826
 827        private string GetVulkanDeviceArgs(int deviceIndex, string deviceName, string srcDeviceAlias, string alias)
 828        {
 0829            alias ??= VulkanAlias;
 0830            deviceIndex = deviceIndex >= 0
 0831                ? deviceIndex
 0832                : 0;
 0833            var vendorOpts = string.IsNullOrEmpty(deviceName)
 0834                ? ":" + deviceIndex
 0835                : ":" + "\"" + deviceName + "\"";
 0836            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0837                ? vendorOpts
 0838                : "@" + srcDeviceAlias;
 839
 0840            return string.Format(
 0841                CultureInfo.InvariantCulture,
 0842                " -init_hw_device vulkan={0}{1}",
 0843                alias,
 0844                options);
 845        }
 846
 847        private string GetOpenclDeviceArgs(int deviceIndex, string deviceVendorName, string srcDeviceAlias, string alias
 848        {
 0849            alias ??= OpenclAlias;
 0850            deviceIndex = deviceIndex >= 0
 0851                ? deviceIndex
 0852                : 0;
 0853            var vendorOpts = string.IsNullOrEmpty(deviceVendorName)
 0854                ? ":0.0"
 0855                : ":." + deviceIndex + ",device_vendor=\"" + deviceVendorName + "\"";
 0856            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0857                ? vendorOpts
 0858                : "@" + srcDeviceAlias;
 859
 0860            return string.Format(
 0861                CultureInfo.InvariantCulture,
 0862                " -init_hw_device opencl={0}{1}",
 0863                alias,
 0864                options);
 865        }
 866
 867        private string GetD3d11vaDeviceArgs(int deviceIndex, string deviceVendorId, string alias)
 868        {
 0869            alias ??= D3d11vaAlias;
 0870            deviceIndex = deviceIndex >= 0 ? deviceIndex : 0;
 0871            var options = string.IsNullOrEmpty(deviceVendorId)
 0872                ? deviceIndex.ToString(CultureInfo.InvariantCulture)
 0873                : ",vendor=" + deviceVendorId;
 874
 0875            return string.Format(
 0876                CultureInfo.InvariantCulture,
 0877                " -init_hw_device d3d11va={0}:{1}",
 0878                alias,
 0879                options);
 880        }
 881
 882        private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string vendorId, st
 883        {
 0884            alias ??= VaapiAlias;
 0885            var haveVendorId = !string.IsNullOrEmpty(vendorId)
 0886                && _mediaEncoder.EncoderVersion >= _minFFmpegVaapiDeviceVendorId;
 887
 888            // Priority: 'renderNodePath' > 'vendorId' > 'kernelDriver'
 0889            var driverOpts = File.Exists(renderNodePath)
 0890                ? renderNodePath
 0891                : (haveVendorId ? $",vendor_id={vendorId}" : (string.IsNullOrEmpty(kernelDriver) ? string.Empty : $",ker
 892
 893            // 'driver' behaves similarly to env LIBVA_DRIVER_NAME
 0894            driverOpts += string.IsNullOrEmpty(driver) ? string.Empty : ",driver=" + driver;
 895
 0896            var options = string.IsNullOrEmpty(srcDeviceAlias)
 0897                ? (string.IsNullOrEmpty(driverOpts) ? string.Empty : ":" + driverOpts)
 0898                : "@" + srcDeviceAlias;
 899
 0900            return string.Format(
 0901                CultureInfo.InvariantCulture,
 0902                " -init_hw_device vaapi={0}{1}",
 0903                alias,
 0904                options);
 905        }
 906
 907        private string GetDrmDeviceArgs(string renderNodePath, string alias)
 908        {
 0909            alias ??= DrmAlias;
 0910            renderNodePath = renderNodePath ?? "/dev/dri/renderD128";
 911
 0912            return string.Format(
 0913                CultureInfo.InvariantCulture,
 0914                " -init_hw_device drm={0}:{1}",
 0915                alias,
 0916                renderNodePath);
 917        }
 918
 919        private string GetQsvDeviceArgs(string renderNodePath, string alias)
 920        {
 0921            var arg = " -init_hw_device qsv=" + (alias ?? QsvAlias);
 0922            if (OperatingSystem.IsLinux())
 923            {
 924                // derive qsv from vaapi device
 0925                return GetVaapiDeviceArgs(renderNodePath, "iHD", "i915", "0x8086", null, VaapiAlias) + arg + "@" + Vaapi
 926            }
 927
 0928            if (OperatingSystem.IsWindows())
 929            {
 930                // on Windows, the deviceIndex is an int
 0931                if (int.TryParse(renderNodePath, NumberStyles.Integer, CultureInfo.InvariantCulture, out int deviceIndex
 932                {
 0933                    return GetD3d11vaDeviceArgs(deviceIndex, string.Empty, D3d11vaAlias) + arg + "@" + D3d11vaAlias;
 934                }
 935
 936                // derive qsv from d3d11va device
 0937                return GetD3d11vaDeviceArgs(0, "0x8086", D3d11vaAlias) + arg + "@" + D3d11vaAlias;
 938            }
 939
 0940            return null;
 941        }
 942
 943        private string GetFilterHwDeviceArgs(string alias)
 944        {
 0945            return string.IsNullOrEmpty(alias)
 0946                ? string.Empty
 0947                : " -filter_hw_device " + alias;
 948        }
 949
 950        public string GetGraphicalSubCanvasSize(EncodingJobInfo state)
 951        {
 952            // DVBSUB uses the fixed canvas size 720x576
 0953            if (state.SubtitleStream is not null
 0954                && ShouldEncodeSubtitle(state)
 0955                && !state.SubtitleStream.IsTextSubtitleStream
 0956                && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 957            {
 0958                var subtitleWidth = state.SubtitleStream?.Width;
 0959                var subtitleHeight = state.SubtitleStream?.Height;
 960
 0961                if (subtitleWidth.HasValue
 0962                    && subtitleHeight.HasValue
 0963                    && subtitleWidth.Value > 0
 0964                    && subtitleHeight.Value > 0)
 965                {
 0966                    return string.Format(
 0967                        CultureInfo.InvariantCulture,
 0968                        " -canvas_size {0}x{1}",
 0969                        subtitleWidth.Value,
 0970                        subtitleHeight.Value);
 971                }
 972            }
 973
 0974            return string.Empty;
 975        }
 976
 977        /// <summary>
 978        /// Gets the input video hwaccel argument.
 979        /// </summary>
 980        /// <param name="state">Encoding state.</param>
 981        /// <param name="options">Encoding options.</param>
 982        /// <returns>Input video hwaccel arguments.</returns>
 983        public string GetInputVideoHwaccelArgs(EncodingJobInfo state, EncodingOptions options)
 984        {
 0985            if (!state.IsVideoRequest)
 986            {
 0987                return string.Empty;
 988            }
 989
 0990            var vidEncoder = GetVideoEncoder(state, options) ?? string.Empty;
 0991            if (IsCopyCodec(vidEncoder))
 992            {
 0993                return string.Empty;
 994            }
 995
 0996            var args = new StringBuilder();
 0997            var isWindows = OperatingSystem.IsWindows();
 0998            var isLinux = OperatingSystem.IsLinux();
 0999            var isMacOS = OperatingSystem.IsMacOS();
 01000            var optHwaccelType = options.HardwareAccelerationType;
 01001            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 01002            var isHwTonemapAvailable = IsHwTonemapAvailable(state, options);
 1003
 01004            if (optHwaccelType == HardwareAccelerationType.vaapi)
 1005            {
 01006                if (!isLinux || !_mediaEncoder.SupportsHwaccel("vaapi"))
 1007                {
 01008                    return string.Empty;
 1009                }
 1010
 01011                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01012                var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01013                if (!isVaapiDecoder && !isVaapiEncoder)
 1014                {
 01015                    return string.Empty;
 1016                }
 1017
 01018                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1019                {
 01020                    args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "iHD", null, null, null, VaapiAlias));
 1021                }
 01022                else if (_mediaEncoder.IsVaapiDeviceInteli965)
 1023                {
 1024                    // Only override i965 since it has lower priority than iHD in libva lookup.
 01025                    Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965");
 01026                    Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965");
 01027                    args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "i965", null, null, null, VaapiAlias));
 1028                }
 1029
 01030                var filterDevArgs = string.Empty;
 01031                var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
 1032
 01033                if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 1034                {
 01035                    if (doOclTonemap && !isVaapiDecoder)
 1036                    {
 01037                        args.Append(GetOpenclDeviceArgs(0, null, VaapiAlias, OpenclAlias));
 01038                        filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1039                    }
 1040                }
 01041                else if (_mediaEncoder.IsVaapiDeviceAmd)
 1042                {
 1043                    // Disable AMD EFC feature since it's still unstable in upstream Mesa.
 01044                    Environment.SetEnvironmentVariable("AMD_DEBUG", "noefc");
 1045
 01046                    if (IsVulkanFullSupported()
 01047                        && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 01048                        && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 1049                    {
 01050                        args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias));
 01051                        args.Append(GetVaapiDeviceArgs(null, null, null, null, DrmAlias, VaapiAlias));
 01052                        args.Append(GetVulkanDeviceArgs(0, null, DrmAlias, VulkanAlias));
 1053
 1054                        // libplacebo wants an explicitly set vulkan filter device.
 01055                        filterDevArgs = GetFilterHwDeviceArgs(VulkanAlias);
 1056                    }
 1057                    else
 1058                    {
 01059                        args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, null, null, VaapiAlias));
 01060                        filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias);
 1061
 01062                        if (doOclTonemap)
 1063                        {
 1064                            // ROCm/ROCr OpenCL runtime
 01065                            args.Append(GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, OpenclAlias));
 01066                            filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1067                        }
 1068                    }
 1069                }
 01070                else if (doOclTonemap)
 1071                {
 01072                    args.Append(GetOpenclDeviceArgs(0, null, null, OpenclAlias));
 01073                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1074                }
 1075
 01076                args.Append(filterDevArgs);
 1077            }
 01078            else if (optHwaccelType == HardwareAccelerationType.qsv)
 1079            {
 01080                if ((!isLinux && !isWindows) || !_mediaEncoder.SupportsHwaccel("qsv"))
 1081                {
 01082                    return string.Empty;
 1083                }
 1084
 01085                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 01086                var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 01087                var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 01088                var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 01089                var isHwDecoder = isQsvDecoder || isVaapiDecoder || isD3d11vaDecoder;
 01090                if (!isHwDecoder && !isQsvEncoder)
 1091                {
 01092                    return string.Empty;
 1093                }
 1094
 01095                args.Append(GetQsvDeviceArgs(options.QsvDevice, QsvAlias));
 01096                var filterDevArgs = GetFilterHwDeviceArgs(QsvAlias);
 1097                // child device used by qsv.
 01098                if (_mediaEncoder.SupportsHwaccel("vaapi") || _mediaEncoder.SupportsHwaccel("d3d11va"))
 1099                {
 01100                    if (isHwTonemapAvailable && IsOpenclFullSupported())
 1101                    {
 01102                        var srcAlias = isLinux ? VaapiAlias : D3d11vaAlias;
 01103                        args.Append(GetOpenclDeviceArgs(0, null, srcAlias, OpenclAlias));
 01104                        if (!isHwDecoder)
 1105                        {
 01106                            filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1107                        }
 1108                    }
 1109                }
 1110
 01111                args.Append(filterDevArgs);
 1112            }
 01113            else if (optHwaccelType == HardwareAccelerationType.nvenc)
 1114            {
 01115                if ((!isLinux && !isWindows) || !IsCudaFullSupported())
 1116                {
 01117                    return string.Empty;
 1118                }
 1119
 01120                var isCuvidDecoder = vidDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase);
 01121                var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 01122                var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 01123                var isHwDecoder = isNvdecDecoder || isCuvidDecoder;
 01124                if (!isHwDecoder && !isNvencEncoder)
 1125                {
 01126                    return string.Empty;
 1127                }
 1128
 01129                args.Append(GetCudaDeviceArgs(0, CudaAlias))
 01130                     .Append(GetFilterHwDeviceArgs(CudaAlias));
 1131            }
 01132            else if (optHwaccelType == HardwareAccelerationType.amf)
 1133            {
 01134                if (!isWindows || !_mediaEncoder.SupportsHwaccel("d3d11va"))
 1135                {
 01136                    return string.Empty;
 1137                }
 1138
 01139                var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 01140                var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 01141                if (!isD3d11vaDecoder && !isAmfEncoder)
 1142                {
 01143                    return string.Empty;
 1144                }
 1145
 1146                // no dxva video processor hw filter.
 01147                args.Append(GetD3d11vaDeviceArgs(0, "0x1002", D3d11vaAlias));
 01148                var filterDevArgs = string.Empty;
 01149                if (IsOpenclFullSupported())
 1150                {
 01151                    args.Append(GetOpenclDeviceArgs(0, null, D3d11vaAlias, OpenclAlias));
 01152                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1153                }
 1154
 01155                args.Append(filterDevArgs);
 1156            }
 01157            else if (optHwaccelType == HardwareAccelerationType.videotoolbox)
 1158            {
 01159                if (!isMacOS || !_mediaEncoder.SupportsHwaccel("videotoolbox"))
 1160                {
 01161                    return string.Empty;
 1162                }
 1163
 01164                var isVideotoolboxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 01165                var isVideotoolboxEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 01166                if (!isVideotoolboxDecoder && !isVideotoolboxEncoder)
 1167                {
 01168                    return string.Empty;
 1169                }
 1170
 1171                // videotoolbox hw filter does not require device selection
 01172                args.Append(GetVideoToolboxDeviceArgs(VideotoolboxAlias));
 1173            }
 01174            else if (optHwaccelType == HardwareAccelerationType.rkmpp)
 1175            {
 01176                if (!isLinux || !_mediaEncoder.SupportsHwaccel("rkmpp"))
 1177                {
 01178                    return string.Empty;
 1179                }
 1180
 01181                var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 01182                var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 01183                if (!isRkmppDecoder && !isRkmppEncoder)
 1184                {
 01185                    return string.Empty;
 1186                }
 1187
 01188                args.Append(GetRkmppDeviceArgs(RkmppAlias));
 1189
 01190                var filterDevArgs = string.Empty;
 01191                var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
 1192
 01193                if (doOclTonemap && !isRkmppDecoder)
 1194                {
 01195                    args.Append(GetOpenclDeviceArgs(0, null, RkmppAlias, OpenclAlias));
 01196                    filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
 1197                }
 1198
 01199                args.Append(filterDevArgs);
 1200            }
 1201
 01202            if (!string.IsNullOrEmpty(vidDecoder))
 1203            {
 01204                args.Append(vidDecoder);
 1205            }
 1206
 01207            return args.ToString().Trim();
 1208        }
 1209
 1210        /// <summary>
 1211        /// Gets the input argument.
 1212        /// </summary>
 1213        /// <param name="state">Encoding state.</param>
 1214        /// <param name="options">Encoding options.</param>
 1215        /// <param name="segmentContainer">Segment Container.</param>
 1216        /// <returns>Input arguments.</returns>
 1217        public string GetInputArgument(EncodingJobInfo state, EncodingOptions options, string segmentContainer)
 1218        {
 01219            var arg = new StringBuilder();
 01220            var inputVidHwaccelArgs = GetInputVideoHwaccelArgs(state, options);
 1221
 01222            if (!string.IsNullOrEmpty(inputVidHwaccelArgs))
 1223            {
 01224                arg.Append(inputVidHwaccelArgs);
 1225            }
 1226
 01227            var canvasArgs = GetGraphicalSubCanvasSize(state);
 01228            if (!string.IsNullOrEmpty(canvasArgs))
 1229            {
 01230                arg.Append(canvasArgs);
 1231            }
 1232
 01233            if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
 1234            {
 01235                var concatFilePath = Path.Join(_configurationManager.CommonApplicationPaths.CachePath, "concat", state.M
 01236                if (!File.Exists(concatFilePath))
 1237                {
 01238                    _mediaEncoder.GenerateConcatConfig(state.MediaSource, concatFilePath);
 1239                }
 1240
 01241                arg.Append(" -f concat -safe 0 -i \"")
 01242                    .Append(concatFilePath)
 01243                    .Append("\" ");
 1244            }
 1245            else
 1246            {
 01247                arg.Append(" -i ")
 01248                    .Append(_mediaEncoder.GetInputPathArgument(state));
 1249            }
 1250
 1251            // sub2video for external graphical subtitles
 01252            if (state.SubtitleStream is not null
 01253                && ShouldEncodeSubtitle(state)
 01254                && !state.SubtitleStream.IsTextSubtitleStream
 01255                && state.SubtitleStream.IsExternal)
 1256            {
 01257                var subtitlePath = state.SubtitleStream.Path;
 01258                var subtitleExtension = Path.GetExtension(subtitlePath.AsSpan());
 1259
 1260                // dvdsub/vobsub graphical subtitles use .sub+.idx pairs
 01261                if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase))
 1262                {
 01263                    var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
 01264                    if (File.Exists(idxFile))
 1265                    {
 01266                        subtitlePath = idxFile;
 1267                    }
 1268                }
 1269
 1270                // Also seek the external subtitles stream.
 01271                var seekSubParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01272                if (!string.IsNullOrEmpty(seekSubParam))
 1273                {
 01274                    arg.Append(' ').Append(seekSubParam);
 1275                }
 1276
 01277                if (!string.IsNullOrEmpty(canvasArgs))
 1278                {
 01279                    arg.Append(canvasArgs);
 1280                }
 1281
 01282                arg.Append(" -i file:\"").Append(subtitlePath).Append('\"');
 1283            }
 1284
 01285            if (state.AudioStream is not null && state.AudioStream.IsExternal)
 1286            {
 1287                // Also seek the external audio stream.
 01288                var seekAudioParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
 01289                if (!string.IsNullOrEmpty(seekAudioParam))
 1290                {
 01291                    arg.Append(' ').Append(seekAudioParam);
 1292                }
 1293
 01294                arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
 1295            }
 1296
 1297            // Disable auto inserted SW scaler for HW decoders in case of changed resolution.
 01298            var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
 01299            if (!isSwDecoder)
 1300            {
 01301                arg.Append(" -noautoscale");
 1302            }
 1303
 01304            return arg.ToString();
 1305        }
 1306
 1307        /// <summary>
 1308        /// Determines whether the specified stream is H264.
 1309        /// </summary>
 1310        /// <param name="stream">The stream.</param>
 1311        /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
 1312        public static bool IsH264(MediaStream stream)
 1313        {
 01314            var codec = stream.Codec ?? string.Empty;
 1315
 01316            return codec.Contains("264", StringComparison.OrdinalIgnoreCase)
 01317                    || codec.Contains("avc", StringComparison.OrdinalIgnoreCase);
 1318        }
 1319
 1320        public static bool IsH265(MediaStream stream)
 1321        {
 01322            var codec = stream.Codec ?? string.Empty;
 1323
 01324            return codec.Contains("265", StringComparison.OrdinalIgnoreCase)
 01325                || codec.Contains("hevc", StringComparison.OrdinalIgnoreCase);
 1326        }
 1327
 1328        public static bool IsAv1(MediaStream stream)
 1329        {
 01330            var codec = stream.Codec ?? string.Empty;
 1331
 01332            return codec.Contains("av1", StringComparison.OrdinalIgnoreCase);
 1333        }
 1334
 1335        public static bool IsAAC(MediaStream stream)
 1336        {
 01337            var codec = stream.Codec ?? string.Empty;
 1338
 01339            return codec.Contains("aac", StringComparison.OrdinalIgnoreCase);
 1340        }
 1341
 1342        public static bool IsDoviWithHdr10Bl(MediaStream stream)
 1343        {
 01344            var rangeType = stream?.VideoRangeType;
 1345
 01346            return rangeType is VideoRangeType.DOVIWithHDR10
 01347                or VideoRangeType.DOVIWithEL
 01348                or VideoRangeType.DOVIWithHDR10Plus
 01349                or VideoRangeType.DOVIWithELHDR10Plus
 01350                or VideoRangeType.DOVIInvalid;
 1351        }
 1352
 1353        public static bool IsDovi(MediaStream stream)
 1354        {
 01355            var rangeType = stream?.VideoRangeType;
 1356
 01357            return IsDoviWithHdr10Bl(stream)
 01358                   || (rangeType is VideoRangeType.DOVI
 01359                       or VideoRangeType.DOVIWithHLG
 01360                       or VideoRangeType.DOVIWithSDR);
 1361        }
 1362
 1363        public static bool IsHdr10Plus(MediaStream stream)
 1364        {
 01365            var rangeType = stream?.VideoRangeType;
 1366
 01367            return rangeType is VideoRangeType.HDR10Plus
 01368                       or VideoRangeType.DOVIWithHDR10Plus
 01369                       or VideoRangeType.DOVIWithELHDR10Plus;
 1370        }
 1371
 1372        /// <summary>
 1373        /// Check if dynamic HDR metadata should be removed during stream copy.
 1374        /// Please note this check assumes the range check has already been done
 1375        /// and trivial fallbacks like HDR10+ to HDR10, DOVIWithHDR10 to HDR10 is already checked.
 1376        /// </summary>
 1377        private static DynamicHdrMetadataRemovalPlan ShouldRemoveDynamicHdrMetadata(EncodingJobInfo state)
 1378        {
 01379            var videoStream = state.VideoStream;
 01380            if (videoStream.VideoRange is not VideoRange.HDR)
 1381            {
 01382                return DynamicHdrMetadataRemovalPlan.None;
 1383            }
 1384
 01385            var requestedRangeTypes = state.GetRequestedRangeTypes(state.VideoStream.Codec);
 01386            if (requestedRangeTypes.Length == 0)
 1387            {
 01388                return DynamicHdrMetadataRemovalPlan.None;
 1389            }
 1390
 01391            var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ordinal
 01392            var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIg
 01393            var requestHasDOVIwithEL = requestedRangeTypes.Contains(VideoRangeType.DOVIWithEL.ToString(), StringComparis
 01394            var requestHasDOVIwithELHDR10plus = requestedRangeTypes.Contains(VideoRangeType.DOVIWithELHDR10Plus.ToString
 1395
 01396            var shouldRemoveHdr10Plus = false;
 1397            // Case 1: Client supports HDR10, does not support DOVI with EL but EL presets
 01398            var shouldRemoveDovi = (!requestHasDOVIwithEL && requestHasHDR10) && videoStream.VideoRangeType == VideoRang
 1399
 1400            // Case 2: Client supports DOVI, does not support broken DOVI config
 1401            // Client does not report DOVI support should be allowed to copy bad data for remuxing as HDR10 players woul
 01402            shouldRemoveDovi = shouldRemoveDovi || (requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVII
 1403
 1404            // Special case: we have a video with both EL and HDR10+
 1405            // If the client supports EL but not in the case of coexistence with HDR10+, remove HDR10+ for compatibility
 1406            // Otherwise, remove DOVI if the client is not a DOVI player
 01407            if (videoStream.VideoRangeType == VideoRangeType.DOVIWithELHDR10Plus)
 1408            {
 01409                shouldRemoveHdr10Plus = requestHasDOVIwithEL && !requestHasDOVIwithELHDR10plus;
 01410                shouldRemoveDovi = shouldRemoveDovi || !shouldRemoveHdr10Plus;
 1411            }
 1412
 01413            if (shouldRemoveDovi)
 1414            {
 01415                return DynamicHdrMetadataRemovalPlan.RemoveDovi;
 1416            }
 1417
 1418            // If the client is a Dolby Vision Player, remove the HDR10+ metadata to avoid playback issues
 01419            shouldRemoveHdr10Plus = shouldRemoveHdr10Plus || (requestHasDOVI && videoStream.VideoRangeType == VideoRange
 01420            return shouldRemoveHdr10Plus ? DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus : DynamicHdrMetadataRemovalPlan
 1421        }
 1422
 1423        private bool CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan plan, MediaStream videoStream)
 1424        {
 01425            return plan switch
 01426            {
 01427                DynamicHdrMetadataRemovalPlan.RemoveDovi => _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFil
 01428                                                            || (IsH265(videoStream) && _mediaEncoder.SupportsBitStreamFi
 01429                                                            || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStreamFil
 01430                DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus => (IsH265(videoStream) && _mediaEncoder.SupportsBitStream
 01431                                                                 || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStre
 01432                _ => true,
 01433            };
 1434        }
 1435
 1436        public bool IsDoviRemoved(EncodingJobInfo state)
 1437        {
 01438            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01439                                              && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Remove
 1440        }
 1441
 1442        public bool IsHdr10PlusRemoved(EncodingJobInfo state)
 1443        {
 01444            return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalP
 01445                                                  && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.Re
 1446        }
 1447
 1448        public string GetBitStreamArgs(EncodingJobInfo state, MediaStreamType streamType)
 1449        {
 01450            if (state is null)
 1451            {
 01452                return null;
 1453            }
 1454
 01455            var stream = streamType switch
 01456            {
 01457                MediaStreamType.Audio => state.AudioStream,
 01458                MediaStreamType.Video => state.VideoStream,
 01459                _ => state.VideoStream
 01460            };
 1461            // TODO This is auto inserted into the mpegts mux so it might not be needed.
 1462            // https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
 01463            if (IsH264(stream))
 1464            {
 01465                return "-bsf:v h264_mp4toannexb";
 1466            }
 1467
 01468            if (IsAAC(stream))
 1469            {
 1470                // Convert adts header(mpegts) to asc header(mp4).
 01471                return "-bsf:a aac_adtstoasc";
 1472            }
 1473
 01474            if (IsH265(stream))
 1475            {
 01476                var filter = "-bsf:v hevc_mp4toannexb";
 1477
 1478                // The following checks are not complete because the copy would be rejected
 1479                // if the encoder cannot remove required metadata.
 1480                // And if bsf is used, we must already be using copy codec.
 01481                switch (ShouldRemoveDynamicHdrMetadata(state))
 1482                {
 1483                    default:
 1484                    case DynamicHdrMetadataRemovalPlan.None:
 1485                        break;
 1486                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01487                        filter += _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.HevcMetadata
 01488                            ? ",hevc_metadata=remove_dovi=1"
 01489                            : ",dovi_rpu=strip=1";
 01490                        break;
 1491                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01492                        filter += ",hevc_metadata=remove_hdr10plus=1";
 1493                        break;
 1494                }
 1495
 01496                return filter;
 1497            }
 1498
 01499            if (IsAv1(stream))
 1500            {
 01501                switch (ShouldRemoveDynamicHdrMetadata(state))
 1502                {
 1503                    default:
 1504                    case DynamicHdrMetadataRemovalPlan.None:
 01505                        return null;
 1506                    case DynamicHdrMetadataRemovalPlan.RemoveDovi:
 01507                        return _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.Av1MetadataRemo
 01508                            ? "-bsf:v av1_metadata=remove_dovi=1"
 01509                            : "-bsf:v dovi_rpu=strip=1";
 1510                    case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
 01511                        return "-bsf:v av1_metadata=remove_hdr10plus=1";
 1512                }
 1513            }
 1514
 01515            return null;
 1516        }
 1517
 1518        public string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceConta
 1519        {
 01520            var bitStreamArgs = string.Empty;
 01521            var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 1522
 1523            // Apply aac_adtstoasc bitstream filter when media source is in mpegts.
 01524            if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)
 01525                && (string.Equals(mediaSourceContainer, "ts", StringComparison.OrdinalIgnoreCase)
 01526                    || string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
 01527                    || string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)))
 1528            {
 01529                bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Audio);
 01530                bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs;
 1531            }
 1532
 01533            return bitStreamArgs;
 1534        }
 1535
 1536        public static string GetSegmentFileExtension(string segmentContainer)
 1537        {
 01538            if (!string.IsNullOrWhiteSpace(segmentContainer))
 1539            {
 01540                return "." + segmentContainer;
 1541            }
 1542
 01543            return ".ts";
 1544        }
 1545
 1546        private string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
 1547        {
 01548            if (state.OutputVideoBitrate is null)
 1549            {
 01550                return string.Empty;
 1551            }
 1552
 01553            int bitrate = state.OutputVideoBitrate.Value;
 1554
 1555            // Bit rate under 1000k is not allowed in h264_qsv
 01556            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 1557            {
 01558                bitrate = Math.Max(bitrate, 1000);
 1559            }
 1560
 1561            // Currently use the same buffer size for all encoders
 01562            int bufsize = bitrate * 2;
 1563
 01564            if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1565            {
 01566                return FormattableString.Invariant($" -b:v {bitrate} -bufsize {bufsize}");
 1567            }
 1568
 01569            if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)
 01570                || string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
 1571            {
 01572                return FormattableString.Invariant($" -maxrate {bitrate} -bufsize {bufsize}");
 1573            }
 1574
 01575            if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01576                || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01577                || string.Equals(videoCodec, "av1_qsv", StringComparison.OrdinalIgnoreCase))
 1578            {
 1579                // TODO: probe QSV encoders' capabilities and enable more tuning options
 1580                // See also https://github.com/intel/media-delivery/blob/master/doc/quality.rst
 1581
 1582                // Enable MacroBlock level bitrate control for better subjective visual quality
 01583                var mbbrcOpt = string.Empty;
 01584                if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01585                    || string.Equals(videoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 1586                {
 01587                    mbbrcOpt = " -mbbrc 1";
 1588                }
 1589
 1590                // Set (maxrate == bitrate + 1) to trigger VBR for better bitrate allocation
 1591                // Set (rc_init_occupancy == 2 * bitrate) and (bufsize == 4 * bitrate) to deal with drastic scene change
 01592                return FormattableString.Invariant($"{mbbrcOpt} -b:v {bitrate} -maxrate {bitrate + 1} -rc_init_occupancy
 1593            }
 1594
 01595            if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01596                || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01597                || string.Equals(videoCodec, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1598            {
 1599                // Override the too high default qmin 18 in transcoding preset
 01600                return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsiz
 1601            }
 1602
 01603            if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01604                || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01605                || string.Equals(videoCodec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1606            {
 1607                // VBR in i965 driver may result in pixelated output.
 01608                if (_mediaEncoder.IsVaapiDeviceInteli965)
 1609                {
 01610                    return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsi
 1611                }
 1612
 01613                return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"
 1614            }
 1615
 01616            if (string.Equals(videoCodec, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 01617                || string.Equals(videoCodec, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase))
 1618            {
 1619                // The `maxrate` and `bufsize` options can potentially lead to performance regression
 1620                // and even encoder hangs, especially when the value is very high.
 01621                return FormattableString.Invariant($" -b:v {bitrate} -qmin -1 -qmax -1");
 1622            }
 1623
 01624            return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
 1625        }
 1626
 1627        private string GetEncoderParam(EncoderPreset? preset, EncoderPreset defaultPreset, EncodingOptions encodingOptio
 1628        {
 01629            var param = string.Empty;
 01630            var encoderPreset = preset ?? defaultPreset;
 01631            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
 1632            {
 01633                var presetString = encoderPreset switch
 01634                {
 01635                    EncoderPreset.auto => EncoderPreset.veryfast.ToString().ToLowerInvariant(),
 01636                    _ => encoderPreset.ToString().ToLowerInvariant()
 01637                };
 1638
 01639                param += " -preset " + presetString;
 1640
 01641                int encodeCrf = encodingOptions.H264Crf;
 01642                if (isLibX265)
 1643                {
 01644                    encodeCrf = encodingOptions.H265Crf;
 1645                }
 1646
 01647                if (encodeCrf >= 0 && encodeCrf <= 51)
 1648                {
 01649                    param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture);
 1650                }
 1651                else
 1652                {
 01653                    string defaultCrf = "23";
 01654                    if (isLibX265)
 1655                    {
 01656                        defaultCrf = "28";
 1657                    }
 1658
 01659                    param += " -crf " + defaultCrf;
 1660                }
 1661            }
 01662            else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1663            {
 1664                // Default to use the recommended preset 10.
 1665                // Omit presets < 5, which are too slow for on the fly encoding.
 1666                // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md
 01667                param += encoderPreset switch
 01668                {
 01669                    EncoderPreset.veryslow => " -preset 5",
 01670                    EncoderPreset.slower => " -preset 6",
 01671                    EncoderPreset.slow => " -preset 7",
 01672                    EncoderPreset.medium => " -preset 8",
 01673                    EncoderPreset.fast => " -preset 9",
 01674                    EncoderPreset.faster => " -preset 10",
 01675                    EncoderPreset.veryfast => " -preset 11",
 01676                    EncoderPreset.superfast => " -preset 12",
 01677                    EncoderPreset.ultrafast => " -preset 13",
 01678                    _ => " -preset 10"
 01679                };
 1680            }
 01681            else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01682                     || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01683                     || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1684            {
 1685                // -compression_level is not reliable on AMD.
 01686                if (_mediaEncoder.IsVaapiDeviceInteliHD)
 1687                {
 01688                    param += encoderPreset switch
 01689                    {
 01690                        EncoderPreset.veryslow => " -compression_level 1",
 01691                        EncoderPreset.slower => " -compression_level 2",
 01692                        EncoderPreset.slow => " -compression_level 3",
 01693                        EncoderPreset.medium => " -compression_level 4",
 01694                        EncoderPreset.fast => " -compression_level 5",
 01695                        EncoderPreset.faster => " -compression_level 6",
 01696                        EncoderPreset.veryfast => " -compression_level 7",
 01697                        EncoderPreset.superfast => " -compression_level 7",
 01698                        EncoderPreset.ultrafast => " -compression_level 7",
 01699                        _ => string.Empty
 01700                    };
 1701                }
 1702            }
 01703            else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv)
 01704                     || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv)
 01705                     || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv)
 1706            {
 01707                EncoderPreset[] valid_presets = [EncoderPreset.veryslow, EncoderPreset.slower, EncoderPreset.slow, Encod
 1708
 01709                param += " -preset " + (valid_presets.Contains(encoderPreset) ? encoderPreset : EncoderPreset.veryfast).
 1710            }
 01711            else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
 01712                        || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_n
 01713                        || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase) // av1 (av1_nven
 01714            )
 1715            {
 01716                param += encoderPreset switch
 01717                {
 01718                        EncoderPreset.veryslow => " -preset p7",
 01719                        EncoderPreset.slower => " -preset p6",
 01720                        EncoderPreset.slow => " -preset p5",
 01721                        EncoderPreset.medium => " -preset p4",
 01722                        EncoderPreset.fast => " -preset p3",
 01723                        EncoderPreset.faster => " -preset p2",
 01724                        _ => " -preset p1"
 01725                };
 1726            }
 01727            else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
 01728                        || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf
 01729                        || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase) // av1 (av1_amf)
 01730            )
 1731            {
 01732                param += encoderPreset switch
 01733                {
 01734                        EncoderPreset.veryslow => " -quality quality",
 01735                        EncoderPreset.slower => " -quality quality",
 01736                        EncoderPreset.slow => " -quality quality",
 01737                        EncoderPreset.medium => " -quality balanced",
 01738                        _ => " -quality speed"
 01739                };
 1740
 01741                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 01742                    || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 1743                {
 01744                    param += " -header_insertion_mode gop";
 1745                }
 1746
 01747                if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
 1748                {
 01749                    param += " -gops_per_idr 1";
 1750                }
 1751            }
 01752            else if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) // h264 (h264_
 01753                        || string.Equals(videoEncoder, "hevc_videotoolbox", StringComparison.OrdinalIgnoreCase) // hevc 
 01754            )
 1755            {
 01756                param += encoderPreset switch
 01757                {
 01758                        EncoderPreset.veryslow => " -prio_speed 0",
 01759                        EncoderPreset.slower => " -prio_speed 0",
 01760                        EncoderPreset.slow => " -prio_speed 0",
 01761                        EncoderPreset.medium => " -prio_speed 0",
 01762                        _ => " -prio_speed 1"
 01763                };
 1764            }
 1765
 01766            return param;
 1767        }
 1768
 1769        public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
 1770        {
 01771            if (double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
 1772            {
 01773                if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
 1774                {
 1775                    // Transcode to level 5.3 (15) and lower for maximum compatibility.
 1776                    // https://en.wikipedia.org/wiki/AV1#Levels
 01777                    if (requestLevel < 0 || requestLevel >= 15)
 1778                    {
 01779                        return "15";
 1780                    }
 1781                }
 01782                else if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 01783                         || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
 1784                {
 1785                    // Transcode to level 5.0 and lower for maximum compatibility.
 1786                    // Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it.
 1787                    // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
 1788                    // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880.
 01789                    if (requestLevel < 0 || requestLevel >= 150)
 1790                    {
 01791                        return "150";
 1792                    }
 1793                }
 01794                else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
 1795                {
 1796                    // Transcode to level 5.1 and lower for maximum compatibility.
 1797                    // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
 1798                    // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
 01799                    if (requestLevel < 0 || requestLevel >= 51)
 1800                    {
 01801                        return "51";
 1802                    }
 1803                }
 1804            }
 1805
 01806            return level;
 1807        }
 1808
 1809        /// <summary>
 1810        /// Gets the text subtitle param.
 1811        /// </summary>
 1812        /// <param name="state">The state.</param>
 1813        /// <param name="enableAlpha">Enable alpha processing.</param>
 1814        /// <param name="enableSub2video">Enable sub2video mode.</param>
 1815        /// <returns>System.String.</returns>
 1816        public string GetTextSubtitlesFilter(EncodingJobInfo state, bool enableAlpha, bool enableSub2video)
 1817        {
 01818            var seconds = Math.Round(TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds);
 1819
 1820            // hls always copies timestamps
 01821            var setPtsParam = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive
 01822                ? string.Empty
 01823                : string.Format(CultureInfo.InvariantCulture, ",setpts=PTS -{0}/TB", seconds);
 1824
 01825            var alphaParam = enableAlpha ? ":alpha=1" : string.Empty;
 01826            var sub2videoParam = enableSub2video ? ":sub2video=1" : string.Empty;
 1827
 01828            var fontPath = _pathManager.GetAttachmentFolderPath(state.MediaSource.Id);
 01829            var fontParam = string.Format(
 01830                CultureInfo.InvariantCulture,
 01831                ":fontsdir='{0}'",
 01832                _mediaEncoder.EscapeSubtitleFilterPath(fontPath));
 1833
 01834            if (state.SubtitleStream.IsExternal)
 1835            {
 01836                var charsetParam = string.Empty;
 1837
 01838                if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
 1839                {
 01840                    var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
 01841                            state.SubtitleStream,
 01842                            state.SubtitleStream.Language,
 01843                            state.MediaSource,
 01844                            CancellationToken.None).GetAwaiter().GetResult();
 1845
 01846                    if (!string.IsNullOrEmpty(charenc))
 1847                    {
 01848                        charsetParam = ":charenc=" + charenc;
 1849                    }
 1850                }
 1851
 01852                return string.Format(
 01853                    CultureInfo.InvariantCulture,
 01854                    "subtitles=f='{0}'{1}{2}{3}{4}{5}",
 01855                    _mediaEncoder.EscapeSubtitleFilterPath(state.SubtitleStream.Path),
 01856                    charsetParam,
 01857                    alphaParam,
 01858                    sub2videoParam,
 01859                    fontParam,
 01860                    setPtsParam);
 1861            }
 1862
 01863            var subtitlePath = _subtitleEncoder.GetSubtitleFilePath(
 01864                    state.SubtitleStream,
 01865                    state.MediaSource,
 01866                    CancellationToken.None).GetAwaiter().GetResult();
 1867
 01868            return string.Format(
 01869                CultureInfo.InvariantCulture,
 01870                "subtitles=f='{0}'{1}{2}{3}{4}",
 01871                _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
 01872                alphaParam,
 01873                sub2videoParam,
 01874                fontParam,
 01875                setPtsParam);
 1876        }
 1877
 1878        public double? GetFramerateParam(EncodingJobInfo state)
 1879        {
 01880            var request = state.BaseRequest;
 1881
 01882            if (request.Framerate.HasValue)
 1883            {
 01884                return request.Framerate.Value;
 1885            }
 1886
 01887            var maxrate = request.MaxFramerate;
 1888
 01889            if (maxrate.HasValue && state.VideoStream is not null)
 1890            {
 01891                var contentRate = state.VideoStream.ReferenceFrameRate;
 1892
 01893                if (contentRate.HasValue && contentRate.Value > maxrate.Value)
 1894                {
 01895                    return maxrate;
 1896                }
 1897            }
 1898
 01899            return null;
 1900        }
 1901
 1902        public string GetHlsVideoKeyFrameArguments(
 1903            EncodingJobInfo state,
 1904            string codec,
 1905            int segmentLength,
 1906            bool isEventPlaylist,
 1907            int? startNumber)
 1908        {
 01909            var args = string.Empty;
 01910            var gopArg = string.Empty;
 1911
 01912            var keyFrameArg = string.Format(
 01913                CultureInfo.InvariantCulture,
 01914                " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"",
 01915                segmentLength);
 1916
 01917            var framerate = state.VideoStream?.RealFrameRate;
 01918            if (framerate.HasValue)
 1919            {
 1920                // This is to make sure keyframe interval is limited to our segment,
 1921                // as forcing keyframes is not enough.
 1922                // Example: we encoded half of desired length, then codec detected
 1923                // scene cut and inserted a keyframe; next forced keyframe would
 1924                // be created outside of segment, which breaks seeking.
 01925                gopArg = string.Format(
 01926                    CultureInfo.InvariantCulture,
 01927                    " -g:v:0 {0} -keyint_min:v:0 {0}",
 01928                    Math.Ceiling(segmentLength * framerate.Value));
 1929            }
 1930
 1931            // Unable to force key frames using these encoders, set key frames by GOP.
 01932            if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 01933                || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 01934                || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)
 01935                || string.Equals(codec, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 01936                || string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
 01937                || string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 01938                || string.Equals(codec, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase)
 01939                || string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 01940                || string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 01941                || string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase)
 01942                || string.Equals(codec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 1943            {
 01944                args += gopArg;
 1945            }
 01946            else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase)
 01947                     || string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)
 01948                     || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 01949                     || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01950                     || string.Equals(codec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 1951            {
 01952                args += keyFrameArg;
 1953
 1954                // prevent the libx264 from post processing to break the set keyframe.
 01955                if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase))
 1956                {
 01957                    args += " -sc_threshold:v:0 0";
 1958                }
 1959            }
 1960            else
 1961            {
 01962                args += keyFrameArg + gopArg;
 1963            }
 1964
 1965            // global_header produced by AMD HEVC VA-API encoder causes non-playable fMP4 on iOS
 01966            if (string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 01967                && _mediaEncoder.IsVaapiDeviceAmd)
 1968            {
 01969                args += " -flags:v -global_header";
 1970            }
 1971
 01972            return args;
 1973        }
 1974
 1975        /// <summary>
 1976        /// Gets the video bitrate to specify on the command line.
 1977        /// </summary>
 1978        /// <param name="state">Encoding state.</param>
 1979        /// <param name="videoEncoder">Video encoder to use.</param>
 1980        /// <param name="encodingOptions">Encoding options.</param>
 1981        /// <param name="defaultPreset">Default present to use for encoding.</param>
 1982        /// <returns>Video bitrate.</returns>
 1983        public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, 
 1984        {
 01985            var param = string.Empty;
 1986
 1987            // Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding.
 1988            // https://01.org/group/43/downloads/firmware
 1989            // https://wiki.archlinux.org/title/intel_graphics#Enable_GuC_/_HuC_firmware_loading
 1990            // Intel Low Power Encoding can save unnecessary CPU-GPU synchronization,
 1991            // which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
 01992            var intelLowPowerHwEncoding = false;
 1993
 1994            // Workaround for linux 5.18 to 6.1.3 i915 hang at cost of performance.
 1995            // https://github.com/intel/media-driver/issues/1456
 01996            var enableWaFori915Hang = false;
 1997
 01998            var hardwareAccelerationType = encodingOptions.HardwareAccelerationType;
 1999
 02000            if (hardwareAccelerationType == HardwareAccelerationType.vaapi)
 2001            {
 02002                var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
 2003
 02004                if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
 2005                {
 02006                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder && isIntelVaapiDriver;
 2007                }
 02008                else if (string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
 2009                {
 02010                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder && isIntelVaapiDriver;
 2011                }
 2012            }
 02013            else if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 2014            {
 02015                if (OperatingSystem.IsLinux())
 2016                {
 02017                    var ver = Environment.OSVersion.Version;
 02018                    var isFixedKernel60 = ver.Major == 6 && ver.Minor == 0 && ver >= _minFixedKernel60i915Hang;
 02019                    var isUnaffectedKernel = ver < _minKerneli915Hang || ver > _maxKerneli915Hang;
 2020
 02021                    if (!(isUnaffectedKernel || isFixedKernel60))
 2022                    {
 02023                        var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
 02024                        var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
 02025                                             || vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 02026                        var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
 02027                            && IsVaapiSupported(state)
 02028                            && IsOpenclFullSupported()
 02029                            && !IsIntelVppTonemapAvailable(state, encodingOptions)
 02030                            && IsHwTonemapAvailable(state, encodingOptions);
 2031
 02032                        enableWaFori915Hang = isIntelDecoder && doOclTonemap;
 2033                    }
 2034                }
 2035
 02036                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
 2037                {
 02038                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
 2039                }
 02040                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2041                {
 02042                    intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
 2043                }
 2044                else
 2045                {
 02046                    enableWaFori915Hang = false;
 2047                }
 2048            }
 2049
 02050            if (intelLowPowerHwEncoding)
 2051            {
 02052                param += " -low_power 1";
 2053            }
 2054
 02055            if (enableWaFori915Hang)
 2056            {
 02057                param += " -async_depth 1";
 2058            }
 2059
 02060            var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
 02061            var encodingPreset = encodingOptions.EncoderPreset;
 2062
 02063            param += GetEncoderParam(encodingPreset, defaultPreset, encodingOptions, videoEncoder, isLibX265);
 02064            param += GetVideoBitrateParam(state, videoEncoder);
 2065
 02066            var framerate = GetFramerateParam(state);
 02067            if (framerate.HasValue)
 2068            {
 02069                param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.Inv
 2070            }
 2071
 02072            var targetVideoCodec = state.ActualOutputVideoCodec;
 02073            if (string.Equals(targetVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
 02074                || string.Equals(targetVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
 2075            {
 02076                targetVideoCodec = "hevc";
 2077            }
 2078
 02079            var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
 02080            profile = WhiteSpaceRegex().Replace(profile, string.Empty).ToLowerInvariant();
 2081
 02082            var videoProfiles = Array.Empty<string>();
 02083            if (string.Equals("h264", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2084            {
 02085                videoProfiles = _videoProfilesH264;
 2086            }
 02087            else if (string.Equals("hevc", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2088            {
 02089                videoProfiles = _videoProfilesH265;
 2090            }
 02091            else if (string.Equals("av1", targetVideoCodec, StringComparison.OrdinalIgnoreCase))
 2092            {
 02093                videoProfiles = _videoProfilesAv1;
 2094            }
 2095
 02096            if (!videoProfiles.Contains(profile, StringComparison.OrdinalIgnoreCase))
 2097            {
 02098                profile = string.Empty;
 2099            }
 2100
 2101            // We only transcode to HEVC 8-bit for now, force Main Profile.
 02102            if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
 02103                || profile.Contains("mainstill", StringComparison.OrdinalIgnoreCase))
 2104            {
 02105                profile = "main";
 2106            }
 2107
 2108            // Extended Profile is not supported by any known h264 encoders, force Main Profile.
 02109            if (profile.Contains("extended", StringComparison.OrdinalIgnoreCase))
 2110            {
 02111                profile = "main";
 2112            }
 2113
 2114            // Only libx264 support encoding H264 High 10 Profile, otherwise force High Profile.
 02115            if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02116                && profile.Contains("high10", StringComparison.OrdinalIgnoreCase))
 2117            {
 02118                profile = "high";
 2119            }
 2120
 2121            // We only need Main profile of AV1 encoders.
 02122            if (videoEncoder.Contains("av1", StringComparison.OrdinalIgnoreCase)
 02123                && (profile.Contains("high", StringComparison.OrdinalIgnoreCase)
 02124                    || profile.Contains("professional", StringComparison.OrdinalIgnoreCase)))
 2125            {
 02126                profile = "main";
 2127            }
 2128
 2129            // h264_vaapi does not support Baseline profile, force Constrained Baseline in this case,
 2130            // which is compatible (and ugly).
 02131            if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02132                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2133            {
 02134                profile = "constrained_baseline";
 2135            }
 2136
 2137            // libx264, h264_{qsv,nvenc,rkmpp} does not support Constrained Baseline profile, force Baseline in this cas
 02138            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02139                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02140                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02141                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02142                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2143            {
 02144                profile = "baseline";
 2145            }
 2146
 2147            // libx264, h264_{qsv,nvenc,vaapi,rkmpp} does not support Constrained High profile, force High in this case.
 02148            if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
 02149                 || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02150                 || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02151                 || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02152                 || string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
 02153                && profile.Contains("high", StringComparison.OrdinalIgnoreCase))
 2154            {
 02155                profile = "high";
 2156            }
 2157
 02158            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02159                && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
 2160            {
 02161                profile = "constrained_baseline";
 2162            }
 2163
 02164            if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02165                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2166            {
 02167                profile = "constrained_high";
 2168            }
 2169
 02170            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02171                && profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase))
 2172            {
 02173                profile = "constrained_baseline";
 2174            }
 2175
 02176            if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase)
 02177                && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase))
 2178            {
 02179                profile = "constrained_high";
 2180            }
 2181
 02182            if (!string.IsNullOrEmpty(profile))
 2183            {
 2184                // Currently there's no profile option in av1_nvenc encoder
 02185                if (!(string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
 02186                      || string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)))
 2187                {
 02188                    param += " -profile:v:0 " + profile;
 2189                }
 2190            }
 2191
 02192            var level = state.GetRequestedLevel(targetVideoCodec);
 2193
 02194            if (!string.IsNullOrEmpty(level))
 2195            {
 02196                level = NormalizeTranscodingLevel(state, level);
 2197
 2198                // libx264, QSV, AMF can adjust the given level to match the output.
 02199                if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
 02200                    || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2201                {
 02202                    param += " -level " + level;
 2203                }
 02204                else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
 2205                {
 2206                    // hevc_qsv use -level 51 instead of -level 153.
 02207                    if (double.TryParse(level, CultureInfo.InvariantCulture, out double hevcLevel))
 2208                    {
 02209                        param += " -level " + (hevcLevel / 3);
 2210                    }
 2211                }
 02212                else if (string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)
 02213                         || string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
 2214                {
 2215                    // libsvtav1 and av1_qsv use -level 60 instead of -level 16
 2216                    // https://aomedia.org/av1/specification/annex-a/
 02217                    if (int.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out int av1Level))
 2218                    {
 02219                        var x = 2 + (av1Level >> 2);
 02220                        var y = av1Level & 3;
 02221                        var res = (x * 10) + y;
 02222                        param += " -level " + res;
 2223                    }
 2224                }
 02225                else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
 02226                         || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
 02227                         || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
 2228                {
 02229                    param += " -level " + level;
 2230                }
 02231                else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
 02232                         || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
 02233                         || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase))
 2234                {
 2235                    // level option may cause NVENC to fail.
 2236                    // NVENC cannot adjust the given level, just throw an error.
 2237                }
 02238                else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
 02239                         || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
 02240                         || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
 2241                {
 2242                    // level option may cause corrupted frames on AMD VAAPI.
 02243                    if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
 2244                    {
 02245                        param += " -level " + level;
 2246                    }
 2247                }
 02248                else if (string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
 02249                         || string.Equals(videoEncoder, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase))
 2250                {
 02251                    param += " -level " + level;
 2252                }
 02253                else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2254                {
 02255                    param += " -level " + level;
 2256                }
 2257            }
 2258
 02259            if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
 2260            {
 02261                param += " -x264opts:0 subme=0:me_range=16:rc_lookahead=10:me=hex:open_gop=0";
 2262            }
 2263
 02264            if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
 2265            {
 2266                // libx265 only accept level option in -x265-params.
 2267                // level option may cause libx265 to fail.
 2268                // libx265 cannot adjust the given level, just throw an error.
 02269                param += " -x265-params:0 no-scenecut=1:no-open-gop=1:no-info=1";
 2270
 02271                if (encodingOptions.EncoderPreset < EncoderPreset.ultrafast)
 2272                {
 2273                    // The following params are slower than the ultrafast preset, don't use when ultrafast is selected.
 02274                    param += ":subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=16:rskip=2:r
 2275                }
 2276            }
 2277
 02278            if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)
 02279                && _mediaEncoder.EncoderVersion >= _minFFmpegSvtAv1Params)
 2280            {
 02281                param += " -svtav1-params:0 rc=1:tune=0:film-grain=0:enable-overlays=1:enable-tf=0";
 2282            }
 2283
 2284            /* Access unit too large: 8192 < 20880 error */
 02285            if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) ||
 02286                 string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) &&
 02287                 _mediaEncoder.EncoderVersion >= _minFFmpegVaapiH26xEncA53CcSei)
 2288            {
 02289                param += " -sei -a53_cc";
 2290            }
 2291
 02292            return param;
 2293        }
 2294
 2295        public bool CanStreamCopyVideo(EncodingJobInfo state, MediaStream videoStream)
 2296        {
 02297            var request = state.BaseRequest;
 2298
 02299            if (!request.AllowVideoStreamCopy)
 2300            {
 02301                return false;
 2302            }
 2303
 02304            if (videoStream.IsInterlaced
 02305                && state.DeInterlace(videoStream.Codec, false))
 2306            {
 02307                return false;
 2308            }
 2309
 02310            if (videoStream.IsAnamorphic ?? false)
 2311            {
 02312                if (request.RequireNonAnamorphic)
 2313                {
 02314                    return false;
 2315                }
 2316            }
 2317
 2318            // Can't stream copy if we're burning in subtitles
 02319            if (request.SubtitleStreamIndex.HasValue
 02320                && request.SubtitleStreamIndex.Value >= 0
 02321                && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
 2322            {
 02323                return false;
 2324            }
 2325
 02326            if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 02327                && videoStream.IsAVC.HasValue
 02328                && !videoStream.IsAVC.Value
 02329                && request.RequireAvc)
 2330            {
 02331                return false;
 2332            }
 2333
 2334            // Source and target codecs must match
 02335            if (string.IsNullOrEmpty(videoStream.Codec)
 02336                || (state.SupportedVideoCodecs.Length != 0
 02337                    && !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparison.OrdinalIgnoreCase)))
 2338            {
 02339                return false;
 2340            }
 2341
 02342            var requestedProfiles = state.GetRequestedProfiles(videoStream.Codec);
 2343
 2344            // If client is requesting a specific video profile, it must match the source
 02345            if (requestedProfiles.Length > 0)
 2346            {
 02347                if (string.IsNullOrEmpty(videoStream.Profile))
 2348                {
 2349                    // return false;
 2350                }
 2351
 02352                var requestedProfile = requestedProfiles[0];
 2353                // strip spaces because they may be stripped out on the query string as well
 02354                if (!string.IsNullOrEmpty(videoStream.Profile)
 02355                    && !requestedProfiles.Contains(videoStream.Profile.Replace(" ", string.Empty, StringComparison.Ordin
 2356                {
 02357                    var currentScore = GetVideoProfileScore(videoStream.Codec, videoStream.Profile);
 02358                    var requestedScore = GetVideoProfileScore(videoStream.Codec, requestedProfile);
 2359
 02360                    if (currentScore == -1 || currentScore > requestedScore)
 2361                    {
 02362                        return false;
 2363                    }
 2364                }
 2365            }
 2366
 02367            var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
 02368            if (requestedRangeTypes.Length > 0)
 2369            {
 02370                if (videoStream.VideoRangeType == VideoRangeType.Unknown)
 2371                {
 02372                    return false;
 2373                }
 2374
 2375                // DOVIWithHDR10 should be compatible with HDR10 supporting players. Same goes with HLG and of course SD
 02376                var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.Ord
 02377                var requestHasHLG = requestedRangeTypes.Contains(VideoRangeType.HLG.ToString(), StringComparison.Ordinal
 02378                var requestHasSDR = requestedRangeTypes.Contains(VideoRangeType.SDR.ToString(), StringComparison.Ordinal
 02379                var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.Ordin
 2380
 2381                // If SDR is the only supported range, we should not copy any of the HDR streams.
 2382                // All the following copy check assumes at least one HDR format is supported.
 02383                if (requestedRangeTypes.Length == 1 && requestHasSDR && videoStream.VideoRangeType != VideoRangeType.SDR
 2384                {
 02385                    return false;
 2386                }
 2387
 2388                // If the client does not support DOVI and the video stream is DOVI without fallback, we should not copy
 02389                if (!requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVI)
 2390                {
 02391                    return false;
 2392                }
 2393
 02394                if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreC
 02395                     && !((requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10)
 02396                            || (requestHasHLG && videoStream.VideoRangeType == VideoRangeType.DOVIWithHLG)
 02397                            || (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)
 02398                            || (requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.HDR10Plus)))
 2399                {
 2400                    // If the video stream is in HDR10+ or a static HDR format, don't allow copy if the client does not 
 02401                    if (videoStream.VideoRangeType is VideoRangeType.HDR10Plus or VideoRangeType.HDR10 or VideoRangeType
 2402                    {
 02403                        return false;
 2404                    }
 2405
 2406                    // Check complicated cases where we need to remove dynamic metadata
 2407                    // Conservatively refuse to copy if the encoder can't remove dynamic metadata,
 2408                    // but a removal is required for compatability reasons.
 02409                    var dynamicHdrMetadataRemovalPlan = ShouldRemoveDynamicHdrMetadata(state);
 02410                    if (!CanEncoderRemoveDynamicHdrMetadata(dynamicHdrMetadataRemovalPlan, videoStream))
 2411                    {
 02412                        return false;
 2413                    }
 2414                }
 2415            }
 2416
 2417            // Video width must fall within requested value
 02418            if (request.MaxWidth.HasValue
 02419                && (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
 2420            {
 02421                return false;
 2422            }
 2423
 2424            // Video height must fall within requested value
 02425            if (request.MaxHeight.HasValue
 02426                && (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value))
 2427            {
 02428                return false;
 2429            }
 2430
 2431            // Video framerate must fall within requested value
 02432            var requestedFramerate = request.MaxFramerate ?? request.Framerate;
 02433            if (requestedFramerate.HasValue)
 2434            {
 02435                var videoFrameRate = videoStream.ReferenceFrameRate;
 2436
 2437                // Add a little tolerance to the framerate check because some videos might record a framerate
 2438                // that is slightly greater than the intended framerate, but the device can still play it correctly.
 2439                // 0.05 fps tolerance should be safe enough.
 02440                if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value + 0.05f)
 2441                {
 02442                    return false;
 2443                }
 2444            }
 2445
 2446            // Video bitrate must fall within requested value
 02447            if (request.VideoBitRate.HasValue
 02448                && (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value))
 2449            {
 2450                // For LiveTV that has no bitrate, let's try copy if other conditions are met
 02451                if (string.IsNullOrWhiteSpace(request.LiveStreamId) || videoStream.BitRate.HasValue)
 2452                {
 02453                    return false;
 2454                }
 2455            }
 2456
 02457            var maxBitDepth = state.GetRequestedVideoBitDepth(videoStream.Codec);
 02458            if (maxBitDepth.HasValue)
 2459            {
 02460                if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > maxBitDepth.Value)
 2461                {
 02462                    return false;
 2463                }
 2464            }
 2465
 02466            var maxRefFrames = state.GetRequestedMaxRefFrames(videoStream.Codec);
 02467            if (maxRefFrames.HasValue
 02468                && videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > maxRefFrames.Value)
 2469            {
 02470                return false;
 2471            }
 2472
 2473            // If a specific level was requested, the source must match or be less than
 02474            var level = state.GetRequestedLevel(videoStream.Codec);
 02475            if (double.TryParse(level, CultureInfo.InvariantCulture, out var requestLevel))
 2476            {
 02477                if (!videoStream.Level.HasValue)
 2478                {
 2479                    // return false;
 2480                }
 2481
 02482                if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
 2483                {
 02484                    return false;
 2485                }
 2486            }
 2487
 02488            if (string.Equals(state.InputContainer, "avi", StringComparison.OrdinalIgnoreCase)
 02489                && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)
 02490                && !(videoStream.IsAVC ?? false))
 2491            {
 2492                // see Coach S01E01 - Kelly and the Professor(0).avi
 02493                return false;
 2494            }
 2495
 02496            return true;
 2497        }
 2498
 2499        public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable<string> supportedAudi
 2500        {
 02501            var request = state.BaseRequest;
 2502
 02503            if (!request.AllowAudioStreamCopy)
 2504            {
 02505                return false;
 2506            }
 2507
 02508            var maxBitDepth = state.GetRequestedAudioBitDepth(audioStream.Codec);
 02509            if (maxBitDepth.HasValue
 02510                && audioStream.BitDepth.HasValue
 02511                && audioStream.BitDepth.Value > maxBitDepth.Value)
 2512            {
 02513                return false;
 2514            }
 2515
 2516            // Source and target codecs must match
 02517            if (string.IsNullOrEmpty(audioStream.Codec)
 02518                || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparison.OrdinalIgnoreCase))
 2519            {
 02520                return false;
 2521            }
 2522
 2523            // Channels must fall within requested value
 02524            var channels = state.GetRequestedAudioChannels(audioStream.Codec);
 02525            if (channels.HasValue)
 2526            {
 02527                if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0)
 2528                {
 02529                    return false;
 2530                }
 2531
 02532                if (audioStream.Channels.Value > channels.Value)
 2533                {
 02534                    return false;
 2535                }
 2536            }
 2537
 2538            // Sample rate must fall within requested value
 02539            if (request.AudioSampleRate.HasValue)
 2540            {
 02541                if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0)
 2542                {
 02543                    return false;
 2544                }
 2545
 02546                if (audioStream.SampleRate.Value > request.AudioSampleRate.Value)
 2547                {
 02548                    return false;
 2549                }
 2550            }
 2551
 2552            // Audio bitrate must fall within requested value
 02553            if (request.AudioBitRate.HasValue
 02554                && audioStream.BitRate.HasValue
 02555                && audioStream.BitRate.Value > request.AudioBitRate.Value)
 2556            {
 02557                return false;
 2558            }
 2559
 02560            return request.EnableAutoStreamCopy;
 2561        }
 2562
 2563        public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideo
 2564        {
 02565            var bitrate = request.VideoBitRate;
 2566
 02567            if (videoStream is not null)
 2568            {
 02569                var isUpscaling = request.Height.HasValue
 02570                    && videoStream.Height.HasValue
 02571                    && request.Height.Value > videoStream.Height.Value
 02572                    && request.Width.HasValue
 02573                    && videoStream.Width.HasValue
 02574                    && request.Width.Value > videoStream.Width.Value;
 2575
 2576                // Don't allow bitrate increases unless upscaling
 02577                if (!isUpscaling && bitrate.HasValue && videoStream.BitRate.HasValue)
 2578                {
 02579                    bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value);
 2580                }
 2581
 02582                if (bitrate.HasValue)
 2583                {
 02584                    var inputVideoCodec = videoStream.Codec;
 02585                    bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
 2586
 2587                    // If a max bitrate was requested, don't let the scaled bitrate exceed it
 02588                    if (request.VideoBitRate.HasValue)
 2589                    {
 02590                        bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
 2591                    }
 2592                }
 2593            }
 2594
 2595            // Cap the max target bitrate to intMax/2 to satisfy the bufsize=bitrate*2.
 02596            return Math.Min(bitrate ?? 0, int.MaxValue / 2);
 2597        }
 2598
 2599        private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
 2600        {
 2601            // these values were chosen from testing to improve low bitrate streams
 02602            if (sourceBitrate <= 2000000)
 2603            {
 02604                sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5);
 2605            }
 02606            else if (sourceBitrate <= 3000000)
 2607            {
 02608                sourceBitrate *= 2;
 2609            }
 2610
 02611            var bitrate = Math.Min(sourceBitrate, requestedBitrate);
 2612
 02613            return bitrate;
 2614        }
 2615
 2616        private static double GetVideoBitrateScaleFactor(string codec)
 2617        {
 2618            // hevc & vp9 - 40% more efficient than h.264
 02619            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
 02620                || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
 02621                || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
 2622            {
 02623                return .6;
 2624            }
 2625
 2626            // av1 - 50% more efficient than h.264
 02627            if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
 2628            {
 02629                return .5;
 2630            }
 2631
 02632            return 1;
 2633        }
 2634
 2635        public static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
 2636        {
 02637            var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
 02638            var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
 2639
 2640            // Don't scale the real bitrate lower than the requested bitrate
 02641            var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
 2642
 02643            if (bitrate <= 500000)
 2644            {
 02645                scaleFactor = Math.Max(scaleFactor, 4);
 2646            }
 02647            else if (bitrate <= 1000000)
 2648            {
 02649                scaleFactor = Math.Max(scaleFactor, 3);
 2650            }
 02651            else if (bitrate <= 2000000)
 2652            {
 02653                scaleFactor = Math.Max(scaleFactor, 2.5);
 2654            }
 02655            else if (bitrate <= 3000000)
 2656            {
 02657                scaleFactor = Math.Max(scaleFactor, 2);
 2658            }
 02659            else if (bitrate >= 30000000)
 2660            {
 2661                // Don't scale beyond 30Mbps, it is hardly visually noticeable for most codecs with our prefer speed enc
 2662                // and will cause extremely high bitrate to be used for av1->h264 transcoding that will overload clients
 02663                scaleFactor = 1;
 2664            }
 2665
 02666            return Convert.ToInt32(scaleFactor * bitrate);
 2667        }
 2668
 2669        public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChanne
 2670        {
 02671            return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
 2672        }
 2673
 2674        public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudio
 2675        {
 02676            if (audioStream is null)
 2677            {
 02678                return null;
 2679            }
 2680
 02681            var inputChannels = audioStream.Channels ?? 0;
 02682            var outputChannels = outputAudioChannels ?? 0;
 02683            var bitrate = audioBitRate ?? int.MaxValue;
 2684
 02685            if (string.IsNullOrEmpty(audioCodec)
 02686                || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
 02687                || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
 02688                || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
 02689                || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
 02690                || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
 02691                || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
 2692            {
 02693                return (inputChannels, outputChannels) switch
 02694                {
 02695                    (>= 6, >= 6 or 0) => Math.Min(640000, bitrate),
 02696                    (> 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
 02697                    (> 0, _) => Math.Min(inputChannels * 128000, bitrate),
 02698                    (_, _) => Math.Min(384000, bitrate)
 02699                };
 2700            }
 2701
 02702            if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
 02703                || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
 2704            {
 02705                return (inputChannels, outputChannels) switch
 02706                {
 02707                    (>= 6, >= 6 or 0) => Math.Min(768000, bitrate),
 02708                    (> 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
 02709                    (> 0, _) => Math.Min(inputChannels * 136000, bitrate),
 02710                    (_, _) => Math.Min(672000, bitrate)
 02711                };
 2712            }
 2713
 2714            // Empty bitrate area is not allow on iOS
 2715            // Default audio bitrate to 128K per channel if we don't have codec specific defaults
 2716            // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
 02717            return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
 2718        }
 2719
 2720        public string GetAudioVbrModeParam(string encoder, int bitrate, int channels)
 2721        {
 02722            var bitratePerChannel = bitrate / Math.Max(channels, 1);
 02723            if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
 2724            {
 02725                return " -vbr:a " + bitratePerChannel switch
 02726                {
 02727                    < 32000 => "1",
 02728                    < 48000 => "2",
 02729                    < 64000 => "3",
 02730                    < 96000 => "4",
 02731                    _ => "5"
 02732                };
 2733            }
 2734
 02735            if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
 2736            {
 2737                // lame's VBR is only good for a certain bitrate range
 2738                // For very low and very high bitrate, use abr mode
 02739                if (bitratePerChannel is < 122500 and > 48000)
 2740                {
 02741                    return " -qscale:a " + bitratePerChannel switch
 02742                    {
 02743                        < 64000 => "6",
 02744                        < 88000 => "4",
 02745                        < 112000 => "2",
 02746                        _ => "0"
 02747                    };
 2748                }
 2749
 02750                return " -abr:a 1" + " -b:a " + bitrate;
 2751            }
 2752
 02753            if (string.Equals(encoder, "aac_at", StringComparison.OrdinalIgnoreCase))
 2754            {
 2755                // aac_at's CVBR mode
 02756                return " -aac_at_mode:a 2" + " -b:a " + bitrate;
 2757            }
 2758
 02759            if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
 2760            {
 02761                return " -qscale:a " + bitratePerChannel switch
 02762                {
 02763                    < 40000 => "0",
 02764                    < 56000 => "2",
 02765                    < 80000 => "4",
 02766                    < 112000 => "6",
 02767                    _ => "8"
 02768                };
 2769            }
 2770
 02771            return null;
 2772        }
 2773
 2774        public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
 2775        {
 02776            var channels = state.OutputAudioChannels;
 2777
 02778            var filters = new List<string>();
 2779
 02780            if (channels is 2 && state.AudioStream?.Channels is > 2)
 2781            {
 02782                var hasDownMixFilter = DownMixAlgorithmsHelper.AlgorithmFilterStrings.TryGetValue((encodingOptions.DownM
 02783                if (hasDownMixFilter)
 2784                {
 02785                    filters.Add(downMixFilterString);
 2786                }
 2787
 02788                if (!encodingOptions.DownMixAudioBoost.Equals(1))
 2789                {
 02790                    filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
 2791                }
 2792            }
 2793
 02794            var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
 02795            if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(st
 2796            {
 02797                var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
 2798
 02799                filters.Add(
 02800                    string.Format(
 02801                        CultureInfo.InvariantCulture,
 02802                        "asetpts=PTS-{0}/TB",
 02803                        Math.Round(seconds)));
 2804            }
 2805
 02806            if (filters.Count > 0)
 2807            {
 02808                return " -af \"" + string.Join(',', filters) + "\"";
 2809            }
 2810
 02811            return string.Empty;
 2812        }
 2813
 2814        /// <summary>
 2815        /// Gets the number of audio channels to specify on the command line.
 2816        /// </summary>
 2817        /// <param name="state">The state.</param>
 2818        /// <param name="audioStream">The audio stream.</param>
 2819        /// <param name="outputAudioCodec">The output audio codec.</param>
 2820        /// <returns>System.Nullable{System.Int32}.</returns>
 2821        public int? GetNumAudioChannelsParam(EncodingJobInfo state, MediaStream audioStream, string outputAudioCodec)
 2822        {
 02823            if (audioStream is null)
 2824            {
 02825                return null;
 2826            }
 2827
 02828            var request = state.BaseRequest;
 2829
 02830            var codec = outputAudioCodec ?? string.Empty;
 2831
 02832            int? resultChannels = state.GetRequestedAudioChannels(codec);
 2833
 02834            var inputChannels = audioStream.Channels;
 2835
 02836            if (inputChannels > 0)
 2837            {
 02838                resultChannels = inputChannels < resultChannels ? inputChannels : resultChannels ?? inputChannels;
 2839            }
 2840
 02841            var isTranscodingAudio = !IsCopyCodec(codec);
 2842
 02843            if (isTranscodingAudio)
 2844            {
 02845                var audioEncoder = GetAudioEncoder(state);
 02846                if (!_audioTranscodeChannelLookup.TryGetValue(audioEncoder, out var transcoderChannelLimit))
 2847                {
 2848                    // Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many c
 02849                    transcoderChannelLimit = 8;
 2850                }
 2851
 2852                // Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelL
 02853                resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? tr
 2854
 02855                if (request.TranscodingMaxAudioChannels < resultChannels)
 2856                {
 02857                    resultChannels = request.TranscodingMaxAudioChannels;
 2858                }
 2859
 2860                // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
 2861                // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_d
 02862                if (state.TranscodingType != TranscodingJobType.Progressive
 02863                    && ((resultChannels > 2 && resultChannels < 6) || resultChannels == 7))
 2864                {
 2865                    // We can let FFMpeg supply an extra LFE channel for 5ch and 7ch to make them 5.1 and 7.1
 02866                    if (resultChannels == 5)
 2867                    {
 02868                        resultChannels = 6;
 2869                    }
 02870                    else if (resultChannels == 7)
 2871                    {
 02872                        resultChannels = 8;
 2873                    }
 2874                    else
 2875                    {
 2876                        // For other weird layout, just downmix to stereo for compatibility
 02877                        resultChannels = 2;
 2878                    }
 2879                }
 2880            }
 2881
 02882            return resultChannels;
 2883        }
 2884
 2885        /// <summary>
 2886        /// Enforces the resolution limit.
 2887        /// </summary>
 2888        /// <param name="state">The state.</param>
 2889        public void EnforceResolutionLimit(EncodingJobInfo state)
 2890        {
 02891            var videoRequest = state.BaseRequest;
 2892
 2893            // Switch the incoming params to be ceilings rather than fixed values
 02894            videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
 02895            videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
 2896
 02897            videoRequest.Width = null;
 02898            videoRequest.Height = null;
 02899        }
 2900
 2901        /// <summary>
 2902        /// Gets the fast seek command line parameter.
 2903        /// </summary>
 2904        /// <param name="state">The state.</param>
 2905        /// <param name="options">The options.</param>
 2906        /// <param name="segmentContainer">Segment Container.</param>
 2907        /// <returns>System.String.</returns>
 2908        /// <value>The fast seek command line parameter.</value>
 2909        public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options, string segmentCont
 2910        {
 02911            var time = state.BaseRequest.StartTimeTicks ?? 0;
 02912            var maxTime = state.RunTimeTicks ?? 0;
 02913            var seekParam = string.Empty;
 2914
 02915            if (time > 0)
 2916            {
 2917                // For direct streaming/remuxing, we seek at the exact position of the keyframe
 2918                // However, ffmpeg will seek to previous keyframe when the exact time is the input
 2919                // Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos.
 2920                // This will help subtitle syncing.
 02921                var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCod
 02922                var seekTick = isHlsRemuxing ? time + 5000000L : time;
 2923
 2924                // Seeking beyond EOF makes no sense in transcoding. Clamp the seekTick value to
 2925                // [0, RuntimeTicks - 5.0s], so that the muxer gets packets and avoid error codes.
 02926                if (maxTime > 0)
 2927                {
 02928                    seekTick = Math.Clamp(seekTick, 0, Math.Max(maxTime - 50000000L, 0));
 2929                }
 2930
 02931                seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekT
 2932
 02933                if (state.IsVideoRequest)
 2934                {
 02935                    var outputVideoCodec = GetVideoEncoder(state, options);
 02936                    var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
 2937
 2938                    // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking
 2939                    // Disable -noaccurate_seek on mpegts container due to the timestamps issue on some clients,
 2940                    // but it's still required for fMP4 container otherwise the audio can't be synced to the video.
 02941                    if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)
 02942                        && !string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)
 02943                        && state.TranscodingType != TranscodingJobType.Progressive
 02944                        && !state.EnableBreakOnNonKeyFrames(outputVideoCodec)
 02945                        && (state.BaseRequest.StartTimeTicks ?? 0) > 0)
 2946                    {
 02947                        seekParam += " -noaccurate_seek";
 2948                    }
 2949                }
 2950            }
 2951
 02952            return seekParam;
 2953        }
 2954
 2955        /// <summary>
 2956        /// Gets the map args.
 2957        /// </summary>
 2958        /// <param name="state">The state.</param>
 2959        /// <returns>System.String.</returns>
 2960        public string GetMapArgs(EncodingJobInfo state)
 2961        {
 2962            // If we don't have known media info
 2963            // If input is video, use -sn to drop subtitles
 2964            // Otherwise just return empty
 02965            if (state.VideoStream is null && state.AudioStream is null)
 2966            {
 02967                return state.IsInputVideo ? "-sn" : string.Empty;
 2968            }
 2969
 2970            // We have media info, but we don't know the stream index
 02971            if (state.VideoStream is not null && state.VideoStream.Index == -1)
 2972            {
 02973                return "-sn";
 2974            }
 2975
 2976            // We have media info, but we don't know the stream index
 02977            if (state.AudioStream is not null && state.AudioStream.Index == -1)
 2978            {
 02979                return state.IsInputVideo ? "-sn" : string.Empty;
 2980            }
 2981
 02982            var args = string.Empty;
 2983
 02984            if (state.VideoStream is not null)
 2985            {
 02986                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 2987
 02988                args += string.Format(
 02989                    CultureInfo.InvariantCulture,
 02990                    "-map 0:{0}",
 02991                    videoStreamIndex);
 2992            }
 2993            else
 2994            {
 2995                // No known video stream
 02996                args += "-vn";
 2997            }
 2998
 02999            if (state.AudioStream is not null)
 3000            {
 03001                int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
 03002                if (state.AudioStream.IsExternal)
 3003                {
 03004                    bool hasExternalGraphicsSubs = state.SubtitleStream is not null
 03005                        && ShouldEncodeSubtitle(state)
 03006                        && state.SubtitleStream.IsExternal
 03007                        && !state.SubtitleStream.IsTextSubtitleStream;
 03008                    int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
 3009
 03010                    args += string.Format(
 03011                        CultureInfo.InvariantCulture,
 03012                        " -map {0}:{1}",
 03013                        externalAudioMapIndex,
 03014                        audioStreamIndex);
 3015                }
 3016                else
 3017                {
 03018                    args += string.Format(
 03019                        CultureInfo.InvariantCulture,
 03020                        " -map 0:{0}",
 03021                        audioStreamIndex);
 3022                }
 3023            }
 3024            else
 3025            {
 03026                args += " -map -0:a";
 3027            }
 3028
 03029            var subtitleMethod = state.SubtitleDeliveryMethod;
 03030            if (state.SubtitleStream is null || subtitleMethod == SubtitleDeliveryMethod.Hls)
 3031            {
 03032                args += " -map -0:s";
 3033            }
 03034            else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
 3035            {
 03036                int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3037
 03038                args += string.Format(
 03039                    CultureInfo.InvariantCulture,
 03040                    " -map 0:{0}",
 03041                    subtitleStreamIndex);
 3042            }
 03043            else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
 3044            {
 03045                int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 3046
 03047                args += string.Format(
 03048                    CultureInfo.InvariantCulture,
 03049                    " -map 1:{0} -sn",
 03050                    externalSubtitleStreamIndex);
 3051            }
 3052
 03053            return args;
 3054        }
 3055
 3056        /// <summary>
 3057        /// Gets the negative map args by filters.
 3058        /// </summary>
 3059        /// <param name="state">The state.</param>
 3060        /// <param name="videoProcessFilters">The videoProcessFilters.</param>
 3061        /// <returns>System.String.</returns>
 3062        public string GetNegativeMapArgsByFilters(EncodingJobInfo state, string videoProcessFilters)
 3063        {
 03064            string args = string.Empty;
 3065
 3066            // http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
 03067            if (state.VideoStream is not null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordina
 3068            {
 03069                int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 3070
 03071                args += string.Format(
 03072                    CultureInfo.InvariantCulture,
 03073                    "-map -0:{0} ",
 03074                    videoStreamIndex);
 3075            }
 3076
 03077            return args;
 3078        }
 3079
 3080        /// <summary>
 3081        /// Determines which stream will be used for playback.
 3082        /// </summary>
 3083        /// <param name="allStream">All stream.</param>
 3084        /// <param name="desiredIndex">Index of the desired.</param>
 3085        /// <param name="type">The type.</param>
 3086        /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
 3087        /// <returns>MediaStream.</returns>
 3088        public MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, b
 3089        {
 03090            var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
 3091
 03092            if (desiredIndex.HasValue)
 3093            {
 03094                var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
 3095
 03096                if (stream is not null)
 3097                {
 03098                    return stream;
 3099                }
 3100            }
 3101
 03102            if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
 3103            {
 03104                return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
 03105                       streams.FirstOrDefault();
 3106            }
 3107
 3108            // Just return the first one
 03109            return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
 3110        }
 3111
 3112        public static (int? Width, int? Height) GetFixedOutputSize(
 3113            int? videoWidth,
 3114            int? videoHeight,
 3115            int? requestedWidth,
 3116            int? requestedHeight,
 3117            int? requestedMaxWidth,
 3118            int? requestedMaxHeight)
 3119        {
 03120            if (!videoWidth.HasValue && !requestedWidth.HasValue)
 3121            {
 03122                return (null, null);
 3123            }
 3124
 03125            if (!videoHeight.HasValue && !requestedHeight.HasValue)
 3126            {
 03127                return (null, null);
 3128            }
 3129
 03130            int inputWidth = Convert.ToInt32(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture);
 03131            int inputHeight = Convert.ToInt32(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture);
 03132            int outputWidth = requestedWidth ?? inputWidth;
 03133            int outputHeight = requestedHeight ?? inputHeight;
 3134
 3135            // Don't transcode video to bigger than 4k when using HW.
 03136            int maximumWidth = Math.Min(requestedMaxWidth ?? outputWidth, 4096);
 03137            int maximumHeight = Math.Min(requestedMaxHeight ?? outputHeight, 4096);
 3138
 03139            if (outputWidth > maximumWidth || outputHeight > maximumHeight)
 3140            {
 03141                var scaleW = (double)maximumWidth / outputWidth;
 03142                var scaleH = (double)maximumHeight / outputHeight;
 03143                var scale = Math.Min(scaleW, scaleH);
 03144                outputWidth = Math.Min(maximumWidth, Convert.ToInt32(outputWidth * scale));
 03145                outputHeight = Math.Min(maximumHeight, Convert.ToInt32(outputHeight * scale));
 3146            }
 3147
 03148            outputWidth = 2 * (outputWidth / 2);
 03149            outputHeight = 2 * (outputHeight / 2);
 3150
 03151            return (outputWidth, outputHeight);
 3152        }
 3153
 3154        public static bool IsScaleRatioSupported(
 3155            int? videoWidth,
 3156            int? videoHeight,
 3157            int? requestedWidth,
 3158            int? requestedHeight,
 3159            int? requestedMaxWidth,
 3160            int? requestedMaxHeight,
 3161            double? maxScaleRatio)
 3162        {
 03163            var (outWidth, outHeight) = GetFixedOutputSize(
 03164                videoWidth,
 03165                videoHeight,
 03166                requestedWidth,
 03167                requestedHeight,
 03168                requestedMaxWidth,
 03169                requestedMaxHeight);
 3170
 03171            if (!videoWidth.HasValue
 03172                 || !videoHeight.HasValue
 03173                 || !outWidth.HasValue
 03174                 || !outHeight.HasValue
 03175                 || !maxScaleRatio.HasValue
 03176                 || (maxScaleRatio.Value < 1.0f))
 3177            {
 03178                return false;
 3179            }
 3180
 03181            var minScaleRatio = 1.0f / maxScaleRatio;
 03182            var scaleRatioW = (double)outWidth / (double)videoWidth;
 03183            var scaleRatioH = (double)outHeight / (double)videoHeight;
 3184
 03185            if (scaleRatioW < minScaleRatio
 03186                || scaleRatioW > maxScaleRatio
 03187                || scaleRatioH < minScaleRatio
 03188                || scaleRatioH > maxScaleRatio)
 3189            {
 03190                return false;
 3191            }
 3192
 03193            return true;
 3194        }
 3195
 3196        public static string GetHwScaleFilter(
 3197            string hwScalePrefix,
 3198            string hwScaleSuffix,
 3199            string videoFormat,
 3200            bool swapOutputWandH,
 3201            int? videoWidth,
 3202            int? videoHeight,
 3203            int? requestedWidth,
 3204            int? requestedHeight,
 3205            int? requestedMaxWidth,
 3206            int? requestedMaxHeight)
 3207        {
 03208            var (outWidth, outHeight) = GetFixedOutputSize(
 03209                videoWidth,
 03210                videoHeight,
 03211                requestedWidth,
 03212                requestedHeight,
 03213                requestedMaxWidth,
 03214                requestedMaxHeight);
 3215
 03216            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03217            var isSizeFixed = !videoWidth.HasValue
 03218                || outWidth.Value != videoWidth.Value
 03219                || !videoHeight.HasValue
 03220                || outHeight.Value != videoHeight.Value;
 3221
 03222            var swpOutW = swapOutputWandH ? outHeight.Value : outWidth.Value;
 03223            var swpOutH = swapOutputWandH ? outWidth.Value : outHeight.Value;
 3224
 03225            var arg1 = isSizeFixed ? $"=w={swpOutW}:h={swpOutH}" : string.Empty;
 03226            var arg2 = isFormatFixed ? $"format={videoFormat}" : string.Empty;
 03227            if (isFormatFixed)
 3228            {
 03229                arg2 = (isSizeFixed ? ':' : '=') + arg2;
 3230            }
 3231
 03232            if (!string.IsNullOrEmpty(hwScaleSuffix) && (isSizeFixed || isFormatFixed))
 3233            {
 03234                return string.Format(
 03235                    CultureInfo.InvariantCulture,
 03236                    "{0}_{1}{2}{3}",
 03237                    hwScalePrefix ?? "scale",
 03238                    hwScaleSuffix,
 03239                    arg1,
 03240                    arg2);
 3241            }
 3242
 03243            return string.Empty;
 3244        }
 3245
 3246        public static string GetGraphicalSubPreProcessFilters(
 3247            int? videoWidth,
 3248            int? videoHeight,
 3249            int? subtitleWidth,
 3250            int? subtitleHeight,
 3251            int? requestedWidth,
 3252            int? requestedHeight,
 3253            int? requestedMaxWidth,
 3254            int? requestedMaxHeight)
 3255        {
 03256            var (outWidth, outHeight) = GetFixedOutputSize(
 03257                videoWidth,
 03258                videoHeight,
 03259                requestedWidth,
 03260                requestedHeight,
 03261                requestedMaxWidth,
 03262                requestedMaxHeight);
 3263
 03264            if (!outWidth.HasValue
 03265                || !outHeight.HasValue
 03266                || outWidth.Value <= 0
 03267                || outHeight.Value <= 0)
 3268            {
 03269                return string.Empty;
 3270            }
 3271
 3272            // Automatically add padding based on subtitle input
 03273            var filters = @"scale,scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:blac
 3274
 03275            if (subtitleWidth.HasValue
 03276                && subtitleHeight.HasValue
 03277                && subtitleWidth.Value > 0
 03278                && subtitleHeight.Value > 0)
 3279            {
 03280                var videoDar = (double)outWidth.Value / outHeight.Value;
 03281                var subtitleDar = (double)subtitleWidth.Value / subtitleHeight.Value;
 3282
 3283                // No need to add padding when DAR is the same -> 1080p PGSSUB on 2160p video
 03284                if (Math.Abs(videoDar - subtitleDar) < 0.01f)
 3285                {
 03286                    filters = @"scale,scale={0}:{1}:fast_bilinear";
 3287                }
 3288            }
 3289
 03290            return string.Format(
 03291                CultureInfo.InvariantCulture,
 03292                filters,
 03293                outWidth.Value,
 03294                outHeight.Value);
 3295        }
 3296
 3297        public static string GetAlphaSrcFilter(
 3298            EncodingJobInfo state,
 3299            int? videoWidth,
 3300            int? videoHeight,
 3301            int? requestedWidth,
 3302            int? requestedHeight,
 3303            int? requestedMaxWidth,
 3304            int? requestedMaxHeight,
 3305            float? framerate)
 3306        {
 03307            var reqTicks = state.BaseRequest.StartTimeTicks ?? 0;
 03308            var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture
 03309            var (outWidth, outHeight) = GetFixedOutputSize(
 03310                videoWidth,
 03311                videoHeight,
 03312                requestedWidth,
 03313                requestedHeight,
 03314                requestedMaxWidth,
 03315                requestedMaxHeight);
 3316
 03317            if (outWidth.HasValue && outHeight.HasValue)
 3318            {
 03319                return string.Format(
 03320                    CultureInfo.InvariantCulture,
 03321                    "alphasrc=s={0}x{1}:r={2}:start='{3}'",
 03322                    outWidth.Value,
 03323                    outHeight.Value,
 03324                    framerate ?? 25,
 03325                    reqTicks > 0 ? startTime : 0);
 3326            }
 3327
 03328            return string.Empty;
 3329        }
 3330
 3331        public static string GetSwScaleFilter(
 3332            EncodingJobInfo state,
 3333            EncodingOptions options,
 3334            string videoEncoder,
 3335            int? videoWidth,
 3336            int? videoHeight,
 3337            Video3DFormat? threedFormat,
 3338            int? requestedWidth,
 3339            int? requestedHeight,
 3340            int? requestedMaxWidth,
 3341            int? requestedMaxHeight)
 3342        {
 03343            var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 03344            var isMjpeg = videoEncoder is not null && videoEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase)
 03345            var scaleVal = isV4l2 ? 64 : 2;
 03346            var targetAr = isMjpeg ? "(a*sar)" : "a"; // manually calculate AR when using mjpeg encoder
 3347
 3348            // If fixed dimensions were supplied
 03349            if (requestedWidth.HasValue && requestedHeight.HasValue)
 3350            {
 03351                if (isV4l2)
 3352                {
 03353                    var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 03354                    var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3355
 03356                    return string.Format(
 03357                            CultureInfo.InvariantCulture,
 03358                            "scale=trunc({0}/64)*64:trunc({1}/2)*2",
 03359                            widthParam,
 03360                            heightParam);
 3361                }
 3362
 03363                return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
 3364            }
 3365
 3366            // If Max dimensions were supplied, for width selects lowest even number between input width and width req s
 3367
 03368            if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
 3369            {
 03370                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 03371                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3372
 03373                return string.Format(
 03374                    CultureInfo.InvariantCulture,
 03375                    @"scale=trunc(min(max(iw\,ih*{3})\,min({0}\,{1}*{3}))/{2})*{2}:trunc(min(max(iw/{3}\,ih)\,min({0}/{3
 03376                    maxWidthParam,
 03377                    maxHeightParam,
 03378                    scaleVal,
 03379                    targetAr);
 3380            }
 3381
 3382            // If a fixed width was requested
 03383            if (requestedWidth.HasValue)
 3384            {
 03385                if (threedFormat.HasValue)
 3386                {
 3387                    // This method can handle 0 being passed in for the requested height
 03388                    return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0);
 3389                }
 3390
 03391                var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
 3392
 03393                return string.Format(
 03394                    CultureInfo.InvariantCulture,
 03395                    "scale={0}:trunc(ow/{1}/2)*2",
 03396                    widthParam,
 03397                    targetAr);
 3398            }
 3399
 3400            // If a fixed height was requested
 03401            if (requestedHeight.HasValue)
 3402            {
 03403                var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
 3404
 03405                return string.Format(
 03406                    CultureInfo.InvariantCulture,
 03407                    "scale=trunc(oh*{2}/{1})*{1}:{0}",
 03408                    heightParam,
 03409                    scaleVal,
 03410                    targetAr);
 3411            }
 3412
 3413            // If a max width was requested
 03414            if (requestedMaxWidth.HasValue)
 3415            {
 03416                var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
 3417
 03418                return string.Format(
 03419                    CultureInfo.InvariantCulture,
 03420                    @"scale=trunc(min(max(iw\,ih*{2})\,{0})/{1})*{1}:trunc(ow/{2}/2)*2",
 03421                    maxWidthParam,
 03422                    scaleVal,
 03423                    targetAr);
 3424            }
 3425
 3426            // If a max height was requested
 03427            if (requestedMaxHeight.HasValue)
 3428            {
 03429                var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
 3430
 03431                return string.Format(
 03432                    CultureInfo.InvariantCulture,
 03433                    @"scale=trunc(oh*{2}/{1})*{1}:min(max(iw/{2}\,ih)\,{0})",
 03434                    maxHeightParam,
 03435                    scaleVal,
 03436                    targetAr);
 3437            }
 3438
 03439            return string.Empty;
 3440        }
 3441
 3442        private static string GetFixedSwScaleFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight
 3443        {
 03444            var widthParam = requestedWidth.ToString(CultureInfo.InvariantCulture);
 03445            var heightParam = requestedHeight.ToString(CultureInfo.InvariantCulture);
 3446
 03447            string filter = null;
 3448
 03449            if (threedFormat.HasValue)
 3450            {
 03451                switch (threedFormat.Value)
 3452                {
 3453                    case Video3DFormat.HalfSideBySide:
 03454                        filter = @"crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(i
 3455                        // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars
 03456                        break;
 3457                    case Video3DFormat.FullSideBySide:
 03458                        filter = @"crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3459                        // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the s
 03460                        break;
 3461                    case Video3DFormat.HalfTopAndBottom:
 03462                        filter = @"crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(
 3463                        // htab crop height in half,scale to correct size, set the display aspect,crop out any black bar
 03464                        break;
 3465                    case Video3DFormat.FullTopAndBottom:
 03466                        filter = @"crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar
 3467                        // ftab crop height in half, set the display aspect,crop out any black bars we may have made the
 3468                        break;
 3469                    default:
 3470                        break;
 3471                }
 3472            }
 3473
 3474            // default
 03475            if (filter is null)
 3476            {
 03477                if (requestedHeight > 0)
 3478                {
 03479                    filter = "scale=trunc({0}/2)*2:trunc({1}/2)*2";
 3480                }
 3481                else
 3482                {
 03483                    filter = "scale={0}:trunc({0}/a/2)*2";
 3484                }
 3485            }
 3486
 03487            return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
 3488        }
 3489
 3490        public static string GetSwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options)
 3491        {
 03492            var doubleRateDeint = options.DeinterlaceDoubleRate && state.VideoStream?.ReferenceFrameRate <= 30;
 03493            return string.Format(
 03494                CultureInfo.InvariantCulture,
 03495                "{0}={1}:-1:0",
 03496                options.DeinterlaceMethod.ToString().ToLowerInvariant(),
 03497                doubleRateDeint ? "1" : "0");
 3498        }
 3499
 3500        public string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix)
 3501        {
 03502            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03503            if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase))
 3504            {
 03505                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3506
 03507                return string.Format(
 03508                    CultureInfo.InvariantCulture,
 03509                    "{0}_cuda={1}:-1:0",
 03510                    useBwdif ? "bwdif" : "yadif",
 03511                    doubleRateDeint ? "1" : "0");
 3512            }
 3513
 03514            if (hwDeintSuffix.Contains("opencl", StringComparison.OrdinalIgnoreCase))
 3515            {
 03516                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif;
 3517
 03518                if (_mediaEncoder.SupportsFilter("yadif_opencl")
 03519                    && _mediaEncoder.SupportsFilter("bwdif_opencl"))
 3520                {
 03521                    return string.Format(
 03522                        CultureInfo.InvariantCulture,
 03523                        "{0}_opencl={1}:-1:0",
 03524                        useBwdif ? "bwdif" : "yadif",
 03525                        doubleRateDeint ? "1" : "0");
 3526                }
 3527            }
 3528
 03529            if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
 3530            {
 03531                return string.Format(
 03532                    CultureInfo.InvariantCulture,
 03533                    "deinterlace_vaapi=rate={0}",
 03534                    doubleRateDeint ? "field" : "frame");
 3535            }
 3536
 03537            if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
 3538            {
 03539                return "deinterlace_qsv=mode=2";
 3540            }
 3541
 03542            if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
 3543            {
 03544                var useBwdif = options.DeinterlaceMethod == DeinterlaceMethod.bwdif && _mediaEncoder.SupportsFilter("bwd
 3545
 03546                return string.Format(
 03547                    CultureInfo.InvariantCulture,
 03548                    "{0}_videotoolbox={1}:-1:0",
 03549                    useBwdif ? "bwdif" : "yadif",
 03550                    doubleRateDeint ? "1" : "0");
 3551            }
 3552
 03553            return string.Empty;
 3554        }
 3555
 3556        private string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat, bool forc
 3557        {
 03558            if (string.IsNullOrEmpty(hwTonemapSuffix))
 3559            {
 03560                return string.Empty;
 3561            }
 3562
 03563            var args = string.Empty;
 03564            var algorithm = options.TonemappingAlgorithm.ToString().ToLowerInvariant();
 03565            var mode = options.TonemappingMode.ToString().ToLowerInvariant();
 03566            var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 03567            var rangeString = range.ToString().ToLowerInvariant();
 3568
 03569            if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
 3570            {
 03571                var doVaVppProcamp = false;
 03572                var procampParams = string.Empty;
 03573                if (options.VppTonemappingBrightness != 0
 03574                    && options.VppTonemappingBrightness >= -100
 03575                    && options.VppTonemappingBrightness <= 100)
 3576                {
 03577                    procampParams += "procamp_vaapi=b={0}";
 03578                    doVaVppProcamp = true;
 3579                }
 3580
 03581                if (options.VppTonemappingContrast > 1
 03582                    && options.VppTonemappingContrast <= 10)
 3583                {
 03584                    procampParams += doVaVppProcamp ? ":c={1}" : "procamp_vaapi=c={1}";
 03585                    doVaVppProcamp = true;
 3586                }
 3587
 03588                args = procampParams + "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
 3589
 03590                return string.Format(
 03591                        CultureInfo.InvariantCulture,
 03592                        args,
 03593                        options.VppTonemappingBrightness,
 03594                        options.VppTonemappingContrast,
 03595                        doVaVppProcamp ? "," : string.Empty,
 03596                        videoFormat ?? "nv12");
 3597            }
 3598            else
 3599            {
 03600                args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
 3601
 03602                var useLegacyTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode
 03603                                           && _legacyTonemapModes.Contains(options.TonemappingMode);
 3604
 03605                var useAdvancedTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegAdvancedTonemapMode
 03606                                              && _advancedTonemapModes.Contains(options.TonemappingMode);
 3607
 03608                if (useLegacyTonemapModes || useAdvancedTonemapModes)
 3609                {
 03610                    args += ":tonemap_mode={5}";
 3611                }
 3612
 03613                if (options.TonemappingParam != 0)
 3614                {
 03615                    args += ":param={6}";
 3616                }
 3617
 03618                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3619                {
 03620                    args += ":range={7}";
 3621                }
 3622            }
 3623
 03624            return string.Format(
 03625                    CultureInfo.InvariantCulture,
 03626                    args,
 03627                    hwTonemapSuffix,
 03628                    videoFormat ?? "nv12",
 03629                    algorithm,
 03630                    options.TonemappingPeak,
 03631                    options.TonemappingDesat,
 03632                    mode,
 03633                    options.TonemappingParam,
 03634                    rangeString);
 3635        }
 3636
 3637        private string GetLibplaceboFilter(
 3638            EncodingOptions options,
 3639            string videoFormat,
 3640            bool doTonemap,
 3641            int? videoWidth,
 3642            int? videoHeight,
 3643            int? requestedWidth,
 3644            int? requestedHeight,
 3645            int? requestedMaxWidth,
 3646            int? requestedMaxHeight,
 3647            bool forceFullRange)
 3648        {
 03649            var (outWidth, outHeight) = GetFixedOutputSize(
 03650                videoWidth,
 03651                videoHeight,
 03652                requestedWidth,
 03653                requestedHeight,
 03654                requestedMaxWidth,
 03655                requestedMaxHeight);
 3656
 03657            var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
 03658            var isSizeFixed = !videoWidth.HasValue
 03659                || outWidth.Value != videoWidth.Value
 03660                || !videoHeight.HasValue
 03661                || outHeight.Value != videoHeight.Value;
 3662
 03663            var sizeArg = isSizeFixed ? (":w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty;
 03664            var formatArg = isFormatFixed ? (":format=" + videoFormat) : string.Empty;
 03665            var tonemapArg = string.Empty;
 3666
 03667            if (doTonemap)
 3668            {
 03669                var algorithm = options.TonemappingAlgorithm;
 03670                var algorithmString = "clip";
 03671                var mode = options.TonemappingMode;
 03672                var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
 3673
 03674                if (algorithm == TonemappingAlgorithm.bt2390)
 3675                {
 03676                    algorithmString = "bt.2390";
 3677                }
 03678                else if (algorithm != TonemappingAlgorithm.none)
 3679                {
 03680                    algorithmString = algorithm.ToString().ToLowerInvariant();
 3681                }
 3682
 03683                tonemapArg = $":tonemapping={algorithmString}:peak_detect=0:color_primaries=bt709:color_trc=bt709:colors
 3684
 03685                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3686                {
 03687                    tonemapArg += ":range=" + range.ToString().ToLowerInvariant();
 3688                }
 3689            }
 3690
 03691            return string.Format(
 03692                CultureInfo.InvariantCulture,
 03693                "libplacebo=upscaler=none:downscaler=none{0}{1}{2}",
 03694                sizeArg,
 03695                formatArg,
 03696                tonemapArg);
 3697        }
 3698
 3699        public string GetVideoTransposeDirection(EncodingJobInfo state)
 3700        {
 03701            return (state.VideoStream?.Rotation ?? 0) switch
 03702            {
 03703                90 => "cclock",
 03704                180 => "reversal",
 03705                -90 => "clock",
 03706                -180 => "reversal",
 03707                _ => string.Empty
 03708            };
 3709        }
 3710
 3711        /// <summary>
 3712        /// Gets the parameter of software filter chain.
 3713        /// </summary>
 3714        /// <param name="state">Encoding state.</param>
 3715        /// <param name="options">Encoding options.</param>
 3716        /// <param name="vidEncoder">Video encoder to use.</param>
 3717        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3718        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetSwVidFilterChain(
 3719            EncodingJobInfo state,
 3720            EncodingOptions options,
 3721            string vidEncoder)
 3722        {
 03723            var inW = state.VideoStream?.Width;
 03724            var inH = state.VideoStream?.Height;
 03725            var reqW = state.BaseRequest.Width;
 03726            var reqH = state.BaseRequest.Height;
 03727            var reqMaxW = state.BaseRequest.MaxWidth;
 03728            var reqMaxH = state.BaseRequest.MaxHeight;
 03729            var threeDFormat = state.MediaSource.Video3DFormat;
 3730
 03731            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03732            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03733            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 03734            var isV4l2Encoder = vidEncoder.Contains("h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
 3735
 03736            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03737            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03738            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03739            var doToneMap = IsSwTonemapAvailable(state, options);
 03740            var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI;
 3741
 03742            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03743            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03744            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 3745
 03746            var rotation = state.VideoStream?.Rotation ?? 0;
 03747            var swapWAndH = Math.Abs(rotation) == 90;
 03748            var swpInW = swapWAndH ? inH : inW;
 03749            var swpInH = swapWAndH ? inW : inH;
 3750
 3751            /* Make main filters for video stream */
 03752            var mainFilters = new List<string>();
 3753
 03754            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doToneMap));
 3755
 3756            // INPUT sw surface(memory/copy-back from vram)
 3757            // sw deint
 03758            if (doDeintH2645)
 3759            {
 03760                var deintFilter = GetSwDeinterlaceFilter(state, options);
 03761                mainFilters.Add(deintFilter);
 3762            }
 3763
 03764            var outFormat = isSwDecoder ? "yuv420p" : "nv12";
 03765            var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, r
 03766            if (isVaapiEncoder)
 3767            {
 03768                outFormat = "nv12";
 3769            }
 03770            else if (isV4l2Encoder)
 3771            {
 03772                outFormat = "yuv420p";
 3773            }
 3774
 3775            // sw scale
 03776            mainFilters.Add(swScaleFilter);
 3777
 3778            // sw tonemap
 03779            if (doToneMap)
 3780            {
 3781                // tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary
 03782                var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat;
 03783                var tonemapArgString = "tonemapx=tonemap={0}:desat={1}:peak={2}:t=bt709:m=bt709:p=bt709:format={3}";
 3784
 03785                if (options.TonemappingParam != 0)
 3786                {
 03787                    tonemapArgString += ":param={4}";
 3788                }
 3789
 03790                var range = options.TonemappingRange;
 03791                if (range == TonemappingRange.tv || range == TonemappingRange.pc)
 3792                {
 03793                    tonemapArgString += ":range={5}";
 3794                }
 3795
 03796                var tonemapArgs = string.Format(
 03797                    CultureInfo.InvariantCulture,
 03798                    tonemapArgString,
 03799                    options.TonemappingAlgorithm,
 03800                    options.TonemappingDesat,
 03801                    options.TonemappingPeak,
 03802                    tonemapFormat,
 03803                    options.TonemappingParam,
 03804                    options.TonemappingRange);
 3805
 03806                mainFilters.Add(tonemapArgs);
 3807            }
 3808            else
 3809            {
 3810                // OUTPUT yuv420p/nv12 surface(memory)
 03811                mainFilters.Add("format=" + outFormat);
 3812            }
 3813
 3814            /* Make sub and overlay filters for subtitle stream */
 03815            var subFilters = new List<string>();
 03816            var overlayFilters = new List<string>();
 03817            if (hasTextSubs)
 3818            {
 3819                // subtitles=f='*.ass':alpha=0
 03820                var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03821                mainFilters.Add(textSubtitlesFilter);
 3822            }
 03823            else if (hasGraphicalSubs)
 3824            {
 03825                var subW = state.SubtitleStream?.Width;
 03826                var subH = state.SubtitleStream?.Height;
 03827                var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, reqMaxW
 03828                subFilters.Add(subPreProcFilters);
 03829                overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 3830            }
 3831
 03832            return (mainFilters, subFilters, overlayFilters);
 3833        }
 3834
 3835        /// <summary>
 3836        /// Gets the parameter of Nvidia NVENC filter chain.
 3837        /// </summary>
 3838        /// <param name="state">Encoding state.</param>
 3839        /// <param name="options">Encoding options.</param>
 3840        /// <param name="vidEncoder">Video encoder to use.</param>
 3841        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 3842        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFilterChain(
 3843            EncodingJobInfo state,
 3844            EncodingOptions options,
 3845            string vidEncoder)
 3846        {
 03847            if (options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 3848            {
 03849                return (null, null, null);
 3850            }
 3851
 03852            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 03853            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03854            var isSwEncoder = !vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 3855
 3856            // legacy cuvid pipeline(copy-back)
 03857            if ((isSwDecoder && isSwEncoder)
 03858                || !IsCudaFullSupported()
 03859                || !_mediaEncoder.SupportsFilter("alphasrc"))
 3860            {
 03861                return GetSwVidFilterChain(state, options, vidEncoder);
 3862            }
 3863
 3864            // preferred nvdec/cuvid + cuda filters + nvenc pipeline
 03865            return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 3866        }
 3867
 3868        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFiltersPrefe
 3869            EncodingJobInfo state,
 3870            EncodingOptions options,
 3871            string vidDecoder,
 3872            string vidEncoder)
 3873        {
 03874            var inW = state.VideoStream?.Width;
 03875            var inH = state.VideoStream?.Height;
 03876            var reqW = state.BaseRequest.Width;
 03877            var reqH = state.BaseRequest.Height;
 03878            var reqMaxW = state.BaseRequest.MaxWidth;
 03879            var reqMaxH = state.BaseRequest.MaxHeight;
 03880            var threeDFormat = state.MediaSource.Video3DFormat;
 3881
 03882            var isNvDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
 03883            var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
 03884            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 03885            var isSwEncoder = !isNvencEncoder;
 03886            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 03887            var isCuInCuOut = isNvDecoder && isNvencEncoder;
 3888
 03889            var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
 03890            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 03891            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 03892            var doDeintH2645 = doDeintH264 || doDeintHevc;
 03893            var doCuTonemap = IsHwTonemapAvailable(state, options);
 3894
 03895            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 03896            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 03897            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 03898            var hasAssSubs = hasSubs
 03899                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 03900                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 03901            var subW = state.SubtitleStream?.Width;
 03902            var subH = state.SubtitleStream?.Height;
 3903
 03904            var rotation = state.VideoStream?.Rotation ?? 0;
 03905            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 03906            var doCuTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_cuda");
 03907            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isNvDecoder && doCuTranspose));
 03908            var swpInW = swapWAndH ? inH : inW;
 03909            var swpInH = swapWAndH ? inW : inH;
 3910
 3911            /* Make main filters for video stream */
 03912            var mainFilters = new List<string>();
 3913
 03914            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doCuTonemap));
 3915
 03916            if (isSwDecoder)
 3917            {
 3918                // INPUT sw surface(memory)
 3919                // sw deint
 03920                if (doDeintH2645)
 3921                {
 03922                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 03923                    mainFilters.Add(swDeintFilter);
 3924                }
 3925
 03926                var outFormat = doCuTonemap ? "yuv420p10le" : "yuv420p";
 03927                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 3928                // sw scale
 03929                mainFilters.Add(swScaleFilter);
 03930                mainFilters.Add($"format={outFormat}");
 3931
 3932                // sw => hw
 03933                if (doCuTonemap)
 3934                {
 03935                    mainFilters.Add("hwupload=derive_device=cuda");
 3936                }
 3937            }
 3938
 03939            if (isNvDecoder)
 3940            {
 3941                // INPUT cuda surface(vram)
 3942                // hw deint
 03943                if (doDeintH2645)
 3944                {
 03945                    var deintFilter = GetHwDeinterlaceFilter(state, options, "cuda");
 03946                    mainFilters.Add(deintFilter);
 3947                }
 3948
 3949                // hw transpose
 03950                if (doCuTranspose)
 3951                {
 03952                    mainFilters.Add($"transpose_cuda=dir={transposeDir}");
 3953                }
 3954
 03955                var isRext = IsVideoStreamHevcRext(state);
 03956                var outFormat = doCuTonemap ? (isRext ? "p010" : string.Empty) : "yuv420p";
 03957                var hwScaleFilter = GetHwScaleFilter("scale", "cuda", outFormat, false, swpInW, swpInH, reqW, reqH, reqM
 3958                // hw scale
 03959                mainFilters.Add(hwScaleFilter);
 3960            }
 3961
 3962            // hw tonemap
 03963            if (doCuTonemap)
 3964            {
 03965                var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p", isMjpegEncoder);
 03966                mainFilters.Add(tonemapFilter);
 3967            }
 3968
 03969            var memoryOutput = false;
 03970            var isUploadForCuTonemap = isSwDecoder && doCuTonemap;
 03971            if ((isNvDecoder && isSwEncoder) || (isUploadForCuTonemap && hasSubs))
 3972            {
 03973                memoryOutput = true;
 3974
 3975                // OUTPUT yuv420p surface(memory)
 03976                mainFilters.Add("hwdownload");
 03977                mainFilters.Add("format=yuv420p");
 3978            }
 3979
 3980            // OUTPUT yuv420p surface(memory)
 03981            if (isSwDecoder && isNvencEncoder && !isUploadForCuTonemap)
 3982            {
 03983                memoryOutput = true;
 3984            }
 3985
 03986            if (memoryOutput)
 3987            {
 3988                // text subtitles
 03989                if (hasTextSubs)
 3990                {
 03991                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 03992                    mainFilters.Add(textSubtitlesFilter);
 3993                }
 3994            }
 3995
 3996            // OUTPUT cuda(yuv420p) surface(vram)
 3997
 3998            /* Make sub and overlay filters for subtitle stream */
 03999            var subFilters = new List<string>();
 04000            var overlayFilters = new List<string>();
 04001            if (isCuInCuOut)
 4002            {
 04003                if (hasSubs)
 4004                {
 04005                    var alphaFormatOpt = string.Empty;
 04006                    if (hasGraphicalSubs)
 4007                    {
 04008                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04009                        subFilters.Add(subPreProcFilters);
 04010                        subFilters.Add("format=yuva420p");
 4011                    }
 04012                    else if (hasTextSubs)
 4013                    {
 04014                        var framerate = state.VideoStream?.RealFrameRate;
 04015                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4016
 4017                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04018                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04019                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04020                        subFilters.Add(alphaSrcFilter);
 04021                        subFilters.Add("format=yuva420p");
 04022                        subFilters.Add(subTextSubtitlesFilter);
 4023
 04024                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayCudaAlphaFormat)
 04025                            ? ":alpha_format=premultiplied" : string.Empty;
 4026                    }
 4027
 04028                    subFilters.Add("hwupload=derive_device=cuda");
 04029                    overlayFilters.Add($"overlay_cuda=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 4030                }
 4031            }
 4032            else
 4033            {
 04034                if (hasGraphicalSubs)
 4035                {
 04036                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04037                    subFilters.Add(subPreProcFilters);
 04038                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4039                }
 4040            }
 4041
 04042            return (mainFilters, subFilters, overlayFilters);
 4043        }
 4044
 4045        /// <summary>
 4046        /// Gets the parameter of AMD AMF filter chain.
 4047        /// </summary>
 4048        /// <param name="state">Encoding state.</param>
 4049        /// <param name="options">Encoding options.</param>
 4050        /// <param name="vidEncoder">Video encoder to use.</param>
 4051        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4052        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVidFilterChain(
 4053            EncodingJobInfo state,
 4054            EncodingOptions options,
 4055            string vidEncoder)
 4056        {
 04057            if (options.HardwareAccelerationType != HardwareAccelerationType.amf)
 4058            {
 04059                return (null, null, null);
 4060            }
 4061
 04062            var isWindows = OperatingSystem.IsWindows();
 04063            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04064            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04065            var isSwEncoder = !vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04066            var isAmfDx11OclSupported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va") && IsOpenclFullSupported()
 4067
 4068            // legacy d3d11va pipeline(copy-back)
 04069            if ((isSwDecoder && isSwEncoder)
 04070                || !isAmfDx11OclSupported
 04071                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4072            {
 04073                return GetSwVidFilterChain(state, options, vidEncoder);
 4074            }
 4075
 4076            // preferred d3d11va + opencl filters + amf pipeline
 04077            return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4078        }
 4079
 4080        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdDx11VidFiltersPref
 4081            EncodingJobInfo state,
 4082            EncodingOptions options,
 4083            string vidDecoder,
 4084            string vidEncoder)
 4085        {
 04086            var inW = state.VideoStream?.Width;
 04087            var inH = state.VideoStream?.Height;
 04088            var reqW = state.BaseRequest.Width;
 04089            var reqH = state.BaseRequest.Height;
 04090            var reqMaxW = state.BaseRequest.MaxWidth;
 04091            var reqMaxH = state.BaseRequest.MaxHeight;
 04092            var threeDFormat = state.MediaSource.Video3DFormat;
 4093
 04094            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04095            var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
 04096            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04097            var isSwEncoder = !isAmfEncoder;
 04098            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04099            var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder;
 4100
 04101            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04102            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04103            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04104            var doOclTonemap = IsHwTonemapAvailable(state, options);
 4105
 04106            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04107            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04108            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04109            var hasAssSubs = hasSubs
 04110                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04111                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04112            var subW = state.SubtitleStream?.Width;
 04113            var subH = state.SubtitleStream?.Height;
 4114
 04115            var rotation = state.VideoStream?.Rotation ?? 0;
 04116            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04117            var doOclTranspose = !string.IsNullOrEmpty(transposeDir)
 04118                && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TransposeOpenclReversal);
 04119            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isD3d11vaDecoder && doOclTranspose));
 04120            var swpInW = swapWAndH ? inH : inW;
 04121            var swpInH = swapWAndH ? inW : inH;
 4122
 4123            /* Make main filters for video stream */
 04124            var mainFilters = new List<string>();
 4125
 04126            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 4127
 04128            if (isSwDecoder)
 4129            {
 4130                // INPUT sw surface(memory)
 4131                // sw deint
 04132                if (doDeintH2645)
 4133                {
 04134                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04135                    mainFilters.Add(swDeintFilter);
 4136                }
 4137
 04138                var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
 04139                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 4140                // sw scale
 04141                mainFilters.Add(swScaleFilter);
 04142                mainFilters.Add($"format={outFormat}");
 4143
 4144                // keep video at memory except ocl tonemap,
 4145                // since the overhead caused by hwupload >>> using sw filter.
 4146                // sw => hw
 04147                if (doOclTonemap)
 4148                {
 04149                    mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=24");
 04150                    mainFilters.Add("format=d3d11");
 04151                    mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4152                }
 4153            }
 4154
 04155            if (isD3d11vaDecoder)
 4156            {
 4157                // INPUT d3d11 surface(vram)
 4158                // map from d3d11va to opencl via d3d11-opencl interop.
 04159                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4160
 4161                // hw deint
 04162                if (doDeintH2645)
 4163                {
 04164                    var deintFilter = GetHwDeinterlaceFilter(state, options, "opencl");
 04165                    mainFilters.Add(deintFilter);
 4166                }
 4167
 4168                // hw transpose
 04169                if (doOclTranspose)
 4170                {
 04171                    mainFilters.Add($"transpose_opencl=dir={transposeDir}");
 4172                }
 4173
 04174                var outFormat = doOclTonemap ? string.Empty : "nv12";
 04175                var hwScaleFilter = GetHwScaleFilter("scale", "opencl", outFormat, false, swpInW, swpInH, reqW, reqH, re
 4176                // hw scale
 04177                mainFilters.Add(hwScaleFilter);
 4178            }
 4179
 4180            // hw tonemap
 04181            if (doOclTonemap)
 4182            {
 04183                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04184                mainFilters.Add(tonemapFilter);
 4185            }
 4186
 04187            var memoryOutput = false;
 04188            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04189            if (isD3d11vaDecoder && isSwEncoder)
 4190            {
 04191                memoryOutput = true;
 4192
 4193                // OUTPUT nv12 surface(memory)
 4194                // prefer hwmap to hwdownload on opencl.
 04195                var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap=mode=read";
 04196                mainFilters.Add(hwTransferFilter);
 04197                mainFilters.Add("format=nv12");
 4198            }
 4199
 4200            // OUTPUT yuv420p surface
 04201            if (isSwDecoder && isAmfEncoder && !isUploadForOclTonemap)
 4202            {
 04203                memoryOutput = true;
 4204            }
 4205
 04206            if (memoryOutput)
 4207            {
 4208                // text subtitles
 04209                if (hasTextSubs)
 4210                {
 04211                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04212                    mainFilters.Add(textSubtitlesFilter);
 4213                }
 4214            }
 4215
 04216            if ((isDxInDxOut || isUploadForOclTonemap) && !hasSubs)
 4217            {
 4218                // OUTPUT d3d11(nv12) surface(vram)
 4219                // reverse-mapping via d3d11-opencl interop.
 04220                mainFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04221                mainFilters.Add("format=d3d11");
 4222            }
 4223
 4224            /* Make sub and overlay filters for subtitle stream */
 04225            var subFilters = new List<string>();
 04226            var overlayFilters = new List<string>();
 04227            if (isDxInDxOut || isUploadForOclTonemap)
 4228            {
 04229                if (hasSubs)
 4230                {
 04231                    var alphaFormatOpt = string.Empty;
 04232                    if (hasGraphicalSubs)
 4233                    {
 04234                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04235                        subFilters.Add(subPreProcFilters);
 04236                        subFilters.Add("format=yuva420p");
 4237                    }
 04238                    else if (hasTextSubs)
 4239                    {
 04240                        var framerate = state.VideoStream?.RealFrameRate;
 04241                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4242
 4243                        // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
 04244                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subF
 04245                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04246                        subFilters.Add(alphaSrcFilter);
 04247                        subFilters.Add("format=yuva420p");
 04248                        subFilters.Add(subTextSubtitlesFilter);
 4249
 04250                        alphaFormatOpt = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclAlphaForma
 04251                            ? ":alpha_format=premultiplied" : string.Empty;
 4252                    }
 4253
 04254                    subFilters.Add("hwupload=derive_device=opencl");
 04255                    overlayFilters.Add($"overlay_opencl=eof_action=pass:repeatlast=0{alphaFormatOpt}");
 04256                    overlayFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1");
 04257                    overlayFilters.Add("format=d3d11");
 4258                }
 4259            }
 04260            else if (memoryOutput)
 4261            {
 04262                if (hasGraphicalSubs)
 4263                {
 04264                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04265                    subFilters.Add(subPreProcFilters);
 04266                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4267                }
 4268            }
 4269
 04270            return (mainFilters, subFilters, overlayFilters);
 4271        }
 4272
 4273        /// <summary>
 4274        /// Gets the parameter of Intel QSV filter chain.
 4275        /// </summary>
 4276        /// <param name="state">Encoding state.</param>
 4277        /// <param name="options">Encoding options.</param>
 4278        /// <param name="vidEncoder">Video encoder to use.</param>
 4279        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4280        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVidFilterChain(
 4281            EncodingJobInfo state,
 4282            EncodingOptions options,
 4283            string vidEncoder)
 4284        {
 04285            if (options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 4286            {
 04287                return (null, null, null);
 4288            }
 4289
 04290            var isWindows = OperatingSystem.IsWindows();
 04291            var isLinux = OperatingSystem.IsLinux();
 04292            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04293            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04294            var isSwEncoder = !vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04295            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 04296            var isIntelDx11OclSupported = isWindows
 04297                && _mediaEncoder.SupportsHwaccel("d3d11va")
 04298                && isQsvOclSupported;
 04299            var isIntelVaapiOclSupported = isLinux
 04300                && IsVaapiSupported(state)
 04301                && isQsvOclSupported;
 4302
 4303            // legacy qsv pipeline(copy-back)
 04304            if ((isSwDecoder && isSwEncoder)
 04305                || (!isIntelVaapiOclSupported && !isIntelDx11OclSupported)
 04306                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4307            {
 04308                return GetSwVidFilterChain(state, options, vidEncoder);
 4309            }
 4310
 4311            // preferred qsv(vaapi) + opencl filters pipeline
 04312            if (isIntelVaapiOclSupported)
 4313            {
 04314                return GetIntelQsvVaapiVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4315            }
 4316
 4317            // preferred qsv(d3d11) + opencl filters pipeline
 04318            if (isIntelDx11OclSupported)
 4319            {
 04320                return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4321            }
 4322
 04323            return (null, null, null);
 4324        }
 4325
 4326        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvDx11VidFilter
 4327            EncodingJobInfo state,
 4328            EncodingOptions options,
 4329            string vidDecoder,
 4330            string vidEncoder)
 4331        {
 04332            var inW = state.VideoStream?.Width;
 04333            var inH = state.VideoStream?.Height;
 04334            var reqW = state.BaseRequest.Width;
 04335            var reqH = state.BaseRequest.Height;
 04336            var reqMaxW = state.BaseRequest.MaxWidth;
 04337            var reqMaxH = state.BaseRequest.MaxHeight;
 04338            var threeDFormat = state.MediaSource.Video3DFormat;
 4339
 04340            var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
 04341            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04342            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04343            var isHwDecoder = isD3d11vaDecoder || isQsvDecoder;
 04344            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04345            var isSwEncoder = !isQsvEncoder;
 04346            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04347            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4348
 04349            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04350            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04351            var doDeintH2645 = doDeintH264 || doDeintHevc;
 04352            var doVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04353            var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
 04354            var doTonemap = doVppTonemap || doOclTonemap;
 4355
 04356            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04357            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04358            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04359            var hasAssSubs = hasSubs
 04360                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04361                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04362            var subW = state.SubtitleStream?.Width;
 04363            var subH = state.SubtitleStream?.Height;
 4364
 04365            var rotation = state.VideoStream?.Rotation ?? 0;
 04366            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04367            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04368            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isD3d11vaDecoder || isQsvDecoder) && doVppTran
 04369            var swpInW = swapWAndH ? inH : inW;
 04370            var swpInH = swapWAndH ? inW : inH;
 4371
 4372            /* Make main filters for video stream */
 04373            var mainFilters = new List<string>();
 4374
 04375            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4376
 04377            if (isSwDecoder)
 4378            {
 4379                // INPUT sw surface(memory)
 4380                // sw deint
 04381                if (doDeintH2645)
 4382                {
 04383                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04384                    mainFilters.Add(swDeintFilter);
 4385                }
 4386
 04387                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04388                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04389                if (isMjpegEncoder && !doOclTonemap)
 4390                {
 4391                    // sw decoder + hw mjpeg encoder
 04392                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4393                }
 4394
 4395                // sw scale
 04396                mainFilters.Add(swScaleFilter);
 04397                mainFilters.Add($"format={outFormat}");
 4398
 4399                // keep video at memory except ocl tonemap,
 4400                // since the overhead caused by hwupload >>> using sw filter.
 4401                // sw => hw
 04402                if (doOclTonemap)
 4403                {
 04404                    mainFilters.Add("hwupload=derive_device=opencl");
 4405                }
 4406            }
 04407            else if (isD3d11vaDecoder || isQsvDecoder)
 4408            {
 04409                var isRext = IsVideoStreamHevcRext(state);
 04410                var twoPassVppTonemap = false;
 04411                var doVppFullRangeOut = isMjpegEncoder
 04412                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04413                var doVppScaleModeHq = isMjpegEncoder
 04414                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 04415                var doVppProcamp = false;
 04416                var procampParams = string.Empty;
 04417                var procampParamsString = string.Empty;
 04418                if (doVppTonemap)
 4419                {
 04420                    if (isRext)
 4421                    {
 4422                        // VPP tonemap requires p010 input
 04423                        twoPassVppTonemap = true;
 4424                    }
 4425
 04426                    if (options.VppTonemappingBrightness != 0
 04427                        && options.VppTonemappingBrightness >= -100
 04428                        && options.VppTonemappingBrightness <= 100)
 4429                    {
 04430                        procampParamsString += ":brightness={0}";
 04431                        twoPassVppTonemap = doVppProcamp = true;
 4432                    }
 4433
 04434                    if (options.VppTonemappingContrast > 1
 04435                        && options.VppTonemappingContrast <= 10)
 4436                    {
 04437                        procampParamsString += ":contrast={1}";
 04438                        twoPassVppTonemap = doVppProcamp = true;
 4439                    }
 4440
 04441                    if (doVppProcamp)
 4442                    {
 04443                        procampParamsString += ":procamp=1:async_depth=2";
 04444                        procampParams = string.Format(
 04445                            CultureInfo.InvariantCulture,
 04446                            procampParamsString,
 04447                            options.VppTonemappingBrightness,
 04448                            options.VppTonemappingContrast);
 4449                    }
 4450                }
 4451
 04452                var outFormat = doOclTonemap ? ((doVppTranspose || isRext) ? "p010" : string.Empty) : "nv12";
 04453                outFormat = twoPassVppTonemap ? "p010" : outFormat;
 4454
 04455                var swapOutputWandH = doVppTranspose && swapWAndH;
 04456                var hwScaleFilter = GetHwScaleFilter("vpp", "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, req
 4457
 4458                // d3d11va doesn't support dynamic pool size, use vpp filter ctx to relay
 4459                // to prevent encoder async and bframes from exhausting the decoder pool.
 04460                if (!string.IsNullOrEmpty(hwScaleFilter) && isD3d11vaDecoder)
 4461                {
 04462                    hwScaleFilter += ":passthrough=0";
 4463                }
 4464
 04465                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
 4466                {
 04467                    hwScaleFilter += $":transpose={transposeDir}";
 4468                }
 4469
 04470                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4471                {
 04472                    hwScaleFilter += (doVppFullRangeOut && !doOclTonemap) ? ":out_range=pc" : string.Empty;
 04473                    hwScaleFilter += doVppScaleModeHq ? ":scale_mode=hq" : string.Empty;
 4474                }
 4475
 04476                if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap)
 4477                {
 04478                    hwScaleFilter += doVppProcamp ? procampParams : (twoPassVppTonemap ? string.Empty : ":tonemap=1");
 4479                }
 4480
 04481                if (isD3d11vaDecoder)
 4482                {
 04483                    if (!string.IsNullOrEmpty(hwScaleFilter) || doDeintH2645)
 4484                    {
 4485                        // INPUT d3d11 surface(vram)
 4486                        // map from d3d11va to qsv.
 04487                        mainFilters.Add("hwmap=derive_device=qsv");
 4488                    }
 4489                }
 4490
 4491                // hw deint
 04492                if (doDeintH2645)
 4493                {
 04494                    var deintFilter = GetHwDeinterlaceFilter(state, options, "qsv");
 04495                    mainFilters.Add(deintFilter);
 4496                }
 4497
 4498                // hw transpose & scale & tonemap(w/o procamp)
 04499                mainFilters.Add(hwScaleFilter);
 4500
 4501                // hw tonemap(w/ procamp)
 04502                if (doVppTonemap && twoPassVppTonemap)
 4503                {
 04504                    mainFilters.Add("vpp_qsv=tonemap=1:format=nv12:async_depth=2");
 4505                }
 4506
 4507                // force bt709 just in case vpp tonemap is not triggered or using MSDK instead of VPL.
 04508                if (doVppTonemap)
 4509                {
 04510                    mainFilters.Add(GetOverwriteColorPropertiesParam(state, false));
 4511                }
 4512            }
 4513
 04514            if (doOclTonemap && isHwDecoder)
 4515            {
 4516                // map from qsv to opencl via qsv(d3d11)-opencl interop.
 04517                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4518            }
 4519
 4520            // hw tonemap
 04521            if (doOclTonemap)
 4522            {
 04523                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04524                mainFilters.Add(tonemapFilter);
 4525            }
 4526
 04527            var memoryOutput = false;
 04528            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04529            var isHwmapUsable = isSwEncoder && doOclTonemap;
 04530            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4531            {
 04532                memoryOutput = true;
 4533
 4534                // OUTPUT nv12 surface(memory)
 4535                // prefer hwmap to hwdownload on opencl.
 4536                // qsv hwmap is not fully implemented for the time being.
 04537                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04538                mainFilters.Add("format=nv12");
 4539            }
 4540
 4541            // OUTPUT nv12 surface(memory)
 04542            if (isSwDecoder && isQsvEncoder)
 4543            {
 04544                memoryOutput = true;
 4545            }
 4546
 04547            if (memoryOutput)
 4548            {
 4549                // text subtitles
 04550                if (hasTextSubs)
 4551                {
 04552                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04553                    mainFilters.Add(textSubtitlesFilter);
 4554                }
 4555            }
 4556
 04557            if (isQsvInQsvOut && doOclTonemap)
 4558            {
 4559                // OUTPUT qsv(nv12) surface(vram)
 4560                // reverse-mapping via qsv(d3d11)-opencl interop.
 04561                mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1");
 04562                mainFilters.Add("format=qsv");
 4563            }
 4564
 4565            /* Make sub and overlay filters for subtitle stream */
 04566            var subFilters = new List<string>();
 04567            var overlayFilters = new List<string>();
 04568            if (isQsvInQsvOut)
 4569            {
 04570                if (hasSubs)
 4571                {
 04572                    if (hasGraphicalSubs)
 4573                    {
 4574                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04575                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04576                        subFilters.Add(subPreProcFilters);
 04577                        subFilters.Add("format=bgra");
 4578                    }
 04579                    else if (hasTextSubs)
 4580                    {
 04581                        var framerate = state.VideoStream?.RealFrameRate;
 04582                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4583
 4584                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 04585                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04586                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04587                        subFilters.Add(alphaSrcFilter);
 04588                        subFilters.Add("format=bgra");
 04589                        subFilters.Add(subTextSubtitlesFilter);
 4590                    }
 4591
 4592                    // qsv requires a fixed pool size.
 4593                    // default to 64 otherwise it will fail on certain iGPU.
 04594                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4595
 04596                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04597                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04598                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04599                        : string.Empty;
 04600                    var overlayQsvFilter = string.Format(
 04601                        CultureInfo.InvariantCulture,
 04602                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04603                        overlaySize);
 04604                    overlayFilters.Add(overlayQsvFilter);
 4605                }
 4606            }
 04607            else if (memoryOutput)
 4608            {
 04609                if (hasGraphicalSubs)
 4610                {
 04611                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04612                    subFilters.Add(subPreProcFilters);
 04613                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4614                }
 4615            }
 4616
 04617            return (mainFilters, subFilters, overlayFilters);
 4618        }
 4619
 4620        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvVaapiVidFilte
 4621            EncodingJobInfo state,
 4622            EncodingOptions options,
 4623            string vidDecoder,
 4624            string vidEncoder)
 4625        {
 04626            var inW = state.VideoStream?.Width;
 04627            var inH = state.VideoStream?.Height;
 04628            var reqW = state.BaseRequest.Width;
 04629            var reqH = state.BaseRequest.Height;
 04630            var reqMaxW = state.BaseRequest.MaxWidth;
 04631            var reqMaxH = state.BaseRequest.MaxHeight;
 04632            var threeDFormat = state.MediaSource.Video3DFormat;
 4633
 04634            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04635            var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04636            var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
 04637            var isHwDecoder = isVaapiDecoder || isQsvDecoder;
 04638            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04639            var isSwEncoder = !isQsvEncoder;
 04640            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04641            var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
 4642
 04643            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04644            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04645            var doVaVppTonemap = IsIntelVppTonemapAvailable(state, options);
 04646            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04647            var doTonemap = doVaVppTonemap || doOclTonemap;
 04648            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4649
 04650            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04651            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04652            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04653            var hasAssSubs = hasSubs
 04654                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04655                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04656            var subW = state.SubtitleStream?.Width;
 04657            var subH = state.SubtitleStream?.Height;
 4658
 04659            var rotation = state.VideoStream?.Rotation ?? 0;
 04660            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04661            var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04662            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isVaapiDecoder || isQsvDecoder) && doVppTransp
 04663            var swpInW = swapWAndH ? inH : inW;
 04664            var swpInH = swapWAndH ? inW : inH;
 4665
 4666            /* Make main filters for video stream */
 04667            var mainFilters = new List<string>();
 4668
 04669            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 4670
 04671            if (isSwDecoder)
 4672            {
 4673                // INPUT sw surface(memory)
 4674                // sw deint
 04675                if (doDeintH2645)
 4676                {
 04677                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 04678                    mainFilters.Add(swDeintFilter);
 4679                }
 4680
 04681                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 04682                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 04683                if (isMjpegEncoder && !doOclTonemap)
 4684                {
 4685                    // sw decoder + hw mjpeg encoder
 04686                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 4687                }
 4688
 4689                // sw scale
 04690                mainFilters.Add(swScaleFilter);
 04691                mainFilters.Add($"format={outFormat}");
 4692
 4693                // keep video at memory except ocl tonemap,
 4694                // since the overhead caused by hwupload >>> using sw filter.
 4695                // sw => hw
 04696                if (doOclTonemap)
 4697                {
 04698                    mainFilters.Add("hwupload=derive_device=opencl");
 4699                }
 4700            }
 04701            else if (isVaapiDecoder || isQsvDecoder)
 4702            {
 04703                var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv";
 04704                var isRext = IsVideoStreamHevcRext(state);
 04705                var doVppFullRangeOut = isMjpegEncoder
 04706                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
 04707                var doVppScaleModeHq = isMjpegEncoder
 04708                    && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
 4709
 4710                // INPUT vaapi/qsv surface(vram)
 4711                // hw deint
 04712                if (doDeintH2645)
 4713                {
 04714                    var deintFilter = GetHwDeinterlaceFilter(state, options, hwFilterSuffix);
 04715                    mainFilters.Add(deintFilter);
 4716                }
 4717
 4718                // hw transpose(vaapi vpp)
 04719                if (isVaapiDecoder && doVppTranspose)
 4720                {
 04721                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 4722                }
 4723
 04724                var outFormat = doTonemap ? (((isQsvDecoder && doVppTranspose) || isRext) ? "p010" : string.Empty) : "nv
 04725                var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH;
 04726                var hwScalePrefix = isQsvDecoder ? "vpp" : "scale";
 04727                var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, 
 4728
 04729                if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose)
 4730                {
 04731                    hwScaleFilter += $":transpose={transposeDir}";
 4732                }
 4733
 04734                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 4735                {
 04736                    hwScaleFilter += ((isQsvDecoder && !doVppFullRangeOut) || doOclTonemap) ? string.Empty : ":out_range
 04737                    hwScaleFilter += isQsvDecoder ? (doVppScaleModeHq ? ":scale_mode=hq" : string.Empty) : ":mode=hq";
 4738                }
 4739
 4740                // allocate extra pool sizes for vaapi vpp scale
 04741                if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
 4742                {
 04743                    hwScaleFilter += ":extra_hw_frames=24";
 4744                }
 4745
 4746                // hw transpose(qsv vpp) & scale
 04747                mainFilters.Add(hwScaleFilter);
 4748            }
 4749
 4750            // vaapi vpp tonemap
 04751            if (doVaVppTonemap && isHwDecoder)
 4752            {
 04753                if (isQsvDecoder)
 4754                {
 4755                    // map from qsv to vaapi.
 04756                    mainFilters.Add("hwmap=derive_device=vaapi");
 04757                    mainFilters.Add("format=vaapi");
 4758                }
 4759
 04760                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 04761                mainFilters.Add(tonemapFilter);
 4762
 04763                if (isQsvDecoder)
 4764                {
 4765                    // map from vaapi to qsv.
 04766                    mainFilters.Add("hwmap=derive_device=qsv");
 04767                    mainFilters.Add("format=qsv");
 4768                }
 4769            }
 4770
 04771            if (doOclTonemap && isHwDecoder)
 4772            {
 4773                // map from qsv to opencl via qsv(vaapi)-opencl interop.
 04774                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 4775            }
 4776
 4777            // ocl tonemap
 04778            if (doOclTonemap)
 4779            {
 04780                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 04781                mainFilters.Add(tonemapFilter);
 4782            }
 4783
 04784            var memoryOutput = false;
 04785            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 04786            var isHwmapUsable = isSwEncoder && (doOclTonemap || isVaapiDecoder);
 04787            if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
 4788            {
 04789                memoryOutput = true;
 4790
 4791                // OUTPUT nv12 surface(memory)
 4792                // prefer hwmap to hwdownload on opencl/vaapi.
 4793                // qsv hwmap is not fully implemented for the time being.
 04794                mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
 04795                mainFilters.Add("format=nv12");
 4796            }
 4797
 4798            // OUTPUT nv12 surface(memory)
 04799            if (isSwDecoder && isQsvEncoder)
 4800            {
 04801                memoryOutput = true;
 4802            }
 4803
 04804            if (memoryOutput)
 4805            {
 4806                // text subtitles
 04807                if (hasTextSubs)
 4808                {
 04809                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 04810                    mainFilters.Add(textSubtitlesFilter);
 4811                }
 4812            }
 4813
 04814            if (isQsvInQsvOut)
 4815            {
 04816                if (doOclTonemap)
 4817                {
 4818                    // OUTPUT qsv(nv12) surface(vram)
 4819                    // reverse-mapping via qsv(vaapi)-opencl interop.
 4820                    // add extra pool size to avoid the 'cannot allocate memory' error on hevc_qsv.
 04821                    mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1:extra_hw_frames=16");
 04822                    mainFilters.Add("format=qsv");
 4823                }
 04824                else if (isVaapiDecoder)
 4825                {
 04826                    mainFilters.Add("hwmap=derive_device=qsv");
 04827                    mainFilters.Add("format=qsv");
 4828                }
 4829            }
 4830
 4831            /* Make sub and overlay filters for subtitle stream */
 04832            var subFilters = new List<string>();
 04833            var overlayFilters = new List<string>();
 04834            if (isQsvInQsvOut)
 4835            {
 04836                if (hasSubs)
 4837                {
 04838                    if (hasGraphicalSubs)
 4839                    {
 4840                        // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
 04841                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 04842                        subFilters.Add(subPreProcFilters);
 04843                        subFilters.Add("format=bgra");
 4844                    }
 04845                    else if (hasTextSubs)
 4846                    {
 04847                        var framerate = state.VideoStream?.RealFrameRate;
 04848                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 4849
 04850                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 04851                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 04852                        subFilters.Add(alphaSrcFilter);
 04853                        subFilters.Add("format=bgra");
 04854                        subFilters.Add(subTextSubtitlesFilter);
 4855                    }
 4856
 4857                    // qsv requires a fixed pool size.
 4858                    // default to 64 otherwise it will fail on certain iGPU.
 04859                    subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
 4860
 04861                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 04862                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 04863                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 04864                        : string.Empty;
 04865                    var overlayQsvFilter = string.Format(
 04866                        CultureInfo.InvariantCulture,
 04867                        "overlay_qsv=eof_action=pass:repeatlast=0{0}",
 04868                        overlaySize);
 04869                    overlayFilters.Add(overlayQsvFilter);
 4870                }
 4871            }
 04872            else if (memoryOutput)
 4873            {
 04874                if (hasGraphicalSubs)
 4875                {
 04876                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 04877                    subFilters.Add(subPreProcFilters);
 04878                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 4879                }
 4880            }
 4881
 04882            return (mainFilters, subFilters, overlayFilters);
 4883        }
 4884
 4885        /// <summary>
 4886        /// Gets the parameter of Intel/AMD VAAPI filter chain.
 4887        /// </summary>
 4888        /// <param name="state">Encoding state.</param>
 4889        /// <param name="options">Encoding options.</param>
 4890        /// <param name="vidEncoder">Video encoder to use.</param>
 4891        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 4892        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiVidFilterChain(
 4893            EncodingJobInfo state,
 4894            EncodingOptions options,
 4895            string vidEncoder)
 4896        {
 04897            if (options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 4898            {
 04899                return (null, null, null);
 4900            }
 4901
 04902            var isLinux = OperatingSystem.IsLinux();
 04903            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 04904            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04905            var isSwEncoder = !vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04906            var isVaapiFullSupported = isLinux && IsVaapiSupported(state) && IsVaapiFullSupported();
 04907            var isVaapiOclSupported = isVaapiFullSupported && IsOpenclFullSupported();
 04908            var isVaapiVkSupported = isVaapiFullSupported && IsVulkanFullSupported();
 4909
 4910            // legacy vaapi pipeline(copy-back)
 04911            if ((isSwDecoder && isSwEncoder)
 04912                || !isVaapiOclSupported
 04913                || !_mediaEncoder.SupportsFilter("alphasrc"))
 4914            {
 04915                var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
 4916
 04917                if (!isSwEncoder)
 4918                {
 04919                    var newfilters = new List<string>();
 04920                    var noOverlay = swFilterChain.OverlayFilters.Count == 0;
 04921                    newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
 04922                    newfilters.Add("hwupload=derive_device=vaapi");
 4923
 04924                    var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
 04925                    var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
 04926                    return (mainFilters, swFilterChain.SubFilters, overlayFilters);
 4927                }
 4928
 04929                return swFilterChain;
 4930            }
 4931
 4932            // preferred vaapi + opencl filters pipeline
 04933            if (_mediaEncoder.IsVaapiDeviceInteliHD)
 4934            {
 4935                // Intel iHD path, with extra vpp tonemap and overlay support.
 04936                return GetIntelVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4937            }
 4938
 4939            // preferred vaapi + vulkan filters pipeline
 04940            if (_mediaEncoder.IsVaapiDeviceAmd
 04941                && isVaapiVkSupported
 04942                && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
 04943                && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
 4944            {
 4945                // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support.
 04946                return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4947            }
 4948
 4949            // Intel i965 and Amd legacy driver path, only featuring scale and deinterlace support.
 04950            return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 4951        }
 4952
 4953        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelVaapiFullVidFilt
 4954            EncodingJobInfo state,
 4955            EncodingOptions options,
 4956            string vidDecoder,
 4957            string vidEncoder)
 4958        {
 04959            var inW = state.VideoStream?.Width;
 04960            var inH = state.VideoStream?.Height;
 04961            var reqW = state.BaseRequest.Width;
 04962            var reqH = state.BaseRequest.Height;
 04963            var reqMaxW = state.BaseRequest.MaxWidth;
 04964            var reqMaxH = state.BaseRequest.MaxHeight;
 04965            var threeDFormat = state.MediaSource.Video3DFormat;
 4966
 04967            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04968            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 04969            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 04970            var isSwEncoder = !isVaapiEncoder;
 04971            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 04972            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 4973
 04974            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 04975            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 04976            var doVaVppTonemap = isVaapiDecoder && IsIntelVppTonemapAvailable(state, options);
 04977            var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
 04978            var doTonemap = doVaVppTonemap || doOclTonemap;
 04979            var doDeintH2645 = doDeintH264 || doDeintHevc;
 4980
 04981            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 04982            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 04983            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 04984            var hasAssSubs = hasSubs
 04985                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 04986                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 04987            var subW = state.SubtitleStream?.Width;
 04988            var subH = state.SubtitleStream?.Height;
 4989
 04990            var rotation = state.VideoStream?.Rotation ?? 0;
 04991            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 04992            var doVaVppTranspose = !string.IsNullOrEmpty(transposeDir);
 04993            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVaVppTranspose));
 04994            var swpInW = swapWAndH ? inH : inW;
 04995            var swpInH = swapWAndH ? inW : inH;
 4996
 4997            /* Make main filters for video stream */
 04998            var mainFilters = new List<string>();
 4999
 05000            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
 5001
 05002            if (isSwDecoder)
 5003            {
 5004                // INPUT sw surface(memory)
 5005                // sw deint
 05006                if (doDeintH2645)
 5007                {
 05008                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05009                    mainFilters.Add(swDeintFilter);
 5010                }
 5011
 05012                var outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05013                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05014                if (isMjpegEncoder && !doOclTonemap)
 5015                {
 5016                    // sw decoder + hw mjpeg encoder
 05017                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5018                }
 5019
 5020                // sw scale
 05021                mainFilters.Add(swScaleFilter);
 05022                mainFilters.Add($"format={outFormat}");
 5023
 5024                // keep video at memory except ocl tonemap,
 5025                // since the overhead caused by hwupload >>> using sw filter.
 5026                // sw => hw
 05027                if (doOclTonemap)
 5028                {
 05029                    mainFilters.Add("hwupload=derive_device=opencl");
 5030                }
 5031            }
 05032            else if (isVaapiDecoder)
 5033            {
 05034                var isRext = IsVideoStreamHevcRext(state);
 5035
 5036                // INPUT vaapi surface(vram)
 5037                // hw deint
 05038                if (doDeintH2645)
 5039                {
 05040                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05041                    mainFilters.Add(deintFilter);
 5042                }
 5043
 5044                // hw transpose
 05045                if (doVaVppTranspose)
 5046                {
 05047                    mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
 5048                }
 5049
 05050                var outFormat = doTonemap ? (isRext ? "p010" : string.Empty) : "nv12";
 05051                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, swpInW, swpInH, reqW, reqH, req
 5052
 05053                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5054                {
 05055                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05056                    hwScaleFilter += ":mode=hq";
 5057                }
 5058
 5059                // allocate extra pool sizes for vaapi vpp
 05060                if (!string.IsNullOrEmpty(hwScaleFilter))
 5061                {
 05062                    hwScaleFilter += ":extra_hw_frames=24";
 5063                }
 5064
 5065                // hw scale
 05066                mainFilters.Add(hwScaleFilter);
 5067            }
 5068
 5069            // vaapi vpp tonemap
 05070            if (doVaVppTonemap && isVaapiDecoder)
 5071            {
 05072                var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
 05073                mainFilters.Add(tonemapFilter);
 5074            }
 5075
 05076            if (doOclTonemap && isVaapiDecoder)
 5077            {
 5078                // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05079                mainFilters.Add("hwmap=derive_device=opencl:mode=read");
 5080            }
 5081
 5082            // ocl tonemap
 05083            if (doOclTonemap)
 5084            {
 05085                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05086                mainFilters.Add(tonemapFilter);
 5087            }
 5088
 05089            if (doOclTonemap && isVaInVaOut)
 5090            {
 5091                // OUTPUT vaapi(nv12) surface(vram)
 5092                // reverse-mapping via vaapi-opencl interop.
 05093                mainFilters.Add("hwmap=derive_device=vaapi:mode=write:reverse=1");
 05094                mainFilters.Add("format=vaapi");
 5095            }
 5096
 05097            var memoryOutput = false;
 05098            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 05099            var isHwmapNotUsable = isUploadForOclTonemap && isVaapiEncoder;
 05100            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap)
 5101            {
 05102                memoryOutput = true;
 5103
 5104                // OUTPUT nv12 surface(memory)
 5105                // prefer hwmap to hwdownload on opencl/vaapi.
 05106                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap=mode=read");
 05107                mainFilters.Add("format=nv12");
 5108            }
 5109
 5110            // OUTPUT nv12 surface(memory)
 05111            if (isSwDecoder && isVaapiEncoder)
 5112            {
 05113                memoryOutput = true;
 5114            }
 5115
 05116            if (memoryOutput)
 5117            {
 5118                // text subtitles
 05119                if (hasTextSubs)
 5120                {
 05121                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05122                    mainFilters.Add(textSubtitlesFilter);
 5123                }
 5124            }
 5125
 05126            if (memoryOutput && isVaapiEncoder)
 5127            {
 05128                if (!hasGraphicalSubs)
 5129                {
 05130                    mainFilters.Add("hwupload_vaapi");
 5131                }
 5132            }
 5133
 5134            /* Make sub and overlay filters for subtitle stream */
 05135            var subFilters = new List<string>();
 05136            var overlayFilters = new List<string>();
 05137            if (isVaInVaOut)
 5138            {
 05139                if (hasSubs)
 5140                {
 05141                    if (hasGraphicalSubs)
 5142                    {
 5143                        // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead
 05144                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 05145                        subFilters.Add(subPreProcFilters);
 05146                        subFilters.Add("format=bgra");
 5147                    }
 05148                    else if (hasTextSubs)
 5149                    {
 05150                        var framerate = state.VideoStream?.RealFrameRate;
 05151                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5152
 05153                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFram
 05154                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05155                        subFilters.Add(alphaSrcFilter);
 05156                        subFilters.Add("format=bgra");
 05157                        subFilters.Add(subTextSubtitlesFilter);
 5158                    }
 5159
 05160                    subFilters.Add("hwupload=derive_device=vaapi");
 5161
 05162                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 05163                    var overlaySize = (overlayW.HasValue && overlayH.HasValue)
 05164                        ? $":w={overlayW.Value}:h={overlayH.Value}"
 05165                        : string.Empty;
 05166                    var overlayVaapiFilter = string.Format(
 05167                        CultureInfo.InvariantCulture,
 05168                        "overlay_vaapi=eof_action=pass:repeatlast=0{0}",
 05169                        overlaySize);
 05170                    overlayFilters.Add(overlayVaapiFilter);
 5171                }
 5172            }
 05173            else if (memoryOutput)
 5174            {
 05175                if (hasGraphicalSubs)
 5176                {
 05177                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05178                    subFilters.Add(subPreProcFilters);
 05179                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5180
 05181                    if (isVaapiEncoder)
 5182                    {
 05183                        overlayFilters.Add("hwupload_vaapi");
 5184                    }
 5185                }
 5186            }
 5187
 05188            return (mainFilters, subFilters, overlayFilters);
 5189        }
 5190
 5191        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdVaapiFullVidFilter
 5192            EncodingJobInfo state,
 5193            EncodingOptions options,
 5194            string vidDecoder,
 5195            string vidEncoder)
 5196        {
 05197            var inW = state.VideoStream?.Width;
 05198            var inH = state.VideoStream?.Height;
 05199            var reqW = state.BaseRequest.Width;
 05200            var reqH = state.BaseRequest.Height;
 05201            var reqMaxW = state.BaseRequest.MaxWidth;
 05202            var reqMaxH = state.BaseRequest.MaxHeight;
 05203            var threeDFormat = state.MediaSource.Video3DFormat;
 5204
 05205            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05206            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05207            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05208            var isSwEncoder = !isVaapiEncoder;
 05209            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5210
 05211            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05212            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05213            var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
 05214            var doDeintH2645 = doDeintH264 || doDeintHevc;
 5215
 05216            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05217            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05218            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05219            var hasAssSubs = hasSubs
 05220                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05221                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5222
 05223            var rotation = state.VideoStream?.Rotation ?? 0;
 05224            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05225            var doVkTranspose = isVaapiDecoder && !string.IsNullOrEmpty(transposeDir);
 05226            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVkTranspose));
 05227            var swpInW = swapWAndH ? inH : inW;
 05228            var swpInH = swapWAndH ? inW : inH;
 5229
 5230            /* Make main filters for video stream */
 05231            var mainFilters = new List<string>();
 5232
 05233            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doVkTonemap));
 5234
 05235            if (isSwDecoder)
 5236            {
 5237                // INPUT sw surface(memory)
 5238                // sw deint
 05239                if (doDeintH2645)
 5240                {
 05241                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05242                    mainFilters.Add(swDeintFilter);
 5243                }
 5244
 05245                if (doVkTonemap || hasSubs)
 5246                {
 5247                    // sw => hw
 05248                    mainFilters.Add("hwupload=derive_device=vulkan");
 05249                    mainFilters.Add("format=vulkan");
 5250                }
 5251                else
 5252                {
 5253                    // sw scale
 05254                    var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW,
 05255                    mainFilters.Add(swScaleFilter);
 05256                    mainFilters.Add("format=nv12");
 5257                }
 5258            }
 05259            else if (isVaapiDecoder)
 5260            {
 5261                // INPUT vaapi surface(vram)
 05262                if (doVkTranspose || doVkTonemap || hasSubs)
 5263                {
 5264                    // map from vaapi to vulkan/drm via interop (Polaris/gfx8+).
 05265                    if (_mediaEncoder.EncoderVersion >= _minFFmpegAlteredVaVkInterop)
 5266                    {
 05267                        if (doVkTranspose || !_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5268                        {
 5269                            // disable the indirect va-drm-vk mapping since it's no longer reliable.
 05270                            mainFilters.Add("hwmap=derive_device=drm");
 05271                            mainFilters.Add("format=drm_prime");
 05272                            mainFilters.Add("hwmap=derive_device=vulkan");
 05273                            mainFilters.Add("format=vulkan");
 5274
 5275                            // workaround for libplacebo using the imported vulkan frame on gfx8.
 05276                            if (!_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier)
 5277                            {
 05278                                mainFilters.Add("scale_vulkan");
 5279                            }
 5280                        }
 05281                        else if (doVkTonemap || hasSubs)
 5282                        {
 5283                            // non ad-hoc libplacebo also accepts drm_prime direct input.
 05284                            mainFilters.Add("hwmap=derive_device=drm");
 05285                            mainFilters.Add("format=drm_prime");
 5286                        }
 5287                    }
 5288                    else // legacy va-vk mapping that works only in jellyfin-ffmpeg6
 5289                    {
 05290                        mainFilters.Add("hwmap=derive_device=vulkan");
 05291                        mainFilters.Add("format=vulkan");
 5292                    }
 5293                }
 5294                else
 5295                {
 5296                    // hw deint
 05297                    if (doDeintH2645)
 5298                    {
 05299                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05300                        mainFilters.Add(deintFilter);
 5301                    }
 5302
 5303                    // hw scale
 05304                    var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", "nv12", false, inW, inH, reqW, reqH, reqMaxW,
 5305
 05306                    if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder && !doVkTonemap)
 5307                    {
 05308                        hwScaleFilter += ":out_range=pc:mode=hq";
 5309                    }
 5310
 05311                    mainFilters.Add(hwScaleFilter);
 5312                }
 5313            }
 5314
 5315            // vk transpose
 05316            if (doVkTranspose)
 5317            {
 05318                if (string.Equals(transposeDir, "reversal", StringComparison.OrdinalIgnoreCase))
 5319                {
 05320                    mainFilters.Add("flip_vulkan");
 5321                }
 5322                else
 5323                {
 05324                    mainFilters.Add($"transpose_vulkan=dir={transposeDir}");
 5325                }
 5326            }
 5327
 5328            // vk libplacebo
 05329            if (doVkTonemap || hasSubs)
 5330            {
 05331                var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, req
 05332                mainFilters.Add(libplaceboFilter);
 05333                mainFilters.Add("format=vulkan");
 5334            }
 5335
 05336            if (doVkTonemap && !hasSubs)
 5337            {
 5338                // OUTPUT vaapi(nv12) surface(vram)
 5339                // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05340                mainFilters.Add("hwmap=derive_device=vaapi");
 05341                mainFilters.Add("format=vaapi");
 5342
 5343                // clear the surf->meta_offset and output nv12
 05344                mainFilters.Add("scale_vaapi=format=nv12");
 5345
 5346                // hw deint
 05347                if (doDeintH2645)
 5348                {
 05349                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05350                    mainFilters.Add(deintFilter);
 5351                }
 5352            }
 5353
 05354            if (!hasSubs)
 5355            {
 5356                // OUTPUT nv12 surface(memory)
 05357                if (isSwEncoder && (doVkTonemap || isVaapiDecoder))
 5358                {
 05359                    mainFilters.Add("hwdownload");
 05360                    mainFilters.Add("format=nv12");
 5361                }
 5362
 05363                if (isSwDecoder && isVaapiEncoder && !doVkTonemap)
 5364                {
 05365                    mainFilters.Add("hwupload_vaapi");
 5366                }
 5367            }
 5368
 5369            /* Make sub and overlay filters for subtitle stream */
 05370            var subFilters = new List<string>();
 05371            var overlayFilters = new List<string>();
 05372            if (hasSubs)
 5373            {
 05374                if (hasGraphicalSubs)
 5375                {
 05376                    var subW = state.SubtitleStream?.Width;
 05377                    var subH = state.SubtitleStream?.Height;
 05378                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05379                    subFilters.Add(subPreProcFilters);
 05380                    subFilters.Add("format=bgra");
 5381                }
 05382                else if (hasTextSubs)
 5383                {
 05384                    var framerate = state.VideoStream?.RealFrameRate;
 05385                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5386
 05387                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05388                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05389                    subFilters.Add(alphaSrcFilter);
 05390                    subFilters.Add("format=bgra");
 05391                    subFilters.Add(subTextSubtitlesFilter);
 5392                }
 5393
 05394                subFilters.Add("hwupload=derive_device=vulkan");
 05395                subFilters.Add("format=vulkan");
 5396
 05397                overlayFilters.Add("overlay_vulkan=eof_action=pass:repeatlast=0");
 5398
 05399                if (isSwEncoder)
 5400                {
 5401                    // OUTPUT nv12 surface(memory)
 05402                    overlayFilters.Add("scale_vulkan=format=nv12");
 05403                    overlayFilters.Add("hwdownload");
 05404                    overlayFilters.Add("format=nv12");
 5405                }
 05406                else if (isVaapiEncoder)
 5407                {
 5408                    // OUTPUT vaapi(nv12) surface(vram)
 5409                    // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
 05410                    overlayFilters.Add("hwmap=derive_device=vaapi");
 05411                    overlayFilters.Add("format=vaapi");
 5412
 5413                    // clear the surf->meta_offset and output nv12
 05414                    overlayFilters.Add("scale_vaapi=format=nv12");
 5415
 5416                    // hw deint
 05417                    if (doDeintH2645)
 5418                    {
 05419                        var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05420                        overlayFilters.Add(deintFilter);
 5421                    }
 5422                }
 5423            }
 5424
 05425            return (mainFilters, subFilters, overlayFilters);
 5426        }
 5427
 5428        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiLimitedVidFilter
 5429            EncodingJobInfo state,
 5430            EncodingOptions options,
 5431            string vidDecoder,
 5432            string vidEncoder)
 5433        {
 05434            var inW = state.VideoStream?.Width;
 05435            var inH = state.VideoStream?.Height;
 05436            var reqW = state.BaseRequest.Width;
 05437            var reqH = state.BaseRequest.Height;
 05438            var reqMaxW = state.BaseRequest.MaxWidth;
 05439            var reqMaxH = state.BaseRequest.MaxHeight;
 05440            var threeDFormat = state.MediaSource.Video3DFormat;
 5441
 05442            var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05443            var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
 05444            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05445            var isSwEncoder = !isVaapiEncoder;
 05446            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05447            var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
 05448            var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965;
 05449            var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd;
 5450
 05451            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05452            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05453            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05454            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5455
 05456            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05457            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05458            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 5459
 05460            var rotation = state.VideoStream?.Rotation ?? 0;
 05461            var swapWAndH = Math.Abs(rotation) == 90 && isSwDecoder;
 05462            var swpInW = swapWAndH ? inH : inW;
 05463            var swpInH = swapWAndH ? inW : inH;
 5464
 5465            /* Make main filters for video stream */
 05466            var mainFilters = new List<string>();
 5467
 05468            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5469
 05470            var outFormat = string.Empty;
 05471            if (isSwDecoder)
 5472            {
 5473                // INPUT sw surface(memory)
 5474                // sw deint
 05475                if (doDeintH2645)
 5476                {
 05477                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05478                    mainFilters.Add(swDeintFilter);
 5479                }
 5480
 05481                outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
 05482                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05483                if (isMjpegEncoder && !doOclTonemap)
 5484                {
 5485                    // sw decoder + hw mjpeg encoder
 05486                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5487                }
 5488
 5489                // sw scale
 05490                mainFilters.Add(swScaleFilter);
 05491                mainFilters.Add("format=" + outFormat);
 5492
 5493                // keep video at memory except ocl tonemap,
 5494                // since the overhead caused by hwupload >>> using sw filter.
 5495                // sw => hw
 05496                if (doOclTonemap)
 5497                {
 05498                    mainFilters.Add("hwupload=derive_device=opencl");
 5499                }
 5500            }
 05501            else if (isVaapiDecoder)
 5502            {
 5503                // INPUT vaapi surface(vram)
 5504                // hw deint
 05505                if (doDeintH2645)
 5506                {
 05507                    var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
 05508                    mainFilters.Add(deintFilter);
 5509                }
 5510
 05511                outFormat = doOclTonemap ? string.Empty : "nv12";
 05512                var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, inW, inH, reqW, reqH, reqMaxW, 
 5513
 05514                if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
 5515                {
 05516                    hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
 05517                    hwScaleFilter += ":mode=hq";
 5518                }
 5519
 5520                // allocate extra pool sizes for vaapi vpp
 05521                if (!string.IsNullOrEmpty(hwScaleFilter))
 5522                {
 05523                    hwScaleFilter += ":extra_hw_frames=24";
 5524                }
 5525
 5526                // hw scale
 05527                mainFilters.Add(hwScaleFilter);
 5528            }
 5529
 05530            if (doOclTonemap && isVaapiDecoder)
 5531            {
 05532                if (isi965Driver)
 5533                {
 5534                    // map from vaapi to opencl via vaapi-opencl interop(Intel only).
 05535                    mainFilters.Add("hwmap=derive_device=opencl");
 5536                }
 5537                else
 5538                {
 05539                    mainFilters.Add("hwdownload");
 05540                    mainFilters.Add("format=p010le");
 05541                    mainFilters.Add("hwupload=derive_device=opencl");
 5542                }
 5543            }
 5544
 5545            // ocl tonemap
 05546            if (doOclTonemap)
 5547            {
 05548                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 05549                mainFilters.Add(tonemapFilter);
 5550            }
 5551
 05552            if (doOclTonemap && isVaInVaOut)
 5553            {
 05554                if (isi965Driver)
 5555                {
 5556                    // OUTPUT vaapi(nv12) surface(vram)
 5557                    // reverse-mapping via vaapi-opencl interop.
 05558                    mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
 05559                    mainFilters.Add("format=vaapi");
 5560                }
 5561            }
 5562
 05563            var memoryOutput = false;
 05564            var isUploadForOclTonemap = doOclTonemap && (isSwDecoder || (isVaapiDecoder && !isi965Driver));
 05565            var isHwmapNotUsable = hasGraphicalSubs || isUploadForOclTonemap;
 05566            var isHwmapForSubs = hasSubs && isVaapiDecoder;
 05567            var isHwUnmapForTextSubs = hasTextSubs && isVaInVaOut && !isUploadForOclTonemap;
 05568            if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap || isHwmapForSubs)
 5569            {
 05570                memoryOutput = true;
 5571
 5572                // OUTPUT nv12 surface(memory)
 5573                // prefer hwmap to hwdownload on opencl/vaapi.
 05574                mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
 05575                mainFilters.Add("format=nv12");
 5576            }
 5577
 5578            // OUTPUT nv12 surface(memory)
 05579            if (isSwDecoder && isVaapiEncoder)
 5580            {
 05581                memoryOutput = true;
 5582            }
 5583
 05584            if (memoryOutput)
 5585            {
 5586                // text subtitles
 05587                if (hasTextSubs)
 5588                {
 05589                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 05590                    mainFilters.Add(textSubtitlesFilter);
 5591                }
 5592            }
 5593
 05594            if (isHwUnmapForTextSubs)
 5595            {
 05596                mainFilters.Add("hwmap");
 05597                mainFilters.Add("format=vaapi");
 5598            }
 05599            else if (memoryOutput && isVaapiEncoder)
 5600            {
 05601                if (!hasGraphicalSubs)
 5602                {
 05603                    mainFilters.Add("hwupload_vaapi");
 5604                }
 5605            }
 5606
 5607            /* Make sub and overlay filters for subtitle stream */
 05608            var subFilters = new List<string>();
 05609            var overlayFilters = new List<string>();
 05610            if (memoryOutput)
 5611            {
 05612                if (hasGraphicalSubs)
 5613                {
 05614                    var subW = state.SubtitleStream?.Width;
 05615                    var subH = state.SubtitleStream?.Height;
 05616                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05617                    subFilters.Add(subPreProcFilters);
 05618                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 5619
 05620                    if (isVaapiEncoder)
 5621                    {
 05622                        overlayFilters.Add("hwupload_vaapi");
 5623                    }
 5624                }
 5625            }
 5626
 05627            return (mainFilters, subFilters, overlayFilters);
 5628        }
 5629
 5630        /// <summary>
 5631        /// Gets the parameter of Apple VideoToolBox filter chain.
 5632        /// </summary>
 5633        /// <param name="state">Encoding state.</param>
 5634        /// <param name="options">Encoding options.</param>
 5635        /// <param name="vidEncoder">Video encoder to use.</param>
 5636        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5637        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFilterChain(
 5638            EncodingJobInfo state,
 5639            EncodingOptions options,
 5640            string vidEncoder)
 5641        {
 05642            if (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 5643            {
 05644                return (null, null, null);
 5645            }
 5646
 5647            // ReSharper disable once InconsistentNaming
 05648            var isMacOS = OperatingSystem.IsMacOS();
 05649            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05650            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05651            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05652            var isVtFullSupported = isMacOS && IsVideoToolboxFullSupported();
 5653
 5654            // legacy videotoolbox pipeline (disable hw filters)
 05655            if (!(isVtEncoder || isVtDecoder)
 05656                || !isVtFullSupported
 05657                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5658            {
 05659                return GetSwVidFilterChain(state, options, vidEncoder);
 5660            }
 5661
 5662            // preferred videotoolbox + metal filters pipeline
 05663            return GetAppleVidFiltersPreferred(state, options, vidDecoder, vidEncoder);
 5664        }
 5665
 5666        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFiltersPrefer
 5667            EncodingJobInfo state,
 5668            EncodingOptions options,
 5669            string vidDecoder,
 5670            string vidEncoder)
 5671        {
 05672            var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05673            var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
 05674            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 5675
 05676            var inW = state.VideoStream?.Width;
 05677            var inH = state.VideoStream?.Height;
 05678            var reqW = state.BaseRequest.Width;
 05679            var reqH = state.BaseRequest.Height;
 05680            var reqMaxW = state.BaseRequest.MaxWidth;
 05681            var reqMaxH = state.BaseRequest.MaxHeight;
 05682            var threeDFormat = state.MediaSource.Video3DFormat;
 5683
 05684            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05685            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05686            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05687            var doVtTonemap = IsVideoToolboxTonemapAvailable(state, options);
 05688            var doMetalTonemap = !doVtTonemap && IsHwTonemapAvailable(state, options);
 05689            var usingHwSurface = isVtDecoder && (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface);
 5690
 05691            var rotation = state.VideoStream?.Rotation ?? 0;
 05692            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05693            var doVtTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_vt");
 05694            var swapWAndH = Math.Abs(rotation) == 90 && doVtTranspose;
 05695            var swpInW = swapWAndH ? inH : inW;
 05696            var swpInH = swapWAndH ? inW : inH;
 5697
 05698            var scaleFormat = string.Empty;
 5699            // Use P010 for Metal tone mapping, otherwise force an 8bit output.
 05700            if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase))
 5701            {
 05702                if (doMetalTonemap)
 5703                {
 05704                    if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 5705                    {
 05706                        scaleFormat = "p010le";
 5707                    }
 5708                }
 5709                else
 5710                {
 05711                    scaleFormat = "nv12";
 5712                }
 5713            }
 5714
 05715            var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW,
 5716
 05717            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05718            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05719            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05720            var hasAssSubs = hasSubs
 05721                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05722                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 5723
 5724            /* Make main filters for video stream */
 05725            var mainFilters = new List<string>();
 5726
 5727            // hw deint
 05728            if (doDeintH2645)
 5729            {
 05730                var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
 05731                mainFilters.Add(deintFilter);
 5732            }
 5733
 5734            // hw transpose
 05735            if (doVtTranspose)
 5736            {
 05737                mainFilters.Add($"transpose_vt=dir={transposeDir}");
 5738            }
 5739
 05740            if (doVtTonemap)
 5741            {
 5742                const string VtTonemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709";
 5743
 5744                // scale_vt can handle scaling & tonemapping in one shot, just like vpp_qsv.
 05745                hwScaleFilter = string.IsNullOrEmpty(hwScaleFilter)
 05746                    ? "scale_vt=" + VtTonemapArgs
 05747                    : hwScaleFilter + ":" + VtTonemapArgs;
 5748            }
 5749
 5750            // hw scale & vt tonemap
 05751            mainFilters.Add(hwScaleFilter);
 5752
 5753            // Metal tonemap
 05754            if (doMetalTonemap)
 5755            {
 05756                var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12", isMjpegEncoder);
 05757                mainFilters.Add(tonemapFilter);
 5758            }
 5759
 5760            /* Make sub and overlay filters for subtitle stream */
 05761            var subFilters = new List<string>();
 05762            var overlayFilters = new List<string>();
 5763
 05764            if (hasSubs)
 5765            {
 05766                if (hasGraphicalSubs)
 5767                {
 05768                    var subW = state.SubtitleStream?.Width;
 05769                    var subH = state.SubtitleStream?.Height;
 05770                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 05771                    subFilters.Add(subPreProcFilters);
 05772                    subFilters.Add("format=bgra");
 5773                }
 05774                else if (hasTextSubs)
 5775                {
 05776                    var framerate = state.VideoStream?.RealFrameRate;
 05777                    var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 5778
 05779                    var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFrame
 05780                    var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 05781                    subFilters.Add(alphaSrcFilter);
 05782                    subFilters.Add("format=bgra");
 05783                    subFilters.Add(subTextSubtitlesFilter);
 5784                }
 5785
 05786                subFilters.Add("hwupload");
 05787                overlayFilters.Add("overlay_videotoolbox=eof_action=pass:repeatlast=0");
 5788            }
 5789
 05790            if (usingHwSurface)
 5791            {
 05792                if (!isVtEncoder)
 5793                {
 05794                    mainFilters.Add("hwdownload");
 05795                    mainFilters.Add("format=nv12");
 5796                }
 5797
 05798                return (mainFilters, subFilters, overlayFilters);
 5799            }
 5800
 5801            // For old jellyfin-ffmpeg that has broken hwsurface, add a hwupload
 05802            var needFiltering = mainFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05803                                subFilters.Any(f => !string.IsNullOrEmpty(f)) ||
 05804                                overlayFilters.Any(f => !string.IsNullOrEmpty(f));
 05805            if (needFiltering)
 5806            {
 5807                // INPUT videotoolbox/memory surface(vram/uma)
 5808                // this will pass-through automatically if in/out format matches.
 05809                mainFilters.Insert(0, "hwupload");
 05810                mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld");
 5811
 05812                if (!isVtEncoder)
 5813                {
 05814                    mainFilters.Add("hwdownload");
 05815                    mainFilters.Add("format=nv12");
 5816                }
 5817            }
 5818
 05819            return (mainFilters, subFilters, overlayFilters);
 5820        }
 5821
 5822        /// <summary>
 5823        /// Gets the parameter of Rockchip RKMPP/RKRGA filter chain.
 5824        /// </summary>
 5825        /// <param name="state">Encoding state.</param>
 5826        /// <param name="options">Encoding options.</param>
 5827        /// <param name="vidEncoder">Video encoder to use.</param>
 5828        /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
 5829        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFilterChain(
 5830            EncodingJobInfo state,
 5831            EncodingOptions options,
 5832            string vidEncoder)
 5833        {
 05834            if (options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 5835            {
 05836                return (null, null, null);
 5837            }
 5838
 05839            var isLinux = OperatingSystem.IsLinux();
 05840            var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
 05841            var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
 05842            var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05843            var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported();
 5844
 05845            if ((isSwDecoder && isSwEncoder)
 05846                || !isRkmppOclSupported
 05847                || !_mediaEncoder.SupportsFilter("alphasrc"))
 5848            {
 05849                return GetSwVidFilterChain(state, options, vidEncoder);
 5850            }
 5851
 5852            // preferred rkmpp + rkrga + opencl filters pipeline
 05853            if (isRkmppOclSupported)
 5854            {
 05855                return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
 5856            }
 5857
 05858            return (null, null, null);
 5859        }
 5860
 5861        public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFiltersPrefer
 5862            EncodingJobInfo state,
 5863            EncodingOptions options,
 5864            string vidDecoder,
 5865            string vidEncoder)
 5866        {
 05867            var inW = state.VideoStream?.Width;
 05868            var inH = state.VideoStream?.Height;
 05869            var reqW = state.BaseRequest.Width;
 05870            var reqH = state.BaseRequest.Height;
 05871            var reqMaxW = state.BaseRequest.MaxWidth;
 05872            var reqMaxH = state.BaseRequest.MaxHeight;
 05873            var threeDFormat = state.MediaSource.Video3DFormat;
 5874
 05875            var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05876            var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
 05877            var isSwDecoder = !isRkmppDecoder;
 05878            var isSwEncoder = !isRkmppEncoder;
 05879            var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
 05880            var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
 05881            var isEncoderSupportAfbc = isRkmppEncoder
 05882                && (vidEncoder.Contains("h264", StringComparison.OrdinalIgnoreCase)
 05883                    || vidEncoder.Contains("hevc", StringComparison.OrdinalIgnoreCase));
 5884
 05885            var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
 05886            var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 05887            var doDeintH2645 = doDeintH264 || doDeintHevc;
 05888            var doOclTonemap = IsHwTonemapAvailable(state, options);
 5889
 05890            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 05891            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 05892            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 05893            var hasAssSubs = hasSubs
 05894                && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
 05895                    || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
 05896            var subW = state.SubtitleStream?.Width;
 05897            var subH = state.SubtitleStream?.Height;
 5898
 05899            var rotation = state.VideoStream?.Rotation ?? 0;
 05900            var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
 05901            var doRkVppTranspose = !string.IsNullOrEmpty(transposeDir);
 05902            var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isRkmppDecoder && doRkVppTranspose));
 05903            var swpInW = swapWAndH ? inH : inW;
 05904            var swpInH = swapWAndH ? inW : inH;
 5905
 5906            /* Make main filters for video stream */
 05907            var mainFilters = new List<string>();
 5908
 05909            mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
 5910
 05911            if (isSwDecoder)
 5912            {
 5913                // INPUT sw surface(memory)
 5914                // sw deint
 05915                if (doDeintH2645)
 5916                {
 05917                    var swDeintFilter = GetSwDeinterlaceFilter(state, options);
 05918                    mainFilters.Add(swDeintFilter);
 5919                }
 5920
 05921                var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
 05922                var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, req
 05923                if (isMjpegEncoder && !doOclTonemap)
 5924                {
 5925                    // sw decoder + hw mjpeg encoder
 05926                    swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_r
 5927                }
 5928
 05929                if (!string.IsNullOrEmpty(swScaleFilter))
 5930                {
 05931                    swScaleFilter += ":flags=fast_bilinear";
 5932                }
 5933
 5934                // sw scale
 05935                mainFilters.Add(swScaleFilter);
 05936                mainFilters.Add($"format={outFormat}");
 5937
 5938                // keep video at memory except ocl tonemap,
 5939                // since the overhead caused by hwupload >>> using sw filter.
 5940                // sw => hw
 05941                if (doOclTonemap)
 5942                {
 05943                    mainFilters.Add("hwupload=derive_device=opencl");
 5944                }
 5945            }
 05946            else if (isRkmppDecoder)
 5947            {
 5948                // INPUT rkmpp/drm surface(gem/dma-heap)
 5949
 05950                var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
 05951                var swapOutputWandH = doRkVppTranspose && swapWAndH;
 05952                var outFormat = doOclTonemap ? "p010" : "nv12";
 05953                var hwScaleFilter = GetHwScaleFilter("vpp", "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, r
 05954                var doScaling = !string.IsNullOrEmpty(GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, sw
 5955
 05956                if (!hasSubs
 05957                     || doRkVppTranspose
 05958                     || !isFullAfbcPipeline
 05959                     || doScaling)
 5960                {
 05961                    var isScaleRatioSupported = IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 5962
 5963                    // RGA3 hardware only support (1/8 ~ 8) scaling in each blit operation,
 5964                    // but in Trickplay there's a case: (3840/320 == 12), enable 2pass for it
 05965                    if (doScaling && !isScaleRatioSupported)
 5966                    {
 5967                        // Vendor provided BSP kernel has an RGA driver bug that causes the output to be corrupted for P
 5968                        // Use NV15 instead of P010 to avoid the issue.
 5969                        // SDR inputs are using BGRA formats already which is not affected.
 05970                        var intermediateFormat = doOclTonemap ? "nv15" : (isMjpegEncoder ? "bgra" : outFormat);
 05971                        var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={intermediateFormat}:force_o
 05972                        mainFilters.Add(hwScaleFilterFirstPass);
 5973                    }
 5974
 5975                    // The RKMPP MJPEG encoder on some newer chip models no longer supports RGB input.
 5976                    // Use 2pass here to enable RGA output of full-range YUV in the 2nd pass.
 05977                    if (isMjpegEncoder && !doOclTonemap && ((doScaling && isScaleRatioSupported) || !doScaling))
 5978                    {
 05979                        var hwScaleFilterFirstPass = "vpp_rkrga=format=bgra:afbc=1";
 05980                        mainFilters.Add(hwScaleFilterFirstPass);
 5981                    }
 5982
 05983                    if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
 5984                    {
 05985                        hwScaleFilter += $":transpose={transposeDir}";
 5986                    }
 5987
 5988                    // try enabling AFBC to save DDR bandwidth
 05989                    if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline)
 5990                    {
 05991                        hwScaleFilter += ":afbc=1";
 5992                    }
 5993
 5994                    // hw transpose & scale
 05995                    mainFilters.Add(hwScaleFilter);
 5996                }
 5997            }
 5998
 05999            if (doOclTonemap && isRkmppDecoder)
 6000            {
 6001                // map from rkmpp/drm to opencl via drm-opencl interop.
 06002                mainFilters.Add("hwmap=derive_device=opencl");
 6003            }
 6004
 6005            // ocl tonemap
 06006            if (doOclTonemap)
 6007            {
 06008                var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
 06009                mainFilters.Add(tonemapFilter);
 6010            }
 6011
 06012            var memoryOutput = false;
 06013            var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
 06014            if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap)
 6015            {
 06016                memoryOutput = true;
 6017
 6018                // OUTPUT nv12 surface(memory)
 06019                mainFilters.Add("hwdownload");
 06020                mainFilters.Add("format=nv12");
 6021            }
 6022
 6023            // OUTPUT nv12 surface(memory)
 06024            if (isSwDecoder && isRkmppEncoder)
 6025            {
 06026                memoryOutput = true;
 6027            }
 6028
 06029            if (memoryOutput)
 6030            {
 6031                // text subtitles
 06032                if (hasTextSubs)
 6033                {
 06034                    var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
 06035                    mainFilters.Add(textSubtitlesFilter);
 6036                }
 6037            }
 6038
 06039            if (isDrmInDrmOut)
 6040            {
 06041                if (doOclTonemap)
 6042                {
 6043                    // OUTPUT drm(nv12) surface(gem/dma-heap)
 6044                    // reverse-mapping via drm-opencl interop.
 06045                    mainFilters.Add("hwmap=derive_device=rkmpp:reverse=1");
 06046                    mainFilters.Add("format=drm_prime");
 6047                }
 6048            }
 6049
 6050            /* Make sub and overlay filters for subtitle stream */
 06051            var subFilters = new List<string>();
 06052            var overlayFilters = new List<string>();
 06053            if (isDrmInDrmOut)
 6054            {
 06055                if (hasSubs)
 6056                {
 06057                    var subMaxH = 1080;
 06058                    if (hasGraphicalSubs)
 6059                    {
 06060                        var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH,
 06061                        subFilters.Add(subPreProcFilters);
 06062                        subFilters.Add("format=bgra");
 6063                    }
 06064                    else if (hasTextSubs)
 6065                    {
 06066                        var framerate = state.VideoStream?.RealFrameRate;
 06067                        var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
 6068
 6069                        // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
 06070                        var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, subMaxH, subF
 06071                        var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
 06072                        subFilters.Add(alphaSrcFilter);
 06073                        subFilters.Add("format=bgra");
 06074                        subFilters.Add(subTextSubtitlesFilter);
 6075                    }
 6076
 06077                    subFilters.Add("hwupload=derive_device=rkmpp");
 6078
 6079                    // offload 1080p+ subtitles swscale upscaling from CPU to RGA
 06080                    var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
 06081                    if (overlayW.HasValue && overlayH.HasValue && overlayH.Value > subMaxH)
 6082                    {
 06083                        subFilters.Add($"vpp_rkrga=w={overlayW.Value}:h={overlayH.Value}:format=bgra:afbc=1");
 6084                    }
 6085
 6086                    // try enabling AFBC to save DDR bandwidth
 06087                    var hwOverlayFilter = "overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12";
 06088                    if (isEncoderSupportAfbc)
 6089                    {
 06090                        hwOverlayFilter += ":afbc=1";
 6091                    }
 6092
 06093                    overlayFilters.Add(hwOverlayFilter);
 6094                }
 6095            }
 06096            else if (memoryOutput)
 6097            {
 06098                if (hasGraphicalSubs)
 6099                {
 06100                    var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, subW, subH, reqW, reqH, req
 06101                    subFilters.Add(subPreProcFilters);
 06102                    overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
 6103                }
 6104            }
 6105
 06106            return (mainFilters, subFilters, overlayFilters);
 6107        }
 6108
 6109        /// <summary>
 6110        /// Gets the parameter of video processing filters.
 6111        /// </summary>
 6112        /// <param name="state">Encoding state.</param>
 6113        /// <param name="options">Encoding options.</param>
 6114        /// <param name="outputVideoCodec">Video codec to use.</param>
 6115        /// <returns>The video processing filters parameter.</returns>
 6116        public string GetVideoProcessingFilterParam(
 6117            EncodingJobInfo state,
 6118            EncodingOptions options,
 6119            string outputVideoCodec)
 6120        {
 06121            var videoStream = state.VideoStream;
 06122            if (videoStream is null)
 6123            {
 06124                return string.Empty;
 6125            }
 6126
 06127            var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
 06128            var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
 06129            var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
 6130
 6131            List<string> mainFilters;
 6132            List<string> subFilters;
 6133            List<string> overlayFilters;
 6134
 06135            (mainFilters, subFilters, overlayFilters) = options.HardwareAccelerationType switch
 06136            {
 06137                HardwareAccelerationType.vaapi => GetVaapiVidFilterChain(state, options, outputVideoCodec),
 06138                HardwareAccelerationType.amf => GetAmdVidFilterChain(state, options, outputVideoCodec),
 06139                HardwareAccelerationType.qsv => GetIntelVidFilterChain(state, options, outputVideoCodec),
 06140                HardwareAccelerationType.nvenc => GetNvidiaVidFilterChain(state, options, outputVideoCodec),
 06141                HardwareAccelerationType.videotoolbox => GetAppleVidFilterChain(state, options, outputVideoCodec),
 06142                HardwareAccelerationType.rkmpp => GetRkmppVidFilterChain(state, options, outputVideoCodec),
 06143                _ => GetSwVidFilterChain(state, options, outputVideoCodec),
 06144            };
 6145
 06146            mainFilters?.RemoveAll(string.IsNullOrEmpty);
 06147            subFilters?.RemoveAll(string.IsNullOrEmpty);
 06148            overlayFilters?.RemoveAll(string.IsNullOrEmpty);
 6149
 06150            var framerate = GetFramerateParam(state);
 06151            if (framerate.HasValue)
 6152            {
 06153                mainFilters.Insert(0, string.Format(
 06154                    CultureInfo.InvariantCulture,
 06155                    "fps={0}",
 06156                    framerate.Value));
 6157            }
 6158
 06159            var mainStr = string.Empty;
 06160            if (mainFilters?.Count > 0)
 6161            {
 06162                mainStr = string.Format(
 06163                    CultureInfo.InvariantCulture,
 06164                    "{0}",
 06165                    string.Join(',', mainFilters));
 6166            }
 6167
 06168            if (overlayFilters?.Count == 0)
 6169            {
 6170                // -vf "scale..."
 06171                return string.IsNullOrEmpty(mainStr) ? string.Empty : " -vf \"" + mainStr + "\"";
 6172            }
 6173
 06174            if (overlayFilters?.Count > 0
 06175                && subFilters?.Count > 0
 06176                && state.SubtitleStream is not null)
 6177            {
 6178                // overlay graphical/text subtitles
 06179                var subStr = string.Format(
 06180                        CultureInfo.InvariantCulture,
 06181                        "{0}",
 06182                        string.Join(',', subFilters));
 6183
 06184                var overlayStr = string.Format(
 06185                        CultureInfo.InvariantCulture,
 06186                        "{0}",
 06187                        string.Join(',', overlayFilters));
 6188
 06189                var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
 06190                var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
 06191                var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
 6192
 06193                if (hasSubs)
 6194                {
 6195                    // -filter_complex "[0:s]scale=s[sub]..."
 06196                    var filterStr = string.IsNullOrEmpty(mainStr)
 06197                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]{5}\""
 06198                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6199
 06200                    if (hasTextSubs)
 6201                    {
 06202                        filterStr = string.IsNullOrEmpty(mainStr)
 06203                            ? " -filter_complex \"{4}[sub];[0:{2}][sub]{5}\""
 06204                            : " -filter_complex \"{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
 6205                    }
 6206
 06207                    return string.Format(
 06208                        CultureInfo.InvariantCulture,
 06209                        filterStr,
 06210                        mapPrefix,
 06211                        subtitleStreamIndex,
 06212                        videoStreamIndex,
 06213                        mainStr,
 06214                        subStr,
 06215                        overlayStr);
 6216                }
 6217            }
 6218
 06219            return string.Empty;
 6220        }
 6221
 6222        public string GetOverwriteColorPropertiesParam(EncodingJobInfo state, bool isTonemapAvailable)
 6223        {
 06224            if (isTonemapAvailable)
 6225            {
 06226                return GetInputHdrParam(state.VideoStream?.ColorTransfer);
 6227            }
 6228
 06229            return GetOutputSdrParam(null);
 6230        }
 6231
 6232        public string GetInputHdrParam(string colorTransfer)
 6233        {
 06234            if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
 6235            {
 6236                // HLG
 06237                return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
 6238            }
 6239
 6240            // HDR10
 06241            return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
 6242        }
 6243
 6244        public string GetOutputSdrParam(string tonemappingRange)
 6245        {
 6246            // SDR
 06247            if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
 6248            {
 06249                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
 6250            }
 6251
 06252            if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
 6253            {
 06254                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
 6255            }
 6256
 06257            return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
 6258        }
 6259
 6260        public static int GetVideoColorBitDepth(EncodingJobInfo state)
 6261        {
 06262            var videoStream = state.VideoStream;
 06263            if (videoStream is not null)
 6264            {
 06265                if (videoStream.BitDepth.HasValue)
 6266                {
 06267                    return videoStream.BitDepth.Value;
 6268                }
 6269
 06270                if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
 06271                    || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
 06272                    || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
 06273                    || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
 6274                {
 06275                    return 8;
 6276                }
 6277
 06278                if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
 06279                    || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
 06280                    || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
 6281                {
 06282                    return 10;
 6283                }
 6284
 06285                if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
 06286                    || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
 06287                    || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
 6288                {
 06289                    return 12;
 6290                }
 6291
 06292                return 8;
 6293            }
 6294
 06295            return 0;
 6296        }
 6297
 6298        /// <summary>
 6299        /// Gets the ffmpeg option string for the hardware accelerated video decoder.
 6300        /// </summary>
 6301        /// <param name="state">The encoding job info.</param>
 6302        /// <param name="options">The encoding options.</param>
 6303        /// <returns>The option string or null if none available.</returns>
 6304        protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
 6305        {
 06306            var videoStream = state.VideoStream;
 06307            var mediaSource = state.MediaSource;
 06308            if (videoStream is null || mediaSource is null)
 6309            {
 06310                return null;
 6311            }
 6312
 6313            // HWA decoders can handle both video files and video folders.
 06314            var videoType = state.VideoType;
 06315            if (videoType != VideoType.VideoFile
 06316                && videoType != VideoType.Iso
 06317                && videoType != VideoType.Dvd
 06318                && videoType != VideoType.BluRay)
 6319            {
 06320                return null;
 6321            }
 6322
 06323            if (IsCopyCodec(state.OutputVideoCodec))
 6324            {
 06325                return null;
 6326            }
 6327
 06328            var hardwareAccelerationType = options.HardwareAccelerationType;
 6329
 06330            if (!string.IsNullOrEmpty(videoStream.Codec) && hardwareAccelerationType != HardwareAccelerationType.none)
 6331            {
 06332                var bitDepth = GetVideoColorBitDepth(state);
 6333
 6334                // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support for most platforms
 06335                if (bitDepth == 10
 06336                    && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06337                         || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
 06338                         || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)
 06339                         || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
 6340                {
 6341                    // RKMPP has H.264 Hi10P decoder
 06342                    bool hasHardwareHi10P = hardwareAccelerationType == HardwareAccelerationType.rkmpp;
 6343
 6344                    // VideoToolbox on Apple Silicon has H.264 Hi10P mode enabled after macOS 14.6
 06345                    if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox)
 6346                    {
 06347                        var ver = Environment.OSVersion.Version;
 06348                        var arch = RuntimeInformation.OSArchitecture;
 06349                        if (arch.Equals(Architecture.Arm64) && ver >= new Version(14, 6))
 6350                        {
 06351                            hasHardwareHi10P = true;
 6352                        }
 6353                    }
 6354
 06355                    if (!hasHardwareHi10P
 06356                        && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6357                    {
 06358                        return null;
 6359                    }
 6360                }
 6361
 06362                var decoder = hardwareAccelerationType switch
 06363                {
 06364                    HardwareAccelerationType.vaapi => GetVaapiVidDecoder(state, options, videoStream, bitDepth),
 06365                    HardwareAccelerationType.amf => GetAmfVidDecoder(state, options, videoStream, bitDepth),
 06366                    HardwareAccelerationType.qsv => GetQsvHwVidDecoder(state, options, videoStream, bitDepth),
 06367                    HardwareAccelerationType.nvenc => GetNvdecVidDecoder(state, options, videoStream, bitDepth),
 06368                    HardwareAccelerationType.videotoolbox => GetVideotoolboxVidDecoder(state, options, videoStream, bitD
 06369                    HardwareAccelerationType.rkmpp => GetRkmppVidDecoder(state, options, videoStream, bitDepth),
 06370                    _ => string.Empty
 06371                };
 6372
 06373                if (!string.IsNullOrEmpty(decoder))
 6374                {
 06375                    return decoder;
 6376                }
 6377            }
 6378
 6379            // leave blank so ffmpeg will decide
 06380            return null;
 6381        }
 6382
 6383        /// <summary>
 6384        /// Gets a hw decoder name.
 6385        /// </summary>
 6386        /// <param name="options">Encoding options.</param>
 6387        /// <param name="decoderPrefix">Decoder prefix.</param>
 6388        /// <param name="decoderSuffix">Decoder suffix.</param>
 6389        /// <param name="videoCodec">Video codec to use.</param>
 6390        /// <param name="bitDepth">Video color bit depth.</param>
 6391        /// <returns>Hardware decoder name.</returns>
 6392        public string GetHwDecoderName(EncodingOptions options, string decoderPrefix, string decoderSuffix, string video
 6393        {
 06394            if (string.IsNullOrEmpty(decoderPrefix) || string.IsNullOrEmpty(decoderSuffix))
 6395            {
 06396                return null;
 6397            }
 6398
 06399            var decoderName = decoderPrefix + '_' + decoderSuffix;
 6400
 06401            var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains
 6402
 6403            // VideoToolbox decoders have built-in SW fallback
 06404            if (bitDepth == 10
 06405                && isCodecAvailable
 06406                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6407            {
 06408                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06409                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
 06410                    && !options.EnableDecodingColorDepth10Hevc)
 6411                {
 06412                    return null;
 6413                }
 6414
 06415                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06416                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06417                    && !options.EnableDecodingColorDepth10Vp9)
 6418                {
 06419                    return null;
 6420                }
 6421            }
 6422
 06423            if (string.Equals(decoderSuffix, "cuvid", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdec
 6424            {
 06425                return null;
 6426            }
 6427
 06428            if (string.Equals(decoderSuffix, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwD
 6429            {
 06430                return null;
 6431            }
 6432
 06433            if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase))
 6434            {
 06435                return null;
 6436            }
 6437
 06438            return isCodecAvailable ? (" -c:v " + decoderName) : null;
 6439        }
 6440
 6441        /// <summary>
 6442        /// Gets a hwaccel type to use as a hardware decoder depending on the system.
 6443        /// </summary>
 6444        /// <param name="state">Encoding state.</param>
 6445        /// <param name="options">Encoding options.</param>
 6446        /// <param name="videoCodec">Video codec to use.</param>
 6447        /// <param name="bitDepth">Video color bit depth.</param>
 6448        /// <param name="outputHwSurface">Specifies if output hw surface.</param>
 6449        /// <returns>Hardware accelerator type.</returns>
 6450        public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, int bitDepth, bo
 6451        {
 06452            var isWindows = OperatingSystem.IsWindows();
 06453            var isLinux = OperatingSystem.IsLinux();
 06454            var isMacOS = OperatingSystem.IsMacOS();
 06455            var isD3d11Supported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va");
 06456            var isVaapiSupported = isLinux && IsVaapiSupported(state);
 06457            var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
 06458            var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
 06459            var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
 06460            var isRkmppSupported = isLinux && IsRkmppFullSupported();
 06461            var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCas
 06462            var hardwareAccelerationType = options.HardwareAccelerationType;
 6463
 06464            var ffmpegVersion = _mediaEncoder.EncoderVersion;
 6465
 6466            // Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
 06467            var isAv1 = ffmpegVersion < _minFFmpegImplicitHwaccel
 06468                && string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
 6469
 6470            // Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
 06471            var profileMismatch = string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
 06472                && string.Equals(state.VideoStream?.Profile, "baseline", StringComparison.OrdinalIgnoreCase);
 6473
 6474            // Disable the extra internal copy in nvdec. We already handle it in filter chain.
 06475            var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput;
 6476
 6477            // Strip the display rotation side data from the transposed fmp4 output stream.
 06478            var stripRotationData = (state.VideoStream?.Rotation ?? 0) != 0
 06479                && ffmpegVersion >= _minFFmpegDisplayRotationOption;
 06480            var stripRotationDataArgs = stripRotationData ? " -display_rotation 0" : string.Empty;
 6481
 6482            // VideoToolbox decoders have built-in SW fallback
 06483            if (isCodecAvailable
 06484                && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
 6485            {
 06486                if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
 06487                    && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase))
 6488                {
 06489                    if (IsVideoStreamHevcRext(state))
 6490                    {
 06491                        if (bitDepth <= 10 && !options.EnableDecodingColorDepth10HevcRext)
 6492                        {
 06493                            return null;
 6494                        }
 6495
 06496                        if (bitDepth == 12 && !options.EnableDecodingColorDepth12HevcRext)
 6497                        {
 06498                            return null;
 6499                        }
 6500
 06501                        if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06502                            && !_mediaEncoder.IsVaapiDeviceInteliHD)
 6503                        {
 06504                            return null;
 6505                        }
 6506                    }
 06507                    else if (bitDepth == 10 && !options.EnableDecodingColorDepth10Hevc)
 6508                    {
 06509                        return null;
 6510                    }
 6511                }
 6512
 06513                if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
 06514                    && options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
 06515                    && bitDepth == 10
 06516                    && !options.EnableDecodingColorDepth10Vp9)
 6517                {
 06518                    return null;
 6519                }
 6520            }
 6521
 6522            // Intel qsv/d3d11va/vaapi
 06523            if (hardwareAccelerationType == HardwareAccelerationType.qsv)
 6524            {
 06525                if (options.PreferSystemNativeHwDecoder)
 6526                {
 06527                    if (isVaapiSupported && isCodecAvailable)
 6528                    {
 06529                        return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + st
 06530                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " 
 6531                    }
 6532
 06533                    if (isD3d11Supported && isCodecAvailable)
 6534                    {
 06535                        return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + 
 06536                            + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 
 6537                    }
 6538                }
 6539                else
 6540                {
 06541                    if (isQsvSupported && isCodecAvailable)
 6542                    {
 06543                        return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv -noautorotate" + stripR
 6544                    }
 6545                }
 6546            }
 6547
 6548            // Nvidia cuda
 06549            if (hardwareAccelerationType == HardwareAccelerationType.nvenc)
 6550            {
 06551                if (isCudaSupported && isCodecAvailable)
 6552                {
 06553                    if (options.EnableEnhancedNvdecDecoder)
 6554                    {
 6555                        // set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support.
 06556                        return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stri
 06557                            + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + 
 6558                    }
 6559
 6560                    // cuvid decoder doesn't have threading issue.
 06561                    return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stripRot
 6562                }
 6563            }
 6564
 6565            // Amd d3d11va
 06566            if (hardwareAccelerationType == HardwareAccelerationType.amf)
 6567            {
 06568                if (isD3d11Supported && isCodecAvailable)
 6569                {
 06570                    return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + stri
 06571                        + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 2" +
 6572                }
 6573            }
 6574
 6575            // Vaapi
 06576            if (hardwareAccelerationType == HardwareAccelerationType.vaapi
 06577                && isVaapiSupported
 06578                && isCodecAvailable)
 6579            {
 06580                return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + stripRotat
 06581                    + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1
 6582            }
 6583
 6584            // Apple videotoolbox
 06585            if (hardwareAccelerationType == HardwareAccelerationType.videotoolbox
 06586                && isVideotoolboxSupported
 06587                && isCodecAvailable)
 6588            {
 06589                return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string
 6590            }
 6591
 6592            // Rockchip rkmpp
 06593            if (hardwareAccelerationType == HardwareAccelerationType.rkmpp
 06594                && isRkmppSupported
 06595                && isCodecAvailable)
 6596            {
 06597                return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime -noautorotate" + stripR
 6598            }
 6599
 06600            return null;
 6601        }
 6602
 6603        public string GetQsvHwVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6604        {
 06605            var isWindows = OperatingSystem.IsWindows();
 06606            var isLinux = OperatingSystem.IsLinux();
 6607
 06608            if ((!isWindows && !isLinux)
 06609                || options.HardwareAccelerationType != HardwareAccelerationType.qsv)
 6610            {
 06611                return null;
 6612            }
 6613
 06614            var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
 06615            var isIntelDx11OclSupported = isWindows
 06616                && _mediaEncoder.SupportsHwaccel("d3d11va")
 06617                && isQsvOclSupported;
 06618            var isIntelVaapiOclSupported = isLinux
 06619                && IsVaapiSupported(state)
 06620                && isQsvOclSupported;
 06621            var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
 06622                && _mediaEncoder.SupportsFilter("alphasrc");
 6623
 06624            var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06625                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06626            var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 06627            var is8_10_12bitSwFormatsQsv = is8_10bitSwFormatsQsv
 06628                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06629                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06630                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06631                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06632                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06633                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06634                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6635            // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
 6636
 06637            if (is8bitSwFormatsQsv)
 6638            {
 06639                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 06640                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 6641                {
 06642                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6643                }
 6644
 06645                if (string.Equals(videoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase))
 6646                {
 06647                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6648                }
 6649
 06650                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 6651                {
 06652                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6653                }
 6654
 06655                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 6656                {
 06657                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6658                }
 6659            }
 6660
 06661            if (is8_10bitSwFormatsQsv)
 6662            {
 06663                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 6664                {
 06665                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6666                }
 6667
 06668                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 6669                {
 06670                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6671                }
 6672            }
 6673
 06674            if (is8_10_12bitSwFormatsQsv)
 6675            {
 06676                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 06677                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 6678                {
 06679                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6680                }
 6681            }
 6682
 06683            return null;
 6684        }
 6685
 6686        public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6687        {
 06688            if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
 06689                || options.HardwareAccelerationType != HardwareAccelerationType.nvenc)
 6690            {
 06691                return null;
 6692            }
 6693
 06694            var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
 06695            var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06696                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06697            var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06698            var is8_10_12bitSwFormatsNvdec = is8_10bitSwFormatsNvdec
 06699                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06700                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06701                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06702                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6703            // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
 6704
 06705            if (is8bitSwFormatsNvdec)
 6706            {
 06707                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06708                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6709                {
 06710                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264
 6711                }
 6712
 06713                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6714                {
 06715                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options,
 6716                }
 6717
 06718                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6719                {
 06720                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1",
 6721                }
 6722
 06723                if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6724                {
 06725                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpe
 6726                }
 6727
 06728                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6729                {
 06730                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8",
 6731                }
 6732            }
 6733
 06734            if (is8_10bitSwFormatsNvdec)
 6735            {
 06736                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6737                {
 06738                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9",
 6739                }
 6740
 06741                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6742                {
 06743                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1",
 6744                }
 6745            }
 6746
 06747            if (is8_10_12bitSwFormatsNvdec)
 6748            {
 06749                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06750                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6751                {
 06752                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc
 6753                }
 6754            }
 6755
 06756            return null;
 6757        }
 6758
 6759        public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitD
 6760        {
 06761            if (!OperatingSystem.IsWindows()
 06762                || options.HardwareAccelerationType != HardwareAccelerationType.amf)
 6763            {
 06764                return null;
 6765            }
 6766
 06767            var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
 06768                && IsOpenclFullSupported()
 06769                && _mediaEncoder.SupportsFilter("alphasrc");
 06770            var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCas
 06771                                     || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnor
 06772            var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, Stri
 6773
 06774            if (is8bitSwFormatsAmf)
 6775            {
 06776                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06777                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6778                {
 06779                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6780                }
 6781
 06782                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6783                {
 06784                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6785                }
 6786
 06787                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6788                {
 06789                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6790                }
 6791            }
 6792
 06793            if (is8_10bitSwFormatsAmf)
 6794            {
 06795                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06796                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6797                {
 06798                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6799                }
 6800
 06801                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6802                {
 06803                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6804                }
 6805
 06806                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6807                {
 06808                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6809                }
 6810            }
 6811
 06812            return null;
 6813        }
 6814
 6815        public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6816        {
 06817            if (!OperatingSystem.IsLinux()
 06818                || options.HardwareAccelerationType != HardwareAccelerationType.vaapi)
 6819            {
 06820                return null;
 6821            }
 6822
 06823            var hwSurface = IsVaapiSupported(state)
 06824                && IsVaapiFullSupported()
 06825                && IsOpenclFullSupported()
 06826                && _mediaEncoder.SupportsFilter("alphasrc");
 06827            var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06828                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06829            var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, 
 06830            var is8_10_12bitSwFormatsVaapi = is8_10bitSwFormatsVaapi
 06831                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06832                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06833                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06834                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06835                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06836                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06837                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 6838
 06839            if (is8bitSwFormatsVaapi)
 6840            {
 06841                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06842                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6843                {
 06844                    return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 6845                }
 6846
 06847                if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6848                {
 06849                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 6850                }
 6851
 06852                if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6853                {
 06854                    return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
 6855                }
 6856
 06857                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6858                {
 06859                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 6860                }
 6861            }
 6862
 06863            if (is8_10bitSwFormatsVaapi)
 6864            {
 06865                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6866                {
 06867                    return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 6868                }
 6869
 06870                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6871                {
 06872                    return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 6873                }
 6874            }
 6875
 06876            if (is8_10_12bitSwFormatsVaapi)
 6877            {
 06878                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06879                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6880                {
 06881                    return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 6882                }
 6883            }
 6884
 06885            return null;
 6886        }
 6887
 6888        public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream,
 6889        {
 06890            if (!OperatingSystem.IsMacOS()
 06891                || options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox)
 6892            {
 06893                return null;
 6894            }
 6895
 06896            var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase
 06897                                    || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnore
 06898            var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, String
 06899            var is8_10_12bitSwFormatsVt = is8_10bitSwFormatsVt
 06900                || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06901                || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06902                || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06903                || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06904                || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06905                || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
 06906                || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
 06907            var isAv1SupportedSwFormatsVt = is8_10bitSwFormatsVt || string.Equals("yuv420p12le", videoStream.PixelFormat
 6908
 6909            // The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1
 06910            bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupp
 6911
 06912            if (is8bitSwFormatsVt)
 6913            {
 06914                if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6915                {
 06916                    return GetHwaccelType(state, options, "vp8", bitDepth, useHwSurface);
 6917                }
 6918            }
 6919
 06920            if (is8_10bitSwFormatsVt)
 6921            {
 06922                if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06923                    || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6924                {
 06925                    return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface);
 6926                }
 6927
 06928                if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6929                {
 06930                    return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
 6931                }
 6932            }
 6933
 06934            if (is8_10_12bitSwFormatsVt)
 6935            {
 06936                if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06937                    || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
 6938                {
 06939                    return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
 6940                }
 6941
 06942                if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
 06943                    && isAv1SupportedSwFormatsVt
 06944                    && _mediaEncoder.IsVideoToolboxAv1DecodeAvailable)
 6945                {
 06946                    return GetHwaccelType(state, options, "av1", bitDepth, useHwSurface);
 6947                }
 6948            }
 6949
 06950            return null;
 6951        }
 6952
 6953        public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bi
 6954        {
 06955            var isLinux = OperatingSystem.IsLinux();
 6956
 06957            if (!isLinux
 06958                || options.HardwareAccelerationType != HardwareAccelerationType.rkmpp)
 6959            {
 06960                return null;
 6961            }
 6962
 06963            var inW = state.VideoStream?.Width;
 06964            var inH = state.VideoStream?.Height;
 06965            var reqW = state.BaseRequest.Width;
 06966            var reqH = state.BaseRequest.Height;
 06967            var reqMaxW = state.BaseRequest.MaxWidth;
 06968            var reqMaxH = state.BaseRequest.MaxHeight;
 6969
 6970            // rkrga RGA2e supports range from 1/16 to 16
 06971            if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f))
 6972            {
 06973                return null;
 6974            }
 6975
 06976            var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported();
 06977            var hwSurface = isRkmppOclSupported
 06978                && _mediaEncoder.SupportsFilter("alphasrc");
 6979
 6980            // rkrga RGA3 supports range from 1/8 to 8
 06981            var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
 6982
 6983            // TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool
 06984            var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreC
 06985                                       || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgn
 06986            var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIg
 06987            var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp;
 6988
 6989            // nv15 and nv20 are bit-stream only formats
 06990            if (is10bitSwFormatsRkmpp && !hwSurface)
 6991            {
 06992                return null;
 6993            }
 6994
 06995            if (is8bitSwFormatsRkmpp)
 6996            {
 06997                if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase))
 6998                {
 06999                    return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface);
 7000                }
 7001
 07002                if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
 7003                {
 07004                    return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
 7005                }
 7006
 07007                if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
 7008                {
 07009                    return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
 7010                }
 7011
 07012                if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
 7013                {
 07014                    return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
 7015                }
 7016            }
 7017
 07018            if (is8_10bitSwFormatsRkmpp)
 7019            {
 07020                if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
 07021                    || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
 7022                {
 07023                    var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
 07024                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7025                }
 7026
 07027                if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
 07028                    || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
 7029                {
 07030                    var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
 07031                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7032                }
 7033
 07034                if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
 7035                {
 07036                    var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
 07037                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7038                }
 7039
 07040                if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
 7041                {
 07042                    var accelType = GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
 07043                    return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Em
 7044                }
 7045            }
 7046
 07047            return null;
 7048        }
 7049
 7050        /// <summary>
 7051        /// Gets the number of threads.
 7052        /// </summary>
 7053        /// <param name="state">Encoding state.</param>
 7054        /// <param name="encodingOptions">Encoding options.</param>
 7055        /// <param name="outputVideoCodec">Video codec to use.</param>
 7056        /// <returns>Number of threads.</returns>
 7057#nullable enable
 7058        public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVide
 7059        {
 07060            var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
 7061
 07062            if (threads <= 0)
 7063            {
 7064                // Automatically set thread count
 07065                return 0;
 7066            }
 7067
 07068            return Math.Min(threads, Environment.ProcessorCount);
 7069        }
 7070
 7071#nullable disable
 7072        public void TryStreamCopy(EncodingJobInfo state)
 7073        {
 07074            if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
 7075            {
 07076                state.OutputVideoCodec = "copy";
 7077            }
 7078            else
 7079            {
 07080                var user = state.User;
 7081
 7082                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07083                if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
 7084                {
 07085                    state.OutputVideoCodec = "copy";
 7086                }
 7087            }
 7088
 07089            if (state.AudioStream is not null
 07090                && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs))
 7091            {
 07092                state.OutputAudioCodec = "copy";
 7093            }
 7094            else
 7095            {
 07096                var user = state.User;
 7097
 7098                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will
 07099                if (user is not null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
 7100                {
 07101                    state.OutputAudioCodec = "copy";
 7102                }
 7103            }
 07104        }
 7105
 7106        public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
 7107        {
 07108            var inputModifier = string.Empty;
 07109            var analyzeDurationArgument = string.Empty;
 7110
 7111            // Apply -analyzeduration as per the environment variable,
 7112            // otherwise ffmpeg will break on certain files due to default value is 0.
 07113            var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
 7114
 07115            if (state.MediaSource.AnalyzeDurationMs > 0)
 7116            {
 07117                analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToStr
 7118            }
 07119            else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
 7120            {
 07121                analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
 7122            }
 7123
 07124            if (!string.IsNullOrEmpty(analyzeDurationArgument))
 7125            {
 07126                inputModifier += " " + analyzeDurationArgument;
 7127            }
 7128
 07129            inputModifier = inputModifier.Trim();
 7130
 7131            // Apply -probesize if configured
 07132            var ffmpegProbeSize = _config.GetFFmpegProbeSize();
 7133
 07134            if (!string.IsNullOrEmpty(ffmpegProbeSize))
 7135            {
 07136                inputModifier += $" -probesize {ffmpegProbeSize}";
 7137            }
 7138
 07139            var userAgentParam = GetUserAgentParam(state);
 7140
 07141            if (!string.IsNullOrEmpty(userAgentParam))
 7142            {
 07143                inputModifier += " " + userAgentParam;
 7144            }
 7145
 07146            inputModifier = inputModifier.Trim();
 7147
 07148            var refererParam = GetRefererParam(state);
 7149
 07150            if (!string.IsNullOrEmpty(refererParam))
 7151            {
 07152                inputModifier += " " + refererParam;
 7153            }
 7154
 07155            inputModifier = inputModifier.Trim();
 7156
 07157            inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions, segmentContainer);
 07158            inputModifier = inputModifier.Trim();
 7159
 07160            if (state.InputProtocol == MediaProtocol.Rtsp)
 7161            {
 07162                inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
 7163            }
 7164
 07165            if (!string.IsNullOrEmpty(state.InputAudioSync))
 7166            {
 07167                inputModifier += " -async " + state.InputAudioSync;
 7168            }
 7169
 7170            // The -fps_mode option cannot be applied to input
 07171            if (!string.IsNullOrEmpty(state.InputVideoSync) && _mediaEncoder.EncoderVersion < new Version(5, 1))
 7172            {
 07173                inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion);
 7174            }
 7175
 07176            if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp)
 7177            {
 07178                inputModifier += " -re";
 7179            }
 07180            else if (encodingOptions.EnableSegmentDeletion
 07181                && state.VideoStream is not null
 07182                && state.TranscodingType == TranscodingJobType.Hls
 07183                && IsCopyCodec(state.OutputVideoCodec)
 07184                && _mediaEncoder.EncoderVersion >= _minFFmpegReadrateOption)
 7185            {
 7186                // Set an input read rate limit 10x for using SegmentDeletion with stream-copy
 7187                // to prevent ffmpeg from exiting prematurely (due to fast drive)
 07188                inputModifier += " -readrate 10";
 7189            }
 7190
 07191            var flags = new List<string>();
 07192            if (state.IgnoreInputDts)
 7193            {
 07194                flags.Add("+igndts");
 7195            }
 7196
 07197            if (state.IgnoreInputIndex)
 7198            {
 07199                flags.Add("+ignidx");
 7200            }
 7201
 07202            if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
 7203            {
 07204                flags.Add("+genpts");
 7205            }
 7206
 07207            if (state.DiscardCorruptFramesInput)
 7208            {
 07209                flags.Add("+discardcorrupt");
 7210            }
 7211
 07212            if (state.EnableFastSeekInput)
 7213            {
 07214                flags.Add("+fastseek");
 7215            }
 7216
 07217            if (flags.Count > 0)
 7218            {
 07219                inputModifier += " -fflags " + string.Join(string.Empty, flags);
 7220            }
 7221
 07222            if (state.IsVideoRequest)
 7223            {
 07224                if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && encodingOpt
 7225                {
 07226                    var inputFormat = GetInputFormat(state.InputContainer);
 07227                    if (!string.IsNullOrEmpty(inputFormat))
 7228                    {
 07229                        inputModifier += " -f " + inputFormat;
 7230                    }
 7231                }
 7232            }
 7233
 07234            if (state.MediaSource.RequiresLooping)
 7235            {
 07236                inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2";
 7237            }
 7238
 07239            return inputModifier;
 7240        }
 7241
 7242        public void AttachMediaSourceInfo(
 7243            EncodingJobInfo state,
 7244            EncodingOptions encodingOptions,
 7245            MediaSourceInfo mediaSource,
 7246            string requestedUrl)
 7247        {
 07248            ArgumentNullException.ThrowIfNull(state);
 7249
 07250            ArgumentNullException.ThrowIfNull(mediaSource);
 7251
 07252            var path = mediaSource.Path;
 07253            var protocol = mediaSource.Protocol;
 7254
 07255            if (!string.IsNullOrEmpty(mediaSource.EncoderPath) && mediaSource.EncoderProtocol.HasValue)
 7256            {
 07257                path = mediaSource.EncoderPath;
 07258                protocol = mediaSource.EncoderProtocol.Value;
 7259            }
 7260
 07261            state.MediaPath = path;
 07262            state.InputProtocol = protocol;
 07263            state.InputContainer = mediaSource.Container;
 07264            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07265            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 7266
 07267            state.IsoType = mediaSource.IsoType;
 7268
 07269            if (mediaSource.Timestamp.HasValue)
 7270            {
 07271                state.InputTimestamp = mediaSource.Timestamp.Value;
 7272            }
 7273
 07274            state.RunTimeTicks = mediaSource.RunTimeTicks;
 07275            state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 07276            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
 7277
 07278            if ((state.ReadInputAtNativeFramerate && !state.IsSegmentedLiveStream)
 07279                || (mediaSource.Protocol == MediaProtocol.File
 07280                && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)))
 7281            {
 07282                state.InputVideoSync = "-1";
 07283                state.InputAudioSync = "1";
 7284            }
 7285
 07286            if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase)
 07287                || string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase))
 7288            {
 7289                // Seeing some stuttering when transcoding wma to audio-only HLS
 07290                state.InputAudioSync = "1";
 7291            }
 7292
 07293            var mediaStreams = mediaSource.MediaStreams;
 7294
 07295            if (state.IsVideoRequest)
 7296            {
 07297                var videoRequest = state.BaseRequest;
 7298
 07299                if (string.IsNullOrEmpty(videoRequest.VideoCodec))
 7300                {
 07301                    if (string.IsNullOrEmpty(requestedUrl))
 7302                    {
 07303                        requestedUrl = "test." + videoRequest.Container;
 7304                    }
 7305
 07306                    videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
 7307                }
 7308
 07309                state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
 07310                state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Su
 07311                state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
 07312                state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
 7313
 07314                if (state.SubtitleStream is not null && !state.SubtitleStream.IsExternal)
 7315                {
 07316                    state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !
 7317                }
 7318
 07319                EnforceResolutionLimit(state);
 7320
 07321                NormalizeSubtitleEmbed(state);
 7322            }
 7323            else
 7324            {
 07325                state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
 7326            }
 7327
 07328            state.MediaSource = mediaSource;
 7329
 07330            var request = state.BaseRequest;
 07331            var supportedAudioCodecs = state.SupportedAudioCodecs;
 07332            if (request is not null && supportedAudioCodecs is not null && supportedAudioCodecs.Length > 0)
 7333            {
 07334                var supportedAudioCodecsList = supportedAudioCodecs.ToList();
 7335
 07336                ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
 7337
 07338                state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray();
 7339
 07340                request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(_mediaEncoder.CanEncodeToAudioCodec)
 07341                    ?? state.SupportedAudioCodecs.FirstOrDefault();
 7342            }
 7343
 07344            var supportedVideoCodecs = state.SupportedVideoCodecs;
 07345            if (request is not null && supportedVideoCodecs is not null && supportedVideoCodecs.Length > 0)
 7346            {
 07347                var supportedVideoCodecsList = supportedVideoCodecs.ToList();
 7348
 07349                ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions);
 7350
 07351                state.SupportedVideoCodecs = supportedVideoCodecsList.ToArray();
 7352
 07353                request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
 7354            }
 07355        }
 7356
 7357        private void ShiftAudioCodecsIfNeeded(List<string> audioCodecs, MediaStream audioStream)
 7358        {
 7359            // No need to shift if there is only one supported audio codec.
 07360            if (audioCodecs.Count < 2)
 7361            {
 07362                return;
 7363            }
 7364
 07365            var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
 07366            var shiftAudioCodecs = new List<string>();
 07367            if (inputChannels >= 6)
 7368            {
 7369                // DTS and TrueHD are not supported by HLS
 7370                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07371                shiftAudioCodecs.Add("dts");
 07372                shiftAudioCodecs.Add("truehd");
 7373            }
 7374            else
 7375            {
 7376                // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
 7377                // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding 
 07378                shiftAudioCodecs.Add("ac3");
 07379                shiftAudioCodecs.Add("eac3");
 7380            }
 7381
 07382            if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7383            {
 07384                return;
 7385            }
 7386
 07387            while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparison.OrdinalIgnoreCase))
 7388            {
 07389                var removed = audioCodecs[0];
 07390                audioCodecs.RemoveAt(0);
 07391                audioCodecs.Add(removed);
 7392            }
 07393        }
 7394
 7395        private void ShiftVideoCodecsIfNeeded(List<string> videoCodecs, EncodingOptions encodingOptions)
 7396        {
 7397            // No need to shift if there is only one supported video codec.
 07398            if (videoCodecs.Count < 2)
 7399            {
 07400                return;
 7401            }
 7402
 7403            // Shift codecs to the end of list if it's not allowed.
 07404            var shiftVideoCodecs = new List<string>();
 07405            if (!encodingOptions.AllowHevcEncoding)
 7406            {
 07407                shiftVideoCodecs.Add("hevc");
 07408                shiftVideoCodecs.Add("h265");
 7409            }
 7410
 07411            if (!encodingOptions.AllowAv1Encoding)
 7412            {
 07413                shiftVideoCodecs.Add("av1");
 7414            }
 7415
 07416            if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
 7417            {
 07418                return;
 7419            }
 7420
 07421            while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparison.OrdinalIgnoreCase))
 7422            {
 07423                var removed = videoCodecs[0];
 07424                videoCodecs.RemoveAt(0);
 07425                videoCodecs.Add(removed);
 7426            }
 07427        }
 7428
 7429        private void NormalizeSubtitleEmbed(EncodingJobInfo state)
 7430        {
 07431            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7432            {
 07433                return;
 7434            }
 7435
 7436            // This is tricky to remux in, after converting to dvdsub it's not positioned correctly
 7437            // Therefore, let's just burn it in
 07438            if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
 7439            {
 07440                state.SubtitleDeliveryMethod = SubtitleDeliveryMethod.Encode;
 7441            }
 07442        }
 7443
 7444        public string GetSubtitleEmbedArguments(EncodingJobInfo state)
 7445        {
 07446            if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
 7447            {
 07448                return string.Empty;
 7449            }
 7450
 07451            var format = state.SupportedSubtitleCodecs.FirstOrDefault();
 7452            string codec;
 7453
 07454            if (string.IsNullOrEmpty(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.Ordin
 7455            {
 07456                codec = "copy";
 7457            }
 7458            else
 7459            {
 07460                codec = format;
 7461            }
 7462
 07463            return " -codec:s:0 " + codec + " -disposition:s:0 default";
 7464        }
 7465
 7466        public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, Encoder
 7467        {
 7468            // Get the output codec name
 07469            var videoCodec = GetVideoEncoder(state, encodingOptions);
 7470
 07471            var format = string.Empty;
 07472            var keyFrame = string.Empty;
 07473            var outputPath = state.OutputFilePath;
 7474
 07475            if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase)
 07476                && state.BaseRequest.Context == EncodingContext.Streaming)
 7477            {
 7478                // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
 07479                format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov";
 7480            }
 7481
 07482            var threads = GetNumberOfThreads(state, encodingOptions, videoCodec);
 7483
 07484            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7485
 07486            return string.Format(
 07487                CultureInfo.InvariantCulture,
 07488                "{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
 07489                inputModifier,
 07490                GetInputArgument(state, encodingOptions, null),
 07491                keyFrame,
 07492                GetMapArgs(state),
 07493                GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset),
 07494                threads,
 07495                GetProgressiveVideoAudioArguments(state, encodingOptions),
 07496                GetSubtitleEmbedArguments(state),
 07497                format,
 07498                outputPath).Trim();
 7499        }
 7500
 7501        public string GetOutputFFlags(EncodingJobInfo state)
 7502        {
 07503            var flags = new List<string>();
 07504            if (state.GenPtsOutput)
 7505            {
 07506                flags.Add("+genpts");
 7507            }
 7508
 07509            if (flags.Count > 0)
 7510            {
 07511                return " -fflags " + string.Join(string.Empty, flags);
 7512            }
 7513
 07514            return string.Empty;
 7515        }
 7516
 7517        public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoC
 7518        {
 07519            var args = "-codec:v:0 " + videoCodec;
 7520
 07521            if (state.BaseRequest.EnableMpegtsM2TsMode)
 7522            {
 07523                args += " -mpegts_m2ts_mode 1";
 7524            }
 7525
 07526            if (IsCopyCodec(videoCodec))
 7527            {
 07528                if (state.VideoStream is not null
 07529                    && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
 07530                    && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
 7531                {
 07532                    string bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Video);
 07533                    if (!string.IsNullOrEmpty(bitStreamArgs))
 7534                    {
 07535                        args += " " + bitStreamArgs;
 7536                    }
 7537                }
 7538
 07539                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7540                {
 07541                    args += " -copyts -avoid_negative_ts disabled -start_at_zero";
 7542                }
 7543
 07544                if (!state.RunTimeTicks.HasValue)
 7545                {
 07546                    args += " -fflags +genpts";
 7547                }
 7548            }
 7549            else
 7550            {
 07551                var keyFrameArg = string.Format(
 07552                    CultureInfo.InvariantCulture,
 07553                    " -force_key_frames \"expr:gte(t,n_forced*{0})\"",
 07554                    5);
 7555
 07556                args += keyFrameArg;
 7557
 07558                var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream &&
 7559
 07560                var hasCopyTs = false;
 7561
 7562                // video processing filters.
 07563                var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
 7564
 07565                var negativeMapArgs = GetNegativeMapArgsByFilters(state, videoProcessParam);
 7566
 07567                args = negativeMapArgs + args + videoProcessParam;
 7568
 07569                hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
 7570
 07571                if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
 7572                {
 07573                    if (!hasCopyTs)
 7574                    {
 07575                        args += " -copyts";
 7576                    }
 7577
 07578                    args += " -avoid_negative_ts disabled";
 7579
 07580                    if (!(state.SubtitleStream is not null && state.SubtitleStream.IsExternal && !state.SubtitleStream.I
 7581                    {
 07582                        args += " -start_at_zero";
 7583                    }
 7584                }
 7585
 07586                var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
 7587
 07588                if (!string.IsNullOrEmpty(qualityParam))
 7589                {
 07590                    args += " " + qualityParam.Trim();
 7591                }
 7592            }
 7593
 07594            if (!string.IsNullOrEmpty(state.OutputVideoSync))
 7595            {
 07596                args += GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion);
 7597            }
 7598
 07599            args += GetOutputFFlags(state);
 7600
 07601            return args;
 7602        }
 7603
 7604        public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
 7605        {
 7606            // If the video doesn't have an audio stream, return a default.
 07607            if (state.AudioStream is null && state.VideoStream is not null)
 7608            {
 07609                return string.Empty;
 7610            }
 7611
 7612            // Get the output codec name
 07613            var codec = GetAudioEncoder(state);
 7614
 07615            var args = "-codec:a:0 " + codec;
 7616
 07617            if (IsCopyCodec(codec))
 7618            {
 07619                return args;
 7620            }
 7621
 07622            var channels = state.OutputAudioChannels;
 7623
 07624            var useDownMixAlgorithm = state.AudioStream is not null
 07625                                      && DownMixAlgorithmsHelper.AlgorithmFilterStrings.ContainsKey((encodingOptions.Dow
 7626
 07627            if (channels.HasValue && !useDownMixAlgorithm)
 7628            {
 07629                args += " -ac " + channels.Value;
 7630            }
 7631
 07632            var bitrate = state.OutputAudioBitrate;
 07633            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
 7634            {
 07635                var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2);
 07636                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7637                {
 07638                    args += vbrParam;
 7639                }
 7640                else
 7641                {
 07642                    args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
 7643                }
 7644            }
 7645
 07646            if (state.OutputAudioSampleRate.HasValue)
 7647            {
 07648                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
 7649            }
 7650
 07651            args += GetAudioFilterParam(state, encodingOptions);
 7652
 07653            return args;
 7654        }
 7655
 7656        public string GetProgressiveAudioFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string 
 7657        {
 07658            var audioTranscodeParams = new List<string>();
 7659
 07660            var bitrate = state.OutputAudioBitrate;
 07661            var channels = state.OutputAudioChannels;
 07662            var outputCodec = state.OutputAudioCodec;
 7663
 07664            if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
 7665            {
 07666                var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2);
 07667                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
 7668                {
 07669                    audioTranscodeParams.Add(vbrParam);
 7670                }
 7671                else
 7672                {
 07673                    audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
 7674                }
 7675            }
 7676
 07677            if (channels.HasValue)
 7678            {
 07679                audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)
 7680            }
 7681
 07682            if (!string.IsNullOrEmpty(outputCodec))
 7683            {
 07684                audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
 7685            }
 7686
 07687            if (GetAudioEncoder(state).StartsWith("pcm_", StringComparison.Ordinal))
 7688            {
 07689                audioTranscodeParams.Add(string.Concat("-f ", GetAudioEncoder(state).AsSpan(4)));
 07690                audioTranscodeParams.Add("-ar " + state.BaseRequest.AudioBitRate);
 7691            }
 7692
 07693            if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
 7694            {
 7695                // opus only supports specific sampling rates
 07696                var sampleRate = state.OutputAudioSampleRate;
 07697                if (sampleRate.HasValue)
 7698                {
 07699                    var sampleRateValue = sampleRate.Value switch
 07700                    {
 07701                        <= 8000 => 8000,
 07702                        <= 12000 => 12000,
 07703                        <= 16000 => 16000,
 07704                        <= 24000 => 24000,
 07705                        _ => 48000
 07706                    };
 7707
 07708                    audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
 7709                }
 7710            }
 7711
 7712            // Copy the movflags from GetProgressiveVideoFullCommandLine
 7713            // See #9248 and the associated PR for why this is needed
 07714            if (_mp4ContainerNames.Contains(state.OutputContainer))
 7715            {
 07716                audioTranscodeParams.Add("-movflags empty_moov+delay_moov");
 7717            }
 7718
 07719            var threads = GetNumberOfThreads(state, encodingOptions, null);
 7720
 07721            var inputModifier = GetInputModifier(state, encodingOptions, null);
 7722
 07723            return string.Format(
 07724                CultureInfo.InvariantCulture,
 07725                "{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"",
 07726                inputModifier,
 07727                GetInputArgument(state, encodingOptions, null),
 07728                threads,
 07729                " -vn",
 07730                string.Join(' ', audioTranscodeParams),
 07731                outputPath,
 07732                string.Empty,
 07733                string.Empty,
 07734                string.Empty).Trim();
 7735        }
 7736
 7737        public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
 7738        {
 07739            var index = 0;
 07740            var length = mediaStreams.Count;
 7741
 07742            for (var i = 0; i < length; i++)
 7743            {
 07744                var currentMediaStream = mediaStreams[i];
 07745                if (currentMediaStream == streamToFind)
 7746                {
 07747                    return index;
 7748                }
 7749
 07750                if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
 7751                {
 07752                    index++;
 7753                }
 7754            }
 7755
 07756            return -1;
 7757        }
 7758
 7759        public static bool IsCopyCodec(string codec)
 7760        {
 07761            return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
 7762        }
 7763
 7764        private static bool ShouldEncodeSubtitle(EncodingJobInfo state)
 7765        {
 07766            return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
 07767                   || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
 7768        }
 7769
 7770        public static string GetVideoSyncOption(string videoSync, Version encoderVersion)
 7771        {
 07772            if (string.IsNullOrEmpty(videoSync))
 7773            {
 07774                return string.Empty;
 7775            }
 7776
 07777            if (encoderVersion >= new Version(5, 1))
 7778            {
 07779                if (int.TryParse(videoSync, CultureInfo.InvariantCulture, out var vsync))
 7780                {
 07781                    return vsync switch
 07782                    {
 07783                        -1 => " -fps_mode auto",
 07784                        0 => " -fps_mode passthrough",
 07785                        1 => " -fps_mode cfr",
 07786                        2 => " -fps_mode vfr",
 07787                        _ => string.Empty
 07788                    };
 7789                }
 7790
 07791                return string.Empty;
 7792            }
 7793
 7794            // -vsync is deprecated in FFmpeg 5.1 and will be removed in the future.
 07795            return $" -vsync {videoSync}";
 7796        }
 7797    }
 7798}

Methods/Properties

.ctor(MediaBrowser.Common.Configuration.IApplicationPaths,MediaBrowser.Controller.MediaEncoding.IMediaEncoder,MediaBrowser.Controller.MediaEncoding.ISubtitleEncoder,Microsoft.Extensions.Configuration.IConfiguration,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.IO.IPathManager)
.cctor()
GetH264Encoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetH265Encoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetAv1Encoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetH26xOrAv1Encoder(System.String,System.String,MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetMjpegEncoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
IsVaapiSupported(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
IsVaapiFullSupported()
IsRkmppFullSupported()
IsOpenclFullSupported()
IsCudaFullSupported()
IsVulkanFullSupported()
IsVideoToolboxFullSupported()
IsSwTonemapAvailable(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
IsHwTonemapAvailable(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
IsVulkanHwTonemapAvailable(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
IsIntelVppTonemapAvailable(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
IsVideoToolboxTonemapAvailable(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
IsVideoStreamHevcRext(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetVideoEncoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetUserAgentParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetRefererParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetInputFormat(System.String)
GetDecoderFromCodec(System.String)
InferAudioCodec(System.String)
InferVideoCodec(System.String)
GetVideoProfileScore(System.String,System.String)
GetAudioEncoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetRkmppDeviceArgs(System.String)
GetVideoToolboxDeviceArgs(System.String)
GetCudaDeviceArgs(System.Int32,System.String)
GetVulkanDeviceArgs(System.Int32,System.String,System.String,System.String)
GetOpenclDeviceArgs(System.Int32,System.String,System.String,System.String)
GetD3d11vaDeviceArgs(System.Int32,System.String,System.String)
GetVaapiDeviceArgs(System.String,System.String,System.String,System.String,System.String,System.String)
GetDrmDeviceArgs(System.String,System.String)
GetQsvDeviceArgs(System.String,System.String)
GetFilterHwDeviceArgs(System.String)
GetGraphicalSubCanvasSize(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetInputVideoHwaccelArgs(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetInputArgument(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
IsH264(MediaBrowser.Model.Entities.MediaStream)
IsH265(MediaBrowser.Model.Entities.MediaStream)
IsAv1(MediaBrowser.Model.Entities.MediaStream)
IsAAC(MediaBrowser.Model.Entities.MediaStream)
IsDoviWithHdr10Bl(MediaBrowser.Model.Entities.MediaStream)
IsDovi(MediaBrowser.Model.Entities.MediaStream)
IsHdr10Plus(MediaBrowser.Model.Entities.MediaStream)
ShouldRemoveDynamicHdrMetadata(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
CanEncoderRemoveDynamicHdrMetadata(MediaBrowser.Controller.MediaEncoding.EncodingHelper/DynamicHdrMetadataRemovalPlan,MediaBrowser.Model.Entities.MediaStream)
IsDoviRemoved(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
IsHdr10PlusRemoved(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetBitStreamArgs(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Entities.MediaStreamType)
GetAudioBitStreamArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String,System.String)
GetSegmentFileExtension(System.String)
GetVideoBitrateParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String)
GetEncoderParam(System.Nullable`1<MediaBrowser.Model.Entities.EncoderPreset>,MediaBrowser.Model.Entities.EncoderPreset,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.Boolean)
NormalizeTranscodingLevel(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String)
GetTextSubtitlesFilter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.Boolean,System.Boolean)
GetFramerateParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetHlsVideoKeyFrameArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String,System.Int32,System.Boolean,System.Nullable`1<System.Int32>)
GetVideoQualityParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.EncoderPreset)
CanStreamCopyVideo(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Entities.MediaStream)
CanStreamCopyAudio(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Entities.MediaStream,System.Collections.Generic.IEnumerable`1<System.String>)
GetVideoBitrateParamValue(MediaBrowser.Controller.MediaEncoding.BaseEncodingJobOptions,MediaBrowser.Model.Entities.MediaStream,System.String)
GetMinBitrate(System.Int32,System.Int32)
GetVideoBitrateScaleFactor(System.String)
ScaleBitrate(System.Int32,System.String,System.String)
GetAudioBitrateParam(MediaBrowser.Controller.MediaEncoding.BaseEncodingJobOptions,MediaBrowser.Model.Entities.MediaStream,System.Nullable`1<System.Int32>)
GetAudioBitrateParam(System.Nullable`1<System.Int32>,System.String,MediaBrowser.Model.Entities.MediaStream,System.Nullable`1<System.Int32>)
GetAudioVbrModeParam(System.String,System.Int32,System.Int32)
GetAudioFilterParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetNumAudioChannelsParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Entities.MediaStream,System.String)
EnforceResolutionLimit(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetFastSeekCommandLineParameter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetMapArgs(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetNegativeMapArgsByFilters(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.String)
GetMediaStream(System.Collections.Generic.IEnumerable`1<MediaBrowser.Model.Entities.MediaStream>,System.Nullable`1<System.Int32>,MediaBrowser.Model.Entities.MediaStreamType,System.Boolean)
GetFixedOutputSize(System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>)
IsScaleRatioSupported(System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Double>)
GetHwScaleFilter(System.String,System.String,System.String,System.Boolean,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>)
GetGraphicalSubPreProcessFilters(System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>)
GetAlphaSrcFilter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Single>)
GetSwScaleFilter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<MediaBrowser.Model.Entities.Video3DFormat>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>)
GetFixedSwScaleFilter(System.Nullable`1<MediaBrowser.Model.Entities.Video3DFormat>,System.Int32,System.Int32)
GetSwDeinterlaceFilter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetHwDeinterlaceFilter(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetHwTonemapFilter(MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String,System.Boolean)
GetLibplaceboFilter(MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.Boolean,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Int32>,System.Boolean)
GetVideoTransposeDirection(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetSwVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetNvidiaVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetNvidiaVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetAmdVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetAmdDx11VidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetIntelVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetIntelQsvDx11VidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetIntelQsvVaapiVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetVaapiVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetIntelVaapiFullVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetAmdVaapiFullVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetVaapiLimitedVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetAppleVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetAppleVidFiltersPreferred(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetRkmppVidFilterChain(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetRkmppVidFiltersPrefered(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String)
GetVideoProcessingFilterParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
GetOverwriteColorPropertiesParam(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,System.Boolean)
GetInputHdrParam(System.String)
GetOutputSdrParam(System.String)
GetVideoColorBitDepth(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetHardwareVideoDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetHwDecoderName(MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.String,System.String,System.Int32)
GetHwaccelType(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,System.Int32,System.Boolean)
GetQsvHwVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetNvdecVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetAmfVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetVaapiVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetVideotoolboxVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetRkmppVidDecoder(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.MediaStream,System.Int32)
GetNumberOfThreads(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
TryStreamCopy(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetInputModifier(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
AttachMediaSourceInfo(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Dto.MediaSourceInfo,System.String)
ShiftAudioCodecsIfNeeded(System.Collections.Generic.List`1<System.String>,MediaBrowser.Model.Entities.MediaStream)
ShiftVideoCodecsIfNeeded(System.Collections.Generic.List`1<System.String>,MediaBrowser.Model.Configuration.EncodingOptions)
NormalizeSubtitleEmbed(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetSubtitleEmbedArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetProgressiveVideoFullCommandLine(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,MediaBrowser.Model.Entities.EncoderPreset)
GetOutputFFlags(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetProgressiveVideoArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String,MediaBrowser.Model.Entities.EncoderPreset)
GetProgressiveVideoAudioArguments(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions)
GetProgressiveAudioFullCommandLine(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo,MediaBrowser.Model.Configuration.EncodingOptions,System.String)
FindIndex(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Model.Entities.MediaStream>,MediaBrowser.Model.Entities.MediaStream)
IsCopyCodec(System.String)
ShouldEncodeSubtitle(MediaBrowser.Controller.MediaEncoding.EncodingJobInfo)
GetVideoSyncOption(System.String,System.Version)