Skip to main content

deepshrink_ffmpeg/
progress.rs

1//! Parse ffmpeg's `-progress pipe:1` key=value stream.
2//!
3//! With `-progress pipe:1 -nostats`, ffmpeg writes blocks of `key=value` lines
4//! to stdout, ending each block with `progress=continue` or `progress=end`.
5//! We only care about the elapsed output time and the terminal marker.
6
7/// One parsed progress line of interest.
8#[derive(Debug, Clone, PartialEq)]
9pub enum Progress {
10    /// Elapsed output time in microseconds.
11    OutTimeUs(u64),
12    /// `progress=continue`.
13    Continue,
14    /// `progress=end` — encoding finished.
15    End,
16}
17
18/// Parse a single line. Returns `None` for keys we ignore.
19pub fn parse_line(line: &str) -> Option<Progress> {
20    let (key, value) = line.split_once('=')?;
21    match key.trim() {
22        "out_time_us" | "out_time_ms" => {
23            // `out_time_us` is microseconds; the older `out_time_ms` alias is
24            // *also* microseconds in ffmpeg (a long-standing naming quirk).
25            value.trim().parse().ok().map(Progress::OutTimeUs)
26        }
27        "progress" => match value.trim() {
28            "end" => Some(Progress::End),
29            "continue" => Some(Progress::Continue),
30            _ => None,
31        },
32        _ => None,
33    }
34}
35
36/// Convert elapsed microseconds and total seconds into a clamped 0.0..=1.0 fraction.
37pub fn fraction(out_time_us: u64, total_secs: f64) -> f64 {
38    if total_secs <= 0.0 {
39        return 0.0;
40    }
41    let elapsed_secs = out_time_us as f64 / 1_000_000.0;
42    (elapsed_secs / total_secs).clamp(0.0, 1.0)
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn parses_out_time() {
51        assert_eq!(
52            parse_line("out_time_us=1500000"),
53            Some(Progress::OutTimeUs(1_500_000))
54        );
55        assert_eq!(
56            parse_line("out_time_ms=2000000"),
57            Some(Progress::OutTimeUs(2_000_000))
58        );
59    }
60
61    #[test]
62    fn parses_progress_markers() {
63        assert_eq!(parse_line("progress=continue"), Some(Progress::Continue));
64        assert_eq!(parse_line("progress=end"), Some(Progress::End));
65    }
66
67    #[test]
68    fn ignores_other_keys() {
69        assert_eq!(parse_line("frame=42"), None);
70        assert_eq!(parse_line("bitrate=  456.7kbits/s"), None);
71        assert_eq!(parse_line("no-equals-sign"), None);
72    }
73
74    #[test]
75    fn fraction_clamps() {
76        assert_eq!(fraction(0, 10.0), 0.0);
77        assert_eq!(fraction(5_000_000, 10.0), 0.5);
78        assert_eq!(fraction(20_000_000, 10.0), 1.0);
79        assert_eq!(fraction(1_000_000, 0.0), 0.0);
80    }
81}