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;
}
}
}