Skip to main content

clip_trim/
clip_trim.rs

1//! Trim a clip without re-encoding using `StreamCopyTrimmer`.
2//!
3//! Stream-copy trimming is fast — it copies codec data directly without decoding
4//! or encoding.  The output will start on the nearest keyframe at or before
5//! `--start`, so the actual trim point may differ slightly from the requested one.
6//!
7//! # Usage
8//!
9//! ```bash
10//! cargo run --example clip_trim -- \
11//!   --input   input.mp4  \
12//!   --output  trimmed.mp4 \
13//!   --start   10.0        \   # start time in seconds (default: 0.0)
14//!   --end     30.0            # end time in seconds (default: 10.0)
15//! ```
16
17use std::{path::Path, process};
18
19use ff_remux::StreamCopyTrimmer;
20
21fn main() {
22    let mut args = std::env::args().skip(1);
23    let mut input = None::<String>;
24    let mut output = None::<String>;
25    let mut start: f64 = 0.0;
26    let mut end: f64 = 10.0;
27
28    while let Some(flag) = args.next() {
29        match flag.as_str() {
30            "--input" | "-i" => input = Some(args.next().unwrap_or_default()),
31            "--output" | "-o" => output = Some(args.next().unwrap_or_default()),
32            "--start" => start = args.next().unwrap_or_default().parse().unwrap_or(0.0),
33            "--end" => end = args.next().unwrap_or_default().parse().unwrap_or(10.0),
34            other => {
35                eprintln!("Unknown flag: {other}");
36                process::exit(1);
37            }
38        }
39    }
40
41    let input = input.unwrap_or_else(|| {
42        eprintln!(
43            "Usage: clip_trim --input <file> --output <file> \
44             --start <seconds> --end <seconds>"
45        );
46        process::exit(1);
47    });
48    let output = output.unwrap_or_else(|| {
49        eprintln!("--output is required");
50        process::exit(1);
51    });
52
53    let in_name = Path::new(&input)
54        .file_name()
55        .and_then(|n| n.to_str())
56        .unwrap_or(&input);
57    let out_name = Path::new(&output)
58        .file_name()
59        .and_then(|n| n.to_str())
60        .unwrap_or(&output);
61
62    println!("Input:   {in_name}");
63    println!("Trim:    {start:.3}s → {end:.3}s  (stream copy — no re-encode)");
64    println!("Output:  {out_name}");
65    println!();
66
67    if let Err(e) = StreamCopyTrimmer::new(&input, start, end, &output).run() {
68        eprintln!("Error: {e}");
69        process::exit(1);
70    }
71
72    let size_str = match std::fs::metadata(&output) {
73        Ok(m) => {
74            #[allow(clippy::cast_precision_loss)]
75            let kb = m.len() as f64 / 1024.0;
76            if kb < 1024.0 {
77                format!("{kb:.0} KB")
78            } else {
79                format!("{:.1} MB", kb / 1024.0)
80            }
81        }
82        Err(_) => "(unknown size)".to_string(),
83    };
84
85    println!("Done. {out_name}  {size_str}");
86}