@ -8,21 +8,28 @@ using ErsatzTV.FFmpeg.Option.HardwareAcceleration;
@@ -8,21 +8,28 @@ using ErsatzTV.FFmpeg.Option.HardwareAcceleration;
using ErsatzTV.FFmpeg.Option.Metadata ;
using ErsatzTV.FFmpeg.OutputFormat ;
using ErsatzTV.FFmpeg.Protocol ;
using ErsatzTV.FFmpeg.State ;
using LanguageExt ;
using Microsoft.Extensions.Logging ;
using static LanguageExt . Prelude ;
namespace ErsatzTV.FFmpeg ;
public class PipelineBuilder
{
private readonly List < IPipelineStep > _ pipelineSteps ;
private readonly List < IPipelineFilterStep > _ audioFilterSteps ;
private readonly List < IPipelineFilterStep > _ videoFilterSteps ;
private readonly IList < InputFile > _ inputFiles ;
private readonly Option < VideoInputFile > _ videoInputFile ;
private readonly Option < AudioInputFile > _ audioInputFile ;
private readonly Option < Watermark InputFile> _ watermarkInputFile ;
private readonly string _ reportsFolder ;
private readonly ILogger _l ogger ;
public PipelineBuilder ( IList < InputFile > inputFiles , string reportsFolder , ILogger logger )
public PipelineBuilder (
Option < VideoInputFile > videoInputFile ,
Option < AudioInputFile > audioInputFile ,
Option < WatermarkInputFile > watermarkInputFile ,
string reportsFolder ,
ILogger logger )
{
_ pipelineSteps = new List < IPipelineStep >
{
@ -37,20 +44,51 @@ public class PipelineBuilder
@@ -37,20 +44,51 @@ public class PipelineBuilder
new ClosedGopOutputOption ( ) ,
} ;
_ audioFilterSteps = new List < IPipelineFilterStep > ( ) ;
_ videoFilterSteps = new List < IPipelineFilterStep > ( ) ;
_ inputFiles = inputFiles ;
_ videoInputFile = videoInputFile ;
_ audioInputFile = audioInputFile ;
_ watermarkInputFile = watermarkInputFile ;
_ reportsFolder = reportsFolder ;
_l ogger = logger ;
}
public FFmpegPipeline Build ( FrameState desired State)
public FFmpegPipeline Concat ( ConcatInputFile concatInputFile , FFmpegState ffmpeg State)
{
var allVideoStreams = _ inputFiles . SelectMany ( f = > f . Streams )
. Filter ( s = > s . Kind = = StreamKind . Video )
. OfType < VideoStream > ( )
. ToList ( ) ;
concatInputFile . AddOption ( new ConcatInputFormat ( ) ) ;
concatInputFile . AddOption ( new RealtimeInputOption ( ) ) ;
concatInputFile . AddOption ( new InfiniteLoopInputOption ( HardwareAccelerationMode . None ) ) ;
_ pipelineSteps . Add ( new NoSceneDetectOutputOption ( 0 ) ) ;
_ pipelineSteps . Add ( new EncoderCopyAll ( ) ) ;
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 ) ) ;
}
_ pipelineSteps . Add ( new OutputFormatMpegTs ( ) ) ;
_ pipelineSteps . Add ( new PipeProtocol ( ) ) ;
if ( ffmpegState . SaveReport )
{
_ pipelineSteps . Add ( new FFReportVariable ( _ reportsFolder , concatInputFile ) ) ;
}
return new FFmpegPipeline ( _ pipelineSteps ) ;
}
public FFmpegPipeline Build ( FFmpegState ffmpegState , FrameState desiredState )
{
var allVideoStreams = _ videoInputFile . SelectMany ( f = > f . VideoStreams ) . ToList ( ) ;
// -sc_threshold 0 is unsupported with mpeg2video
_ pipelineSteps . Add (
@ -58,14 +96,24 @@ public class PipelineBuilder
@@ -58,14 +96,24 @@ public class PipelineBuilder
? new NoSceneDetectOutputOption ( 0 )
: new NoSceneDetectOutputOption ( 1 _ 0 0 0 _ 0 0 0 _ 0 0 0 ) ) ;
var allAudioStreams = _ inputFiles . SelectMany ( f = > f . Streams )
. Filter ( s = > s . Kind = = StreamKind . Audio )
. OfType < AudioStream > ( )
. ToList ( ) ;
if ( ffmpegState . SaveReport )
{
_ pipelineSteps . Add ( new FFReportVariable ( _ reportsFolder , None ) ) ;
}
VideoStream videoStream = allVideoStreams . Head ( ) ;
Option < AudioStream > audioStream = allAudioStreams . HeadOrNone ( ) ;
if ( videoStream ! = null )
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 ) ) ;
}
foreach ( TimeSpan desiredFinish in ffmpegState . Finish )
{
_ pipelineSteps . Add ( new TimeLimitOutputOption ( desiredFinish ) ) ;
}
foreach ( VideoStream videoStream in allVideoStreams )
{
Option < int > initialFrameRate = Option < int > . None ;
foreach ( string frameRateString in videoStream . FrameRate )
@ -77,14 +125,8 @@ public class PipelineBuilder
@@ -77,14 +125,8 @@ public class PipelineBuilder
}
var currentState = new FrameState (
false , // save report
HardwareAccelerationMode . None ,
Option < string > . None ,
Option < string > . None ,
false , // realtime
false , // infinite loop
Option < TimeSpan > . None ,
Option < TimeSpan > . None ,
videoStream . Codec ,
videoStream . PixelFormat ,
videoStream . FrameSize ,
@ -93,52 +135,8 @@ public class PipelineBuilder
@@ -93,52 +135,8 @@ public class PipelineBuilder
Option < int > . None ,
Option < int > . None ,
Option < int > . None ,
false , // deinterlace
audioStream . Map ( a = > a . Codec ) ,
audioStream . Map ( a = > a . Channels ) ,
Option < int > . None ,
Option < int > . None ,
Option < int > . None ,
Option < TimeSpan > . None ,
false ,
false ,
Option < string > . None ,
Option < string > . None ,
Option < string > . None ,
OutputFormatKind . None ,
Option < string > . None ,
Option < string > . None ,
0 ) ;
if ( desiredState . SaveReport & & ! currentState . SaveReport )
{
IPipelineStep step = new FFReportVariable ( _ reportsFolder , _ inputFiles ) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
}
foreach ( TimeSpan desiredStart in desiredState . Start )
{
if ( currentState . Start ! = desiredStart )
{
// _logger.LogInformation("Setting stream seek: {DesiredStart}", desiredStart);
IPipelineStep step = new StreamSeekInputOption ( desiredStart ) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
}
}
foreach ( TimeSpan desiredFinish in desiredState . Finish )
{
if ( currentState . Finish ! = desiredFinish )
{
// _logger.LogInformation("Setting time limit: {DesiredFinish}", desiredFinish);
IPipelineStep step = new TimeLimitOutputOption ( desiredFinish ) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
}
}
false ) ; // deinterlace
IEncoder encoder ;
if ( IsDesiredVideoState ( currentState , desiredState ) )
@ -148,79 +146,72 @@ public class PipelineBuilder
@@ -148,79 +146,72 @@ public class PipelineBuilder
}
else
{
if ( currentState . HardwareAccelerationMode ! = desiredState . HardwareAccelerationMode )
{
Option < IPipelineStep > maybeAccel = AvailableHardwareAccelerationOptions . ForMode (
desiredState . HardwareAccelerationMode ,
desiredState . VaapiDevice ,
_l ogger ) ;
Option < IPipelineStep > maybeAccel = AvailableHardwareAccelerationOptions . ForMode (
ffmpegState . HardwareAccelerationMode ,
ffmpegState . VaapiDevice ,
_l ogger ) ;
if ( maybeAccel . IsNone )
{
desiredState = desiredState with
{
// disable hw accel if we don't match anything
HardwareAccelerationMode = HardwareAccelerationMode . None
} ;
}
foreach ( IPipelineStep accel in maybeAccel )
if ( maybeAccel . IsNone )
{
ffmpegState = ffmpegState with
{
currentState = accel . NextState ( currentState ) ;
_ pipelineSteps . Add ( accel ) ;
}
// disable hw accel if we don't match anything
HardwareAccelerationMode = HardwareAccelerationMode . None
} ;
}
foreach ( string desiredVaapiDriver in desiredState . VaapiDriver )
foreach ( IPipelineStep accel in maybeAccel )
{
if ( currentState . VaapiDriver ! = desiredVaapiDriver )
{
IPipelineStep step = new LibvaDriverNameVariable ( desiredVaapiDriver ) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
}
currentState = accel . NextState ( currentState ) ;
_ pipelineSteps . Add ( accel ) ;
}
foreach ( IDecoder decoder in AvailableDecoders . ForVideoFormat ( currentState , desiredState , _l ogger ) )
// nvenc requires yuv420p background with yuva420p overlay
if ( ffmpegState . HardwareAccelerationMode = = HardwareAccelerationMode . Nvenc & &
_ watermarkInputFile . IsSome )
{
currentState = decoder . NextState ( currentState ) ;
_ pipelineSteps . Add ( decoder ) ;
desiredState = desiredState with { PixelFormat = new PixelFormatYuv420P ( ) } ;
}
if ( _ inputFiles . OfType < ConcatInputFile > ( ) . Any ( ) )
foreach ( string desiredVaapiDriver in ffmpegState . VaapiDriver )
{
IPipelineStep concatInputFormat = new ConcatInputFormat ( ) ;
currentState = concatInputFormat . NextState ( currentState ) ;
_ pipelineSteps . Add ( concatInputFormat ) ;
IPipelineStep step = new LibvaDriverNameVariable ( desiredVaapiDriver ) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
}
IPipelineStep copyCodec = new EncoderCopyAll ( ) ;
currentState = copyCodec . NextState ( currentState ) ;
_ pipelineSteps . Add ( copyCodec ) ;
foreach ( IDecoder decoder in AvailableDecoders . ForVideoFormat ( ffmpegState , currentState , _l ogger ) )
{
foreach ( VideoInputFile videoInputFile in _ videoInputFile )
{
videoInputFile . AddOption ( decoder ) ;
currentState = decoder . NextState ( currentState ) ;
}
}
}
if ( videoStream . StillImage )
{
_ pipelineSteps . Add ( new InfiniteLoopInputOption ( currentState ) ) ;
var option = new InfiniteLoopInputOption ( ffmpegState . HardwareAccelerationMode ) ;
_ videoInputFile . Iter ( f = > f . AddOption ( option ) ) ;
}
// TODO: while?
if ( ! IsDesiredVideoState ( currentState , desiredState ) )
{
if ( ! currentState . Realtime & & desiredState . Realtime )
if ( desiredState . Realtime )
{
IPipelineStep step = new RealtimeInputOption ( ) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
var option = new RealtimeInputOption ( ) ;
_ audioInputFile . Iter ( f = > f . AddOption ( option ) ) ;
_ videoInputFile . Iter ( f = > f . AddOption ( option ) ) ;
}
if ( ! currentState . InfiniteLoop & & desiredState . InfiniteLoop )
if ( desiredState . InfiniteLoop )
{
IPipelineStep step = new InfiniteLoopInputOption ( currentStat e) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
var option = new InfiniteLoopInputOption ( ffmpegState . HardwareAccelerationMod e) ;
_ audioInputFile . Iter ( f = > f . AddOption ( option ) ) ;
_ videoInputFile . Iter ( f = > f . AddOption ( option ) ) ;
}
foreach ( int desiredFrameRate in desiredState . FrameRate )
{
if ( currentState . FrameRate ! = desiredFrameRate )
@ -264,15 +255,14 @@ public class PipelineBuilder
@@ -264,15 +255,14 @@ public class PipelineBuilder
if ( desiredState . Deinterlaced & & ! currentState . Deinterlaced )
{
IPipelineFilterStep step = AvailableDeinterlaceFilters . ForAcceleration (
current State. HardwareAccelerationMode ,
ffmpeg State. HardwareAccelerationMode ,
currentState ) ;
currentState = step . NextState ( currentState ) ;
_ videoFilterSteps . Add ( step ) ;
_ videoInputFile . Iter ( f = > f . FilterSteps . Add ( step ) ) ;
}
// TODO: this is a software-only flow, will need to be different for hardware accel
if ( current State. HardwareAccelerationMode = = HardwareAccelerationMode . None )
if ( ffmpeg State. HardwareAccelerationMode = = HardwareAccelerationMode . None )
{
if ( currentState . ScaledSize ! = desiredState . ScaledSize | |
currentState . PaddedSize ! = desiredState . PaddedSize )
@ -282,82 +272,126 @@ public class PipelineBuilder
@@ -282,82 +272,126 @@ public class PipelineBuilder
desiredState . ScaledSize ,
desiredState . PaddedSize ) ;
currentState = scaleStep . NextState ( currentState ) ;
_ videoFilterSteps . Add ( scaleStep ) ;
_ 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 ) ;
_ videoFilterSteps . Add ( padStep ) ;
_ videoInputFile . Iter ( f = > f . FilterSteps . Add ( padStep ) ) ;
IPipelineFilterStep sarStep = new SetSarFilter ( ) ;
currentState = sarStep . NextState ( currentState ) ;
_ videoFilterSteps . Add ( sarStep ) ;
_ videoInputFile . Iter ( f = > f . FilterSteps . Add ( sarStep ) ) ;
}
}
else if ( currentState . ScaledSize ! = desiredState . ScaledSize )
{
IPipelineFilterStep scaleFilter = AvailableScaleFilters . ForAcceleration (
current State. HardwareAccelerationMode ,
ffmpeg State. HardwareAccelerationMode ,
currentState ,
desiredState . ScaledSize ,
desiredState . PaddedSize ) ;
currentState = scaleFilter . NextState ( currentState ) ;
_ videoFilterSteps . Add ( scaleFilter ) ;
_ 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 ) ;
_ videoFilterSteps . Add ( padStep ) ;
_ videoInputFile . Iter ( f = > f . FilterSteps . Add ( padStep ) ) ;
}
IPipelineFilterStep sarStep = new SetSarFilter ( ) ;
currentState = sarStep . NextState ( currentState ) ;
_ videoFilterSteps . Add ( sarStep ) ;
_ videoInputFile . Iter ( f = > f . FilterSteps . Add ( sarStep ) ) ;
}
else if ( currentState . PaddedSize ! = desiredState . PaddedSize )
{
IPipelineFilterStep scaleFilter = AvailableScaleFilters . ForAcceleration (
current State. HardwareAccelerationMode ,
ffmpeg State. HardwareAccelerationMode ,
currentState ,
desiredState . ScaledSize ,
desiredState . PaddedSize ) ;
currentState = scaleFilter . NextState ( currentState ) ;
_ videoFilterSteps . Add ( scaleFilter ) ;
_ videoInputFile . Iter ( f = > f . FilterSteps . Add ( scaleFilter ) ) ;
if ( currentState . PaddedSize ! = desiredState . PaddedSize )
{
IPipelineFilterStep padStep = new PadFilter ( currentState , desiredState . PaddedSize ) ;
currentState = padStep . NextState ( currentState ) ;
_ videoFilterSteps . Add ( padStep ) ;
_ videoInputFile . Iter ( f = > f . FilterSteps . Add ( padStep ) ) ;
}
IPipelineFilterStep sarStep = new SetSarFilter ( ) ;
currentState = sarStep . NextState ( currentState ) ;
_ videoFilterSteps . Add ( sarStep ) ;
_ videoInputFile . Iter ( f = > f . FilterSteps . Add ( sarStep ) ) ;
}
else if ( _ watermarkInputFile . IsSome & & currentState . PixelFormat ! = desiredState . PixelFormat )
{
// 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 ) ) ;
_ videoInputFile . Iter ( f = > f . FilterSteps . Add ( new HardwareUploadFilter ( ffmpegState ) ) ) ;
}
else
{
// 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 ) ) ;
}
}
}
if ( currentState . PtsOffset ! = desiredState . PtsOffset )
if ( ffmpegState . PtsOffset > 0 )
{
foreach ( int videoTrackTimeScale in desiredState . VideoTrackTimeScale )
{
IPipelineStep step = new OutputTsOffsetOption (
desiredState . PtsOffset ,
ffmpeg State. PtsOffset ,
videoTrackTimeScale ) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
}
}
foreach ( IPixelFormat desiredPixelFormat in desiredState . PixelFormat )
{
if ( currentState . PixelFormat . Map ( pf = > pf . FFmpegName ) ! = desiredPixelFormat . FFmpegName )
{
IPipelineStep step = new PixelFormatOutputOption ( desiredPixelFormat ) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
}
}
// after everything else is done, apply the encoder
if ( ! _ pipelineSteps . OfType < IEncoder > ( ) . Any ( ) )
{
foreach ( IEncoder e in AvailableEncoders . ForVideoFormat ( currentState , desiredState , _l ogger ) )
foreach ( IEncoder e in AvailableEncoders . ForVideoFormat (
ffmpegState ,
currentState ,
desiredState ,
_l ogger ) )
{
encoder = e ;
_ pipelineSteps . Add ( encoder ) ;
_ videoFilterSteps . Add ( encoder ) ;
_ videoInputFile . Iter ( f = > f . FilterSteps . Add ( encoder ) ) ;
currentState = encoder . NextState ( currentState ) ;
}
}
@ -366,129 +400,114 @@ public class PipelineBuilder
@@ -366,129 +400,114 @@ public class PipelineBuilder
// 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
if ( audioStream . IsSome & & IsDesiredAudioState ( currentState , desiredState ) )
{
_ pipelineSteps . Add ( new EncoderCopyAudio ( ) ) ;
}
else
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 ( desiredState , _l ogger ) )
foreach ( IEncoder step in AvailableEncoders . ForAudioFormat ( audioInputFile . DesiredState , _l ogger ) )
{
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
}
}
// TODO: while?
if ( ! IsDesiredAudioState ( currentState , desiredState ) )
{
foreach ( int desiredAudioChannels in desiredState . AudioChannels )
foreach ( int desiredAudioChannels in audioInputFile . DesiredState . AudioChannels )
{
if ( currentState . AudioChannels ! = desiredAudioChannels )
{
var step = new AudioChannelsOutputOption ( desiredAudioChannels ) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
}
_ pipelineSteps . Add ( new AudioChannelsOutputOption ( desiredAudioChannels ) ) ;
}
foreach ( int desiredBitrate in desiredState . AudioBitrate )
foreach ( int desiredBitrate in audioInputFile . DesiredState . AudioBitrate )
{
if ( currentState . AudioBitrate ! = desiredBitrate )
{
IPipelineStep step = new AudioBitrateOutputOption ( desiredBitrate ) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
}
_ pipelineSteps . Add ( new AudioBitrateOutputOption ( desiredBitrate ) ) ;
}
foreach ( int desiredBufferSize in desiredState . AudioBufferSize )
foreach ( int desiredBufferSize in audioInputFile . DesiredState . AudioBufferSize )
{
if ( currentState . AudioBufferSize ! = desiredBufferSize )
{
IPipelineStep step = new AudioBufferSizeOutputOption ( desiredBufferSize ) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
}
_ pipelineSteps . Add ( new AudioBufferSizeOutputOption ( desiredBufferSize ) ) ;
}
foreach ( int desiredSampleRate in desiredState . AudioSampleRate )
foreach ( int desiredSampleRate in audioInputFile . DesiredState . AudioSampleRate )
{
if ( currentState . AudioSampleRate ! = desiredSampleRate )
{
IPipelineStep step = new AudioSampleRateOutputOption ( desiredSampleRate ) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
}
_ pipelineSteps . Add ( new AudioSampleRateOutputOption ( desiredSampleRate ) ) ;
}
if ( desiredState . NormalizeLoudness & & ! current State. NormalizeLoudness )
if ( audioInputFile . DesiredState . NormalizeLoudness )
{
IPipelineFilterStep step = new NormalizeLoudnessFilter ( ) ;
currentState = step . NextState ( currentState ) ;
_ audioFilterSteps . Add ( step ) ;
_ audioInputFile . Iter ( f = > f . FilterSteps . Add ( new NormalizeLoudnessFilter ( ) ) ) ;
}
foreach ( TimeSpan desiredDuration in desiredState . AudioDuration )
foreach ( TimeSpan desiredDuration in au dioInputFile . D esiredState. AudioDuration )
{
if ( currentState . AudioDuration ! = desiredDuration )
_ audioInputFile . Iter ( f = > f . FilterSteps . Add ( new AudioPadFilter ( desiredDuration ) ) ) ;
}
}
foreach ( WatermarkInputFile watermarkInputFile in _ watermarkInputFile )
{
watermarkInputFile . FilterSteps . Add (
new WatermarkPixelFormatFilter ( ffmpegState , watermarkInputFile . DesiredState ) ) ;
foreach ( VideoStream watermarkStream in watermarkInputFile . VideoStreams )
{
if ( watermarkStream . StillImage = = false )
{
IPipelineFilterStep step = new AudioPadFilter ( desiredDuration ) ;
currentState = step . NextState ( currentState ) ;
_ audioFilterSteps . Add ( step ) ;
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 ! = 1 0 0 )
{
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 HardwareUploadFilter ( ffmpegState ) ) ;
}
if ( desiredState . DoNotMapMetadata & & ! currentState . DoNotMapMetadata )
if ( ffmpeg State. DoNotMapMetadata )
{
IPipelineStep step = new DoNotMapMetadataOutputOption ( ) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
_ pipelineSteps . Add ( new DoNotMapMetadataOutputOption ( ) ) ;
}
foreach ( string desiredServiceProvider in desiredState . MetadataServiceProvider )
foreach ( string desiredServiceProvider in ffmpeg State. MetadataServiceProvider )
{
if ( currentState . MetadataServiceProvider ! = desiredServiceProvider )
{
IPipelineStep step = new MetadataServiceProviderOutputOption ( desiredServiceProvider ) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
}
_ pipelineSteps . Add ( new MetadataServiceProviderOutputOption ( desiredServiceProvider ) ) ;
}
foreach ( string desiredServiceName in desired State. MetadataServiceName )
foreach ( string desiredServiceName in ffmpegState . MetadataServiceName )
{
if ( currentState . MetadataServiceName ! = desiredServiceName )
{
IPipelineStep step = new MetadataServiceNameOutputOption ( desiredServiceName ) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
}
_ pipelineSteps . Add ( new MetadataServiceNameOutputOption ( desiredServiceName ) ) ;
}
foreach ( string desiredAudioLanguage in desired State. MetadataAudioLanguage )
foreach ( string desiredAudioLanguage in ffmpegState . MetadataAudioLanguage )
{
if ( currentState . MetadataAudioLanguage ! = desiredAudioLanguage )
{
IPipelineStep step = new MetadataAudioLanguageOutputOption ( desiredAudioLanguage ) ;
currentState = step . NextState ( currentState ) ;
_ pipelineSteps . Add ( step ) ;
}
_ pipelineSteps . Add ( new MetadataAudioLanguageOutputOption ( desiredAudioLanguage ) ) ;
}
switch ( desired State. OutputFormat )
switch ( ffmpegState . OutputFormat )
{
case OutputFormatKind . MpegTs :
_ pipelineSteps . Add ( new OutputFormatMpegTs ( ) ) ;
_ pipelineSteps . Add ( new PipeProtocol ( ) ) ;
currentState = currentState with { OutputFormat = OutputFormatKind . MpegTs } ;
// currentState = currentState with { OutputFormat = OutputFormatKind.MpegTs };
break ;
case OutputFormatKind . Hls :
foreach ( string playlistPath in desired State. HlsPlaylistPath )
foreach ( string playlistPath in ffmpeg State. HlsPlaylistPath )
{
foreach ( string segmentTemplate in desired State. HlsSegmentTemplate )
foreach ( string segmentTemplate in ffmpeg State. HlsSegmentTemplate )
{
var step = new OutputFormatHls (
desiredState ,
@ -503,20 +522,22 @@ public class PipelineBuilder
@@ -503,20 +522,22 @@ public class PipelineBuilder
break ;
}
// add a complex filter unless we are concatenating
if ( ! _ inputFiles . OfType < ConcatInputFile > ( ) . Any ( ) )
{
_ pipelineSteps . Add ( new ComplexFilter ( _ inputFiles , _ audioFilterSteps , _ videoFilterSteps ) ) ;
}
var complexFilter = new ComplexFilter (
ffmpegState ,
_ videoInputFile ,
_ audioInputFile ,
_ watermarkInputFile ,
currentState . PaddedSize ) ;
_ pipelineSteps . Add ( complexFilter ) ;
}
return new FFmpegPipeline ( _ pipelineSteps , _ videoFilterSteps , _ audioFilterSteps ) ;
return new FFmpegPipeline ( _ pipelineSteps ) ;
}
private static bool IsDesiredVideoState ( FrameState currentState , FrameState desiredState )
{
return currentState . HardwareAccelerationMode = = desiredState . HardwareAccelerationMode & &
currentState . VideoFormat = = desiredState . VideoFormat & &
return currentState . VideoFormat = = desiredState . VideoFormat & &
currentState . PixelFormat . Match ( pf = > pf . Name , ( ) = > string . Empty ) = =
desiredState . PixelFormat . Match ( pf = > pf . Name , string . Empty ) & &
( desiredState . VideoBitrate . IsNone | | currentState . VideoBitrate = = desiredState . VideoBitrate ) & &
@ -528,15 +549,4 @@ public class PipelineBuilder
@@ -528,15 +549,4 @@ public class PipelineBuilder
currentState . PaddedSize = = desiredState . PaddedSize & &
( desiredState . FrameRate . IsNone | | currentState . FrameRate = = desiredState . FrameRate ) ;
}
private static bool IsDesiredAudioState ( FrameState currentState , FrameState desiredState )
{
return currentState . AudioFormat = = desiredState . AudioFormat & &
currentState . AudioChannels = = desiredState . AudioChannels & &
( desiredState . AudioBitrate . IsNone | | currentState . AudioBitrate = = desiredState . AudioBitrate ) & &
( desiredState . AudioBufferSize . IsNone | | currentState . AudioBufferSize = = desiredState . AudioBufferSize ) & &
( desiredState . AudioSampleRate . IsNone | | currentState . AudioSampleRate = = desiredState . AudioSampleRate ) & &
( desiredState . AudioDuration . IsNone | | currentState . AudioDuration = = desiredState . AudioDuration ) & &
currentState . NormalizeLoudness = = desiredState . NormalizeLoudness ;
}
}