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