mp4forge 0.8.0

Rust library and CLI for inspecting, probing, extracting, muxing, and rewriting MP4 structures
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
//! Box definitions and box-specific codecs.

use std::collections::BTreeMap;

use crate::FourCc;
use crate::codec::{CodecBox, DynCodecBox};

/// AV1 sample-entry and codec-configuration box definitions.
pub mod av1;
/// AVS3 sample-entry and decoder-configuration box definitions.
pub mod avs3;
/// Dolby audio sample-entry child box definitions.
pub mod dolby;
/// DTS sample-entry child box definitions.
pub mod dts;
/// ETSI TS 102 366 AC-3 sample-entry and decoder-configuration box definitions.
pub mod etsi_ts_102_366;
/// ETSI TS 103 190 AC-4 sample-entry and decoder-configuration box definitions.
pub mod etsi_ts_103_190;
/// FLAC sample-entry and decoder-configuration box definitions.
pub mod flac;
/// IAMF sample-entry child box definitions.
pub mod iamf;
/// ISMA Cryp protection-related box definitions.
pub mod isma_cryp;
/// ISO/IEC 14496-12 box definitions and codec support.
pub mod iso14496_12;
/// ISO/IEC 14496-14 ES descriptor box definitions and codec support.
pub mod iso14496_14;
/// ISO/IEC 14496-15 VVC sample-entry and decoder-configuration box definitions.
pub mod iso14496_15;
/// ISO/IEC 14496-30 WebVTT box definitions and codec support.
pub mod iso14496_30;
/// ISO/IEC 23001-5 uncompressed-audio box definitions and codec support.
pub mod iso23001_5;
/// ISO/IEC 23001-7 common-encryption box definitions and codec support.
pub mod iso23001_7;
/// Marlin IPMP protection-related box definitions and payload helpers.
pub mod marlin;
/// Item-list metadata and key-table box definitions.
pub mod metadata;
/// MPEG-H sample-entry and decoder-configuration box definitions.
pub mod mpeg_h;
/// OMA DCF decryption-related box definitions.
pub mod oma_dcf;
/// Opus sample-entry and decoder-configuration box definitions.
pub mod opus;
/// 3GPP `udta`-scoped metadata string box definitions and codec support.
pub mod threegpp;
/// VP8/VP9 sample-entry and codec-configuration box definitions.
pub mod vp;

/// Trait implemented by runtime-typed box wrappers whose `FourCc` is supplied by the registry.
pub trait AnyTypeBox {
    /// Sets the concrete box type chosen by the registry.
    fn set_box_type(&mut self, box_type: FourCc);
}

type BoxConstructor = fn(FourCc) -> Box<dyn DynCodecBox>;
type ContextPredicate = fn(BoxLookupContext) -> bool;
type DynamicContextPredicate = fn(FourCc, BoxLookupContext) -> bool;

/// Parent-scope state used when selecting context-sensitive box registrations.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct BoxLookupContext {
    pub(crate) is_quicktime_compatible: bool,
    pub(crate) quicktime_keys_meta_entry_count: usize,
    pub(crate) ilst_meta_item: Option<FourCc>,
    pub(crate) under_audio_sample_entry: bool,
    pub(crate) under_wave: bool,
    pub(crate) under_ilst: bool,
    pub(crate) under_ilst_meta: bool,
    pub(crate) under_ilst_free_meta: bool,
    pub(crate) under_udta: bool,
}

impl BoxLookupContext {
    /// Creates an empty lookup context with no active parent scopes.
    pub const fn new() -> Self {
        Self {
            is_quicktime_compatible: false,
            quicktime_keys_meta_entry_count: 0,
            ilst_meta_item: None,
            under_audio_sample_entry: false,
            under_wave: false,
            under_ilst: false,
            under_ilst_meta: false,
            under_ilst_free_meta: false,
            under_udta: false,
        }
    }

    /// Returns `true` when the file-level compatible-brand scan found the QuickTime brand.
    pub const fn is_quicktime_compatible(&self) -> bool {
        self.is_quicktime_compatible
    }

    /// Carries the QuickTime-compatible root flag into later child lookups.
    pub const fn with_quicktime_compatible(mut self, is_quicktime_compatible: bool) -> Self {
        self.is_quicktime_compatible = is_quicktime_compatible;
        self
    }

    /// Carries the parsed `keys` entry count into later numbered-item lookups.
    pub const fn with_metadata_keys_entry_count(
        mut self,
        quicktime_keys_meta_entry_count: usize,
    ) -> Self {
        self.quicktime_keys_meta_entry_count = quicktime_keys_meta_entry_count;
        self
    }

    /// Returns the current numbered-item upper bound learned from `keys`.
    pub const fn metadata_keys_entry_count(&self) -> usize {
        self.quicktime_keys_meta_entry_count
    }

    /// Returns the active item-list metadata identifier when walking under an `ilst` item box.
    pub const fn ilst_meta_item(&self) -> Option<FourCc> {
        self.ilst_meta_item
    }

    /// Returns `true` when the current lookup runs under an audio sample-entry box.
    pub const fn under_audio_sample_entry(&self) -> bool {
        self.under_audio_sample_entry
    }

    /// Returns `true` when the current lookup runs under a `wave` box.
    pub const fn under_wave(&self) -> bool {
        self.under_wave
    }

    /// Returns `true` when the current lookup runs under an `ilst` box.
    pub const fn under_ilst(&self) -> bool {
        self.under_ilst
    }

    /// Returns `true` when the current lookup runs under an `ilst` item container.
    pub const fn under_ilst_meta(&self) -> bool {
        self.under_ilst_meta
    }

    /// Returns `true` when the current lookup runs under a free-form `----` item container.
    pub const fn under_ilst_free_meta(&self) -> bool {
        self.under_ilst_free_meta
    }

    /// Returns `true` when the current lookup runs under a `udta` box.
    pub const fn under_udta(&self) -> bool {
        self.under_udta
    }

    /// Returns the child-lookup context that applies after entering `box_type`.
    pub fn enter(mut self, box_type: FourCc) -> Self {
        const WAVE: FourCc = FourCc::from_bytes(*b"wave");
        const ILST: FourCc = FourCc::from_bytes(*b"ilst");
        const UDTA: FourCc = FourCc::from_bytes(*b"udta");
        const FREE_FORM: FourCc = FourCc::from_bytes(*b"----");
        const MP4A: FourCc = FourCc::from_bytes(*b"mp4a");
        const ENCA: FourCc = FourCc::from_bytes(*b"enca");
        const SAMR: FourCc = FourCc::from_bytes(*b"samr");
        const SAWB: FourCc = FourCc::from_bytes(*b"sawb");
        const SQCP: FourCc = FourCc::from_bytes(*b"sqcp");
        const SEVC: FourCc = FourCc::from_bytes(*b"sevc");
        const SSMV: FourCc = FourCc::from_bytes(*b"ssmv");
        const ALAC: FourCc = FourCc::from_bytes(*b"alac");
        const AC_3: FourCc = FourCc::from_bytes(*b"ac-3");
        const EC_3: FourCc = FourCc::from_bytes(*b"ec-3");
        const AC_4: FourCc = FourCc::from_bytes(*b"ac-4");
        const MLPA: FourCc = FourCc::from_bytes(*b"mlpa");
        const DTSC: FourCc = FourCc::from_bytes(*b"dtsc");
        const DTSE: FourCc = FourCc::from_bytes(*b"dtse");
        const DTSH: FourCc = FourCc::from_bytes(*b"dtsh");
        const DTSL: FourCc = FourCc::from_bytes(*b"dtsl");
        const DTSM: FourCc = FourCc::from_bytes(*b"dtsm");
        const DTS_MINUS: FourCc = FourCc::from_bytes(*b"dts-");
        const DTSX: FourCc = FourCc::from_bytes(*b"dtsx");
        const DTSY: FourCc = FourCc::from_bytes(*b"dtsy");
        const FLAC: FourCc = FourCc::from_bytes(*b"fLaC");
        const OPUS: FourCc = FourCc::from_bytes(*b"Opus");
        const IAMF: FourCc = FourCc::from_bytes(*b"iamf");
        const MHA1: FourCc = FourCc::from_bytes(*b"mha1");
        const MHM1: FourCc = FourCc::from_bytes(*b"mhm1");

        if matches!(
            box_type,
            MP4A | ENCA
                | SAMR
                | SAWB
                | SQCP
                | SEVC
                | SSMV
                | ALAC
                | AC_3
                | EC_3
                | AC_4
                | MLPA
                | DTSC
                | DTSE
                | DTSH
                | DTSL
                | DTSM
                | DTS_MINUS
                | DTSX
                | DTSY
                | FLAC
                | OPUS
                | IAMF
                | MHA1
                | MHM1
        ) {
            self.under_audio_sample_entry = true;
        }

        if box_type == WAVE {
            self.under_wave = true;
        } else if box_type == ILST {
            self.ilst_meta_item = None;
            self.under_ilst = true;
        } else if self.under_ilst
            && !self.under_ilst_meta
            && metadata::is_ilst_meta_box_type(box_type)
        {
            self.ilst_meta_item = Some(box_type);
            self.under_ilst_meta = true;
            if box_type == FREE_FORM {
                self.under_ilst_free_meta = true;
            }
        } else if box_type == UDTA {
            self.under_udta = true;
        }

        self
    }
}

/// Registry entry for a single supported box type.
#[derive(Clone, Copy)]
pub struct BoxRegistration {
    box_type: FourCc,
    supported_versions: &'static [u8],
    constructor: BoxConstructor,
}

impl BoxRegistration {
    fn new(
        box_type: FourCc,
        supported_versions: &'static [u8],
        constructor: BoxConstructor,
    ) -> Self {
        Self {
            box_type,
            supported_versions,
            constructor,
        }
    }

    /// Returns the registered four-character box type.
    pub const fn box_type(&self) -> FourCc {
        self.box_type
    }

    /// Returns the supported versions recorded for the box type.
    pub const fn supported_versions(&self) -> &'static [u8] {
        self.supported_versions
    }
}

#[derive(Clone, Copy)]
struct ContextualBoxRegistration {
    registration: BoxRegistration,
    matches: ContextPredicate,
}

impl ContextualBoxRegistration {
    const fn new(registration: BoxRegistration, matches: ContextPredicate) -> Self {
        Self {
            registration,
            matches,
        }
    }
}

#[derive(Clone, Copy)]
struct DynamicBoxRegistration {
    supported_versions: &'static [u8],
    constructor: BoxConstructor,
    matches: DynamicContextPredicate,
}

impl DynamicBoxRegistration {
    const fn new(
        supported_versions: &'static [u8],
        constructor: BoxConstructor,
        matches: DynamicContextPredicate,
    ) -> Self {
        Self {
            supported_versions,
            constructor,
            matches,
        }
    }
}

#[derive(Clone, Copy)]
struct ResolvedRegistration {
    supported_versions: &'static [u8],
    constructor: BoxConstructor,
}

impl ResolvedRegistration {
    const fn new(supported_versions: &'static [u8], constructor: BoxConstructor) -> Self {
        Self {
            supported_versions,
            constructor,
        }
    }
}

/// Registry that maps box identifiers to descriptor-backed constructors.
#[derive(Clone, Default)]
pub struct BoxRegistry {
    entries: BTreeMap<FourCc, BoxRegistration>,
    contextual_entries: BTreeMap<FourCc, Vec<ContextualBoxRegistration>>,
    dynamic_entries: Vec<DynamicBoxRegistration>,
}

impl BoxRegistry {
    /// Creates an empty registry.
    pub fn new() -> Self {
        Self::default()
    }

    /// Returns `true` when a constructor has been registered for `box_type`.
    pub fn is_registered(&self, box_type: FourCc) -> bool {
        self.is_registered_with_context(box_type, BoxLookupContext::new())
    }

    /// Returns `true` when `box_type` has an active constructor in `context`.
    pub fn is_registered_with_context(&self, box_type: FourCc, context: BoxLookupContext) -> bool {
        self.resolve_registration(box_type, context).is_some()
    }

    /// Returns the supported-version list for a registered box type.
    pub fn supported_versions(&self, box_type: FourCc) -> Option<&'static [u8]> {
        self.supported_versions_with_context(box_type, BoxLookupContext::new())
    }

    /// Returns the supported-version list for a registration active in `context`.
    pub fn supported_versions_with_context(
        &self,
        box_type: FourCc,
        context: BoxLookupContext,
    ) -> Option<&'static [u8]> {
        self.resolve_registration(box_type, context)
            .map(|registration| registration.supported_versions)
    }

    /// Returns `true` when `version` is accepted for the registered box type.
    pub fn is_supported_version(&self, box_type: FourCc, version: u8) -> bool {
        self.is_supported_version_with_context(box_type, version, BoxLookupContext::new())
    }

    /// Returns `true` when `version` is accepted for the registration active in `context`.
    pub fn is_supported_version_with_context(
        &self,
        box_type: FourCc,
        version: u8,
        context: BoxLookupContext,
    ) -> bool {
        let Some(supported_versions) = self.supported_versions_with_context(box_type, context)
        else {
            return false;
        };

        supported_versions.is_empty() || supported_versions.contains(&version)
    }

    /// Registers a fixed-type box whose runtime `FourCc` always matches the type parameter.
    pub fn register<T>(&mut self, box_type: FourCc) -> Option<BoxRegistration>
    where
        T: CodecBox + Default + 'static,
    {
        self.insert(BoxRegistration::new(
            box_type,
            T::SUPPORTED_VERSIONS,
            construct_fixed::<T>,
        ))
    }

    /// Registers a box whose runtime `FourCc` must be injected into the constructed value.
    pub fn register_any<T>(&mut self, box_type: FourCc) -> Option<BoxRegistration>
    where
        T: CodecBox + AnyTypeBox + Default + 'static,
    {
        self.insert(BoxRegistration::new(
            box_type,
            T::SUPPORTED_VERSIONS,
            construct_any::<T>,
        ))
    }

    /// Registers a fixed-type box that is only active when `matches(context)` returns `true`.
    pub fn register_contextual<T>(
        &mut self,
        box_type: FourCc,
        matches: fn(BoxLookupContext) -> bool,
    ) where
        T: CodecBox + Default + 'static,
    {
        let registration = ContextualBoxRegistration::new(
            BoxRegistration::new(box_type, T::SUPPORTED_VERSIONS, construct_fixed::<T>),
            matches,
        );
        self.contextual_entries
            .entry(box_type)
            .or_default()
            .push(registration);
    }

    /// Registers an any-type box that is only active when `matches(context)` returns `true`.
    pub fn register_contextual_any<T>(
        &mut self,
        box_type: FourCc,
        matches: fn(BoxLookupContext) -> bool,
    ) where
        T: CodecBox + AnyTypeBox + Default + 'static,
    {
        let registration = ContextualBoxRegistration::new(
            BoxRegistration::new(box_type, T::SUPPORTED_VERSIONS, construct_any::<T>),
            matches,
        );
        self.contextual_entries
            .entry(box_type)
            .or_default()
            .push(registration);
    }

    pub(crate) fn register_dynamic_any<T>(&mut self, matches: DynamicContextPredicate)
    where
        T: CodecBox + AnyTypeBox + Default + 'static,
    {
        self.dynamic_entries.push(DynamicBoxRegistration::new(
            T::SUPPORTED_VERSIONS,
            construct_any::<T>,
            matches,
        ));
    }

    /// Creates a new descriptor-backed box instance for `box_type`.
    pub fn new_box(&self, box_type: FourCc) -> Option<Box<dyn DynCodecBox>> {
        self.new_box_with_context(box_type, BoxLookupContext::new())
    }

    /// Creates a new descriptor-backed box instance for the registration active in `context`.
    pub fn new_box_with_context(
        &self,
        box_type: FourCc,
        context: BoxLookupContext,
    ) -> Option<Box<dyn DynCodecBox>> {
        self.resolve_registration(box_type, context)
            .map(|registration| (registration.constructor)(box_type))
    }

    fn insert(&mut self, registration: BoxRegistration) -> Option<BoxRegistration> {
        self.entries.insert(registration.box_type, registration)
    }

    fn resolve_registration(
        &self,
        box_type: FourCc,
        context: BoxLookupContext,
    ) -> Option<ResolvedRegistration> {
        if let Some(registration) = self.entries.get(&box_type) {
            return Some(ResolvedRegistration::new(
                registration.supported_versions(),
                registration.constructor,
            ));
        }

        if let Some(registrations) = self.contextual_entries.get(&box_type)
            && let Some(registration) = registrations
                .iter()
                .find(|registration| (registration.matches)(context))
        {
            return Some(ResolvedRegistration::new(
                registration.registration.supported_versions(),
                registration.registration.constructor,
            ));
        }

        self.dynamic_entries
            .iter()
            .find(|registration| (registration.matches)(box_type, context))
            .map(|registration| {
                ResolvedRegistration::new(registration.supported_versions, registration.constructor)
            })
    }
}

/// Builds the built-in registry for the currently landed box families.
pub fn default_registry() -> BoxRegistry {
    let mut registry = BoxRegistry::new();
    iso14496_12::register_boxes(&mut registry);
    iso14496_14::register_boxes(&mut registry);
    iso14496_30::register_boxes(&mut registry);
    isma_cryp::register_boxes(&mut registry);
    marlin::register_boxes(&mut registry);
    metadata::register_boxes(&mut registry);
    oma_dcf::register_boxes(&mut registry);
    threegpp::register_boxes(&mut registry);
    av1::register_boxes(&mut registry);
    avs3::register_boxes(&mut registry);
    dolby::register_boxes(&mut registry);
    etsi_ts_102_366::register_boxes(&mut registry);
    etsi_ts_103_190::register_boxes(&mut registry);
    flac::register_boxes(&mut registry);
    mpeg_h::register_boxes(&mut registry);
    opus::register_boxes(&mut registry);
    vp::register_boxes(&mut registry);
    iso14496_15::register_boxes(&mut registry);
    iso23001_5::register_boxes(&mut registry);
    iso23001_7::register_boxes(&mut registry);
    registry
}

fn construct_fixed<T>(_box_type: FourCc) -> Box<dyn DynCodecBox>
where
    T: CodecBox + Default + 'static,
{
    Box::new(T::default())
}

fn construct_any<T>(box_type: FourCc) -> Box<dyn DynCodecBox>
where
    T: CodecBox + AnyTypeBox + Default + 'static,
{
    let mut boxed = T::default();
    boxed.set_box_type(box_type);
    Box::new(boxed)
}