Skip to main content

pito_codecs/mp4/
remux.rs

1use crate::media::{Error, Result};
2use crate::mp4::reader::{Edit, Reader, Track, TrackKind};
3use crate::mp4::source::ByteSource;
4use crate::mp4::truncated;
5use crate::mp4::writer::{Brand, Options, TrackConfig, Writer};
6use std::io::{self, Seek, SeekFrom, Write};
7use std::ops::Range;
8use std::path::Path;
9
10const RUN: u64 = 8 << 20;
11const INTERLEAVE: f64 = 0.5;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum Snap {
15    Inward,
16    Outward,
17}
18
19#[derive(Clone, Copy, Debug, PartialEq)]
20pub struct Span {
21    pub start: f64,
22    pub end: f64,
23}
24
25#[derive(Clone, Debug, PartialEq)]
26pub struct Cut {
27    pub span: Span,
28    pub tracks: Vec<(usize, Range<usize>)>,
29}
30
31pub(crate) trait Timeline {
32    fn len(&self) -> usize;
33    fn keyframes(&self) -> &[u32];
34    fn pts(&self, i: usize) -> i64;
35    fn duration(&self, i: usize) -> i64;
36    fn ticks(&self, seconds: f64) -> i64;
37    fn seconds(&self, ticks: i64) -> f64;
38
39    fn end(&self) -> i64 {
40        (0..self.len())
41            .map(|i| self.pts(i).saturating_add(self.duration(i)))
42            .max()
43            .unwrap_or(0)
44    }
45}
46
47impl Timeline for Track {
48    fn len(&self) -> usize {
49        self.samples.len()
50    }
51    fn keyframes(&self) -> &[u32] {
52        Track::keyframes(self)
53    }
54    fn pts(&self, i: usize) -> i64 {
55        self.presentation(i).unwrap_or(0)
56    }
57    fn duration(&self, i: usize) -> i64 {
58        self.samples[i].duration as i64
59    }
60    fn ticks(&self, seconds: f64) -> i64 {
61        Track::ticks(self, seconds)
62    }
63    fn seconds(&self, ticks: i64) -> f64 {
64        Track::seconds(self, ticks)
65    }
66}
67
68fn video_range<T: Timeline>(
69    t: &T,
70    from: f64,
71    to: f64,
72    snap: Snap,
73) -> Result<(Range<usize>, i64, i64)> {
74    let keys: Vec<usize> = t.keyframes().iter().map(|&k| k as usize).collect();
75    if keys.is_empty() {
76        return Err(Error::Invalid("video track has no keyframes".into()));
77    }
78    let n = t.len();
79    let gop_min: Vec<i64> = keys
80        .iter()
81        .enumerate()
82        .map(|(j, &k)| {
83            let end = keys.get(j + 1).copied().unwrap_or(n);
84            (k..end).map(|i| t.pts(i)).min().unwrap_or(i64::MAX)
85        })
86        .collect();
87    let (from_t, to_t) = (t.ticks(from), t.ticks(to));
88    let end_t = t.end();
89    match snap {
90        Snap::Outward => {
91            let ja = keys.iter().rposition(|&k| t.pts(k) <= from_t).unwrap_or(0);
92            let b = (ja + 1..keys.len())
93                .find(|&j| gop_min[j] >= to_t)
94                .map_or(n, |j| keys[j]);
95            let a = keys[ja];
96            let copied_end = (a..b)
97                .map(|i| t.pts(i).saturating_add(t.duration(i)))
98                .max()
99                .unwrap_or(0);
100            Ok((a..b, from_t.max(t.pts(a)), to_t.min(copied_end)))
101        }
102        Snap::Inward => {
103            let ja = keys
104                .iter()
105                .position(|&k| t.pts(k) >= from_t)
106                .ok_or_else(|| Error::Invalid(format!("no keyframe after {from:.3} s")))?;
107            let start = t.pts(keys[ja]);
108            if to_t >= end_t {
109                return Ok((keys[ja]..n, start, end_t));
110            }
111            let jb = (ja + 1..keys.len())
112                .rev()
113                .find(|&j| t.pts(keys[j]) <= to_t)
114                .ok_or_else(|| {
115                    Error::Invalid(format!(
116                        "no keyframe-to-keyframe stretch inside {from:.3}..{to:.3} s"
117                    ))
118                })?;
119            Ok((keys[ja]..keys[jb], start, t.pts(keys[jb])))
120        }
121    }
122}
123
124fn audio_range<T: Timeline>(t: &T, start: f64, end: f64) -> Range<usize> {
125    let (s, e) = (t.ticks(start), t.ticks(end));
126    let n = t.len();
127    let first = (0..n)
128        .position(|i| t.pts(i).saturating_add(t.duration(i)) > s)
129        .unwrap_or(n);
130    let first = if first < n && t.pts(first) < s {
131        first.saturating_sub(1)
132    } else {
133        first
134    };
135    let last = (first..n).find(|&i| t.pts(i) >= e).unwrap_or(n);
136    first..last
137}
138
139pub(crate) fn plan_tracks<T: Timeline>(
140    total: f64,
141    tracks: &[T],
142    video: Option<usize>,
143    audio: Vec<usize>,
144    (from, to): (f64, f64),
145    snap: Snap,
146) -> Result<Cut> {
147    let (from, to) = (from.max(0.0), to.min(total.max(from)));
148    if to.is_nan() || from.is_nan() || to <= from {
149        return Err(Error::Invalid(format!("empty range {from:.3}..{to:.3} s")));
150    }
151    let mut cut = Vec::new();
152    let span = match video {
153        Some(v) => {
154            let t = &tracks[v];
155            let (range, start, end) = video_range(t, from, to, snap)?;
156            cut.push((v, range));
157            Span {
158                start: t.seconds(start),
159                end: t.seconds(end),
160            }
161        }
162        None => Span {
163            start: from,
164            end: to,
165        },
166    };
167    for i in audio {
168        cut.push((i, audio_range(&tracks[i], span.start, span.end)));
169    }
170    Ok(Cut { span, tracks: cut })
171}
172
173pub fn plan<S: ByteSource>(reader: &Reader<S>, from: f64, to: f64, snap: Snap) -> Result<Cut> {
174    plan_tracks(
175        reader.duration_seconds(),
176        &reader.tracks,
177        reader.video(),
178        reader.audio(),
179        (from, to),
180        snap,
181    )
182}
183
184pub fn copy_samples<S: ByteSource, W: Write + Seek>(
185    reader: &Reader<S>,
186    track: usize,
187    range: Range<usize>,
188    writer: &mut Writer<W>,
189    dst: usize,
190    shift: i64,
191) -> Result<()> {
192    let mut buf = Vec::new();
193    copy_run(reader, track, range, writer, dst, shift, &mut buf, false)
194}
195
196#[allow(clippy::too_many_arguments)]
197fn copy_run<S: ByteSource, W: Write + Seek>(
198    reader: &Reader<S>,
199    track: usize,
200    range: Range<usize>,
201    writer: &mut Writer<W>,
202    dst: usize,
203    shift: i64,
204    buf: &mut Vec<u8>,
205    dry: bool,
206) -> Result<()> {
207    let t = reader
208        .tracks
209        .get(track)
210        .ok_or_else(|| Error::Invalid(format!("no track {track}")))?;
211    let samples = t
212        .samples
213        .get(range.clone())
214        .ok_or_else(|| Error::Invalid(format!("no samples {range:?} in track {track}")))?;
215    let len = reader.source().len();
216    let mut entry = None;
217    let mut i = 0;
218    while i < samples.len() {
219        let start = samples[i].offset;
220        let mut end = start;
221        let mut j = i;
222        while j < samples.len()
223            && samples[j].offset == end
224            && (j == i || end - start + samples[j].size as u64 <= RUN)
225        {
226            end = end
227                .checked_add(samples[j].size as u64)
228                .filter(|&e| e <= len)
229                .ok_or_else(|| truncated("sample data"))?;
230            j += 1;
231        }
232        buf.resize((end - start) as usize, 0);
233        if !dry {
234            reader.read_span(start, buf)?;
235        }
236        let mut at = 0usize;
237        for s in &samples[i..j] {
238            if entry != Some(s.entry) {
239                writer.use_entry(dst, s.entry)?;
240                entry = Some(s.entry);
241            }
242            let size = s.size as usize;
243            writer.push_sample(
244                dst,
245                &buf[at..at + size],
246                s.pts.saturating_add(shift),
247                s.dts.saturating_add(shift),
248                s.duration,
249                s.keyframe,
250            )?;
251            at += size;
252        }
253        i = j;
254    }
255    Ok(())
256}
257
258struct Sink {
259    pos: u64,
260    end: u64,
261}
262
263impl Write for Sink {
264    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
265        self.pos += data.len() as u64;
266        self.end = self.end.max(self.pos);
267        Ok(data.len())
268    }
269
270    fn flush(&mut self) -> io::Result<()> {
271        Ok(())
272    }
273}
274
275impl Seek for Sink {
276    fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {
277        self.pos = match to {
278            SeekFrom::Start(p) => p,
279            SeekFrom::Current(d) => self.pos.saturating_add_signed(d),
280            SeekFrom::End(d) => self.end.saturating_add_signed(d),
281        };
282        Ok(self.pos)
283    }
284}
285
286fn feed<S: ByteSource, W: Write + Seek>(
287    reader: &Reader<S>,
288    cut: &Cut,
289    writer: &mut Writer<W>,
290    dry: bool,
291) -> Result<()> {
292    let segment = ((cut.span.end - cut.span.start) * 1000.0).round().max(0.0) as u64;
293    let mut lanes = Vec::new();
294    for (ti, range) in &cut.tracks {
295        let t = &reader.tracks[*ti];
296        if range.is_empty() || !matches!(t.kind, TrackKind::Video | TrackKind::Audio) {
297            continue;
298        }
299        let mut cfg = TrackConfig::from_track(t)?;
300        let origin = t.samples[range.start].dts;
301        let start = t
302            .ticks(cut.span.start)
303            .saturating_sub(t.edit_offset)
304            .saturating_sub(origin);
305        cfg.edits = vec![Edit {
306            segment_duration: segment,
307            media_time: start.max(0),
308            media_rate: 0x10000,
309        }];
310        let dst = writer.add_track(cfg)?;
311        for e in t.entries.iter().skip(1) {
312            writer.add_entry(dst, e.clone())?;
313        }
314        lanes.push((*ti, range.clone(), dst, origin, range.start));
315    }
316    let mut buf = Vec::new();
317    let mut window = INTERLEAVE;
318    loop {
319        let mut left = false;
320        let mut ahead = f64::INFINITY;
321        for (ti, range, dst, origin, next) in &mut lanes {
322            let t = &reader.tracks[*ti];
323            let stop = (*next..range.end)
324                .find(|&i| t.seconds(t.samples[i].dts.saturating_sub(*origin)) >= window)
325                .unwrap_or(range.end);
326            if stop > *next {
327                copy_run(
328                    reader,
329                    *ti,
330                    *next..stop,
331                    writer,
332                    *dst,
333                    -*origin,
334                    &mut buf,
335                    dry,
336                )?;
337                *next = stop;
338            }
339            if *next < range.end {
340                left = true;
341                ahead = ahead.min(t.seconds(t.samples[*next].dts.saturating_sub(*origin)));
342            }
343        }
344        if !left {
345            return Ok(());
346        }
347        window = (window + INTERLEAVE).max(ahead);
348    }
349}
350
351pub fn remux(
352    src: impl AsRef<Path>,
353    dst: impl AsRef<Path>,
354    from: f64,
355    to: f64,
356    snap: Snap,
357) -> Result<Span> {
358    let reader = Reader::open(src)?;
359    let cut = plan(&reader, from, to, snap)?;
360    let brand = if reader.quicktime {
361        Brand::Mov
362    } else {
363        Brand::Mp4
364    };
365    let mut dry = Writer::new(Sink { pos: 0, end: 0 }, Options { brand, reserve: 0 })?;
366    feed(&reader, &cut, &mut dry, true)?;
367    let moov = dry.moov_len()?;
368    let reserve = moov
369        + 8
370        + if dry.position() > u32::MAX as u64 / 2 {
371            moov
372        } else {
373            0
374        };
375    let mut writer = Writer::create_with(dst, Options { brand, reserve })?;
376    feed(&reader, &cut, &mut writer, false)?;
377    writer.finish()?;
378    Ok(cut.span)
379}