Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 218 additions & 0 deletions FFMpegCore.Test/ArgumentBuilderTest.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Drawing;
using FFMpegCore.Arguments;
using FFMpegCore.Arguments.MainOptions;
using FFMpegCore.Enums;
using FFMpegCore.Pipes;

Expand Down Expand Up @@ -46,6 +47,38 @@ public void Builder_BuildString_AudioBitrate()
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()
{
Expand Down Expand Up @@ -552,6 +585,191 @@ public void Builder_BuildString_PadFilter_Alt()
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()
{
Expand Down
11 changes: 11 additions & 0 deletions FFMpegCore/FFMpeg/Arguments/BaseBoolArgument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace FFMpegCore.Arguments;

/// <summary>
/// Base class for boolean arguments with value <c>0</c> or <c>1</c>.
/// </summary>
/// <param name="value"></param>
public abstract class BaseBoolArgument(bool value) : IArgument
{
protected abstract string ArgumentName { get; }
public string Text => $"-{ArgumentName} {(value ? '1' : '0')}";
}
14 changes: 14 additions & 0 deletions FFMpegCore/FFMpeg/Arguments/BaseOptionArgument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace FFMpegCore.Arguments;

/// <summary>
/// <see href="https://ffmpeg.org/ffmpeg.html#Options" />
/// 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.
/// </summary>
/// <param name="value"></param>
public abstract class BaseOptionArgument(bool value) : IArgument
{
protected abstract string ArgumentName { get; }
public string Text => $"-{(value ? "" : "no")}{ArgumentName}";
}
12 changes: 12 additions & 0 deletions FFMpegCore/FFMpeg/Arguments/Codecs/Vaapi/VaapiRcModeArgument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using FFMpegCore.Enums;

namespace FFMpegCore.Arguments.Codecs.Vaapi;

/// <summary>
/// <see href="https://www.ffmpeg.org/ffmpeg-codecs.html#VAAPI-encoders" />
/// Set the rate control mode to use. A given driver may only support a subset of modes.
/// </summary>
public sealed class VaapiRcModeArgument(VaapiRcMode rcMode) : IArgument
{
public string Text => $"-rc_mode {rcMode}";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace FFMpegCore.Arguments.Codecs.Vaapi.h264Vaapi;

/// <summary>
/// <see href="https://ffmpeg.org/ffmpeg-formats.html#image2_002c-image2pipe" />
/// </summary>
public sealed class H264VaapiArgumentOptions
{
private readonly FFMpegArgumentOptions _options;

internal H264VaapiArgumentOptions(FFMpegArgumentOptions options)
{
_options = options;
}

/// <summary>
/// <inheritdoc cref="VaapiQpArgument"/>
/// </summary>
/// <param name="quantizer"><c>0</c> - <c>52</c></param>
/// <returns></returns>
public H264VaapiArgumentOptions WithQuantizer(sbyte quantizer)
{
return WithArgument(new VaapiQpArgument(quantizer));
}

public H264VaapiArgumentOptions WithArgument(IH264VaapiArgument argument)
{
_options.WithArgument(argument);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace FFMpegCore.Arguments.Codecs.Vaapi.h264Vaapi;

public interface IH264VaapiArgument : IArgument;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace FFMpegCore.Arguments.Codecs.Vaapi.h264Vaapi;

/// <summary>
/// undocumented(?) <see href="https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/vaapi_encode_h264.c#L1073"/>
/// Constant QP (for P-frames; scaled by qfactor/qoffset for I/B)
/// </summary>
/// <param name="quantizer"><c>0</c> - <c>52</c></param>
public sealed class VaapiQpArgument(sbyte quantizer) : IH264VaapiArgument
{
public string Text => $"-qp {quantizer}";
}
19 changes: 19 additions & 0 deletions FFMpegCore/FFMpeg/Arguments/Formats/AnalyzeDurationArgument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace FFMpegCore.Arguments.Formats;

/// <summary>
/// <see href="https://ffmpeg.org/ffmpeg-formats.html#Format-Options"/>
/// 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.
/// </summary>
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}";
}
3 changes: 3 additions & 0 deletions FFMpegCore/FFMpeg/Arguments/Formats/IDemuxerArgument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace FFMpegCore.Arguments.Formats;

public interface IDemuxerArgument : IArgument;
3 changes: 3 additions & 0 deletions FFMpegCore/FFMpeg/Arguments/Formats/IMuxerArgument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace FFMpegCore.Arguments.Formats;

public interface IMuxerArgument : IArgument;
12 changes: 12 additions & 0 deletions FFMpegCore/FFMpeg/Arguments/Formats/ProbeSizeArgument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace FFMpegCore.Arguments.Formats;

/// <summary>
/// <see href="https://ffmpeg.org/ffmpeg-formats.html#Format-Options"/>
/// 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.
/// </summary>
public sealed class ProbeSizeArgument(long probesize) : IArgument
{
public string Text => $"-probesize {probesize}";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace FFMpegCore.Arguments.Formats;

/// <summary>
/// <see href="https://ffmpeg.org/ffmpeg-formats.html#Format-Options" />
/// Use wallclock as timestamps if set to 1. Default is 0.
/// </summary>
public sealed class UseWallclockAsTimestampsArgument(bool value) : BaseBoolArgument(value)
{
protected override string ArgumentName => "use_wallclock_as_timestamps";
}
3 changes: 3 additions & 0 deletions FFMpegCore/FFMpeg/Arguments/Formats/image2/IImage2Argument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace FFMpegCore.Arguments.Formats.image2;

public interface IImage2Argument : IArgument;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace FFMpegCore.Arguments.Formats.image2;

public interface IImage2MuxerArgument :
IImage2Argument,
IDemuxerArgument;
Loading
Loading