use super::boxes::BoxBuilder;
use crate::AudioInfo;
pub(super) struct AudioBuildPlan {
pub(super) info: AudioInfo,
pub(super) sample_sizes: Vec<u32>,
pub(super) durations: Vec<u32>,
pub(super) total_duration_in_own_ts: u64,
pub(super) total_duration_in_movie_ts: u64,
pub(super) samples_per_chunk: u32,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct InterleaveStep {
pub(super) track: InterleaveTrack,
pub(super) bytes: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum InterleaveTrack {
Video,
Audio,
}
pub(super) fn chunk_count_of(sample_count: usize, spc: u32) -> usize {
if sample_count == 0 {
return 0;
}
let spc = spc.max(1) as usize;
sample_count.div_ceil(spc)
}
fn chunk_byte_sizes(sample_sizes: &[u32], spc: u32) -> Vec<u64> {
let spc = spc.max(1) as usize;
let mut out = Vec::new();
let mut i = 0usize;
while i < sample_sizes.len() {
let end = (i + spc).min(sample_sizes.len());
let mut total: u64 = 0;
for &s in &sample_sizes[i..end] {
total += s as u64;
}
out.push(total);
i = end;
}
out
}
pub(super) fn plan_interleaved_layout(
first_sample_file_offset: u64,
video_sample_sizes: &[u32],
video_spc: u32,
audio_plan: Option<&AudioBuildPlan>,
) -> (Vec<u64>, Vec<u64>, Vec<InterleaveStep>) {
let video_chunks = chunk_byte_sizes(video_sample_sizes, video_spc);
let audio_chunks = match audio_plan {
Some(p) => chunk_byte_sizes(&p.sample_sizes, p.samples_per_chunk),
None => Vec::new(),
};
let mut video_offsets: Vec<u64> = Vec::with_capacity(video_chunks.len());
let mut audio_offsets: Vec<u64> = Vec::with_capacity(audio_chunks.len());
let mut plan: Vec<InterleaveStep> = Vec::with_capacity(video_chunks.len() + audio_chunks.len());
let mut cursor = first_sample_file_offset;
let mut vi = 0usize;
let mut ai = 0usize;
loop {
if vi < video_chunks.len() {
video_offsets.push(cursor);
let size = video_chunks[vi];
plan.push(InterleaveStep {
track: InterleaveTrack::Video,
bytes: size,
});
cursor = cursor.saturating_add(size);
vi += 1;
}
if ai < audio_chunks.len() {
audio_offsets.push(cursor);
let size = audio_chunks[ai];
plan.push(InterleaveStep {
track: InterleaveTrack::Audio,
bytes: size,
});
cursor = cursor.saturating_add(size);
ai += 1;
}
if vi >= video_chunks.len() && ai >= audio_chunks.len() {
break;
}
}
(video_offsets, audio_offsets, plan)
}
pub(super) fn build_stsc(sample_count: u32, samples_per_chunk: u32) -> Vec<u8> {
let mut b = BoxBuilder::new(b"stsc");
b.u8(0);
b.extend(&[0, 0, 0]);
let spc = samples_per_chunk.max(1);
if sample_count == 0 {
b.u32(0);
return b.finish();
}
let full_chunks = sample_count / spc;
let remainder = sample_count % spc;
if remainder == 0 {
b.u32(1);
b.u32(1); b.u32(spc); b.u32(1); } else if full_chunks == 0 {
b.u32(1);
b.u32(1);
b.u32(remainder);
b.u32(1);
} else {
b.u32(2);
b.u32(1);
b.u32(spc);
b.u32(1);
b.u32(full_chunks + 1); b.u32(remainder);
b.u32(1);
}
b.finish()
}
pub(super) fn build_stsz(sample_sizes: &[u32]) -> Vec<u8> {
let mut b = BoxBuilder::new(b"stsz");
b.u8(0);
b.extend(&[0, 0, 0]);
b.u32(0); b.u32(sample_sizes.len() as u32); for &s in sample_sizes {
b.u32(s);
}
b.finish()
}
pub(super) fn build_stco(chunk_offsets: &[u64]) -> Vec<u8> {
let mut b = BoxBuilder::new(b"stco");
b.u8(0);
b.extend(&[0, 0, 0]);
b.u32(chunk_offsets.len() as u32);
for &off in chunk_offsets {
debug_assert!(
off <= u32::MAX as u64,
"stco offset exceeds u32; should be co64"
);
b.u32(off as u32);
}
b.finish()
}
pub(super) fn build_co64(chunk_offsets: &[u64]) -> Vec<u8> {
let mut b = BoxBuilder::new(b"co64");
b.u8(0);
b.extend(&[0, 0, 0]);
b.u32(chunk_offsets.len() as u32);
for &off in chunk_offsets {
b.u64(off);
}
b.finish()
}
#[cfg(test)]
pub(super) fn compute_chunk_offsets(
first_sample_file_offset: u64,
sample_sizes: &[u32],
samples_per_chunk: u32,
) -> Vec<u64> {
let spc = samples_per_chunk.max(1) as usize;
let total = sample_sizes.len();
if total == 0 {
return Vec::new();
}
let chunk_count = (total + spc - 1) / spc;
let mut offsets = Vec::with_capacity(chunk_count);
let mut cursor = first_sample_file_offset;
let mut sample_idx = 0usize;
for _ in 0..chunk_count {
offsets.push(cursor);
let end = (sample_idx + spc).min(total);
for &size in &sample_sizes[sample_idx..end] {
cursor = cursor.saturating_add(size as u64);
}
sample_idx = end;
}
offsets
}