use alloc::vec::Vec;
use broadcast_common::Unpackage;
use crate::error::{Error, Result};
use crate::media::{Fmp4Demux, Media, Track};
use crate::pipeline::{CodecConfig, TrackSpec};
use crate::segmenter::Segmenter;
fn anchor_index(media: &Media) -> Option<usize> {
if media.tracks.is_empty() {
return None;
}
Some(
media
.tracks
.iter()
.position(|t| matches!(t.spec.config, CodecConfig::Avc { .. }))
.unwrap_or(0),
)
}
fn presentation_times(track: &Track) -> Vec<i64> {
let mut out = Vec::with_capacity(track.samples.len());
let mut dts: i64 = 0;
for s in &track.samples {
out.push(dts + s.composition_offset as i64);
dts += s.duration as i64;
}
out
}
fn rescale_floor(ticks: u64, from_timescale: u32, to_timescale: u32) -> i64 {
if from_timescale == 0 || from_timescale == to_timescale {
return ticks as i64;
}
((ticks as u128 * to_timescale as u128) / from_timescale as u128) as i64
}
fn gcd(mut a: u64, mut b: u64) -> u64 {
while b != 0 {
let t = b;
b = a % b;
a = t;
}
a
}
fn lcm(a: u64, b: u64) -> u64 {
let a = a.max(1);
let b = b.max(1);
a / gcd(a, b) * b
}
impl Media {
pub fn select_tracks(&self, indices: &[usize]) -> Result<Media> {
if indices.is_empty() {
return Err(Error::InvalidInput("select_tracks: empty track selection"));
}
let mut tracks = Vec::with_capacity(indices.len());
for &i in indices {
let t = self.tracks.get(i).ok_or(Error::InvalidInput(
"select_tracks: track index out of range",
))?;
tracks.push(t.clone());
}
Ok(Media::new(tracks, self.movie_timescale))
}
pub fn select_tracks_by<F>(&self, mut keep: F) -> Result<Media>
where
F: FnMut(&Track) -> bool,
{
let tracks: Vec<Track> = self.tracks.iter().filter(|t| keep(t)).cloned().collect();
if tracks.is_empty() {
return Err(Error::InvalidInput(
"select_tracks_by: predicate kept no track",
));
}
Ok(Media::new(tracks, self.movie_timescale))
}
pub fn trim(&self, start: u64, end: u64) -> Result<Media> {
if start >= end {
return Err(Error::InvalidInput("trim: start must be < end"));
}
if self.tracks.is_empty() {
return Err(Error::InvalidInput("trim: media has no tracks"));
}
let anchor = anchor_index(self).expect("non-empty media has an anchor");
let mut out_tracks = Vec::with_capacity(self.tracks.len());
let mut kept_any = false;
for (ti, track) in self.tracks.iter().enumerate() {
let ts = track.spec.timescale;
let lo = rescale_floor(start, self.movie_timescale, ts);
let hi = rescale_floor(end, self.movie_timescale, ts);
let pts = presentation_times(track);
let first_in = pts.iter().position(|&p| p >= lo && p < hi);
let mut kept = Vec::new();
if let Some(mut start_idx) = first_in {
if ti == anchor {
while start_idx > 0 && !track.samples[start_idx].is_sync {
start_idx -= 1;
}
}
for s in &track.samples[start_idx..] {
let idx = start_idx + kept.len();
if pts[idx] >= hi {
break;
}
kept.push(s.clone());
}
}
if !kept.is_empty() {
kept_any = true;
}
out_tracks.push(Track::new(track.spec.clone(), kept));
}
if !kept_any {
return Err(Error::InvalidInput(
"trim: window selected no samples on any track",
));
}
Ok(Media::new(out_tracks, self.movie_timescale))
}
pub fn anchor_duration(&self) -> Option<(u64, u32)> {
let anchor = anchor_index(self)?;
let t = &self.tracks[anchor];
let ticks: u64 = t.samples.iter().map(|s| s.duration as u64).sum();
Some((ticks, t.spec.timescale))
}
}
#[derive(Debug, Clone)]
pub struct Repackage {
target_duration_secs: f64,
select: Option<Vec<usize>>,
trim: Option<(u64, u64)>,
}
impl Repackage {
pub fn new(target_duration_secs: f64) -> Self {
Self {
target_duration_secs,
select: None,
trim: None,
}
}
pub fn select_tracks(mut self, indices: &[usize]) -> Self {
self.select = Some(indices.to_vec());
self
}
pub fn trim(mut self, start: u64, end: u64) -> Self {
self.trim = Some((start, end));
self
}
pub fn run_media(&self, media: &Media) -> Result<RepackageOutput> {
let mut work = media.clone();
if let Some(indices) = &self.select {
work = work.select_tracks(indices)?;
}
if let Some((start, end)) = self.trim {
work = work.trim(start, end)?;
}
self.segment(&work)
}
pub fn run(&self, fmp4: &[u8]) -> Result<RepackageOutput> {
let media = Fmp4Demux::new().unpackage(fmp4)?;
self.run_media(&media)
}
fn segment(&self, media: &Media) -> Result<RepackageOutput> {
if media.tracks.is_empty() {
return Err(Error::InvalidInput("repackage: media has no tracks"));
}
let specs: Vec<TrackSpec> = media.tracks.iter().map(|t| t.spec.clone()).collect();
let movie_timescale = media.movie_timescale;
let mut seg = Segmenter::new(specs, movie_timescale, self.target_duration_secs)?;
let init_segment = seg.init_segment()?;
let mut seg_ready: Vec<Vec<u8>> = Vec::new();
let mut cursors = alloc::vec![0usize; media.tracks.len()];
let common = media
.tracks
.iter()
.map(|t| t.spec.timescale as u64)
.fold(1u64, lcm);
let mut dts = alloc::vec![0u128; media.tracks.len()];
loop {
let mut best: Option<usize> = None;
let mut best_key = u128::MAX;
for (ti, track) in media.tracks.iter().enumerate() {
if cursors[ti] >= track.samples.len() {
continue;
}
let scale = (common / track.spec.timescale.max(1) as u64) as u128;
let key = dts[ti] * scale;
if key < best_key {
best_key = key;
best = Some(ti);
}
}
let Some(ti) = best else { break };
let track = &media.tracks[ti];
let sample = &track.samples[cursors[ti]];
seg.push(track.spec.track_id, sample.clone())?;
dts[ti] += sample.duration as u128;
cursors[ti] += 1;
for s in seg.take_ready() {
seg_ready.push(s);
}
}
seg.flush()?;
seg_ready.extend(seg.take_ready());
Ok(RepackageOutput {
init_segment,
media_segments: seg_ready,
})
}
}
#[derive(Debug, Clone)]
pub struct RepackageOutput {
pub init_segment: Vec<u8>,
pub media_segments: Vec<Vec<u8>>,
}
impl RepackageOutput {
pub fn to_contiguous(&self) -> Vec<u8> {
let total =
self.init_segment.len() + self.media_segments.iter().map(Vec::len).sum::<usize>();
let mut out = Vec::with_capacity(total);
out.extend_from_slice(&self.init_segment);
for seg in &self.media_segments {
out.extend_from_slice(seg);
}
out
}
pub fn segment_count(&self) -> usize {
self.media_segments.len()
}
}