1use crate::parse::validate_tree;
2use crate::*;
3use std::io::{Seek, SeekFrom, Write};
4
5pub struct Mp4Writer<W: Write + Seek> {
7 writer: W,
8 config: WriterConfig,
9 tracks: Vec<WriterTrack>,
10 first_sample_written: bool,
11 current_track: Option<TrackId>,
12 ftyp_offset: u64,
13 free_offset: u64,
14 mdat_offset: u64,
15 data_offset: u64,
16}
17
18struct WriterTrack {
19 id: TrackId,
20 config: TrackConfig,
21 samples: Vec<WriterSample>,
22 chunks: Vec<WriterChunk>,
23 force_co64: bool,
24}
25
26#[derive(Clone, Copy)]
27struct WriterSample {
28 size: u32,
29 duration: u32,
30 cts_offset: i32,
31 is_sync: bool,
32}
33
34#[derive(Clone, Copy)]
35struct WriterChunk {
36 offset: u64,
37 sample_count: u32,
38}
39
40impl<W: Write + Seek> Mp4Writer<W> {
41 pub fn new(mut writer: W, config: WriterConfig) -> Result<Self, Error> {
43 if config.movie_timescale == 0 {
44 return Err(Error::InvalidTimescale { track: None });
45 }
46 let ftyp_offset = writer.stream_position()?;
47 let ftyp = make_ftyp(&config, false)?;
48 writer.write_all(&ftyp)?;
49 let free_offset = writer.stream_position()?;
50 writer.write_all(&8u32.to_be_bytes())?;
51 writer.write_all(b"free")?;
52 let mdat_offset = writer.stream_position()?;
53 writer.write_all(&0u32.to_be_bytes())?;
54 writer.write_all(b"mdat")?;
55 let data_offset = writer.stream_position()?;
56 Ok(Self {
57 writer,
58 config,
59 tracks: Vec::new(),
60 first_sample_written: false,
61 current_track: None,
62 ftyp_offset,
63 free_offset,
64 mdat_offset,
65 data_offset,
66 })
67 }
68
69 pub fn add_track(&mut self, track: TrackConfig) -> Result<TrackId, Error> {
71 if self.first_sample_written {
72 return Err(Error::WriterState("tracks must be added before samples"));
73 }
74 validate_track_config(&track)?;
75 let next = self
76 .tracks
77 .len()
78 .checked_add(1)
79 .ok_or(Error::WriterState("track count overflow"))?;
80 let id = TrackId(
81 u32::try_from(next).map_err(|_| Error::WriterState("track count exceeds u32"))?,
82 );
83 self.tracks.push(WriterTrack {
84 id,
85 config: track,
86 samples: Vec::new(),
87 chunks: Vec::new(),
88 force_co64: self.config.chunk_offsets == ChunkOffsetChoice::Co64,
89 });
90 Ok(id)
91 }
92
93 pub fn write_sample(&mut self, id: TrackId, sample: &SampleInput<'_>) -> Result<(), Error> {
95 let position = self
96 .tracks
97 .iter()
98 .position(|track| track.id == id)
99 .ok_or(Error::NoSuchTrack(id))?;
100 let size = u32::try_from(sample.data.len()).map_err(|_| Error::LimitExceeded {
101 limit: "sample_size",
102 value: u64::try_from(sample.data.len()).unwrap_or(u64::MAX),
103 max: u64::from(u32::MAX),
104 })?;
105 let offset = self.writer.stream_position()?;
106 self.writer.write_all(sample.data)?;
107 let track = self
108 .tracks
109 .get_mut(position)
110 .ok_or(Error::NoSuchTrack(id))?;
111 if self.current_track != Some(id) {
112 track.chunks.push(WriterChunk {
113 offset,
114 sample_count: 0,
115 });
116 self.current_track = Some(id);
117 }
118 let chunk = track
119 .chunks
120 .last_mut()
121 .ok_or(Error::WriterState("sample chunk is missing"))?;
122 chunk.sample_count = chunk
123 .sample_count
124 .checked_add(1)
125 .ok_or(Error::WriterState("chunk sample count overflow"))?;
126 track.samples.push(WriterSample {
127 size,
128 duration: sample.duration,
129 cts_offset: sample.cts_offset,
130 is_sync: sample.is_sync,
131 });
132 self.first_sample_written = true;
133 Ok(())
134 }
135
136 pub fn finish(mut self) -> Result<W, Error> {
138 if self.tracks.is_empty() {
139 return Err(Error::WriterState("at least one track is required"));
140 }
141 let media_end = self.writer.stream_position()?;
142 let payload_size = media_end
143 .checked_sub(self.data_offset)
144 .ok_or(Error::WriterState("media-data size underflow"))?;
145 if payload_size <= u64::from(u32::MAX).saturating_sub(8) {
146 let size = u32::try_from(payload_size.saturating_add(8))
147 .map_err(|_| Error::WriterState("media-data size exceeds u32"))?;
148 self.writer.seek(SeekFrom::Start(self.mdat_offset))?;
149 self.writer.write_all(&size.to_be_bytes())?;
150 self.writer.write_all(b"mdat")?;
151 } else {
152 let size = payload_size
153 .checked_add(16)
154 .ok_or(Error::WriterState("media-data size overflow"))?;
155 self.writer.seek(SeekFrom::Start(self.free_offset))?;
156 self.writer.write_all(&1u32.to_be_bytes())?;
157 self.writer.write_all(b"mdat")?;
158 self.writer.write_all(&size.to_be_bytes())?;
159 }
160 let has_avc = self
161 .tracks
162 .iter()
163 .any(|track| matches!(track.config.codec, Codec::H264 { .. }));
164 self.writer.seek(SeekFrom::Start(self.ftyp_offset))?;
165 self.writer.write_all(&make_ftyp(&self.config, has_avc)?)?;
166 self.writer.seek(SeekFrom::Start(media_end))?;
167 let moov = make_moov(&self.config, &self.tracks)?;
168 validate_tree(
169 moov.get(8..).ok_or(Error::WriterState(
170 "encoded movie is smaller than its header",
171 ))?,
172 FourCc(*b"moov"),
173 0,
174 Limits::default(),
175 )?;
176 self.writer.write_all(&moov)?;
177 self.writer.flush()?;
178 Ok(self.writer)
179 }
180}
181
182fn validate_track_config(track: &TrackConfig) -> Result<(), Error> {
183 if track.timescale == 0 {
184 return Err(Error::InvalidTimescale { track: None });
185 }
186 let bytes = track.language.as_bytes();
187 if bytes.len() != 3 || !bytes.iter().all(|byte| byte.is_ascii_lowercase()) {
188 return Err(Error::WriterState(
189 "language must be three lowercase ASCII letters",
190 ));
191 }
192 if !track.width.is_finite()
193 || !track.height.is_finite()
194 || track.width < 0.0
195 || track.height < 0.0
196 {
197 return Err(Error::WriterState(
198 "track dimensions must be finite and nonnegative",
199 ));
200 }
201 if track.width > 65_535.0 || track.height > 65_535.0 {
202 return Err(Error::WriterState("track dimensions exceed 16.16 range"));
203 }
204 Ok(())
205}
206
207fn make_box(kind: FourCc, body: Vec<u8>) -> Result<Vec<u8>, Error> {
208 let size = body
209 .len()
210 .checked_add(8)
211 .ok_or(Error::WriterState("box size overflow"))?;
212 let size = u32::try_from(size).map_err(|_| Error::WriterState("box exceeds u32 size"))?;
213 let mut result = Vec::with_capacity(usize::try_from(size).unwrap_or(0));
214 result.extend_from_slice(&size.to_be_bytes());
215 result.extend_from_slice(&kind.0);
216 result.extend_from_slice(&body);
217 Ok(result)
218}
219
220fn full_box(version: u8, flags: u32, mut body: Vec<u8>) -> Vec<u8> {
221 let mut result = Vec::with_capacity(body.len().saturating_add(4));
222 result.push(version);
223 let bytes = flags.to_be_bytes();
224 result.extend_from_slice(bytes.get(1..).unwrap_or_default());
225 result.append(&mut body);
226 result
227}
228
229fn make_ftyp(config: &WriterConfig, has_avc: bool) -> Result<Vec<u8>, Error> {
230 let mut brands = config.compatible_brands.clone();
231 for required in [FourCc(*b"isom"), FourCc(*b"iso2"), FourCc(*b"mp41")] {
232 if !brands.contains(&required) {
233 brands.push(required);
234 }
235 }
236 brands.push(if has_avc {
237 FourCc(*b"avc1")
238 } else {
239 config.major_brand
240 });
241 let mut body = Vec::with_capacity(8usize.saturating_add(brands.len().saturating_mul(4)));
242 body.extend_from_slice(&config.major_brand.0);
243 body.extend_from_slice(&512u32.to_be_bytes());
244 for brand in brands {
245 body.extend_from_slice(&brand.0);
246 }
247 make_box(FourCc(*b"ftyp"), body)
248}
249
250fn make_moov(config: &WriterConfig, tracks: &[WriterTrack]) -> Result<Vec<u8>, Error> {
251 let movie_duration = tracks.iter().try_fold(0u64, |maximum, track| {
252 Ok::<u64, Error>(maximum.max(track_movie_duration(config.movie_timescale, track)?))
253 })?;
254 let mut body = make_mvhd(config.movie_timescale, movie_duration, tracks.len())?;
255 for track in tracks {
256 body.extend_from_slice(&make_trak(config.movie_timescale, track)?);
257 }
258 make_box(FourCc(*b"moov"), body)
259}
260
261fn track_media_duration(track: &WriterTrack) -> Result<u64, Error> {
262 track.samples.iter().try_fold(0u64, |sum, sample| {
263 sum.checked_add(u64::from(sample.duration))
264 .ok_or(Error::WriterState("track duration overflow"))
265 })
266}
267
268fn track_movie_duration(movie_timescale: u32, track: &WriterTrack) -> Result<u64, Error> {
269 if !track.config.edit_list.is_empty() {
270 return track.config.edit_list.iter().try_fold(0u64, |sum, edit| {
271 sum.checked_add(edit.segment_duration)
272 .ok_or(Error::WriterState("edit duration overflow"))
273 });
274 }
275 let media = u128::from(track_media_duration(track)?);
276 let scaled = media
277 .checked_mul(u128::from(movie_timescale))
278 .ok_or(Error::WriterState("movie duration overflow"))?
279 .checked_div(u128::from(track.config.timescale))
280 .ok_or(Error::InvalidTimescale {
281 track: Some(track.id),
282 })?;
283 u64::try_from(scaled).map_err(|_| Error::WriterState("movie duration exceeds u64"))
284}
285
286fn make_mvhd(timescale: u32, duration: u64, track_count: usize) -> Result<Vec<u8>, Error> {
287 let version = if duration > u64::from(u32::MAX) { 1 } else { 0 };
288 let mut body = Vec::new();
289 if version == 1 {
290 body.extend_from_slice(&0u64.to_be_bytes());
291 body.extend_from_slice(&0u64.to_be_bytes());
292 body.extend_from_slice(×cale.to_be_bytes());
293 body.extend_from_slice(&duration.to_be_bytes());
294 } else {
295 body.extend_from_slice(&0u32.to_be_bytes());
296 body.extend_from_slice(&0u32.to_be_bytes());
297 body.extend_from_slice(×cale.to_be_bytes());
298 body.extend_from_slice(&u32::try_from(duration).unwrap_or(u32::MAX).to_be_bytes());
299 }
300 body.extend_from_slice(&0x0001_0000u32.to_be_bytes());
301 body.extend_from_slice(&0x0100u16.to_be_bytes());
302 body.extend_from_slice(&[0; 10]);
303 append_matrix(&mut body);
304 body.extend_from_slice(&[0; 24]);
305 let next = u32::try_from(track_count)
306 .unwrap_or(u32::MAX)
307 .saturating_add(1);
308 body.extend_from_slice(&next.to_be_bytes());
309 make_box(FourCc(*b"mvhd"), full_box(version, 0, body))
310}
311
312fn append_matrix(body: &mut Vec<u8>) {
313 for value in [0x0001_0000u32, 0, 0, 0, 0x0001_0000, 0, 0, 0, 0x4000_0000] {
314 body.extend_from_slice(&value.to_be_bytes());
315 }
316}
317
318fn make_trak(movie_timescale: u32, track: &WriterTrack) -> Result<Vec<u8>, Error> {
319 let duration = track_movie_duration(movie_timescale, track)?;
320 let mut body = make_tkhd(duration, track)?;
321 if !track.config.edit_list.is_empty() {
322 body.extend_from_slice(&make_edts(&track.config.edit_list)?);
323 }
324 body.extend_from_slice(&make_mdia(track)?);
325 make_box(FourCc(*b"trak"), body)
326}
327
328fn fixed_16_16(value: f64) -> Result<u32, Error> {
329 let scaled = value * 65_536.0;
330 if !(0.0..=f64::from(u32::MAX)).contains(&scaled) {
331 return Err(Error::WriterState("fixed-point value is out of range"));
332 }
333 Ok(scaled.round() as u32)
334}
335
336fn make_tkhd(duration: u64, track: &WriterTrack) -> Result<Vec<u8>, Error> {
337 let version = if duration > u64::from(u32::MAX) { 1 } else { 0 };
338 let mut body = Vec::new();
339 if version == 1 {
340 body.extend_from_slice(&[0; 16]);
341 body.extend_from_slice(&track.id.0.to_be_bytes());
342 body.extend_from_slice(&0u32.to_be_bytes());
343 body.extend_from_slice(&duration.to_be_bytes());
344 } else {
345 body.extend_from_slice(&[0; 8]);
346 body.extend_from_slice(&track.id.0.to_be_bytes());
347 body.extend_from_slice(&0u32.to_be_bytes());
348 body.extend_from_slice(&u32::try_from(duration).unwrap_or(u32::MAX).to_be_bytes());
349 }
350 body.extend_from_slice(&[0; 8]);
351 body.extend_from_slice(&0i16.to_be_bytes());
352 body.extend_from_slice(&0i16.to_be_bytes());
353 let volume = if track.config.kind == TrackKind::Audio {
354 0x0100u16
355 } else {
356 0
357 };
358 body.extend_from_slice(&volume.to_be_bytes());
359 body.extend_from_slice(&0u16.to_be_bytes());
360 append_matrix(&mut body);
361 body.extend_from_slice(&fixed_16_16(track.config.width)?.to_be_bytes());
362 body.extend_from_slice(&fixed_16_16(track.config.height)?.to_be_bytes());
363 make_box(FourCc(*b"tkhd"), full_box(version, 3, body))
364}
365
366fn make_edts(edits: &[Edit]) -> Result<Vec<u8>, Error> {
367 let version = if edits.iter().any(|edit| {
368 edit.segment_duration > u64::from(u32::MAX)
369 || edit
370 .media_time
371 .is_some_and(|value| value > i64::from(i32::MAX) as u64)
372 }) {
373 1
374 } else {
375 0
376 };
377 let mut body = Vec::new();
378 body.extend_from_slice(
379 &u32::try_from(edits.len())
380 .map_err(|_| Error::WriterState("edit count exceeds u32"))?
381 .to_be_bytes(),
382 );
383 for edit in edits {
384 if version == 1 {
385 body.extend_from_slice(&edit.segment_duration.to_be_bytes());
386 let media_time = edit
387 .media_time
388 .map(|value| i64::try_from(value).unwrap_or(i64::MAX))
389 .unwrap_or(-1);
390 body.extend_from_slice(&media_time.to_be_bytes());
391 } else {
392 body.extend_from_slice(
393 &u32::try_from(edit.segment_duration)
394 .unwrap_or(u32::MAX)
395 .to_be_bytes(),
396 );
397 let media_time = edit
398 .media_time
399 .map(|value| i32::try_from(value).unwrap_or(i32::MAX))
400 .unwrap_or(-1);
401 body.extend_from_slice(&media_time.to_be_bytes());
402 }
403 let integer = edit.media_rate.trunc();
404 let fraction = ((edit.media_rate - integer) * 65_536.0).round();
405 body.extend_from_slice(&(integer as i16).to_be_bytes());
406 body.extend_from_slice(&(fraction as u16).to_be_bytes());
407 }
408 let elst = make_box(FourCc(*b"elst"), full_box(version, 0, body))?;
409 make_box(FourCc(*b"edts"), elst)
410}
411
412fn make_mdia(track: &WriterTrack) -> Result<Vec<u8>, Error> {
413 let mut body = make_mdhd(track)?;
414 body.extend_from_slice(&make_hdlr(track)?);
415 body.extend_from_slice(&make_minf(track)?);
416 make_box(FourCc(*b"mdia"), body)
417}
418
419fn pack_language(language: &str) -> Result<u16, Error> {
420 let bytes = language.as_bytes();
421 let first = u16::from(
422 *bytes
423 .first()
424 .ok_or(Error::WriterState("language is empty"))?,
425 )
426 .saturating_sub(0x60);
427 let second = u16::from(
428 *bytes
429 .get(1)
430 .ok_or(Error::WriterState("language is too short"))?,
431 )
432 .saturating_sub(0x60);
433 let third = u16::from(
434 *bytes
435 .get(2)
436 .ok_or(Error::WriterState("language is too short"))?,
437 )
438 .saturating_sub(0x60);
439 Ok((first << 10) | (second << 5) | third)
440}
441
442fn make_mdhd(track: &WriterTrack) -> Result<Vec<u8>, Error> {
443 let duration = track_media_duration(track)?;
444 let version = if duration > u64::from(u32::MAX) { 1 } else { 0 };
445 let mut body = Vec::new();
446 if version == 1 {
447 body.extend_from_slice(&[0; 16]);
448 body.extend_from_slice(&track.config.timescale.to_be_bytes());
449 body.extend_from_slice(&duration.to_be_bytes());
450 } else {
451 body.extend_from_slice(&[0; 8]);
452 body.extend_from_slice(&track.config.timescale.to_be_bytes());
453 body.extend_from_slice(&u32::try_from(duration).unwrap_or(u32::MAX).to_be_bytes());
454 }
455 body.extend_from_slice(&pack_language(&track.config.language)?.to_be_bytes());
456 body.extend_from_slice(&0u16.to_be_bytes());
457 make_box(FourCc(*b"mdhd"), full_box(version, 0, body))
458}
459
460fn handler_type(kind: TrackKind) -> FourCc {
461 match kind {
462 TrackKind::Video => FourCc(*b"vide"),
463 TrackKind::Audio => FourCc(*b"soun"),
464 TrackKind::Subtitle => FourCc(*b"subt"),
465 TrackKind::Other(value) => value,
466 }
467}
468
469fn make_hdlr(track: &WriterTrack) -> Result<Vec<u8>, Error> {
470 let mut body = Vec::new();
471 body.extend_from_slice(&0u32.to_be_bytes());
472 body.extend_from_slice(&handler_type(track.config.kind).0);
473 body.extend_from_slice(&[0; 12]);
474 body.extend_from_slice(track.config.handler_name.as_bytes());
475 body.push(0);
476 make_box(FourCc(*b"hdlr"), full_box(0, 0, body))
477}
478
479fn make_minf(track: &WriterTrack) -> Result<Vec<u8>, Error> {
480 let mut body = match track.config.kind {
481 TrackKind::Video => {
482 let mut fields = Vec::new();
483 fields.extend_from_slice(&0u16.to_be_bytes());
484 fields.extend_from_slice(&[0; 6]);
485 make_box(FourCc(*b"vmhd"), full_box(0, 1, fields))?
486 }
487 TrackKind::Audio => {
488 let mut fields = Vec::new();
489 fields.extend_from_slice(&0i16.to_be_bytes());
490 fields.extend_from_slice(&0u16.to_be_bytes());
491 make_box(FourCc(*b"smhd"), full_box(0, 0, fields))?
492 }
493 TrackKind::Subtitle | TrackKind::Other(_) => {
494 make_box(FourCc(*b"nmhd"), full_box(0, 0, Vec::new()))?
495 }
496 };
497 body.extend_from_slice(&make_dinf()?);
498 body.extend_from_slice(&make_stbl(track)?);
499 make_box(FourCc(*b"minf"), body)
500}
501
502fn make_dinf() -> Result<Vec<u8>, Error> {
503 let url = make_box(FourCc(*b"url "), full_box(0, 1, Vec::new()))?;
504 let mut dref_body = Vec::new();
505 dref_body.extend_from_slice(&1u32.to_be_bytes());
506 dref_body.extend_from_slice(&url);
507 let dref = make_box(FourCc(*b"dref"), full_box(0, 0, dref_body))?;
508 make_box(FourCc(*b"dinf"), dref)
509}
510
511fn make_stbl(track: &WriterTrack) -> Result<Vec<u8>, Error> {
512 let mut body = make_stsd(track)?;
513 body.extend_from_slice(&make_stts(track)?);
514 if track.samples.iter().any(|sample| sample.cts_offset != 0) {
515 body.extend_from_slice(&make_ctts(track)?);
516 }
517 if track.samples.iter().any(|sample| !sample.is_sync) {
518 body.extend_from_slice(&make_stss(track)?);
519 }
520 body.extend_from_slice(&make_stsc(track)?);
521 body.extend_from_slice(&make_stsz(track)?);
522 body.extend_from_slice(&make_chunk_offsets(track)?);
523 make_box(FourCc(*b"stbl"), body)
524}
525
526fn make_stsd(track: &WriterTrack) -> Result<Vec<u8>, Error> {
527 let entry = make_sample_entry(track)?;
528 let mut body = Vec::new();
529 body.extend_from_slice(&1u32.to_be_bytes());
530 body.extend_from_slice(&entry);
531 make_box(FourCc(*b"stsd"), full_box(0, 0, body))
532}
533
534fn visual_fields(width: f64, height: f64) -> Result<Vec<u8>, Error> {
535 let mut body = Vec::with_capacity(78);
536 body.extend_from_slice(&[0; 6]);
537 body.extend_from_slice(&1u16.to_be_bytes());
538 body.extend_from_slice(&[0; 16]);
539 body.extend_from_slice(
540 &u16::try_from(width.round() as u64)
541 .map_err(|_| Error::WriterState("video width exceeds u16"))?
542 .to_be_bytes(),
543 );
544 body.extend_from_slice(
545 &u16::try_from(height.round() as u64)
546 .map_err(|_| Error::WriterState("video height exceeds u16"))?
547 .to_be_bytes(),
548 );
549 body.extend_from_slice(&0x0048_0000u32.to_be_bytes());
550 body.extend_from_slice(&0x0048_0000u32.to_be_bytes());
551 body.extend_from_slice(&0u32.to_be_bytes());
552 body.extend_from_slice(&1u16.to_be_bytes());
553 body.extend_from_slice(&[0; 32]);
554 body.extend_from_slice(&0x0018u16.to_be_bytes());
555 body.extend_from_slice(&u16::MAX.to_be_bytes());
556 Ok(body)
557}
558
559fn audio_fields(channels: u16, sample_rate: u32) -> Vec<u8> {
560 let mut body = Vec::with_capacity(28);
561 body.extend_from_slice(&[0; 6]);
562 body.extend_from_slice(&1u16.to_be_bytes());
563 body.extend_from_slice(&[0; 8]);
564 body.extend_from_slice(&channels.to_be_bytes());
565 body.extend_from_slice(&16u16.to_be_bytes());
566 body.extend_from_slice(&0u16.to_be_bytes());
567 body.extend_from_slice(&0u16.to_be_bytes());
568 let fixed_rate = if sample_rate > 65_535 {
572 0
573 } else {
574 sample_rate.saturating_mul(65_536)
575 };
576 body.extend_from_slice(&fixed_rate.to_be_bytes());
577 body
578}
579
580fn append_sidecars(body: &mut Vec<u8>, sidecars: &[SidecarBox]) -> Result<(), Error> {
581 for sidecar in sidecars {
582 body.extend_from_slice(&make_box(sidecar.kind, sidecar.payload.clone())?);
583 }
584 Ok(())
585}
586
587fn make_sample_entry(track: &WriterTrack) -> Result<Vec<u8>, Error> {
588 match &track.config.codec {
589 Codec::H264 { entry, config } => {
590 let mut body = visual_fields(track.config.width, track.config.height)?;
591 body.extend_from_slice(&make_box(FourCc(*b"avcC"), config.raw.clone())?);
592 append_sidecars(&mut body, &config.sidecars)?;
593 make_box(*entry, body)
594 }
595 Codec::H265 { entry, config } => {
596 let mut body = visual_fields(track.config.width, track.config.height)?;
597 body.extend_from_slice(&make_box(FourCc(*b"hvcC"), config.raw.clone())?);
598 append_sidecars(&mut body, &config.sidecars)?;
599 make_box(*entry, body)
600 }
601 Codec::Aac { config } => {
602 let mut body = audio_fields(config.channels, config.sample_rate);
603 body.extend_from_slice(&make_box(FourCc(*b"esds"), config.raw_esds.clone())?);
604 append_sidecars(&mut body, &config.sidecars)?;
605 make_box(FourCc(*b"mp4a"), body)
606 }
607 Codec::Opus { config } => {
608 let mut body = audio_fields(u16::from(config.channels), track.config.timescale);
609 body.extend_from_slice(&make_box(FourCc(*b"dOps"), config.raw.clone())?);
610 append_sidecars(&mut body, &config.sidecars)?;
611 make_box(FourCc(*b"Opus"), body)
612 }
613 Codec::Other { entry, raw } => make_box(*entry, raw.clone()),
614 }
615}
616
617fn rle_u32<I>(values: I) -> Vec<(u32, u32)>
618where
619 I: IntoIterator<Item = u32>,
620{
621 let mut result: Vec<(u32, u32)> = Vec::new();
622 for value in values {
623 if let Some(last) = result.last_mut() {
624 if last.1 == value {
625 last.0 = last.0.saturating_add(1);
626 continue;
627 }
628 }
629 result.push((1, value));
630 }
631 result
632}
633
634fn make_stts(track: &WriterTrack) -> Result<Vec<u8>, Error> {
635 let entries = rle_u32(track.samples.iter().map(|sample| sample.duration));
636 let mut body = Vec::new();
637 body.extend_from_slice(
638 &u32::try_from(entries.len())
639 .map_err(|_| Error::WriterState("time entry count exceeds u32"))?
640 .to_be_bytes(),
641 );
642 for (count, delta) in entries {
643 body.extend_from_slice(&count.to_be_bytes());
644 body.extend_from_slice(&delta.to_be_bytes());
645 }
646 make_box(FourCc(*b"stts"), full_box(0, 0, body))
647}
648
649fn make_ctts(track: &WriterTrack) -> Result<Vec<u8>, Error> {
650 let version = if track.samples.iter().any(|sample| sample.cts_offset < 0) {
651 1
652 } else {
653 0
654 };
655 let mut entries: Vec<(u32, i32)> = Vec::new();
656 for sample in &track.samples {
657 if let Some(last) = entries.last_mut() {
658 if last.1 == sample.cts_offset {
659 last.0 = last.0.saturating_add(1);
660 continue;
661 }
662 }
663 entries.push((1, sample.cts_offset));
664 }
665 let mut body = Vec::new();
666 body.extend_from_slice(
667 &u32::try_from(entries.len())
668 .map_err(|_| Error::WriterState("composition entry count exceeds u32"))?
669 .to_be_bytes(),
670 );
671 for (count, offset) in entries {
672 body.extend_from_slice(&count.to_be_bytes());
673 if version == 1 {
674 body.extend_from_slice(&offset.to_be_bytes());
675 } else {
676 body.extend_from_slice(
677 &u32::try_from(offset)
678 .map_err(|_| Error::WriterState("negative version-zero composition offset"))?
679 .to_be_bytes(),
680 );
681 }
682 }
683 make_box(FourCc(*b"ctts"), full_box(version, 0, body))
684}
685
686fn make_stss(track: &WriterTrack) -> Result<Vec<u8>, Error> {
687 let count = track.samples.iter().filter(|sample| sample.is_sync).count();
688 let mut body = Vec::new();
689 body.extend_from_slice(
690 &u32::try_from(count)
691 .map_err(|_| Error::WriterState("sync sample count exceeds u32"))?
692 .to_be_bytes(),
693 );
694 for (index, sample) in track.samples.iter().enumerate() {
695 if sample.is_sync {
696 let number = u32::try_from(index)
697 .map_err(|_| Error::WriterState("sample index exceeds u32"))?
698 .saturating_add(1);
699 body.extend_from_slice(&number.to_be_bytes());
700 }
701 }
702 make_box(FourCc(*b"stss"), full_box(0, 0, body))
703}
704
705fn make_stsc(track: &WriterTrack) -> Result<Vec<u8>, Error> {
706 let mut entries: Vec<(u32, u32)> = Vec::new();
707 for (index, chunk) in track.chunks.iter().enumerate() {
708 if entries
709 .last()
710 .is_some_and(|entry| entry.1 == chunk.sample_count)
711 {
712 continue;
713 }
714 entries.push((
715 u32::try_from(index)
716 .map_err(|_| Error::WriterState("chunk index exceeds u32"))?
717 .saturating_add(1),
718 chunk.sample_count,
719 ));
720 }
721 let mut body = Vec::new();
722 body.extend_from_slice(
723 &u32::try_from(entries.len())
724 .map_err(|_| Error::WriterState("chunk map count exceeds u32"))?
725 .to_be_bytes(),
726 );
727 for (first_chunk, samples_per_chunk) in entries {
728 body.extend_from_slice(&first_chunk.to_be_bytes());
729 body.extend_from_slice(&samples_per_chunk.to_be_bytes());
730 body.extend_from_slice(&1u32.to_be_bytes());
731 }
732 make_box(FourCc(*b"stsc"), full_box(0, 0, body))
733}
734
735fn make_stsz(track: &WriterTrack) -> Result<Vec<u8>, Error> {
736 let fixed = track
737 .samples
738 .first()
739 .map(|first| first.size)
740 .filter(|size| track.samples.iter().all(|sample| sample.size == *size))
741 .unwrap_or(0);
742 let mut body = Vec::new();
743 body.extend_from_slice(&fixed.to_be_bytes());
744 body.extend_from_slice(
745 &u32::try_from(track.samples.len())
746 .map_err(|_| Error::WriterState("sample count exceeds u32"))?
747 .to_be_bytes(),
748 );
749 if fixed == 0 {
750 for sample in &track.samples {
751 body.extend_from_slice(&sample.size.to_be_bytes());
752 }
753 }
754 make_box(FourCc(*b"stsz"), full_box(0, 0, body))
755}
756
757fn make_chunk_offsets(track: &WriterTrack) -> Result<Vec<u8>, Error> {
758 let use_co64 = track.force_co64
759 || track
760 .chunks
761 .iter()
762 .any(|chunk| chunk.offset > u64::from(u32::MAX));
763 let mut body = Vec::new();
764 body.extend_from_slice(
765 &u32::try_from(track.chunks.len())
766 .map_err(|_| Error::WriterState("chunk count exceeds u32"))?
767 .to_be_bytes(),
768 );
769 if use_co64 {
770 for chunk in &track.chunks {
771 body.extend_from_slice(&chunk.offset.to_be_bytes());
772 }
773 make_box(FourCc(*b"co64"), full_box(0, 0, body))
774 } else {
775 for chunk in &track.chunks {
776 body.extend_from_slice(
777 &u32::try_from(chunk.offset)
778 .unwrap_or(u32::MAX)
779 .to_be_bytes(),
780 );
781 }
782 make_box(FourCc(*b"stco"), full_box(0, 0, body))
783 }
784}