pub struct SceneDetector { /* private fields */ }Expand description
Detects scene changes in a video file and returns their timestamps.
Uses FFmpeg’s select=gt(scene\,threshold) filter to identify frames
where the scene changes. The threshold controls detection sensitivity:
lower values detect more cuts (including subtle ones); higher values detect
only hard cuts.
§Examples
use ff_analysis::SceneDetector;
let cuts = SceneDetector::new("video.mp4")
.threshold(0.3)
.run()?;
for ts in &cuts {
println!("Scene change at {:?}", ts);
}Implementations§
Source§impl SceneDetector
impl SceneDetector
Sourcepub fn new(input: impl AsRef<Path>) -> Self
pub fn new(input: impl AsRef<Path>) -> Self
Creates a new detector for the given video file.
The default detection threshold is 0.4. Call
threshold to override it.
Examples found in repository?
18fn main() {
19 let mut args = std::env::args().skip(1);
20 let mut input = None::<String>;
21 let mut threshold = 0.4_f64;
22
23 while let Some(flag) = args.next() {
24 match flag.as_str() {
25 "--input" | "-i" => input = Some(args.next().unwrap_or_default()),
26 "--threshold" | "-t" => {
27 let raw = args.next().unwrap_or_default();
28 threshold = raw.parse().unwrap_or_else(|_| {
29 eprintln!("Invalid threshold: {raw}");
30 process::exit(1);
31 });
32 }
33 other => {
34 eprintln!("Unknown flag: {other}");
35 process::exit(1);
36 }
37 }
38 }
39
40 let input = input.unwrap_or_else(|| {
41 eprintln!("Usage: scene_detection --input <video> [--threshold <0.0–1.0>]");
42 process::exit(1);
43 });
44
45 println!("Detecting scene changes in: {input}");
46 println!("Threshold: {threshold:.2}");
47 println!();
48
49 let cuts = SceneDetector::new(&input)
50 .threshold(threshold)
51 .run()
52 .unwrap_or_else(|e| {
53 eprintln!("Error: {e}");
54 process::exit(1);
55 });
56
57 if cuts.is_empty() {
58 println!("No scene changes detected.");
59 } else {
60 println!("Detected {} scene change(s):", cuts.len());
61 for (i, ts) in cuts.iter().enumerate() {
62 let secs = ts.as_secs_f64();
63 let h = ts.as_secs() / 3600;
64 let m = (ts.as_secs() % 3600) / 60;
65 let s = ts.as_secs() % 60;
66 let ms = ts.subsec_millis();
67 println!(" [{i:3}] {h:02}:{m:02}:{s:02}.{ms:03} ({secs:.3}s)");
68 }
69 }
70}Sourcepub fn threshold(self, t: f64) -> Self
pub fn threshold(self, t: f64) -> Self
Sets the scene-change detection threshold.
Must be in the range [0.0, 1.0]. Lower values make the detector more
sensitive (more cuts reported); higher values require a larger visual
difference. Passing a value outside this range causes
run to return AnalysisError::Failed.
Default: 0.4.
Examples found in repository?
18fn main() {
19 let mut args = std::env::args().skip(1);
20 let mut input = None::<String>;
21 let mut threshold = 0.4_f64;
22
23 while let Some(flag) = args.next() {
24 match flag.as_str() {
25 "--input" | "-i" => input = Some(args.next().unwrap_or_default()),
26 "--threshold" | "-t" => {
27 let raw = args.next().unwrap_or_default();
28 threshold = raw.parse().unwrap_or_else(|_| {
29 eprintln!("Invalid threshold: {raw}");
30 process::exit(1);
31 });
32 }
33 other => {
34 eprintln!("Unknown flag: {other}");
35 process::exit(1);
36 }
37 }
38 }
39
40 let input = input.unwrap_or_else(|| {
41 eprintln!("Usage: scene_detection --input <video> [--threshold <0.0–1.0>]");
42 process::exit(1);
43 });
44
45 println!("Detecting scene changes in: {input}");
46 println!("Threshold: {threshold:.2}");
47 println!();
48
49 let cuts = SceneDetector::new(&input)
50 .threshold(threshold)
51 .run()
52 .unwrap_or_else(|e| {
53 eprintln!("Error: {e}");
54 process::exit(1);
55 });
56
57 if cuts.is_empty() {
58 println!("No scene changes detected.");
59 } else {
60 println!("Detected {} scene change(s):", cuts.len());
61 for (i, ts) in cuts.iter().enumerate() {
62 let secs = ts.as_secs_f64();
63 let h = ts.as_secs() / 3600;
64 let m = (ts.as_secs() % 3600) / 60;
65 let s = ts.as_secs() % 60;
66 let ms = ts.subsec_millis();
67 println!(" [{i:3}] {h:02}:{m:02}:{s:02}.{ms:03} ({secs:.3}s)");
68 }
69 }
70}Sourcepub fn run(self) -> Result<Vec<Duration>, AnalysisError>
pub fn run(self) -> Result<Vec<Duration>, AnalysisError>
Runs scene-change detection and returns one Duration per detected cut.
Timestamps are sorted in ascending order and represent the PTS of the first frame of each new scene.
§Errors
AnalysisError::Failed— threshold outside[0.0, 1.0], input file not found, or an internal filter-graph error.
Examples found in repository?
18fn main() {
19 let mut args = std::env::args().skip(1);
20 let mut input = None::<String>;
21 let mut threshold = 0.4_f64;
22
23 while let Some(flag) = args.next() {
24 match flag.as_str() {
25 "--input" | "-i" => input = Some(args.next().unwrap_or_default()),
26 "--threshold" | "-t" => {
27 let raw = args.next().unwrap_or_default();
28 threshold = raw.parse().unwrap_or_else(|_| {
29 eprintln!("Invalid threshold: {raw}");
30 process::exit(1);
31 });
32 }
33 other => {
34 eprintln!("Unknown flag: {other}");
35 process::exit(1);
36 }
37 }
38 }
39
40 let input = input.unwrap_or_else(|| {
41 eprintln!("Usage: scene_detection --input <video> [--threshold <0.0–1.0>]");
42 process::exit(1);
43 });
44
45 println!("Detecting scene changes in: {input}");
46 println!("Threshold: {threshold:.2}");
47 println!();
48
49 let cuts = SceneDetector::new(&input)
50 .threshold(threshold)
51 .run()
52 .unwrap_or_else(|e| {
53 eprintln!("Error: {e}");
54 process::exit(1);
55 });
56
57 if cuts.is_empty() {
58 println!("No scene changes detected.");
59 } else {
60 println!("Detected {} scene change(s):", cuts.len());
61 for (i, ts) in cuts.iter().enumerate() {
62 let secs = ts.as_secs_f64();
63 let h = ts.as_secs() / 3600;
64 let m = (ts.as_secs() % 3600) / 60;
65 let s = ts.as_secs() % 60;
66 let ms = ts.subsec_millis();
67 println!(" [{i:3}] {h:02}:{m:02}:{s:02}.{ms:03} ({secs:.3}s)");
68 }
69 }
70}