fix: Snapshots from rotated videos should have correct width/height

Some video-files have their metadata as -90 instead of 90. This PR handles correct aspect-ratio and rotation when taking snapshots.

The sample video provided in the PR is my own recording. A bit too big maybe? I couldn't find any command to set the metadata to `-90` on existing video. Would be happy to get it working on already existing test-data to reduce size.

Fixes #509
This commit is contained in:
Fredrik Hagfjäll 2024-03-30 09:10:13 +01:00
parent eb221c3e49
commit 82cc9715dc
6 changed files with 33 additions and 1 deletions

View file

@ -51,6 +51,9 @@
<None Update="Resources\input_3sec_rotation_90deg.mp4"> <None Update="Resources\input_3sec_rotation_90deg.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Update="Resources\input_3sec_rotation_negative_90deg.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\input_audio_only_10sec.mp4"> <None Update="Resources\input_audio_only_10sec.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>

View file

@ -145,6 +145,13 @@ public void Probe_Rotation()
Assert.AreEqual(90, info.PrimaryVideoStream.Rotation); Assert.AreEqual(90, info.PrimaryVideoStream.Rotation);
} }
[TestMethod]
public void Probe_Rotation_Negative_Value()
{
var info = FFProbe.Analyse(TestResources.Mp4VideoRotationNegative);
Assert.AreEqual(-90, info.PrimaryVideoStream.Rotation);
}
[TestMethod, Timeout(10000)] [TestMethod, Timeout(10000)]
public async Task Probe_Async_Success() public async Task Probe_Async_Success()
{ {

View file

@ -4,6 +4,7 @@ public static class TestResources
{ {
public static readonly string Mp4Video = "./Resources/input_3sec.mp4"; public static readonly string Mp4Video = "./Resources/input_3sec.mp4";
public static readonly string Mp4VideoRotation = "./Resources/input_3sec_rotation_90deg.mp4"; public static readonly string Mp4VideoRotation = "./Resources/input_3sec_rotation_90deg.mp4";
public static readonly string Mp4VideoRotationNegative = "./Resources/input_3sec_rotation_negative_90deg.mp4";
public static readonly string WebmVideo = "./Resources/input_3sec.webm"; public static readonly string WebmVideo = "./Resources/input_3sec.webm";
public static readonly string Mp4WithoutVideo = "./Resources/input_audio_only_10sec.mp4"; public static readonly string Mp4WithoutVideo = "./Resources/input_audio_only_10sec.mp4";
public static readonly string Mp4WithoutAudio = "./Resources/input_video_only_3sec.mp4"; public static readonly string Mp4WithoutAudio = "./Resources/input_video_only_3sec.mp4";

View file

@ -480,6 +480,21 @@ public void Video_Snapshot_PersistSnapshot()
Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName); Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
public void Video_Snapshot_Rotated_PersistSnapshot()
{
using var outputPath = new TemporaryFile("out.png");
var input = FFProbe.Analyse(TestResources.Mp4VideoRotationNegative);
FFMpeg.Snapshot(TestResources.Mp4VideoRotationNegative, outputPath);
var analysis = FFProbe.Analyse(outputPath);
// height and width should be swapped
Assert.AreEqual(input.PrimaryVideoStream!.Height, analysis.PrimaryVideoStream!.Width);
Assert.AreEqual(input.PrimaryVideoStream.Width, analysis.PrimaryVideoStream!.Height);
Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName);
}
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod, Timeout(BaseTimeoutMilliseconds)]
public void Video_GifSnapshot_PersistSnapshot() public void Video_GifSnapshot_PersistSnapshot()
{ {

View file

@ -64,7 +64,7 @@ public static (FFMpegArguments, Action<FFMpegArgumentOptions> outputOptions) Bui
} }
var currentSize = new Size(source.PrimaryVideoStream.Width, source.PrimaryVideoStream.Height); var currentSize = new Size(source.PrimaryVideoStream.Width, source.PrimaryVideoStream.Height);
if (source.PrimaryVideoStream.Rotation == 90 || source.PrimaryVideoStream.Rotation == 180) if (IsRotated(source.PrimaryVideoStream.Rotation))
{ {
currentSize = new Size(source.PrimaryVideoStream.Height, source.PrimaryVideoStream.Width); currentSize = new Size(source.PrimaryVideoStream.Height, source.PrimaryVideoStream.Width);
} }
@ -88,4 +88,10 @@ public static (FFMpegArguments, Action<FFMpegArgumentOptions> outputOptions) Bui
return null; return null;
} }
private static bool IsRotated(int rotation)
{
var absRotation = Math.Abs(rotation);
return absRotation == 90 || absRotation == 180;
}
} }