using System.Globalization;
namespace ytLive.Services;
///
/// TASK 21 (slice 2): parses the native frame rate of a video from ffprobe
/// -show_entries stream=avg_frame_rate,r_frame_rate output, which reports
/// each as a rational like 30/1 or 60000/1001 (or 0/0 when
/// unknown). Prefers avg_frame_rate (the true display rate over the whole
/// file, correct for VFR) and falls back to r_frame_rate (the container/
/// codec rate). Pure: no ffprobe, no I/O, unit-testable.
///
public static class FfmpegFrameRateParser
{
public static double? ParseFrameRate(string probeOutput)
{
foreach (var key in new[] { "avg_frame_rate=", "r_frame_rate=" })
{
var value = FindValue(probeOutput, key);
if (value is not null && TryParseRational(value, out var fps))
return fps;
}
return null;
}
/// The trimmed value of the first line beginning with .
private static string? FindValue(string text, string key)
{
foreach (var line in text.Split('\n'))
{
var trimmed = line.Trim();
if (trimmed.StartsWith(key, StringComparison.Ordinal))
return trimmed.Substring(key.Length).Trim();
}
return null;
}
/// Parses v as N (integer) or N/M (rational) into
/// a frames-per-second double. 0 and 0/M count as unknown.
private static bool TryParseRational(string value, out double fps)
{
fps = 0;
var parts = value.Split('/');
if (parts.Length == 0 || !int.TryParse(parts[0], NumberStyles.None, CultureInfo.InvariantCulture, out var num))
return false;
var den = 1;
if (parts.Length > 1 && !int.TryParse(parts[1], NumberStyles.None, CultureInfo.InvariantCulture, out den))
return false;
if (num <= 0 || den <= 0) return false;
fps = (double)num / den;
return true;
}
}