1use std::collections::BTreeMap;
16use std::error::Error;
17use std::fmt;
18use std::fs::File;
19use std::io::{self, BufWriter, Read, Seek, SeekFrom, Write};
20use std::path::{Path, PathBuf};
21use std::str::FromStr;
22
23#[cfg(feature = "async")]
24use tokio::fs::File as TokioFile;
25#[cfg(feature = "async")]
26use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
27
28use crate::FourCc;
29#[cfg(feature = "async")]
30use crate::async_io::{AsyncReadForward, AsyncReadSeek, AsyncWrite, AsyncWriteForward};
31use crate::codec::CodecError;
32use crate::header::HeaderError;
33use crate::queue::{OrderedWorkQueue, QueueWorkItem};
34use crate::writer::WriterError;
35
36mod coordination;
37mod demux;
38pub(crate) mod event;
39mod import;
40#[cfg_attr(docsrs, doc(cfg(feature = "mux")))]
42pub mod inspect;
43mod mp4;
44#[cfg_attr(docsrs, doc(cfg(feature = "mux")))]
46pub mod rewrite;
47#[cfg_attr(docsrs, doc(cfg(feature = "mux")))]
49pub mod sample_reader;
50
51use coordination::MuxCoordinationPlan;
52pub(crate) use coordination::{
53 MuxDurationBoundaryKind, TrackCoordinationDirective, build_capped_duration_chunk_sample_counts,
54 build_duration_chunk_sample_counts, build_duration_chunk_sample_counts_with_start_time,
55 build_fragmented_duration_chunk_sample_counts_with_start_time,
56 build_sync_aligned_fragmented_duration_chunk_sample_counts,
57 build_sync_aligned_segment_chunk_sample_counts,
58 rebalance_small_multi_audio_chunk_sample_counts,
59};
60pub(crate) use event::{MuxEventCursor, MuxEventGraph, MuxSampleEvent};
61pub use import::mux_fragmented_to_paths;
62#[cfg(feature = "async")]
63pub use import::mux_fragmented_to_paths_async;
64pub use import::mux_into_path;
65#[cfg(feature = "async")]
66pub use import::mux_into_path_async;
67pub use import::mux_to_path;
68#[cfg(feature = "async")]
69pub use import::mux_to_path_async;
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
72pub(crate) enum MuxRawCodec {
73 Av1,
75 Mpeg2v,
77 Mp4v,
79 H263,
81 H264,
83 H265,
85 Vvc,
87 Vp8,
89 Vp9,
91 Vp10,
93 Aac,
95 Latm,
97 Mp3,
99 Ac3,
101 Eac3,
103 Ac4,
105 Amr,
107 AmrWb,
109 Qcp,
111 Jpeg,
113 Png,
115 Bmp,
117 Prores,
119 Y4m,
121 J2k,
123 Pcm,
125 Dts,
127 Truehd,
129 Alac,
131 Flac,
133 Iamf,
135 MpegH,
137 Opus,
139 Vorbis,
141 Speex,
143 Theora,
145}
146
147impl MuxRawCodec {
148 pub const fn prefix(&self) -> &'static str {
149 match self {
150 Self::Av1 => "av1",
151 Self::Mpeg2v => "mpeg2v",
152 Self::Mp4v => "mp4v",
153 Self::H263 => "h263",
154 Self::H264 => "h264",
155 Self::H265 => "h265",
156 Self::Vvc => "vvc",
157 Self::Vp8 => "vp8",
158 Self::Vp9 => "vp9",
159 Self::Vp10 => "vp10",
160 Self::Aac => "aac",
161 Self::Latm => "latm",
162 Self::Mp3 => "mp3",
163 Self::Ac3 => "ac3",
164 Self::Eac3 => "ec3",
165 Self::Ac4 => "ac4",
166 Self::Amr => "amr",
167 Self::AmrWb => "amr-wb",
168 Self::Qcp => "qcp",
169 Self::Jpeg => "jpeg",
170 Self::Png => "png",
171 Self::Bmp => "bmp",
172 Self::Prores => "prores",
173 Self::Y4m => "y4m",
174 Self::J2k => "j2k",
175 Self::Pcm => "pcm",
176 Self::Dts => "dts",
177 Self::Truehd => "truehd",
178 Self::Alac => "alac",
179 Self::Flac => "flac",
180 Self::Iamf => "iamf",
181 Self::MpegH => "mhas",
182 Self::Opus => "opus",
183 Self::Vorbis => "vorbis",
184 Self::Speex => "speex",
185 Self::Theora => "theora",
186 }
187 }
188}
189
190#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
192pub enum MuxMp4TrackSelector {
193 Video,
195 Audio { occurrence: u32 },
200 Text { occurrence: u32 },
204 TrackId { track_id: u32 },
206}
207
208#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
221pub enum MuxRawVideoPixelFormat {
222 Yuv420p8,
224 Yvu420p8,
226 Yuv420p10,
228 Yuv422p8,
230 Yuv422p10,
232 Yuv444p8,
234 Yuv444p10,
236 Yuva420p8,
238 Yuvd420p8,
240 Yuva444p8,
242 Nv12p8,
244 Nv21p8,
246 Nv12p10,
248 Nv21p10,
250 Uyvy422p8,
252 Vyuy422p8,
254 Yuyv422p8,
256 Yvyu422p8,
258 Uyvy422p10,
260 Vyuy422p10,
262 Yuyv422p10,
264 Yvyu422p10,
266 Yuv444Packed8,
268 Vyu444Packed8,
270 Yuva444Packed8,
272 Uyva444Packed8,
274 Yuv444Packed10,
276 V210,
278 Grey8,
280 AlphaGrey8,
282 GreyAlpha8,
284 Rgb332,
286 Rgb444,
288 Rgb555,
290 Rgb565,
292 Rgb24,
294 Bgr24,
296 Rgbx32,
298 Bgrx32,
300 Xrgb32,
302 Xbgr32,
304 Argb32,
306 Rgba32,
308 Bgra32,
310 Abgr32,
312 Rgbd32,
314 Rgbds32,
316}
317
318impl MuxRawVideoPixelFormat {
319 pub const fn canonical_name(self) -> &'static str {
321 match self {
322 Self::Yuv420p8 => "yuv420",
323 Self::Yvu420p8 => "yvu420",
324 Self::Yuv420p10 => "yuv420_10",
325 Self::Yuv422p8 => "yuv422",
326 Self::Yuv422p10 => "yuv422_10",
327 Self::Yuv444p8 => "yuv444",
328 Self::Yuv444p10 => "yuv444_10",
329 Self::Yuva420p8 => "yuva",
330 Self::Yuvd420p8 => "yuvd",
331 Self::Yuva444p8 => "yuv444a",
332 Self::Nv12p8 => "nv12",
333 Self::Nv21p8 => "nv21",
334 Self::Nv12p10 => "nv12_10",
335 Self::Nv21p10 => "nv21_10",
336 Self::Uyvy422p8 => "uyvy",
337 Self::Vyuy422p8 => "vyuy",
338 Self::Yuyv422p8 => "yuyv",
339 Self::Yvyu422p8 => "yvyu",
340 Self::Uyvy422p10 => "uyvl",
341 Self::Vyuy422p10 => "vyul",
342 Self::Yuyv422p10 => "yuyl",
343 Self::Yvyu422p10 => "yvyl",
344 Self::Yuv444Packed8 => "yuv444p",
345 Self::Vyu444Packed8 => "v308",
346 Self::Yuva444Packed8 => "yuv444ap",
347 Self::Uyva444Packed8 => "v408",
348 Self::Yuv444Packed10 => "v410",
349 Self::V210 => "v210",
350 Self::Grey8 => "grey",
351 Self::AlphaGrey8 => "algr",
352 Self::GreyAlpha8 => "gral",
353 Self::Rgb332 => "rgb8",
354 Self::Rgb444 => "rgb4",
355 Self::Rgb555 => "rgb5",
356 Self::Rgb565 => "rgb6",
357 Self::Rgb24 => "rgb",
358 Self::Bgr24 => "bgr",
359 Self::Rgbx32 => "rgbx",
360 Self::Bgrx32 => "bgrx",
361 Self::Xrgb32 => "xrgb",
362 Self::Xbgr32 => "xbgr",
363 Self::Argb32 => "argb",
364 Self::Rgba32 => "rgba",
365 Self::Bgra32 => "bgra",
366 Self::Abgr32 => "abgr",
367 Self::Rgbd32 => "rgbd",
368 Self::Rgbds32 => "rgbds",
369 }
370 }
371
372 fn parse(spec: &str, value: &str) -> Result<Self, MuxError> {
373 match value {
374 "yuv420" | "yuv" => Ok(Self::Yuv420p8),
375 "yvu420" | "yvu" => Ok(Self::Yvu420p8),
376 "yuv420_10" | "yuvl" => Ok(Self::Yuv420p10),
377 "yuv422" | "yuv2" => Ok(Self::Yuv422p8),
378 "yuv422_10" | "yp2l" => Ok(Self::Yuv422p10),
379 "yuv444" | "yuv4" => Ok(Self::Yuv444p8),
380 "yuv444_10" | "yp4l" => Ok(Self::Yuv444p10),
381 "yuva" => Ok(Self::Yuva420p8),
382 "yuvd" => Ok(Self::Yuvd420p8),
383 "yuv444a" | "yp4a" => Ok(Self::Yuva444p8),
384 "nv12" => Ok(Self::Nv12p8),
385 "nv21" => Ok(Self::Nv21p8),
386 "nv12_10" | "nv1l" => Ok(Self::Nv12p10),
387 "nv21_10" | "nv2l" => Ok(Self::Nv21p10),
388 "uyvy" => Ok(Self::Uyvy422p8),
389 "vyuy" => Ok(Self::Vyuy422p8),
390 "yuyv" => Ok(Self::Yuyv422p8),
391 "yvyu" => Ok(Self::Yvyu422p8),
392 "uyvl" => Ok(Self::Uyvy422p10),
393 "vyul" => Ok(Self::Vyuy422p10),
394 "yuyl" => Ok(Self::Yuyv422p10),
395 "yvyl" => Ok(Self::Yvyu422p10),
396 "yuv444p" | "yv4p" => Ok(Self::Yuv444Packed8),
397 "v308" => Ok(Self::Vyu444Packed8),
398 "yuv444ap" | "y4ap" => Ok(Self::Yuva444Packed8),
399 "v408" => Ok(Self::Uyva444Packed8),
400 "v410" => Ok(Self::Yuv444Packed10),
401 "v210" => Ok(Self::V210),
402 "grey" => Ok(Self::Grey8),
403 "algr" => Ok(Self::AlphaGrey8),
404 "gral" => Ok(Self::GreyAlpha8),
405 "rgb8" => Ok(Self::Rgb332),
406 "rgb4" => Ok(Self::Rgb444),
407 "rgb5" => Ok(Self::Rgb555),
408 "rgb6" => Ok(Self::Rgb565),
409 "rgb" => Ok(Self::Rgb24),
410 "bgr" => Ok(Self::Bgr24),
411 "rgbx" => Ok(Self::Rgbx32),
412 "bgrx" => Ok(Self::Bgrx32),
413 "xrgb" => Ok(Self::Xrgb32),
414 "xbgr" => Ok(Self::Xbgr32),
415 "argb" => Ok(Self::Argb32),
416 "rgba" => Ok(Self::Rgba32),
417 "bgra" => Ok(Self::Bgra32),
418 "abgr" => Ok(Self::Abgr32),
419 "rgbd" => Ok(Self::Rgbd32),
420 "rgbds" => Ok(Self::Rgbds32),
421 _ => Err(MuxError::InvalidTrackSpec {
422 spec: spec.to_string(),
423 message: format!(
424 "unsupported rawvideo `spfmt={value}`; expected one of the rawvideo pixel formats supported by mp4forge"
425 ),
426 }),
427 }
428 }
429}
430
431#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
433pub struct MuxRawVideoParams {
434 width: u32,
435 height: u32,
436 pixel_format: MuxRawVideoPixelFormat,
437 fps_num: u32,
438 fps_den: u32,
439}
440
441impl MuxRawVideoParams {
442 pub fn new(
444 width: u32,
445 height: u32,
446 pixel_format: MuxRawVideoPixelFormat,
447 fps_num: u32,
448 fps_den: u32,
449 ) -> Result<Self, MuxError> {
450 if width == 0 || height == 0 {
451 return Err(MuxError::InvalidTrackSpec {
452 spec: "rawvideo".to_string(),
453 message: "rawvideo `size` must declare non-zero width and height".to_string(),
454 });
455 }
456 if fps_num == 0 || fps_den == 0 {
457 return Err(MuxError::InvalidTrackSpec {
458 spec: "rawvideo".to_string(),
459 message: "rawvideo `fps` must declare non-zero numerator and denominator"
460 .to_string(),
461 });
462 }
463 Ok(Self {
464 width,
465 height,
466 pixel_format,
467 fps_num,
468 fps_den,
469 })
470 }
471
472 pub const fn width(&self) -> u32 {
474 self.width
475 }
476
477 pub const fn height(&self) -> u32 {
479 self.height
480 }
481
482 pub const fn pixel_format(&self) -> MuxRawVideoPixelFormat {
484 self.pixel_format
485 }
486
487 pub const fn fps_num(&self) -> u32 {
489 self.fps_num
490 }
491
492 pub const fn fps_den(&self) -> u32 {
494 self.fps_den
495 }
496
497 fn format_suffix(&self) -> String {
498 format!(
499 "rawvideo:size={}x{},spfmt={},fps={}/{}",
500 self.width,
501 self.height,
502 self.pixel_format.canonical_name(),
503 self.fps_num,
504 self.fps_den
505 )
506 }
507}
508
509#[derive(Clone, Debug, PartialEq, Eq, Hash)]
510pub enum MuxTrackSpec {
511 Path {
513 path: PathBuf,
515 selector: Option<MuxMp4TrackSelector>,
517 },
518 RawVideo {
520 path: PathBuf,
522 params: MuxRawVideoParams,
524 },
525}
526
527impl MuxTrackSpec {
528 pub fn path(path: impl Into<PathBuf>) -> Self {
530 Self::Path {
531 path: path.into(),
532 selector: None,
533 }
534 }
535
536 pub fn selected(path: impl Into<PathBuf>, selector: MuxMp4TrackSelector) -> Self {
538 Self::Path {
539 path: path.into(),
540 selector: Some(selector),
541 }
542 }
543
544 pub fn mp4(path: impl Into<PathBuf>, selector: MuxMp4TrackSelector) -> Self {
546 Self::selected(path, selector)
547 }
548
549 pub fn raw_video(path: impl Into<PathBuf>, params: MuxRawVideoParams) -> Self {
551 Self::RawVideo {
552 path: path.into(),
553 params,
554 }
555 }
556
557 pub fn input_path(&self) -> &Path {
559 match self {
560 Self::Path { path, .. } => path.as_path(),
561 Self::RawVideo { path, .. } => path.as_path(),
562 }
563 }
564}
565
566impl FromStr for MuxTrackSpec {
567 type Err = MuxError;
568
569 fn from_str(value: &str) -> Result<Self, Self::Err> {
570 if value.is_empty() {
571 return Err(MuxError::InvalidTrackSpec {
572 spec: value.to_string(),
573 message: "missing input path".to_string(),
574 });
575 }
576
577 if let Some((path, selector_text)) = value.rsplit_once('#') {
578 if path.is_empty() {
579 return Err(MuxError::InvalidTrackSpec {
580 spec: value.to_string(),
581 message: "missing input path before `#`".to_string(),
582 });
583 }
584 if let Some(rawvideo_text) = selector_text.strip_prefix("rawvideo:") {
585 let params = parse_raw_video_params(value, rawvideo_text)?;
586 return Ok(Self::RawVideo {
587 path: PathBuf::from(path),
588 params,
589 });
590 }
591 let selector = parse_mp4_track_selector(value, selector_text)?;
592 return Ok(Self::Path {
593 path: PathBuf::from(path),
594 selector: Some(selector),
595 });
596 }
597
598 Ok(Self::path(value))
599 }
600}
601
602fn parse_mp4_track_selector(spec: &str, selector: &str) -> Result<MuxMp4TrackSelector, MuxError> {
603 if selector.is_empty() {
604 return Err(MuxError::InvalidTrackSpec {
605 spec: spec.to_string(),
606 message:
607 "expected one selector after `#`, such as `video`, `audio`, `text`, or `track:ID`"
608 .to_string(),
609 });
610 }
611 if selector.contains('=') || selector.contains(',') {
612 return Err(MuxError::InvalidTrackSpec {
613 spec: spec.to_string(),
614 message: "public mux track specs only allow selector suffixes such as `#video`, `#audio`, `#text`, or `#track:ID`; raw `#name=value` parameters are no longer accepted".to_string(),
615 });
616 }
617 if selector == "video" {
618 return Ok(MuxMp4TrackSelector::Video);
619 }
620 if selector == "audio" {
621 return Ok(MuxMp4TrackSelector::Audio { occurrence: 1 });
622 }
623 if selector == "text" {
624 return Ok(MuxMp4TrackSelector::Text { occurrence: 1 });
625 }
626 if let Some(index) = selector.strip_prefix("audio:") {
627 let occurrence = index
628 .parse::<u32>()
629 .map_err(|_| MuxError::InvalidTrackSpec {
630 spec: spec.to_string(),
631 message: format!("invalid audio occurrence `{index}`"),
632 })?;
633 if occurrence == 0 {
634 return Err(MuxError::InvalidTrackSpec {
635 spec: spec.to_string(),
636 message: "audio occurrences are one-based; `audio:0` is invalid".to_string(),
637 });
638 }
639 return Ok(MuxMp4TrackSelector::Audio { occurrence });
640 }
641 if let Some(index) = selector.strip_prefix("text:") {
642 let occurrence = index
643 .parse::<u32>()
644 .map_err(|_| MuxError::InvalidTrackSpec {
645 spec: spec.to_string(),
646 message: format!("invalid text occurrence `{index}`"),
647 })?;
648 if occurrence == 0 {
649 return Err(MuxError::InvalidTrackSpec {
650 spec: spec.to_string(),
651 message: "text occurrences are one-based; `text:0` is invalid".to_string(),
652 });
653 }
654 return Ok(MuxMp4TrackSelector::Text { occurrence });
655 }
656 if let Some(track_id) = selector.strip_prefix("track:") {
657 let track_id = track_id
658 .parse::<u32>()
659 .map_err(|_| MuxError::InvalidTrackSpec {
660 spec: spec.to_string(),
661 message: format!("invalid track id `{track_id}`"),
662 })?;
663 if track_id == 0 {
664 return Err(MuxError::InvalidTrackSpec {
665 spec: spec.to_string(),
666 message: "track ids are one-based; `track:0` is invalid".to_string(),
667 });
668 }
669 return Ok(MuxMp4TrackSelector::TrackId { track_id });
670 }
671
672 Err(MuxError::InvalidTrackSpec {
673 spec: spec.to_string(),
674 message: format!(
675 "unsupported MP4 track selector `{selector}`; expected `video`, `audio`, `audio:N`, `text`, `text:N`, or `track:ID`"
676 ),
677 })
678}
679
680fn parse_raw_video_params(spec: &str, rawvideo_text: &str) -> Result<MuxRawVideoParams, MuxError> {
681 if rawvideo_text.is_empty() {
682 return Err(MuxError::InvalidTrackSpec {
683 spec: spec.to_string(),
684 message:
685 "expected rawvideo parameters after `#rawvideo:`, such as `size=1920x1080,spfmt=yuv420,fps=25/1`"
686 .to_string(),
687 });
688 }
689
690 let mut width = None::<u32>;
691 let mut height = None::<u32>;
692 let mut pixel_format = None::<MuxRawVideoPixelFormat>;
693 let mut fps_num = None::<u32>;
694 let mut fps_den = None::<u32>;
695
696 for token in rawvideo_text.split(',') {
697 let (name, value) = token.split_once('=').ok_or_else(|| MuxError::InvalidTrackSpec {
698 spec: spec.to_string(),
699 message: format!(
700 "invalid rawvideo parameter `{token}`; expected `name=value` pairs separated by commas"
701 ),
702 })?;
703 match name {
704 "size" => {
705 let (parsed_width, parsed_height) =
706 value
707 .split_once('x')
708 .ok_or_else(|| MuxError::InvalidTrackSpec {
709 spec: spec.to_string(),
710 message: "rawvideo `size` must use `WIDTHxHEIGHT`".to_string(),
711 })?;
712 width =
713 Some(
714 parsed_width
715 .parse::<u32>()
716 .map_err(|_| MuxError::InvalidTrackSpec {
717 spec: spec.to_string(),
718 message: format!("invalid rawvideo width `{parsed_width}`"),
719 })?,
720 );
721 height =
722 Some(
723 parsed_height
724 .parse::<u32>()
725 .map_err(|_| MuxError::InvalidTrackSpec {
726 spec: spec.to_string(),
727 message: format!("invalid rawvideo height `{parsed_height}`"),
728 })?,
729 );
730 }
731 "spfmt" => {
732 pixel_format = Some(MuxRawVideoPixelFormat::parse(spec, value)?);
733 }
734 "fps" => {
735 let (parsed_num, parsed_den) =
736 value
737 .split_once('/')
738 .ok_or_else(|| MuxError::InvalidTrackSpec {
739 spec: spec.to_string(),
740 message: "rawvideo `fps` must use `NUM/DEN`".to_string(),
741 })?;
742 fps_num =
743 Some(
744 parsed_num
745 .parse::<u32>()
746 .map_err(|_| MuxError::InvalidTrackSpec {
747 spec: spec.to_string(),
748 message: format!(
749 "invalid rawvideo frame-rate numerator `{parsed_num}`"
750 ),
751 })?,
752 );
753 fps_den =
754 Some(
755 parsed_den
756 .parse::<u32>()
757 .map_err(|_| MuxError::InvalidTrackSpec {
758 spec: spec.to_string(),
759 message: format!(
760 "invalid rawvideo frame-rate denominator `{parsed_den}`"
761 ),
762 })?,
763 );
764 }
765 _ => {
766 return Err(MuxError::InvalidTrackSpec {
767 spec: spec.to_string(),
768 message: format!(
769 "unsupported rawvideo parameter `{name}`; expected `size`, `spfmt`, or `fps`"
770 ),
771 });
772 }
773 }
774 }
775
776 let width = width.ok_or_else(|| MuxError::InvalidTrackSpec {
777 spec: spec.to_string(),
778 message: "rawvideo track specs must declare `size=WIDTHxHEIGHT`".to_string(),
779 })?;
780 let height = height.ok_or_else(|| MuxError::InvalidTrackSpec {
781 spec: spec.to_string(),
782 message: "rawvideo track specs must declare `size=WIDTHxHEIGHT`".to_string(),
783 })?;
784 let pixel_format = pixel_format.ok_or_else(|| MuxError::InvalidTrackSpec {
785 spec: spec.to_string(),
786 message: "rawvideo track specs must declare `spfmt=PIXFMT`".to_string(),
787 })?;
788 let fps_num = fps_num.ok_or_else(|| MuxError::InvalidTrackSpec {
789 spec: spec.to_string(),
790 message: "rawvideo track specs must declare `fps=NUM/DEN`".to_string(),
791 })?;
792 let fps_den = fps_den.ok_or_else(|| MuxError::InvalidTrackSpec {
793 spec: spec.to_string(),
794 message: "rawvideo track specs must declare `fps=NUM/DEN`".to_string(),
795 })?;
796 MuxRawVideoParams::new(width, height, pixel_format, fps_num, fps_den).map_err(|error| {
797 match error {
798 MuxError::InvalidTrackSpec { message, .. } => MuxError::InvalidTrackSpec {
799 spec: spec.to_string(),
800 message,
801 },
802 other => other,
803 }
804 })
805}
806
807#[derive(Clone, Copy, Debug, PartialEq)]
813pub enum MuxDurationMode {
814 Segment { seconds: f64 },
816 Fragment { seconds: f64 },
818}
819
820impl MuxDurationMode {
821 pub const fn label(&self) -> &'static str {
823 match self {
824 Self::Segment { .. } => "segment_duration",
825 Self::Fragment { .. } => "fragment_duration",
826 }
827 }
828
829 pub const fn seconds(&self) -> f64 {
831 match self {
832 Self::Segment { seconds } | Self::Fragment { seconds } => *seconds,
833 }
834 }
835}
836
837#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
843pub enum MuxOutputLayout {
844 #[default]
846 Flat,
847 Fragmented,
849}
850
851impl MuxOutputLayout {
852 pub const fn label(&self) -> &'static str {
854 match self {
855 Self::Flat => "flat",
856 Self::Fragmented => "fragmented",
857 }
858 }
859}
860
861#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
868pub enum MuxDestinationMode {
869 #[default]
871 CreateNew,
872 UpdateOrCreateDestination,
874}
875
876impl MuxDestinationMode {
877 pub const fn label(&self) -> &'static str {
879 match self {
880 Self::CreateNew => "create-new",
881 Self::UpdateOrCreateDestination => "update-or-create-destination",
882 }
883 }
884}
885
886#[derive(Clone, Debug, PartialEq, Eq)]
888pub struct MuxFragmentEventMessage {
889 fragment_index: u32,
890 version: u8,
891 scheme_id_uri: String,
892 value: String,
893 timescale: u32,
894 presentation_time_delta: u32,
895 presentation_time: u64,
896 event_duration: u32,
897 id: u32,
898 message_data: Vec<u8>,
899}
900
901impl MuxFragmentEventMessage {
902 #[allow(clippy::too_many_arguments)]
904 pub fn new_v0<S, V, M>(
905 fragment_index: u32,
906 scheme_id_uri: S,
907 value: V,
908 timescale: u32,
909 presentation_time_delta: u32,
910 event_duration: u32,
911 id: u32,
912 message_data: M,
913 ) -> Self
914 where
915 S: Into<String>,
916 V: Into<String>,
917 M: Into<Vec<u8>>,
918 {
919 Self {
920 fragment_index,
921 version: 0,
922 scheme_id_uri: scheme_id_uri.into(),
923 value: value.into(),
924 timescale,
925 presentation_time_delta,
926 presentation_time: 0,
927 event_duration,
928 id,
929 message_data: message_data.into(),
930 }
931 }
932
933 #[allow(clippy::too_many_arguments)]
935 pub fn new_v1<S, V, M>(
936 fragment_index: u32,
937 scheme_id_uri: S,
938 value: V,
939 timescale: u32,
940 presentation_time: u64,
941 event_duration: u32,
942 id: u32,
943 message_data: M,
944 ) -> Self
945 where
946 S: Into<String>,
947 V: Into<String>,
948 M: Into<Vec<u8>>,
949 {
950 Self {
951 fragment_index,
952 version: 1,
953 scheme_id_uri: scheme_id_uri.into(),
954 value: value.into(),
955 timescale,
956 presentation_time_delta: 0,
957 presentation_time,
958 event_duration,
959 id,
960 message_data: message_data.into(),
961 }
962 }
963
964 pub const fn fragment_index(&self) -> u32 {
966 self.fragment_index
967 }
968
969 pub const fn version(&self) -> u8 {
971 self.version
972 }
973
974 pub fn scheme_id_uri(&self) -> &str {
976 &self.scheme_id_uri
977 }
978
979 pub fn value(&self) -> &str {
981 &self.value
982 }
983
984 pub const fn timescale(&self) -> u32 {
986 self.timescale
987 }
988
989 pub const fn presentation_time_delta(&self) -> u32 {
991 self.presentation_time_delta
992 }
993
994 pub const fn presentation_time(&self) -> u64 {
996 self.presentation_time
997 }
998
999 pub const fn event_duration(&self) -> u32 {
1001 self.event_duration
1002 }
1003
1004 pub const fn id(&self) -> u32 {
1006 self.id
1007 }
1008
1009 pub fn message_data(&self) -> &[u8] {
1011 &self.message_data
1012 }
1013}
1014
1015#[derive(Clone, Debug, PartialEq, Eq)]
1017pub struct MuxProducerReferenceTime {
1018 fragment_index: u32,
1019 version: u8,
1020 flags: u32,
1021 reference_track_id: u32,
1022 ntp_timestamp: u64,
1023 media_time: u64,
1024}
1025
1026impl MuxProducerReferenceTime {
1027 pub const fn new(
1029 fragment_index: u32,
1030 reference_track_id: u32,
1031 ntp_timestamp: u64,
1032 media_time: u64,
1033 ) -> Self {
1034 Self {
1035 fragment_index,
1036 version: 1,
1037 flags: 0,
1038 reference_track_id,
1039 ntp_timestamp,
1040 media_time,
1041 }
1042 }
1043
1044 pub const fn with_version(mut self, version: u8) -> Self {
1046 self.version = version;
1047 self
1048 }
1049
1050 pub const fn with_flags(mut self, flags: u32) -> Self {
1052 self.flags = flags;
1053 self
1054 }
1055
1056 pub const fn fragment_index(&self) -> u32 {
1058 self.fragment_index
1059 }
1060
1061 pub const fn version(&self) -> u8 {
1063 self.version
1064 }
1065
1066 pub const fn flags(&self) -> u32 {
1068 self.flags
1069 }
1070
1071 pub const fn reference_track_id(&self) -> u32 {
1073 self.reference_track_id
1074 }
1075
1076 pub const fn ntp_timestamp(&self) -> u64 {
1078 self.ntp_timestamp
1079 }
1080
1081 pub const fn media_time(&self) -> u64 {
1083 self.media_time
1084 }
1085}
1086
1087#[derive(Clone, Debug, Default, PartialEq)]
1093pub struct MuxRequest {
1094 tracks: Vec<MuxTrackSpec>,
1095 output_layout: MuxOutputLayout,
1096 destination_mode: MuxDestinationMode,
1097 duration_mode: Option<MuxDurationMode>,
1098 preserve_flat_authority_layout: bool,
1099 fragment_event_messages: Vec<MuxFragmentEventMessage>,
1100 producer_reference_times: Vec<MuxProducerReferenceTime>,
1101}
1102
1103impl MuxRequest {
1104 pub fn new(tracks: Vec<MuxTrackSpec>) -> Self {
1106 Self {
1107 tracks,
1108 output_layout: MuxOutputLayout::Flat,
1109 destination_mode: MuxDestinationMode::CreateNew,
1110 duration_mode: None,
1111 preserve_flat_authority_layout: false,
1112 fragment_event_messages: Vec::new(),
1113 producer_reference_times: Vec::new(),
1114 }
1115 }
1116
1117 pub fn tracks(&self) -> &[MuxTrackSpec] {
1119 &self.tracks
1120 }
1121
1122 pub const fn output_layout(&self) -> MuxOutputLayout {
1124 self.output_layout
1125 }
1126
1127 pub const fn destination_mode(&self) -> MuxDestinationMode {
1129 self.destination_mode
1130 }
1131
1132 pub const fn duration_mode(&self) -> Option<MuxDurationMode> {
1134 self.duration_mode
1135 }
1136
1137 pub(crate) const fn preserve_flat_authority_layout(&self) -> bool {
1138 self.preserve_flat_authority_layout
1139 }
1140
1141 pub fn fragment_event_messages(&self) -> &[MuxFragmentEventMessage] {
1143 &self.fragment_event_messages
1144 }
1145
1146 pub fn producer_reference_times(&self) -> &[MuxProducerReferenceTime] {
1148 &self.producer_reference_times
1149 }
1150
1151 pub const fn with_output_layout(mut self, output_layout: MuxOutputLayout) -> Self {
1153 self.output_layout = output_layout;
1154 self
1155 }
1156
1157 pub const fn with_destination_mode(mut self, destination_mode: MuxDestinationMode) -> Self {
1159 self.destination_mode = destination_mode;
1160 self
1161 }
1162
1163 pub const fn with_duration_mode(mut self, duration_mode: MuxDurationMode) -> Self {
1165 self.duration_mode = Some(duration_mode);
1166 self
1167 }
1168
1169 pub(crate) const fn with_preserve_flat_authority_layout(
1170 mut self,
1171 preserve_flat_authority_layout: bool,
1172 ) -> Self {
1173 self.preserve_flat_authority_layout = preserve_flat_authority_layout;
1174 self
1175 }
1176
1177 pub fn with_fragment_event_message(mut self, message: MuxFragmentEventMessage) -> Self {
1179 self.fragment_event_messages.push(message);
1180 self
1181 }
1182
1183 pub fn with_producer_reference_time(mut self, entry: MuxProducerReferenceTime) -> Self {
1185 self.producer_reference_times.push(entry);
1186 self
1187 }
1188}
1189
1190#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
1192pub enum MuxInterleavePolicy {
1193 #[default]
1196 DecodeTime,
1197 ChunkOrdinalThenSource,
1201}
1202
1203#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1209pub struct MuxStagedMediaItem {
1210 source_index: usize,
1211 track_id: u32,
1212 decode_time: u64,
1213 composition_time_offset: i32,
1214 duration: u32,
1215 data_offset: u64,
1216 data_size: u32,
1217 is_sync_sample: bool,
1218 sample_description_index: u32,
1219}
1220
1221impl MuxStagedMediaItem {
1222 pub const fn new(
1224 source_index: usize,
1225 track_id: u32,
1226 decode_time: u64,
1227 duration: u32,
1228 data_offset: u64,
1229 data_size: u32,
1230 ) -> Self {
1231 Self {
1232 source_index,
1233 track_id,
1234 decode_time,
1235 composition_time_offset: 0,
1236 duration,
1237 data_offset,
1238 data_size,
1239 is_sync_sample: false,
1240 sample_description_index: 1,
1241 }
1242 }
1243
1244 pub const fn source_index(&self) -> usize {
1246 self.source_index
1247 }
1248
1249 pub const fn track_id(&self) -> u32 {
1251 self.track_id
1252 }
1253
1254 pub const fn decode_time(&self) -> u64 {
1256 self.decode_time
1257 }
1258
1259 pub const fn composition_time_offset(&self) -> i32 {
1261 self.composition_time_offset
1262 }
1263
1264 pub const fn duration(&self) -> u32 {
1266 self.duration
1267 }
1268
1269 pub const fn data_offset(&self) -> u64 {
1271 self.data_offset
1272 }
1273
1274 pub const fn data_size(&self) -> u32 {
1276 self.data_size
1277 }
1278
1279 pub const fn is_sync_sample(&self) -> bool {
1281 self.is_sync_sample
1282 }
1283
1284 pub const fn sample_description_index(&self) -> u32 {
1286 self.sample_description_index
1287 }
1288
1289 pub const fn with_composition_time_offset(mut self, composition_time_offset: i32) -> Self {
1291 self.composition_time_offset = composition_time_offset;
1292 self
1293 }
1294
1295 pub const fn with_sync_sample(mut self, is_sync_sample: bool) -> Self {
1297 self.is_sync_sample = is_sync_sample;
1298 self
1299 }
1300
1301 pub const fn with_sample_description_index(mut self, sample_description_index: u32) -> Self {
1303 self.sample_description_index = sample_description_index;
1304 self
1305 }
1306}
1307
1308#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1314pub struct MuxPlannedMediaItem {
1315 staged: MuxStagedMediaItem,
1316 output_offset: u64,
1317}
1318
1319impl MuxPlannedMediaItem {
1320 pub const fn staged(&self) -> &MuxStagedMediaItem {
1322 &self.staged
1323 }
1324
1325 pub const fn output_offset(&self) -> u64 {
1327 self.output_offset
1328 }
1329
1330 pub const fn output_end_offset(&self) -> u64 {
1332 self.output_offset + self.staged.data_size as u64
1333 }
1334
1335 pub const fn decode_end_time(&self) -> u64 {
1337 self.staged.decode_time + self.staged.duration as u64
1338 }
1339}
1340
1341#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1343pub struct MuxTrackPlan {
1344 track_id: u32,
1345 item_count: u32,
1346 first_decode_time: u64,
1347 end_decode_time: u64,
1348}
1349
1350impl MuxTrackPlan {
1351 pub const fn track_id(&self) -> u32 {
1353 self.track_id
1354 }
1355
1356 pub const fn item_count(&self) -> u32 {
1358 self.item_count
1359 }
1360
1361 pub const fn first_decode_time(&self) -> u64 {
1363 self.first_decode_time
1364 }
1365
1366 pub const fn end_decode_time(&self) -> u64 {
1368 self.end_decode_time
1369 }
1370}
1371
1372#[derive(Clone, Debug, PartialEq, Eq)]
1379pub struct MuxPlan {
1380 interleave_policy: MuxInterleavePolicy,
1381 planned_items: Vec<MuxPlannedMediaItem>,
1382 track_plans: Vec<MuxTrackPlan>,
1383 total_payload_size: u64,
1384 coordination: MuxCoordinationPlan,
1385 event_graph: MuxEventGraph,
1386}
1387
1388impl MuxPlan {
1389 pub const fn interleave_policy(&self) -> MuxInterleavePolicy {
1391 self.interleave_policy
1392 }
1393
1394 pub fn planned_items(&self) -> &[MuxPlannedMediaItem] {
1400 &self.planned_items
1401 }
1402
1403 pub fn track_plans(&self) -> &[MuxTrackPlan] {
1405 &self.track_plans
1406 }
1407
1408 pub const fn total_payload_size(&self) -> u64 {
1410 self.total_payload_size
1411 }
1412
1413 pub(crate) fn chunk_sample_counts(&self, track_id: u32) -> Result<&[u32], MuxError> {
1414 self.coordination.chunk_sample_counts(track_id)
1415 }
1416
1417 pub(crate) fn event_graph(&self) -> &MuxEventGraph {
1418 &self.event_graph
1419 }
1420}
1421
1422#[derive(Clone, Debug, PartialEq, Eq)]
1424pub struct MuxFileConfig {
1425 movie_timescale: u32,
1426 major_brand: FourCc,
1427 minor_version: u32,
1428 compatible_brands: Vec<FourCc>,
1429 auto_flat_profile: bool,
1430 allow_audio_only_iods: bool,
1431 keep_flat_free_box: bool,
1432 keep_flat_authority_brands: bool,
1433 preserve_auto_flat_movie_timescale: bool,
1434 emit_default_flat_tool_metadata: bool,
1435 flat_source_encoding_metadata: Option<String>,
1436 flat_source_encoder_metadata: Option<String>,
1437 flat_source_movie_creation_time: Option<u64>,
1438 flat_source_movie_modification_time: Option<u64>,
1439 preserved_flat_prefix_bytes: Vec<u8>,
1440 preserved_flat_iods_bytes: Option<Vec<u8>>,
1441 preserved_flat_udta_bytes: Option<Vec<u8>>,
1442 fragment_event_messages: Vec<MuxFragmentEventMessage>,
1443 producer_reference_times: Vec<MuxProducerReferenceTime>,
1444}
1445
1446impl MuxFileConfig {
1447 pub fn new(movie_timescale: u32) -> Self {
1451 Self {
1452 movie_timescale,
1453 major_brand: FourCc::from_bytes(*b"isom"),
1454 minor_version: 0,
1455 compatible_brands: vec![FourCc::from_bytes(*b"isom"), FourCc::from_bytes(*b"mp42")],
1456 auto_flat_profile: false,
1457 allow_audio_only_iods: false,
1458 keep_flat_free_box: false,
1459 keep_flat_authority_brands: false,
1460 preserve_auto_flat_movie_timescale: false,
1461 emit_default_flat_tool_metadata: true,
1462 flat_source_encoding_metadata: None,
1463 flat_source_encoder_metadata: None,
1464 flat_source_movie_creation_time: None,
1465 flat_source_movie_modification_time: None,
1466 preserved_flat_prefix_bytes: Vec::new(),
1467 preserved_flat_iods_bytes: None,
1468 preserved_flat_udta_bytes: None,
1469 fragment_event_messages: Vec::new(),
1470 producer_reference_times: Vec::new(),
1471 }
1472 }
1473
1474 pub const fn movie_timescale(&self) -> u32 {
1476 self.movie_timescale
1477 }
1478
1479 pub const fn major_brand(&self) -> FourCc {
1481 self.major_brand
1482 }
1483
1484 pub const fn minor_version(&self) -> u32 {
1486 self.minor_version
1487 }
1488
1489 pub fn compatible_brands(&self) -> &[FourCc] {
1491 &self.compatible_brands
1492 }
1493
1494 pub const fn with_major_brand(mut self, major_brand: FourCc) -> Self {
1496 self.major_brand = major_brand;
1497 self
1498 }
1499
1500 pub const fn with_minor_version(mut self, minor_version: u32) -> Self {
1502 self.minor_version = minor_version;
1503 self
1504 }
1505
1506 pub fn add_compatible_brand(&mut self, brand: FourCc) {
1508 if !self.compatible_brands.contains(&brand) {
1509 self.compatible_brands.push(brand);
1510 }
1511 }
1512
1513 pub fn with_compatible_brand(mut self, brand: FourCc) -> Self {
1515 self.add_compatible_brand(brand);
1516 self
1517 }
1518
1519 pub(crate) fn with_compatible_brands(mut self, compatible_brands: Vec<FourCc>) -> Self {
1520 self.compatible_brands = compatible_brands;
1521 self
1522 }
1523
1524 pub(crate) const fn auto_flat_profile(&self) -> bool {
1525 self.auto_flat_profile
1526 }
1527
1528 pub(crate) const fn with_auto_flat_profile(mut self, auto_flat_profile: bool) -> Self {
1529 self.auto_flat_profile = auto_flat_profile;
1530 self
1531 }
1532
1533 pub(crate) const fn allow_audio_only_iods(&self) -> bool {
1534 self.allow_audio_only_iods
1535 }
1536
1537 pub(crate) const fn with_allow_audio_only_iods(mut self, allow_audio_only_iods: bool) -> Self {
1538 self.allow_audio_only_iods = allow_audio_only_iods;
1539 self
1540 }
1541
1542 pub(crate) const fn keep_flat_free_box(&self) -> bool {
1543 self.keep_flat_free_box
1544 }
1545
1546 pub(crate) const fn with_keep_flat_free_box(mut self, keep_flat_free_box: bool) -> Self {
1547 self.keep_flat_free_box = keep_flat_free_box;
1548 self
1549 }
1550
1551 pub(crate) const fn keep_flat_authority_brands(&self) -> bool {
1552 self.keep_flat_authority_brands
1553 }
1554
1555 pub(crate) const fn with_keep_flat_authority_brands(
1556 mut self,
1557 keep_flat_authority_brands: bool,
1558 ) -> Self {
1559 self.keep_flat_authority_brands = keep_flat_authority_brands;
1560 self
1561 }
1562
1563 pub(crate) const fn preserve_auto_flat_movie_timescale(&self) -> bool {
1564 self.preserve_auto_flat_movie_timescale
1565 }
1566
1567 pub(crate) const fn with_preserve_auto_flat_movie_timescale(
1568 mut self,
1569 preserve_auto_flat_movie_timescale: bool,
1570 ) -> Self {
1571 self.preserve_auto_flat_movie_timescale = preserve_auto_flat_movie_timescale;
1572 self
1573 }
1574
1575 pub(crate) const fn emit_default_flat_tool_metadata(&self) -> bool {
1576 self.emit_default_flat_tool_metadata
1577 }
1578
1579 pub(crate) const fn with_emit_default_flat_tool_metadata(
1580 mut self,
1581 emit_default_flat_tool_metadata: bool,
1582 ) -> Self {
1583 self.emit_default_flat_tool_metadata = emit_default_flat_tool_metadata;
1584 self
1585 }
1586
1587 pub(crate) fn flat_source_encoding_metadata(&self) -> Option<&str> {
1588 self.flat_source_encoding_metadata.as_deref()
1589 }
1590
1591 pub(crate) fn with_flat_source_encoding_metadata(
1592 mut self,
1593 flat_source_encoding_metadata: Option<String>,
1594 ) -> Self {
1595 self.flat_source_encoding_metadata = flat_source_encoding_metadata;
1596 self
1597 }
1598
1599 pub(crate) fn flat_source_encoder_metadata(&self) -> Option<&str> {
1600 self.flat_source_encoder_metadata.as_deref()
1601 }
1602
1603 pub(crate) fn with_flat_source_encoder_metadata(
1604 mut self,
1605 flat_source_encoder_metadata: Option<String>,
1606 ) -> Self {
1607 self.flat_source_encoder_metadata = flat_source_encoder_metadata;
1608 self
1609 }
1610
1611 pub(crate) const fn flat_source_movie_creation_time(&self) -> Option<u64> {
1612 self.flat_source_movie_creation_time
1613 }
1614
1615 pub(crate) const fn with_flat_source_movie_creation_time(
1616 mut self,
1617 flat_source_movie_creation_time: Option<u64>,
1618 ) -> Self {
1619 self.flat_source_movie_creation_time = flat_source_movie_creation_time;
1620 self
1621 }
1622
1623 pub(crate) const fn flat_source_movie_modification_time(&self) -> Option<u64> {
1624 self.flat_source_movie_modification_time
1625 }
1626
1627 pub(crate) const fn with_flat_source_movie_modification_time(
1628 mut self,
1629 flat_source_movie_modification_time: Option<u64>,
1630 ) -> Self {
1631 self.flat_source_movie_modification_time = flat_source_movie_modification_time;
1632 self
1633 }
1634
1635 pub(crate) fn preserved_flat_prefix_bytes(&self) -> &[u8] {
1636 &self.preserved_flat_prefix_bytes
1637 }
1638
1639 pub(crate) fn with_preserved_flat_prefix_bytes(
1640 mut self,
1641 preserved_flat_prefix_bytes: Vec<u8>,
1642 ) -> Self {
1643 self.preserved_flat_prefix_bytes = preserved_flat_prefix_bytes;
1644 self
1645 }
1646
1647 pub(crate) fn preserved_flat_iods_bytes(&self) -> Option<&[u8]> {
1648 self.preserved_flat_iods_bytes.as_deref()
1649 }
1650
1651 pub(crate) fn with_preserved_flat_iods_bytes(
1652 mut self,
1653 preserved_flat_iods_bytes: Option<Vec<u8>>,
1654 ) -> Self {
1655 self.preserved_flat_iods_bytes = preserved_flat_iods_bytes;
1656 self
1657 }
1658
1659 pub(crate) fn preserved_flat_udta_bytes(&self) -> Option<&[u8]> {
1660 self.preserved_flat_udta_bytes.as_deref()
1661 }
1662
1663 pub(crate) fn with_preserved_flat_udta_bytes(
1664 mut self,
1665 preserved_flat_udta_bytes: Option<Vec<u8>>,
1666 ) -> Self {
1667 self.preserved_flat_udta_bytes = preserved_flat_udta_bytes;
1668 self
1669 }
1670
1671 pub(crate) fn fragment_event_messages(&self) -> &[MuxFragmentEventMessage] {
1672 &self.fragment_event_messages
1673 }
1674
1675 pub(crate) fn with_fragment_event_messages(
1676 mut self,
1677 fragment_event_messages: Vec<MuxFragmentEventMessage>,
1678 ) -> Self {
1679 self.fragment_event_messages = fragment_event_messages;
1680 self
1681 }
1682
1683 pub(crate) fn producer_reference_times(&self) -> &[MuxProducerReferenceTime] {
1684 &self.producer_reference_times
1685 }
1686
1687 pub(crate) fn with_producer_reference_times(
1688 mut self,
1689 producer_reference_times: Vec<MuxProducerReferenceTime>,
1690 ) -> Self {
1691 self.producer_reference_times = producer_reference_times;
1692 self
1693 }
1694}
1695
1696#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1698pub enum MuxTrackKind {
1699 Audio,
1701 Video,
1703 Text,
1705 Subtitle,
1707}
1708
1709impl MuxTrackKind {
1710 pub const fn is_audio(self) -> bool {
1712 matches!(self, Self::Audio)
1713 }
1714
1715 pub const fn is_video(self) -> bool {
1717 matches!(self, Self::Video)
1718 }
1719
1720 pub const fn is_textual(self) -> bool {
1722 matches!(self, Self::Text | Self::Subtitle)
1723 }
1724}
1725
1726const DEFAULT_TKHD_FLAGS: u32 = 0x0000_0001 | 0x0000_0002 | 0x0000_0004;
1727const DEFAULT_AUDIO_ALTERNATE_GROUP: i16 = 1;
1728const DEFAULT_SUBTITLE_ALTERNATE_GROUP: i16 = 0;
1729const DEFAULT_TKHD_MATRIX: [i32; 9] = [0x0001_0000, 0, 0, 0, 0x0001_0000, 0, 0, 0, 0x4000_0000];
1730
1731const fn default_alternate_group_for_kind(kind: MuxTrackKind) -> i16 {
1732 match kind {
1733 MuxTrackKind::Audio => DEFAULT_AUDIO_ALTERNATE_GROUP,
1734 MuxTrackKind::Subtitle => DEFAULT_SUBTITLE_ALTERNATE_GROUP,
1735 MuxTrackKind::Video | MuxTrackKind::Text => 0,
1736 }
1737}
1738
1739#[derive(Clone, Debug, PartialEq, Eq)]
1744pub struct MuxTrackConfig {
1745 track_id: u32,
1746 kind: MuxTrackKind,
1747 timescale: u32,
1748 language: [u8; 3],
1749 handler_name: String,
1750 track_width: u16,
1751 track_height: u16,
1752 track_width_fixed_16_16: Option<u32>,
1753 track_height_fixed_16_16: Option<u32>,
1754 tkhd_flags: u32,
1755 alternate_group: i16,
1756 volume: i16,
1757 matrix: [i32; 9],
1758 edit_media_time: Option<u64>,
1759 sample_roll_distance: Option<i16>,
1760 emit_roll_sbgp: bool,
1761 sample_entry_box: Vec<u8>,
1762 sample_entry_boxes: Vec<Vec<u8>>,
1763 sync_sample_table_mode: SyncSampleTableMode,
1764 stts_run_encoding_mode: SttsRunEncodingMode,
1765 stsc_run_encoding_mode: StscRunEncodingMode,
1766 flat_timing_override: Option<FlatTimingOverride>,
1767 flat_audio_profile_level_indication: Option<u8>,
1768 fragmented_decode_time_offset: Option<u64>,
1769 fragmented_reference_group_fragment_counts: Option<Vec<u32>>,
1770 flat_source_track_creation_time: Option<u64>,
1771 flat_source_track_modification_time: Option<u64>,
1772 flat_source_media_creation_time: Option<u64>,
1773 flat_source_media_modification_time: Option<u64>,
1774 omit_flat_iods: bool,
1775 flat_stsc_override: Option<crate::boxes::iso14496_12::Stsc>,
1776 preserved_flat_stbl_boxes: Vec<Vec<u8>>,
1777 preserved_flat_trak_boxes: Vec<Vec<u8>>,
1778}
1779
1780#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1781pub(crate) enum SyncSampleTableMode {
1782 Auto,
1783 ForceEmpty,
1784 ForceFirstOnly,
1785}
1786
1787#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1788pub(crate) enum StscRunEncodingMode {
1789 CollapseIdentical,
1790 PreserveTerminalBoundary,
1791}
1792
1793#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1794pub(crate) enum SttsRunEncodingMode {
1795 CollapseIdentical,
1796 PreservePerSample,
1797}
1798
1799#[derive(Clone, Debug, PartialEq, Eq)]
1800pub(crate) struct FlatTimingOverride {
1801 pub(crate) sample_durations: Vec<u32>,
1802 pub(crate) composition_offsets: Vec<i32>,
1803 pub(crate) media_duration: u64,
1804 pub(crate) presentation_duration: u64,
1805}
1806
1807impl MuxTrackConfig {
1808 pub fn new_audio(track_id: u32, timescale: u32, sample_entry_box: Vec<u8>) -> Self {
1810 Self {
1811 track_id,
1812 kind: MuxTrackKind::Audio,
1813 timescale,
1814 language: *b"und",
1815 handler_name: "SoundHandler".to_string(),
1816 track_width: 0,
1817 track_height: 0,
1818 track_width_fixed_16_16: None,
1819 track_height_fixed_16_16: None,
1820 tkhd_flags: DEFAULT_TKHD_FLAGS,
1821 alternate_group: default_alternate_group_for_kind(MuxTrackKind::Audio),
1822 volume: 0x0100,
1823 matrix: DEFAULT_TKHD_MATRIX,
1824 edit_media_time: None,
1825 sample_roll_distance: None,
1826 emit_roll_sbgp: true,
1827 sample_entry_box: sample_entry_box.clone(),
1828 sample_entry_boxes: vec![sample_entry_box],
1829 sync_sample_table_mode: SyncSampleTableMode::Auto,
1830 stts_run_encoding_mode: SttsRunEncodingMode::CollapseIdentical,
1831 stsc_run_encoding_mode: StscRunEncodingMode::CollapseIdentical,
1832 flat_timing_override: None,
1833 flat_audio_profile_level_indication: None,
1834 fragmented_decode_time_offset: None,
1835 fragmented_reference_group_fragment_counts: None,
1836 flat_source_track_creation_time: None,
1837 flat_source_track_modification_time: None,
1838 flat_source_media_creation_time: None,
1839 flat_source_media_modification_time: None,
1840 omit_flat_iods: false,
1841 flat_stsc_override: None,
1842 preserved_flat_stbl_boxes: Vec::new(),
1843 preserved_flat_trak_boxes: Vec::new(),
1844 }
1845 }
1846
1847 pub fn new_video(
1849 track_id: u32,
1850 timescale: u32,
1851 width: u16,
1852 height: u16,
1853 sample_entry_box: Vec<u8>,
1854 ) -> Self {
1855 Self {
1856 track_id,
1857 kind: MuxTrackKind::Video,
1858 timescale,
1859 language: *b"und",
1860 handler_name: "VideoHandler".to_string(),
1861 track_width: width,
1862 track_height: height,
1863 track_width_fixed_16_16: None,
1864 track_height_fixed_16_16: None,
1865 tkhd_flags: DEFAULT_TKHD_FLAGS,
1866 alternate_group: default_alternate_group_for_kind(MuxTrackKind::Video),
1867 volume: 0,
1868 matrix: DEFAULT_TKHD_MATRIX,
1869 edit_media_time: None,
1870 sample_roll_distance: None,
1871 emit_roll_sbgp: true,
1872 sample_entry_box: sample_entry_box.clone(),
1873 sample_entry_boxes: vec![sample_entry_box],
1874 sync_sample_table_mode: SyncSampleTableMode::Auto,
1875 stts_run_encoding_mode: SttsRunEncodingMode::CollapseIdentical,
1876 stsc_run_encoding_mode: StscRunEncodingMode::CollapseIdentical,
1877 flat_timing_override: None,
1878 flat_audio_profile_level_indication: None,
1879 fragmented_decode_time_offset: None,
1880 fragmented_reference_group_fragment_counts: None,
1881 flat_source_track_creation_time: None,
1882 flat_source_track_modification_time: None,
1883 flat_source_media_creation_time: None,
1884 flat_source_media_modification_time: None,
1885 omit_flat_iods: false,
1886 flat_stsc_override: None,
1887 preserved_flat_stbl_boxes: Vec::new(),
1888 preserved_flat_trak_boxes: Vec::new(),
1889 }
1890 }
1891
1892 pub fn new_text(
1894 track_id: u32,
1895 timescale: u32,
1896 width: u16,
1897 height: u16,
1898 sample_entry_box: Vec<u8>,
1899 ) -> Self {
1900 Self {
1901 track_id,
1902 kind: MuxTrackKind::Text,
1903 timescale,
1904 language: *b"und",
1905 handler_name: "TextHandler".to_string(),
1906 track_width: width,
1907 track_height: height,
1908 track_width_fixed_16_16: None,
1909 track_height_fixed_16_16: None,
1910 tkhd_flags: DEFAULT_TKHD_FLAGS,
1911 alternate_group: default_alternate_group_for_kind(MuxTrackKind::Text),
1912 volume: 0,
1913 matrix: DEFAULT_TKHD_MATRIX,
1914 edit_media_time: None,
1915 sample_roll_distance: None,
1916 emit_roll_sbgp: true,
1917 sample_entry_box: sample_entry_box.clone(),
1918 sample_entry_boxes: vec![sample_entry_box],
1919 sync_sample_table_mode: SyncSampleTableMode::Auto,
1920 stts_run_encoding_mode: SttsRunEncodingMode::CollapseIdentical,
1921 stsc_run_encoding_mode: StscRunEncodingMode::CollapseIdentical,
1922 flat_timing_override: None,
1923 flat_audio_profile_level_indication: None,
1924 fragmented_decode_time_offset: None,
1925 fragmented_reference_group_fragment_counts: None,
1926 flat_source_track_creation_time: None,
1927 flat_source_track_modification_time: None,
1928 flat_source_media_creation_time: None,
1929 flat_source_media_modification_time: None,
1930 omit_flat_iods: false,
1931 flat_stsc_override: None,
1932 preserved_flat_stbl_boxes: Vec::new(),
1933 preserved_flat_trak_boxes: Vec::new(),
1934 }
1935 }
1936
1937 pub fn new_subtitle(
1939 track_id: u32,
1940 timescale: u32,
1941 width: u16,
1942 height: u16,
1943 sample_entry_box: Vec<u8>,
1944 ) -> Self {
1945 Self {
1946 track_id,
1947 kind: MuxTrackKind::Subtitle,
1948 timescale,
1949 language: *b"und",
1950 handler_name: "SubtitleHandler".to_string(),
1951 track_width: width,
1952 track_height: height,
1953 track_width_fixed_16_16: None,
1954 track_height_fixed_16_16: None,
1955 tkhd_flags: DEFAULT_TKHD_FLAGS,
1956 alternate_group: default_alternate_group_for_kind(MuxTrackKind::Subtitle),
1957 volume: 0,
1958 matrix: DEFAULT_TKHD_MATRIX,
1959 edit_media_time: None,
1960 sample_roll_distance: None,
1961 emit_roll_sbgp: true,
1962 sample_entry_box: sample_entry_box.clone(),
1963 sample_entry_boxes: vec![sample_entry_box],
1964 sync_sample_table_mode: SyncSampleTableMode::Auto,
1965 stts_run_encoding_mode: SttsRunEncodingMode::CollapseIdentical,
1966 stsc_run_encoding_mode: StscRunEncodingMode::CollapseIdentical,
1967 flat_timing_override: None,
1968 flat_audio_profile_level_indication: None,
1969 fragmented_decode_time_offset: None,
1970 fragmented_reference_group_fragment_counts: None,
1971 flat_source_track_creation_time: None,
1972 flat_source_track_modification_time: None,
1973 flat_source_media_creation_time: None,
1974 flat_source_media_modification_time: None,
1975 omit_flat_iods: false,
1976 flat_stsc_override: None,
1977 preserved_flat_stbl_boxes: Vec::new(),
1978 preserved_flat_trak_boxes: Vec::new(),
1979 }
1980 }
1981
1982 pub const fn track_id(&self) -> u32 {
1984 self.track_id
1985 }
1986
1987 pub const fn kind(&self) -> MuxTrackKind {
1989 self.kind
1990 }
1991
1992 pub const fn timescale(&self) -> u32 {
1994 self.timescale
1995 }
1996
1997 pub const fn language(&self) -> [u8; 3] {
1999 self.language
2000 }
2001
2002 pub fn handler_name(&self) -> &str {
2004 &self.handler_name
2005 }
2006
2007 pub const fn track_width(&self) -> u16 {
2009 self.track_width
2010 }
2011
2012 pub const fn track_height(&self) -> u16 {
2014 self.track_height
2015 }
2016
2017 pub(crate) const fn track_width_fixed_16_16(&self) -> Option<u32> {
2018 self.track_width_fixed_16_16
2019 }
2020
2021 pub(crate) const fn track_height_fixed_16_16(&self) -> Option<u32> {
2022 self.track_height_fixed_16_16
2023 }
2024
2025 pub(crate) const fn tkhd_flags(&self) -> u32 {
2026 self.tkhd_flags
2027 }
2028
2029 pub(crate) const fn flat_source_track_creation_time(&self) -> Option<u64> {
2030 self.flat_source_track_creation_time
2031 }
2032
2033 pub(crate) const fn flat_source_track_modification_time(&self) -> Option<u64> {
2034 self.flat_source_track_modification_time
2035 }
2036
2037 pub(crate) const fn flat_source_media_creation_time(&self) -> Option<u64> {
2038 self.flat_source_media_creation_time
2039 }
2040
2041 pub(crate) const fn flat_source_media_modification_time(&self) -> Option<u64> {
2042 self.flat_source_media_modification_time
2043 }
2044
2045 pub(crate) const fn omit_flat_iods(&self) -> bool {
2046 self.omit_flat_iods
2047 }
2048
2049 pub(crate) const fn alternate_group(&self) -> i16 {
2050 self.alternate_group
2051 }
2052
2053 pub const fn volume(&self) -> i16 {
2055 self.volume
2056 }
2057
2058 pub(crate) const fn matrix(&self) -> [i32; 9] {
2059 self.matrix
2060 }
2061
2062 pub const fn edit_media_time(&self) -> Option<u64> {
2064 self.edit_media_time
2065 }
2066
2067 pub(crate) const fn sample_roll_distance(&self) -> Option<i16> {
2068 self.sample_roll_distance
2069 }
2070
2071 pub(crate) const fn emit_roll_sbgp(&self) -> bool {
2072 self.emit_roll_sbgp
2073 }
2074
2075 pub fn sample_entry_box(&self) -> &[u8] {
2077 &self.sample_entry_box
2078 }
2079
2080 pub fn sample_entry_boxes(&self) -> &[Vec<u8>] {
2082 &self.sample_entry_boxes
2083 }
2084
2085 pub const fn with_language(mut self, language: [u8; 3]) -> Self {
2087 self.language = language;
2088 self
2089 }
2090
2091 pub fn with_handler_name(mut self, handler_name: impl Into<String>) -> Self {
2093 self.handler_name = handler_name.into();
2094 self
2095 }
2096
2097 pub(crate) const fn with_tkhd_flags(mut self, tkhd_flags: u32) -> Self {
2098 self.tkhd_flags = tkhd_flags;
2099 self
2100 }
2101
2102 pub(crate) const fn with_flat_source_track_creation_time(
2103 mut self,
2104 flat_source_track_creation_time: Option<u64>,
2105 ) -> Self {
2106 self.flat_source_track_creation_time = flat_source_track_creation_time;
2107 self
2108 }
2109
2110 pub(crate) const fn with_flat_source_track_modification_time(
2111 mut self,
2112 flat_source_track_modification_time: Option<u64>,
2113 ) -> Self {
2114 self.flat_source_track_modification_time = flat_source_track_modification_time;
2115 self
2116 }
2117
2118 pub(crate) const fn with_flat_source_media_creation_time(
2119 mut self,
2120 flat_source_media_creation_time: Option<u64>,
2121 ) -> Self {
2122 self.flat_source_media_creation_time = flat_source_media_creation_time;
2123 self
2124 }
2125
2126 pub(crate) const fn with_flat_source_media_modification_time(
2127 mut self,
2128 flat_source_media_modification_time: Option<u64>,
2129 ) -> Self {
2130 self.flat_source_media_modification_time = flat_source_media_modification_time;
2131 self
2132 }
2133
2134 pub(crate) const fn with_omit_flat_iods(mut self, omit_flat_iods: bool) -> Self {
2135 self.omit_flat_iods = omit_flat_iods;
2136 self
2137 }
2138
2139 pub(crate) const fn with_alternate_group(mut self, alternate_group: i16) -> Self {
2140 self.alternate_group = alternate_group;
2141 self
2142 }
2143
2144 pub const fn with_volume(mut self, volume: i16) -> Self {
2146 self.volume = volume;
2147 self
2148 }
2149
2150 pub(crate) const fn with_matrix(mut self, matrix: [i32; 9]) -> Self {
2151 self.matrix = matrix;
2152 self
2153 }
2154
2155 pub(crate) const fn with_tkhd_dimensions_fixed_16_16(
2156 mut self,
2157 track_width_fixed_16_16: u32,
2158 track_height_fixed_16_16: u32,
2159 ) -> Self {
2160 self.track_width_fixed_16_16 = Some(track_width_fixed_16_16);
2161 self.track_height_fixed_16_16 = Some(track_height_fixed_16_16);
2162 self
2163 }
2164
2165 pub const fn with_edit_media_time(mut self, edit_media_time: u64) -> Self {
2167 self.edit_media_time = Some(edit_media_time);
2168 self
2169 }
2170
2171 pub(crate) const fn with_sample_roll_distance(mut self, sample_roll_distance: i16) -> Self {
2172 self.sample_roll_distance = Some(sample_roll_distance);
2173 self
2174 }
2175
2176 pub(crate) const fn with_emit_roll_sbgp(mut self, emit_roll_sbgp: bool) -> Self {
2177 self.emit_roll_sbgp = emit_roll_sbgp;
2178 self
2179 }
2180
2181 pub(crate) fn with_sample_entry_boxes(mut self, sample_entry_boxes: Vec<Vec<u8>>) -> Self {
2182 if let Some(first) = sample_entry_boxes.first() {
2183 self.sample_entry_box = first.clone();
2184 }
2185 self.sample_entry_boxes = sample_entry_boxes;
2186 self
2187 }
2188
2189 pub(crate) const fn with_sync_sample_table_mode(
2190 mut self,
2191 sync_sample_table_mode: SyncSampleTableMode,
2192 ) -> Self {
2193 self.sync_sample_table_mode = sync_sample_table_mode;
2194 self
2195 }
2196
2197 pub(crate) const fn stts_run_encoding_mode(&self) -> SttsRunEncodingMode {
2198 self.stts_run_encoding_mode
2199 }
2200
2201 pub(crate) const fn with_stts_run_encoding_mode(
2202 mut self,
2203 stts_run_encoding_mode: SttsRunEncodingMode,
2204 ) -> Self {
2205 self.stts_run_encoding_mode = stts_run_encoding_mode;
2206 self
2207 }
2208
2209 pub(crate) const fn stsc_run_encoding_mode(&self) -> StscRunEncodingMode {
2210 self.stsc_run_encoding_mode
2211 }
2212
2213 pub(crate) const fn with_stsc_run_encoding_mode(
2214 mut self,
2215 stsc_run_encoding_mode: StscRunEncodingMode,
2216 ) -> Self {
2217 self.stsc_run_encoding_mode = stsc_run_encoding_mode;
2218 self
2219 }
2220
2221 pub(crate) fn flat_timing_override(&self) -> Option<&FlatTimingOverride> {
2222 self.flat_timing_override.as_ref()
2223 }
2224
2225 pub(crate) fn with_flat_timing_override(
2226 mut self,
2227 flat_timing_override: FlatTimingOverride,
2228 ) -> Self {
2229 self.flat_timing_override = Some(flat_timing_override);
2230 self
2231 }
2232
2233 pub(crate) const fn flat_audio_profile_level_indication(&self) -> Option<u8> {
2234 self.flat_audio_profile_level_indication
2235 }
2236
2237 pub(crate) const fn with_flat_audio_profile_level_indication(
2238 mut self,
2239 flat_audio_profile_level_indication: u8,
2240 ) -> Self {
2241 self.flat_audio_profile_level_indication = Some(flat_audio_profile_level_indication);
2242 self
2243 }
2244
2245 pub(crate) const fn fragmented_decode_time_offset(&self) -> Option<u64> {
2246 self.fragmented_decode_time_offset
2247 }
2248
2249 pub(crate) const fn with_fragmented_decode_time_offset(
2250 mut self,
2251 fragmented_decode_time_offset: u64,
2252 ) -> Self {
2253 self.fragmented_decode_time_offset = Some(fragmented_decode_time_offset);
2254 self
2255 }
2256
2257 pub(crate) fn fragmented_reference_group_fragment_counts(&self) -> Option<&[u32]> {
2258 self.fragmented_reference_group_fragment_counts.as_deref()
2259 }
2260
2261 pub(crate) fn with_fragmented_reference_group_fragment_counts(
2262 mut self,
2263 fragmented_reference_group_fragment_counts: Vec<u32>,
2264 ) -> Self {
2265 self.fragmented_reference_group_fragment_counts =
2266 Some(fragmented_reference_group_fragment_counts);
2267 self
2268 }
2269
2270 pub(crate) fn flat_stsc_override(&self) -> Option<&crate::boxes::iso14496_12::Stsc> {
2271 self.flat_stsc_override.as_ref()
2272 }
2273
2274 pub(crate) fn with_flat_stsc_override(
2275 mut self,
2276 flat_stsc_override: crate::boxes::iso14496_12::Stsc,
2277 ) -> Self {
2278 self.flat_stsc_override = Some(flat_stsc_override);
2279 self
2280 }
2281
2282 pub(crate) fn preserved_flat_stbl_boxes(&self) -> &[Vec<u8>] {
2283 &self.preserved_flat_stbl_boxes
2284 }
2285
2286 pub(crate) fn with_preserved_flat_stbl_boxes(
2287 mut self,
2288 preserved_flat_stbl_boxes: Vec<Vec<u8>>,
2289 ) -> Self {
2290 self.preserved_flat_stbl_boxes = preserved_flat_stbl_boxes;
2291 self
2292 }
2293
2294 pub(crate) fn preserved_flat_trak_boxes(&self) -> &[Vec<u8>] {
2295 &self.preserved_flat_trak_boxes
2296 }
2297
2298 pub(crate) fn with_preserved_flat_trak_boxes(
2299 mut self,
2300 preserved_flat_trak_boxes: Vec<Vec<u8>>,
2301 ) -> Self {
2302 self.preserved_flat_trak_boxes = preserved_flat_trak_boxes;
2303 self
2304 }
2305}
2306
2307#[derive(Debug)]
2309pub enum MuxError {
2310 InvalidTrackSpec { spec: String, message: String },
2312 MultipleVideoTracks { count: usize },
2314 MissingTrackSpecs,
2316 MissingTrackSelection { spec: String },
2318 UnsupportedTrackImport { spec: String, message: String },
2320 InvalidDurationMode { mode: &'static str, message: String },
2322 InvalidOutputLayout {
2324 layout: &'static str,
2325 message: String,
2326 },
2327 InvalidDestinationMode { mode: &'static str, message: String },
2329 OutputPathConflict { output: PathBuf, input: PathBuf },
2331 IncompatibleTrackTiming {
2333 track_id: u32,
2334 track_timescale: u32,
2335 movie_timescale: u32,
2336 value: i64,
2337 },
2338 InvalidChunkPlan { track_id: u32, message: String },
2340 PayloadSizeOverflow,
2342 MissingSourceIndex {
2344 source_index: usize,
2345 source_count: usize,
2346 },
2347 NonMonotonicSourceOffset {
2349 source_index: usize,
2350 previous_offset: u64,
2351 next_offset: u64,
2352 },
2353 IncompleteAdvance {
2355 source_index: usize,
2356 expected_offset: u64,
2357 actual_offset: u64,
2358 },
2359 IncompleteCopy {
2361 source_index: usize,
2362 expected_size: u64,
2363 actual_size: u64,
2364 },
2365 InvalidMovieTimescale,
2367 InvalidTrackTimescale { track_id: u32 },
2369 InvalidTrackLanguage { track_id: u32, language: String },
2371 DuplicateTrackId { track_id: u32 },
2373 MissingTrackId { track_id: u32 },
2375 TrackHasNoSamples { track_id: u32 },
2377 NonMonotonicTrackDecodeTime {
2379 track_id: u32,
2380 previous_decode_time: u64,
2381 next_decode_time: u64,
2382 },
2383 InvalidSampleEntryBox { track_id: u32, message: String },
2385 LayoutOverflow(&'static str),
2387 Codec(CodecError),
2389 Writer(WriterError),
2391 Header(HeaderError),
2393 Extract(crate::extract::ExtractError),
2395 Probe(crate::probe::ProbeError),
2397 Io(io::Error),
2399}
2400
2401impl fmt::Display for MuxError {
2402 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2403 match self {
2404 Self::InvalidTrackSpec { spec, message } => {
2405 write!(f, "invalid mux track spec `{spec}`: {message}")
2406 }
2407 Self::MultipleVideoTracks { count } => write!(
2408 f,
2409 "fragmented output supports at most one video track per mux output, but {count} were requested"
2410 ),
2411 Self::MissingTrackSpecs => {
2412 write!(
2413 f,
2414 "the current mux surface requires at least one `--track` input"
2415 )
2416 }
2417 Self::MissingTrackSelection { spec } => {
2418 write!(
2419 f,
2420 "mux track spec `{spec}` did not resolve to a matching input track"
2421 )
2422 }
2423 Self::UnsupportedTrackImport { spec, message } => {
2424 write!(f, "mux track spec `{spec}` is not supported: {message}")
2425 }
2426 Self::InvalidDurationMode { mode, message } => {
2427 write!(f, "invalid mux {mode}: {message}")
2428 }
2429 Self::InvalidOutputLayout { layout, message } => {
2430 write!(f, "invalid mux layout `{layout}`: {message}")
2431 }
2432 Self::InvalidDestinationMode { mode, message } => {
2433 write!(f, "invalid mux destination mode `{mode}`: {message}")
2434 }
2435 Self::OutputPathConflict { output, input } => write!(
2436 f,
2437 "output path `{}` conflicts with input `{}`",
2438 output.display(),
2439 input.display()
2440 ),
2441 Self::IncompatibleTrackTiming {
2442 track_id,
2443 track_timescale,
2444 movie_timescale,
2445 value,
2446 } => write!(
2447 f,
2448 "track {track_id} timing value {value} from timescale {track_timescale} cannot be normalized exactly onto movie timescale {movie_timescale}"
2449 ),
2450 Self::InvalidChunkPlan { track_id, message } => {
2451 write!(
2452 f,
2453 "track {track_id} produced an invalid chunk plan: {message}"
2454 )
2455 }
2456 Self::PayloadSizeOverflow => {
2457 write!(f, "planned mux payload size overflowed the supported range")
2458 }
2459 Self::MissingSourceIndex {
2460 source_index,
2461 source_count,
2462 } => write!(
2463 f,
2464 "mux plan referenced source index {source_index}, but only {source_count} sources were provided"
2465 ),
2466 Self::NonMonotonicSourceOffset {
2467 source_index,
2468 previous_offset,
2469 next_offset,
2470 } => write!(
2471 f,
2472 "source index {source_index} would need to move backward from offset {previous_offset} to {next_offset}"
2473 ),
2474 Self::IncompleteAdvance {
2475 source_index,
2476 expected_offset,
2477 actual_offset,
2478 } => write!(
2479 f,
2480 "source index {source_index} ended while advancing to offset {expected_offset}; only reached {actual_offset}"
2481 ),
2482 Self::IncompleteCopy {
2483 source_index,
2484 expected_size,
2485 actual_size,
2486 } => write!(
2487 f,
2488 "source index {source_index} produced {actual_size} bytes, expected {expected_size}"
2489 ),
2490 Self::InvalidMovieTimescale => {
2491 write!(f, "real mux output requires a non-zero movie timescale")
2492 }
2493 Self::InvalidTrackTimescale { track_id } => {
2494 write!(
2495 f,
2496 "track {track_id} uses an invalid or incompatible media timescale for the planned mux timeline"
2497 )
2498 }
2499 Self::InvalidTrackLanguage { track_id, language } => write!(
2500 f,
2501 "track {track_id} uses invalid language code `{language}`; expected three ASCII letters"
2502 ),
2503 Self::DuplicateTrackId { track_id } => {
2504 write!(f, "duplicate mux track id {track_id}")
2505 }
2506 Self::MissingTrackId { track_id } => {
2507 write!(
2508 f,
2509 "mux plan referenced track id {track_id}, but no matching track configuration was provided"
2510 )
2511 }
2512 Self::TrackHasNoSamples { track_id } => {
2513 write!(f, "mux track {track_id} has no planned samples")
2514 }
2515 Self::NonMonotonicTrackDecodeTime {
2516 track_id,
2517 previous_decode_time,
2518 next_decode_time,
2519 } => write!(
2520 f,
2521 "track {track_id} regressed in decode order from {previous_decode_time} to {next_decode_time}"
2522 ),
2523 Self::InvalidSampleEntryBox { track_id, message } => write!(
2524 f,
2525 "track {track_id} provided an invalid sample-entry box: {message}"
2526 ),
2527 Self::LayoutOverflow(field) => write!(
2528 f,
2529 "real mux layout overflowed the supported range while building {field}"
2530 ),
2531 Self::Codec(error) => error.fmt(f),
2532 Self::Writer(error) => error.fmt(f),
2533 Self::Header(error) => error.fmt(f),
2534 Self::Extract(error) => error.fmt(f),
2535 Self::Probe(error) => error.fmt(f),
2536 Self::Io(error) => write!(f, "{error}"),
2537 }
2538 }
2539}
2540
2541impl MuxError {
2542 pub fn category(&self) -> &'static str {
2544 match self {
2545 Self::InvalidTrackSpec { .. }
2546 | Self::MultipleVideoTracks { .. }
2547 | Self::MissingTrackSpecs
2548 | Self::MissingTrackSelection { .. }
2549 | Self::InvalidDurationMode { .. }
2550 | Self::InvalidOutputLayout { .. }
2551 | Self::InvalidDestinationMode { .. }
2552 | Self::OutputPathConflict { .. }
2553 | Self::InvalidMovieTimescale
2554 | Self::InvalidTrackTimescale { .. }
2555 | Self::InvalidTrackLanguage { .. } => "input",
2556 Self::UnsupportedTrackImport { .. } => "unsupported",
2557 Self::IncompatibleTrackTiming { .. } | Self::NonMonotonicTrackDecodeTime { .. } => {
2558 "timing"
2559 }
2560 Self::InvalidChunkPlan { .. }
2561 | Self::PayloadSizeOverflow
2562 | Self::MissingSourceIndex { .. }
2563 | Self::NonMonotonicSourceOffset { .. }
2564 | Self::IncompleteAdvance { .. }
2565 | Self::IncompleteCopy { .. }
2566 | Self::DuplicateTrackId { .. }
2567 | Self::MissingTrackId { .. }
2568 | Self::TrackHasNoSamples { .. }
2569 | Self::InvalidSampleEntryBox { .. }
2570 | Self::LayoutOverflow(_) => "layout",
2571 Self::Codec(_) | Self::Writer(_) | Self::Header(_) => "writer",
2572 Self::Extract(_) | Self::Probe(_) => "input",
2573 Self::Io(_) => "io",
2574 }
2575 }
2576
2577 pub fn stage(&self) -> &'static str {
2579 match self {
2580 Self::InvalidTrackSpec { .. }
2581 | Self::MultipleVideoTracks { .. }
2582 | Self::MissingTrackSpecs
2583 | Self::InvalidDurationMode { .. }
2584 | Self::InvalidOutputLayout { .. }
2585 | Self::InvalidDestinationMode { .. }
2586 | Self::OutputPathConflict { .. } => "request",
2587 Self::MissingTrackSelection { .. }
2588 | Self::UnsupportedTrackImport { .. }
2589 | Self::Extract(_)
2590 | Self::Probe(_) => "import",
2591 Self::IncompatibleTrackTiming { .. }
2592 | Self::InvalidChunkPlan { .. }
2593 | Self::PayloadSizeOverflow
2594 | Self::MissingSourceIndex { .. }
2595 | Self::InvalidMovieTimescale
2596 | Self::InvalidTrackTimescale { .. }
2597 | Self::InvalidTrackLanguage { .. }
2598 | Self::DuplicateTrackId { .. }
2599 | Self::MissingTrackId { .. }
2600 | Self::TrackHasNoSamples { .. }
2601 | Self::NonMonotonicTrackDecodeTime { .. }
2602 | Self::InvalidSampleEntryBox { .. }
2603 | Self::LayoutOverflow(_) => "plan",
2604 Self::NonMonotonicSourceOffset { .. }
2605 | Self::IncompleteAdvance { .. }
2606 | Self::IncompleteCopy { .. }
2607 | Self::Io(_) => "payload",
2608 Self::Codec(_) | Self::Writer(_) | Self::Header(_) => "write",
2609 }
2610 }
2611}
2612
2613impl Error for MuxError {
2614 fn source(&self) -> Option<&(dyn Error + 'static)> {
2615 match self {
2616 Self::Codec(error) => Some(error),
2617 Self::Writer(error) => Some(error),
2618 Self::Header(error) => Some(error),
2619 Self::Extract(error) => Some(error),
2620 Self::Probe(error) => Some(error),
2621 Self::Io(error) => Some(error),
2622 _ => None,
2623 }
2624 }
2625}
2626
2627impl From<io::Error> for MuxError {
2628 fn from(error: io::Error) -> Self {
2629 Self::Io(error)
2630 }
2631}
2632
2633impl From<CodecError> for MuxError {
2634 fn from(error: CodecError) -> Self {
2635 Self::Codec(error)
2636 }
2637}
2638
2639impl From<WriterError> for MuxError {
2640 fn from(error: WriterError) -> Self {
2641 Self::Writer(error)
2642 }
2643}
2644
2645impl From<HeaderError> for MuxError {
2646 fn from(error: HeaderError) -> Self {
2647 Self::Header(error)
2648 }
2649}
2650
2651impl From<crate::extract::ExtractError> for MuxError {
2652 fn from(error: crate::extract::ExtractError) -> Self {
2653 Self::Extract(error)
2654 }
2655}
2656
2657impl From<crate::probe::ProbeError> for MuxError {
2658 fn from(error: crate::probe::ProbeError) -> Self {
2659 Self::Probe(error)
2660 }
2661}
2662
2663pub fn plan_staged_media_items(
2665 items: Vec<MuxStagedMediaItem>,
2666 interleave_policy: MuxInterleavePolicy,
2667) -> Result<MuxPlan, MuxError> {
2668 plan_staged_media_items_with_coordination(items, interleave_policy, Vec::new())
2669}
2670
2671pub fn plan_staged_media_items_with_chunk_sample_counts<I>(
2677 items: Vec<MuxStagedMediaItem>,
2678 interleave_policy: MuxInterleavePolicy,
2679 chunk_sample_counts_by_track: I,
2680) -> Result<MuxPlan, MuxError>
2681where
2682 I: IntoIterator<Item = (u32, Vec<u32>)>,
2683{
2684 let coordination = chunk_sample_counts_by_track
2685 .into_iter()
2686 .map(|(track_id, chunk_sample_counts)| {
2687 TrackCoordinationDirective::new(track_id, chunk_sample_counts)
2688 })
2689 .collect();
2690 plan_staged_media_items_with_coordination(items, interleave_policy, coordination)
2691}
2692
2693pub(crate) fn plan_staged_media_items_with_coordination(
2694 items: Vec<MuxStagedMediaItem>,
2695 interleave_policy: MuxInterleavePolicy,
2696 coordination_directives: Vec<TrackCoordinationDirective>,
2697) -> Result<MuxPlan, MuxError> {
2698 let mut queue_items = items
2699 .into_iter()
2700 .map(MuxQueueItem::from_staged)
2701 .collect::<Vec<_>>();
2702
2703 match interleave_policy {
2704 MuxInterleavePolicy::DecodeTime | MuxInterleavePolicy::ChunkOrdinalThenSource => {
2705 queue_items.sort_by_key(|item| {
2709 (
2710 item.staged.source_index,
2711 item.staged.data_offset,
2712 item.staged.track_id,
2713 )
2714 });
2715 }
2716 }
2717
2718 let queue = OrderedWorkQueue::new(queue_items);
2719 let mut items_by_track = BTreeMap::<u32, Vec<MuxStagedMediaItem>>::new();
2720 let mut track_state = BTreeMap::<u32, MuxTrackPlanState>::new();
2721
2722 for item in queue.iter() {
2723 let end_decode_time = item
2724 .staged
2725 .decode_time
2726 .checked_add(u64::from(item.staged.duration))
2727 .ok_or(MuxError::PayloadSizeOverflow)?;
2728 items_by_track
2729 .entry(item.staged.track_id)
2730 .or_default()
2731 .push(item.staged);
2732 track_state
2733 .entry(item.staged.track_id)
2734 .and_modify(|state| {
2735 state.item_count += 1;
2736 state.end_decode_time = state.end_decode_time.max(end_decode_time);
2737 state.first_decode_time = state.first_decode_time.min(item.staged.decode_time);
2738 })
2739 .or_insert(MuxTrackPlanState {
2740 item_count: 1,
2741 first_decode_time: item.staged.decode_time,
2742 end_decode_time,
2743 });
2744 }
2745
2746 let track_plans = track_state
2747 .into_iter()
2748 .map(|(track_id, state)| MuxTrackPlan {
2749 track_id,
2750 item_count: state.item_count,
2751 first_decode_time: state.first_decode_time,
2752 end_decode_time: state.end_decode_time,
2753 })
2754 .collect::<Vec<_>>();
2755
2756 let coordination =
2757 MuxCoordinationPlan::from_track_plans(&track_plans, coordination_directives)?;
2758 let (planned_items, total_payload_size) =
2759 build_planned_items_from_tracks(&items_by_track, &coordination, interleave_policy)?;
2760 let event_graph = MuxEventGraph::from_plan(
2761 &planned_items,
2762 &track_plans,
2763 total_payload_size,
2764 &coordination,
2765 );
2766
2767 Ok(MuxPlan {
2768 interleave_policy,
2769 planned_items,
2770 track_plans,
2771 total_payload_size,
2772 coordination,
2773 event_graph,
2774 })
2775}
2776
2777pub fn write_mp4_mux<R, W>(
2784 sources: &mut [R],
2785 writer: &mut W,
2786 file_config: &MuxFileConfig,
2787 track_configs: &[MuxTrackConfig],
2788 plan: &MuxPlan,
2789) -> Result<(), MuxError>
2790where
2791 R: Read + Seek,
2792 W: Write,
2793{
2794 mp4::write_mp4_mux(sources, writer, file_config, track_configs, plan)
2795}
2796
2797pub fn write_mp4_mux_to_path<P, Q>(
2799 source_paths: &[P],
2800 output_path: Q,
2801 file_config: &MuxFileConfig,
2802 track_configs: &[MuxTrackConfig],
2803 plan: &MuxPlan,
2804) -> Result<(), MuxError>
2805where
2806 P: AsRef<Path>,
2807 Q: AsRef<Path>,
2808{
2809 mp4::write_mp4_mux_to_path(source_paths, output_path, file_config, track_configs, plan)
2810}
2811
2812pub fn write_fragmented_mp4_mux<R, W>(
2818 sources: &mut [R],
2819 writer: &mut W,
2820 file_config: &MuxFileConfig,
2821 track_configs: &[MuxTrackConfig],
2822 single_sidx_reference: bool,
2823 plan: &MuxPlan,
2824) -> Result<(), MuxError>
2825where
2826 R: Read + Seek,
2827 W: Write,
2828{
2829 mp4::write_fragmented_mp4_mux(
2830 sources,
2831 writer,
2832 file_config,
2833 track_configs,
2834 single_sidx_reference,
2835 plan,
2836 )
2837}
2838
2839pub fn write_fragmented_mp4_mux_to_path<P, Q>(
2841 source_paths: &[P],
2842 output_path: Q,
2843 file_config: &MuxFileConfig,
2844 track_configs: &[MuxTrackConfig],
2845 single_sidx_reference: bool,
2846 plan: &MuxPlan,
2847) -> Result<(), MuxError>
2848where
2849 P: AsRef<Path>,
2850 Q: AsRef<Path>,
2851{
2852 let mut sources = source_paths
2853 .iter()
2854 .map(File::open)
2855 .collect::<Result<Vec<_>, _>>()?;
2856 let mut writer = BufWriter::new(File::create(output_path)?);
2857 write_fragmented_mp4_mux(
2858 &mut sources,
2859 &mut writer,
2860 file_config,
2861 track_configs,
2862 single_sidx_reference,
2863 plan,
2864 )
2865}
2866
2867pub fn write_fragmented_mp4_mux_split<R, I, M>(
2872 sources: &mut [R],
2873 init_writer: &mut I,
2874 media_writer: &mut M,
2875 file_config: &MuxFileConfig,
2876 track_configs: &[MuxTrackConfig],
2877 single_sidx_reference: bool,
2878 plan: &MuxPlan,
2879) -> Result<(), MuxError>
2880where
2881 R: Read + Seek,
2882 I: Write,
2883 M: Write,
2884{
2885 mp4::write_fragmented_mp4_mux_split(
2886 sources,
2887 init_writer,
2888 media_writer,
2889 file_config,
2890 track_configs,
2891 single_sidx_reference,
2892 plan,
2893 )
2894}
2895
2896pub fn write_fragmented_mp4_mux_segmented<R, I, M>(
2901 sources: &mut [R],
2902 init_writer: &mut I,
2903 media_writer: &mut M,
2904 file_config: &MuxFileConfig,
2905 track_configs: &[MuxTrackConfig],
2906 plan: &MuxPlan,
2907) -> Result<(), MuxError>
2908where
2909 R: Read + Seek,
2910 I: Write,
2911 M: Write,
2912{
2913 mp4::write_fragmented_mp4_mux_segmented(
2914 sources,
2915 init_writer,
2916 media_writer,
2917 file_config,
2918 track_configs,
2919 plan,
2920 )
2921}
2922
2923pub fn write_fragmented_mp4_mux_split_to_paths<P, I, M>(
2925 source_paths: &[P],
2926 init_path: I,
2927 media_path: M,
2928 file_config: &MuxFileConfig,
2929 track_configs: &[MuxTrackConfig],
2930 single_sidx_reference: bool,
2931 plan: &MuxPlan,
2932) -> Result<(), MuxError>
2933where
2934 P: AsRef<Path>,
2935 I: AsRef<Path>,
2936 M: AsRef<Path>,
2937{
2938 let mut sources = source_paths
2939 .iter()
2940 .map(File::open)
2941 .collect::<Result<Vec<_>, _>>()?;
2942 let mut init_writer = BufWriter::new(File::create(init_path)?);
2943 let mut media_writer = BufWriter::new(File::create(media_path)?);
2944 write_fragmented_mp4_mux_split(
2945 &mut sources,
2946 &mut init_writer,
2947 &mut media_writer,
2948 file_config,
2949 track_configs,
2950 single_sidx_reference,
2951 plan,
2952 )
2953}
2954
2955pub fn write_fragmented_mp4_mux_chunked<R, W>(
2961 sources: &mut [R],
2962 writer: &mut W,
2963 file_config: &MuxFileConfig,
2964 track_configs: &[MuxTrackConfig],
2965 single_sidx_reference: bool,
2966 plan: &MuxPlan,
2967) -> Result<(), MuxError>
2968where
2969 R: Read + Seek,
2970 W: Write,
2971{
2972 mp4::write_fragmented_mp4_mux_chunked(
2973 sources,
2974 writer,
2975 file_config,
2976 track_configs,
2977 single_sidx_reference,
2978 plan,
2979 )
2980}
2981
2982#[cfg(feature = "async")]
2984#[cfg_attr(docsrs, doc(cfg(all(feature = "mux", feature = "async"))))]
2985pub async fn write_mp4_mux_async<R, W>(
2986 sources: &mut [R],
2987 writer: &mut W,
2988 file_config: &MuxFileConfig,
2989 track_configs: &[MuxTrackConfig],
2990 plan: &MuxPlan,
2991) -> Result<(), MuxError>
2992where
2993 R: AsyncReadSeek,
2994 W: AsyncWrite + Unpin,
2995{
2996 mp4::write_mp4_mux_async(sources, writer, file_config, track_configs, plan).await
2997}
2998
2999#[cfg(feature = "async")]
3001#[cfg_attr(docsrs, doc(cfg(all(feature = "mux", feature = "async"))))]
3002pub async fn write_mp4_mux_to_path_async<P, Q>(
3003 source_paths: &[P],
3004 output_path: Q,
3005 file_config: &MuxFileConfig,
3006 track_configs: &[MuxTrackConfig],
3007 plan: &MuxPlan,
3008) -> Result<(), MuxError>
3009where
3010 P: AsRef<Path>,
3011 Q: AsRef<Path>,
3012{
3013 mp4::write_mp4_mux_to_path_async(source_paths, output_path, file_config, track_configs, plan)
3014 .await
3015}
3016
3017#[cfg(feature = "async")]
3019#[cfg_attr(docsrs, doc(cfg(all(feature = "mux", feature = "async"))))]
3020pub async fn write_fragmented_mp4_mux_async<R, W>(
3021 sources: &mut [R],
3022 writer: &mut W,
3023 file_config: &MuxFileConfig,
3024 track_configs: &[MuxTrackConfig],
3025 single_sidx_reference: bool,
3026 plan: &MuxPlan,
3027) -> Result<(), MuxError>
3028where
3029 R: AsyncReadSeek,
3030 W: AsyncWrite + Unpin,
3031{
3032 mp4::write_fragmented_mp4_mux_async(
3033 sources,
3034 writer,
3035 file_config,
3036 track_configs,
3037 single_sidx_reference,
3038 plan,
3039 )
3040 .await
3041}
3042
3043#[cfg(feature = "async")]
3045#[cfg_attr(docsrs, doc(cfg(all(feature = "mux", feature = "async"))))]
3046pub async fn write_fragmented_mp4_mux_to_path_async<P, Q>(
3047 source_paths: &[P],
3048 output_path: Q,
3049 file_config: &MuxFileConfig,
3050 track_configs: &[MuxTrackConfig],
3051 single_sidx_reference: bool,
3052 plan: &MuxPlan,
3053) -> Result<(), MuxError>
3054where
3055 P: AsRef<Path>,
3056 Q: AsRef<Path>,
3057{
3058 let mut sources = Vec::with_capacity(source_paths.len());
3059 for path in source_paths {
3060 sources.push(TokioFile::open(path).await?);
3061 }
3062 let output = TokioFile::create(output_path).await?;
3063 let mut writer = tokio::io::BufWriter::new(output);
3064 write_fragmented_mp4_mux_async(
3065 &mut sources,
3066 &mut writer,
3067 file_config,
3068 track_configs,
3069 single_sidx_reference,
3070 plan,
3071 )
3072 .await
3073}
3074
3075#[cfg(feature = "async")]
3077#[cfg_attr(docsrs, doc(cfg(all(feature = "mux", feature = "async"))))]
3078pub async fn write_fragmented_mp4_mux_split_async<R, I, M>(
3079 sources: &mut [R],
3080 init_writer: &mut I,
3081 media_writer: &mut M,
3082 file_config: &MuxFileConfig,
3083 track_configs: &[MuxTrackConfig],
3084 single_sidx_reference: bool,
3085 plan: &MuxPlan,
3086) -> Result<(), MuxError>
3087where
3088 R: AsyncReadSeek,
3089 I: AsyncWrite + Unpin,
3090 M: AsyncWrite + Unpin,
3091{
3092 mp4::write_fragmented_mp4_mux_split_async(
3093 sources,
3094 init_writer,
3095 media_writer,
3096 file_config,
3097 track_configs,
3098 single_sidx_reference,
3099 plan,
3100 )
3101 .await
3102}
3103
3104#[cfg(feature = "async")]
3107#[cfg_attr(docsrs, doc(cfg(all(feature = "mux", feature = "async"))))]
3108pub async fn write_fragmented_mp4_mux_segmented_async<R, I, M>(
3109 sources: &mut [R],
3110 init_writer: &mut I,
3111 media_writer: &mut M,
3112 file_config: &MuxFileConfig,
3113 track_configs: &[MuxTrackConfig],
3114 plan: &MuxPlan,
3115) -> Result<(), MuxError>
3116where
3117 R: AsyncReadSeek,
3118 I: AsyncWrite + Unpin,
3119 M: AsyncWrite + Unpin,
3120{
3121 mp4::write_fragmented_mp4_mux_segmented_async(
3122 sources,
3123 init_writer,
3124 media_writer,
3125 file_config,
3126 track_configs,
3127 plan,
3128 )
3129 .await
3130}
3131
3132#[cfg(feature = "async")]
3135#[cfg_attr(docsrs, doc(cfg(all(feature = "mux", feature = "async"))))]
3136pub async fn write_fragmented_mp4_mux_chunked_async<R, W>(
3137 sources: &mut [R],
3138 writer: &mut W,
3139 file_config: &MuxFileConfig,
3140 track_configs: &[MuxTrackConfig],
3141 single_sidx_reference: bool,
3142 plan: &MuxPlan,
3143) -> Result<(), MuxError>
3144where
3145 R: AsyncReadSeek,
3146 W: AsyncWrite + Unpin,
3147{
3148 mp4::write_fragmented_mp4_mux_chunked_async(
3149 sources,
3150 writer,
3151 file_config,
3152 track_configs,
3153 single_sidx_reference,
3154 plan,
3155 )
3156 .await
3157}
3158
3159pub fn copy_planned_payloads<R, W>(
3162 sources: &mut [R],
3163 writer: &mut W,
3164 plan: &MuxPlan,
3165) -> Result<(), MuxError>
3166where
3167 R: Read + Seek,
3168 W: Write,
3169{
3170 let mut cursor = plan.event_graph.cursor();
3171 while let Some(sample) = cursor.next_sample() {
3172 let staged = sample.planned_item().staged();
3173 let Some(source) = sources.get_mut(staged.source_index()) else {
3174 return Err(MuxError::MissingSourceIndex {
3175 source_index: staged.source_index(),
3176 source_count: sources.len(),
3177 });
3178 };
3179
3180 source.seek(SeekFrom::Start(staged.data_offset()))?;
3181 let mut limited = source.take(u64::from(staged.data_size()));
3182 let copied = io::copy(&mut limited, writer)?;
3183 if copied != u64::from(staged.data_size()) {
3184 return Err(MuxError::IncompleteCopy {
3185 source_index: staged.source_index(),
3186 expected_size: u64::from(staged.data_size()),
3187 actual_size: copied,
3188 });
3189 }
3190 }
3191
3192 Ok(())
3193}
3194
3195pub fn copy_planned_payloads_progressive<R, W>(
3201 sources: &mut [R],
3202 writer: &mut W,
3203 plan: &MuxPlan,
3204) -> Result<(), MuxError>
3205where
3206 R: Read,
3207 W: Write,
3208{
3209 let mut source_offsets = vec![0_u64; sources.len()];
3210 let mut cursor = plan.event_graph.cursor();
3211 while let Some(sample) = cursor.next_sample() {
3212 let staged = sample.planned_item().staged();
3213 let Some(source) = sources.get_mut(staged.source_index()) else {
3214 return Err(MuxError::MissingSourceIndex {
3215 source_index: staged.source_index(),
3216 source_count: sources.len(),
3217 });
3218 };
3219
3220 let source_offset = source_offsets.get_mut(staged.source_index()).unwrap();
3221 advance_progressive_source(
3222 source,
3223 staged.source_index(),
3224 source_offset,
3225 staged.data_offset(),
3226 )?;
3227 copy_progressive_payload(
3228 source,
3229 writer,
3230 staged.source_index(),
3231 source_offset,
3232 u64::from(staged.data_size()),
3233 )?;
3234 }
3235
3236 Ok(())
3237}
3238
3239pub fn copy_planned_payloads_to_path<P, Q>(
3241 source_paths: &[P],
3242 output_path: Q,
3243 plan: &MuxPlan,
3244) -> Result<(), MuxError>
3245where
3246 P: AsRef<Path>,
3247 Q: AsRef<Path>,
3248{
3249 let mut sources = source_paths
3250 .iter()
3251 .map(File::open)
3252 .collect::<Result<Vec<_>, _>>()?;
3253 let mut writer = BufWriter::new(File::create(output_path)?);
3254 copy_planned_payloads(&mut sources, &mut writer, plan)
3255}
3256
3257#[cfg(feature = "async")]
3260#[cfg_attr(docsrs, doc(cfg(all(feature = "mux", feature = "async"))))]
3261pub async fn copy_planned_payloads_async<R, W>(
3262 sources: &mut [R],
3263 writer: &mut W,
3264 plan: &MuxPlan,
3265) -> Result<(), MuxError>
3266where
3267 R: AsyncReadSeek,
3268 W: AsyncWrite + Unpin,
3269{
3270 let mut buffer = vec![0_u8; 16 * 1024];
3271 let mut cursor = plan.event_graph.cursor();
3272 while let Some(sample) = cursor.next_sample() {
3273 let staged = sample.planned_item().staged();
3274 let Some(source) = sources.get_mut(staged.source_index()) else {
3275 return Err(MuxError::MissingSourceIndex {
3276 source_index: staged.source_index(),
3277 source_count: sources.len(),
3278 });
3279 };
3280
3281 source.seek(SeekFrom::Start(staged.data_offset())).await?;
3282 let mut remaining = u64::from(staged.data_size());
3283 let mut copied = 0_u64;
3284 while remaining > 0 {
3285 let chunk_len = remaining.min(buffer.len() as u64) as usize;
3286 let read = source.read(&mut buffer[..chunk_len]).await?;
3287 if read == 0 {
3288 break;
3289 }
3290 writer.write_all(&buffer[..read]).await?;
3291 copied += read as u64;
3292 remaining -= read as u64;
3293 }
3294
3295 if copied != u64::from(staged.data_size()) {
3296 return Err(MuxError::IncompleteCopy {
3297 source_index: staged.source_index(),
3298 expected_size: u64::from(staged.data_size()),
3299 actual_size: copied,
3300 });
3301 }
3302 }
3303
3304 writer.flush().await?;
3305 Ok(())
3306}
3307
3308#[cfg(feature = "async")]
3314#[cfg_attr(docsrs, doc(cfg(all(feature = "mux", feature = "async"))))]
3315pub async fn copy_planned_payloads_async_progressive<R, W>(
3316 sources: &mut [R],
3317 writer: &mut W,
3318 plan: &MuxPlan,
3319) -> Result<(), MuxError>
3320where
3321 R: AsyncReadForward,
3322 W: AsyncWriteForward,
3323{
3324 let mut source_offsets = vec![0_u64; sources.len()];
3325 let mut buffer = vec![0_u8; 16 * 1024];
3326 let mut cursor = plan.event_graph.cursor();
3327 while let Some(sample) = cursor.next_sample() {
3328 let staged = sample.planned_item().staged();
3329 let Some(source) = sources.get_mut(staged.source_index()) else {
3330 return Err(MuxError::MissingSourceIndex {
3331 source_index: staged.source_index(),
3332 source_count: sources.len(),
3333 });
3334 };
3335
3336 let source_offset = source_offsets.get_mut(staged.source_index()).unwrap();
3337 advance_progressive_source_async(
3338 source,
3339 staged.source_index(),
3340 source_offset,
3341 staged.data_offset(),
3342 &mut buffer,
3343 )
3344 .await?;
3345 copy_progressive_payload_async(
3346 source,
3347 writer,
3348 staged.source_index(),
3349 source_offset,
3350 u64::from(staged.data_size()),
3351 &mut buffer,
3352 )
3353 .await?;
3354 }
3355
3356 writer.flush().await?;
3357 Ok(())
3358}
3359
3360#[cfg(feature = "async")]
3363#[cfg_attr(docsrs, doc(cfg(all(feature = "mux", feature = "async"))))]
3364pub async fn copy_planned_payloads_to_path_async<P, Q>(
3365 source_paths: &[P],
3366 output_path: Q,
3367 plan: &MuxPlan,
3368) -> Result<(), MuxError>
3369where
3370 P: AsRef<Path>,
3371 Q: AsRef<Path>,
3372{
3373 let mut sources = Vec::with_capacity(source_paths.len());
3374 for path in source_paths {
3375 sources.push(TokioFile::open(path).await?);
3376 }
3377 let mut writer = TokioFile::create(output_path).await?;
3378 copy_planned_payloads_async(&mut sources, &mut writer, plan).await
3379}
3380
3381struct MuxQueueItem {
3382 staged: MuxStagedMediaItem,
3383}
3384
3385impl MuxQueueItem {
3386 fn from_staged(staged: MuxStagedMediaItem) -> Self {
3387 Self { staged }
3388 }
3389}
3390
3391impl QueueWorkItem for MuxQueueItem {
3392 fn queue_order_key(&self) -> u64 {
3393 self.staged.decode_time
3394 }
3395}
3396
3397struct MuxTrackPlanState {
3398 item_count: u32,
3399 first_decode_time: u64,
3400 end_decode_time: u64,
3401}
3402
3403#[derive(Clone, Copy)]
3404struct PlannedChunk {
3405 chunk_index: usize,
3406 order_key: PlannedChunkOrderKey,
3407 track_id: u32,
3408 start_index: usize,
3409 end_index: usize,
3410}
3411
3412#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
3413struct PlannedChunkOrderKey {
3414 decode_time: u64,
3415 source_index: usize,
3416 data_offset: u64,
3417 track_id: u32,
3418}
3419
3420fn build_planned_items_from_tracks(
3421 items_by_track: &BTreeMap<u32, Vec<MuxStagedMediaItem>>,
3422 coordination: &MuxCoordinationPlan,
3423 interleave_policy: MuxInterleavePolicy,
3424) -> Result<(Vec<MuxPlannedMediaItem>, u64), MuxError> {
3425 let mut chunks = Vec::new();
3426 let total_sample_count = items_by_track.values().map(Vec::len).sum();
3427 for (&track_id, items) in items_by_track {
3428 let chunk_sample_counts = coordination.chunk_sample_counts(track_id)?;
3429 let mut start_index = 0_usize;
3430 for (chunk_index, &samples_per_chunk) in chunk_sample_counts.iter().enumerate() {
3431 let chunk_len = usize::try_from(samples_per_chunk)
3432 .map_err(|_| MuxError::LayoutOverflow("chunk sample-count conversion"))?;
3433 let end_index = start_index
3434 .checked_add(chunk_len)
3435 .ok_or(MuxError::LayoutOverflow("chunk sample indexing"))?;
3436 let first_sample =
3437 items
3438 .get(start_index)
3439 .ok_or_else(|| MuxError::InvalidChunkPlan {
3440 track_id,
3441 message: "chunk boundaries ran past the staged sample count".to_string(),
3442 })?;
3443 chunks.push(PlannedChunk {
3444 chunk_index,
3445 order_key: PlannedChunkOrderKey {
3446 decode_time: first_sample.decode_time(),
3447 source_index: first_sample.source_index(),
3448 data_offset: first_sample.data_offset(),
3449 track_id,
3450 },
3451 track_id,
3452 start_index,
3453 end_index,
3454 });
3455 start_index = end_index;
3456 }
3457 if start_index != items.len() {
3458 return Err(MuxError::InvalidChunkPlan {
3459 track_id,
3460 message: "chunk boundaries did not cover every staged sample".to_string(),
3461 });
3462 }
3463 }
3464
3465 match interleave_policy {
3466 MuxInterleavePolicy::DecodeTime => {
3467 chunks.sort_by_key(|chunk| chunk.order_key);
3468 }
3469 MuxInterleavePolicy::ChunkOrdinalThenSource => {
3470 chunks.sort_by_key(|chunk| {
3471 (
3472 chunk.chunk_index,
3473 chunk.order_key.source_index,
3474 chunk.order_key.data_offset,
3475 chunk.order_key.track_id,
3476 chunk.order_key.decode_time,
3477 )
3478 });
3479 }
3480 }
3481
3482 let mut planned_items = Vec::with_capacity(total_sample_count);
3483 let mut total_payload_size = 0_u64;
3484 for chunk in chunks {
3485 let items = items_by_track
3486 .get(&chunk.track_id)
3487 .ok_or(MuxError::MissingTrackId {
3488 track_id: chunk.track_id,
3489 })?;
3490 for staged in &items[chunk.start_index..chunk.end_index] {
3491 planned_items.push(MuxPlannedMediaItem {
3492 staged: *staged,
3493 output_offset: total_payload_size,
3494 });
3495 total_payload_size = total_payload_size
3496 .checked_add(u64::from(staged.data_size()))
3497 .ok_or(MuxError::PayloadSizeOverflow)?;
3498 }
3499 }
3500
3501 Ok((planned_items, total_payload_size))
3502}
3503
3504fn advance_progressive_source<R>(
3505 source: &mut R,
3506 source_index: usize,
3507 current_offset: &mut u64,
3508 target_offset: u64,
3509) -> Result<(), MuxError>
3510where
3511 R: Read,
3512{
3513 if target_offset < *current_offset {
3514 return Err(MuxError::NonMonotonicSourceOffset {
3515 source_index,
3516 previous_offset: *current_offset,
3517 next_offset: target_offset,
3518 });
3519 }
3520
3521 let mut remaining = target_offset - *current_offset;
3522 let mut buffer = [0_u8; 16 * 1024];
3523 while remaining > 0 {
3524 let chunk_len = remaining.min(buffer.len() as u64) as usize;
3525 let read = source.read(&mut buffer[..chunk_len])?;
3526 if read == 0 {
3527 return Err(MuxError::IncompleteAdvance {
3528 source_index,
3529 expected_offset: target_offset,
3530 actual_offset: *current_offset,
3531 });
3532 }
3533 *current_offset += read as u64;
3534 remaining -= read as u64;
3535 }
3536
3537 Ok(())
3538}
3539
3540fn copy_progressive_payload<R, W>(
3541 source: &mut R,
3542 writer: &mut W,
3543 source_index: usize,
3544 current_offset: &mut u64,
3545 size: u64,
3546) -> Result<(), MuxError>
3547where
3548 R: Read,
3549 W: Write,
3550{
3551 let mut remaining = size;
3552 let mut copied = 0_u64;
3553 let mut buffer = [0_u8; 16 * 1024];
3554 while remaining > 0 {
3555 let chunk_len = remaining.min(buffer.len() as u64) as usize;
3556 let read = source.read(&mut buffer[..chunk_len])?;
3557 if read == 0 {
3558 return Err(MuxError::IncompleteCopy {
3559 source_index,
3560 expected_size: size,
3561 actual_size: copied,
3562 });
3563 }
3564 writer.write_all(&buffer[..read])?;
3565 *current_offset += read as u64;
3566 copied += read as u64;
3567 remaining -= read as u64;
3568 }
3569
3570 Ok(())
3571}
3572
3573#[cfg(feature = "async")]
3574async fn advance_progressive_source_async<R>(
3575 source: &mut R,
3576 source_index: usize,
3577 current_offset: &mut u64,
3578 target_offset: u64,
3579 buffer: &mut [u8],
3580) -> Result<(), MuxError>
3581where
3582 R: AsyncReadForward,
3583{
3584 if target_offset < *current_offset {
3585 return Err(MuxError::NonMonotonicSourceOffset {
3586 source_index,
3587 previous_offset: *current_offset,
3588 next_offset: target_offset,
3589 });
3590 }
3591
3592 let mut remaining = target_offset - *current_offset;
3593 while remaining > 0 {
3594 let chunk_len = remaining.min(buffer.len() as u64) as usize;
3595 let read = source.read(&mut buffer[..chunk_len]).await?;
3596 if read == 0 {
3597 return Err(MuxError::IncompleteAdvance {
3598 source_index,
3599 expected_offset: target_offset,
3600 actual_offset: *current_offset,
3601 });
3602 }
3603 *current_offset += read as u64;
3604 remaining -= read as u64;
3605 }
3606
3607 Ok(())
3608}
3609
3610#[cfg(feature = "async")]
3611async fn copy_progressive_payload_async<R, W>(
3612 source: &mut R,
3613 writer: &mut W,
3614 source_index: usize,
3615 current_offset: &mut u64,
3616 size: u64,
3617 buffer: &mut [u8],
3618) -> Result<(), MuxError>
3619where
3620 R: AsyncReadForward,
3621 W: AsyncWriteForward,
3622{
3623 let mut remaining = size;
3624 let mut copied = 0_u64;
3625 while remaining > 0 {
3626 let chunk_len = remaining.min(buffer.len() as u64) as usize;
3627 let read = source.read(&mut buffer[..chunk_len]).await?;
3628 if read == 0 {
3629 return Err(MuxError::IncompleteCopy {
3630 source_index,
3631 expected_size: size,
3632 actual_size: copied,
3633 });
3634 }
3635 writer.write_all(&buffer[..read]).await?;
3636 *current_offset += read as u64;
3637 copied += read as u64;
3638 remaining -= read as u64;
3639 }
3640
3641 Ok(())
3642}
3643
3644#[cfg(test)]
3645mod tests {
3646 use super::*;
3647
3648 #[test]
3649 fn coordinated_chunk_plans_keep_multi_sample_chunks_contiguous_in_output_order() {
3650 let plan = plan_staged_media_items_with_coordination(
3651 vec![
3652 MuxStagedMediaItem::new(0, 1, 0, 10, 0, 4),
3653 MuxStagedMediaItem::new(0, 1, 10, 10, 4, 4),
3654 MuxStagedMediaItem::new(1, 2, 0, 10, 0, 3),
3655 MuxStagedMediaItem::new(1, 2, 10, 10, 3, 3),
3656 ],
3657 MuxInterleavePolicy::DecodeTime,
3658 vec![
3659 TrackCoordinationDirective::new(1, vec![2]),
3660 TrackCoordinationDirective::new(2, vec![2]),
3661 ],
3662 )
3663 .unwrap();
3664
3665 let planned = plan.planned_items();
3666 assert_eq!(planned.len(), 4);
3667 assert_eq!(planned[0].staged().track_id(), 1);
3668 assert_eq!(planned[1].staged().track_id(), 1);
3669 assert_eq!(planned[2].staged().track_id(), 2);
3670 assert_eq!(planned[3].staged().track_id(), 2);
3671 assert_eq!(planned[0].output_offset(), 0);
3672 assert_eq!(planned[1].output_offset(), 4);
3673 assert_eq!(planned[2].output_offset(), 8);
3674 assert_eq!(planned[3].output_offset(), 11);
3675 }
3676
3677 #[test]
3678 fn chunk_ordinal_interleave_keeps_aligned_chunks_in_source_pair_order() {
3679 let plan = plan_staged_media_items_with_coordination(
3680 vec![
3681 MuxStagedMediaItem::new(0, 1, 0, 10, 0, 4),
3682 MuxStagedMediaItem::new(0, 1, 10, 10, 4, 4),
3683 MuxStagedMediaItem::new(0, 1, 20, 10, 8, 4),
3684 MuxStagedMediaItem::new(0, 1, 30, 10, 12, 4),
3685 MuxStagedMediaItem::new(1, 2, 0, 10, 0, 3),
3686 MuxStagedMediaItem::new(1, 2, 15, 10, 3, 3),
3687 MuxStagedMediaItem::new(1, 2, 31, 10, 6, 3),
3688 ],
3689 MuxInterleavePolicy::ChunkOrdinalThenSource,
3690 vec![
3691 TrackCoordinationDirective::new(1, vec![1, 1, 1, 1]),
3692 TrackCoordinationDirective::new(2, vec![1, 1, 1]),
3693 ],
3694 )
3695 .unwrap();
3696
3697 let track_order = plan
3698 .planned_items()
3699 .iter()
3700 .map(|item| item.staged().track_id())
3701 .collect::<Vec<_>>();
3702 assert_eq!(track_order, vec![1, 2, 1, 2, 1, 2, 1]);
3703 }
3704}