Skip to content

Commit

Permalink
Merge branch 'main' into chore/deterministic-dotnet-builds
Browse files Browse the repository at this point in the history
  • Loading branch information
vladjerca authored Jun 28, 2024
2 parents e9264a3 + eb221c3 commit 5e62d9b
Show file tree
Hide file tree
Showing 24 changed files with 463 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="SkiaSharp" Version="2.88.3" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.3" />
<PackageReference Include="SkiaSharp" Version="2.88.6" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.6" />
</ItemGroup>

<ItemGroup>
Expand Down
76 changes: 75 additions & 1 deletion FFMpegCore.Test/ArgumentBuilderTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FFMpegCore.Arguments;
using System.Drawing;
using FFMpegCore.Arguments;
using FFMpegCore.Enums;
using Microsoft.VisualStudio.TestTools.UnitTesting;

Expand Down Expand Up @@ -537,5 +538,78 @@ public void Builder_BuildString_PadFilter_Alt()
"-i \"input.mp4\" -vf \"pad=aspect=4/3:x=(ow-iw)/2:y=(oh-ih)/2:color=violet:eval=frame\" \"output.mp4\"",
str);
}

[TestMethod]
public void Builder_BuildString_GifPalette()
{
var streamIndex = 0;
var size = new Size(640, 480);

var str = FFMpegArguments
.FromFileInput("input.mp4")
.OutputToFile("output.gif", false, opt => opt
.WithGifPaletteArgument(streamIndex, size))
.Arguments;

Assert.AreEqual($"""
-i "input.mp4" -filter_complex "[0:v] fps=12,scale=w={size.Width}:h={size.Height},split [a][b];[a] palettegen=max_colors=32 [p];[b][p] paletteuse=dither=bayer" "output.gif"
""", str);
}

[TestMethod]
public void Builder_BuildString_GifPalette_NullSize_FpsSupplied()
{
var streamIndex = 1;

var str = FFMpegArguments
.FromFileInput("input.mp4")
.OutputToFile("output.gif", false, opt => opt
.WithGifPaletteArgument(streamIndex, null, 10))
.Arguments;

Assert.AreEqual($"""
-i "input.mp4" -filter_complex "[{streamIndex}:v] fps=10,split [a][b];[a] palettegen=max_colors=32 [p];[b][p] paletteuse=dither=bayer" "output.gif"
""", str);
}

[TestMethod]
public void Builder_BuildString_MultiOutput()
{
var str = FFMpegArguments.FromFileInput("input.mp4")
.MultiOutput(args => args
.OutputToFile("output.mp4", overwrite: true, args => args.CopyChannel())
.OutputToFile("output.ts", overwrite: false, args => args.CopyChannel().ForceFormat("mpegts"))
.OutputToUrl("http://server/path", options => options.ForceFormat("webm")))
.Arguments;
Assert.AreEqual($"""
-i "input.mp4" -c:a copy -c:v copy "output.mp4" -y -c:a copy -c:v copy -f mpegts "output.ts" -f webm http://server/path
""", str);
}

[TestMethod]
public void Builder_BuildString_MBROutput()
{
var str = FFMpegArguments.FromFileInput("input.mp4")
.MultiOutput(args => args
.OutputToFile("sd.mp4", overwrite: true, args => args.Resize(1200, 720))
.OutputToFile("hd.mp4", overwrite: false, args => args.Resize(1920, 1080)))
.Arguments;
Assert.AreEqual($"""
-i "input.mp4" -s 1200x720 "sd.mp4" -y -s 1920x1080 "hd.mp4"
""", str);
}

[TestMethod]
public void Builder_BuildString_TeeOutput()
{
var str = FFMpegArguments.FromFileInput("input.mp4")
.OutputToTee(args => args
.OutputToFile("output.mp4", overwrite: false, args => args.WithFastStart())
.OutputToUrl("http://server/path", options => options.ForceFormat("mpegts").SelectStream(0, channel: Channel.Video)))
.Arguments;
Assert.AreEqual($"""
-i "input.mp4" -f tee "[movflags=faststart]output.mp4|[f=mpegts:select=\'0:v:0\']http://server/path"
""", str);
}
}
}
14 changes: 7 additions & 7 deletions FFMpegCore.Test/FFMpegCore.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" Version="6.10.0" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.0.1">
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.0.2" />
<PackageReference Include="SkiaSharp" Version="2.88.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="SkiaSharp" Version="2.88.6" />
</ItemGroup>

<ItemGroup>
Expand Down
14 changes: 11 additions & 3 deletions FFMpegCore.Test/FFProbeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public async Task PacketAnalysis_Async()
var packets = packetAnalysis.Packets;
Assert.AreEqual(96, packets.Count);
Assert.IsTrue(packets.All(f => f.CodecType == "video"));
Assert.AreEqual("K_", packets[0].Flags);
Assert.IsTrue(packets[0].Flags.StartsWith("K_"));
Assert.AreEqual(1362, packets.Last().Size);
}

Expand All @@ -57,7 +57,7 @@ public void PacketAnalysis_Sync()

Assert.AreEqual(96, packets.Count);
Assert.IsTrue(packets.All(f => f.CodecType == "video"));
Assert.AreEqual("K_", packets[0].Flags);
Assert.IsTrue(packets[0].Flags.StartsWith("K_"));
Assert.AreEqual(1362, packets.Last().Size);
}

Expand All @@ -70,7 +70,7 @@ public void PacketAnalysisAudioVideo_Sync()
var actual = packets.Select(f => f.CodecType).Distinct().ToList();
var expected = new List<string> { "audio", "video" };
CollectionAssert.AreEquivalent(expected, actual);
Assert.IsTrue(packets.Where(t => t.CodecType == "audio").All(f => f.Flags == "K_"));
Assert.IsTrue(packets.Where(t => t.CodecType == "audio").All(f => f.Flags.StartsWith("K_")));
Assert.AreEqual(75, packets.Count(t => t.CodecType == "video"));
Assert.AreEqual(141, packets.Count(t => t.CodecType == "audio"));
}
Expand Down Expand Up @@ -105,6 +105,7 @@ public void Probe_Success()
{
var info = FFProbe.Analyse(TestResources.Mp4Video);
Assert.AreEqual(3, info.Duration.Seconds);
Assert.AreEqual(0, info.Chapters.Count);

Assert.AreEqual("5.1", info.PrimaryAudioStream!.ChannelLayout);
Assert.AreEqual(6, info.PrimaryAudioStream.Channels);
Expand Down Expand Up @@ -235,5 +236,12 @@ public async Task Probe_Success_32BitWavBitDepth_Async()
Assert.IsNotNull(info.PrimaryAudioStream);
Assert.AreEqual(32, info.PrimaryAudioStream.BitDepth);
}

[TestMethod]
public void Probe_Success_Custom_Arguments()
{
var info = FFProbe.Analyse(TestResources.Mp4Video, customArguments: "-headers \"Hello: World\"");
Assert.AreEqual(3, info.Duration.Seconds);
}
}
}
61 changes: 60 additions & 1 deletion FFMpegCore.Test/VideoTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Drawing.Imaging;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.Versioning;
using System.Text;
using FFMpegCore.Arguments;
Expand Down Expand Up @@ -479,6 +480,64 @@ public void Video_Snapshot_PersistSnapshot()
Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName);
}

[TestMethod, Timeout(BaseTimeoutMilliseconds)]
public void Video_GifSnapshot_PersistSnapshot()
{
using var outputPath = new TemporaryFile("out.gif");
var input = FFProbe.Analyse(TestResources.Mp4Video);

FFMpeg.GifSnapshot(TestResources.Mp4Video, outputPath, captureTime: TimeSpan.FromSeconds(0));

var analysis = FFProbe.Analyse(outputPath);
Assert.AreNotEqual(input.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Width);
Assert.AreNotEqual(input.PrimaryVideoStream.Height, analysis.PrimaryVideoStream!.Height);
Assert.AreEqual("gif", analysis.PrimaryVideoStream!.CodecName);
}

[TestMethod, Timeout(BaseTimeoutMilliseconds)]
public void Video_GifSnapshot_PersistSnapshot_SizeSupplied()
{
using var outputPath = new TemporaryFile("out.gif");
var input = FFProbe.Analyse(TestResources.Mp4Video);
var desiredGifSize = new Size(320, 240);

FFMpeg.GifSnapshot(TestResources.Mp4Video, outputPath, desiredGifSize, captureTime: TimeSpan.FromSeconds(0));

var analysis = FFProbe.Analyse(outputPath);
Assert.AreNotEqual(input.PrimaryVideoStream!.Width, desiredGifSize.Width);
Assert.AreNotEqual(input.PrimaryVideoStream.Height, desiredGifSize.Height);
Assert.AreEqual("gif", analysis.PrimaryVideoStream!.CodecName);
}

[TestMethod, Timeout(BaseTimeoutMilliseconds)]
public async Task Video_GifSnapshot_PersistSnapshotAsync()
{
using var outputPath = new TemporaryFile("out.gif");
var input = FFProbe.Analyse(TestResources.Mp4Video);

await FFMpeg.GifSnapshotAsync(TestResources.Mp4Video, outputPath, captureTime: TimeSpan.FromSeconds(0));

var analysis = FFProbe.Analyse(outputPath);
Assert.AreNotEqual(input.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Width);
Assert.AreNotEqual(input.PrimaryVideoStream.Height, analysis.PrimaryVideoStream!.Height);
Assert.AreEqual("gif", analysis.PrimaryVideoStream!.CodecName);
}

[TestMethod, Timeout(BaseTimeoutMilliseconds)]
public async Task Video_GifSnapshot_PersistSnapshotAsync_SizeSupplied()
{
using var outputPath = new TemporaryFile("out.gif");
var input = FFProbe.Analyse(TestResources.Mp4Video);
var desiredGifSize = new Size(320, 240);

await FFMpeg.GifSnapshotAsync(TestResources.Mp4Video, outputPath, desiredGifSize, captureTime: TimeSpan.FromSeconds(0));

var analysis = FFProbe.Analyse(outputPath);
Assert.AreNotEqual(input.PrimaryVideoStream!.Width, desiredGifSize.Width);
Assert.AreNotEqual(input.PrimaryVideoStream.Height, desiredGifSize.Height);
Assert.AreEqual("gif", analysis.PrimaryVideoStream!.CodecName);
}

[TestMethod, Timeout(BaseTimeoutMilliseconds)]
public void Video_Join()
{
Expand Down
10 changes: 10 additions & 0 deletions FFMpegCore/FFMpeg/Arguments/CopyCodecArgument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace FFMpegCore.Arguments
{
/// <summary>
/// Represents a copy codec parameter
/// </summary>
public class CopyCodecArgument : IArgument
{
public string Text => $"-codec copy";
}
}
24 changes: 24 additions & 0 deletions FFMpegCore/FFMpeg/Arguments/GifPaletteArgument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Drawing;

namespace FFMpegCore.Arguments
{
public class GifPaletteArgument : IArgument
{
private readonly int _streamIndex;

private readonly int _fps;

private readonly Size? _size;

public GifPaletteArgument(int streamIndex, int fps, Size? size)
{
_streamIndex = streamIndex;
_fps = fps;
_size = size;
}

private string ScaleText => _size.HasValue ? $"scale=w={_size.Value.Width}:h={_size.Value.Height}," : string.Empty;

public string Text => $"-filter_complex \"[{_streamIndex}:v] fps={_fps},{ScaleText}split [a][b];[a] palettegen=max_colors=32 [p];[b][p] paletteuse=dither=bayer\"";
}
}
57 changes: 57 additions & 0 deletions FFMpegCore/FFMpeg/Arguments/OutputTeeArgument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

namespace FFMpegCore.Arguments
{
internal class OutputTeeArgument : IOutputArgument
{
private readonly FFMpegMultiOutputOptions _options;

public OutputTeeArgument(FFMpegMultiOutputOptions options)
{
if (options.Outputs.Count == 0)
{
throw new ArgumentException("Atleast one output must be specified.", nameof(options));
}

_options = options;
}

public string Text => $"-f tee \"{string.Join("|", _options.Outputs.Select(MapOptions))}\"";

public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;

public void Post()
{
}

public void Pre()
{
}

private static string MapOptions(FFMpegArgumentOptions option)
{
var optionPrefix = string.Empty;
if (option.Arguments.Count > 1)
{
var options = option.Arguments.Take(option.Arguments.Count - 1);
optionPrefix = $"[{string.Join(":", options.Select(MapArgument))}]";
}

var output = option.Arguments.OfType<IOutputArgument>().Single();
return $"{optionPrefix}{output.Text.Trim('"')}";
}

private static string MapArgument(IArgument argument)
{
if (argument is MapStreamArgument map)
{
return map.Text.Replace("-map ", "select=\\'") + "\\'";
}
else if (argument is BitStreamFilterArgument bitstreamFilter)
{
return bitstreamFilter.Text.Replace("-bsf:", "bsfs/").Replace(' ', '=');
}

return argument.Text.TrimStart('-').Replace(' ', '=');
}
}
}
2 changes: 2 additions & 0 deletions FFMpegCore/FFMpeg/Builders/MetaData/ChapterData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ public class ChapterData
public TimeSpan Start { get; private set; }
public TimeSpan End { get; private set; }

public TimeSpan Duration => End - Start;

public ChapterData(string title, TimeSpan start, TimeSpan end)
{
Title = title;
Expand Down
1 change: 1 addition & 0 deletions FFMpegCore/FFMpeg/Enums/FileExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ public static string Extension(this Codec type)
public static readonly string WebM = VideoType.WebM.Extension;
public static readonly string Png = ".png";
public static readonly string Mp3 = ".mp3";
public static readonly string Gif = ".gif";
}
}
Loading

0 comments on commit 5e62d9b

Please sign in to comment.