diff --git a/CHANGELOG.md b/CHANGELOG.md
index b0f8df48..c2df080b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,12 +9,21 @@ and this project adheres to
## [Unreleased]
- FairPlay Utilities [#131](https://github.com/streaming-video-technology-alliance/common-media-library/issues/131)
+## [0.9.0] - 2025-02-21
+
+### Added
+
+- ISO BMFF parser and utilities
+ ([#118](https://github.com/streaming-video-technology-alliance/common-media-library/issues/118))
+
+## [0.8.0] - 2025-02-14
+
### Added
- Add DASH Segment Template Utility
([#129](https://github.com/streaming-video-technology-alliance/common-media-library/issues/129))
- Add XML parsing utils
- [#125](https://github.com/streaming-video-technology-alliance/common-media-library/issues/125)
+ ([#125](https://github.com/streaming-video-technology-alliance/common-media-library/issues/125))
### Changed
@@ -260,7 +269,9 @@ and this project adheres to
- Bootstrap project
([#2](https://github.com/streaming-video-technology-alliance/common-media-library/issues/2))
-[Unreleased]: https://github.com/streaming-video-technology-alliance/common-media-library/compare/v0.7.4...HEAD
+[Unreleased]: https://github.com/streaming-video-technology-alliance/common-media-library/compare/v0.9.0...HEAD
+[0.9.0]: https://github.com/streaming-video-technology-alliance/common-media-library/compare/v0.8.0...v0.9.0
+[0.8.0]: https://github.com/streaming-video-technology-alliance/common-media-library/compare/v0.7.4...v0.8.0
[0.7.4]: https://github.com/streaming-video-technology-alliance/common-media-library/compare/v0.7.3...v0.7.4
[0.7.3]: https://github.com/streaming-video-technology-alliance/common-media-library/compare/v0.7.2...v0.7.3
[0.7.2]: https://github.com/streaming-video-technology-alliance/common-media-library/compare/v0.7.1...v0.7.2
diff --git a/dev/isobmff.html b/dev/isobmff.html
new file mode 100644
index 00000000..5e68650d
--- /dev/null
+++ b/dev/isobmff.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dev/package.json b/dev/package.json
index 8986cd4d..ae823e3f 100644
--- a/dev/package.json
+++ b/dev/package.json
@@ -1,7 +1,7 @@
{
"name": "@svta/common-media-library-dev",
"private": true,
- "version": "0.7.4",
+ "version": "0.9.0",
"license": "Apache-2.0",
"homepage": "https://github.com/streaming-video-technology-alliance/common-media-library",
"authors": "Casey Occhialini <1508707+littlespex@users.noreply.github.com>",
diff --git a/docs/package.json b/docs/package.json
index 15525285..82383a3d 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -1,7 +1,7 @@
{
"name": "@svta/common-media-library-docs",
"private": true,
- "version": "0.7.4",
+ "version": "0.9.0",
"license": "Apache-2.0",
"homepage": "https://github.com/streaming-video-technology-alliance/common-media-library",
"authors": "Casey Occhialini <1508707+littlespex@users.noreply.github.com>",
diff --git a/jsconfig.json b/jsconfig.json
index e6635787..690d1c1f 100644
--- a/jsconfig.json
+++ b/jsconfig.json
@@ -9,7 +9,7 @@
"exclude": [
"**/node_modules",
"**/dist",
- "**/samples"
+ "**/samples"
],
"include": [
"**/*.js"
diff --git a/lib/NOTICE b/lib/NOTICE
index 90c691f5..c3a33b65 100644
--- a/lib/NOTICE
+++ b/lib/NOTICE
@@ -449,3 +449,35 @@ SOFTWARE.
```
---
+
+The following implementation in this project is derived from the
+`codem-isoboxer` library (https://github.com/Dash-Industry-Forum/codem-isoboxer)
+
+- src/iso/bmff/*
+
+```
+Copyright (c) 2015 Hiro, Sjoerd Tieleman
+
+http://madebyhiro.com/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+```
+
+---
diff --git a/lib/config/common-media-library.api.md b/lib/config/common-media-library.api.md
index c2db0a9e..856af044 100644
--- a/lib/config/common-media-library.api.md
+++ b/lib/config/common-media-library.api.md
@@ -4,6 +4,37 @@
```ts
+// @alpha
+export type AdaptationSet = {
+ $: {
+ audioSamplingRate?: string;
+ codecs?: string;
+ contentType?: string;
+ frameRate?: string;
+ group?: string;
+ id?: string;
+ lang?: string;
+ maxBandwidth?: string;
+ maxFrameRate?: string;
+ maxHeight?: string;
+ maxWidth?: string;
+ mimeType?: string;
+ minBandwidth?: string;
+ par?: string;
+ sar?: string;
+ segmentAlignment: string;
+ startWithSAP?: string;
+ subsegmentAlignment?: string;
+ subsegmentStartsWithSAP?: string;
+ };
+ AudioChannelConfiguration?: AudioChannelConfiguration[];
+ ContentComponent?: ContentComponent[];
+ Role?: Role[];
+ Representation: Representation[];
+ SegmentTemplate?: SegmentTemplate[];
+ SegmentList?: SegmentList[];
+};
+
// @alpha
export type AlignedSwitchingSet = {
switchingSets: SwitchingSet[];
@@ -15,18 +46,81 @@ export function appendCmcdHeaders(headers: Record, cmcd: Cmcd, o
// @beta
export function appendCmcdQuery(url: string, cmcd: Cmcd, options?: CmcdEncodeOptions): string;
+// @beta
+export function ardi(view: IsoView): AudioRenderingIndicationBox;
+
+// @alpha
+export type AudioChannelConfiguration = {
+ $: {
+ schemeIdUri: string;
+ value: string;
+ };
+};
+
+// @beta
+export type AudioRenderingIndicationBox = FullBox & {
+ audioRenderingIndication: number;
+};
+
+// @beta
+export type AudioSampleEntry = SampleEntry & {
+ reserved2: number[];
+ channelcount: number;
+ samplesize: number;
+ preDefined: number;
+ reserved3: number;
+ samplerate: number;
+ esds: Uint8Array;
+};
+
// @alpha
export type AudioTrack = Track & {
sampleRate: number;
channels: number;
};
+// @beta
+export function avc1(view: IsoView): VisualSampleEntry;
+
+// @beta
+export const avc2: BoxParser;
+
+// @beta
+export const avc3: BoxParser;
+
+// @beta
+export const avc4: BoxParser;
+
// @beta
export function base64decode(str: string): Uint8Array;
// @beta
export function base64encode(binary: Uint8Array): string;
+// @beta
+export type Box = T & {
+ type: string;
+ size: number;
+ largesize?: number;
+ usertype?: number[];
+ boxes?: Box[];
+};
+
+// @beta
+export type BoxFilter = (box: Box) => boolean;
+
+// @beta
+export type BoxParser = (view: IsoView, config?: IsoViewConfig) => V;
+
+// @beta
+export type BoxParserMap = Record;
+
+// @alpha
+export type Byterange = {
+ length: number;
+ offset: number;
+};
+
// @beta
export function canParseId3(data: Uint8Array, offset: number): boolean;
@@ -316,6 +410,29 @@ export type CommonMediaResponse = {
resourceTiming: ResourceTiming;
};
+// @beta
+export type CompositionTimeToSampleBox = FullBox & {
+ entryCount: number;
+ entries: CompositionTimeToSampleEntry[];
+};
+
+// @beta
+export type CompositionTimeToSampleEntry = {
+ sampleCount: number;
+ sampleOffset: number;
+};
+
+// @alpha (undocumented)
+export type ContentComponent = {
+ $: {
+ contentType: string;
+ id: string;
+ };
+};
+
+// @beta
+export function createIsoView(raw: IsoData, config?: IsoViewConfig): IsoView;
+
// @beta
export class Cta608Channel {
constructor(channelNumber: number, outputFilter: CueHandler, logger?: CaptionsLogger);
@@ -375,6 +492,9 @@ export class Cta608Parser {
reset(): void;
}
+// @beta
+export function ctts(view: IsoView): CompositionTimeToSampleBox;
+
// @beta
export type CueHandler = {
newCue(startTime: number, endTime: number, screen: CaptionScreen): void;
@@ -400,6 +520,15 @@ export type DashManifest = {
// @alpha
export function dashToHam(manifest: string): Presentation[];
+// @beta
+export const DATA = "data";
+
+// @beta
+export type DataReferenceBox = FullBox & {
+ entryCount: number;
+ entries: Box[];
+};
+
// @beta
export function decodeCmcd(cmcd: string): Cmcd;
@@ -428,6 +557,47 @@ export function decodeSfItem(input: string, options?: SfDecodeOptions): SfItem;
// @beta
export function decodeSfList(input: string, options?: SfDecodeOptions): SfMember[];
+// @beta
+export type DecodingTimeSample = {
+ sampleCount: number;
+ sampleDelta: number;
+};
+
+// @beta
+export type DecodingTimeToSampleBox = FullBox & {
+ entryCount: number;
+ entries: DecodingTimeSample[];
+};
+
+// @beta
+export function dref(view: IsoView): DataReferenceBox;
+
+// @beta
+export type EditListBox = FullBox & {
+ entryCount: number;
+ entries: EditListEntry[];
+};
+
+// @beta
+export type EditListEntry = {
+ segmentDuration: number;
+ mediaTime: number;
+ mediaRateInteger: number;
+ mediaRateFraction: number;
+};
+
+// @beta
+export function elng(view: IsoView): ExtendedLanguageBox;
+
+// @beta
+export function elst(view: IsoView): EditListBox;
+
+// @beta
+export function emsg(view: IsoView): EventMessageBox;
+
+// @beta
+export const enca: BoxParser;
+
// @beta
export function encodeCmcd(cmcd: Cmcd, options?: CmcdEncodeOptions): string;
@@ -455,9 +625,49 @@ export function encodeSfItem(value: SfBareItem, params?: SfParameters): string;
// @beta
export function encodeSfList(value: SfMember[], options?: SfEncodeOptions): string;
+// @beta
+export const encv: BoxParser;
+
+// @beta
+export type Entity = {
+ entityId: number;
+};
+
+// @beta
+export type EventMessageBox = FullBox & {
+ schemeIdUri: string;
+ value: string;
+ timescale: number;
+ presentationTime: number;
+ presentationTimeDelta: number;
+ eventDuration: number;
+ id: number;
+ messageData: Uint8Array;
+};
+
+// @beta
+export type ExtendedLanguageBox = FullBox & {
+ extendedLanguage: string;
+};
+
// @beta
export function extractCta608Data(raw: DataView, cta608Range: Array): Array>;
+// @beta
+export type FileTypeBox = TypeBox;
+
+// @beta
+export function filterBoxes(raw: IsoData, config: IsoViewConfig, fn: BoxFilter): Box[];
+
+// @beta
+export function filterBoxesByType(type: string, raw: IsoData, config?: IsoViewConfig): Box[];
+
+// @beta
+export function findBox(raw: IsoData, config: IsoViewConfig, fn: BoxFilter): Box | null;
+
+// @beta
+export function findBoxByType(type: string, raw: IsoData, config?: IsoViewConfig): Box | null;
+
// @beta
export function findCta608Nalus(raw: DataView, startPos: number, size: number): Array>;
@@ -467,12 +677,32 @@ export type FrameRate = {
frameRateDenominator?: number;
};
+// @beta
+export function free(view: IsoView): FreeSpaceBox;
+
+// @beta
+export type FreeSpaceBox = {
+ data: Uint8Array;
+};
+
+// @beta
+export function frma(view: IsoView): OriginalFormatBox;
+
// @beta
export function fromCmcdHeaders(headers: Record | Headers): Cmcd;
// @beta
export function fromCmcdQuery(query: string | URLSearchParams): Cmcd;
+// @beta
+export function ftyp(view: IsoView): FileTypeBox;
+
+// @beta
+export type FullBox = {
+ version: number;
+ flags: number;
+};
+
// Warning: (ae-internal-missing-underscore) The name "getId3Data" should be prefixed with an underscore because the declaration is marked as @internal
//
// @internal
@@ -504,6 +734,20 @@ export function hamToDash(presentation: Presentation[]): Manifest;
// @alpha
export function hamToHls(presentation: Presentation[]): Manifest;
+// @beta
+export type HandlerReferenceBox = {
+ preDefined: number;
+ handlerType: string;
+ reserved: number[];
+ name: string;
+};
+
+// @beta
+export function hdlr(view: IsoView): HandlerReferenceBox;
+
+// @beta
+export const hev1: BoxParser;
+
// @alpha
export type HlsManifest = {
playlists: PlayList[];
@@ -515,14 +759,108 @@ export type HlsManifest = {
// @alpha
export function hlsToHam(manifest: string, ancillaryManifests: string[]): Presentation[];
+// @beta
+export function hvc1(view: IsoView): VisualSampleEntry;
+
// @beta
export type Id3Frame = DecodedId3Frame;
+// @beta
+export type IdentifiedMediaDataBox = {
+ imdaIdentifier: number;
+ data: Uint8Array;
+};
+
+// @beta
+export function imda(view: IsoView): IdentifiedMediaDataBox;
+
+// @alpha
+export type Initialization = {
+ $: {
+ range?: string;
+ sourceURL?: string;
+ };
+};
+
+// @beta
+export const INT = "int";
+
// Warning: (ae-internal-missing-underscore) The name "isId3TimestampFrame" should be prefixed with an underscore because the declaration is marked as @internal
//
// @internal
export function isId3TimestampFrame(frame: Id3Frame): boolean;
+// @beta
+export type IsoData = ArrayBuffer | DataView | IsoView | Uint8Array;
+
+// @beta
+export type ISOFieldTypeMap = {
+ uint: number;
+ int: number;
+ template: number;
+ string: string;
+ data: Uint8Array;
+ utf8: string;
+ utf8string: string;
+};
+
+// @beta
+export class IsoView {
+ // (undocumented)
+ [Symbol.iterator](): Generator;
+ constructor(raw: ArrayBuffer | DataView | Uint8Array, config?: IsoViewConfig);
+ // (undocumented)
+ get bytesRemaining(): number;
+ // (undocumented)
+ get cursor(): number;
+ // (undocumented)
+ get done(): boolean;
+ // (undocumented)
+ readArray: (type: T, size: number, length: number) => ISOFieldTypeMap[T][];
+ // (undocumented)
+ readBox: () => RawBox;
+ // (undocumented)
+ readBoxes: (length: number) => Box[];
+ // (undocumented)
+ readData: (size: number) => Uint8Array;
+ // (undocumented)
+ readEntries: (length: number, map: () => T) => T[];
+ // (undocumented)
+ readFullBox: () => FullBox;
+ // (undocumented)
+ readInt: (size: number) => number;
+ // (undocumented)
+ readString: (size: number) => string;
+ // (undocumented)
+ readTemplate: (size: number) => number;
+ // (undocumented)
+ readUint: (size: number) => number;
+ // (undocumented)
+ readUtf8: (size?: number) => string;
+ // (undocumented)
+ slice: (size: number) => IsoView;
+}
+
+// @beta
+export type IsoViewConfig = {
+ parsers?: BoxParserMap;
+ recursive?: boolean;
+};
+
+// @beta
+export function kind(view: IsoView): TrackKindBox;
+
+// @beta
+export type LabelBox = FullBox & {
+ isGroupLabel: boolean;
+ labelId: number;
+ language: string;
+ label: string;
+};
+
+// @beta
+export function labl(view: IsoView): LabelBox;
+
// @alpha
export type Manifest = {
manifest: string;
@@ -535,6 +873,103 @@ export type Manifest = {
// @alpha
export type ManifestFormat = 'hls' | 'dash';
+// @beta
+export function mdat(view: IsoView): MediaDataBox;
+
+// @beta
+export function mdhd(view: IsoView): MediaHeaderBox;
+
+// @beta
+export type MediaDataBox = {
+ data: Uint8Array;
+};
+
+// @alpha
+export type MediaGroups = {
+ AUDIO: {
+ [key: string]: {
+ [key: string]: {
+ language: string;
+ };
+ };
+ };
+ SUBTITLES: {
+ [key: string]: {
+ [key: string]: {
+ language: string;
+ };
+ };
+ };
+};
+
+// @beta
+export type MediaHeaderBox = FullBox & {
+ creationTime: number;
+ modificationTime: number;
+ timescale: number;
+ duration: number;
+ language: string;
+ preDefined: number;
+};
+
+// @beta
+export function mehd(view: IsoView): MovieExtendsHeaderBox;
+
+// @beta
+export function meta(view: IsoView): MetaBox;
+
+// @beta
+export type MetaBox = FullBox;
+
+// @beta
+export function mfhd(view: IsoView): MovieFragmentHeaderBox;
+
+// @beta
+export function mfro(view: IsoView): MovieFragmentRandomAccessBox;
+
+// @beta
+export type MovieExtendsHeaderBox = FullBox & {
+ fragmentDuration: number;
+};
+
+// @beta
+export type MovieFragmentHeaderBox = FullBox & {
+ sequenceNumber: number;
+};
+
+// @beta
+export type MovieFragmentRandomAccessBox = FullBox & {
+ mfra_size: number;
+};
+
+// @beta
+export type MovieHeaderBox = {
+ version: number;
+ flags: number;
+ creationTime: number;
+ modificationTime: number;
+ timescale: number;
+ duration: number;
+ rate: number;
+ volume: number;
+ reserved1: number;
+ reserved2: number[];
+ matrix: number[];
+ preDefined: number[];
+ nextTrackId: number;
+};
+
+// @beta
+export function mp4a(view: IsoView): AudioSampleEntry;
+
+// @beta
+export function mvhd(view: IsoView): MovieHeaderBox;
+
+// @beta
+export type OriginalFormatBox = {
+ dataFormat: number;
+};
+
// @beta
export type PACData = {
row: number;
@@ -544,9 +979,15 @@ export type PACData = {
italics: boolean;
};
+// @beta
+export function parseBoxes(raw: IsoData, config?: IsoViewConfig): Box[];
+
// @beta
export function parseXml(input: string, options?: XmlParseOptions): XmlNode;
+// @beta
+export function payl(view: IsoView): WebVTTCuePayloadBox;
+
// @beta
export class PenState {
// (undocumented)
@@ -580,14 +1021,122 @@ export type PenStyles = {
flash: boolean;
};
+// @alpha
+export type Period = {
+ $: {
+ duration: string;
+ id?: string;
+ start?: string;
+ };
+ AdaptationSet: AdaptationSet[];
+};
+
+// @alpha
+export type PlayList = {
+ uri: string;
+ attributes: {
+ FRAME_RATE: number;
+ CODECS: string;
+ BANDWIDTH: number;
+ RESOLUTION: {
+ width: number;
+ height: number;
+ };
+ };
+};
+
+// @beta
+export type PreselectionGroupBox = FullBox & {
+ groupId: number;
+ numEntitiesInGroup: number;
+ entities: Entity[];
+ preselectionTag?: string;
+ selectionPriority?: number;
+ interleavingTag?: string;
+};
+
// @alpha
export type Presentation = Ham & {
selectionSets: SelectionSet[];
};
+// @beta
+export function prft(view: IsoView): ProducerReferenceTimeBox;
+
// @beta
export function processUriTemplate(uriTemplate: string, representationId: string | null | undefined, number: number | null | undefined, subNumber: number | null | undefined, bandwidth: number | null | undefined, time: number | null | undefined): string;
+// @beta
+export type ProducerReferenceTimeBox = FullBox & {
+ referenceTrackId: number;
+ ntpTimestampSec: number;
+ ntpTimestampFrac: number;
+ mediaTime: number;
+};
+
+// @beta
+export type ProtectionSystemSpecificHeaderBox = FullBox & {
+ systemID: number[];
+ dataSize: number;
+ data: number[];
+};
+
+// @beta
+export function prsl(view: IsoView): PreselectionGroupBox;
+
+// @beta
+export function pssh(view: IsoView): ProtectionSystemSpecificHeaderBox;
+
+// @beta
+type Range_2 = {
+ level: number;
+ rangeSize: number;
+};
+export { Range_2 as Range }
+
+// @beta
+export type RawBox = {
+ type: string;
+ size: number;
+ largesize?: number;
+ usertype?: number[];
+ data: IsoView;
+};
+
+// @beta
+export type Reference = {
+ reference: number;
+ subsegmentDuration: number;
+ sap: number;
+ referenceType: number;
+ referencedSize: number;
+ startsWithSap: number;
+ sapType: number;
+ sapDeltaTime: number;
+};
+
+// @alpha
+export type Representation = {
+ $: {
+ audioSamplingRate?: string;
+ bandwidth: string;
+ codecs?: string;
+ frameRate?: string;
+ height?: string;
+ id: string;
+ mimeType?: string;
+ sar?: string;
+ scanType?: string;
+ startWithSAP?: string;
+ width?: string;
+ };
+ AudioChannelConfiguration?: AudioChannelConfiguration[];
+ BaseURL?: string[];
+ SegmentBase?: SegmentBase[];
+ SegmentList?: SegmentList[];
+ SegmentTemplate?: SegmentTemplate[];
+};
+
// @beta
export type RequestInterceptor = (request: CommonMediaRequest) => Promise;
@@ -603,6 +1152,14 @@ export type ResourceTiming = {
// @beta
export type ResponseInterceptor = (response: CommonMediaResponse) => Promise;
+// @alpha
+export type Role = {
+ $: {
+ schemeIdUri: string;
+ value: string;
+ };
+};
+
// @beta
export function roundToEven(value: number, precision: number): number;
@@ -636,6 +1193,23 @@ export class Row {
setPenStyles(styles: Partial): void;
}
+// @beta
+export type SampleDependencyTypeBox = FullBox & {
+ sampleDependencyTable: number[];
+};
+
+// @beta
+export type SampleDescriptionBox = FullBox & {
+ entryCount: number;
+ entries: any[];
+};
+
+// @beta
+export type SampleEntry = {
+ reserved1: number[];
+ dataReferenceIndex: number;
+};
+
// @beta
export class SccParser {
constructor(processor: any, field?: number | any);
@@ -661,6 +1235,19 @@ export class SccParser {
timeConverter(smpteTs: string): number;
}
+// @beta
+export type SchemeTypeBox = FullBox & {
+ schemeType: number;
+ schemeVersion: number;
+ schemeUri?: string;
+};
+
+// @beta
+export function schm(view: IsoView): SchemeTypeBox;
+
+// @beta
+export function sdtp(view: IsoView): SampleDependencyTypeBox;
+
// @alpha
export type Segment = {
duration: number;
@@ -668,6 +1255,70 @@ export type Segment = {
byteRange?: string;
};
+// @alpha
+export type SegmentBase = {
+ $: {
+ indexRange: string;
+ indexRangeExact: string;
+ timescale: string;
+ };
+ Initialization: Initialization[];
+};
+
+// @alpha
+export type SegmentHls = {
+ title?: string;
+ duration: number;
+ byterange?: Byterange;
+ uri?: string;
+ timeline?: number;
+ map?: {
+ uri: string;
+ byterange: Byterange;
+ };
+};
+
+// @beta
+export type SegmentIndexBox = FullBox & {
+ referenceId: number;
+ timescale: number;
+ earliestPresentationTime: number;
+ firstOffset: number;
+ reserved: number;
+ references: Reference[];
+};
+
+// @alpha
+export type SegmentList = {
+ $: {
+ duration: string;
+ timescale: string;
+ };
+ Initialization: Initialization[];
+ SegmentURL?: SegmentURL[];
+};
+
+// @alpha
+export type SegmentTemplate = {
+ $: {
+ duration: string;
+ initialization: string;
+ media: string;
+ startNumber: string;
+ timescale: string;
+ };
+};
+
+// @beta
+export type SegmentTypeBox = TypeBox;
+
+// @alpha
+export type SegmentURL = {
+ $: {
+ media?: string;
+ };
+};
+
// @alpha
export type SelectionSet = Ham & {
switchingSets: SwitchingSet[];
@@ -736,6 +1387,42 @@ export class SfToken {
description: string;
}
+// @beta
+export function sidx(view: IsoView): SegmentIndexBox;
+
+// @beta
+export const skip: BoxParser;
+
+// @beta
+export function smhd(view: IsoView): SoundMediaHeaderBox;
+
+// @beta
+export type SoundMediaHeaderBox = FullBox & {
+ balance: number;
+ reserved: number;
+};
+
+// @beta
+export function ssix(view: IsoView): SubsegmentIndexBox;
+
+// @beta
+export function sthd(view: IsoView): SubtitleMediaHeaderBox;
+
+// @beta
+export const STRING = "string";
+
+// @beta
+export function stsd(view: IsoView): SampleDescriptionBox;
+
+// @beta
+export function stss(view: IsoView): SyncSampleBox;
+
+// @beta
+export function sttg(view: IsoView): WebVTTSettingsBox;
+
+// @beta
+export function stts(view: IsoView): DecodingTimeToSampleBox;
+
// @beta
export class StyledUnicodeChar {
// (undocumented)
@@ -756,6 +1443,48 @@ export class StyledUnicodeChar {
uchar: string;
}
+// @beta
+export const styp: BoxParser;
+
+// @beta
+export function subs(view: IsoView): SubSampleInformationBox;
+
+// @beta
+export type SubSample = {
+ subsampleSize: number;
+ subsamplePriority: number;
+ discardable: number;
+ codecSpecificParameters: number;
+};
+
+// @beta
+export type SubSampleEntry = {
+ sampleDelta: number;
+ subsampleCount: number;
+ subsamples: SubSample[];
+};
+
+// @beta
+export type SubSampleInformationBox = FullBox & {
+ entryCount: number;
+ entries: SubSampleEntry[];
+};
+
+// @beta
+export type Subsegment = {
+ rangesCount: number;
+ ranges: Range_2[];
+};
+
+// @beta
+export type SubsegmentIndexBox = FullBox & {
+ subsegmentCount: number;
+ subsegments: Subsegment[];
+};
+
+// @beta
+export type SubtitleMediaHeaderBox = FullBox;
+
// @beta
export type SupportedField = 1 | 3;
@@ -764,10 +1493,39 @@ export type SwitchingSet = Ham & {
tracks: Track[];
};
+// @beta
+export type SyncSample = {
+ sampleNumber: number;
+};
+
+// @beta
+export type SyncSampleBox = FullBox & {
+ entryCount: number;
+ entries: SyncSample[];
+};
+
+// @beta
+export const TEMPLATE = "template";
+
+// @beta
+export function tenc(view: IsoView): TrackEncryptionBox;
+
// @alpha
type TextTrack_2 = Track;
export { TextTrack_2 as TextTrack }
+// @beta
+export function tfdt(view: IsoView): TrackFragmentDecodeTimeBox;
+
+// @beta
+export function tfhd(view: IsoView): TrackFragmentHeaderBox;
+
+// @beta
+export function tfra(view: IsoView): TrackFragmentRandomAccessBox;
+
+// @beta
+export function tkhd(view: IsoView): TrackHeaderBox;
+
// @beta
export function toCmcdHeaders(cmcd: Cmcd, options?: CmcdEncodeOptions): Record;
@@ -790,15 +1548,141 @@ export type Track = Ham & {
segments: Segment[];
};
+// @beta
+export type TrackEncryptionBox = FullBox & {
+ defaultIsEncrypted: number;
+ defaultIvSize: number;
+ defaultKid: number[];
+};
+
+// @beta
+export type TrackExtendsBox = FullBox & {
+ trackId: number;
+ defaultSampleDescriptionIndex: number;
+ defaultSampleDuration: number;
+ defaultSampleSize: number;
+ defaultSampleFlags: number;
+};
+
+// @beta
+export type TrackFragmentDecodeTimeBox = FullBox & {
+ baseMediaDecodeTime: number;
+};
+
+// @beta
+export type TrackFragmentHeaderBox = FullBox & {
+ trackId: number;
+ baseDataOffset?: number;
+ sampleDescriptionOffset?: number;
+ defaultSampleDuration?: number;
+ defaultSampleSize?: number;
+ defaultSampleFlags?: number;
+};
+
+// @beta
+export type TrackFragmentRandomAccessBox = FullBox & {
+ trackId: number;
+ reserved: number;
+ numberOfEntry: number;
+ lengthSizeOfTrafNum: number;
+ lengthSizeOfTrunNum: number;
+ lengthSizeOfSampleNum: number;
+ entries: TrackFragmentRandomAccessEntry[];
+};
+
+// @beta
+export type TrackFragmentRandomAccessEntry = {
+ time: number;
+ moofOffset: number;
+ trafNumber: number;
+ trunNumber: number;
+ sampleNumber: number;
+};
+
+// @beta
+export type TrackHeaderBox = FullBox & {
+ creationTime: number;
+ modificationTime: number;
+ trackId: number;
+ reserved1: number;
+ duration: number;
+ reserved2: number[];
+ layer: number;
+ alternateGroup: number;
+ volume: number;
+ reserved3: number;
+ matrix: number[];
+ width: number;
+ height: number;
+};
+
+// @beta
+export type TrackKindBox = FullBox & {
+ schemeUri: string;
+ value: string;
+};
+
+// @beta
+export type TrackRunBox = FullBox & {
+ sampleCount: number;
+ dataOffset?: number;
+ firstSampleFlags?: number;
+ samples: TrackRunSample[];
+};
+
+// @beta
+export type TrackRunSample = {
+ sampleDuration?: number;
+ sampleSize?: number;
+ sampleFlags?: number;
+ sampleCompositionTimeOffset?: number;
+};
+
// @alpha
export type TrackType = 'audio' | 'video' | 'text';
+// @beta
+export function trex(view: IsoView): TrackExtendsBox;
+
+// @beta
+export function trun(view: IsoView): TrackRunBox;
+
+// @beta
+export type TypeBox = {
+ majorBrand: string;
+ minorVersion: number;
+ compatibleBrands: string[];
+};
+
+// @beta
+export const UINT = "uint";
+
// @beta
export function unescapeHtml(text: string): string;
+// @beta
+export function url(view: IsoView): UrlBox;
+
+// @beta
+export type UrlBox = FullBox & {
+ location: string;
+};
+
// @beta
export function urlToRelativePath(url: string, base: string): string;
+// @beta
+export function urn(view: IsoView): UrnBox;
+
+// @beta
+export type UrnBox = FullBox & {
+ name: string;
+ location: string;
+};
+
+// @beta
+export const UTF8 = "utf8";
+
// @beta
export function utf8ArrayToStr(array: Uint8Array, exitOnNull?: boolean): string;
@@ -851,6 +1735,12 @@ export const VerboseLevel: {
// @beta (undocumented)
export type VerboseLevel = ValueOf;
+// @beta
+export type VideoMediaHeaderBox = FullBox & {
+ graphicsmode: number;
+ opcolor: number[];
+};
+
// @alpha
export type VideoTrack = Track & {
width: number;
@@ -861,6 +1751,58 @@ export type VideoTrack = Track & {
scanType: string;
};
+// @beta
+export type VisualSampleEntry = SampleEntry & {
+ preDefined1: number;
+ reserved2: number;
+ preDefined2: number[];
+ width: number;
+ height: number;
+ horizresolution: number;
+ vertresolution: number;
+ reserved3: number;
+ frameCount: number;
+ compressorName: number[];
+ depth: number;
+ preDefined3: number;
+ config: Uint8Array;
+};
+
+// @beta
+export function vlab(view: IsoView): WebVTTSourceLabelBox;
+
+// @beta
+export function vmhd(view: IsoView): VideoMediaHeaderBox;
+
+// @beta
+export function vttC(view: IsoView): WebVTTConfigurationBox;
+
+// @beta
+export function vtte(): WebVTTEmptySampleBox;
+
+// @beta
+export type WebVTTConfigurationBox = {
+ config: string;
+};
+
+// @beta
+export type WebVTTCuePayloadBox = {
+ cueText: string;
+};
+
+// @beta
+export type WebVTTEmptySampleBox = object;
+
+// @beta
+export type WebVTTSettingsBox = {
+ settings: string;
+};
+
+// @beta
+export type WebVTTSourceLabelBox = {
+ sourceLabel: string;
+};
+
// @beta
export type XmlNode = {
nodeName: string;
@@ -876,11 +1818,4 @@ export type XmlParseOptions = {
keepComments?: boolean;
};
-// Warnings were encountered during analysis:
-//
-// src/cmaf/ham/types/mapper/dash/DashManifest.ts:19:3 - (ae-forgotten-export) The symbol "Period" needs to be exported by the entry point index.d.ts
-// src/cmaf/ham/types/mapper/hls/HlsManifest.ts:12:2 - (ae-forgotten-export) The symbol "PlayList" needs to be exported by the entry point index.d.ts
-// src/cmaf/ham/types/mapper/hls/HlsManifest.ts:13:2 - (ae-forgotten-export) The symbol "MediaGroups" needs to be exported by the entry point index.d.ts
-// src/cmaf/ham/types/mapper/hls/HlsManifest.ts:14:2 - (ae-forgotten-export) The symbol "SegmentHls" needs to be exported by the entry point index.d.ts
-
```
diff --git a/lib/package.json b/lib/package.json
index 36d681cd..04333315 100644
--- a/lib/package.json
+++ b/lib/package.json
@@ -1,6 +1,6 @@
{
"name": "@svta/common-media-library",
- "version": "0.7.4",
+ "version": "0.9.0",
"license": "Apache-2.0",
"homepage": "https://github.com/streaming-video-technology-alliance/common-media-library",
"authors": "Casey Occhialini <1508707+littlespex@users.noreply.github.com>",
diff --git a/lib/src/cmaf-ham.ts b/lib/src/cmaf-ham.ts
index f3dd0772..4280bb80 100644
--- a/lib/src/cmaf-ham.ts
+++ b/lib/src/cmaf-ham.ts
@@ -20,8 +20,23 @@ export type { VideoTrack } from './cmaf/ham/types/model/VideoTrack.js';
export type { Manifest } from './cmaf/ham/types/manifest/Manifest.js';
export type { ManifestFormat } from './cmaf/ham/types/manifest/ManifestFormat.js';
+export type { AdaptationSet } from './cmaf/ham/types/mapper/dash/AdaptationSet.js';
+export type { AudioChannelConfiguration } from './cmaf/ham/types/mapper/dash/AudioChannelConfiguration.js';
+export type { ContentComponent } from './cmaf/ham/types/mapper/dash/ContentComponent.js';
export type { DashManifest } from './cmaf/ham/types/mapper/dash/DashManifest.js';
+export type { Initialization } from './cmaf/ham/types/mapper/dash/Initialization.js';
+export type { Period } from './cmaf/ham/types/mapper/dash/Period.js';
+export type { Representation } from './cmaf/ham/types/mapper/dash/Representation.js';
+export type { Role } from './cmaf/ham/types/mapper/dash/Role.js';
+export type { SegmentBase } from './cmaf/ham/types/mapper/dash/SegmentBase.js';
+export type { SegmentList } from './cmaf/ham/types/mapper/dash/SegmentList.js';
+export type { SegmentTemplate } from './cmaf/ham/types/mapper/dash/SegmentTemplate.js';
+export type { SegmentURL } from './cmaf/ham/types/mapper/dash/SegmentUrl.js';
+export type { Byterange } from './cmaf/ham/types/mapper/hls/Byterange.js';
export type { HlsManifest } from './cmaf/ham/types/mapper/hls/HlsManifest.js';
+export type { MediaGroups } from './cmaf/ham/types/mapper/hls/MediaGroups.js';
+export type { PlayList } from './cmaf/ham/types/mapper/hls/Playlist.js';
+export type { SegmentHls } from './cmaf/ham/types/mapper/hls/SegmentHls.js';
export type { Validation } from './cmaf/ham/types/Validation.js';
export { setDashParser } from './cmaf/ham/utils/dash/parseDashManifest.js';
diff --git a/lib/src/cmaf/ham/types/mapper/dash/ContentComponent.ts b/lib/src/cmaf/ham/types/mapper/dash/ContentComponent.ts
index 546ea1bb..9fa59d9b 100644
--- a/lib/src/cmaf/ham/types/mapper/dash/ContentComponent.ts
+++ b/lib/src/cmaf/ham/types/mapper/dash/ContentComponent.ts
@@ -1,3 +1,8 @@
+/**
+ * @group CMAF
+ *
+ * @alpha
+ */
export type ContentComponent = {
$: {
contentType: string;
diff --git a/lib/src/cmaf/ham/types/mapper/dash/Role.ts b/lib/src/cmaf/ham/types/mapper/dash/Role.ts
index 972abb40..84d61307 100644
--- a/lib/src/cmaf/ham/types/mapper/dash/Role.ts
+++ b/lib/src/cmaf/ham/types/mapper/dash/Role.ts
@@ -1,3 +1,9 @@
+/**
+ * Role
+ *
+ * @group CMAF
+ * @alpha
+ */
export type Role = {
$: {
schemeIdUri: string;
diff --git a/lib/src/cmaf/ham/types/mapper/hls/Byterange.ts b/lib/src/cmaf/ham/types/mapper/hls/Byterange.ts
index 4d056336..a1b5f887 100644
--- a/lib/src/cmaf/ham/types/mapper/hls/Byterange.ts
+++ b/lib/src/cmaf/ham/types/mapper/hls/Byterange.ts
@@ -1 +1,8 @@
+/**
+ * Byterange
+ *
+ * @group CMAF
+ *
+ * @alpha
+ */
export type Byterange = { length: number; offset: number };
diff --git a/lib/src/id3/util/decodeId3ImageFrame.ts b/lib/src/id3/util/decodeId3ImageFrame.ts
index 3bda888a..84fe56c6 100644
--- a/lib/src/id3/util/decodeId3ImageFrame.ts
+++ b/lib/src/id3/util/decodeId3ImageFrame.ts
@@ -1,8 +1,8 @@
+import { utf8ArrayToStr } from '../../utils.js';
import type { DecodedId3Frame } from '../DecodedId3Frame.js';
import type { RawId3Frame } from './RawFrame.js';
-import { toUint8 } from './utf8.js';
import { toArrayBuffer } from './toArrayBuffer.js';
-import { utf8ArrayToStr } from '../../utils.js';
+import { toUint8 } from './utf8.js';
type MetadataFrame = {
key: string;
@@ -54,7 +54,7 @@ export function decodeId3ImageFrame(
data = utf8ArrayToStr(
toUint8(frame.data, 4 + mimeTypeEndIndex + descriptionEndIndex),
);
- }
+ }
else {
data = toArrayBuffer(
frame.data.subarray(4 + mimeTypeEndIndex + descriptionEndIndex),
diff --git a/lib/src/index.ts b/lib/src/index.ts
index ce33db4c..b9792f1a 100644
--- a/lib/src/index.ts
+++ b/lib/src/index.ts
@@ -11,6 +11,7 @@ export type * from './cta.js';
export * from './dash.js';
export * from './id3.js';
export * from './iso8601.js';
+export * from './isobmff.js';
export type * from './request.js';
export * from './structuredfield.js';
export * from './utils.js';
diff --git a/lib/src/iso/bmff/Box.ts b/lib/src/iso/bmff/Box.ts
new file mode 100644
index 00000000..898e3123
--- /dev/null
+++ b/lib/src/iso/bmff/Box.ts
@@ -0,0 +1,14 @@
+/**
+ * Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type Box = T & {
+ type: string;
+ size: number;
+ largesize?: number;
+ usertype?: number[];
+ boxes?: Box[];
+}
diff --git a/lib/src/iso/bmff/BoxFilter.ts b/lib/src/iso/bmff/BoxFilter.ts
new file mode 100644
index 00000000..cca423ec
--- /dev/null
+++ b/lib/src/iso/bmff/BoxFilter.ts
@@ -0,0 +1,10 @@
+import type { Box } from './Box.js';
+
+/**
+ * BoxFilter
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type BoxFilter = (box: Box) => boolean;
diff --git a/lib/src/iso/bmff/BoxParser.ts b/lib/src/iso/bmff/BoxParser.ts
new file mode 100644
index 00000000..599c5104
--- /dev/null
+++ b/lib/src/iso/bmff/BoxParser.ts
@@ -0,0 +1,11 @@
+import type { IsoView } from './IsoView.js';
+import type { IsoViewConfig } from './IsoViewConfig.js';
+
+/**
+ * Box parser
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type BoxParser = (view: IsoView, config?: IsoViewConfig) => V;
diff --git a/lib/src/iso/bmff/BoxParserMap.ts b/lib/src/iso/bmff/BoxParserMap.ts
new file mode 100644
index 00000000..61a68567
--- /dev/null
+++ b/lib/src/iso/bmff/BoxParserMap.ts
@@ -0,0 +1,10 @@
+import type { BoxParser } from './BoxParser.js';
+
+/**
+ * A map of box parsers to their box types
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type BoxParserMap = Record;
diff --git a/lib/src/iso/bmff/ContainerBoxes.ts b/lib/src/iso/bmff/ContainerBoxes.ts
new file mode 100644
index 00000000..23da11df
--- /dev/null
+++ b/lib/src/iso/bmff/ContainerBoxes.ts
@@ -0,0 +1,25 @@
+export const ContainerBoxes: string[] = [
+ 'dinf',
+ 'edts',
+ 'enca',
+ 'encv',
+ 'grpl',
+ 'mdia',
+ 'meco',
+ 'meta',
+ 'mfra',
+ 'minf',
+ 'moof',
+ 'moov',
+ 'mvex',
+ 'prsl',
+ 'schi',
+ 'sinf',
+ 'stbl',
+ 'strk',
+ 'traf',
+ 'trak',
+ 'tref',
+ 'udta',
+ 'vttc',
+];
diff --git a/lib/src/iso/bmff/FullBox.ts b/lib/src/iso/bmff/FullBox.ts
new file mode 100644
index 00000000..2b95fe0f
--- /dev/null
+++ b/lib/src/iso/bmff/FullBox.ts
@@ -0,0 +1,11 @@
+/**
+ * FullBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type FullBox = {
+ version: number;
+ flags: number;
+}
diff --git a/lib/src/iso/bmff/IsoData.ts b/lib/src/iso/bmff/IsoData.ts
new file mode 100644
index 00000000..7094d0a6
--- /dev/null
+++ b/lib/src/iso/bmff/IsoData.ts
@@ -0,0 +1,10 @@
+import type { IsoView } from './IsoView.js';
+
+/**
+ * ISO data
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type IsoData = ArrayBuffer | DataView | IsoView | Uint8Array;
diff --git a/lib/src/iso/bmff/IsoView.ts b/lib/src/iso/bmff/IsoView.ts
new file mode 100644
index 00000000..5e0d93b7
--- /dev/null
+++ b/lib/src/iso/bmff/IsoView.ts
@@ -0,0 +1,261 @@
+import type { Box } from './Box.js';
+import { ContainerBoxes } from './ContainerBoxes.js';
+import { DATA } from './fields/DATA.js';
+import { INT } from './fields/INT.js';
+import { STRING } from './fields/STRING.js';
+import { TEMPLATE } from './fields/TEMPLATE.js';
+import { UINT } from './fields/UINT.js';
+import { UTF8 } from './fields/UTF8.js';
+import type { FullBox } from './FullBox.js';
+import type { IsoViewConfig } from './IsoViewConfig.js';
+import type { ISOFieldTypeMap } from './readers/ISOFieldTypeMap.js';
+import { readData } from './readers/readData.js';
+import { readInt } from './readers/readInt.js';
+import { readString } from './readers/readString.js';
+import { readTemplate } from './readers/readTemplate.js';
+import { readTerminatedString } from './readers/readTerminatedString.js';
+import { readUint } from './readers/readUint.js';
+import { readUTF8String } from './readers/readUTF8String.js';
+import { readUTF8TerminatedString } from './readers/readUTF8TerminatedString.js';
+
+/**
+ * Raw ISO BMFF data box.
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type RawBox = {
+ type: string;
+ size: number;
+ largesize?: number;
+ usertype?: number[];
+ data: IsoView;
+}
+
+/**
+ * ISO BMFF data view. Similar to DataView, but with additional methods for reading ISO BMFF data.
+ * It implements the iterator protocol, so it can be used in a for...of loop.
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export class IsoView {
+ private dataView: DataView;
+ private offset: number;
+ private config: IsoViewConfig;
+ private truncated: boolean = false;
+
+ constructor(raw: ArrayBuffer | DataView | Uint8Array, config?: IsoViewConfig) {
+ this.dataView = (raw instanceof ArrayBuffer) ? new DataView(raw) : (raw instanceof Uint8Array) ? new DataView(raw.buffer, raw.byteOffset, raw.byteLength) : raw;
+ this.offset = this.dataView.byteOffset;
+ this.config = config || { recursive: false, parsers: {} };
+ }
+
+ get cursor(): number {
+ return this.offset - this.dataView.byteOffset;
+ }
+
+ get done(): boolean {
+ return this.cursor >= this.dataView.byteLength || this.truncated;
+ }
+
+ get bytesRemaining(): number {
+ return this.dataView.byteLength - this.cursor;
+ }
+
+ slice = (size: number): IsoView => {
+ const dataView = new DataView(this.dataView.buffer, this.offset, size);
+ this.offset += size;
+ return new IsoView(dataView, this.config);
+ };
+
+ private read = (type: T, size: number = 0): ISOFieldTypeMap[T] => {
+ // TODO: Change all sizes from bits to bytes
+ const { dataView, offset } = this;
+
+ let result: any;
+ let cursor = size;
+
+ switch (type) {
+ case UINT:
+ result = readUint(dataView, offset, size);
+ break;
+
+ case INT:
+ result = readInt(dataView, offset, size);
+ break;
+
+ case TEMPLATE:
+ result = readTemplate(dataView, offset, size);
+ break;
+
+ case STRING:
+ if (size === -1) {
+ result = readTerminatedString(dataView, offset);
+ cursor = result.length + 1;
+ }
+ else {
+ result = readString(dataView, offset, size);
+ }
+ break;
+
+ case DATA:
+ result = readData(dataView, offset, size);
+ cursor = result.length;
+ break;
+
+ case UTF8:
+ if (size === -1) {
+ result = readUTF8TerminatedString(dataView, offset);
+ cursor = result.length + 1;
+ }
+ else {
+ result = readUTF8String(dataView, offset);
+ }
+ break;
+
+ default:
+ result = -1;
+ }
+
+ this.offset += cursor;
+
+ return result;
+ };
+
+ readUint = (size: number): number => {
+ return this.read(UINT, size);
+ };
+
+ readInt = (size: number): number => {
+ return this.read(INT, size);
+ };
+
+ readString = (size: number): string => {
+ return this.read(STRING, size);
+ };
+
+ readTemplate = (size: number): number => {
+ return this.read(TEMPLATE, size);
+ };
+
+ readData = (size: number): Uint8Array => {
+ return this.read(DATA, size);
+ };
+
+ readUtf8 = (size?: number): string => {
+ return this.read(UTF8, size);
+ };
+
+ readFullBox = (): FullBox => {
+ return {
+ version: this.readUint(1),
+ flags: this.readUint(3),
+ };
+ };
+
+ readArray = (type: T, size: number, length: number): ISOFieldTypeMap[T][] => {
+ const value = [];
+
+ for (let i = 0; i < length; i++) {
+ value.push(this.read(type, size));
+ }
+
+ return value as ISOFieldTypeMap[T][];
+ };
+
+ readBox = (): RawBox => {
+ const { dataView, offset } = this;
+
+ // read box size and type without advancing the cursor in case the box is truncated
+ let cursor = 0;
+
+ const box = {
+ size: readUint(dataView, offset, 4),
+ type: readString(dataView, offset + 4, 4),
+ } as RawBox;
+
+ cursor += 8;
+
+ if (box.size === 1) {
+ box.largesize = readUint(dataView, offset + cursor, 8);
+ cursor += 8;
+ }
+
+ const actualSize = box.largesize || box.size;
+ if (this.cursor + actualSize > dataView.byteLength) {
+ this.truncated = true;
+ throw new Error('Truncated box');
+ }
+
+ this.offset += cursor;
+ if (box.type === 'uuid') {
+ box.usertype = this.readArray('uint', 1, 16);
+ }
+
+ const viewSize = box.size === 0 ? this.bytesRemaining : actualSize - cursor;
+ box.data = this.slice(viewSize);
+
+ return box;
+ };
+
+ readBoxes = (length: number): Box[] => {
+ const result: Box[] = [];
+
+ for (const box of this) {
+ result.push(box);
+
+ if (length > 0 && result.length >= length) {
+ break;
+ }
+ }
+
+ return result;
+ };
+
+ readEntries = (length: number, map: () => T): T[] => {
+ const result: T[] = [];
+
+ for (let i = 0; i < length; i++) {
+ result.push(map());
+ }
+
+ return result;
+ };
+
+ *[Symbol.iterator](): Generator {
+ const { parsers = {}, recursive = false } = this.config;
+
+ while (!this.done) {
+ try {
+ const { type, data, ...rest } = this.readBox();
+ const box = { type, ...rest } as Box;
+ const parser = parsers[type] || parsers[type.trim()]; // url and urn boxes have a trailing space in their type field
+ if (parser) {
+ Object.assign(box, parser(data, this.config));
+ }
+
+ if (ContainerBoxes.includes(type)) {
+ const boxes = [];
+
+ for (const child of data) {
+ if (recursive) {
+ yield child;
+ }
+
+ boxes.push(child);
+ }
+
+ box.boxes = boxes;
+ }
+
+ yield box;
+ }
+ catch (error) {
+ break;
+ }
+ }
+ }
+}
diff --git a/lib/src/iso/bmff/IsoViewConfig.ts b/lib/src/iso/bmff/IsoViewConfig.ts
new file mode 100644
index 00000000..ff380cf0
--- /dev/null
+++ b/lib/src/iso/bmff/IsoViewConfig.ts
@@ -0,0 +1,20 @@
+import type { BoxParserMap } from './BoxParserMap.js';
+
+/**
+ * ISO View configuration
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type IsoViewConfig = {
+ /**
+ * A map of box parsers to their box types
+ */
+ parsers?: BoxParserMap;
+
+ /**
+ * Whether to parse boxes recursively
+ */
+ recursive?: boolean;
+};
diff --git a/lib/src/iso/bmff/TypeBox.ts b/lib/src/iso/bmff/TypeBox.ts
new file mode 100644
index 00000000..8c1a57f0
--- /dev/null
+++ b/lib/src/iso/bmff/TypeBox.ts
@@ -0,0 +1,12 @@
+/**
+ * TypeBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type TypeBox = {
+ majorBrand: string;
+ minorVersion: number;
+ compatibleBrands: string[];
+};
diff --git a/lib/src/iso/bmff/createIsoView.ts b/lib/src/iso/bmff/createIsoView.ts
new file mode 100644
index 00000000..e6aea791
--- /dev/null
+++ b/lib/src/iso/bmff/createIsoView.ts
@@ -0,0 +1,19 @@
+import type { IsoData } from './IsoData.js';
+import { IsoView } from './IsoView.js';
+import type { IsoViewConfig } from './IsoViewConfig.js';
+
+/**
+ * Create an IsoView from a raw ISO data.
+ *
+ * @param raw - The raw ISO data
+ * @param config - The configuration for the IsoView
+ *
+ * @returns The created IsoView
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function createIsoView(raw: IsoData, config?: IsoViewConfig): IsoView {
+ return raw instanceof IsoView ? raw : new IsoView(raw, config);
+}
diff --git a/lib/src/iso/bmff/fields/DATA.ts b/lib/src/iso/bmff/fields/DATA.ts
new file mode 100644
index 00000000..91c233a3
--- /dev/null
+++ b/lib/src/iso/bmff/fields/DATA.ts
@@ -0,0 +1,8 @@
+/**
+ * The data field type
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export const DATA = 'data';
diff --git a/lib/src/iso/bmff/fields/INT.ts b/lib/src/iso/bmff/fields/INT.ts
new file mode 100644
index 00000000..b925dfa9
--- /dev/null
+++ b/lib/src/iso/bmff/fields/INT.ts
@@ -0,0 +1,8 @@
+/**
+ * The integer field type
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export const INT = 'int';
diff --git a/lib/src/iso/bmff/fields/STRING.ts b/lib/src/iso/bmff/fields/STRING.ts
new file mode 100644
index 00000000..fa874357
--- /dev/null
+++ b/lib/src/iso/bmff/fields/STRING.ts
@@ -0,0 +1,8 @@
+/**
+ * The string field type
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export const STRING = 'string';
diff --git a/lib/src/iso/bmff/fields/TEMPLATE.ts b/lib/src/iso/bmff/fields/TEMPLATE.ts
new file mode 100644
index 00000000..cfda14db
--- /dev/null
+++ b/lib/src/iso/bmff/fields/TEMPLATE.ts
@@ -0,0 +1,8 @@
+/**
+ * The template field type
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export const TEMPLATE = 'template';
diff --git a/lib/src/iso/bmff/fields/UINT.ts b/lib/src/iso/bmff/fields/UINT.ts
new file mode 100644
index 00000000..a53bcd4c
--- /dev/null
+++ b/lib/src/iso/bmff/fields/UINT.ts
@@ -0,0 +1,8 @@
+/**
+ * The unsigned integer field type
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export const UINT = 'uint';
diff --git a/lib/src/iso/bmff/fields/UTF8.ts b/lib/src/iso/bmff/fields/UTF8.ts
new file mode 100644
index 00000000..3717c60a
--- /dev/null
+++ b/lib/src/iso/bmff/fields/UTF8.ts
@@ -0,0 +1,8 @@
+/**
+ * The UTF8 field type
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export const UTF8 = 'utf8';
diff --git a/lib/src/iso/bmff/filterBoxes.ts b/lib/src/iso/bmff/filterBoxes.ts
new file mode 100644
index 00000000..202411bc
--- /dev/null
+++ b/lib/src/iso/bmff/filterBoxes.ts
@@ -0,0 +1,34 @@
+import type { Box } from './Box.js';
+import type { BoxFilter } from './BoxFilter.js';
+import { createIsoView } from './createIsoView.js';
+import type { IsoData } from './IsoData.js';
+import type { IsoViewConfig } from './IsoViewConfig.js';
+
+function filter(iterator: Iterable, recursive: boolean, fn: BoxFilter, boxes: Box[] = []): Box[] {
+ for (const box of iterator) {
+ if (fn(box)) {
+ boxes.push(box);
+ }
+
+ if (recursive && Array.isArray(box.boxes)) {
+ filter(box.boxes, recursive, fn, boxes);
+ }
+ }
+
+ return boxes;
+}
+
+/**
+ * Filters boxes based on the given filter function.
+ *
+ * @param raw - The raw boxes to filter.
+ * @param config - The box parser configuration.
+ * @param fn - The filter function.
+ * @returns The filtered boxes.
+ *
+ * @group ISOBMFF
+ * @beta
+ */
+export function filterBoxes(raw: IsoData, config: IsoViewConfig, fn: BoxFilter): Box[] {
+ return filter(createIsoView(raw, { ...config, recursive: false }), !!config.recursive, fn);
+}
diff --git a/lib/src/iso/bmff/filterBoxesByType.ts b/lib/src/iso/bmff/filterBoxesByType.ts
new file mode 100644
index 00000000..07fe72c3
--- /dev/null
+++ b/lib/src/iso/bmff/filterBoxesByType.ts
@@ -0,0 +1,21 @@
+import type { Box } from './Box.js';
+import { filterBoxes } from './filterBoxes.js';
+import type { IsoData } from './IsoData.js';
+import type { IsoViewConfig } from './IsoViewConfig.js';
+
+/**
+ * Filter boxes by type from an IsoView
+ *
+ * @param type - The type of boxes to filter
+ * @param raw - The raw ISO data
+ * @param config - The configuration for the IsoView
+ *
+ * @returns The filtered boxes
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function filterBoxesByType(type: string, raw: IsoData, config: IsoViewConfig = {}): Box[] {
+ return filterBoxes(raw, config, box => box.type === type);
+}
diff --git a/lib/src/iso/bmff/findBox.ts b/lib/src/iso/bmff/findBox.ts
new file mode 100644
index 00000000..1d8960c4
--- /dev/null
+++ b/lib/src/iso/bmff/findBox.ts
@@ -0,0 +1,40 @@
+import type { Box } from './Box.js';
+import type { BoxFilter } from './BoxFilter.js';
+import { createIsoView } from './createIsoView.js';
+import type { IsoData } from './IsoData.js';
+import type { IsoViewConfig } from './IsoViewConfig.js';
+
+function find(iterator: Iterable, recursive: boolean, fn: BoxFilter): Box | null {
+ for (const box of iterator) {
+ if (fn(box)) {
+ return box;
+ }
+
+ if (recursive && Array.isArray(box.boxes)) {
+ const result = find(box.boxes, recursive, fn);
+
+ if (result) {
+ return result;
+ }
+ }
+ }
+
+ return null;
+}
+
+/**
+ * Find a box from an IsoView that matches a filter function
+ *
+ * @param raw - The raw ISO data
+ * @param config - The configuration for the IsoView
+ * @param fn - The filter function
+ *
+ * @returns The first box that matches the filter function
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function findBox(raw: IsoData, config: IsoViewConfig, fn: BoxFilter): Box | null {
+ return find(createIsoView(raw, { ...config, recursive: false }), !!config.recursive, fn);
+}
diff --git a/lib/src/iso/bmff/findBoxByType.ts b/lib/src/iso/bmff/findBoxByType.ts
new file mode 100644
index 00000000..213ac943
--- /dev/null
+++ b/lib/src/iso/bmff/findBoxByType.ts
@@ -0,0 +1,21 @@
+import type { Box } from './Box.js';
+import { findBox } from './findBox.js';
+import type { IsoData } from './IsoData.js';
+import type { IsoViewConfig } from './IsoViewConfig.js';
+
+/**
+ * Find a box from an IsoView that matches a given type
+ *
+ * @param type - The type of box to find
+ * @param raw - The raw ISO data
+ * @param config - The configuration for the IsoView
+ *
+ * @returns The first box that matches the type
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function findBoxByType(type: string, raw: IsoData, config: IsoViewConfig = {}): Box | null {
+ return findBox(raw, config, box => box.type === type);
+}
diff --git a/lib/src/iso/bmff/parseBoxes.ts b/lib/src/iso/bmff/parseBoxes.ts
new file mode 100644
index 00000000..f23254f7
--- /dev/null
+++ b/lib/src/iso/bmff/parseBoxes.ts
@@ -0,0 +1,26 @@
+import type { Box } from './Box.js';
+import { createIsoView } from './createIsoView.js';
+import type { IsoData } from './IsoData.js';
+import type { IsoViewConfig } from './IsoViewConfig.js';
+
+/**
+ * Parse boxes from an IsoView
+ *
+ * @param raw - The raw ISO data
+ * @param config - The configuration for the IsoView
+ *
+ * @returns The parsed boxes
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function parseBoxes(raw: IsoData, config?: IsoViewConfig): Box[] {
+ const boxes = [];
+
+ for (const box of createIsoView(raw, config)) {
+ boxes.push(box);
+ }
+
+ return boxes;
+}
diff --git a/lib/src/iso/bmff/parsers.ts b/lib/src/iso/bmff/parsers.ts
new file mode 100644
index 00000000..3082aa6e
--- /dev/null
+++ b/lib/src/iso/bmff/parsers.ts
@@ -0,0 +1,59 @@
+export * from './parsers/ardi.js';
+export * from './parsers/avc1.js';
+export * from './parsers/avc2.js';
+export * from './parsers/avc3.js';
+export * from './parsers/avc4.js';
+export * from './parsers/ctts.js';
+export * from './parsers/dref.js';
+export * from './parsers/elng.js';
+export * from './parsers/elst.js';
+export * from './parsers/emsg.js';
+export * from './parsers/enca.js';
+export * from './parsers/encv.js';
+export * from './parsers/free.js';
+export * from './parsers/frma.js';
+export * from './parsers/ftyp.js';
+export * from './parsers/hdlr.js';
+export * from './parsers/hev1.js';
+export * from './parsers/hvc1.js';
+export * from './parsers/imda.js';
+export * from './parsers/kind.js';
+export * from './parsers/labl.js';
+export * from './parsers/mdat.js';
+export * from './parsers/mdhd.js';
+export * from './parsers/mehd.js';
+export * from './parsers/meta.js';
+export * from './parsers/mfhd.js';
+export * from './parsers/mfro.js';
+export * from './parsers/mp4a.js';
+export * from './parsers/mvhd.js';
+export * from './parsers/payl.js';
+export * from './parsers/prft.js';
+export * from './parsers/prsl.js';
+export * from './parsers/pssh.js';
+export * from './parsers/schm.js';
+export * from './parsers/sdtp.js';
+export * from './parsers/sidx.js';
+export * from './parsers/skip.js';
+export * from './parsers/smhd.js';
+export * from './parsers/ssix.js';
+export * from './parsers/sthd.js';
+export * from './parsers/stsd.js';
+export * from './parsers/stss.js';
+export * from './parsers/sttg.js';
+export * from './parsers/stts.js';
+export * from './parsers/styp.js';
+export * from './parsers/subs.js';
+export * from './parsers/tenc.js';
+export * from './parsers/tfdt.js';
+export * from './parsers/tfhd.js';
+export * from './parsers/tfra.js';
+export * from './parsers/tkhd.js';
+export * from './parsers/trex.js';
+export * from './parsers/trun.js';
+export * from './parsers/url.js';
+export * from './parsers/urn.js';
+export * from './parsers/vlab.js';
+export * from './parsers/vmhd.js';
+export * from './parsers/vttC.js';
+export * from './parsers/vtte.js';
diff --git a/lib/src/iso/bmff/parsers/ardi.ts b/lib/src/iso/bmff/parsers/ardi.ts
new file mode 100644
index 00000000..b1c8b01e
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/ardi.ts
@@ -0,0 +1,31 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:202x - 12.2.8 Audio rendering indication box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type AudioRenderingIndicationBox = FullBox & {
+ audioRenderingIndication: number;
+}
+
+/**
+ * Parse a AudioRenderingIndicationBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed AudioRenderingIndicationBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function ardi(view: IsoView): AudioRenderingIndicationBox {
+ return {
+ ...view.readFullBox(),
+ audioRenderingIndication: view.readUint(1),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/avc1.ts b/lib/src/iso/bmff/parsers/avc1.ts
new file mode 100644
index 00000000..95a3d2a7
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/avc1.ts
@@ -0,0 +1,70 @@
+import { UINT } from '../fields/UINT.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2015 - 8.5.2.2 Sample Entry
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type SampleEntry = {
+ reserved1: number[];
+ dataReferenceIndex: number;
+}
+
+/**
+ * ISO/IEC 14496-15:2014 - 12.1.3.1 avc1/2/3/4, hev1, hvc1, encv
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type VisualSampleEntry = SampleEntry & {
+ preDefined1: number;
+ reserved2: number;
+ preDefined2: number[];
+ width: number;
+ height: number;
+ horizresolution: number;
+ vertresolution: number;
+ reserved3: number;
+ frameCount: number;
+ compressorName: number[];
+ depth: number;
+ preDefined3: number;
+ config: Uint8Array;
+}
+
+/**
+ * Parse a VisualSampleEntryBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed VisualSampleEntryBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function avc1(view: IsoView): VisualSampleEntry {
+ const { readArray, readUint, readInt, readTemplate, readData } = view;
+
+ return {
+ reserved1: readArray(UINT, 1, 6),
+ dataReferenceIndex: readUint(2),
+ preDefined1: readUint(2),
+ reserved2: readUint(2),
+ preDefined2: readArray(UINT, 4, 3),
+ width: readUint(2),
+ height: readUint(2),
+ horizresolution: readTemplate(4),
+ vertresolution: readTemplate(4),
+ reserved3: readUint(4),
+ frameCount: readUint(2),
+ compressorName: readArray(UINT, 1, 32),
+ depth: readUint(2),
+ preDefined3: readInt(2),
+ config: readData(-1),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/avc2.ts b/lib/src/iso/bmff/parsers/avc2.ts
new file mode 100644
index 00000000..4d988082
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/avc2.ts
@@ -0,0 +1,15 @@
+import type { BoxParser } from '../BoxParser.js';
+import { avc1, type VisualSampleEntry } from './avc1.js';
+
+/**
+ * Parse a VisualSampleEntryBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed VisualSampleEntryBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export const avc2: BoxParser = avc1;
diff --git a/lib/src/iso/bmff/parsers/avc3.ts b/lib/src/iso/bmff/parsers/avc3.ts
new file mode 100644
index 00000000..85c37f54
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/avc3.ts
@@ -0,0 +1,15 @@
+import type { BoxParser } from '../BoxParser.js';
+import { avc1, type VisualSampleEntry } from './avc1.js';
+
+/**
+ * Parse a VisualSampleEntryBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed VisualSampleEntryBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export const avc3: BoxParser = avc1;
diff --git a/lib/src/iso/bmff/parsers/avc4.ts b/lib/src/iso/bmff/parsers/avc4.ts
new file mode 100644
index 00000000..2e54e392
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/avc4.ts
@@ -0,0 +1,15 @@
+import type { BoxParser } from '../BoxParser.js';
+import { avc1, type VisualSampleEntry } from './avc1.js';
+
+/**
+ * Parse a VisualSampleEntryBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed VisualSampleEntryBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export const avc4: BoxParser = avc1;
diff --git a/lib/src/iso/bmff/parsers/ctts.ts b/lib/src/iso/bmff/parsers/ctts.ts
new file mode 100644
index 00000000..afd9e3b1
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/ctts.ts
@@ -0,0 +1,55 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * A Composition Time To Sample Entry
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type CompositionTimeToSampleEntry = {
+ sampleCount: number;
+ sampleOffset: number;
+}
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.6.1.3 Composition Time To Sample Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type CompositionTimeToSampleBox = FullBox & {
+ entryCount: number;
+ entries: CompositionTimeToSampleEntry[];
+};
+
+/**
+ * Parse a CompositionTimeToSampleBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed CompositionTimeToSampleBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function ctts(view: IsoView): CompositionTimeToSampleBox {
+ const { version, flags } = view.readFullBox();
+ const read = version === 1 ? view.readInt : view.readUint;
+
+ const entryCount = view.readUint(4);
+ const entries = view.readEntries(entryCount, () => ({
+ sampleCount: view.readUint(4),
+ sampleOffset: read(4),
+ }));
+
+ return {
+ version,
+ flags,
+ entryCount,
+ entries,
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/dref.ts b/lib/src/iso/bmff/parsers/dref.ts
new file mode 100644
index 00000000..50a39b54
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/dref.ts
@@ -0,0 +1,39 @@
+import type { Box } from '../Box.js';
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.7.2 Data Reference Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type DataReferenceBox = FullBox & {
+ entryCount: number;
+ entries: Box[];
+};
+
+/**
+ * Parse a DataReferenceBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed DataReferenceBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function dref(view: IsoView): DataReferenceBox {
+ const { version, flags } = view.readFullBox();
+ const entryCount = view.readUint(4);
+ const entries = view.readBoxes(entryCount);
+
+ return {
+ version,
+ flags,
+ entryCount,
+ entries,
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/elng.ts b/lib/src/iso/bmff/parsers/elng.ts
new file mode 100644
index 00000000..352c19e0
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/elng.ts
@@ -0,0 +1,31 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:202x - 8.4.6 Extended language tag
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type ExtendedLanguageBox = FullBox & {
+ extendedLanguage: string;
+}
+
+/**
+ * Parse a ExtendedLanguageBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed ExtendedLanguageBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function elng(view: IsoView): ExtendedLanguageBox {
+ return {
+ ...view.readFullBox(),
+ extendedLanguage: view.readUtf8(-1),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/elst.ts b/lib/src/iso/bmff/parsers/elst.ts
new file mode 100644
index 00000000..096430c9
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/elst.ts
@@ -0,0 +1,60 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * An edit list entry.
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type EditListEntry = {
+ segmentDuration: number;
+ mediaTime: number;
+ mediaRateInteger: number;
+ mediaRateFraction: number;
+}
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.6.6 Edit List Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type EditListBox = FullBox & {
+ entryCount: number;
+ entries: EditListEntry[];
+}
+
+/**
+ * Parse a Box from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function elst(view: IsoView): EditListBox {
+ const { version, flags } = view.readFullBox();
+ const v1 = version === 1;
+ const size = v1 ? 8 : 4;
+
+ const entryCount = view.readUint(4);
+ const entries = view.readEntries(entryCount, () => ({
+ segmentDuration: view.readUint(size),
+ mediaTime: view.readInt(size),
+ mediaRateInteger: view.readInt(2),
+ mediaRateFraction: view.readInt(2),
+ }));
+
+ return {
+ version,
+ flags,
+ entryCount,
+ entries,
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/emsg.ts b/lib/src/iso/bmff/parsers/emsg.ts
new file mode 100644
index 00000000..05ab3d70
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/emsg.ts
@@ -0,0 +1,58 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 23009-1:2014 - 5.10.3.3 Event Message Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type EventMessageBox = FullBox & {
+ schemeIdUri: string,
+ value: string,
+ timescale: number,
+ presentationTime: number,
+ presentationTimeDelta: number,
+ eventDuration: number,
+ id: number,
+ messageData: Uint8Array,
+}
+
+/**
+ * Parse an EventMessageBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed EventMessageBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function emsg(view: IsoView): EventMessageBox {
+ const { readUint, readString, readData } = view;
+
+ const result = { ...view.readFullBox() } as EventMessageBox;
+
+ if (result.version == 1) {
+ result.timescale = readUint(4);
+ result.presentationTime = readUint(8);
+ result.eventDuration = readUint(4);
+ result.id = readUint(4);
+ result.schemeIdUri = readString(-1);
+ result.value = readString(-1);
+ }
+ else {
+ result.schemeIdUri = readString(-1);
+ result.value = readString(-1);
+ result.timescale = readUint(4);
+ result.presentationTimeDelta = readUint(4);
+ result.eventDuration = readUint(4);
+ result.id = readUint(4);
+ }
+
+ result.messageData = readData(-1);
+
+ return result;
+}
diff --git a/lib/src/iso/bmff/parsers/enca.ts b/lib/src/iso/bmff/parsers/enca.ts
new file mode 100644
index 00000000..3b589989
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/enca.ts
@@ -0,0 +1,15 @@
+import type { BoxParser } from '../BoxParser.js';
+import { mp4a, type AudioSampleEntry } from './mp4a.js';
+
+/**
+ * Parse an AudioSampleEntry from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed AudioSampleEntry
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export const enca: BoxParser = mp4a;
diff --git a/lib/src/iso/bmff/parsers/encv.ts b/lib/src/iso/bmff/parsers/encv.ts
new file mode 100644
index 00000000..2beb705c
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/encv.ts
@@ -0,0 +1,15 @@
+import type { BoxParser } from '../BoxParser.js';
+import { avc1, type VisualSampleEntry } from './avc1.js';
+
+/**
+ * Parse a VisualSampleEntryBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed VisualSampleEntryBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export const encv: BoxParser = avc1;
diff --git a/lib/src/iso/bmff/parsers/free.ts b/lib/src/iso/bmff/parsers/free.ts
new file mode 100644
index 00000000..ddc3ce60
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/free.ts
@@ -0,0 +1,29 @@
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.1.2 Free Space Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type FreeSpaceBox = {
+ data: Uint8Array;
+};
+
+/**
+ * Parse a Box from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function free(view: IsoView): FreeSpaceBox {
+ return {
+ data: view.readData(-1),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/frma.ts b/lib/src/iso/bmff/parsers/frma.ts
new file mode 100644
index 00000000..e4033bf9
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/frma.ts
@@ -0,0 +1,29 @@
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.12.2 Original Format Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type OriginalFormatBox = {
+ dataFormat: number;
+}
+
+/**
+ * Parse an OriginalFormatBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed OriginalFormatBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function frma(view: IsoView): OriginalFormatBox {
+ return {
+ dataFormat: view.readUint(4),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/ftyp.ts b/lib/src/iso/bmff/parsers/ftyp.ts
new file mode 100644
index 00000000..d01355dd
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/ftyp.ts
@@ -0,0 +1,37 @@
+import type { IsoView } from '../IsoView.js';
+import type { TypeBox } from '../TypeBox.js';
+import { STRING } from '../fields/STRING.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 4.3 File Type Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type FileTypeBox = TypeBox;
+
+/**
+ * Parse a FileTypeBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed FileTypeBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function ftyp(view: IsoView): FileTypeBox {
+ const size = 4;
+ const majorBrand = view.readString(4);
+ const minorVersion = view.readUint(4);
+ const length = view.bytesRemaining / size;
+ const compatibleBrands = view.readArray(STRING, size, length);
+
+ return {
+ majorBrand,
+ minorVersion,
+ compatibleBrands,
+ };
+}
diff --git a/lib/src/iso/bmff/parsers/hdlr.ts b/lib/src/iso/bmff/parsers/hdlr.ts
new file mode 100644
index 00000000..c9cb5333
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/hdlr.ts
@@ -0,0 +1,37 @@
+import { UINT } from '../fields/UINT.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.4.3 Handler Reference Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type HandlerReferenceBox = {
+ preDefined: number;
+ handlerType: string;
+ reserved: number[];
+ name: string;
+};
+
+/**
+ * Parse a HandlerReferenceBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed HandlerReferenceBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function hdlr(view: IsoView): HandlerReferenceBox {
+ return {
+ ...view.readFullBox(),
+ preDefined: view.readUint(4),
+ handlerType: view.readString(4),
+ reserved: view.readArray(UINT, 4, 3),
+ name: view.readString(-1),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/hev1.ts b/lib/src/iso/bmff/parsers/hev1.ts
new file mode 100644
index 00000000..35a48402
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/hev1.ts
@@ -0,0 +1,15 @@
+import type { BoxParser } from '../BoxParser.js';
+import { avc1, type VisualSampleEntry } from './avc1.js';
+
+/**
+ * Parse a VisualSampleEntryBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed VisualSampleEntryBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export const hev1: BoxParser = avc1;
diff --git a/lib/src/iso/bmff/parsers/hvc1.ts b/lib/src/iso/bmff/parsers/hvc1.ts
new file mode 100644
index 00000000..96b1d4ac
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/hvc1.ts
@@ -0,0 +1,17 @@
+import type { IsoView } from '../IsoView.js';
+import { avc1, type VisualSampleEntry } from './avc1.js';
+
+/**
+ * Parse a VisualSampleEntryBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed VisualSampleEntryBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function hvc1(view: IsoView): VisualSampleEntry {
+ return avc1(view);
+}
diff --git a/lib/src/iso/bmff/parsers/imda.ts b/lib/src/iso/bmff/parsers/imda.ts
new file mode 100644
index 00000000..62b43872
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/imda.ts
@@ -0,0 +1,31 @@
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 9.1.4.1 Identified media data box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type IdentifiedMediaDataBox = {
+ imdaIdentifier: number;
+ data: Uint8Array;
+};
+
+/**
+ * Parse a IdentifiedMediaDataBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed IdentifiedMediaDataBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function imda(view: IsoView): IdentifiedMediaDataBox {
+ return {
+ imdaIdentifier: view.readUint(4),
+ data: view.readData(-1),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/kind.ts b/lib/src/iso/bmff/parsers/kind.ts
new file mode 100644
index 00000000..f7085af5
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/kind.ts
@@ -0,0 +1,33 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:202x - 8.10.4 Track kind box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type TrackKindBox = FullBox & {
+ schemeUri: string;
+ value: string;
+}
+
+/**
+ * Parse a TrackKinBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed TrackKindBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function kind(view: IsoView): TrackKindBox {
+ return {
+ ...view.readFullBox(),
+ schemeUri: view.readUtf8(-1),
+ value: view.readUtf8(-1),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/labl.ts b/lib/src/iso/bmff/parsers/labl.ts
new file mode 100644
index 00000000..b0c4a3bf
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/labl.ts
@@ -0,0 +1,40 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:202x - 8.10.5 Label box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type LabelBox = FullBox & {
+ isGroupLabel: boolean;
+ labelId: number;
+ language: string;
+ label: string;
+}
+
+/**
+ * Parse a LabelBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed LabelBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function labl(view: IsoView): LabelBox {
+ const { version, flags } = view.readFullBox();
+
+ return {
+ version,
+ flags,
+ isGroupLabel: (flags & 0x1) !== 0,
+ labelId: view.readUint(2),
+ language: view.readUtf8(-1),
+ label: view.readUtf8(-1),
+ };
+}
diff --git a/lib/src/iso/bmff/parsers/mdat.ts b/lib/src/iso/bmff/parsers/mdat.ts
new file mode 100644
index 00000000..d322be7b
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/mdat.ts
@@ -0,0 +1,29 @@
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.1.1 Media Data Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type MediaDataBox = {
+ data: Uint8Array;
+};
+
+/**
+ * Parse a MediaDataBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed MediaDataBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function mdat(view: IsoView): MediaDataBox {
+ return {
+ data: view.readData(-1),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/mdhd.ts b/lib/src/iso/bmff/parsers/mdhd.ts
new file mode 100644
index 00000000..f3183147
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/mdhd.ts
@@ -0,0 +1,66 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.4.2 Media Header Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type MediaHeaderBox = FullBox & {
+ /** A 32-bit integer that specifies the creation time of the media in this track. */
+ creationTime: number;
+
+ /** A 32-bit integer that specifies the most recent time the media in this track was modified. */
+ modificationTime: number;
+
+ /** A time value that indicates the time-scale for this media; this is the number of time units that pass in one second. */
+ timescale: number;
+
+ /** A time value that indicates the duration of this media. */
+ duration: number;
+
+ /** A 16-bit integer that specifies the language code for this media. */
+ language: string;
+
+ /** A 16-bit value that is reserved for use in other specifications. */
+ preDefined: number;
+}
+
+/**
+ * Parse a MediaHeaderBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed MediaHeaderBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function mdhd(view: IsoView): MediaHeaderBox {
+ const { version, flags } = view.readFullBox();
+
+ const creationTime = view.readUint(version == 1 ? 8 : 4);
+ const modificationTime = view.readUint(version == 1 ? 8 : 4);
+ const timescale = view.readUint(4);
+ const duration = view.readUint(version == 1 ? 8 : 4);
+ const lang = view.readUint(2);
+ const language = String.fromCharCode(((lang >> 10) & 0x1F) + 0x60,
+ ((lang >> 5) & 0x1F) + 0x60,
+ (lang & 0x1F) + 0x60);
+
+ const preDefined = view.readUint(2);
+
+ return {
+ version,
+ flags,
+ creationTime,
+ modificationTime,
+ timescale,
+ duration,
+ language,
+ preDefined,
+ };
+}
diff --git a/lib/src/iso/bmff/parsers/mehd.ts b/lib/src/iso/bmff/parsers/mehd.ts
new file mode 100644
index 00000000..ddbcf215
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/mehd.ts
@@ -0,0 +1,34 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.8.2 Movie Extends Header Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type MovieExtendsHeaderBox = FullBox & {
+ fragmentDuration: number;
+};
+
+/**
+ * Parse a MovieExtendsHeaderBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed MovieExtendsHeaderBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function mehd(view: IsoView): MovieExtendsHeaderBox {
+ const { version, flags } = view.readFullBox();
+
+ return {
+ version,
+ flags,
+ fragmentDuration: view.readUint((version === 1) ? 8 : 4),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/meta.ts b/lib/src/iso/bmff/parsers/meta.ts
new file mode 100644
index 00000000..799196d9
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/meta.ts
@@ -0,0 +1,26 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:202x - 8.11.1 Meta box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type MetaBox = FullBox;
+
+/**
+ * Parse a MetaBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed MetaBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function meta(view: IsoView): MetaBox {
+ return view.readFullBox();
+};
diff --git a/lib/src/iso/bmff/parsers/mfhd.ts b/lib/src/iso/bmff/parsers/mfhd.ts
new file mode 100644
index 00000000..d33ca678
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/mfhd.ts
@@ -0,0 +1,31 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.8.5 Movie Fragment Header Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type MovieFragmentHeaderBox = FullBox & {
+ sequenceNumber: number;
+};
+
+/**
+ * Parse a MovieFragmentHeaderBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed MovieFragmentHeaderBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function mfhd(view: IsoView): MovieFragmentHeaderBox {
+ return {
+ ...view.readFullBox(),
+ sequenceNumber: view.readUint(4),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/mfro.ts b/lib/src/iso/bmff/parsers/mfro.ts
new file mode 100644
index 00000000..2a0cd737
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/mfro.ts
@@ -0,0 +1,32 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.8.11 Movie Fragment Random Access Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type MovieFragmentRandomAccessBox = FullBox & {
+ mfra_size: number;
+}
+
+/**
+ * Parse a MovieFragmentRandomAccessBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed MovieFragmentRandomAccessBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function mfro(view: IsoView): MovieFragmentRandomAccessBox {
+ return {
+ ...view.readFullBox(),
+ mfra_size: view.readUint(4),
+ };
+};
+
diff --git a/lib/src/iso/bmff/parsers/mp4a.ts b/lib/src/iso/bmff/parsers/mp4a.ts
new file mode 100644
index 00000000..14b8da7d
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/mp4a.ts
@@ -0,0 +1,47 @@
+import { UINT } from '../fields/UINT.js';
+import type { IsoView } from '../IsoView.js';
+import type { SampleEntry } from './avc1.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.5.2.2 mp4a box (use AudioSampleEntry definition and naming)
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type AudioSampleEntry = SampleEntry & {
+ reserved2: number[];
+ channelcount: number;
+ samplesize: number;
+ preDefined: number;
+ reserved3: number;
+ samplerate: number;
+ esds: Uint8Array;
+};
+
+/**
+ * Parse an AudioSampleEntry from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed AudioSampleEntry
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function mp4a(view: IsoView): AudioSampleEntry {
+ const { readArray, readUint, readTemplate, readData } = view;
+
+ return {
+ reserved1: readArray(UINT, 1, 6),
+ dataReferenceIndex: readUint(2),
+ reserved2: readArray(UINT, 4, 2),
+ channelcount: readUint(2),
+ samplesize: readUint(2),
+ preDefined: readUint(2),
+ reserved3: readUint(2),
+ samplerate: readTemplate(4),
+ esds: readData(-1),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/mvhd.ts b/lib/src/iso/bmff/parsers/mvhd.ts
new file mode 100644
index 00000000..239f9f13
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/mvhd.ts
@@ -0,0 +1,59 @@
+import type { IsoView } from '../IsoView.js';
+import { UINT } from '../fields/UINT.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.2.2 Movie Header Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type MovieHeaderBox = {
+ version: number;
+ flags: number;
+ creationTime: number;
+ modificationTime: number;
+ timescale: number;
+ duration: number;
+ rate: number;
+ volume: number;
+ reserved1: number;
+ reserved2: number[];
+ matrix: number[];
+ preDefined: number[];
+ nextTrackId: number;
+};
+
+/**
+ * Parse a Box from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function mvhd(view: IsoView): MovieHeaderBox {
+ const { readUint, readTemplate, readArray } = view;
+
+ const { version, flags } = view.readFullBox();
+ const size = (version == 1) ? 8 : 4;
+
+ return {
+ version,
+ flags,
+ creationTime: readUint(size),
+ modificationTime: readUint(size),
+ timescale: readUint(4),
+ duration: readUint(size),
+ rate: readTemplate(4),
+ volume: readTemplate(2),
+ reserved1: readUint(2),
+ reserved2: readArray(UINT, 4, 2),
+ matrix: readArray(UINT, 4, 9),
+ preDefined: readArray(UINT, 4, 6),
+ nextTrackId: readUint(4),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/payl.ts b/lib/src/iso/bmff/parsers/payl.ts
new file mode 100644
index 00000000..90cb7606
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/payl.ts
@@ -0,0 +1,29 @@
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-30:2014 - WebVTT Cue Payload Box.
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type WebVTTCuePayloadBox = {
+ cueText: string;
+};
+
+/**
+ * Parse a WebVTTCuePayloadBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed WebVTTCuePayloadBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function payl(view: IsoView): WebVTTCuePayloadBox {
+ return {
+ cueText: view.readUtf8(-1),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/prft.ts b/lib/src/iso/bmff/parsers/prft.ts
new file mode 100644
index 00000000..c21fadbf
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/prft.ts
@@ -0,0 +1,40 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.16.5 Producer Reference Time
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type ProducerReferenceTimeBox = FullBox & {
+ referenceTrackId: number;
+ ntpTimestampSec: number;
+ ntpTimestampFrac: number;
+ mediaTime: number;
+};
+
+/**
+ * Parse a ProducerReferenceTimeBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed ProducerReferenceTimeBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function prft(view: IsoView): ProducerReferenceTimeBox {
+ const { version, flags } = view.readFullBox();
+
+ return {
+ version,
+ flags,
+ referenceTrackId: view.readUint(4),
+ ntpTimestampSec: view.readUint(4),
+ ntpTimestampFrac: view.readUint(4),
+ mediaTime: view.readUint(version === 1 ? 8 : 4),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/prsl.ts b/lib/src/iso/bmff/parsers/prsl.ts
new file mode 100644
index 00000000..ef2abc16
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/prsl.ts
@@ -0,0 +1,70 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * Entity
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type Entity = {
+ /** Entity ID */
+ entityId: number;
+};
+
+/**
+ * ISO/IEC 14496-12:202x - 8.18.4.1 Preselection group box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type PreselectionGroupBox = FullBox & {
+ /** Group ID */
+ groupId: number;
+
+ /** Number of entities in group */
+ numEntitiesInGroup: number;
+
+ /** Entities */
+ entities: Entity[];
+
+ preselectionTag?: string;
+ selectionPriority?: number;
+ interleavingTag?: string;
+};
+
+/**
+ * Parse a PreselectionGroupBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed PreselectionGroupBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function prsl(view: IsoView): PreselectionGroupBox {
+ const { version, flags } = view.readFullBox();
+ const groupId = view.readUint(4);
+ const numEntitiesInGroup = view.readUint(4);
+ const entities = view.readEntries(numEntitiesInGroup, () => ({
+ entityId: view.readUint(4),
+ }));
+ const preselectionTag = flags & 0x1000 ? view.readUtf8(-1) : undefined;
+ const selectionPriority = flags & 0x2000 ? view.readUint(1) : undefined;
+ const interleavingTag = flags & 0x4000 ? view.readUtf8(-1) : undefined;
+
+ return {
+ version,
+ flags,
+ groupId,
+ numEntitiesInGroup,
+ entities,
+ preselectionTag,
+ selectionPriority,
+ interleavingTag,
+ };
+}
diff --git a/lib/src/iso/bmff/parsers/pssh.ts b/lib/src/iso/bmff/parsers/pssh.ts
new file mode 100644
index 00000000..8e2e5aec
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/pssh.ts
@@ -0,0 +1,44 @@
+import { UINT } from '../fields/UINT.js';
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 23001-7:2011 - 8.1 Protection System Specific Header Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type ProtectionSystemSpecificHeaderBox = FullBox & {
+ systemID: number[];
+ dataSize: number;
+ data: number[];
+}
+
+/**
+ * Parse a ProtectionSystemSpecificHeaderBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed ProtectionSystemSpecificHeaderBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function pssh(view: IsoView): ProtectionSystemSpecificHeaderBox {
+ const { readUint, readArray } = view;
+ const { version, flags } = view.readFullBox();
+
+ const systemID = readArray(UINT, 1, 16);
+ const dataSize = readUint(4);
+ const data = readArray(UINT, 1, dataSize);
+
+ return {
+ version,
+ flags,
+ systemID,
+ dataSize,
+ data,
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/schm.ts b/lib/src/iso/bmff/parsers/schm.ts
new file mode 100644
index 00000000..a5cc5bd4
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/schm.ts
@@ -0,0 +1,38 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.12.5 Scheme Type Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type SchemeTypeBox = FullBox & {
+ schemeType: number;
+ schemeVersion: number;
+ schemeUri?: string;
+};
+
+/**
+ * Parse a SchemeTypeBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed SchemeTypeBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function schm(view: IsoView): SchemeTypeBox {
+ const { version, flags } = view.readFullBox();
+
+ return {
+ version,
+ flags,
+ schemeType: view.readUint(4),
+ schemeVersion: view.readUint(4),
+ schemeUri: flags & 0x000001 ? view.readString(-1) : undefined,
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/sdtp.ts b/lib/src/iso/bmff/parsers/sdtp.ts
new file mode 100644
index 00000000..5cc65114
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/sdtp.ts
@@ -0,0 +1,34 @@
+import { UINT } from '../fields/UINT.js';
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.6.4.1 Sample Dependency Type box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type SampleDependencyTypeBox = FullBox & {
+ sampleDependencyTable: number[];
+};
+
+//
+
+/**
+ * Parse a SampleDependencyTypeBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed SampleDependencyTypeBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function sdtp(view: IsoView): SampleDependencyTypeBox {
+ return {
+ ...view.readFullBox(),
+ sampleDependencyTable: view.readArray(UINT, 1, view.bytesRemaining),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/sidx.ts b/lib/src/iso/bmff/parsers/sidx.ts
new file mode 100644
index 00000000..03900027
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/sidx.ts
@@ -0,0 +1,86 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * Segment index reference
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type Reference = {
+ reference: number,
+ subsegmentDuration: number,
+ sap: number,
+ referenceType: number,
+ referencedSize: number,
+ startsWithSap: number,
+ sapType: number,
+ sapDeltaTime: number,
+}
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.16.3 Segment Index Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type SegmentIndexBox = FullBox & {
+ referenceId: number,
+ timescale: number,
+ earliestPresentationTime: number,
+ firstOffset: number,
+ reserved: number,
+ references: Reference[],
+};
+
+/**
+ * Parse a SegmentIndexBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed SegmentIndexBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function sidx(view: IsoView): SegmentIndexBox {
+ const { readUint } = view;
+ const { version, flags } = view.readFullBox();
+ const v1 = version === 1;
+ const size = v1 ? 8 : 4;
+
+ const referenceId = readUint(4);
+ const timescale = readUint(4);
+ const earliestPresentationTime = readUint(size);
+ const firstOffset = readUint(size);
+ const reserved = readUint(2);
+ const referenceCount = readUint(2);
+ const references = view.readEntries(referenceCount, () => {
+ const entry = {} as any;
+
+ entry.reference = readUint(4);
+ entry.subsegmentDuration = readUint(4);
+ entry.sap = readUint(4);
+ entry.referenceType = (entry.reference >> 31) & 0x00000001;
+ entry.referencedSize = entry.reference & 0x7FFFFFFF;
+ entry.startsWithSap = (entry.sap >> 31) & 0x00000001;
+ entry.sapType = (entry.sap >> 28) & 0x00000007;
+ entry.sapDeltaTime = (entry.sap & 0x0FFFFFFF);
+
+ return entry;
+ });
+
+ return {
+ version,
+ flags,
+ referenceId,
+ timescale,
+ earliestPresentationTime,
+ firstOffset,
+ reserved,
+ references,
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/skip.ts b/lib/src/iso/bmff/parsers/skip.ts
new file mode 100644
index 00000000..c315b471
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/skip.ts
@@ -0,0 +1,15 @@
+import type { BoxParser } from '../BoxParser.js';
+import { free, type FreeSpaceBox } from './free.js';
+
+/**
+ * Parse a FreeSpaceBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed FreeSpaceBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export const skip: BoxParser = free;
diff --git a/lib/src/iso/bmff/parsers/smhd.ts b/lib/src/iso/bmff/parsers/smhd.ts
new file mode 100644
index 00000000..853882d1
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/smhd.ts
@@ -0,0 +1,33 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.4.5.3 Sound Media Header Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type SoundMediaHeaderBox = FullBox & {
+ balance: number;
+ reserved: number;
+};
+
+/**
+ * Parse a SoundMediaHeaderBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed SoundMediaHeaderBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function smhd(view: IsoView): SoundMediaHeaderBox {
+ return {
+ ...view.readFullBox(),
+ balance: view.readUint(2),
+ reserved: view.readUint(2),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/ssix.ts b/lib/src/iso/bmff/parsers/ssix.ts
new file mode 100644
index 00000000..2e073d11
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/ssix.ts
@@ -0,0 +1,69 @@
+import type { FullBox } from '../FullBox';
+import type { IsoView } from '../IsoView';
+
+/**
+ * Subsegment range
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type Range = {
+ level: number;
+ rangeSize: number;
+};
+
+/**
+ * Subsegment
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type Subsegment = {
+ rangesCount: number;
+ ranges: Range[];
+};
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.16.4 Subsegment Index Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type SubsegmentIndexBox = FullBox & {
+ subsegmentCount: number;
+ subsegments: Subsegment[];
+};
+
+/**
+ * Parse a SubsegmentIndexBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed SubsegmentIndexBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function ssix(view: IsoView): SubsegmentIndexBox {
+ const { version, flags } = view.readFullBox();
+ const subsegmentCount = view.readUint(4);
+ const subsegments = view.readEntries(subsegmentCount, () => {
+ const rangesCount = view.readUint(4);
+ const ranges = view.readEntries(rangesCount, () => ({
+ level: view.readUint(1),
+ rangeSize: view.readUint(3),
+ }));
+ return { rangesCount, ranges };
+ });
+
+ return {
+ version,
+ flags,
+ subsegmentCount,
+ subsegments,
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/sthd.ts b/lib/src/iso/bmff/parsers/sthd.ts
new file mode 100644
index 00000000..10753c65
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/sthd.ts
@@ -0,0 +1,26 @@
+import type { FullBox } from '../FullBox';
+import type { IsoView } from '../IsoView';
+
+/**
+ * ISO/IEC 14496-12:2015 - 12.6.2 Subtitle media header Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type SubtitleMediaHeaderBox = FullBox;
+
+/**
+ * Parse a SubtitleMediaHeaderBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed SubtitleMediaHeaderBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function sthd(view: IsoView): SubtitleMediaHeaderBox {
+ return view.readFullBox();
+};
diff --git a/lib/src/iso/bmff/parsers/stsd.ts b/lib/src/iso/bmff/parsers/stsd.ts
new file mode 100644
index 00000000..35fdd1db
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/stsd.ts
@@ -0,0 +1,37 @@
+import type { FullBox } from '../FullBox';
+import type { IsoView } from '../IsoView';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.5.2 Sample Description Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type SampleDescriptionBox = FullBox & {
+ entryCount: number,
+ entries: any[],
+};
+
+/**
+ * Parse a SampleDescriptionBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed SampleDescriptionBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function stsd(view: IsoView): SampleDescriptionBox {
+ const { version, flags } = view.readFullBox();
+ const entryCount = view.readUint(4);
+
+ return {
+ version,
+ flags,
+ entryCount,
+ entries: view.readBoxes(entryCount),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/stss.ts b/lib/src/iso/bmff/parsers/stss.ts
new file mode 100644
index 00000000..cf802a9a
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/stss.ts
@@ -0,0 +1,50 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * Sync sample
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type SyncSample = {
+ sampleNumber: number;
+}
+
+/**
+ * ISO/IEC 14496-12:2015 - 8.6.2 Sync Sample Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type SyncSampleBox = FullBox & {
+ entryCount: number;
+ entries: SyncSample[];
+};
+
+/**
+ * Parse a SyncSampleBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed SyncSampleBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function stss(view: IsoView): SyncSampleBox {
+ const { version, flags } = view.readFullBox();
+ const entryCount = view.readUint(4);
+
+ return {
+ version,
+ flags,
+ entryCount,
+ entries: view.readEntries(entryCount, () => ({
+ sampleNumber: view.readUint(4),
+ })),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/sttg.ts b/lib/src/iso/bmff/parsers/sttg.ts
new file mode 100644
index 00000000..95888230
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/sttg.ts
@@ -0,0 +1,29 @@
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-30:2014 - WebVTT Cue Settings Box.
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type WebVTTSettingsBox = {
+ settings: string;
+};
+
+/**
+ * Parse a WebVTTSettingsBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed WebVTTSettingsBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function sttg(view: IsoView): WebVTTSettingsBox {
+ return {
+ settings: view.readUtf8(-1),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/stts.ts b/lib/src/iso/bmff/parsers/stts.ts
new file mode 100644
index 00000000..32a1e345
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/stts.ts
@@ -0,0 +1,59 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * sample
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type DecodingTimeSample = {
+ /** A 32-bit integer that specifies the number of consecutive samples that have the same decoding time delta. */
+ sampleCount: number;
+
+ /** A 32-bit integer that specifies the delta of the decoding time of each sample in the table. */
+ sampleDelta: number;
+};
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.6.1.2 Decoding Time To Sample Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type DecodingTimeToSampleBox = FullBox & {
+ /** A 32-bit integer that specifies the number of entries in the decoding time-to-sample table. */
+ entryCount: number;
+
+ /** An array of decoding time-to-sample entries. */
+ entries: DecodingTimeSample[];
+};
+
+/**
+ * Parse a DecodingTimeToSampleBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed DecodingTimeToSampleBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function stts(view: IsoView): DecodingTimeToSampleBox {
+ const { version, flags } = view.readFullBox();
+ const entryCount = view.readUint(4);
+ const entries = view.readEntries(entryCount, () => ({
+ sampleCount: view.readUint(4),
+ sampleDelta: view.readUint(4),
+ }));
+
+ return {
+ version,
+ flags,
+ entryCount,
+ entries,
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/styp.ts b/lib/src/iso/bmff/parsers/styp.ts
new file mode 100644
index 00000000..f391feda
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/styp.ts
@@ -0,0 +1,25 @@
+import type { BoxParser } from '../BoxParser.js';
+import { type TypeBox } from '../TypeBox.js';
+import { ftyp } from './ftyp.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.16.2 Segment Type Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type SegmentTypeBox = TypeBox;
+
+/**
+ * Parse a SegmentTypeBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed SegmentTypeBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export const styp: BoxParser = ftyp;
diff --git a/lib/src/iso/bmff/parsers/subs.ts b/lib/src/iso/bmff/parsers/subs.ts
new file mode 100644
index 00000000..33df2479
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/subs.ts
@@ -0,0 +1,80 @@
+import type { FullBox } from '../FullBox';
+import type { IsoView } from '../IsoView';
+
+/**
+ * Sub sample
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type SubSample = {
+ subsampleSize: number;
+ subsamplePriority: number;
+ discardable: number;
+ codecSpecificParameters: number;
+}
+
+/**
+ * Sub sample entry
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type SubSampleEntry = {
+ sampleDelta: number;
+ subsampleCount: number;
+ subsamples: SubSample[];
+};
+
+/**
+ * ISO/IEC 14496-12:2015 - 8.7.7 Sub-Sample Information Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type SubSampleInformationBox = FullBox & {
+ entryCount: number;
+ entries: SubSampleEntry[];
+};
+
+/**
+ * Parse a SubSampleInformationBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed SubSampleInformationBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function subs(view: IsoView): SubSampleInformationBox {
+ const { version, flags } = view.readFullBox();
+ const entryCount = view.readUint(4);
+ const entries = view.readEntries(entryCount, () => {
+ const sampleDelta = view.readUint(4);
+ const subsampleCount = view.readUint(2);
+ const subsamples = view.readEntries(subsampleCount, () => ({
+ subsampleSize: view.readUint((version === 1) ? 4 : 2),
+ subsamplePriority: view.readUint(1),
+ discardable: view.readUint(1),
+ codecSpecificParameters: view.readUint(4),
+ }));
+
+ return {
+ sampleDelta,
+ subsampleCount,
+ subsamples,
+ };
+ });
+
+ return {
+ version,
+ flags,
+ entryCount,
+ entries,
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/tenc.ts b/lib/src/iso/bmff/parsers/tenc.ts
new file mode 100644
index 00000000..ec4d057c
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/tenc.ts
@@ -0,0 +1,36 @@
+import { UINT } from '../fields/UINT.js';
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 23001-7:2011 - 8.2 Track Encryption Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type TrackEncryptionBox = FullBox & {
+ defaultIsEncrypted: number;
+ defaultIvSize: number;
+ defaultKid: number[];
+};
+
+/**
+ * Parse a TrackEncryptionBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed TrackEncryptionBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function tenc(view: IsoView): TrackEncryptionBox {
+ return {
+ ...view.readFullBox(),
+ defaultIsEncrypted: view.readUint(3),
+ defaultIvSize: view.readUint(1),
+ defaultKid: view.readArray(UINT, 1, 16),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/tfdt.ts b/lib/src/iso/bmff/parsers/tfdt.ts
new file mode 100644
index 00000000..9191d6ac
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/tfdt.ts
@@ -0,0 +1,34 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.8.12 Track Fragment Decode Time
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type TrackFragmentDecodeTimeBox = FullBox & {
+ baseMediaDecodeTime: number;
+};
+
+/**
+ * Parse a TrackFragmentDecodeTimeBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed TrackFragmentDecodeTimeBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function tfdt(view: IsoView): TrackFragmentDecodeTimeBox {
+ const { version, flags } = view.readFullBox();
+
+ return {
+ version,
+ flags,
+ baseMediaDecodeTime: view.readUint((version == 1) ? 8 : 4),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/tfhd.ts b/lib/src/iso/bmff/parsers/tfhd.ts
new file mode 100644
index 00000000..5bc56bf7
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/tfhd.ts
@@ -0,0 +1,44 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.8.7 Track Fragment Header Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type TrackFragmentHeaderBox = FullBox & {
+ trackId: number;
+ baseDataOffset?: number;
+ sampleDescriptionOffset?: number;
+ defaultSampleDuration?: number;
+ defaultSampleSize?: number;
+ defaultSampleFlags?: number;
+};
+
+/**
+ * Parse a TrackFragmentHeaderBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed TrackFragmentHeaderBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function tfhd(view: IsoView): TrackFragmentHeaderBox {
+ const { version, flags } = view.readFullBox();
+
+ return {
+ version,
+ flags,
+ trackId: view.readUint(4),
+ baseDataOffset: flags & 0x01 ? view.readUint(8) : undefined,
+ sampleDescriptionOffset: flags & 0x02 ? view.readUint(4) : undefined,
+ defaultSampleDuration: flags & 0x08 ? view.readUint(4) : undefined,
+ defaultSampleSize: flags & 0x10 ? view.readUint(4) : undefined,
+ defaultSampleFlags: flags & 0x20 ? view.readUint(4) : undefined,
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/tfra.ts b/lib/src/iso/bmff/parsers/tfra.ts
new file mode 100644
index 00000000..965d40f9
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/tfra.ts
@@ -0,0 +1,77 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView';
+
+/**
+ * Track fragment random access entry
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type TrackFragmentRandomAccessEntry = {
+ time: number;
+ moofOffset: number;
+ trafNumber: number;
+ trunNumber: number;
+ sampleNumber: number;
+}
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.8.10 Track Fragment Random Access Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type TrackFragmentRandomAccessBox = FullBox & {
+ trackId: number;
+ reserved: number;
+ numberOfEntry: number;
+ lengthSizeOfTrafNum: number;
+ lengthSizeOfTrunNum: number;
+ lengthSizeOfSampleNum: number;
+ entries: TrackFragmentRandomAccessEntry[];
+};
+
+/**
+ * Parse a TrackFragmentRandomAccessBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed TrackFragmentRandomAccessBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function tfra(view: IsoView): TrackFragmentRandomAccessBox {
+ const { version, flags } = view.readFullBox();
+ const trackId = view.readUint(4);
+ const reserved = view.readUint(4);
+
+ const lengthSizeOfTrafNum = (reserved & 0x00000030) >> 4;
+ const lengthSizeOfTrunNum = (reserved & 0x0000000C) >> 2;
+ const lengthSizeOfSampleNum = (reserved & 0x00000003);
+
+ const numberOfEntry = view.readUint(4);
+
+ const entries = view.readEntries(numberOfEntry, () => ({
+ time: view.readUint((version === 1) ? 8 : 4),
+ moofOffset: view.readUint((version === 1) ? 8 : 4),
+ trafNumber: view.readUint(lengthSizeOfTrafNum + 1),
+ trunNumber: view.readUint(lengthSizeOfTrunNum + 1),
+ sampleNumber: view.readUint(lengthSizeOfSampleNum + 1),
+ }));
+
+ return {
+ version,
+ flags,
+ trackId,
+ reserved,
+ lengthSizeOfTrafNum,
+ lengthSizeOfTrunNum,
+ lengthSizeOfSampleNum,
+ numberOfEntry,
+ entries,
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/tkhd.ts b/lib/src/iso/bmff/parsers/tkhd.ts
new file mode 100644
index 00000000..21b0b03a
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/tkhd.ts
@@ -0,0 +1,61 @@
+import { TEMPLATE } from '../fields/TEMPLATE.js';
+import { UINT } from '../fields/UINT.js';
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.3.2 Track Header Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type TrackHeaderBox = FullBox & {
+ creationTime: number;
+ modificationTime: number;
+ trackId: number;
+ reserved1: number;
+ duration: number;
+ reserved2: number[];
+ layer: number;
+ alternateGroup: number;
+ volume: number;
+ reserved3: number;
+ matrix: number[];
+ width: number;
+ height: number;
+};
+
+/**
+ * Parse a TrackHeaderBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed TrackHeaderBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function tkhd(view: IsoView): TrackHeaderBox {
+ const { version, flags } = view.readFullBox();
+ const size = version === 1 ? 8 : 4;
+
+ return {
+ version,
+ flags,
+ creationTime: view.readUint(size),
+ modificationTime: view.readUint(size),
+ trackId: view.readUint(4),
+ reserved1: view.readUint(4),
+ duration: view.readUint(size),
+ reserved2: view.readArray(UINT, 4, 2),
+ layer: view.readUint(2),
+ alternateGroup: view.readUint(2),
+ volume: view.readTemplate(2),
+ reserved3: view.readUint(2),
+ matrix: view.readArray(TEMPLATE, 4, 9),
+ width: view.readTemplate(4),
+ height: view.readTemplate(4),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/trex.ts b/lib/src/iso/bmff/parsers/trex.ts
new file mode 100644
index 00000000..b8fd6016
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/trex.ts
@@ -0,0 +1,39 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.8.3 Track Extends Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type TrackExtendsBox = FullBox & {
+ trackId: number;
+ defaultSampleDescriptionIndex: number;
+ defaultSampleDuration: number;
+ defaultSampleSize: number;
+ defaultSampleFlags: number;
+};
+
+/**
+ * Parse a TrackExtendsBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed TrackExtendsBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function trex(view: IsoView): TrackExtendsBox {
+ return {
+ ...view.readFullBox(),
+ trackId: view.readUint(4),
+ defaultSampleDescriptionIndex: view.readUint(4),
+ defaultSampleDuration: view.readUint(4),
+ defaultSampleSize: view.readUint(4),
+ defaultSampleFlags: view.readUint(4),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/trun.ts b/lib/src/iso/bmff/parsers/trun.ts
new file mode 100644
index 00000000..b54e3f36
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/trun.ts
@@ -0,0 +1,87 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * Track run sample
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type TrackRunSample = {
+ sampleDuration?: number;
+ sampleSize?: number;
+ sampleFlags?: number;
+ sampleCompositionTimeOffset?: number;
+}
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.8.8 Track Run Box
+ *
+ * Note: the 'trun' box has a direct relation to the 'tfhd' box for defaults.
+ * These defaults are not set explicitly here, but are left to resolve for the user.
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type TrackRunBox = FullBox & {
+ sampleCount: number;
+ dataOffset?: number;
+ firstSampleFlags?: number;
+ samples: TrackRunSample[];
+};
+
+/**
+ * Parse a TrackRunBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed TrackRunBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function trun(view: IsoView): TrackRunBox {
+ const { version, flags } = view.readFullBox();
+ const sampleCount = view.readUint(4);
+ let dataOffset: number | undefined;
+ let firstSampleFlags: number | undefined;
+
+ if (flags & 0x1) {
+ dataOffset = view.readInt(4);
+ }
+
+ if (flags & 0x4) {
+ firstSampleFlags = view.readUint(4);
+ }
+
+ const samples = view.readEntries(sampleCount, () => {
+ const sample: TrackRunSample = {};
+
+ if (flags & 0x100) {
+ sample.sampleDuration = view.readUint(4);
+ }
+ if (flags & 0x200) {
+ sample.sampleSize = view.readUint(4);
+ }
+ if (flags & 0x400) {
+ sample.sampleFlags = view.readUint(4);
+ }
+ if (flags & 0x800) {
+ sample.sampleCompositionTimeOffset = (version === 1) ? view.readInt(4) : view.readUint(4);
+ }
+
+ return sample;
+ });
+
+ return {
+ version,
+ flags,
+ sampleCount,
+ dataOffset,
+ firstSampleFlags,
+ samples,
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/url.ts b/lib/src/iso/bmff/parsers/url.ts
new file mode 100644
index 00000000..ad6a93b1
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/url.ts
@@ -0,0 +1,31 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.7.2 Data Reference Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type UrlBox = FullBox & {
+ location: string;
+};
+
+/**
+ * Parse a UrlBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed UrlBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function url(view: IsoView): UrlBox {
+ return {
+ ...view.readFullBox(),
+ location: view.readString(-1),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/urn.ts b/lib/src/iso/bmff/parsers/urn.ts
new file mode 100644
index 00000000..95a5eeaf
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/urn.ts
@@ -0,0 +1,33 @@
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.7.2 Data Reference Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type UrnBox = FullBox & {
+ name: string;
+ location: string;
+};
+
+/**
+ * Parse a UrnBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed UrnBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function urn(view: IsoView): UrnBox {
+ return {
+ ...view.readFullBox(),
+ name: view.readString(-1),
+ location: view.readString(-1),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/vlab.ts b/lib/src/iso/bmff/parsers/vlab.ts
new file mode 100644
index 00000000..ce2b7fde
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/vlab.ts
@@ -0,0 +1,29 @@
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-30:2014 - WebVTT Source Label Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type WebVTTSourceLabelBox = {
+ sourceLabel: string;
+};
+
+/**
+ * Parse a WebVTTSourceLabelBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed WebVTTSourceLabelBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function vlab(view: IsoView): WebVTTSourceLabelBox {
+ return {
+ sourceLabel: view.readUtf8(-1),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/vmhd.ts b/lib/src/iso/bmff/parsers/vmhd.ts
new file mode 100644
index 00000000..e6fd3f7d
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/vmhd.ts
@@ -0,0 +1,34 @@
+import { UINT } from '../fields/UINT.js';
+import type { FullBox } from '../FullBox.js';
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-12:2012 - 8.4.5.2 Video Media Header Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type VideoMediaHeaderBox = FullBox & {
+ graphicsmode: number,
+ opcolor: number[],
+};
+
+/**
+ * Parse a VideoMediaHeaderBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed VideoMediaHeaderBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function vmhd(view: IsoView): VideoMediaHeaderBox {
+ return {
+ ...view.readFullBox(),
+ graphicsmode: view.readUint(2),
+ opcolor: view.readArray(UINT, 2, 3),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/vttC.ts b/lib/src/iso/bmff/parsers/vttC.ts
new file mode 100644
index 00000000..d4e1e309
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/vttC.ts
@@ -0,0 +1,29 @@
+import type { IsoView } from '../IsoView.js';
+
+/**
+ * ISO/IEC 14496-30:2014 - WebVTT Configuration Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type WebVTTConfigurationBox = {
+ config: string;
+};
+
+/**
+ * Parse a WebVTTConfigurationBox from an IsoView
+ *
+ * @param view - The IsoView to read data from
+ *
+ * @returns A parsed WebVTTConfigurationBox
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function vttC(view: IsoView): WebVTTConfigurationBox {
+ return {
+ config: view.readUtf8(),
+ };
+};
diff --git a/lib/src/iso/bmff/parsers/vtte.ts b/lib/src/iso/bmff/parsers/vtte.ts
new file mode 100644
index 00000000..c1243a30
--- /dev/null
+++ b/lib/src/iso/bmff/parsers/vtte.ts
@@ -0,0 +1,22 @@
+/**
+ * ISO/IEC 14496-30:2014 - WebVTT Empty Sample Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type WebVTTEmptySampleBox = object;
+
+/**
+ * Parse a WebVTT Empty Sample Box from an IsoView
+ *
+ * @returns A parsed WebVTT Empty Sample Box
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export function vtte(): WebVTTEmptySampleBox {
+ // Nothing should happen here.
+ return {};
+};
diff --git a/lib/src/iso/bmff/readers/ISOFieldType.ts b/lib/src/iso/bmff/readers/ISOFieldType.ts
new file mode 100644
index 00000000..b54522ad
--- /dev/null
+++ b/lib/src/iso/bmff/readers/ISOFieldType.ts
@@ -0,0 +1 @@
+export type ISOFieldType = 'uint' | 'int' | 'template' | 'string' | 'data' | 'utf8';
diff --git a/lib/src/iso/bmff/readers/ISOFieldTypeMap.ts b/lib/src/iso/bmff/readers/ISOFieldTypeMap.ts
new file mode 100644
index 00000000..25f03e07
--- /dev/null
+++ b/lib/src/iso/bmff/readers/ISOFieldTypeMap.ts
@@ -0,0 +1,16 @@
+/**
+ * ISOFieldTypeMap is a map of ISO BMFF field types to their corresponding JavaScript types.
+ *
+ * @group ISOBMFF
+ *
+ * @beta
+ */
+export type ISOFieldTypeMap = {
+ uint: number;
+ int: number;
+ template: number;
+ string: string;
+ data: Uint8Array;
+ utf8: string;
+ utf8string: string;
+};
diff --git a/lib/src/iso/bmff/readers/dataViewToString.ts b/lib/src/iso/bmff/readers/dataViewToString.ts
new file mode 100644
index 00000000..8f10d69c
--- /dev/null
+++ b/lib/src/iso/bmff/readers/dataViewToString.ts
@@ -0,0 +1,49 @@
+export function dataViewToString(dataView: DataView, encoding: string = 'utf-8'): string {
+ if (typeof TextDecoder !== 'undefined') {
+ return new TextDecoder(encoding).decode(dataView);
+ }
+
+ const a: string[] = [];
+ let i = 0;
+
+ if (encoding === 'utf-8') {
+ /* The following algorithm is essentially a rewrite of the UTF8.decode at
+ http://bannister.us/weblog/2007/simple-base64-encodedecode-javascript/
+ */
+
+ while (i < dataView.byteLength) {
+ let c = dataView.getUint8(i++);
+
+ if (c < 0x80) {
+ // 1-byte character (7 bits)
+ }
+ else if (c < 0xe0) {
+ // 2-byte character (11 bits)
+ c = (c & 0x1f) << 6;
+ c |= (dataView.getUint8(i++) & 0x3f);
+ }
+ else if (c < 0xf0) {
+ // 3-byte character (16 bits)
+ c = (c & 0xf) << 12;
+ c |= (dataView.getUint8(i++) & 0x3f) << 6;
+ c |= (dataView.getUint8(i++) & 0x3f);
+ }
+ else {
+ // 4-byte character (21 bits)
+ c = (c & 0x7) << 18;
+ c |= (dataView.getUint8(i++) & 0x3f) << 12;
+ c |= (dataView.getUint8(i++) & 0x3f) << 6;
+ c |= (dataView.getUint8(i++) & 0x3f);
+ }
+
+ a.push(String.fromCharCode(c));
+ }
+ }
+ else { // Just map byte-by-byte (probably wrong)
+ while (i < dataView.byteLength) {
+ a.push(String.fromCharCode(dataView.getUint8(i++)));
+ }
+ }
+
+ return a.join('');
+};
diff --git a/lib/src/iso/bmff/readers/readData.ts b/lib/src/iso/bmff/readers/readData.ts
new file mode 100644
index 00000000..2183e174
--- /dev/null
+++ b/lib/src/iso/bmff/readers/readData.ts
@@ -0,0 +1,4 @@
+export function readData(dataView: DataView, offset: number, size: number): Uint8Array {
+ const length = (size > 0) ? size : (dataView.byteLength - (offset - dataView.byteOffset));
+ return new Uint8Array(dataView.buffer, offset, Math.max(length, 0));
+};
diff --git a/lib/src/iso/bmff/readers/readInt.ts b/lib/src/iso/bmff/readers/readInt.ts
new file mode 100644
index 00000000..84695081
--- /dev/null
+++ b/lib/src/iso/bmff/readers/readInt.ts
@@ -0,0 +1,28 @@
+export function readInt(dataView: DataView, offset: number, size: number): number {
+ let result = NaN;
+ const cursor = offset - dataView.byteOffset;
+
+ switch (size) {
+ case 1:
+ result = dataView.getInt8(cursor);
+ break;
+
+ case 2:
+ result = dataView.getInt16(cursor);
+ break;
+
+ case 4:
+ result = dataView.getInt32(cursor);
+ break;
+
+ case 8:
+ // Warning: JavaScript cannot handle 64-bit integers natively.
+ // This will give unexpected results for integers >= 2^53
+ const s1 = dataView.getInt32(cursor);
+ const s2 = dataView.getInt32(cursor + 4);
+ result = (s1 * Math.pow(2, 32)) + s2;
+ break;
+ }
+
+ return result;
+};
diff --git a/lib/src/iso/bmff/readers/readString.ts b/lib/src/iso/bmff/readers/readString.ts
new file mode 100644
index 00000000..73df2693
--- /dev/null
+++ b/lib/src/iso/bmff/readers/readString.ts
@@ -0,0 +1,13 @@
+import { readUint } from './readUint.js';
+
+export function readString(dataView: DataView, offset: number, length: number): string {
+ let str = '';
+
+ for (let c = 0; c < length; c++) {
+ const cursor = offset + c;
+ const char = readUint(dataView, cursor, 1);
+ str += String.fromCharCode(char);
+ }
+
+ return str;
+}
diff --git a/lib/src/iso/bmff/readers/readTemplate.ts b/lib/src/iso/bmff/readers/readTemplate.ts
new file mode 100644
index 00000000..805dce73
--- /dev/null
+++ b/lib/src/iso/bmff/readers/readTemplate.ts
@@ -0,0 +1,8 @@
+import { readUint } from './readUint.js';
+
+export function readTemplate(dataView: DataView, offset: number, size: number): number {
+ const half = size / 2;
+ const pre = readUint(dataView, offset, half);
+ const post = readUint(dataView, offset + half, half);
+ return pre + (post / Math.pow(2, half));
+};
diff --git a/lib/src/iso/bmff/readers/readTerminatedString.ts b/lib/src/iso/bmff/readers/readTerminatedString.ts
new file mode 100644
index 00000000..b4deb05b
--- /dev/null
+++ b/lib/src/iso/bmff/readers/readTerminatedString.ts
@@ -0,0 +1,18 @@
+import { readUint } from './readUint.js';
+
+export function readTerminatedString(dataView: DataView, offset: number): string {
+ let str = '';
+ let cursor = offset;
+
+ while (cursor - dataView.byteOffset < dataView.byteLength) {
+ const char = readUint(dataView, cursor, 1);
+ if (char === 0) {
+ break;
+ }
+
+ str += String.fromCharCode(char);
+ cursor++;
+ }
+
+ return str;
+};
diff --git a/lib/src/iso/bmff/readers/readUTF8String.ts b/lib/src/iso/bmff/readers/readUTF8String.ts
new file mode 100644
index 00000000..4e50e904
--- /dev/null
+++ b/lib/src/iso/bmff/readers/readUTF8String.ts
@@ -0,0 +1,6 @@
+import { dataViewToString } from './dataViewToString.js';
+
+export function readUTF8String(dataView: DataView, offset: number): string {
+ const length = dataView.byteLength - (offset - dataView.byteOffset);
+ return (length > 0) ? dataViewToString(new DataView(dataView.buffer, offset, length)) : '';
+};
diff --git a/lib/src/iso/bmff/readers/readUTF8TerminatedString.ts b/lib/src/iso/bmff/readers/readUTF8TerminatedString.ts
new file mode 100644
index 00000000..e821b4a1
--- /dev/null
+++ b/lib/src/iso/bmff/readers/readUTF8TerminatedString.ts
@@ -0,0 +1,24 @@
+import { dataViewToString } from './dataViewToString.js';
+
+export function readUTF8TerminatedString(dataView: DataView, offset: number): string {
+ const length = dataView.byteLength - (offset - dataView.byteOffset);
+
+ let data = '';
+
+ if (length > 0) {
+ const view = new DataView(dataView.buffer, offset, length);
+
+ let l = 0;
+
+ for (; l < length; l++) {
+ if (view.getUint8(l) === 0) {
+ break;
+ }
+ }
+
+ // remap the Dataview with the actual length
+ data = dataViewToString(new DataView(dataView.buffer, offset, l));
+ }
+
+ return data;
+};
diff --git a/lib/src/iso/bmff/readers/readUint.ts b/lib/src/iso/bmff/readers/readUint.ts
new file mode 100644
index 00000000..9bb4c4b6
--- /dev/null
+++ b/lib/src/iso/bmff/readers/readUint.ts
@@ -0,0 +1,37 @@
+export function readUint(dataView: DataView, offset: number, size: number): number {
+ const cursor = offset - dataView.byteOffset;
+
+ let value: number = NaN;
+ let s1: number;
+ let s2: number;
+
+ switch (size) {
+ case 1:
+ value = dataView.getUint8(cursor);
+ break;
+
+ case 2:
+ value = dataView.getUint16(cursor);
+ break;
+
+ case 3:
+ s1 = dataView.getUint16(cursor);
+ s2 = dataView.getUint8(cursor + 2);
+ value = (s1 << 8) + s2;
+ break;
+
+ case 4:
+ value = dataView.getUint32(cursor);
+ break;
+
+ case 8:
+ // Warning: JavaScript cannot handle 64-bit integers natively.
+ // This will give unexpected results for integers >= 2^53
+ s1 = dataView.getUint32(cursor);
+ s2 = dataView.getUint32(cursor + 4);
+ value = (s1 * Math.pow(2, 32)) + s2;
+ break;
+ }
+
+ return value;
+};
diff --git a/lib/src/isobmff.ts b/lib/src/isobmff.ts
new file mode 100644
index 00000000..d4167d33
--- /dev/null
+++ b/lib/src/isobmff.ts
@@ -0,0 +1,23 @@
+export * from './iso/bmff/Box.js';
+export * from './iso/bmff/BoxFilter.js';
+export * from './iso/bmff/BoxParser.js';
+export * from './iso/bmff/BoxParserMap.js';
+export * from './iso/bmff/createIsoView.js';
+export * from './iso/bmff/fields/DATA.js';
+export * from './iso/bmff/fields/INT.js';
+export * from './iso/bmff/fields/STRING.js';
+export * from './iso/bmff/fields/TEMPLATE.js';
+export * from './iso/bmff/fields/UINT.js';
+export * from './iso/bmff/fields/UTF8.js';
+export * from './iso/bmff/filterBoxes.js';
+export * from './iso/bmff/filterBoxesByType.js';
+export * from './iso/bmff/findBox.js';
+export * from './iso/bmff/findBoxByType.js';
+export * from './iso/bmff/FullBox.js';
+export * from './iso/bmff/IsoData.js';
+export * from './iso/bmff/IsoView.js';
+export * from './iso/bmff/IsoViewConfig.js';
+export * from './iso/bmff/parseBoxes.js';
+export * from './iso/bmff/parsers.js';
+export * from './iso/bmff/readers/ISOFieldTypeMap.js';
+export * from './iso/bmff/TypeBox.js';
diff --git a/lib/test/id3/data/PTS.ts b/lib/test/id3/data/PTS.ts
index 4d70483b..bb9e5b24 100644
--- a/lib/test/id3/data/PTS.ts
+++ b/lib/test/id3/data/PTS.ts
@@ -10,4 +10,6 @@ export const PTS: Uint8Array = createId3('PRIV', new Uint8Array([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]));
+console.log('PTS', PTS);
+
export const PTS_FRAME: Id3Frame = getId3Frames(PTS)[0];
diff --git a/lib/test/id3/data/createId3.ts b/lib/test/id3/data/createId3.ts
index 44c71e99..ebb32504 100644
--- a/lib/test/id3/data/createId3.ts
+++ b/lib/test/id3/data/createId3.ts
@@ -13,7 +13,7 @@ function createId3Size(size: number) {
}
export function createId3(type: string, data: Uint8Array): Uint8Array {
- return new Uint8Array([
+ const id3 = new Uint8Array([
////////////
// Header //
////////////
@@ -46,4 +46,8 @@ export function createId3(type: string, data: Uint8Array): Uint8Array {
// Payload
...data,
]);
+
+ console.log('ID3_BYTES', id3);
+
+ return id3;
}
diff --git a/lib/test/iso/bmff/ardi.test.ts b/lib/test/iso/bmff/ardi.test.ts
new file mode 100644
index 00000000..2ab4b55b
--- /dev/null
+++ b/lib/test/iso/bmff/ardi.test.ts
@@ -0,0 +1,9 @@
+import { ardi, assert, describe, findBox, it, meta, prsl } from './util/box';
+describe('ardi box', function () {
+ it('should correctly parse the box from sample data', function () {
+ const box = findBox('SRMP_AC4.mp4', [ardi, prsl, meta]);
+ assert.ok(box);
+ assert.strictEqual(box.type, 'ardi');
+ assert.strictEqual(box.audioRenderingIndication <= 4, true);
+ });
+});
diff --git a/lib/test/iso/bmff/avc1.test.ts b/lib/test/iso/bmff/avc1.test.ts
new file mode 100644
index 00000000..51bade8a
--- /dev/null
+++ b/lib/test/iso/bmff/avc1.test.ts
@@ -0,0 +1,30 @@
+import { assert, avc1, describe, filterBoxes, it, stsd } from './util/box';
+
+describe('avc1 box', function () {
+ it('should correctly parse the box', function () {
+ const container = filterBoxes('240fps_go_pro_hero_4.mp4', [stsd, avc1]);
+ const box = container[0].entries[0];
+
+ assert.strictEqual(box.type, 'avc1');
+ assert.strictEqual(box.size, 184);
+
+ assert.deepStrictEqual(box.reserved1, [0, 0, 0, 0, 0, 0]);
+ assert.strictEqual(box.dataReferenceIndex, 1);
+ assert.strictEqual(box.preDefined1, 0);
+ assert.strictEqual(box.reserved2, 0);
+ assert.deepStrictEqual(box.preDefined2, [0, 0, 0]);
+ assert.strictEqual(box.width, 1280);
+ assert.strictEqual(box.height, 720);
+ assert.strictEqual(box.horizresolution, 72);
+ assert.strictEqual(box.vertresolution, 72);
+ assert.strictEqual(box.reserved3, 0);
+ assert.strictEqual(box.frameCount, 1);
+ assert.deepStrictEqual(box.compressorName, [0x11, 0x47, 0x6F, 0x50, 0x72, 0x6F, 0x20, 0x41,
+ 0x56, 0x43, 0x20, 0x65, 0x6E, 0x63, 0x6F, 0x64,
+ 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); // length + 'GoPro AVC encoder'
+ assert.strictEqual(box.depth, 24);
+ assert.strictEqual(box.preDefined3, -1);
+ assert.strictEqual(box.config.byteLength, 98);
+ });
+});
diff --git a/lib/test/iso/bmff/ctts.test.ts b/lib/test/iso/bmff/ctts.test.ts
new file mode 100644
index 00000000..b06a1ad6
--- /dev/null
+++ b/lib/test/iso/bmff/ctts.test.ts
@@ -0,0 +1,27 @@
+import { assert, ctts, describe, filterBoxes, it } from './util/box';
+
+describe('ctts box', function () {
+ it('should correctly parse the box', function () {
+ const boxes = filterBoxes('time_to_sample.mp4', ctts);
+ const box = boxes[0];
+
+ assert.strictEqual(boxes.length, 1);
+
+ assert.strictEqual(box.type, 'ctts');
+
+ assert.strictEqual(box.entryCount, 5);
+
+ const { entries } = box;
+ assert.strictEqual(entries.length, 5);
+ assert.strictEqual(entries[0].sampleCount, 1);
+ assert.strictEqual(entries[0].sampleOffset, 1024);
+ assert.strictEqual(entries[1].sampleCount, 1);
+ assert.strictEqual(entries[1].sampleOffset, 2560);
+ assert.strictEqual(entries[2].sampleCount, 1);
+ assert.strictEqual(entries[2].sampleOffset, 1024);
+ assert.strictEqual(entries[3].sampleCount, 1);
+ assert.strictEqual(entries[3].sampleOffset, 0);
+ assert.strictEqual(entries[4].sampleCount, 1);
+ assert.strictEqual(entries[4].sampleOffset, 512);
+ });
+});
diff --git a/lib/test/iso/bmff/dref.test.ts b/lib/test/iso/bmff/dref.test.ts
new file mode 100644
index 00000000..b0ba3e46
--- /dev/null
+++ b/lib/test/iso/bmff/dref.test.ts
@@ -0,0 +1,18 @@
+import { assert, describe, dref, filterBoxes, it, url } from './util/box';
+
+describe('dref box', function () {
+ it('should correctly parse the box from sample data', function () {
+ const boxes = filterBoxes('captions.mp4', [dref, url]);
+ assert.strictEqual(boxes.length, 1);
+
+ const box = boxes[0];
+ assert.strictEqual(box.type, 'dref');
+ assert.strictEqual(box.entries.length, 1);
+
+ const entry = box.entries[0];
+ assert.strictEqual(entry.type, 'url ');
+ assert.strictEqual(entry.version, 0);
+ assert.strictEqual(entry.flags, 1);
+ assert.strictEqual(entry.location, '');
+ });
+});
diff --git a/lib/test/iso/bmff/elng.test.ts b/lib/test/iso/bmff/elng.test.ts
new file mode 100644
index 00000000..c0227405
--- /dev/null
+++ b/lib/test/iso/bmff/elng.test.ts
@@ -0,0 +1,10 @@
+import { assert, describe, elng, findBox, it, meta, prsl } from './util/box';
+
+describe('elng box', function () {
+ it('should correctly parse the box from sample data', function () {
+ const box = findBox('SRMP_AC4.mp4', [elng, meta, prsl]);
+
+ assert.strictEqual(box.type, 'elng');
+ assert.strictEqual(box.extendedLanguage.localeCompare('en'), 0);
+ });
+});
diff --git a/lib/test/iso/bmff/elst.test.ts b/lib/test/iso/bmff/elst.test.ts
new file mode 100644
index 00000000..c9428234
--- /dev/null
+++ b/lib/test/iso/bmff/elst.test.ts
@@ -0,0 +1,14 @@
+import { assert, describe, elst, findBox, it } from './util/box';
+
+describe('elst box', function () {
+ it('should correctly parse the box', function () {
+ const box = findBox('editlist.mp4', elst);
+
+ assert.strictEqual(box.size, 28);
+ assert.strictEqual(box.entryCount, 1);
+ assert.strictEqual(box.entries[0].segmentDuration, 1000);
+ assert.strictEqual(box.entries[0].mediaRateInteger, 1);
+ assert.strictEqual(box.entries[0].mediaRateFraction, 0);
+ assert.strictEqual(box.entries[0].mediaTime, 1024);
+ });
+});
diff --git a/lib/test/iso/bmff/emsg.test.ts b/lib/test/iso/bmff/emsg.test.ts
new file mode 100644
index 00000000..ab25f491
--- /dev/null
+++ b/lib/test/iso/bmff/emsg.test.ts
@@ -0,0 +1,18 @@
+import { assert, describe, emsg, findBox, it, type EventMessageBox } from './util/box';
+
+describe('emsg box', function () {
+ it('should correctly parse the box from sample data', function () {
+ const box = findBox('emsg.m4s', [emsg]);
+
+ assert.strictEqual(box.type, 'emsg');
+ assert.strictEqual(box.size, 74);
+ assert.strictEqual(box.version, 0);
+ assert.strictEqual(box.schemeIdUri, 'urn:mpeg:dash:event:2012');
+ assert.strictEqual(box.value, '1');
+ assert.strictEqual(box.eventDuration, 65535);
+ assert.strictEqual(box.id, 1);
+ assert.strictEqual(box.timescale, 1);
+ assert.strictEqual(box.presentationTimeDelta, 1);
+ assert.deepEqual(box.messageData, new Uint8Array([50, 48, 49, 52, 45, 48, 54, 45, 49, 49, 84, 49, 51, 58, 48, 54, 58, 50, 52]));
+ });
+});
diff --git a/lib/test/iso/bmff/filterBoxesByType.test.ts b/lib/test/iso/bmff/filterBoxesByType.test.ts
new file mode 100644
index 00000000..f11f825c
--- /dev/null
+++ b/lib/test/iso/bmff/filterBoxesByType.test.ts
@@ -0,0 +1,12 @@
+import { assert, describe, filterBoxesByType, it } from './util/box';
+import { load } from './util/load';
+
+describe('filter boxes by type', function () {
+ it('filter boxes by type', function () {
+ const buffer = load('./captions_fragmented.mp4');
+ const boxes = filterBoxesByType('mdat', buffer);
+
+ assert.strictEqual(boxes.length, 158);
+ assert.strictEqual(boxes.every(box => box.type === 'mdat'), true);
+ });
+});
diff --git a/lib/test/iso/bmff/findBoxByType.test.ts b/lib/test/iso/bmff/findBoxByType.test.ts
new file mode 100644
index 00000000..3e676cdf
--- /dev/null
+++ b/lib/test/iso/bmff/findBoxByType.test.ts
@@ -0,0 +1,13 @@
+import { assert, describe, findBoxByType, it } from './util/box';
+import { load } from './util/load';
+
+describe('find a box by type', function () {
+ it('find a box by type', function () {
+ // Sample 'ftyp' box (20 bytes)
+ const buffer = load('./captions.mp4');
+ const box = findBoxByType('mdat', buffer);
+
+ assert.strictEqual(box.type, 'mdat');
+ assert.strictEqual(box.size, 21530);
+ });
+});
diff --git a/lib/test/iso/bmff/fixtures/240fps_go_pro_hero_4.mp4 b/lib/test/iso/bmff/fixtures/240fps_go_pro_hero_4.mp4
new file mode 100644
index 00000000..1d7d148b
Binary files /dev/null and b/lib/test/iso/bmff/fixtures/240fps_go_pro_hero_4.mp4 differ
diff --git a/lib/test/iso/bmff/fixtures/SRMP_AC4.mp4 b/lib/test/iso/bmff/fixtures/SRMP_AC4.mp4
new file mode 100644
index 00000000..aaa35766
Binary files /dev/null and b/lib/test/iso/bmff/fixtures/SRMP_AC4.mp4 differ
diff --git a/lib/test/iso/bmff/fixtures/captions.mp4 b/lib/test/iso/bmff/fixtures/captions.mp4
new file mode 100644
index 00000000..9ed445bf
Binary files /dev/null and b/lib/test/iso/bmff/fixtures/captions.mp4 differ
diff --git a/lib/test/iso/bmff/fixtures/captions_fragmented.mp4 b/lib/test/iso/bmff/fixtures/captions_fragmented.mp4
new file mode 100644
index 00000000..0425def0
Binary files /dev/null and b/lib/test/iso/bmff/fixtures/captions_fragmented.mp4 differ
diff --git a/lib/test/iso/bmff/fixtures/dash-chunks-prft.m4s b/lib/test/iso/bmff/fixtures/dash-chunks-prft.m4s
new file mode 100644
index 00000000..563438b6
Binary files /dev/null and b/lib/test/iso/bmff/fixtures/dash-chunks-prft.m4s differ
diff --git a/lib/test/iso/bmff/fixtures/editlist.mp4 b/lib/test/iso/bmff/fixtures/editlist.mp4
new file mode 100644
index 00000000..50e445a1
Binary files /dev/null and b/lib/test/iso/bmff/fixtures/editlist.mp4 differ
diff --git a/lib/test/iso/bmff/fixtures/emsg.m4s b/lib/test/iso/bmff/fixtures/emsg.m4s
new file mode 100644
index 00000000..ca4b5025
Binary files /dev/null and b/lib/test/iso/bmff/fixtures/emsg.m4s differ
diff --git a/lib/test/iso/bmff/fixtures/hvc1_init.mp4 b/lib/test/iso/bmff/fixtures/hvc1_init.mp4
new file mode 100644
index 00000000..026693a2
Binary files /dev/null and b/lib/test/iso/bmff/fixtures/hvc1_init.mp4 differ
diff --git a/lib/test/iso/bmff/fixtures/mss_moof_tfdt.mp4 b/lib/test/iso/bmff/fixtures/mss_moof_tfdt.mp4
new file mode 100644
index 00000000..91f3a4cf
Binary files /dev/null and b/lib/test/iso/bmff/fixtures/mss_moof_tfdt.mp4 differ
diff --git a/lib/test/iso/bmff/fixtures/spliced_10000.m4v b/lib/test/iso/bmff/fixtures/spliced_10000.m4v
new file mode 100644
index 00000000..0d5dbb3e
Binary files /dev/null and b/lib/test/iso/bmff/fixtures/spliced_10000.m4v differ
diff --git a/lib/test/iso/bmff/fixtures/subsample.m4s b/lib/test/iso/bmff/fixtures/subsample.m4s
new file mode 100644
index 00000000..2fdb3756
Binary files /dev/null and b/lib/test/iso/bmff/fixtures/subsample.m4s differ
diff --git a/lib/test/iso/bmff/fixtures/test_frag.mp4 b/lib/test/iso/bmff/fixtures/test_frag.mp4
new file mode 100644
index 00000000..4d8b69ba
Binary files /dev/null and b/lib/test/iso/bmff/fixtures/test_frag.mp4 differ
diff --git a/lib/test/iso/bmff/fixtures/time_to_sample.mp4 b/lib/test/iso/bmff/fixtures/time_to_sample.mp4
new file mode 100644
index 00000000..626907ab
Binary files /dev/null and b/lib/test/iso/bmff/fixtures/time_to_sample.mp4 differ
diff --git a/lib/test/iso/bmff/fixtures/webvtt.m4s b/lib/test/iso/bmff/fixtures/webvtt.m4s
new file mode 100644
index 00000000..a48ae159
Binary files /dev/null and b/lib/test/iso/bmff/fixtures/webvtt.m4s differ
diff --git a/lib/test/iso/bmff/free.test.ts b/lib/test/iso/bmff/free.test.ts
new file mode 100644
index 00000000..30e2af4f
--- /dev/null
+++ b/lib/test/iso/bmff/free.test.ts
@@ -0,0 +1,11 @@
+import { assert, describe, free, it, parseBox } from './util/box';
+
+describe('free box', () => {
+ it('should correctly parse the box', () => {
+ const box = parseBox('captions.mp4', free, 3);
+
+ assert.strictEqual(box.type, 'free');
+ assert.strictEqual(box.size, 59);
+ assert.strictEqual(box.data.length, 51);
+ });
+});
diff --git a/lib/test/iso/bmff/ftyp.test.ts b/lib/test/iso/bmff/ftyp.test.ts
new file mode 100644
index 00000000..50e5353d
--- /dev/null
+++ b/lib/test/iso/bmff/ftyp.test.ts
@@ -0,0 +1,13 @@
+import { ftyp } from '@svta/common-media-library';
+import { assert, describe, it, parseBox } from './util/box';
+describe('ftyp box', function () {
+ it('should correctly parse the box', function () {
+ const box = parseBox('captions.mp4', ftyp, 0);
+
+ assert.strictEqual(box.type, 'ftyp');
+ assert.strictEqual(box.size, 20);
+ assert.strictEqual(box.majorBrand, 'isom');
+ assert.strictEqual(box.minorVersion, 1);
+ assert.deepStrictEqual(box.compatibleBrands, ['isom']);
+ });
+});
diff --git a/lib/test/iso/bmff/hdlr.test.ts b/lib/test/iso/bmff/hdlr.test.ts
new file mode 100644
index 00000000..0c8995ad
--- /dev/null
+++ b/lib/test/iso/bmff/hdlr.test.ts
@@ -0,0 +1,23 @@
+import { hdlr } from '@svta/common-media-library';
+import { assert, describe, findBox, it } from './util/box';
+
+describe('hdlr box', function () {
+ it('should correctly parse the box', function () {
+ const box = findBox('captions.mp4', hdlr);
+
+ assert.strictEqual(box.type, 'hdlr');
+ assert.strictEqual(box.size, 68);
+
+ assert.strictEqual(box.preDefined, 0);
+ assert.strictEqual(box.handlerType, 'subt');
+ assert.deepStrictEqual(box.reserved, [0, 0, 0]);
+ assert.strictEqual(box.name, '*xml:ext=ttml@GPAC0.5.1-DEV-rev5545');
+ });
+
+ it('should handle null-terminated strings that are not null-terminated and might exceed box boundaries', function () {
+ const box = findBox('240fps_go_pro_hero_4.mp4', hdlr);
+
+ assert.ok(box);
+ assert.strictEqual(box.name, '\tGoPro AVC');
+ });
+});
diff --git a/lib/test/iso/bmff/hvc1.test.ts b/lib/test/iso/bmff/hvc1.test.ts
new file mode 100644
index 00000000..02454e98
--- /dev/null
+++ b/lib/test/iso/bmff/hvc1.test.ts
@@ -0,0 +1,30 @@
+import { assert, describe, filterBoxes, hvc1, it, stsd } from './util/box';
+
+describe('hvc1 box', function () {
+ it('should correctly parse the box', function () {
+ const container = filterBoxes('hvc1_init.mp4', [stsd, hvc1]);
+ const box = container[0].entries[0];
+
+ assert.strictEqual(box.type, 'hvc1');
+ assert.strictEqual(box.size, 137);
+
+ assert.deepStrictEqual(box.reserved1, [0, 0, 0, 0, 0, 0]);
+ assert.strictEqual(box.dataReferenceIndex, 1);
+ assert.strictEqual(box.preDefined1, 0);
+ assert.strictEqual(box.reserved2, 0);
+ assert.deepStrictEqual(box.preDefined2, [0, 0, 0]);
+ assert.strictEqual(box.width, 192);
+ assert.strictEqual(box.height, 108);
+ assert.strictEqual(box.horizresolution, 72);
+ assert.strictEqual(box.vertresolution, 72);
+ assert.strictEqual(box.reserved3, 0);
+ assert.strictEqual(box.frameCount, 1);
+ assert.deepStrictEqual(box.compressorName, [0x0B, 0x48, 0x45, 0x56, 0x43, 0x20, 0x43, 0x6F,
+ 0x64, 0x69, 0x6E, 0x67, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); // length + 'HEVC Coding'
+ assert.strictEqual(box.depth, 24);
+ assert.strictEqual(box.preDefined3, -1);
+ assert.strictEqual(box.config.byteLength, 51);
+ });
+});
diff --git a/lib/test/iso/bmff/kind.test.ts b/lib/test/iso/bmff/kind.test.ts
new file mode 100644
index 00000000..1e4bdf60
--- /dev/null
+++ b/lib/test/iso/bmff/kind.test.ts
@@ -0,0 +1,11 @@
+import { assert, describe, findBox, it, kind, meta, prsl } from './util/box';
+
+describe('kind box', function () {
+ it('should correctly parse the box from sample data', function () {
+ const box = findBox('SRMP_AC4.mp4', [kind, meta, prsl]);
+
+ assert.strictEqual(box.type, 'kind');
+ assert.strictEqual(box.schemeUri, 'urn:mpeg:dash:role:2011');
+ assert.strictEqual(box.value, 'main');
+ });
+});
diff --git a/lib/test/iso/bmff/labl.test.ts b/lib/test/iso/bmff/labl.test.ts
new file mode 100644
index 00000000..1414f641
--- /dev/null
+++ b/lib/test/iso/bmff/labl.test.ts
@@ -0,0 +1,23 @@
+import { assert, describe, findBox, it, labl, meta, prsl, type Box } from './util/box';
+
+describe('labl box', function () {
+ it('should correctly parse the box from sample data', function () {
+ const box = findBox('SRMP_AC4.mp4', [meta, prsl, labl])
+ .boxes?.filter((box: Box) => box.type === 'grpl')[0]
+ .boxes?.filter((box: Box) => box.groupId === 234)[0];
+
+ assert.ok(box);
+ assert.ok(box.boxes);
+
+ const boxes = box.boxes.filter((box: Box) => box.type === 'labl');
+
+ assert.strictEqual(boxes[0].type, 'labl');
+ assert.strictEqual(boxes[0].isGroupLabel, false);
+ assert.strictEqual(boxes[0].language, 'en');
+ assert.strictEqual(boxes[0].label, 'Spanish');
+ assert.strictEqual(boxes[1].type, 'labl');
+ assert.strictEqual(boxes[1].isGroupLabel, false);
+ assert.strictEqual(boxes[1].language, 'es');
+ assert.strictEqual(boxes[1].label, 'EspaƱol');
+ });
+});
diff --git a/lib/test/iso/bmff/mdat.test.ts b/lib/test/iso/bmff/mdat.test.ts
new file mode 100644
index 00000000..d233b64b
--- /dev/null
+++ b/lib/test/iso/bmff/mdat.test.ts
@@ -0,0 +1,12 @@
+import { mdat } from '@svta/common-media-library';
+import { assert, describe, it, parseBox } from './util/box';
+
+describe('mdat box', function () {
+ it('should correctly parse the mdat box', function () {
+ const box = parseBox('captions.mp4', mdat, 2);
+
+ assert.strictEqual(box.type, 'mdat');
+ assert.strictEqual(box.size, 21530);
+ assert.strictEqual(box.data.byteLength, 21522);
+ });
+});
diff --git a/lib/test/iso/bmff/mdhd.test.ts b/lib/test/iso/bmff/mdhd.test.ts
new file mode 100644
index 00000000..26ab4e3c
--- /dev/null
+++ b/lib/test/iso/bmff/mdhd.test.ts
@@ -0,0 +1,14 @@
+import { assert, describe, findBox, it, mdhd, type MediaHeaderBox } from './util/box';
+
+describe('mdhd box', function () {
+ it('should correctly parse the box from sample data', function () {
+ const box = findBox('captions.mp4', [mdhd]);
+
+ assert.strictEqual(box.creationTime, 3507186411);
+ assert.strictEqual(box.modificationTime, 3507186411);
+ assert.strictEqual(box.timescale, 1000);
+ assert.strictEqual(box.duration, 629800);
+ assert.strictEqual(box.language, 'und');
+ assert.strictEqual(box.preDefined, 0);
+ });
+});
diff --git a/lib/test/iso/bmff/mehd.test.ts b/lib/test/iso/bmff/mehd.test.ts
new file mode 100644
index 00000000..843321b0
--- /dev/null
+++ b/lib/test/iso/bmff/mehd.test.ts
@@ -0,0 +1,12 @@
+import { mehd } from '@svta/common-media-library';
+import { assert, describe, findBox, it } from './util/box';
+
+describe('mehd box', function () {
+ it('should correctly parse the box', function () {
+ const box = findBox('test_frag.mp4', mehd);
+
+ assert.strictEqual(box.type, 'mehd');
+ assert.strictEqual(box.size, 16);
+ assert.strictEqual(box.fragmentDuration, 2047);
+ });
+});
diff --git a/lib/test/iso/bmff/mfro.test.ts b/lib/test/iso/bmff/mfro.test.ts
new file mode 100644
index 00000000..fb88328b
--- /dev/null
+++ b/lib/test/iso/bmff/mfro.test.ts
@@ -0,0 +1,12 @@
+import { mfro } from '@svta/common-media-library';
+import { assert, describe, findBox, it } from './util/box';
+
+describe('mfro box', function () {
+ it('should correctly parse the box', function () {
+ const box = findBox('test_frag.mp4', mfro);
+
+ assert.strictEqual(box.type, 'mfro');
+ assert.strictEqual(box.size, 16);
+ assert.strictEqual(box.mfra_size, 105);
+ });
+});
diff --git a/lib/test/iso/bmff/moov.test.ts b/lib/test/iso/bmff/moov.test.ts
new file mode 100644
index 00000000..b80b4bc9
--- /dev/null
+++ b/lib/test/iso/bmff/moov.test.ts
@@ -0,0 +1,12 @@
+import { assert, describe, it, parseContainer } from './util/box';
+
+describe('moov box', function () {
+ it('should correctly parse the box', function () {
+ const box = parseContainer('captions.mp4', -3);
+
+ assert.ok(box);
+ assert.strictEqual(box.type, 'moov');
+ assert.strictEqual(box.size, 1028);
+ assert.strictEqual(box.boxes.length, 2);
+ });
+});
diff --git a/lib/test/iso/bmff/mp4a.test.ts b/lib/test/iso/bmff/mp4a.test.ts
new file mode 100644
index 00000000..fcd45f98
--- /dev/null
+++ b/lib/test/iso/bmff/mp4a.test.ts
@@ -0,0 +1,21 @@
+import { assert, describe, filterBoxes, it, mp4a, stsd } from './util/box';
+
+describe('mp4a box', function () {
+ it('should correctly parse the box', function () {
+ const container = filterBoxes('240fps_go_pro_hero_4.mp4', [stsd, mp4a]);
+ const box = container[1].entries[0];
+
+ assert.strictEqual(box.type, 'mp4a');
+ assert.strictEqual(box.size, 86);
+
+ assert.deepStrictEqual(box.reserved1, [0, 0, 0, 0, 0, 0]);
+ assert.strictEqual(box.dataReferenceIndex, 1);
+ assert.deepStrictEqual(box.reserved2, [0, 0]);
+ assert.strictEqual(box.channelcount, 2);
+ assert.strictEqual(box.samplesize, 16);
+ //assert.strictEqual(box.pre_defined, 0); // not conformed value in the file, not tested
+ assert.strictEqual(box.reserved3, 0);
+ assert.strictEqual(box.samplerate, 48000);
+ assert.strictEqual(box.esds.byteLength, 50);
+ });
+});
diff --git a/lib/test/iso/bmff/parseBoxes.test.ts b/lib/test/iso/bmff/parseBoxes.test.ts
new file mode 100644
index 00000000..c4c1a66f
--- /dev/null
+++ b/lib/test/iso/bmff/parseBoxes.test.ts
@@ -0,0 +1,24 @@
+import { assert, describe, ftyp, it, parseBoxes } from './util/box';
+
+describe('parseBoxes', function () {
+ it('should parse a buffer', function () {
+ // Sample 'ftyp' box (20 bytes)
+ const arrayBuffer = new Uint8Array([0x00, 0x00, 0x00, 0x14, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x01, 0x69, 0x73, 0x6f, 0x6d]).buffer;
+ const boxes = parseBoxes(arrayBuffer, { parsers: { ftyp } });
+ const box = boxes[0];
+
+ assert.strictEqual(boxes.length, 1);
+ assert.strictEqual(box.type, 'ftyp');
+ assert.strictEqual(box.size, 20);
+ assert.strictEqual(box.majorBrand, 'isom');
+ assert.strictEqual(box.minorVersion, 1);
+ assert.deepEqual(box.compatibleBrands, ['isom']);
+ });
+
+ it('should exit the parser if garbage is zero', function () {
+ const zero = new Uint8Array(1500);
+ const boxes = parseBoxes(zero.buffer);
+ assert.strictEqual(boxes.length, 1);
+ assert.strictEqual(boxes[0].size, 0);
+ });
+});
diff --git a/lib/test/iso/bmff/payl.test.ts b/lib/test/iso/bmff/payl.test.ts
new file mode 100644
index 00000000..38a3975d
--- /dev/null
+++ b/lib/test/iso/bmff/payl.test.ts
@@ -0,0 +1,13 @@
+import { assert, describe, findBox, it, mdat, parseBoxes, payl } from './util/box';
+
+describe('payl box', function () {
+ it('should correctly parse the box from sample data', function () {
+ const { data } = findBox('webvtt.m4s', mdat);
+ const boxes = parseBoxes(data, { parsers: { payl } });
+ const box = boxes[0].boxes?.[0];
+
+ assert.ok(box);
+ assert.strictEqual(box.type, 'payl');
+ assert.strictEqual(box.cueText, "You're a jerk, Thom.\n");
+ });
+});
diff --git a/lib/test/iso/bmff/prft.test.ts b/lib/test/iso/bmff/prft.test.ts
new file mode 100644
index 00000000..c72f998e
--- /dev/null
+++ b/lib/test/iso/bmff/prft.test.ts
@@ -0,0 +1,13 @@
+import { assert, describe, filterBoxes, it, prft } from './util/box';
+
+describe('prft box', function () {
+ it('should correctly parse the box from sample data', function () {
+ const boxes = filterBoxes('dash-chunks-prft.m4s', prft);
+ assert.strictEqual(boxes.length, 60);
+ assert.strictEqual(boxes[0].type, 'prft');
+ assert.strictEqual(boxes[0].referenceTrackId, 1);
+ assert.strictEqual(boxes[0].ntpTimestampSec, 3879495203);
+ assert.strictEqual(boxes[0].ntpTimestampFrac, 197568495);
+ assert.strictEqual(boxes[0].mediaTime, 1355974620);
+ });
+});
diff --git a/lib/test/iso/bmff/prsl.test.ts b/lib/test/iso/bmff/prsl.test.ts
new file mode 100644
index 00000000..33527196
--- /dev/null
+++ b/lib/test/iso/bmff/prsl.test.ts
@@ -0,0 +1,15 @@
+import { assert, describe, filterBoxes, it, meta, prsl } from './util/box';
+
+describe('prsl box', function () {
+ it('should correctly parse the prsl box from sample data', function () {
+ const boxes = filterBoxes('SRMP_AC4.mp4', [prsl, meta]);
+
+ assert.strictEqual(boxes.length, 6);
+ assert.strictEqual(boxes[1].type, 'prsl');
+ assert.strictEqual(boxes[1].groupId, 234);
+ assert.strictEqual(boxes[1].numEntitiesInGroup, 1);
+ assert.strictEqual(boxes[1].entities[0].entityId, 1);
+ assert.strictEqual(boxes[1].preselectionTag, '1');
+ assert.strictEqual(boxes[1].selectionPriority, 1);
+ });
+});
diff --git a/lib/test/iso/bmff/smhd.test.ts b/lib/test/iso/bmff/smhd.test.ts
new file mode 100644
index 00000000..c42b069a
--- /dev/null
+++ b/lib/test/iso/bmff/smhd.test.ts
@@ -0,0 +1,10 @@
+import { assert, describe, filterBoxes, it, smhd } from './util/box';
+
+describe('smhd box', function () {
+ it('should correctly parse the box from sample data', function () {
+ const boxes = filterBoxes('240fps_go_pro_hero_4.mp4', smhd);
+ assert.strictEqual(boxes.length, 1);
+ assert.strictEqual(boxes[0].type, 'smhd');
+ assert.strictEqual(boxes[0].balance, 0.0);
+ });
+});
diff --git a/lib/test/iso/bmff/ssix.test.ts b/lib/test/iso/bmff/ssix.test.ts
new file mode 100644
index 00000000..400cba7c
--- /dev/null
+++ b/lib/test/iso/bmff/ssix.test.ts
@@ -0,0 +1,17 @@
+import { assert, describe, it, parseBox, ssix } from './util/box';
+
+describe('ssix box', function () {
+ it('should correctly parse the box', function () {
+ const box = parseBox('spliced_10000.m4v', ssix, 4);
+
+ assert.strictEqual(box.type, 'ssix');
+ assert.strictEqual(box.size, 8124);
+ assert.strictEqual(box.subsegmentCount, 25);
+ assert.strictEqual(box.subsegments.length, 25);
+
+ // Test one of the subsegments
+ assert.strictEqual(box.subsegments[0].rangesCount, 70);
+ assert.strictEqual(box.subsegments[0].ranges[45].level, 2);
+ assert.strictEqual(box.subsegments[0].ranges[45].rangeSize, 7312);
+ });
+});
diff --git a/lib/test/iso/bmff/stsd.test.ts b/lib/test/iso/bmff/stsd.test.ts
new file mode 100644
index 00000000..bd14e980
--- /dev/null
+++ b/lib/test/iso/bmff/stsd.test.ts
@@ -0,0 +1,12 @@
+import { assert, describe, filterBoxes, it, stsd } from './util/box';
+
+describe('stsd box', function () {
+ it('should correctly parse the box', function () {
+ const boxes = filterBoxes('240fps_go_pro_hero_4.mp4', stsd);
+
+ assert.strictEqual(boxes.length, 3);
+ assert.strictEqual(boxes[0].entries[0].type, 'avc1');
+ assert.strictEqual(boxes[1].entries[0].type, 'mp4a');
+ assert.strictEqual(boxes[2].entries[0].type, 'fdsc');
+ });
+});
diff --git a/lib/test/iso/bmff/stts.test.ts b/lib/test/iso/bmff/stts.test.ts
new file mode 100644
index 00000000..52212fbe
--- /dev/null
+++ b/lib/test/iso/bmff/stts.test.ts
@@ -0,0 +1,18 @@
+import { assert, describe, filterBoxes, it, stts } from './util/box';
+
+describe('stts box', function () {
+ it('should correctly parse the box', function () {
+ const boxes = filterBoxes('editlist.mp4', stts);
+ const box = boxes[0];
+
+ assert.strictEqual(boxes.length, 1);
+
+ assert.strictEqual(box.type, 'stts');
+ assert.strictEqual(box.entryCount, 2);
+ assert.strictEqual(box.entries.length, 2);
+ assert.strictEqual(box.entries[0].sampleCount, 47);
+ assert.strictEqual(box.entries[0].sampleDelta, 1024);
+ assert.strictEqual(box.entries[1].sampleCount, 1);
+ assert.strictEqual(box.entries[1].sampleDelta, 896);
+ });
+});
diff --git a/lib/test/iso/bmff/subs.test.ts b/lib/test/iso/bmff/subs.test.ts
new file mode 100644
index 00000000..ed76e486
--- /dev/null
+++ b/lib/test/iso/bmff/subs.test.ts
@@ -0,0 +1,24 @@
+import { assert, describe, filterBoxes, it, subs } from './util/box';
+
+describe('subs box', function () {
+ it('should correctly parse the box from sample data', function () {
+ const boxes = filterBoxes('subsample.m4s', subs);
+
+ assert.strictEqual(boxes.length, 1);
+ assert.strictEqual(boxes[0].type, 'subs');
+
+ const { entries } = boxes[0];
+ assert.strictEqual(entries.length, 1);
+
+ const entry = entries[0];
+ assert.strictEqual(entry.sampleDelta, 1);
+ assert.strictEqual(entry.subsampleCount, 3);
+ assert.strictEqual(entry.subsamples.length, 3);
+
+ const subsample = entry.subsamples[0];
+ assert.strictEqual(subsample.subsampleSize, 5);
+ assert.strictEqual(subsample.subsamplePriority, 0);
+ assert.strictEqual(subsample.discardable, 0);
+ assert.strictEqual(subsample.codecSpecificParameters, 0);
+ });
+});
diff --git a/lib/test/iso/bmff/trex.test.ts b/lib/test/iso/bmff/trex.test.ts
new file mode 100644
index 00000000..6f960de1
--- /dev/null
+++ b/lib/test/iso/bmff/trex.test.ts
@@ -0,0 +1,25 @@
+import { assert, describe, filterBoxes, it, trex } from './util/box';
+
+describe('trex box', function () {
+ it('should correctly parse the box', function () {
+ const boxes = filterBoxes('test_frag.mp4', trex);
+
+ assert.strictEqual(boxes.length, 2);
+
+ assert.strictEqual(boxes[0].type, 'trex');
+ assert.strictEqual(boxes[0].size, 32);
+ assert.strictEqual(boxes[0].trackId, 1);
+ assert.strictEqual(boxes[0].defaultSampleDescriptionIndex, 1);
+ assert.strictEqual(boxes[0].defaultSampleDuration, 0);
+ assert.strictEqual(boxes[0].defaultSampleSize, 0);
+ assert.strictEqual(boxes[0].defaultSampleFlags, 0);
+
+ assert.strictEqual(boxes[1].type, 'trex');
+ assert.strictEqual(boxes[1].size, 32);
+ assert.strictEqual(boxes[1].trackId, 2);
+ assert.strictEqual(boxes[1].defaultSampleDescriptionIndex, 1);
+ assert.strictEqual(boxes[1].defaultSampleDuration, 0);
+ assert.strictEqual(boxes[1].defaultSampleSize, 0);
+ assert.strictEqual(boxes[1].defaultSampleFlags, 0);
+ });
+});
diff --git a/lib/test/iso/bmff/trun.test.ts b/lib/test/iso/bmff/trun.test.ts
new file mode 100644
index 00000000..a9180347
--- /dev/null
+++ b/lib/test/iso/bmff/trun.test.ts
@@ -0,0 +1,17 @@
+import { assert, describe, findBox, it, trun, type TrackRunBox } from './util/box';
+
+describe('trun box', function () {
+ it('should correctly parse the box from sample data', function () {
+ const box = findBox('mss_moof_tfdt.mp4', [trun]);
+
+ assert.strictEqual(box.sampleCount, 93);
+ assert.strictEqual(box.dataOffset, 856);
+ assert.strictEqual(box.firstSampleFlags, undefined);
+
+ const sample = box.samples[0];
+ assert.strictEqual(sample.sampleDuration, 213334);
+ assert.strictEqual(sample.sampleSize, 247);
+ assert.strictEqual(sample.sampleFlags, undefined);
+ assert.strictEqual(sample.sampleCompositionTimeOffset, undefined);
+ });
+});
diff --git a/lib/test/iso/bmff/util/box.ts b/lib/test/iso/bmff/util/box.ts
new file mode 100644
index 00000000..0a8c4071
--- /dev/null
+++ b/lib/test/iso/bmff/util/box.ts
@@ -0,0 +1,11 @@
+import assert from 'node:assert';
+import { describe, it } from 'node:test';
+import { filterBoxes } from './filterBoxes';
+import { findBox } from './findBox';
+import { parseBox } from './parseBox';
+import { parseContainer } from './parseContainer';
+import { parseFile } from './parseFile';
+
+export * from '@svta/common-media-library/isobmff';
+export { assert, describe, filterBoxes, findBox, it, parseBox, parseContainer, parseFile };
+
diff --git a/lib/test/iso/bmff/util/createParsers.ts b/lib/test/iso/bmff/util/createParsers.ts
new file mode 100644
index 00000000..50cb40d1
--- /dev/null
+++ b/lib/test/iso/bmff/util/createParsers.ts
@@ -0,0 +1,15 @@
+import type { BoxParser, BoxParserMap } from './box';
+
+export function createParsers(parsers: BoxParser | BoxParser[]): { name: string, parsers: BoxParserMap } {
+ if (!Array.isArray(parsers)) {
+ parsers = [parsers];
+ }
+
+ return {
+ name: parsers[0].name,
+ parsers: parsers.reduce((acc, parser) => {
+ acc[parser.name] = parser;
+ return acc;
+ }, {} as BoxParserMap),
+ };
+}
diff --git a/lib/test/iso/bmff/util/filterBoxes.ts b/lib/test/iso/bmff/util/filterBoxes.ts
new file mode 100644
index 00000000..5c0417b1
--- /dev/null
+++ b/lib/test/iso/bmff/util/filterBoxes.ts
@@ -0,0 +1,8 @@
+import { filterBoxesByType, type Box, type BoxParser } from '@svta/common-media-library';
+import { createParsers } from './createParsers';
+import { load } from './load';
+
+export function filterBoxes(file: string, boxParsers: BoxParser | BoxParser[]): Box[] {
+ const { name, parsers } = createParsers(boxParsers);
+ return filterBoxesByType(name, load(file), { parsers, recursive: true });
+}
diff --git a/lib/test/iso/bmff/util/findBox.ts b/lib/test/iso/bmff/util/findBox.ts
new file mode 100644
index 00000000..24ac9e04
--- /dev/null
+++ b/lib/test/iso/bmff/util/findBox.ts
@@ -0,0 +1,13 @@
+import { findBoxByType, type Box, type BoxParser } from '@svta/common-media-library';
+import assert from 'node:assert';
+import { createParsers } from './createParsers';
+import { load } from './load';
+
+export function findBox(file: string, boxParsers: BoxParser | BoxParser[]): Box {
+ const { name, parsers } = createParsers(boxParsers);
+ const box = findBoxByType(name, load(file), { parsers, recursive: true });
+
+ assert.ok(box);
+
+ return box;
+}
diff --git a/lib/test/iso/bmff/util/load.ts b/lib/test/iso/bmff/util/load.ts
new file mode 100644
index 00000000..2460375a
--- /dev/null
+++ b/lib/test/iso/bmff/util/load.ts
@@ -0,0 +1,5 @@
+import fs from 'node:fs';
+
+export function load(file: string): ArrayBuffer {
+ return new Uint8Array(fs.readFileSync(`./test/isobmff/fixtures/${file}`)).buffer;
+}
diff --git a/lib/test/iso/bmff/util/parseBox.ts b/lib/test/iso/bmff/util/parseBox.ts
new file mode 100644
index 00000000..f6d1dc04
--- /dev/null
+++ b/lib/test/iso/bmff/util/parseBox.ts
@@ -0,0 +1,6 @@
+import type { Box, BoxParser } from '@svta/common-media-library';
+import { parseFile } from './parseFile';
+
+export function parseBox(file: string, parser: BoxParser, index: number): Box {
+ return parseFile(file, { parsers: { [parser.name]: parser } })[index];
+}
diff --git a/lib/test/iso/bmff/util/parseContainer.ts b/lib/test/iso/bmff/util/parseContainer.ts
new file mode 100644
index 00000000..f9295881
--- /dev/null
+++ b/lib/test/iso/bmff/util/parseContainer.ts
@@ -0,0 +1,6 @@
+import type { Box } from '@svta/common-media-library';
+import { parseFile } from './parseFile';
+
+export function parseContainer(file: string, index: number): Box | null {
+ return parseFile(file, { recursive: false }).at(index) || null;
+}
diff --git a/lib/test/iso/bmff/util/parseFile.ts b/lib/test/iso/bmff/util/parseFile.ts
new file mode 100644
index 00000000..fe58a48c
--- /dev/null
+++ b/lib/test/iso/bmff/util/parseFile.ts
@@ -0,0 +1,6 @@
+import { parseBoxes, type Box, type IsoViewConfig } from '@svta/common-media-library';
+import { load } from './load';
+
+export function parseFile(file: string, config: IsoViewConfig): Box[] {
+ return parseBoxes(load(file), config);
+}
diff --git a/lib/test/iso/bmff/vmhd.test.ts b/lib/test/iso/bmff/vmhd.test.ts
new file mode 100644
index 00000000..a81ba559
--- /dev/null
+++ b/lib/test/iso/bmff/vmhd.test.ts
@@ -0,0 +1,12 @@
+import { assert, describe, filterBoxes, it, vmhd } from './util/box';
+
+describe('vmhd box', function () {
+ it('should correctly parse the box from sample data', function () {
+ const boxes = filterBoxes('240fps_go_pro_hero_4.mp4', vmhd);
+
+ assert.strictEqual(boxes.length, 1);
+ assert.strictEqual(boxes[0].type, 'vmhd');
+ assert.strictEqual(boxes[0].graphicsmode, 0);
+ assert.deepStrictEqual(boxes[0].opcolor, [0, 0, 0]);
+ });
+});
diff --git a/lib/test/iso/bmff/vttc.test.ts b/lib/test/iso/bmff/vttc.test.ts
new file mode 100644
index 00000000..9e9e98b9
--- /dev/null
+++ b/lib/test/iso/bmff/vttc.test.ts
@@ -0,0 +1,9 @@
+import { assert, describe, findBox, it, mdat, parseBoxes } from './util/box';
+
+describe('vttc box', function () {
+ it('should correctly parse the box from sample data', function () {
+ const { data } = findBox('webvtt.m4s', mdat);
+ const boxes = parseBoxes(data);
+ assert.strictEqual(boxes[0].type, 'vttc');
+ });
+});
diff --git a/lib/test/iso/bmff/vtte.test.ts b/lib/test/iso/bmff/vtte.test.ts
new file mode 100644
index 00000000..39c234c3
--- /dev/null
+++ b/lib/test/iso/bmff/vtte.test.ts
@@ -0,0 +1,10 @@
+import { assert, describe, findBox, it, mdat, parseBoxes, vtte } from './util/box';
+
+describe('vtte box', function () {
+ it('should correctly parse the box from sample data', function () {
+ const { data } = findBox('webvtt.m4s', mdat);
+ const boxes = parseBoxes(data, { parsers: { vtte } });
+ assert.strictEqual(boxes[1].type, 'vtte');
+ });
+});
+
diff --git a/package-lock.json b/package-lock.json
index 1b6f8e20..9c7d2617 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@svta/common-media-library-workspace",
- "version": "0.7.4",
+ "version": "0.9.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@svta/common-media-library-workspace",
- "version": "0.7.4",
+ "version": "0.9.0",
"license": "Apache-2.0",
"workspaces": [
"lib",
@@ -35,7 +35,7 @@
},
"dev": {
"name": "@svta/common-media-library-dev",
- "version": "0.7.4",
+ "version": "0.9.0",
"license": "Apache-2.0",
"devDependencies": {
"@web/dev-server": "0.4.6"
@@ -43,12 +43,12 @@
},
"docs": {
"name": "@svta/common-media-library-docs",
- "version": "0.7.4",
+ "version": "0.9.0",
"license": "Apache-2.0"
},
"lib": {
"name": "@svta/common-media-library",
- "version": "0.7.4",
+ "version": "0.9.0",
"license": "Apache-2.0"
},
"node_modules/@babel/code-frame": {
@@ -6459,7 +6459,7 @@
}
},
"samples/cmaf-ham-conversion": {
- "version": "0.7.4",
+ "version": "0.9.0",
"license": "ISC",
"dependencies": {
"@svta/common-media-library": "*"
diff --git a/package.json b/package.json
index c7ff05e8..09875942 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@svta/common-media-library-workspace",
- "version": "0.7.4",
+ "version": "0.9.0",
"license": "Apache-2.0",
"homepage": "https://github.com/streaming-video-technology-alliance/common-media-library",
"authors": "Casey Occhialini <1508707+littlespex@users.noreply.github.com>",
diff --git a/samples/cmaf-ham-conversion/package.json b/samples/cmaf-ham-conversion/package.json
index 7bf1df76..5de7b2eb 100644
--- a/samples/cmaf-ham-conversion/package.json
+++ b/samples/cmaf-ham-conversion/package.json
@@ -1,6 +1,6 @@
{
"name": "cmaf-ham-conversion",
- "version": "0.7.4",
+ "version": "0.9.0",
"description": "",
"type": "module",
"scripts": {
diff --git a/scripts/build.mts b/scripts/build.mts
index e8bc31e4..2a7895fb 100644
--- a/scripts/build.mts
+++ b/scripts/build.mts
@@ -1,7 +1,5 @@
import { rm } from 'node:fs/promises';
import { cmd } from './cmd.mjs';
-import { removeBlankFiles } from './removeBlankFiles.mjs';
await rm('dist', { recursive: true, force: true });
await cmd('tsc');
-await removeBlankFiles();