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}