pub struct StreamCopyTrimmer { /* private fields */ }Expand description
Trim a media file to a time range using stream copy (no re-encode).
Uses avformat_seek_file to seek to the start point, then copies packets
until the presentation timestamp exceeds the end point. All streams
(video, audio, subtitles) are copied verbatim from the input.
§Example
use ff_remux::StreamCopyTrimmer;
StreamCopyTrimmer::new("input.mp4", 2.0, 7.0, "output.mp4")
.run()?;Implementations§
Source§impl StreamCopyTrimmer
impl StreamCopyTrimmer
Sourcepub fn new(
input: impl Into<PathBuf>,
start_sec: f64,
end_sec: f64,
output: impl Into<PathBuf>,
) -> Self
pub fn new( input: impl Into<PathBuf>, start_sec: f64, end_sec: f64, output: impl Into<PathBuf>, ) -> Self
Create a new StreamCopyTrimmer.
start_sec and end_sec are absolute timestamps in seconds measured
from the start of the source file. run returns
RemuxError::InvalidConfig if start_sec >= end_sec.
Examples found in repository?
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}More examples
23fn main() {
24 let mut args = std::env::args().skip(1);
25 let mut input = None::<String>;
26 let mut output = None::<String>;
27 let mut start_sec = 0.0_f64;
28 let mut end_sec = None::<f64>;
29
30 while let Some(flag) = args.next() {
31 match flag.as_str() {
32 "--input" | "-i" => input = Some(args.next().unwrap_or_default()),
33 "--output" | "-o" => output = Some(args.next().unwrap_or_default()),
34 "--start" => {
35 let raw = args.next().unwrap_or_default();
36 start_sec = raw.parse().unwrap_or_else(|_| {
37 eprintln!("Invalid start: {raw}");
38 process::exit(1);
39 });
40 }
41 "--end" => {
42 let raw = args.next().unwrap_or_default();
43 end_sec = Some(raw.parse().unwrap_or_else(|_| {
44 eprintln!("Invalid end: {raw}");
45 process::exit(1);
46 }));
47 }
48 other => {
49 eprintln!("Unknown flag: {other}");
50 process::exit(1);
51 }
52 }
53 }
54
55 let input = input.unwrap_or_else(|| {
56 eprintln!(
57 "Usage: stream_copy_trim --input <file> --start <secs> --end <secs> --output <file>"
58 );
59 process::exit(1);
60 });
61 let end_sec = end_sec.unwrap_or_else(|| {
62 eprintln!("--end is required");
63 process::exit(1);
64 });
65 let output = output.unwrap_or_else(|| {
66 eprintln!("--output is required");
67 process::exit(1);
68 });
69
70 if start_sec >= end_sec {
71 eprintln!("Error: --start ({start_sec}) must be less than --end ({end_sec})");
72 process::exit(1);
73 }
74
75 let duration = end_sec - start_sec;
76 println!("Input: {input}");
77 println!("Range: {start_sec:.3}s – {end_sec:.3}s ({duration:.3}s)");
78 println!("Output: {output}");
79 println!();
80 println!("Trimming (stream-copy, no re-encode)…");
81
82 StreamCopyTrimmer::new(&input, start_sec, end_sec, &output)
83 .run()
84 .unwrap_or_else(|e| {
85 eprintln!("Error: {e}");
86 process::exit(1);
87 });
88
89 let size = match std::fs::metadata(&output) {
90 Ok(m) => {
91 #[allow(clippy::cast_precision_loss)]
92 let kb = m.len() as f64 / 1024.0;
93 if kb < 1024.0 {
94 format!("{kb:.0} KB")
95 } else {
96 format!("{:.1} MB", kb / 1024.0)
97 }
98 }
99 Err(_) => "(unknown size)".to_string(),
100 };
101
102 println!("Done. {output} {size}");
103}Sourcepub fn video_bsf(self, spec: impl Into<String>) -> Self
pub fn video_bsf(self, spec: impl Into<String>) -> Self
Applies a bitstream filter chain to every video stream.
spec is the syntax ffmpeg -bsf takes: a comma-separated chain whose
elements may carry options, e.g. "dump_extra" or
"h264_metadata=level=40,extract_extradata".
This is only for filters FFmpeg does not apply on its own. libavformat
already inserts the filter a container requires — copying H.264 from MP4 into
MPEG-TS produces Annex B with nothing set here — so this exists for the
explicit ones (extract_extradata, dump_extra, the *_metadata family).
See ADR-0011.
An unregistered or malformed spec fails in run with
RemuxError::InvalidConfig.
Sourcepub fn audio_bsf(self, spec: impl Into<String>) -> Self
pub fn audio_bsf(self, spec: impl Into<String>) -> Self
Applies a bitstream filter chain to every audio stream.
See video_bsf for the spec syntax and when to reach for
this at all.
Sourcepub fn run(self) -> Result<(), RemuxError>
pub fn run(self) -> Result<(), RemuxError>
Execute the trim operation.
§Errors
RemuxError::InvalidConfigifstart_sec >= end_sec.RemuxError::Ffmpegif anyFFmpegAPI call fails.
Examples found in repository?
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}More examples
23fn main() {
24 let mut args = std::env::args().skip(1);
25 let mut input = None::<String>;
26 let mut output = None::<String>;
27 let mut start_sec = 0.0_f64;
28 let mut end_sec = None::<f64>;
29
30 while let Some(flag) = args.next() {
31 match flag.as_str() {
32 "--input" | "-i" => input = Some(args.next().unwrap_or_default()),
33 "--output" | "-o" => output = Some(args.next().unwrap_or_default()),
34 "--start" => {
35 let raw = args.next().unwrap_or_default();
36 start_sec = raw.parse().unwrap_or_else(|_| {
37 eprintln!("Invalid start: {raw}");
38 process::exit(1);
39 });
40 }
41 "--end" => {
42 let raw = args.next().unwrap_or_default();
43 end_sec = Some(raw.parse().unwrap_or_else(|_| {
44 eprintln!("Invalid end: {raw}");
45 process::exit(1);
46 }));
47 }
48 other => {
49 eprintln!("Unknown flag: {other}");
50 process::exit(1);
51 }
52 }
53 }
54
55 let input = input.unwrap_or_else(|| {
56 eprintln!(
57 "Usage: stream_copy_trim --input <file> --start <secs> --end <secs> --output <file>"
58 );
59 process::exit(1);
60 });
61 let end_sec = end_sec.unwrap_or_else(|| {
62 eprintln!("--end is required");
63 process::exit(1);
64 });
65 let output = output.unwrap_or_else(|| {
66 eprintln!("--output is required");
67 process::exit(1);
68 });
69
70 if start_sec >= end_sec {
71 eprintln!("Error: --start ({start_sec}) must be less than --end ({end_sec})");
72 process::exit(1);
73 }
74
75 let duration = end_sec - start_sec;
76 println!("Input: {input}");
77 println!("Range: {start_sec:.3}s – {end_sec:.3}s ({duration:.3}s)");
78 println!("Output: {output}");
79 println!();
80 println!("Trimming (stream-copy, no re-encode)…");
81
82 StreamCopyTrimmer::new(&input, start_sec, end_sec, &output)
83 .run()
84 .unwrap_or_else(|e| {
85 eprintln!("Error: {e}");
86 process::exit(1);
87 });
88
89 let size = match std::fs::metadata(&output) {
90 Ok(m) => {
91 #[allow(clippy::cast_precision_loss)]
92 let kb = m.len() as f64 / 1024.0;
93 if kb < 1024.0 {
94 format!("{kb:.0} KB")
95 } else {
96 format!("{:.1} MB", kb / 1024.0)
97 }
98 }
99 Err(_) => "(unknown size)".to_string(),
100 };
101
102 println!("Done. {output} {size}");
103}