using VFXReviewWorker; using Xunit; namespace VFXReviewWorker.Tests; /// /// The preview MP4 opens on the Netflix slate. ffmpeg's thumbnail filter /// only considers the first 100 frames, so every thumbnail the pipeline has ever /// produced was a picture of the slate card rather than of the shot — invisible /// while thumbnails were only decoration in the QC queue, and unacceptable now /// they can become a shot's thumbnail. /// /// The fix is to seek past the slate, so the seek arithmetic is worth pinning. /// public class ThumbnailSeekTests { private static PreviewBuildResult Build(double? pictureStart, int? frameCount) => new() { PictureStartSeconds = pictureStart, FrameCount = frameCount }; [Fact] public void SeeksAQuarterIntoPictureNotIntoTheFile() { // 5s slate, then 96 frames (4s) of picture at 24fps → 5 + 1 = 6s. var seek = PreviewStage.ThumbnailSeekSeconds(Build(5.0, 96), 24); Assert.NotNull(seek); Assert.Equal(6.0, seek!.Value, 3); } [Fact] public void HandlesATemplateWithNoSlateAtAll() { var seek = PreviewStage.ThumbnailSeekSeconds(Build(0.0, 240), 24); Assert.Equal(2.5, seek!.Value, 3); } [Fact] public void FallsBackWhenTheBuildDidNotReportWherePictureStarts() { // Older worker, or a template with no SHOT layer: null means "keep the // old whole-file behaviour" rather than guessing an offset. Assert.Null(PreviewStage.ThumbnailSeekSeconds(Build(null, 96), 24)); Assert.Null(PreviewStage.ThumbnailSeekSeconds(null, 24)); } [Theory] [InlineData(-1.0, 96, 24)] // negative slate length [InlineData(5.0, 0, 24)] // no picture frames [InlineData(5.0, -5, 24)] // nonsense frame count [InlineData(5.0, 96, 0)] // no fps to divide by public void RefusesNumbersThatDoNotDescribeARealClip(double start, int frames, double fps) { Assert.Null(PreviewStage.ThumbnailSeekSeconds(Build(start, frames), fps)); } }