/// <summary> /// Inits the image encoder filter. /// </summary> static void InitImageEncoderFilter() { string filter1 = IMAGE_FILES_DIALOG_FILTER; _imageEncoderFilterDefaultIndex = 3; filter1 += "|" + SVG_FILE_DIALOG_FILTER; // if JBIG2 encoder is available if (AvailableEncoders.IsEncoderAvailable("Jbig2")) { filter1 += "|" + JBIG2_FILE_DIALOG_FILTER; } // if PDF encoder is available if (AvailableEncoders.IsEncoderAvailable("Pdf")) { filter1 += "|" + PDF_FILE_DIALOG_FILTER; } // if JPEG encoder is available if (AvailableEncoders.IsEncoderAvailable("Jpeg2000")) { filter1 += "|" + JPEG2000_FILE_DIALOG_FILTER; } _imageEncoderFilter = filter1; }
/// <summary> /// Returns the encoder for the specified <paramref name="filename"/>. /// </summary> /// <param name="filename">The filename.</param> /// <param name="encoder">The encoder.</param> /// <returns> /// <b>True</b> if the encoder is created and initialized; otherwise, <b>false</b>. /// </returns> /// <exception cref="NotSupportedException">Thrown if encoder is not created.</exception> public bool GetEncoder(string filename, out EncoderBase encoder) { // create encoder encoder = AvailableEncoders.CreateEncoder(filename); // indicates whether the encoder can add images to the existing multipage image file bool canAddImagesToExistingFile = false; // if encoder can add images to the existing multipage image file if (CanAddImagesToExistingFile) { // if file exists if (File.Exists(filename)) { // specify that encoder can add images to the existing multipage image file canAddImagesToExistingFile = true; } } // if encoder is not found if (encoder == null) { throw new NotSupportedException(string.Format("Can not find encoder for '{0}'.", filename)); } // set encoder settings return(ShowEncoderSettingsDialogAndCheckOverwrite( filename, ref encoder, canAddImagesToExistingFile, true)); }
/// <summary> /// Inits the multipage image encoder with annotations filter. /// </summary> static void InitMultipageImageEncoderWithAnnotationsFilter() { string filter1 = TIFF_FILE_DIALOG_FILTER; _multipageImageEncoderWithAnnotationsFilterDefaultIndex = 0; // if PDF encoder is available if (AvailableEncoders.IsEncoderAvailable("Pdf")) { filter1 += "|" + PDF_FILE_DIALOG_FILTER; } _multipageImageEncoderWithAnnotationsFilter = filter1; }
/// <summary> /// Returns the encoder for the specified name (<paramref name="name"/>). /// </summary> /// <param name="name">The encoder name.</param> /// <param name="encoder">The encoder.</param> /// <returns> /// <b>True</b> if the encoder is created and initialized; otherwise, <b>false</b>. /// </returns> /// <exception cref="NotSupportedException">Thrown if encoder for <i>name</i> is not created.</exception> public bool GetEncoderByName(string name, out EncoderBase encoder) { // create encoder by name encoder = AvailableEncoders.CreateEncoderByName(name); // if encoder is not found if (encoder == null) { throw new NotSupportedException(string.Format("Can not find encoder for '{0}'.", name)); } // set encoder settings return(ShowEncoderSettingsDialogAndCheckOverwrite(name, ref encoder, false, false)); }
/// <summary> /// Initializes a new instance of the <see cref="PdfCompressionControl"/> class. /// </summary> public PdfCompressionControl() { InitializeComponent(); if (!AvailableEncoders.IsEncoderAvailable("Jbig2")) { compressionJbig2RadioButton.Enabled = false; } if (!AvailableEncoders.IsEncoderAvailable("Jpeg2000")) { compressionJpeg2000RadioButton.Enabled = false; } foreach (BinarizationMode mode in Enum.GetValues(typeof(BinarizationMode))) { binarizationModeComboBox.Items.Add(mode); } }
/// <summary> /// Inits the multipage image encoder filter. /// </summary> static void InitMultipageImageEncoderFilter() { string filter1 = "TIFF Files|*.tif;*.tiff"; _multipageImageEncoderFilterDefaultIndex = 0; // if JBIG2 encoder is available if (AvailableEncoders.IsEncoderAvailable("Jbig2")) { filter1 += "|" + JBIG2_FILE_DIALOG_FILTER; } // if PDF encoder is available if (AvailableEncoders.IsEncoderAvailable("Pdf")) { filter1 += "|" + PDF_FILE_DIALOG_FILTER; } filter1 += "|" + GIF_FILE_DIALOG_FILTER; _multipageImageEncoderFilter = filter1; }
/// <summary> /// Returns the multipage encoder for the specified <paramref name="filename"/>. /// </summary> /// <param name="filename">The filename.</param> /// <param name="multipageEncoder">The multipage encoder.</param> /// <returns> /// <b>True</b> if the multipage encoder is created and initialized; otherwise, <b>false</b>. /// </returns> public bool GetMultipageEncoder(string filename, out MultipageEncoderBase multipageEncoder) { // create encoder EncoderBase encoder = AvailableEncoders.CreateEncoder(filename); // indicates whether the encoder can add images to the existing multipage image file bool canAddImagesToExistingFile = false; // if encoder can add images to the existing multipage image file if (CanAddImagesToExistingFile) { // if file exists if (File.Exists(filename)) { // specify that encoder can add images to the existing multipage image file canAddImagesToExistingFile = true; } } // get the multipage encoder multipageEncoder = encoder as MultipageEncoderBase; // if encoder is not multipage if (multipageEncoder == null) { return(false); } // set encoder settings bool result = ShowEncoderSettingsDialogAndCheckOverwrite( filename, ref encoder, canAddImagesToExistingFile, multipageEncoder.CreateNewFile); multipageEncoder = encoder as MultipageEncoderBase; return(result); }
public FFVideoEncoder(AvailableEncoders Enc) { enc = Enc; }
public FFmpegPipeline Build(FFmpegState ffmpegState, FrameState desiredState) { var allVideoStreams = _videoInputFile.SelectMany(f => f.VideoStreams).ToList(); // -sc_threshold 0 is unsupported with mpeg2video _pipelineSteps.Add( allVideoStreams.All(s => s.Codec != VideoFormat.Mpeg2Video) && desiredState.VideoFormat != VideoFormat.Mpeg2Video ? new NoSceneDetectOutputOption(0) : new NoSceneDetectOutputOption(1_000_000_000)); if (ffmpegState.SaveReport) { _pipelineSteps.Add(new FFReportVariable(_reportsFolder, None)); } foreach (TimeSpan desiredStart in ffmpegState.Start.Filter(s => s > TimeSpan.Zero)) { var option = new StreamSeekInputOption(desiredStart); _audioInputFile.Iter(f => f.AddOption(option)); _videoInputFile.Iter(f => f.AddOption(option)); // need to seek text subtitle files if (_subtitleInputFile.Map(s => !s.IsImageBased).IfNone(false)) { _pipelineSteps.Add(new StreamSeekFilterOption(desiredStart)); } } foreach (TimeSpan desiredFinish in ffmpegState.Finish) { _pipelineSteps.Add(new TimeLimitOutputOption(desiredFinish)); } foreach (VideoStream videoStream in allVideoStreams) { bool hasOverlay = _watermarkInputFile.IsSome || _subtitleInputFile.Map(s => s.IsImageBased && !s.Copy).IfNone(false); Option <int> initialFrameRate = Option <int> .None; foreach (string frameRateString in videoStream.FrameRate) { if (int.TryParse(frameRateString, out int parsedFrameRate)) { initialFrameRate = parsedFrameRate; } } var currentState = new FrameState( false, // realtime false, // infinite loop videoStream.Codec, videoStream.PixelFormat, videoStream.FrameSize, videoStream.FrameSize, initialFrameRate, Option <int> .None, Option <int> .None, Option <int> .None, false); // deinterlace IEncoder encoder; if (IsDesiredVideoState(currentState, desiredState)) { encoder = new EncoderCopyVideo(); _pipelineSteps.Add(encoder); } else { Option <IPipelineStep> maybeAccel = AvailableHardwareAccelerationOptions.ForMode( ffmpegState.HardwareAccelerationMode, ffmpegState.VaapiDevice, _logger); if (maybeAccel.IsNone) { ffmpegState = ffmpegState with { // disable hw accel if we don't match anything HardwareAccelerationMode = HardwareAccelerationMode.None }; } foreach (IPipelineStep accel in maybeAccel) { currentState = accel.NextState(currentState); _pipelineSteps.Add(accel); } // nvenc requires yuv420p background with yuva420p overlay if (ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Nvenc && hasOverlay) { desiredState = desiredState with { PixelFormat = new PixelFormatYuv420P() }; } // qsv should stay nv12 if (ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Qsv && hasOverlay) { IPixelFormat pixelFormat = desiredState.PixelFormat.IfNone(new PixelFormatYuv420P()); desiredState = desiredState with { PixelFormat = new PixelFormatNv12(pixelFormat.Name) }; } foreach (string desiredVaapiDriver in ffmpegState.VaapiDriver) { IPipelineStep step = new LibvaDriverNameVariable(desiredVaapiDriver); currentState = step.NextState(currentState); _pipelineSteps.Add(step); } foreach (IDecoder decoder in AvailableDecoders.ForVideoFormat( ffmpegState, currentState, desiredState, _watermarkInputFile, _subtitleInputFile, _logger)) { foreach (VideoInputFile videoInputFile in _videoInputFile) { videoInputFile.AddOption(decoder); currentState = decoder.NextState(currentState); } } } if (_subtitleInputFile.Map(s => s.Copy) == Some(true)) { _pipelineSteps.Add(new EncoderCopySubtitle()); } if (videoStream.StillImage) { var option = new InfiniteLoopInputOption(ffmpegState.HardwareAccelerationMode); _videoInputFile.Iter(f => f.AddOption(option)); } if (!IsDesiredVideoState(currentState, desiredState)) { if (desiredState.Realtime) { var option = new RealtimeInputOption(); _audioInputFile.Iter(f => f.AddOption(option)); _videoInputFile.Iter(f => f.AddOption(option)); } if (desiredState.InfiniteLoop) { var option = new InfiniteLoopInputOption(ffmpegState.HardwareAccelerationMode); _audioInputFile.Iter(f => f.AddOption(option)); _videoInputFile.Iter(f => f.AddOption(option)); } foreach (int desiredFrameRate in desiredState.FrameRate) { if (currentState.FrameRate != desiredFrameRate) { IPipelineStep step = new FrameRateOutputOption(desiredFrameRate); currentState = step.NextState(currentState); _pipelineSteps.Add(step); } } foreach (int desiredTimeScale in desiredState.VideoTrackTimeScale) { if (currentState.VideoTrackTimeScale != desiredTimeScale) { IPipelineStep step = new VideoTrackTimescaleOutputOption(desiredTimeScale); currentState = step.NextState(currentState); _pipelineSteps.Add(step); } } foreach (int desiredBitrate in desiredState.VideoBitrate) { if (currentState.VideoBitrate != desiredBitrate) { IPipelineStep step = new VideoBitrateOutputOption(desiredBitrate); currentState = step.NextState(currentState); _pipelineSteps.Add(step); } } foreach (int desiredBufferSize in desiredState.VideoBufferSize) { if (currentState.VideoBufferSize != desiredBufferSize) { IPipelineStep step = new VideoBufferSizeOutputOption(desiredBufferSize); currentState = step.NextState(currentState); _pipelineSteps.Add(step); } } if (desiredState.Deinterlaced && !currentState.Deinterlaced) { IPipelineFilterStep step = AvailableDeinterlaceFilters.ForAcceleration( ffmpegState.HardwareAccelerationMode, currentState, desiredState, _watermarkInputFile, _subtitleInputFile); currentState = step.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(step)); } // TODO: this is a software-only flow, will need to be different for hardware accel if (ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.None) { if (currentState.ScaledSize != desiredState.ScaledSize || currentState.PaddedSize != desiredState.PaddedSize) { IPipelineFilterStep scaleStep = new ScaleFilter( currentState, desiredState.ScaledSize, desiredState.PaddedSize); currentState = scaleStep.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(scaleStep)); // TODO: padding might not be needed, can we optimize this out? IPipelineFilterStep padStep = new PadFilter(currentState, desiredState.PaddedSize); currentState = padStep.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(padStep)); IPipelineFilterStep sarStep = new SetSarFilter(); currentState = sarStep.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(sarStep)); } } else if (currentState.ScaledSize != desiredState.ScaledSize) { IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration( ffmpegState.HardwareAccelerationMode, currentState, desiredState.ScaledSize, desiredState.PaddedSize); currentState = scaleFilter.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(scaleFilter)); // TODO: padding might not be needed, can we optimize this out? if (currentState.PaddedSize != desiredState.PaddedSize) { IPipelineFilterStep padStep = new PadFilter(currentState, desiredState.PaddedSize); currentState = padStep.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(padStep)); } IPipelineFilterStep sarStep = new SetSarFilter(); currentState = sarStep.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(sarStep)); } else if (currentState.PaddedSize != desiredState.PaddedSize) { IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration( ffmpegState.HardwareAccelerationMode, currentState, desiredState.ScaledSize, desiredState.PaddedSize); currentState = scaleFilter.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(scaleFilter)); if (currentState.PaddedSize != desiredState.PaddedSize) { IPipelineFilterStep padStep = new PadFilter(currentState, desiredState.PaddedSize); currentState = padStep.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(padStep)); } IPipelineFilterStep sarStep = new SetSarFilter(); currentState = sarStep.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(sarStep)); } if (hasOverlay && currentState.PixelFormat.Map(pf => pf.FFmpegName) != desiredState.PixelFormat.Map(pf => pf.FFmpegName)) { // this should only happen with nvenc? // use scale filter to fix pixel format foreach (IPixelFormat pixelFormat in desiredState.PixelFormat) { if (currentState.FrameDataLocation == FrameDataLocation.Software) { IPipelineFilterStep formatFilter = new PixelFormatFilter(pixelFormat); currentState = formatFilter.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(formatFilter)); switch (ffmpegState.HardwareAccelerationMode) { case HardwareAccelerationMode.Nvenc: var uploadFilter = new HardwareUploadFilter(ffmpegState); currentState = uploadFilter.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(uploadFilter)); break; } } else { if (ffmpegState.HardwareAccelerationMode != HardwareAccelerationMode.Qsv) { // the filter re-applies the current pixel format, so we have to set it first currentState = currentState with { PixelFormat = desiredState.PixelFormat }; IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration( ffmpegState.HardwareAccelerationMode, currentState, desiredState.ScaledSize, desiredState.PaddedSize); currentState = scaleFilter.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(scaleFilter)); } } } } // nvenc custom logic if (ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Nvenc) { foreach (VideoInputFile videoInputFile in _videoInputFile) { // if we only deinterlace, we need to set pixel format again (using scale_cuda) bool onlyYadif = videoInputFile.FilterSteps.Count == 1 && videoInputFile.FilterSteps.Any(fs => fs is YadifCudaFilter); // if we have no filters and an overlay, we need to set pixel format bool unfilteredWithOverlay = videoInputFile.FilterSteps.Count == 0 && hasOverlay; if (onlyYadif || unfilteredWithOverlay) { // the filter re-applies the current pixel format, so we have to set it first currentState = currentState with { PixelFormat = desiredState.PixelFormat }; IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration( ffmpegState.HardwareAccelerationMode, currentState, desiredState.ScaledSize, desiredState.PaddedSize); currentState = scaleFilter.NextState(currentState); videoInputFile.FilterSteps.Add(scaleFilter); } } } if (ffmpegState.PtsOffset > 0) { foreach (int videoTrackTimeScale in desiredState.VideoTrackTimeScale) { IPipelineStep step = new OutputTsOffsetOption( ffmpegState.PtsOffset, videoTrackTimeScale); currentState = step.NextState(currentState); _pipelineSteps.Add(step); } } foreach (IPixelFormat desiredPixelFormat in desiredState.PixelFormat) { if (currentState.PixelFormat.Map(pf => pf.FFmpegName) != desiredPixelFormat.FFmpegName) { // qsv doesn't seem to like this if (ffmpegState.HardwareAccelerationMode != HardwareAccelerationMode.Qsv) { IPipelineStep step = new PixelFormatOutputOption(desiredPixelFormat); currentState = step.NextState(currentState); _pipelineSteps.Add(step); } } } } // TODO: if all video filters are software, use software pixel format for hwaccel output // might be able to skip scale_cuda=format=whatever,hwdownload,format=whatever foreach (AudioInputFile audioInputFile in _audioInputFile) { // always need to specify audio codec so ffmpeg doesn't default to a codec we don't want foreach (IEncoder step in AvailableEncoders.ForAudioFormat(audioInputFile.DesiredState, _logger)) { currentState = step.NextState(currentState); _pipelineSteps.Add(step); } foreach (int desiredAudioChannels in audioInputFile.DesiredState.AudioChannels) { _pipelineSteps.Add(new AudioChannelsOutputOption(desiredAudioChannels)); } foreach (int desiredBitrate in audioInputFile.DesiredState.AudioBitrate) { _pipelineSteps.Add(new AudioBitrateOutputOption(desiredBitrate)); } foreach (int desiredBufferSize in audioInputFile.DesiredState.AudioBufferSize) { _pipelineSteps.Add(new AudioBufferSizeOutputOption(desiredBufferSize)); } foreach (int desiredSampleRate in audioInputFile.DesiredState.AudioSampleRate) { _pipelineSteps.Add(new AudioSampleRateOutputOption(desiredSampleRate)); } if (audioInputFile.DesiredState.NormalizeLoudness) { _audioInputFile.Iter(f => f.FilterSteps.Add(new NormalizeLoudnessFilter())); } foreach (TimeSpan desiredDuration in audioInputFile.DesiredState.AudioDuration) { _audioInputFile.Iter(f => f.FilterSteps.Add(new AudioPadFilter(desiredDuration))); } } foreach (SubtitleInputFile subtitleInputFile in _subtitleInputFile) { if (subtitleInputFile.IsImageBased) { // vaapi and videotoolbox use a software overlay, so we need to ensure the background is already in software // though videotoolbox uses software decoders, so no need to download for that if (ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Vaapi) { var downloadFilter = new HardwareDownloadFilter(currentState); currentState = downloadFilter.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(downloadFilter)); } subtitleInputFile.FilterSteps.Add(new SubtitlePixelFormatFilter(ffmpegState)); subtitleInputFile.FilterSteps.Add(new SubtitleHardwareUploadFilter(currentState, ffmpegState)); } else { _videoInputFile.Iter(f => f.AddOption(new CopyTimestampInputOption())); // text-based subtitles are always added in software, so always try to download the background // nvidia needs some extra format help if the only filter will be the download filter if (ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Nvenc && _videoInputFile.Map(f => f.FilterSteps.Count).IfNone(1) == 0) { IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration( ffmpegState.HardwareAccelerationMode, currentState, desiredState.ScaledSize, desiredState.PaddedSize); currentState = scaleFilter.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(scaleFilter)); } var downloadFilter = new HardwareDownloadFilter(currentState); currentState = downloadFilter.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(downloadFilter)); } } foreach (WatermarkInputFile watermarkInputFile in _watermarkInputFile) { // vaapi and videotoolbox use a software overlay, so we need to ensure the background is already in software // though videotoolbox uses software decoders, so no need to download for that if (ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Vaapi) { var downloadFilter = new HardwareDownloadFilter(currentState); currentState = downloadFilter.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(downloadFilter)); } watermarkInputFile.FilterSteps.Add( new WatermarkPixelFormatFilter(ffmpegState, watermarkInputFile.DesiredState)); foreach (VideoStream watermarkStream in watermarkInputFile.VideoStreams) { if (watermarkStream.StillImage == false) { watermarkInputFile.AddOption(new DoNotIgnoreLoopInputOption()); } else if (watermarkInputFile.DesiredState.MaybeFadePoints.Map(fp => fp.Count > 0).IfNone(false)) { // looping is required to fade a static image in and out watermarkInputFile.AddOption(new InfiniteLoopInputOption(ffmpegState.HardwareAccelerationMode)); } } if (watermarkInputFile.DesiredState.Size == WatermarkSize.Scaled) { watermarkInputFile.FilterSteps.Add( new WatermarkScaleFilter(watermarkInputFile.DesiredState, currentState.PaddedSize)); } if (watermarkInputFile.DesiredState.Opacity != 100) { watermarkInputFile.FilterSteps.Add(new WatermarkOpacityFilter(watermarkInputFile.DesiredState)); } foreach (List <WatermarkFadePoint> fadePoints in watermarkInputFile.DesiredState.MaybeFadePoints) { watermarkInputFile.FilterSteps.AddRange(fadePoints.Map(fp => new WatermarkFadeFilter(fp))); } watermarkInputFile.FilterSteps.Add(new WatermarkHardwareUploadFilter(currentState, ffmpegState)); } // after everything else is done, apply the encoder if (_pipelineSteps.OfType <IEncoder>().All(e => e.Kind != StreamKind.Video)) { foreach (IEncoder e in AvailableEncoders.ForVideoFormat( ffmpegState, currentState, desiredState, _watermarkInputFile, _subtitleInputFile, _logger)) { encoder = e; _pipelineSteps.Add(encoder); _videoInputFile.Iter(f => f.FilterSteps.Add(encoder)); currentState = encoder.NextState(currentState); } } if (ffmpegState.DoNotMapMetadata) { _pipelineSteps.Add(new DoNotMapMetadataOutputOption()); } foreach (string desiredServiceProvider in ffmpegState.MetadataServiceProvider) { _pipelineSteps.Add(new MetadataServiceProviderOutputOption(desiredServiceProvider)); } foreach (string desiredServiceName in ffmpegState.MetadataServiceName) { _pipelineSteps.Add(new MetadataServiceNameOutputOption(desiredServiceName)); } foreach (string desiredAudioLanguage in ffmpegState.MetadataAudioLanguage) { _pipelineSteps.Add(new MetadataAudioLanguageOutputOption(desiredAudioLanguage)); } switch (ffmpegState.OutputFormat) { case OutputFormatKind.MpegTs: _pipelineSteps.Add(new OutputFormatMpegTs()); _pipelineSteps.Add(new PipeProtocol()); // currentState = currentState with { OutputFormat = OutputFormatKind.MpegTs }; break; case OutputFormatKind.Hls: foreach (string playlistPath in ffmpegState.HlsPlaylistPath) { foreach (string segmentTemplate in ffmpegState.HlsSegmentTemplate) { var step = new OutputFormatHls( desiredState, videoStream.FrameRate, segmentTemplate, playlistPath); currentState = step.NextState(currentState); _pipelineSteps.Add(step); } } break; } var complexFilter = new ComplexFilter( currentState, ffmpegState, _videoInputFile, _audioInputFile, _watermarkInputFile, _subtitleInputFile, currentState.PaddedSize, _fontsFolder); _pipelineSteps.Add(complexFilter); } return(new FFmpegPipeline(_pipelineSteps)); }