using System.Diagnostics; using System.IO; using ytLive.Services.Encoder; namespace ytLive.Services; /// /// TASK 64: reads ONE frame out of a video file so the app can look at its background before /// the creator says anything. Kept behind an interface so tests never spawn ffmpeg, and separate /// from the decode pipe because this is a read, not a playback session. /// public interface IChromaProbe { /// The file's first usable frame at a small size, or null when it cannot be read /// (bad path, unsupported file, ffmpeg gave nothing). Null is a normal answer: the layer /// simply gets no suggestion. Task ProbeAsync(string path, CancellationToken cancellationToken = default); } /// Default : one ffmpeg pass, scaled to 160×90 so the look is /// cheap and the detector sees plenty of the backdrop. public sealed class FfmpegChromaProbe : IChromaProbe { private const int ProbeWidth = 160; private const int ProbeHeight = 90; private readonly IFfmpegLocator _locator; private readonly Func _processFactory; public FfmpegChromaProbe(IFfmpegLocator locator, Func processFactory) { _locator = locator; _processFactory = processFactory; } public async Task ProbeAsync(string path, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(path)) return null; try { var ffmpegPath = await _locator.LocateAsync(cancellationToken).ConfigureAwait(false); var args = $"-hide_banner -loglevel error -i \"{path}\" -frames:v 1 -an " + $"-f rawvideo -pix_fmt bgra -vf scale={ProbeWidth}:{ProbeHeight} -"; using var process = _processFactory(); process.Start(new ProcessStartInfo { FileName = ffmpegPath, Arguments = args, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, }); var frames = await Task.Run(() => { using var buffer = new MemoryStream(); process.StandardOutput.CopyTo(buffer); var reader = new RawVideoFrameReader(ProbeWidth, ProbeHeight); var fed = reader.Feed(buffer.ToArray()); return fed; }, cancellationToken).ConfigureAwait(false); return frames.Count > 0 ? frames[0] : null; } catch (Exception ex) { Helpers.AppLog.Write($"ChromaProbe: '{Path.GetFileName(path)}' unreadable: {ex.Message}"); return null; } } }