diff --git a/FFMpegCore.Test/ArgumentBuilderTest.cs b/FFMpegCore.Test/ArgumentBuilderTest.cs index 64a116b..a1be089 100644 --- a/FFMpegCore.Test/ArgumentBuilderTest.cs +++ b/FFMpegCore.Test/ArgumentBuilderTest.cs @@ -1,5 +1,6 @@ using System.Drawing; using FFMpegCore.Arguments; +using FFMpegCore.Arguments.MainOptions; using FFMpegCore.Enums; using FFMpegCore.Pipes; @@ -46,6 +47,38 @@ public class ArgumentBuilderTest Assert.AreEqual("-i \"input.mp4\" -b:a 128k \"output.mp4\" -y", str); } + [TestMethod] + public void Builder_BuildString_HideBanner() + { + var str = FFMpegArguments.FromFileInput("input.mp4").WithGlobalOptions(opt => opt.WithHideBanner()) + .OutputToFile("output.mp4", false).Arguments; + Assert.AreEqual("-hide_banner -i \"input.mp4\" \"output.mp4\"", str); + } + + [TestMethod] + public void Builder_BuildString_NoStats() + { + var str = FFMpegArguments.FromFileInput("input.mp4").WithGlobalOptions(opt => opt.WithNoStats()) + .OutputToFile("output.mp4", false).Arguments; + Assert.AreEqual("-nostats -i \"input.mp4\" \"output.mp4\"", str); + } + + [TestMethod] + public void Builder_BuildString_With_Explicit_Stats() + { + var str = FFMpegArguments.FromFileInput("input.mp4").WithGlobalOptions(opt => opt.WithArgument(new Stats(true))) + .OutputToFile("output.mp4", false).Arguments; + Assert.AreEqual("-stats -i \"input.mp4\" \"output.mp4\"", str); + } + + [TestMethod] + public void Builder_BuildString_HardwareAccelerationOutputFormat() + { + var str = FFMpegArguments.FromFileInput("input.mp4").WithGlobalOptions(opt => opt.WithHardwareAccelerationOutputFormat(HardwareAccelerationDevice.Auto)) + .OutputToFile("output.mp4", false).Arguments; + Assert.AreEqual("-hwaccel_output_format auto -i \"input.mp4\" \"output.mp4\"", str); + } + [TestMethod] public void Builder_BuildString_Quiet() { @@ -552,6 +585,191 @@ public class ArgumentBuilderTest str); } + [TestMethod] + public void Builder_BuildString_VideoFilter_Vaapi_Scale() + { + var str = FFMpegArguments + .FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt + .WithVideoFilters(filterOptions => filterOptions + .Format("nv12", "vaapi") + .HardwareUpload() + .WithVaapiVideoFilter(options => options + .Scale(VideoSize.FullHd) + ) + ) + .WithVaapiRcMode(VaapiRcMode.CQP) + .WithH264VaapiOptions(options => options + .WithQuantizer(28) + ) + ) + .Arguments; + + Assert.AreEqual( + "-i \"input.mp4\" -vf \"format=pix_fmts=nv12|vaapi, hwupload, scale_vaapi=-1:1080\" -rc_mode CQP -qp 28 \"output.mp4\"", + str); + } + + [TestMethod] + public void Builder_BuildString_VideoFilter_Vaapi_Scale2() + { + var str = FFMpegArguments + .FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt + .WithVideoFilters(filterOptions => filterOptions + .Format("nv12", "vaapi") + .HardwareUpload() + .WithVaapiVideoFilter(options => options + .Scale(2560, 1440) + ) + ) + .WithVaapiRcMode(VaapiRcMode.CQP) + .WithH264VaapiOptions(options => options + .WithQuantizer(28) + ) + ) + .Arguments; + + Assert.AreEqual( + "-i \"input.mp4\" -vf \"format=pix_fmts=nv12|vaapi, hwupload, scale_vaapi=2560:1440\" -rc_mode CQP -qp 28 \"output.mp4\"", + str); + } + + [TestMethod] + public void Builder_BuildString_VideoFilter_Vaapi_Scale3() + { + var str = FFMpegArguments + .FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt + .WithVideoFilters(filterOptions => filterOptions + .Format("nv12", "vaapi") + .HardwareUpload() + .WithVaapiVideoFilter(options => options + .Scale(new Size(1280, 720)) + ) + ) + .WithVaapiRcMode(VaapiRcMode.CQP) + .WithH264VaapiOptions(options => options + .WithQuantizer(28) + ) + ) + .Arguments; + + Assert.AreEqual( + "-i \"input.mp4\" -vf \"format=pix_fmts=nv12|vaapi, hwupload, scale_vaapi=1280:720\" -rc_mode CQP -qp 28 \"output.mp4\"", + str); + } + + [TestMethod] + public void Builder_BuildString_VideoFilter_Vaapi_Scale4() + { + var str = FFMpegArguments + .FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt + .WithVideoFilters(filterOptions => filterOptions + .Format("nv12", "vaapi") + .HardwareUpload() + .WithVaapiVideoFilter(options => options + .Scale(VideoSize.Original) + ) + ) + .WithVaapiRcMode(VaapiRcMode.CQP) + .WithH264VaapiOptions(options => options + .WithQuantizer(28) + ) + ) + .Arguments; + + Assert.AreEqual( + "-i \"input.mp4\" -vf \"format=pix_fmts=nv12|vaapi, hwupload\" -rc_mode CQP -qp 28 \"output.mp4\"", + str); + } + + [TestMethod] + public void Builder_BuildString_Single_Image() + { + var str = FFMpegArguments + .FromFileInput("input.jpg") + .OutputToFile("output.jpg", false, opt => opt + .WithVideoFilters(filterOptions => filterOptions + .Scale(-1, 120) + ) + .WithImage2Options(options => options + .WithUpdate() + ) + ) + .Arguments; + + Assert.AreEqual( + "-i \"input.jpg\" -vf \"scale=-1:120\" -update 1 \"output.jpg\"", + str); + } + + [TestMethod] + public void Builder_BuildString_Segments() + { + var str = FFMpegArguments + .FromFileInput("input.mp4") + .OutputToFile("output-%Y%m%d-%s.mp4", false, opt => opt + .WithUseWallclockAsTimestamps() + .ForceFormat("segment") + .WithSegmentOptions(options => options + .WithSegmentAtClocktime() + .WithSegmentTime(TimeSpan.FromMinutes(10)) + .WithMinimumSegmentDuration(TimeSpan.FromMinutes(5)) + .WithResetTimestamps() + .WithStrftime() + ) + ) + .Arguments; + + Assert.AreEqual( + "-i \"input.mp4\" -use_wallclock_as_timestamps 1 -f segment -segment_atclocktime 1 -segment_time 600 -min_seg_duration 300 -reset_timestamps 1 -strftime 1 \"output-%Y%m%d-%s.mp4\"", + str); + } + + [TestMethod] + public void Builder_BuildString_Segments_WUseWallclockTimestamps() + { + var str = FFMpegArguments + .FromFileInput("input.mp4") + .OutputToFile("output-%Y%m%d-%s.mp4", false, opt => opt + .WithUseWallclockAsTimestamps(false) + .ForceFormat("segment") + .WithSegmentOptions(options => options + .WithSegmentAtClocktime() + .WithSegmentTime(TimeSpan.FromMinutes(10)) + .WithMinimumSegmentDuration(TimeSpan.FromMinutes(5)) + .WithResetTimestamps() + .WithStrftime() + ) + ) + .Arguments; + + Assert.AreEqual( + "-i \"input.mp4\" -use_wallclock_as_timestamps 0 -f segment -segment_atclocktime 1 -segment_time 600 -min_seg_duration 300 -reset_timestamps 1 -strftime 1 \"output-%Y%m%d-%s.mp4\"", + str); + } + + [TestMethod] + public void Builder_BuildString_Rtsp_Stream() + { + var str = FFMpegArguments + .FromUrlInput(new Uri("rtsp://server/stream?query"), options => options + .WithAnalyzeDuration(TimeSpan.FromSeconds(1)) + .WithProbeSize(1_000_000) + .WithRtspProtocolOptions(argumentOptions => argumentOptions + .WithRtspTransport(RtspTransportProtocol.tcp) + ) + ) + .OutputToFile("output.mp4", false) + .Arguments; + + Assert.AreEqual( + "-analyzeduration 1000000 -probesize 1000000 -rtsp_transport tcp -i \"rtsp://server/stream?query\" \"output.mp4\"", + str); + } + [TestMethod] public void Builder_BuildString_GifPalette() { diff --git a/FFMpegCore/FFMpeg/Arguments/BaseBoolArgument.cs b/FFMpegCore/FFMpeg/Arguments/BaseBoolArgument.cs new file mode 100644 index 0000000..dc62087 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/BaseBoolArgument.cs @@ -0,0 +1,11 @@ +namespace FFMpegCore.Arguments; + +/// +/// Base class for boolean arguments with value 0 or 1. +/// +/// +public abstract class BaseBoolArgument(bool value) : IArgument +{ + protected abstract string ArgumentName { get; } + public string Text => $"-{ArgumentName} {(value ? '1' : '0')}"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/BaseOptionArgument.cs b/FFMpegCore/FFMpeg/Arguments/BaseOptionArgument.cs new file mode 100644 index 0000000..094324a --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/BaseOptionArgument.cs @@ -0,0 +1,14 @@ +namespace FFMpegCore.Arguments; + +/// +/// +/// Base class for option arguments. +/// Options which do not take arguments are boolean options, and set the corresponding value to true. +/// They can be set to false by prefixing the option name with "no". For example using "-nofoo" will set the boolean option with name "foo" to false. +/// +/// +public abstract class BaseOptionArgument(bool value) : IArgument +{ + protected abstract string ArgumentName { get; } + public string Text => $"-{(value ? "" : "no")}{ArgumentName}"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/Codecs/Vaapi/VaapiRcModeArgument.cs b/FFMpegCore/FFMpeg/Arguments/Codecs/Vaapi/VaapiRcModeArgument.cs new file mode 100644 index 0000000..461172a --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Codecs/Vaapi/VaapiRcModeArgument.cs @@ -0,0 +1,12 @@ +using FFMpegCore.Enums; + +namespace FFMpegCore.Arguments.Codecs.Vaapi; + +/// +/// +/// Set the rate control mode to use. A given driver may only support a subset of modes. +/// +public sealed class VaapiRcModeArgument(VaapiRcMode rcMode) : IArgument +{ + public string Text => $"-rc_mode {rcMode}"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/Codecs/Vaapi/h264Vaapi/H264VaapiArgumentOptions.cs b/FFMpegCore/FFMpeg/Arguments/Codecs/Vaapi/h264Vaapi/H264VaapiArgumentOptions.cs new file mode 100644 index 0000000..5395c47 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Codecs/Vaapi/h264Vaapi/H264VaapiArgumentOptions.cs @@ -0,0 +1,30 @@ +namespace FFMpegCore.Arguments.Codecs.Vaapi.h264Vaapi; + +/// +/// +/// +public sealed class H264VaapiArgumentOptions +{ + private readonly FFMpegArgumentOptions _options; + + internal H264VaapiArgumentOptions(FFMpegArgumentOptions options) + { + _options = options; + } + + /// + /// + /// + /// 0 - 52 + /// + public H264VaapiArgumentOptions WithQuantizer(sbyte quantizer) + { + return WithArgument(new VaapiQpArgument(quantizer)); + } + + public H264VaapiArgumentOptions WithArgument(IH264VaapiArgument argument) + { + _options.WithArgument(argument); + return this; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/Codecs/Vaapi/h264Vaapi/IH264VaapiArgument.cs b/FFMpegCore/FFMpeg/Arguments/Codecs/Vaapi/h264Vaapi/IH264VaapiArgument.cs new file mode 100644 index 0000000..d979291 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Codecs/Vaapi/h264Vaapi/IH264VaapiArgument.cs @@ -0,0 +1,3 @@ +namespace FFMpegCore.Arguments.Codecs.Vaapi.h264Vaapi; + +public interface IH264VaapiArgument : IArgument; diff --git a/FFMpegCore/FFMpeg/Arguments/Codecs/Vaapi/h264Vaapi/VaapiQpArgument.cs b/FFMpegCore/FFMpeg/Arguments/Codecs/Vaapi/h264Vaapi/VaapiQpArgument.cs new file mode 100644 index 0000000..f4b1a8f --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Codecs/Vaapi/h264Vaapi/VaapiQpArgument.cs @@ -0,0 +1,11 @@ +namespace FFMpegCore.Arguments.Codecs.Vaapi.h264Vaapi; + +/// +/// undocumented(?) +/// Constant QP (for P-frames; scaled by qfactor/qoffset for I/B) +/// +/// 0 - 52 +public sealed class VaapiQpArgument(sbyte quantizer) : IH264VaapiArgument +{ + public string Text => $"-qp {quantizer}"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/AnalyzeDurationArgument.cs b/FFMpegCore/FFMpeg/Arguments/Formats/AnalyzeDurationArgument.cs new file mode 100644 index 0000000..274c1e3 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/AnalyzeDurationArgument.cs @@ -0,0 +1,19 @@ +namespace FFMpegCore.Arguments.Formats; + +/// +/// +/// Specify how many microseconds are analyzed to probe the input. +/// A higher value will enable detecting more accurate information, but will increase latency. +/// It defaults to 5,000,000 microseconds = 5 seconds. +/// +public sealed class AnalyzeDurationArgument(TimeSpan duration) : IArgument +{ +#if NET8_OR_GREATER + private readonly long _duration = Convert.ToInt64(duration.TotalMicroseconds); +#else + // https://github.com/dotnet/runtime/blob/e8812e7419db9137f20b990786a53ed71e27e11e/src/libraries/System.Private.CoreLib/src/System/TimeSpan.cs#L371 + private readonly long _duration = Convert.ToInt64((double)duration.Ticks / 10); +#endif + + public string Text => $"-analyzeduration {_duration}"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/IDemuxerArgument.cs b/FFMpegCore/FFMpeg/Arguments/Formats/IDemuxerArgument.cs new file mode 100644 index 0000000..5f372c3 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/IDemuxerArgument.cs @@ -0,0 +1,3 @@ +namespace FFMpegCore.Arguments.Formats; + +public interface IDemuxerArgument : IArgument; diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/IMuxerArgument.cs b/FFMpegCore/FFMpeg/Arguments/Formats/IMuxerArgument.cs new file mode 100644 index 0000000..c78223c --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/IMuxerArgument.cs @@ -0,0 +1,3 @@ +namespace FFMpegCore.Arguments.Formats; + +public interface IMuxerArgument : IArgument; diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/ProbeSizeArgument.cs b/FFMpegCore/FFMpeg/Arguments/Formats/ProbeSizeArgument.cs new file mode 100644 index 0000000..c282d50 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/ProbeSizeArgument.cs @@ -0,0 +1,12 @@ +namespace FFMpegCore.Arguments.Formats; + +/// +/// +/// Set probing size in bytes, i.e. the size of the data to analyze to get stream information. +/// A higher value will enable detecting more information in case it is dispersed into the stream, but will increase latency. +/// Must be an integer not lesser than 32. It is 5000000 by default. +/// +public sealed class ProbeSizeArgument(long probesize) : IArgument +{ + public string Text => $"-probesize {probesize}"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/UseWallclockAsTimestampsArgument.cs b/FFMpegCore/FFMpeg/Arguments/Formats/UseWallclockAsTimestampsArgument.cs new file mode 100644 index 0000000..8c5741f --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/UseWallclockAsTimestampsArgument.cs @@ -0,0 +1,10 @@ +namespace FFMpegCore.Arguments.Formats; + +/// +/// +/// Use wallclock as timestamps if set to 1. Default is 0. +/// +public sealed class UseWallclockAsTimestampsArgument(bool value) : BaseBoolArgument(value) +{ + protected override string ArgumentName => "use_wallclock_as_timestamps"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/image2/IImage2Argument.cs b/FFMpegCore/FFMpeg/Arguments/Formats/image2/IImage2Argument.cs new file mode 100644 index 0000000..25a5bb1 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/image2/IImage2Argument.cs @@ -0,0 +1,3 @@ +namespace FFMpegCore.Arguments.Formats.image2; + +public interface IImage2Argument : IArgument; diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/image2/IImage2MuxerArgument.cs b/FFMpegCore/FFMpeg/Arguments/Formats/image2/IImage2MuxerArgument.cs new file mode 100644 index 0000000..15278a3 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/image2/IImage2MuxerArgument.cs @@ -0,0 +1,5 @@ +namespace FFMpegCore.Arguments.Formats.image2; + +public interface IImage2MuxerArgument : + IImage2Argument, + IDemuxerArgument; diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/image2/Image2ArgumentOptions.cs b/FFMpegCore/FFMpeg/Arguments/Formats/image2/Image2ArgumentOptions.cs new file mode 100644 index 0000000..37bde3c --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/image2/Image2ArgumentOptions.cs @@ -0,0 +1,29 @@ +namespace FFMpegCore.Arguments.Formats.image2; + +/// +/// +/// +public sealed class Image2ArgumentOptions +{ + private readonly FFMpegArgumentOptions _options; + + internal Image2ArgumentOptions(FFMpegArgumentOptions options) + { + _options = options; + } + + /// + /// + /// + /// + public Image2ArgumentOptions WithUpdate(bool value = true) + { + return WithArgument(new UpdateArgument(value)); + } + + public Image2ArgumentOptions WithArgument(IImage2Argument argument) + { + _options.WithArgument(argument); + return this; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/image2/UpdateArgument.cs b/FFMpegCore/FFMpeg/Arguments/Formats/image2/UpdateArgument.cs new file mode 100644 index 0000000..d473c32 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/image2/UpdateArgument.cs @@ -0,0 +1,10 @@ +namespace FFMpegCore.Arguments.Formats.image2; + +/// +/// +/// If set to 1, the filename will always be interpreted as just a filename, not a pattern, and the corresponding file will be continuously overwritten with new images. Default value is 0. +/// +public sealed class UpdateArgument(bool value = false) : BaseBoolArgument(value), IImage2MuxerArgument +{ + protected override string ArgumentName => "update"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/segment/ISegmentArgument.cs b/FFMpegCore/FFMpeg/Arguments/Formats/segment/ISegmentArgument.cs new file mode 100644 index 0000000..e62a59a --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/segment/ISegmentArgument.cs @@ -0,0 +1,3 @@ +namespace FFMpegCore.Arguments.Formats.segment; + +public interface ISegmentArgument : IArgument; diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/segment/ISegmentMuxerArgument.cs b/FFMpegCore/FFMpeg/Arguments/Formats/segment/ISegmentMuxerArgument.cs new file mode 100644 index 0000000..985bee0 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/segment/ISegmentMuxerArgument.cs @@ -0,0 +1,5 @@ +namespace FFMpegCore.Arguments.Formats.segment; + +public interface ISegmentMuxerArgument : + ISegmentArgument, + IDemuxerArgument; diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/segment/MinSegmentDurationArgument.cs b/FFMpegCore/FFMpeg/Arguments/Formats/segment/MinSegmentDurationArgument.cs new file mode 100644 index 0000000..747777c --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/segment/MinSegmentDurationArgument.cs @@ -0,0 +1,14 @@ +namespace FFMpegCore.Arguments.Formats.segment; + +/// +/// +/// Set minimum segment duration to time, the value must be a duration specification. +/// This prevents the muxer ending segments at a duration below this value. +/// Only effective with segment_time. Default value is "0". +/// +public sealed class MinSegmentDurationArgument(TimeSpan duration) : ISegmentMuxerArgument +{ + private readonly long _duration = Convert.ToInt64(duration.TotalSeconds); + + public string Text => $"-min_seg_duration {_duration}"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/segment/ResetTimestampsArgument.cs b/FFMpegCore/FFMpeg/Arguments/Formats/segment/ResetTimestampsArgument.cs new file mode 100644 index 0000000..2727a17 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/segment/ResetTimestampsArgument.cs @@ -0,0 +1,13 @@ +namespace FFMpegCore.Arguments.Formats.segment; + +/// +/// +/// Reset timestamps at the beginning of each segment, so that each segment will start with near-zero timestamps. +/// It is meant to ease the playback of the generated segments. +/// May not work with some combinations of muxers/codecs. +/// It is set to 0 by default. +/// +public sealed class ResetTimestampsArgument(bool value) : BaseBoolArgument(value), ISegmentMuxerArgument +{ + protected override string ArgumentName => "reset_timestamps"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/segment/SegmentArgumentOptions.cs b/FFMpegCore/FFMpeg/Arguments/Formats/segment/SegmentArgumentOptions.cs new file mode 100644 index 0000000..f2c9710 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/segment/SegmentArgumentOptions.cs @@ -0,0 +1,70 @@ +namespace FFMpegCore.Arguments.Formats.segment; + +/// +/// +/// +public sealed class SegmentArgumentOptions +{ + private readonly FFMpegArgumentOptions _options; + + internal SegmentArgumentOptions(FFMpegArgumentOptions options) + { + _options = options; + } + + /// + /// + /// + /// + /// + public SegmentArgumentOptions WithMinimumSegmentDuration(TimeSpan duration) + { + return WithArgument(new MinSegmentDurationArgument(duration)); + } + + /// + /// + /// + /// + /// + public SegmentArgumentOptions WithSegmentTime(TimeSpan duration) + { + return WithArgument(new SegmentTimeArgument(duration)); + } + + /// + /// + /// + /// + /// + public SegmentArgumentOptions WithSegmentAtClocktime(bool value = true) + { + return WithArgument(new SegmentAtClocktimeArgument(value)); + } + + /// + /// + /// + /// + /// + public SegmentArgumentOptions WithStrftime(bool value = true) + { + return WithArgument(new StrftimeArgument(value)); + } + + /// + /// + /// + /// + /// + public SegmentArgumentOptions WithResetTimestamps(bool value = true) + { + return WithArgument(new ResetTimestampsArgument(value)); + } + + public SegmentArgumentOptions WithArgument(ISegmentArgument argument) + { + _options.WithArgument(argument); + return this; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/segment/SegmentAtClocktimeArgument.cs b/FFMpegCore/FFMpeg/Arguments/Formats/segment/SegmentAtClocktimeArgument.cs new file mode 100644 index 0000000..dca875b --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/segment/SegmentAtClocktimeArgument.cs @@ -0,0 +1,13 @@ +namespace FFMpegCore.Arguments.Formats.segment; + +/// +/// +/// If set to "1" split at regular clock time intervals starting from 00:00 o’clock. +/// The time value specified in segment_time is used for setting the length of the splitting interval. +/// For example with segment_time set to "900" this makes it possible to create files at 12:00 o’clock, 12:15, 12:30, etc. +/// Default value is "0". +/// +public sealed class SegmentAtClocktimeArgument(bool value) : BaseBoolArgument(value), ISegmentMuxerArgument +{ + protected override string ArgumentName => "segment_atclocktime"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/segment/SegmentTimeArgument.cs b/FFMpegCore/FFMpeg/Arguments/Formats/segment/SegmentTimeArgument.cs new file mode 100644 index 0000000..37c7e4d --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/segment/SegmentTimeArgument.cs @@ -0,0 +1,13 @@ +namespace FFMpegCore.Arguments.Formats.segment; + +/// +/// +/// Set segment duration to time, the value must be a duration specification. Default value is "2". See also the segment_times option. +/// Note that splitting may not be accurate, unless you force the reference stream key-frames at the given time. See the introductory notice and the examples below. +/// +public sealed class SegmentTimeArgument(TimeSpan duration) : ISegmentMuxerArgument +{ + private readonly long _duration = Convert.ToInt64(duration.TotalSeconds); + + public string Text => $"-segment_time {_duration}"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/Formats/segment/StrftimeArgument.cs b/FFMpegCore/FFMpeg/Arguments/Formats/segment/StrftimeArgument.cs new file mode 100644 index 0000000..88ae126 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Formats/segment/StrftimeArgument.cs @@ -0,0 +1,12 @@ +namespace FFMpegCore.Arguments.Formats.segment; + +/// +/// +/// Use the strftime function to define the name of the new segments to write. +/// If this is selected, the output segment name must contain a strftime function template. +/// Default value is 0. +/// +public sealed class StrftimeArgument(bool value) : BaseBoolArgument(value), ISegmentMuxerArgument +{ + protected override string ArgumentName => "strftime"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/GenericOptions/HideBanner.cs b/FFMpegCore/FFMpeg/Arguments/GenericOptions/HideBanner.cs new file mode 100644 index 0000000..23d390b --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/GenericOptions/HideBanner.cs @@ -0,0 +1,12 @@ +namespace FFMpegCore.Arguments.GenericOptions; + +/// +/// +/// Suppress printing banner. +/// All FFmpeg tools will normally show a copyright notice, build options and library versions. +/// This option can be used to suppress printing this information. +/// +public sealed class HideBanner : IArgument +{ + public string Text => "-hide_banner"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/MainOptions/Stats.cs b/FFMpegCore/FFMpeg/Arguments/MainOptions/Stats.cs new file mode 100644 index 0000000..9702de9 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/MainOptions/Stats.cs @@ -0,0 +1,10 @@ +namespace FFMpegCore.Arguments.MainOptions; + +/// +/// +/// Explicitly disable logging of encoding progress/statistics. +/// +public sealed class Stats(bool value) : BaseOptionArgument(value) +{ + protected override string ArgumentName => "stats"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/Protocols/Rtsp/IRtspArgument.cs b/FFMpegCore/FFMpeg/Arguments/Protocols/Rtsp/IRtspArgument.cs new file mode 100644 index 0000000..4f845e3 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Protocols/Rtsp/IRtspArgument.cs @@ -0,0 +1,3 @@ +namespace FFMpegCore.Arguments.Protocols.Rtsp; + +public interface IRtspArgument : IArgument; diff --git a/FFMpegCore/FFMpeg/Arguments/Protocols/Rtsp/IRtspDemuxerArgument.cs b/FFMpegCore/FFMpeg/Arguments/Protocols/Rtsp/IRtspDemuxerArgument.cs new file mode 100644 index 0000000..8cda6ba --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Protocols/Rtsp/IRtspDemuxerArgument.cs @@ -0,0 +1,7 @@ +using FFMpegCore.Arguments.Formats; + +namespace FFMpegCore.Arguments.Protocols.Rtsp; + +public interface IRtspDemuxerArgument : + IRtspArgument, + IDemuxerArgument; diff --git a/FFMpegCore/FFMpeg/Arguments/Protocols/Rtsp/IRtspMuxerArgument.cs b/FFMpegCore/FFMpeg/Arguments/Protocols/Rtsp/IRtspMuxerArgument.cs new file mode 100644 index 0000000..ebc33c2 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Protocols/Rtsp/IRtspMuxerArgument.cs @@ -0,0 +1,7 @@ +using FFMpegCore.Arguments.Formats; + +namespace FFMpegCore.Arguments.Protocols.Rtsp; + +public interface IRtspMuxerArgument : + IRtspArgument, + IDemuxerArgument; diff --git a/FFMpegCore/FFMpeg/Arguments/Protocols/Rtsp/RtspArgumentOptions.cs b/FFMpegCore/FFMpeg/Arguments/Protocols/Rtsp/RtspArgumentOptions.cs new file mode 100644 index 0000000..b0231b8 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Protocols/Rtsp/RtspArgumentOptions.cs @@ -0,0 +1,32 @@ +using FFMpegCore.Enums; + +namespace FFMpegCore.Arguments.Protocols.Rtsp; + +/// +/// +/// +public sealed class RtspArgumentOptions +{ + private readonly FFMpegArgumentOptions _options; + + internal RtspArgumentOptions(FFMpegArgumentOptions options) + { + _options = options; + } + + /// + /// + /// + /// + /// + public RtspArgumentOptions WithRtspTransport(RtspTransportProtocol rtspTransportProtocol) + { + return WithArgument(new RtspTransportArgument(rtspTransportProtocol)); + } + + public RtspArgumentOptions WithArgument(IRtspArgument argument) + { + _options.WithArgument(argument); + return this; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/Protocols/Rtsp/RtspTransportArgument.cs b/FFMpegCore/FFMpeg/Arguments/Protocols/Rtsp/RtspTransportArgument.cs new file mode 100644 index 0000000..fc7f7f4 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/Protocols/Rtsp/RtspTransportArgument.cs @@ -0,0 +1,12 @@ +using FFMpegCore.Enums; + +namespace FFMpegCore.Arguments.Protocols.Rtsp; + +/// +/// +/// Set RTSP transport protocols. +/// +public sealed class RtspTransportArgument(RtspTransportProtocol protocol) : IRtspMuxerArgument, IRtspDemuxerArgument +{ + public string Text => $"-rtsp_transport {protocol}"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/VideoFilters/Vaapi/IVaapiVideoFilterArgument.cs b/FFMpegCore/FFMpeg/Arguments/VideoFilters/Vaapi/IVaapiVideoFilterArgument.cs new file mode 100644 index 0000000..6c1480d --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/VideoFilters/Vaapi/IVaapiVideoFilterArgument.cs @@ -0,0 +1,3 @@ +namespace FFMpegCore.Arguments.VideoFilters.Vaapi; + +public interface IVaapiVideoFilterArgument : IVideoFilterArgument; diff --git a/FFMpegCore/FFMpeg/Arguments/VideoFilters/Vaapi/VaapiVideoFilterOptions.cs b/FFMpegCore/FFMpeg/Arguments/VideoFilters/Vaapi/VaapiVideoFilterOptions.cs new file mode 100644 index 0000000..8acf253 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/VideoFilters/Vaapi/VaapiVideoFilterOptions.cs @@ -0,0 +1,54 @@ +using System.Drawing; +using FFMpegCore.Enums; + +namespace FFMpegCore.Arguments.VideoFilters.Vaapi; + +/// +/// +/// +public sealed class VaapiVideoFilterOptions +{ + private readonly VideoFilterOptions _options; + + internal VaapiVideoFilterOptions(VideoFilterOptions options) + { + _options = options; + } + + /// + /// + /// + /// + /// + public VaapiVideoFilterOptions Scale(VideoSize videosize) + { + return WithArgument(new VideoFilterScaleVaapiArgument(videosize)); + } + + /// + /// + /// + /// + /// + /// + public VaapiVideoFilterOptions Scale(int width, int height) + { + return WithArgument(new VideoFilterScaleVaapiArgument(width, height)); + } + + /// + /// + /// + /// + /// + public VaapiVideoFilterOptions Scale(Size size) + { + return WithArgument(new VideoFilterScaleVaapiArgument(size)); + } + + public VaapiVideoFilterOptions WithArgument(IVaapiVideoFilterArgument argument) + { + _options.Arguments.Add(argument); + return this; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/VideoFilters/Vaapi/VideoFilterScaleVaapiArgument.cs b/FFMpegCore/FFMpeg/Arguments/VideoFilters/Vaapi/VideoFilterScaleVaapiArgument.cs new file mode 100644 index 0000000..a1a6878 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/VideoFilters/Vaapi/VideoFilterScaleVaapiArgument.cs @@ -0,0 +1,29 @@ +using System.Drawing; +using FFMpegCore.Enums; + +namespace FFMpegCore.Arguments.VideoFilters.Vaapi; + +/// +/// +/// undocumented in: +/// may refer to: +/// +public sealed class VideoFilterScaleVaapiArgument : IVaapiVideoFilterArgument +{ + private readonly Size? _size; + + public VideoFilterScaleVaapiArgument(Size? size) + { + _size = size; + } + + public VideoFilterScaleVaapiArgument(int width, int height) : this(new Size(width, height)) { } + + public VideoFilterScaleVaapiArgument(VideoSize videosize) + { + _size = videosize == VideoSize.Original ? null : new Size(-1, (int)videosize); + } + + public string Key => "scale_vaapi"; + public string Value => _size == null ? string.Empty : $"{_size.Value.Width}:{_size.Value.Height}"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/VideoFilters/VideoFilterFormatArgument.cs b/FFMpegCore/FFMpeg/Arguments/VideoFilters/VideoFilterFormatArgument.cs new file mode 100644 index 0000000..0034522 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/VideoFilters/VideoFilterFormatArgument.cs @@ -0,0 +1,26 @@ +namespace FFMpegCore.Arguments.VideoFilters; + +/// +/// +/// Convert the input video to one of the specified pixel formats. +/// Libavfilter will try to pick one that is suitable as input to the next filter. +/// +public sealed class VideoFilterFormatArgument : IVideoFilterArgument +{ + public string Key => "format"; + public string Value { get; } + + private VideoFilterFormatArgument(string pixelFormat) + { + Value = $"pix_fmts={pixelFormat}"; + } + + public VideoFilterFormatArgument(params ReadOnlySpan pixelFormats) +#if NETSTANDARD2_1_OR_GREATER || NET8_OR_GREATER + : this(string.Join('|', pixelFormats)) +#else + : this(string.Join("|", pixelFormats.ToArray())) +#endif + { + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/VideoFilters/VideoFilterHardwareUploadArgument.cs b/FFMpegCore/FFMpeg/Arguments/VideoFilters/VideoFilterHardwareUploadArgument.cs new file mode 100644 index 0000000..011012d --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/VideoFilters/VideoFilterHardwareUploadArgument.cs @@ -0,0 +1,11 @@ +namespace FFMpegCore.Arguments.VideoFilters; + +/// +/// +/// Upload system memory frames to hardware surfaces. +/// +public sealed class VideoFilterHardwareUploadArgument : IVideoFilterArgument +{ + public string Key => string.Empty; + public string Value => "hwupload"; +} diff --git a/FFMpegCore/FFMpeg/Arguments/VideoFiltersArgument.cs b/FFMpegCore/FFMpeg/Arguments/VideoFiltersArgument.cs index 40d1bb1..5c7d1f5 100644 --- a/FFMpegCore/FFMpeg/Arguments/VideoFiltersArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/VideoFiltersArgument.cs @@ -1,4 +1,6 @@ using System.Drawing; +using FFMpegCore.Arguments.VideoFilters; +using FFMpegCore.Arguments.VideoFilters.Vaapi; using FFMpegCore.Enums; using FFMpegCore.Exceptions; @@ -94,7 +96,32 @@ public class VideoFilterOptions return WithArgument(new PadArgument(padOptions)); } - private VideoFilterOptions WithArgument(IVideoFilterArgument argument) + /// + /// + /// + /// + /// + public VideoFilterOptions Format(params ReadOnlySpan pixelFormats) + { + return WithArgument(new VideoFilterFormatArgument(pixelFormats)); + } + + /// + /// + /// + /// + public VideoFilterOptions HardwareUpload() + { + return WithArgument(new VideoFilterHardwareUploadArgument()); + } + + public VideoFilterOptions WithVaapiVideoFilter(Action setupAction) + { + setupAction(new VaapiVideoFilterOptions(this)); + return this; + } + + public VideoFilterOptions WithArgument(IVideoFilterArgument argument) { Arguments.Add(argument); return this; diff --git a/FFMpegCore/FFMpeg/Arguments/VideoOptions/HardwareAccelerationOutputFormatArgument.cs b/FFMpegCore/FFMpeg/Arguments/VideoOptions/HardwareAccelerationOutputFormatArgument.cs new file mode 100644 index 0000000..0cb35af --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/VideoOptions/HardwareAccelerationOutputFormatArgument.cs @@ -0,0 +1,12 @@ +using FFMpegCore.Enums; + +namespace FFMpegCore.Arguments.VideoOptions; + +/// +/// +/// undocumented in +/// +public sealed class HardwareAccelerationOutputFormatArgument(HardwareAccelerationDevice hardwareAccelerationDevice) : IArgument +{ + public string Text => $"-hwaccel_output_format {hardwareAccelerationDevice.ToString().ToLowerInvariant()}"; +} diff --git a/FFMpegCore/FFMpeg/Enums/RtspTransportProtocol.cs b/FFMpegCore/FFMpeg/Enums/RtspTransportProtocol.cs new file mode 100644 index 0000000..7bcfa59 --- /dev/null +++ b/FFMpegCore/FFMpeg/Enums/RtspTransportProtocol.cs @@ -0,0 +1,14 @@ +namespace FFMpegCore.Enums; + +/// +/// +/// RTSP transport protocols +/// +public enum RtspTransportProtocol +{ + udp, + tcp, + udp_multicast, + http, + https, +} diff --git a/FFMpegCore/FFMpeg/Enums/VaapiRcMode.cs b/FFMpegCore/FFMpeg/Enums/VaapiRcMode.cs new file mode 100644 index 0000000..0277e90 --- /dev/null +++ b/FFMpegCore/FFMpeg/Enums/VaapiRcMode.cs @@ -0,0 +1,42 @@ +namespace FFMpegCore.Enums; + +/// +/// +/// +public enum VaapiRcMode +{ + /// + /// Choose the mode automatically based on driver support and the other options. This is the default. + /// + auto, + + /// + /// Constant-quality. + /// + CQP, + + /// + /// Constant-bitrate. + /// + CBR, + + /// + /// Variable-bitrate. + /// + VBR, + + /// + /// Intelligent constant-quality. + /// + ICQ, + + /// + /// Quality-defined variable-bitrate. + /// + QVBR, + + /// + /// Average variable bitrate. + /// + AVBR, +} diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs b/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs index 4b6a170..6937692 100644 --- a/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs +++ b/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs @@ -1,5 +1,11 @@ using System.Drawing; using FFMpegCore.Arguments; +using FFMpegCore.Arguments.Codecs.Vaapi; +using FFMpegCore.Arguments.Codecs.Vaapi.h264Vaapi; +using FFMpegCore.Arguments.Formats; +using FFMpegCore.Arguments.Formats.image2; +using FFMpegCore.Arguments.Formats.segment; +using FFMpegCore.Arguments.Protocols.Rtsp; using FFMpegCore.Enums; namespace FFMpegCore; @@ -268,6 +274,70 @@ public class FFMpegArgumentOptions : FFMpegArgumentsBase return WithArgument(new CopyCodecArgument()); } + /// + /// + /// + /// + /// + public FFMpegArgumentOptions WithUseWallclockAsTimestamps(bool value = true) + { + return WithArgument(new UseWallclockAsTimestampsArgument(value)); + } + + /// + /// + /// + /// + /// + public FFMpegArgumentOptions WithAnalyzeDuration(TimeSpan duration) + { + return WithArgument(new AnalyzeDurationArgument(duration)); + } + + /// + /// + /// + /// + /// + public FFMpegArgumentOptions WithProbeSize(long probesizeInBytes = 5000000) + { + return WithArgument(new ProbeSizeArgument(probesizeInBytes)); + } + + /// + /// + /// + /// + /// + public FFMpegArgumentOptions WithVaapiRcMode(VaapiRcMode rcMode) + { + return WithArgument(new VaapiRcModeArgument(rcMode)); + } + + public FFMpegArgumentOptions WithImage2Options(Action setupAction) + { + setupAction(new Image2ArgumentOptions(this)); + return this; + } + + public FFMpegArgumentOptions WithSegmentOptions(Action setupAction) + { + setupAction(new SegmentArgumentOptions(this)); + return this; + } + + public FFMpegArgumentOptions WithH264VaapiOptions(Action setupAction) + { + setupAction(new H264VaapiArgumentOptions(this)); + return this; + } + + public FFMpegArgumentOptions WithRtspProtocolOptions(Action setupAction) + { + setupAction(new RtspArgumentOptions(this)); + return this; + } + public FFMpegArgumentOptions WithArgument(IArgument argument) { Arguments.Add(argument); diff --git a/FFMpegCore/FFMpeg/FFMpegGlobalArguments.cs b/FFMpegCore/FFMpeg/FFMpegGlobalArguments.cs index 069244a..7c8c30d 100644 --- a/FFMpegCore/FFMpeg/FFMpegGlobalArguments.cs +++ b/FFMpegCore/FFMpeg/FFMpegGlobalArguments.cs @@ -1,4 +1,8 @@ using FFMpegCore.Arguments; +using FFMpegCore.Arguments.GenericOptions; +using FFMpegCore.Arguments.MainOptions; +using FFMpegCore.Arguments.VideoOptions; +using FFMpegCore.Enums; namespace FFMpegCore; @@ -8,10 +12,38 @@ public sealed class FFMpegGlobalArguments : FFMpegArgumentsBase public FFMpegGlobalArguments WithVerbosityLevel(VerbosityLevel verbosityLevel = VerbosityLevel.Error) { - return WithOption(new VerbosityLevelArgument(verbosityLevel)); + return WithArgument(new VerbosityLevelArgument(verbosityLevel)); } - private FFMpegGlobalArguments WithOption(IArgument argument) + /// + /// + /// + /// + /// + public FFMpegGlobalArguments WithHardwareAccelerationOutputFormat(HardwareAccelerationDevice device) + { + return WithArgument(new HardwareAccelerationOutputFormatArgument(device)); + } + + /// + /// + /// + /// + public FFMpegGlobalArguments WithHideBanner() + { + return WithArgument(new HideBanner()); + } + + /// + /// + /// + /// + public FFMpegGlobalArguments WithNoStats() + { + return WithArgument(new Stats(false)); + } + + public FFMpegGlobalArguments WithArgument(IArgument argument) { Arguments.Add(argument); return this;