public FFmpegComplexFilterBuilder WithHardwareAcceleration(HardwareAccelerationKind hardwareAccelerationKind)
 {
     _hardwareAccelerationKind = Some(hardwareAccelerationKind);
     return(this);
 }
        public Option <FFmpegComplexFilter> Build()
        {
            var complexFilter = new StringBuilder();

            var videoLabel = "0:V";
            var audioLabel = "0:a";

            HardwareAccelerationKind acceleration = _hardwareAccelerationKind.IfNone(HardwareAccelerationKind.None);
            bool isHardwareDecode = acceleration switch
            {
                HardwareAccelerationKind.Vaapi => _inputCodec != "mpeg4",
                HardwareAccelerationKind.Nvenc => true,
                HardwareAccelerationKind.Qsv => true,
                _ => false
            };

            _audioDuration.IfSome(
                audioDuration =>
            {
                complexFilter.Append($"[{audioLabel}]");
                complexFilter.Append($"apad=whole_dur={audioDuration.TotalMilliseconds}ms");
                audioLabel = "[a]";
                complexFilter.Append(audioLabel);
            });

            var filterQueue = new List <string>();

            bool usesHardwareFilters = acceleration != HardwareAccelerationKind.None && !isHardwareDecode &&
                                       (_deinterlace || _scaleToSize.IsSome);

            if (usesHardwareFilters)
            {
                filterQueue.Add("hwupload");
            }

            if (_deinterlace)
            {
                string filter = acceleration switch
                {
                    HardwareAccelerationKind.Qsv => "deinterlace_qsv",
                    HardwareAccelerationKind.Nvenc => "", // TODO: yadif_cuda support in docker
                    HardwareAccelerationKind.Vaapi => "deinterlace_vaapi",
                    _ => "yadif=1"
                };

                if (!string.IsNullOrWhiteSpace(filter))
                {
                    filterQueue.Add(filter);
                }
            }

            _scaleToSize.IfSome(
                size =>
            {
                string filter = acceleration switch
                {
                    HardwareAccelerationKind.Qsv => $"scale_qsv=w={size.Width}:h={size.Height}",
                    HardwareAccelerationKind.Nvenc => $"scale_npp={size.Width}:{size.Height}",
                    HardwareAccelerationKind.Vaapi => $"scale_vaapi=w={size.Width}:h={size.Height}",
                    _ => $"scale={size.Width}:{size.Height}:flags=fast_bilinear"
                };

                if (!string.IsNullOrWhiteSpace(filter))
                {
                    filterQueue.Add(filter);
                }
            });

            if (_scaleToSize.IsSome || _padToSize.IsSome)
            {
                if (acceleration != HardwareAccelerationKind.None && (isHardwareDecode || usesHardwareFilters))
                {
                    filterQueue.Add("hwdownload");
                    string format = acceleration switch
                    {
                        HardwareAccelerationKind.Vaapi => "format=nv12|vaapi",
                        _ => "format=nv12"
                    };
                    filterQueue.Add(format);
                }

                filterQueue.Add("setsar=1");
            }

            _padToSize.IfSome(size => filterQueue.Add($"pad={size.Width}:{size.Height}:(ow-iw)/2:(oh-ih)/2"));

            if ((_scaleToSize.IsSome || _padToSize.IsSome) && acceleration != HardwareAccelerationKind.None)
            {
                string upload = acceleration switch
                {
                    HardwareAccelerationKind.Qsv => "hwupload=extra_hw_frames=64",
                    _ => "hwupload"
                };
                filterQueue.Add(upload);
            }

            if (filterQueue.Any())
            {
                // TODO: any audio filter
                if (_audioDuration.IsSome)
                {
                    complexFilter.Append(';');
                }

                complexFilter.Append($"[{videoLabel}]");
                complexFilter.Append(string.Join(",", filterQueue));
                videoLabel = "[v]";
                complexFilter.Append(videoLabel);
            }

            var filterResult = complexFilter.ToString();

            return(string.IsNullOrWhiteSpace(filterResult)
                ? Option <FFmpegComplexFilter> .None
                : new FFmpegComplexFilter(filterResult, videoLabel, audioLabel));
        }
    }
}
    public Option <FFmpegComplexFilter> Build(
        bool videoOnly,
        int videoInput,
        int videoStreamIndex,
        int audioInput,
        Option <int> audioStreamIndex,
        bool isSong)
    {
        // since .Contains is used on pixel format, we need it to be not null
        _pixelFormat ??= string.Empty;

        var complexFilter = new StringBuilder();

        string videoLabel = $"{videoInput}:{(isSong ? "v" : videoStreamIndex.ToString())}";
        string audioLabel = audioStreamIndex.Match(index => $"{audioInput}:{index}", () => "0:a");

        HardwareAccelerationKind acceleration = _hardwareAccelerationKind.IfNone(HardwareAccelerationKind.None);

        bool isHardwareDecode = acceleration switch
        {
            HardwareAccelerationKind.Vaapi => !isSong && _inputCodec != "mpeg4" &&
            (_deinterlace == false || !_pixelFormat.Contains("p10le")),

            // we need an initial hwupload_cuda when only padding with these pixel formats
            HardwareAccelerationKind.Nvenc when _scaleToSize.IsNone&& _padToSize.IsSome =>
            !isSong && !_pixelFormat.Contains("p10le") && !_pixelFormat.Contains("444"),

            HardwareAccelerationKind.Nvenc => !isSong &&
            (string.IsNullOrWhiteSpace(_videoDecoder) ||
             _videoDecoder.Contains("cuvid")),
            HardwareAccelerationKind.Qsv => !isSong,
            HardwareAccelerationKind.VideoToolbox => false,
            _ => false
        };

        bool nvencDeinterlace = acceleration == HardwareAccelerationKind.Nvenc && _videoDecoder == "mpeg2_cuvid" &&
                                _deinterlace;

        // mpeg2_cuvid will handle deinterlace and is "not" a hardware decode
        if (nvencDeinterlace)
        {
            _deinterlace     = false;
            isHardwareDecode = false;
        }

        var    audioFilterQueue    = new List <string>();
        var    videoFilterQueue    = new List <string>();
        var    watermarkPreprocess = new List <string>();
        string watermarkOverlay    = string.Empty;

        if (_normalizeLoudness)
        {
            audioFilterQueue.Add("loudnorm=I=-16:TP=-1.5:LRA=11");
        }

        _audioDuration.IfSome(
            audioDuration =>
        {
            var durationString = audioDuration.TotalMilliseconds.ToString(NumberFormatInfo.InvariantInfo);
            audioFilterQueue.Add($"apad=whole_dur={durationString}ms");
        });

        bool usesHardwareFilters = acceleration != HardwareAccelerationKind.None &&
                                   acceleration != HardwareAccelerationKind.VideoToolbox &&
                                   !isHardwareDecode &&
                                   (_deinterlace || _scaleToSize.IsSome);

        if (isSong)
        {
            switch (acceleration)
            {
            case HardwareAccelerationKind.Qsv:
                videoFilterQueue.Add("format=nv12");
                break;

            case HardwareAccelerationKind.Vaapi:
                videoFilterQueue.Add("format=nv12|vaapi");
                break;

            default:
                videoFilterQueue.Add("format=yuv420p");
                break;
            }
        }

        switch (usesHardwareFilters || isSong, acceleration)
        {