using System.Diagnostics; using System.IO; using ytLive.Services.Encoder; namespace ytLive.Services; /// /// TASK 21 (slice 2): resolves the native frame rate of a media file so the /// decoder can pace frames to real time. Seam so /// never touches ffprobe directly and tests can fake the whole probe. /// public interface IFrameRateProbe { /// Native video FPS for , or null when unknown /// (no video stream, ffprobe unavailable, unparsable). Task ProbeAsync(string path, CancellationToken cancellationToken = default); } /// /// Default probe: locates ffmpeg (via ), derives the /// sibling ffprobe.exe from its directory, and asks ffprobe for the /// avg_frame_rate/r_frame_rate. Reuses the binary-stdout /// seam for the subprocess (ffprobe text is read from /// the same raw stdout). Returns null (no pacing) if ffprobe is unavailable. /// public sealed class FfmpegFrameRateProbe : IFrameRateProbe { private readonly IFfmpegLocator _locator; private readonly Func _probeProcessFactory; public FfmpegFrameRateProbe(IFfmpegLocator locator, Func probeProcessFactory) { _locator = locator; _probeProcessFactory = probeProcessFactory; } public async Task ProbeAsync(string path, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(path)) return null; var ffmpegPath = await _locator.LocateAsync(cancellationToken).ConfigureAwait(false); var ffprobePath = Path.Combine(Path.GetDirectoryName(ffmpegPath) ?? string.Empty, "ffprobe.exe"); if (!File.Exists(ffprobePath)) return null; var startInfo = new ProcessStartInfo { FileName = ffprobePath, Arguments = $"-v error -select_streams v:0 " + $"-show_entries stream=avg_frame_rate,r_frame_rate " + $"-of default=noprint_wrappers=1 \"{path}\"", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, }; using var process = _probeProcessFactory(); process.Start(startInfo); string output; using (var reader = new StreamReader(process.StandardOutput)) output = reader.ReadToEnd(); await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false); return FfmpegFrameRateParser.ParseFrameRate(output); } }