Skip to main content

black_frames/
black_frames.rs

1//! Detect black intervals in a video file and print their start timestamps.
2//!
3//! Uses [`BlackFrameDetector`] to identify segments where the proportion of
4//! near-black pixels exceeds a configurable threshold.  Black intervals are
5//! commonly used to detect chapter breaks, fade-to-black transitions, and
6//! advertising boundaries.
7//!
8//! # Usage
9//!
10//! ```bash
11//! cargo run --example black_frames -- --input video.mp4
12//! cargo run --example black_frames -- --input video.mp4 --threshold 0.2
13//! ```
14
15use std::process;
16use std::time::Duration;
17
18use ff_analysis::BlackFrameDetector;
19
20fn fmt_duration(d: Duration) -> String {
21    let h = d.as_secs() / 3600;
22    let m = (d.as_secs() % 3600) / 60;
23    let s = d.as_secs() % 60;
24    let ms = d.subsec_millis();
25    format!("{h:02}:{m:02}:{s:02}.{ms:03}")
26}
27
28fn main() {
29    let mut args = std::env::args().skip(1);
30    let mut input = None::<String>;
31    let mut threshold = 0.1_f64;
32
33    while let Some(flag) = args.next() {
34        match flag.as_str() {
35            "--input" | "-i" => input = Some(args.next().unwrap_or_default()),
36            "--threshold" | "-t" => {
37                let raw = args.next().unwrap_or_default();
38                threshold = raw.parse().unwrap_or_else(|_| {
39                    eprintln!("Invalid threshold: {raw}");
40                    process::exit(1);
41                });
42            }
43            other => {
44                eprintln!("Unknown flag: {other}");
45                process::exit(1);
46            }
47        }
48    }
49
50    let input = input.unwrap_or_else(|| {
51        eprintln!("Usage: black_frames --input <video> [--threshold <0.0–1.0>]");
52        process::exit(1);
53    });
54
55    println!("Detecting black frames in: {input}");
56    println!("Threshold: {threshold:.2}");
57    println!();
58
59    let black_starts = BlackFrameDetector::new(&input)
60        .threshold(threshold)
61        .run()
62        .unwrap_or_else(|e| {
63            eprintln!("Error: {e}");
64            process::exit(1);
65        });
66
67    if black_starts.is_empty() {
68        println!("No black intervals detected.");
69    } else {
70        println!("Detected {} black interval(s):", black_starts.len());
71        for (i, ts) in black_starts.iter().enumerate() {
72            println!("  [{i:3}] {}", fmt_duration(*ts));
73        }
74    }
75}