using System; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Xunit; using ytLive.Services; using ytLive.Services.Encoder; namespace ytLive.Tests; /// /// TASK 64 against a REAL clip (creator's `test_greenscreen_moving_subject.mp4`, 1920×1080, 5 s, a /// subject moving over a green field). The detector had only ever seen synthetic frames; this runs /// the bundled ffmpeg, our probe, our detector, and our keyed decode on actual video. /// It also pins the decision that the key colour comes from the measured screen rather than a /// canonical green — on this clip the field is #00FE00 and a hardcoded #00B140 keys nothing /// (measured: 0.0% of pixels transparent), while the detected colour keys 97.8%. /// public class ChromaKeyRealClipTests { private const string ClipName = "test_greenscreen_moving_subject.mp4"; private static string ClipPath => Path.Combine(AppContext.BaseDirectory, "Fixtures", ClipName); private static string FfmpegPath => Path.Combine(AppContext.BaseDirectory, "ffmpeg", "ffmpeg.exe"); [Fact] public async Task RealGreenScreenClip_IsDetected_AndKeysWithTheMeasuredColour() { Assert.True(File.Exists(ClipPath), $"missing fixture: {ClipPath}"); Assert.True(File.Exists(FfmpegPath), $"the bundled ffmpeg should sit beside the test binary: {FfmpegPath}"); // 1. The probe reads one real frame of the clip. var probe = new FfmpegChromaProbe(new FixedLocator(FfmpegPath), () => new FfmpegDecodeProcess()); var frame = await probe.ProbeAsync(ClipPath); Assert.NotNull(frame); // 2. The detector says "green screen", with high confidence. var suggestion = ChromaKeyDetector.Detect(frame!); Assert.Equal(ChromaScreen.Green, suggestion.Screen); Assert.True(suggestion.IsHighConfidence, $"a clean green field should be high confidence, got {suggestion.Confidence:0.00}"); Assert.NotNull(suggestion.ColorHex); // …and the colour it names is the field as shot: green-dominant, almost no red or blue. var (r, g, b) = ParseHex(suggestion.ColorHex!); Assert.True(g > 0xE0 && r < 0x20 && b < 0x20, $"the detected colour should be the measured field, got {suggestion.ColorHex}"); // 3. That colour actually keys the clip through our own pipe: most pixels go transparent, // the moving subject survives. var keyed = ChromaKeyOptions.For(suggestion); var firstFrame = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); using var pipe = new VideoClipPipe( ClipPath, 320, 180, new FixedLocator(FfmpegPath), () => new FfmpegDecodeProcess(), audio: false, chromaKey: keyed); pipe.FrameAvailable += f => firstFrame.TrySetResult(f); await pipe.StartAsync(); var keyedFrame = await firstFrame.Task.WaitAsync(TimeSpan.FromSeconds(30)); await pipe.StopAsync(); var transparent = 0; for (var i = 3; i < keyedFrame.BgraPixels.Length; i += 4) if (keyedFrame.BgraPixels[i] == 0) transparent++; var ratio = (double)transparent / (keyedFrame.Width * keyedFrame.Height); Assert.True(ratio > 0.5, $"the keyed decode left only {ratio:P1} transparent — the filter chain is not keying this clip"); Assert.True(ratio < 1.0, "everything vanished; the subject should survive the key"); } private static (int R, int G, int B) ParseHex(string hex) { var value = hex.TrimStart('#'); return (Convert.ToInt32(value[..2], 16), Convert.ToInt32(value.Substring(2, 2), 16), Convert.ToInt32(value.Substring(4, 2), 16)); } private sealed class FixedLocator : IFfmpegLocator { private readonly string _path; public FixedLocator(string path) => _path = path; public Task LocateAsync(CancellationToken ct = default) => Task.FromResult(_path); } }