Skip to main content

ffcine_assembly/
diff.rs

1//! `ffcine diff`: which segments of a new plan aren't in the old one's cache.
2
3use std::fmt::Write as _;
4
5use crate::plan::{Plan, key_set};
6
7/// Diff of two plans by segment key.
8#[derive(Debug, Clone, PartialEq)]
9pub struct Diff {
10    /// `(segment index in the new plan, re-renders)`.
11    pub segments: Vec<(usize, bool)>,
12    /// Old segments no longer used.
13    pub dropped: usize,
14    pub audio_changed: bool,
15}
16
17impl Diff {
18    pub fn new(old: &Plan, new: &Plan) -> Self {
19        let old_keys = key_set(old);
20        let new_keys = key_set(new);
21        Self {
22            segments: new
23                .segments
24                .iter()
25                .enumerate()
26                .map(|(i, s)| (i, !old_keys.contains(&s.key)))
27                .collect(),
28            dropped: old_keys.difference(&new_keys).count(),
29            audio_changed: old.audio.as_ref().map(|a| &a.key) != new.audio.as_ref().map(|a| &a.key),
30        }
31    }
32
33    pub fn render_count(&self) -> usize {
34        self.segments.iter().filter(|(_, r)| *r).count()
35    }
36
37    /// One line per segment (`+` re-renders, `=` cached) and a summary.
38    #[allow(clippy::cast_precision_loss)]
39    pub fn report(&self, new: &Plan, all: bool) -> String {
40        let mut out = String::new();
41        let secs = |f: i64| f as f64 / new.rate;
42        let mut render_frames = 0;
43        for &(i, render) in &self.segments {
44            let s = &new.segments[i];
45            if render {
46                render_frames += s.frames;
47            }
48            if render || all {
49                let t = secs(s.start);
50                let _ = writeln!(
51                    out,
52                    "{} {:>3}:{:06.3} {:>5}f  {:<32} {}",
53                    if render { '+' } else { '=' },
54                    (t / 60.0).floor(),
55                    t % 60.0,
56                    s.frames,
57                    s.name,
58                    s.key.short()
59                );
60            }
61        }
62        let n = self.render_count();
63        let _ = writeln!(
64            out,
65            "{n} of {} segments render ({:.1} s of {:.1} s), {} old dropped; audio {}",
66            self.segments.len(),
67            secs(render_frames),
68            secs(new.frames()),
69            self.dropped,
70            if self.audio_changed {
71                "changed"
72            } else {
73                "same"
74            },
75        );
76        if n == 0 {
77            let _ = writeln!(
78                out,
79                "{}",
80                if self.audio_changed {
81                    "picture unchanged: audio remux only (no video encode)"
82                } else {
83                    "nothing to render"
84                }
85            );
86        }
87        out
88    }
89}