Skip to main content

ffmpeg_pipeline/
remux.rs

1use std::{collections::HashMap, path::PathBuf};
2
3use ffmpeg_next::{codec, Packet, Rational};
4
5use crate::{input_file, output_file, FFmpegResult};
6
7#[derive(Clone, Debug, Default)]
8pub struct RemuxStream {
9    pub input_index: usize,
10    pub title: Option<String>,
11    pub language: Option<String>,
12    pub filename: Option<String>,
13    pub mimetype: Option<String>,
14}
15
16#[derive(Clone, Debug)]
17pub struct RemuxRequest {
18    pub input: PathBuf,
19    pub output: PathBuf,
20    pub streams: Vec<RemuxStream>,
21}
22
23pub fn remux(request: &RemuxRequest) -> FFmpegResult {
24    let mut input = input_file(&request.input)?;
25    let mut output = output_file(&request.output)?;
26    let mut stream_mapping = HashMap::<usize, usize>::new();
27    let mut input_time_bases = HashMap::<usize, Rational>::new();
28    let mut stream_metadata = HashMap::<usize, RemuxStream>::new();
29    let mut next_dts = HashMap::<usize, i64>::new();
30
31    for stream in &request.streams {
32        if stream_mapping.contains_key(&stream.input_index) {
33            continue;
34        }
35        let Some(input_stream) = input.stream(stream.input_index) else {
36            continue;
37        };
38        let mut output_stream = output.add_stream(codec::encoder::find(codec::Id::None))?;
39        output_stream.set_parameters(input_stream.parameters());
40        unsafe {
41            (*output_stream.parameters().as_mut_ptr()).codec_tag = 0;
42        }
43        let output_index = output_stream.index();
44        stream_mapping.insert(stream.input_index, output_index);
45        input_time_bases.insert(stream.input_index, input_stream.time_base());
46        stream_metadata.insert(output_index, stream.clone());
47    }
48
49    if stream_mapping.is_empty() {
50        return Ok(());
51    }
52
53    output.set_metadata(input.metadata().to_owned());
54    for (output_index, stream) in stream_metadata {
55        let Some(mut output_stream) = output.stream_mut(output_index) else {
56            continue;
57        };
58        let mut metadata = output_stream.metadata().to_owned();
59        if let Some(title) = stream.title {
60            metadata.set("title", &title);
61        }
62        if let Some(language) = stream.language {
63            metadata.set("language", &language);
64        }
65        if let Some(filename) = stream.filename {
66            metadata.set("filename", &filename);
67        }
68        if let Some(mimetype) = stream.mimetype {
69            metadata.set("mimetype", &mimetype);
70        }
71        output_stream.set_metadata(metadata);
72    }
73
74    output.write_header()?;
75    for (stream, mut packet) in input.packets() {
76        let input_index = stream.index();
77        let Some(output_index) = stream_mapping.get(&input_index).copied() else {
78            continue;
79        };
80        let Some(output_stream) = output.stream(output_index) else {
81            continue;
82        };
83        let input_time_base = input_time_bases
84            .get(&input_index)
85            .copied()
86            .unwrap_or(stream.time_base());
87        packet.rescale_ts(input_time_base, output_stream.time_base());
88        normalize_timestamps(&mut packet, output_index, &mut next_dts);
89        packet.set_position(-1);
90        packet.set_stream(output_index);
91        packet.write_interleaved(&mut output)?;
92    }
93    output.write_trailer()?;
94    Ok(())
95}
96
97fn normalize_timestamps(packet: &mut Packet, stream: usize, next_dts: &mut HashMap<usize, i64>) {
98    let Some(dts) = packet.dts() else {
99        return;
100    };
101    let adjusted_dts = next_dts
102        .get(&stream)
103        .copied()
104        .map_or(dts, |next| dts.max(next));
105    if adjusted_dts != dts {
106        let offset = adjusted_dts.saturating_sub(dts);
107        packet.set_dts(Some(adjusted_dts));
108        packet.set_pts(packet.pts().map(|pts| pts.saturating_add(offset)));
109    }
110    next_dts.insert(
111        stream,
112        adjusted_dts.saturating_add(packet.duration().max(1)),
113    );
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn remux_repairs_backwards_dts_without_changing_composition_offset() {
122        let mut next_dts = HashMap::new();
123        let mut first = Packet::empty();
124        first.set_dts(Some(12));
125        first.set_pts(Some(15));
126        first.set_duration(10);
127        normalize_timestamps(&mut first, 0, &mut next_dts);
128
129        let mut second = Packet::empty();
130        second.set_dts(Some(3));
131        second.set_pts(Some(5));
132        second.set_duration(10);
133        normalize_timestamps(&mut second, 0, &mut next_dts);
134
135        assert_eq!(second.dts(), Some(22));
136        assert_eq!(second.pts(), Some(24));
137    }
138}