init commit

This commit is contained in:
Carlos
2026-02-21 10:33:18 +01:00
parent c863a943ed
commit 9d955bf338
9512 changed files with 2015317 additions and 1305 deletions

View File

@@ -0,0 +1,15 @@
import type { ParserState } from '../../state/parser-state';
import type { QueuedVideoSample } from '../../state/riff/queued-frames';
export declare const convertQueuedSampleToMediaParserSample: ({ sample, state, trackId, }: {
sample: QueuedVideoSample;
state: ParserState;
trackId: number;
}) => {
timestamp: number;
decodingTimestamp: number;
type: "key" | "delta";
data: Uint8Array;
duration: number | undefined;
offset: number;
avc?: import("../../webcodec-sample-types").MediaParserAvcExtraInfo | undefined;
};

View File

@@ -0,0 +1,55 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertQueuedSampleToMediaParserSample = void 0;
const convert_audio_or_video_sample_1 = require("../../convert-audio-or-video-sample");
const get_strh_for_index_1 = require("./get-strh-for-index");
const getKeyFrameOffsetAndPocs = ({ state, sample, trackId, }) => {
var _a, _b;
if (sample.type === 'key') {
const sampleOffset = state.riff.sampleCounter.getSampleCountForTrack({
trackId,
});
return {
sampleOffsetAtKeyframe: sampleOffset,
pocsAtKeyframeOffset: [(_b = (_a = sample.avc) === null || _a === void 0 ? void 0 : _a.poc) !== null && _b !== void 0 ? _b : 0],
};
}
const riffKeyframes = state.riff.sampleCounter.riffKeys.getKeyframes();
const keyframeAtOffset = riffKeyframes.findLast((k) => k.positionInBytes <= sample.offset);
if (!keyframeAtOffset) {
throw new Error('no keyframe at offset');
}
const sampleOffsetAtKeyframe = keyframeAtOffset.sampleCounts[trackId];
const pocsAtKeyframeOffset = state.riff.sampleCounter.getPocAtKeyframeOffset({
keyframeOffset: keyframeAtOffset.positionInBytes,
});
return {
sampleOffsetAtKeyframe,
pocsAtKeyframeOffset,
};
};
const convertQueuedSampleToMediaParserSample = ({ sample, state, trackId, }) => {
const strh = (0, get_strh_for_index_1.getStrhForIndex)(state.structure.getRiffStructure(), trackId);
const samplesPerSecond = strh.rate / strh.scale;
const { sampleOffsetAtKeyframe, pocsAtKeyframeOffset } = getKeyFrameOffsetAndPocs({
sample,
state,
trackId,
});
const indexOfPoc = pocsAtKeyframeOffset.findIndex((poc) => { var _a; return poc === ((_a = sample.avc) === null || _a === void 0 ? void 0 : _a.poc); });
if (indexOfPoc === -1) {
throw new Error('poc not found');
}
const nthSample = indexOfPoc + sampleOffsetAtKeyframe;
const timestamp = nthSample / samplesPerSecond;
const videoSample = (0, convert_audio_or_video_sample_1.convertAudioOrVideoSampleToWebCodecsTimestamps)({
sample: {
...sample,
timestamp,
decodingTimestamp: timestamp,
},
timescale: 1,
});
return videoSample;
};
exports.convertQueuedSampleToMediaParserSample = convertQueuedSampleToMediaParserSample;

View File

@@ -0,0 +1,11 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { ParserState } from '../../state/parser-state';
import type { RiffBox } from './riff-box';
export type RiffResult = {
box: RiffBox | null;
};
export declare const postProcessRiffBox: (state: ParserState, box: RiffBox) => Promise<void>;
export declare const expectRiffBox: ({ iterator, stateIfExpectingSideEffects, }: {
iterator: BufferIterator;
stateIfExpectingSideEffects: ParserState | null;
}) => Promise<RiffBox | null>;

View File

@@ -0,0 +1,75 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.expectRiffBox = exports.postProcessRiffBox = void 0;
const register_track_1 = require("../../register-track");
const get_tracks_from_avi_1 = require("./get-tracks-from-avi");
const has_index_1 = require("./has-index");
const is_movi_1 = require("./is-movi");
const parse_riff_box_1 = require("./parse-riff-box");
const postProcessRiffBox = async (state, box) => {
if (box.type === 'strh-box') {
if (box.strf.type === 'strf-box-audio' && state.onAudioTrack) {
const audioTrack = (0, get_tracks_from_avi_1.makeAviAudioTrack)({
index: state.riff.getNextTrackIndex(),
strf: box.strf,
});
await (0, register_track_1.registerAudioTrack)({
track: audioTrack,
container: 'avi',
registerAudioSampleCallback: state.callbacks.registerAudioSampleCallback,
tracks: state.callbacks.tracks,
logLevel: state.logLevel,
onAudioTrack: state.onAudioTrack,
});
}
if (state.onVideoTrack && box.strf.type === 'strf-box-video') {
const videoTrack = (0, get_tracks_from_avi_1.makeAviVideoTrack)({
strh: box,
index: state.riff.getNextTrackIndex(),
strf: box.strf,
});
(0, register_track_1.registerVideoTrackWhenProfileIsAvailable)({
state,
track: videoTrack,
container: 'avi',
});
}
state.riff.incrementNextTrackIndex();
}
};
exports.postProcessRiffBox = postProcessRiffBox;
const expectRiffBox = async ({ iterator, stateIfExpectingSideEffects, }) => {
// Need at least 16 bytes to read LIST,size,movi,size
if (iterator.bytesRemaining() < 16) {
return null;
}
const checkpoint = iterator.startCheckpoint();
const ckId = iterator.getByteString(4, false);
const ckSize = iterator.getUint32Le();
if ((0, is_movi_1.isMoviAtom)(iterator, ckId)) {
iterator.discard(4);
if (!stateIfExpectingSideEffects) {
throw new Error('No state if expecting side effects');
}
stateIfExpectingSideEffects.mediaSection.addMediaSection({
start: iterator.counter.getOffset(),
size: ckSize - 4,
});
if ((0, has_index_1.riffHasIndex)(stateIfExpectingSideEffects.structure.getRiffStructure())) {
stateIfExpectingSideEffects.riff.lazyIdx1.triggerLoad(iterator.counter.getOffset() + ckSize - 4);
}
return null;
}
if (iterator.bytesRemaining() < ckSize) {
checkpoint.returnToCheckpoint();
return null;
}
const box = await (0, parse_riff_box_1.parseRiffBox)({
id: ckId,
size: ckSize,
iterator,
stateIfExpectingSideEffects,
});
return box;
};
exports.expectRiffBox = expectRiffBox;

View File

@@ -0,0 +1,3 @@
import type { RiffStructure } from './riff-box';
export declare const getDurationFromAvi: (structure: RiffStructure) => number;
export declare const getSampleRateFromAvi: (structure: RiffStructure) => number | null;

View File

@@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSampleRateFromAvi = exports.getDurationFromAvi = void 0;
const traversal_1 = require("./traversal");
const getDurationFromAvi = (structure) => {
const strl = (0, traversal_1.getStrlBoxes)(structure);
const lengths = [];
for (const s of strl) {
const strh = (0, traversal_1.getStrhBox)(s.children);
if (!strh) {
throw new Error('No strh box');
}
const samplesPerSecond = strh.rate / strh.scale;
const streamLength = strh.length / samplesPerSecond;
lengths.push(streamLength);
}
return Math.max(...lengths);
};
exports.getDurationFromAvi = getDurationFromAvi;
const getSampleRateFromAvi = (structure) => {
const strl = (0, traversal_1.getStrlBoxes)(structure);
for (const s of strl) {
const strh = (0, traversal_1.getStrhBox)(s.children);
if (!strh) {
throw new Error('No strh box');
}
if (strh.strf.type === 'strf-box-audio') {
return strh.strf.sampleRate;
}
}
return null;
};
exports.getSampleRateFromAvi = getSampleRateFromAvi;

View File

@@ -0,0 +1,10 @@
import type { AvcState } from '../../state/avc/avc-state';
import type { RiffState } from '../../state/riff';
import type { SeekResolution } from '../../work-on-seek-request';
import type { RiffSeekingHints } from './seeking-hints';
export declare const getSeekingByteForRiff: ({ info, time, riffState, avcState, }: {
info: RiffSeekingHints;
time: number;
riffState: RiffState;
avcState: AvcState;
}) => Promise<SeekResolution>;

View File

@@ -0,0 +1,63 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSeekingByteForRiff = void 0;
const find_last_keyframe_1 = require("../../find-last-keyframe");
const getSeekingByteForRiff = async ({ info, time, riffState, avcState, }) => {
const idx1Entries = await (info.hasIndex
? riffState.lazyIdx1.waitForLoaded()
: Promise.resolve(null));
if (idx1Entries === null) {
const lastKeyframe = (0, find_last_keyframe_1.findLastKeyframe)({
keyframes: info.observedKeyframes,
timeInSeconds: time,
});
if (lastKeyframe === null) {
return {
type: 'valid-but-must-wait',
};
}
riffState.sampleCounter.setSamplesFromSeek(lastKeyframe.sampleCounts);
riffState.queuedBFrames.clear();
avcState.clear();
return {
type: 'do-seek',
byte: lastKeyframe.positionInBytes,
timeInSeconds: Math.min(lastKeyframe.decodingTimeInSeconds, lastKeyframe.presentationTimeInSeconds),
};
}
if (idx1Entries.videoTrackIndex === null) {
throw new Error('videoTrackIndex is null');
}
if (info.samplesPerSecond === null) {
throw new Error('samplesPerSecond is null');
}
const index = Math.floor(time * info.samplesPerSecond);
let bestEntry = null;
for (const entry of idx1Entries.entries) {
if (entry.sampleCounts[idx1Entries.videoTrackIndex] > index) {
continue;
}
if (bestEntry &&
entry.sampleCounts[idx1Entries.videoTrackIndex] <
bestEntry.sampleCounts[idx1Entries.videoTrackIndex]) {
continue;
}
bestEntry = entry;
}
if (!bestEntry) {
throw new Error('No best entry');
}
if (info.moviOffset === null) {
throw new Error('moviOffset is null');
}
riffState.sampleCounter.setSamplesFromSeek(bestEntry.sampleCounts);
riffState.queuedBFrames.clear();
avcState.clear();
return {
type: 'do-seek',
byte: bestEntry.offset + info.moviOffset - 4,
timeInSeconds: bestEntry.sampleCounts[idx1Entries.videoTrackIndex] /
info.samplesPerSecond,
};
};
exports.getSeekingByteForRiff = getSeekingByteForRiff;

View File

@@ -0,0 +1,2 @@
import type { RiffStructure, StrhBox } from './riff-box';
export declare const getStrhForIndex: (structure: RiffStructure, trackId: number) => StrhBox;

View File

@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getStrhForIndex = void 0;
const traversal_1 = require("./traversal");
const getStrhForIndex = (structure, trackId) => {
const boxes = (0, traversal_1.getStrlBoxes)(structure);
const box = boxes[trackId];
if (!box) {
throw new Error('Expected box');
}
const strh = (0, traversal_1.getStrhBox)(box.children);
if (!strh) {
throw new Error('strh');
}
return strh;
};
exports.getStrhForIndex = getStrhForIndex;

View File

@@ -0,0 +1,16 @@
import type { MediaParserAudioTrack, MediaParserTrack, MediaParserVideoTrack } from '../../get-tracks';
import type { ParserState } from '../../state/parser-state';
import type { RiffStructure, StrfBoxAudio, StrfBoxVideo, StrhBox } from './riff-box';
export declare const TO_BE_OVERRIDDEN_LATER = "to-be-overriden-later";
export declare const getNumberOfTracks: (structure: RiffStructure) => number;
export declare const makeAviAudioTrack: ({ strf, index, }: {
strf: StrfBoxAudio;
index: number;
}) => MediaParserAudioTrack;
export declare const makeAviVideoTrack: ({ strh, strf, index, }: {
strh: StrhBox;
strf: StrfBoxVideo;
index: number;
}) => MediaParserVideoTrack;
export declare const getTracksFromAvi: (structure: RiffStructure, state: ParserState) => MediaParserTrack[];
export declare const hasAllTracksFromAvi: (state: ParserState) => boolean;

View File

@@ -0,0 +1,117 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasAllTracksFromAvi = exports.getTracksFromAvi = exports.makeAviVideoTrack = exports.makeAviAudioTrack = exports.getNumberOfTracks = exports.TO_BE_OVERRIDDEN_LATER = void 0;
const add_avc_profile_to_track_1 = require("../../add-avc-profile-to-track");
const webcodecs_timescale_1 = require("../../webcodecs-timescale");
const timescale_1 = require("./timescale");
const traversal_1 = require("./traversal");
exports.TO_BE_OVERRIDDEN_LATER = 'to-be-overriden-later';
const getNumberOfTracks = (structure) => {
const avihBox = (0, traversal_1.getAvihBox)(structure);
if (avihBox) {
return avihBox.streams;
}
throw new Error('No avih box found');
};
exports.getNumberOfTracks = getNumberOfTracks;
const makeAviAudioTrack = ({ strf, index, }) => {
// 255 = AAC
if (strf.formatTag !== 255) {
throw new Error(`Unsupported audio format ${strf.formatTag}`);
}
return {
type: 'audio',
codec: 'mp4a.40.2', // According to Claude 3.5 Sonnet
codecData: { type: 'aac-config', data: new Uint8Array([18, 16]) },
codecEnum: 'aac',
description: new Uint8Array([18, 16]),
numberOfChannels: strf.numberOfChannels,
sampleRate: strf.sampleRate,
originalTimescale: timescale_1.MEDIA_PARSER_RIFF_TIMESCALE,
trackId: index,
startInSeconds: 0,
timescale: webcodecs_timescale_1.WEBCODECS_TIMESCALE,
trackMediaTimeOffsetInTrackTimescale: 0,
};
};
exports.makeAviAudioTrack = makeAviAudioTrack;
const makeAviVideoTrack = ({ strh, strf, index, }) => {
if (strh.handler !== 'H264') {
throw new Error(`Unsupported video codec ${strh.handler}`);
}
return {
codecData: null,
codec: exports.TO_BE_OVERRIDDEN_LATER,
codecEnum: 'h264',
codedHeight: strf.height,
codedWidth: strf.width,
width: strf.width,
height: strf.height,
type: 'video',
displayAspectHeight: strf.height,
originalTimescale: timescale_1.MEDIA_PARSER_RIFF_TIMESCALE,
description: undefined,
m3uStreamFormat: null,
trackId: index,
colorSpace: {
fullRange: null,
matrix: null,
primaries: null,
transfer: null,
},
advancedColor: {
fullRange: null,
matrix: null,
primaries: null,
transfer: null,
},
displayAspectWidth: strf.width,
rotation: 0,
sampleAspectRatio: {
numerator: 1,
denominator: 1,
},
fps: strh.rate / strh.scale,
startInSeconds: 0,
timescale: webcodecs_timescale_1.WEBCODECS_TIMESCALE,
trackMediaTimeOffsetInTrackTimescale: 0,
};
};
exports.makeAviVideoTrack = makeAviVideoTrack;
const getTracksFromAvi = (structure, state) => {
const tracks = [];
const boxes = (0, traversal_1.getStrlBoxes)(structure);
let i = 0;
for (const box of boxes) {
const strh = (0, traversal_1.getStrhBox)(box.children);
if (!strh) {
continue;
}
const { strf } = strh;
if (strf.type === 'strf-box-video') {
tracks.push((0, add_avc_profile_to_track_1.addAvcProfileToTrack)((0, exports.makeAviVideoTrack)({ strh, strf, index: i }), state.riff.getAvcProfile()));
}
else if (strh.fccType === 'auds') {
tracks.push((0, exports.makeAviAudioTrack)({ strf, index: i }));
}
else {
throw new Error(`Unsupported track type ${strh.fccType}`);
}
i++;
}
return tracks;
};
exports.getTracksFromAvi = getTracksFromAvi;
const hasAllTracksFromAvi = (state) => {
try {
const structure = state.structure.getRiffStructure();
const numberOfTracks = (0, exports.getNumberOfTracks)(structure);
const tracks = (0, exports.getTracksFromAvi)(structure, state);
return (tracks.length === numberOfTracks &&
!tracks.find((t) => t.type === 'video' && t.codec === exports.TO_BE_OVERRIDDEN_LATER));
}
catch (_a) {
return false;
}
};
exports.hasAllTracksFromAvi = hasAllTracksFromAvi;

View File

@@ -0,0 +1,2 @@
import type { RiffStructure } from './riff-box';
export declare const riffHasIndex: (structure: RiffStructure) => boolean;

View File

@@ -0,0 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.riffHasIndex = void 0;
const riffHasIndex = (structure) => {
var _a, _b, _c;
return ((_c = (_b = (_a = structure.boxes.find((b) => b.type === 'list-box' && b.listType === 'hdrl')) === null || _a === void 0 ? void 0 : _a.children.find((box) => box.type === 'avih-box')) === null || _b === void 0 ? void 0 : _b.hasIndex) !== null && _c !== void 0 ? _c : false);
};
exports.riffHasIndex = riffHasIndex;

View File

@@ -0,0 +1,2 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
export declare const isMoviAtom: (iterator: BufferIterator, ckId: string) => boolean;

View File

@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isMoviAtom = void 0;
const isMoviAtom = (iterator, ckId) => {
if (ckId !== 'LIST') {
return false;
}
const listType = iterator.getByteString(4, false);
iterator.counter.decrement(4);
return listType === 'movi';
};
exports.isMoviAtom = isMoviAtom;

View File

@@ -0,0 +1,6 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { RiffBox } from './riff-box';
export declare const parseAvih: ({ iterator, size, }: {
iterator: BufferIterator;
size: number;
}) => RiffBox;

View File

@@ -0,0 +1,35 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseAvih = void 0;
const AVIF_HAS_INDEX = 0x00000010;
const parseAvih = ({ iterator, size, }) => {
const { expectNoMoreBytes } = iterator.startBox(size);
const dwMicroSecPerFrame = iterator.getUint32Le();
const dwMaxBytesPerSec = iterator.getUint32Le();
const paddingGranularity = iterator.getUint32Le();
const flags = iterator.getUint32Le();
const totalFrames = iterator.getUint32Le();
const initialFrames = iterator.getUint32Le();
const streams = iterator.getUint32Le();
const suggestedBufferSize = iterator.getUint32Le();
const width = iterator.getUint32Le();
const height = iterator.getUint32Le();
const hasIndex = (flags & AVIF_HAS_INDEX) !== 0;
iterator.discard(16);
expectNoMoreBytes();
return {
type: 'avih-box',
hasIndex,
microSecPerFrame: dwMicroSecPerFrame,
maxBytesPerSecond: dwMaxBytesPerSec,
paddingGranularity,
flags,
totalFrames,
initialFrames,
streams,
suggestedBufferSize,
height,
width,
};
};
exports.parseAvih = parseAvih;

View File

@@ -0,0 +1,6 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { RiffBox } from './riff-box';
export declare const parseIdx1: ({ iterator, size, }: {
iterator: BufferIterator;
size: number;
}) => RiffBox;

View File

@@ -0,0 +1,47 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseIdx1 = void 0;
const AVIIF_KEYFRAME = 0x00000010;
const parseIdx1 = ({ iterator, size, }) => {
const box = iterator.startBox(size);
const offset = iterator.counter.getOffset();
const entries = [];
const sampleCounts = {};
let videoTrackIndex = null;
while (iterator.counter.getOffset() < offset + size) {
const chunkId = iterator.getByteString(4, false);
const flags = iterator.getUint32Le();
const moffset = iterator.getUint32Le();
const msize = iterator.getUint32Le();
const chunk = chunkId.match(/^([0-9]{2})(wb|dc)$/);
const isVideo = chunkId.endsWith('dc');
if (isVideo) {
videoTrackIndex = chunk ? parseInt(chunk[1], 10) : null;
}
const trackId = chunk ? parseInt(chunk[1], 10) : null;
if (trackId === null) {
continue;
}
if (!sampleCounts[trackId]) {
sampleCounts[trackId] = 0;
}
const isKeyFrame = (flags & AVIIF_KEYFRAME) !== 0;
if (isKeyFrame) {
entries.push({
flags,
id: chunkId,
offset: moffset,
size: msize,
sampleCounts: { ...sampleCounts },
});
}
sampleCounts[trackId]++;
}
box.expectNoMoreBytes();
return {
type: 'idx1-box',
entries,
videoTrackIndex,
};
};
exports.parseIdx1 = parseIdx1;

View File

@@ -0,0 +1,6 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { IsftBox } from './riff-box';
export declare const parseIsft: ({ iterator, size, }: {
iterator: BufferIterator;
size: number;
}) => IsftBox;

View File

@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseIsft = void 0;
const parseIsft = ({ iterator, size, }) => {
const { expectNoMoreBytes } = iterator.startBox(size);
const software = iterator.getByteString(size - 1, false);
const last = iterator.getUint8();
if (last !== 0) {
throw new Error(`Expected 0 byte, got ${last}`);
}
expectNoMoreBytes();
return {
type: 'isft-box',
software,
};
};
exports.parseIsft = parseIsft;

View File

@@ -0,0 +1,8 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { ParserState } from '../../state/parser-state';
import type { RiffBox } from './riff-box';
export declare const parseListBox: ({ size, iterator, stateIfExpectingSideEffects, }: {
size: number;
iterator: BufferIterator;
stateIfExpectingSideEffects: ParserState | null;
}) => Promise<RiffBox>;

View File

@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseListBox = void 0;
const expect_riff_box_1 = require("./expect-riff-box");
const parseListBox = async ({ size, iterator, stateIfExpectingSideEffects, }) => {
const counter = iterator.counter.getOffset();
const listType = iterator.getByteString(4, false);
if (listType === 'movi') {
throw new Error('should not be handled here');
}
const boxes = [];
const maxOffset = counter + size;
while (iterator.counter.getOffset() < maxOffset) {
const box = await (0, expect_riff_box_1.expectRiffBox)({
iterator,
stateIfExpectingSideEffects,
});
if (box === null) {
throw new Error('Unexpected result');
}
if (stateIfExpectingSideEffects) {
await (0, expect_riff_box_1.postProcessRiffBox)(stateIfExpectingSideEffects, box);
}
boxes.push(box);
}
return {
type: 'list-box',
listType,
children: boxes,
};
};
exports.parseListBox = parseListBox;

View File

@@ -0,0 +1,9 @@
import type { ParserState } from '../../state/parser-state';
export declare const handleChunk: ({ state, ckId, ckSize, }: {
state: ParserState;
ckId: string;
ckSize: number;
}) => Promise<void>;
export declare const parseMovi: ({ state, }: {
state: ParserState;
}) => Promise<void>;

View File

@@ -0,0 +1,135 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseMovi = exports.handleChunk = void 0;
const webcodecs_timescale_1 = require("../../webcodecs-timescale");
const key_1 = require("../avc/key");
const parse_avc_1 = require("../avc/parse-avc");
const convert_queued_sample_to_mediaparser_sample_1 = require("./convert-queued-sample-to-mediaparser-sample");
const get_strh_for_index_1 = require("./get-strh-for-index");
const handleChunk = async ({ state, ckId, ckSize, }) => {
var _a;
const { iterator } = state;
const offset = iterator.counter.getOffset() - 8;
const videoChunk = ckId.match(/^([0-9]{2})dc$/);
if (videoChunk) {
const trackId = parseInt(videoChunk[1], 10);
const strh = (0, get_strh_for_index_1.getStrhForIndex)(state.structure.getRiffStructure(), trackId);
const samplesPerSecond = strh.rate / strh.scale;
const data = iterator.getSlice(ckSize);
const infos = (0, parse_avc_1.parseAvc)(data, state.avc);
const keyOrDelta = (0, key_1.getKeyFrameOrDeltaFromAvcInfo)(infos);
const info = infos.find((i) => i.type === 'keyframe' || i.type === 'delta-frame');
const avcProfile = infos.find((i) => i.type === 'avc-profile');
const ppsProfile = infos.find((i) => i.type === 'avc-pps');
if (avcProfile && ppsProfile && !state.riff.getAvcProfile()) {
await state.riff.onProfile({ pps: ppsProfile, sps: avcProfile });
state.callbacks.tracks.setIsDone(state.logLevel);
}
const rawSample = {
data,
// We must also NOT pass a duration because if the the next sample is 0,
// this sample would be longer. Chrome will pad it with silence.
// If we'd pass a duration instead, it would shift the audio and we think that audio is not finished
duration: 1 / samplesPerSecond,
type: keyOrDelta === 'bidirectional' ? 'delta' : keyOrDelta,
offset,
avc: info,
};
const maxFramesInBuffer = state.avc.getMaxFramesInBuffer();
if (maxFramesInBuffer === null) {
throw new Error('maxFramesInBuffer is null');
}
if (((_a = info === null || info === void 0 ? void 0 : info.poc) !== null && _a !== void 0 ? _a : null) === null) {
throw new Error('poc is null');
}
const keyframeOffset = state.riff.sampleCounter.getKeyframeAtOffset(rawSample);
if (keyframeOffset !== null) {
state.riff.sampleCounter.setPocAtKeyframeOffset({
keyframeOffset,
poc: info.poc,
});
}
state.riff.queuedBFrames.addFrame({
frame: rawSample,
trackId,
maxFramesInBuffer,
timescale: samplesPerSecond,
});
const releasedFrame = state.riff.queuedBFrames.getReleasedFrame();
if (!releasedFrame) {
return;
}
const videoSample = (0, convert_queued_sample_to_mediaparser_sample_1.convertQueuedSampleToMediaParserSample)({
sample: releasedFrame.sample,
state,
trackId: releasedFrame.trackId,
});
state.riff.sampleCounter.onVideoSample({
trackId,
videoSample,
});
await state.callbacks.onVideoSample({
videoSample,
trackId,
});
}
const audioChunk = ckId.match(/^([0-9]{2})wb$/);
if (audioChunk) {
const trackId = parseInt(audioChunk[1], 10);
const strh = (0, get_strh_for_index_1.getStrhForIndex)(state.structure.getRiffStructure(), trackId);
const { strf } = strh;
if (strf.type !== 'strf-box-audio') {
throw new Error('audio');
}
const samplesPerSecond = (strh.rate / strh.scale) * strf.numberOfChannels;
const nthSample = state.riff.sampleCounter.getSampleCountForTrack({
trackId,
});
const timeInSec = nthSample / samplesPerSecond;
const timestamp = Math.floor(timeInSec * webcodecs_timescale_1.WEBCODECS_TIMESCALE);
const data = iterator.getSlice(ckSize);
const audioSample = {
decodingTimestamp: timestamp,
data, // We must also NOT pass a duration because if the the next sample is 0,
// this sample would be longer. Chrome will pad it with silence.
// If we'd pass a duration instead, it would shift the audio and we think that audio is not finished
duration: undefined,
timestamp,
type: 'key',
offset,
};
state.riff.sampleCounter.onAudioSample(trackId, audioSample);
// In example.avi, we have samples with 0 data
// Chrome fails on these
await state.callbacks.onAudioSample({
audioSample,
trackId,
});
}
};
exports.handleChunk = handleChunk;
const parseMovi = async ({ state, }) => {
const { iterator } = state;
if (iterator.bytesRemaining() < 8) {
return Promise.resolve();
}
const checkpoint = iterator.startCheckpoint();
const ckId = iterator.getByteString(4, false);
const ckSize = iterator.getUint32Le();
if (iterator.bytesRemaining() < ckSize) {
checkpoint.returnToCheckpoint();
return Promise.resolve();
}
await (0, exports.handleChunk)({ state, ckId, ckSize });
const mediaSection = state.mediaSection.getMediaSectionAssertOnlyOne();
const maxOffset = mediaSection.start + mediaSection.size;
// Discard added zeroes
while (iterator.counter.getOffset() < maxOffset &&
iterator.bytesRemaining() > 0) {
if (iterator.getUint8() !== 0) {
iterator.counter.decrement(1);
break;
}
}
};
exports.parseMovi = parseMovi;

View File

@@ -0,0 +1,3 @@
import type { ParseResult } from '../../parse-result';
import type { ParserState } from '../../state/parser-state';
export declare const parseRiffBody: (state: ParserState) => Promise<ParseResult>;

View File

@@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseRiffBody = void 0;
const skip_1 = require("../../skip");
const may_skip_video_data_1 = require("../../state/may-skip-video-data");
const video_section_1 = require("../../state/video-section");
const convert_queued_sample_to_mediaparser_sample_1 = require("./convert-queued-sample-to-mediaparser-sample");
const expect_riff_box_1 = require("./expect-riff-box");
const parse_video_section_1 = require("./parse-video-section");
const parseRiffBody = async (state) => {
const releasedFrame = state.riff.queuedBFrames.getReleasedFrame();
if (releasedFrame) {
const converted = (0, convert_queued_sample_to_mediaparser_sample_1.convertQueuedSampleToMediaParserSample)({
sample: releasedFrame.sample,
state,
trackId: releasedFrame.trackId,
});
state.riff.sampleCounter.onVideoSample({
trackId: releasedFrame.trackId,
videoSample: converted,
});
await state.callbacks.onVideoSample({
videoSample: converted,
trackId: releasedFrame.trackId,
});
return null;
}
if (state.mediaSection.isCurrentByteInMediaSection(state.iterator) ===
'in-section') {
if ((0, may_skip_video_data_1.maySkipVideoData)({
state,
}) &&
state.riff.getAvcProfile()) {
const mediaSection = (0, video_section_1.getCurrentMediaSection)({
offset: state.iterator.counter.getOffset(),
mediaSections: state.mediaSection.getMediaSections(),
});
if (!mediaSection) {
throw new Error('No video section defined');
}
// only skipping forward in query mode
return Promise.resolve((0, skip_1.makeSkip)(mediaSection.start + mediaSection.size));
}
await (0, parse_video_section_1.parseMediaSection)(state);
return null;
}
const box = await (0, expect_riff_box_1.expectRiffBox)({
iterator: state.iterator,
stateIfExpectingSideEffects: state,
});
if (box !== null) {
await (0, expect_riff_box_1.postProcessRiffBox)(state, box);
const structure = state.structure.getRiffStructure();
structure.boxes.push(box);
}
return null;
};
exports.parseRiffBody = parseRiffBody;

View File

@@ -0,0 +1,9 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { ParserState } from '../../state/parser-state';
import type { RiffBox } from './riff-box';
export declare const parseRiffBox: ({ size, id, iterator, stateIfExpectingSideEffects, }: {
size: number;
id: string;
iterator: BufferIterator;
stateIfExpectingSideEffects: ParserState | null;
}) => Promise<RiffBox>;

View File

@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseRiffBox = void 0;
const parse_avih_1 = require("./parse-avih");
const parse_idx1_1 = require("./parse-idx1");
const parse_isft_1 = require("./parse-isft");
const parse_list_box_1 = require("./parse-list-box");
const parse_strh_1 = require("./parse-strh");
const parseRiffBox = ({ size, id, iterator, stateIfExpectingSideEffects, }) => {
if (id === 'LIST') {
return (0, parse_list_box_1.parseListBox)({
size,
iterator,
stateIfExpectingSideEffects,
});
}
if (id === 'ISFT') {
return Promise.resolve((0, parse_isft_1.parseIsft)({ iterator, size }));
}
if (id === 'avih') {
return Promise.resolve((0, parse_avih_1.parseAvih)({ iterator, size }));
}
if (id === 'strh') {
return Promise.resolve((0, parse_strh_1.parseStrh)({ iterator, size }));
}
if (id === 'idx1') {
return Promise.resolve((0, parse_idx1_1.parseIdx1)({ iterator, size }));
}
iterator.discard(size);
const box = {
type: 'riff-box',
size,
id,
};
return Promise.resolve(box);
};
exports.parseRiffBox = parseRiffBox;

View File

@@ -0,0 +1,3 @@
import type { ParseResult } from '../../parse-result';
import type { ParserState } from '../../state/parser-state';
export declare const parseRiffHeader: (state: ParserState) => ParseResult;

View File

@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseRiffHeader = void 0;
const parseRiffHeader = (state) => {
const riff = state.iterator.getByteString(4, false);
if (riff !== 'RIFF') {
throw new Error('Not a RIFF file');
}
const structure = state.structure.getRiffStructure();
const size = state.iterator.getUint32Le();
const fileType = state.iterator.getByteString(4, false);
if (fileType !== 'WAVE' && fileType !== 'AVI') {
throw new Error(`File type ${fileType} not supported`);
}
structure.boxes.push({ type: 'riff-header', fileSize: size, fileType });
return null;
};
exports.parseRiffHeader = parseRiffHeader;

View File

@@ -0,0 +1,3 @@
import type { ParseResult } from '../../parse-result';
import type { ParserState } from '../../state/parser-state';
export declare const parseRiff: (state: ParserState) => Promise<ParseResult>;

View File

@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseRiff = void 0;
const parse_riff_body_1 = require("./parse-riff-body");
const parse_riff_header_1 = require("./parse-riff-header");
const parseRiff = (state) => {
if (state.iterator.counter.getOffset() === 0) {
return Promise.resolve((0, parse_riff_header_1.parseRiffHeader)(state));
}
return (0, parse_riff_body_1.parseRiffBody)(state);
};
exports.parseRiff = parseRiff;

View File

@@ -0,0 +1,7 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { FccType, StrfBoxAudio, StrfBoxVideo } from './riff-box';
export declare const parseStrf: ({ iterator, size, fccType, }: {
iterator: BufferIterator;
size: number;
fccType: FccType;
}) => StrfBoxAudio | StrfBoxVideo;

View File

@@ -0,0 +1,63 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseStrf = void 0;
const parseStrfAudio = ({ iterator, size, }) => {
const box = iterator.startBox(size);
const formatTag = iterator.getUint16Le();
const numberOfChannels = iterator.getUint16Le();
const samplesPerSec = iterator.getUint32Le();
const avgBytesPerSec = iterator.getUint32Le();
const blockAlign = iterator.getUint16Le();
const bitsPerSample = iterator.getUint16Le();
const cbSize = iterator.getUint16Le();
box.expectNoMoreBytes();
return {
type: 'strf-box-audio',
avgBytesPerSecond: avgBytesPerSec,
bitsPerSample,
blockAlign,
cbSize,
formatTag,
numberOfChannels,
sampleRate: samplesPerSec,
};
};
const parseStrfVideo = ({ iterator, size, }) => {
const box = iterator.startBox(size);
const biSize = iterator.getUint32Le();
const width = iterator.getInt32Le();
const height = iterator.getInt32Le();
const planes = iterator.getUint16Le();
const bitCount = iterator.getUint16Le();
const compression = iterator.getByteString(4, false);
const sizeImage = iterator.getUint32Le();
const xPelsPerMeter = iterator.getInt32Le();
const yPelsPerMeter = iterator.getInt32Le();
const clrUsed = iterator.getUint32Le();
const clrImportant = iterator.getUint32Le();
box.expectNoMoreBytes();
return {
type: 'strf-box-video',
biSize,
bitCount,
clrImportant,
clrUsed,
compression,
height,
planes,
sizeImage,
width,
xPelsPerMeter,
yPelsPerMeter,
};
};
const parseStrf = ({ iterator, size, fccType, }) => {
if (fccType === 'vids') {
return parseStrfVideo({ iterator, size });
}
if (fccType === 'auds') {
return parseStrfAudio({ iterator, size });
}
throw new Error(`Unsupported fccType: ${fccType}`);
};
exports.parseStrf = parseStrf;

View File

@@ -0,0 +1,6 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { RiffBox } from './riff-box';
export declare const parseStrh: ({ iterator, size, }: {
iterator: BufferIterator;
size: number;
}) => RiffBox;

View File

@@ -0,0 +1,59 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseStrh = void 0;
const parse_strf_1 = require("./parse-strf");
const parseStrh = ({ iterator, size, }) => {
const box = iterator.startBox(size);
const fccType = iterator.getByteString(4, false);
if (fccType !== 'vids' && fccType !== 'auds') {
throw new Error('Expected AVI handler to be vids / auds');
}
const handler = fccType === 'vids'
? iterator.getByteString(4, false)
: iterator.getUint32Le();
if (typeof handler === 'string' && handler !== 'H264') {
throw new Error(`Only H264 is supported as a stream type in .avi, got ${handler}`);
}
if (fccType === 'auds' && handler !== 1) {
throw new Error(`Only "1" is supported as a stream type in .avi, got ${handler}`);
}
const flags = iterator.getUint32Le();
const priority = iterator.getUint16Le();
const language = iterator.getUint16Le();
const initialFrames = iterator.getUint32Le();
const scale = iterator.getUint32Le();
const rate = iterator.getUint32Le();
const start = iterator.getUint32Le();
const length = iterator.getUint32Le();
const suggestedBufferSize = iterator.getUint32Le();
const quality = iterator.getUint32Le();
const sampleSize = iterator.getUint32Le();
box.discardRest();
const ckId = iterator.getByteString(4, false);
const ckSize = iterator.getUint32Le();
if (ckId !== 'strf') {
throw new Error(`Expected strf, got ${JSON.stringify(ckId)}`);
}
if (iterator.bytesRemaining() < ckSize) {
throw new Error('Expected strf to be complete');
}
const strf = (0, parse_strf_1.parseStrf)({ iterator, size: ckSize, fccType });
return {
type: 'strh-box',
fccType,
handler,
flags,
priority,
initialFrames,
length,
quality,
rate,
sampleSize,
scale,
start,
suggestedBufferSize,
language,
strf,
};
};
exports.parseStrh = parseStrh;

View File

@@ -0,0 +1,2 @@
import type { ParserState } from '../../state/parser-state';
export declare const parseMediaSection: (state: ParserState) => Promise<void>;

View File

@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseMediaSection = void 0;
const get_tracks_1 = require("../../get-tracks");
const get_tracks_from_avi_1 = require("./get-tracks-from-avi");
const parse_movi_1 = require("./parse-movi");
const parseMediaSection = async (state) => {
await (0, parse_movi_1.parseMovi)({
state,
});
const tracks = (0, get_tracks_1.getTracks)(state, false);
if (!tracks.some((t) => t.type === 'video' && t.codec === get_tracks_from_avi_1.TO_BE_OVERRIDDEN_LATER) &&
!state.callbacks.tracks.getIsDone()) {
state.callbacks.tracks.setIsDone(state.logLevel);
}
};
exports.parseMediaSection = parseMediaSection;

View File

@@ -0,0 +1,92 @@
export type ListBox = {
type: 'list-box';
listType: string;
children: RiffBox[];
};
export type RiffRegularBox = {
type: 'riff-box';
size: number;
id: string;
};
export type AvihBox = {
type: 'avih-box';
microSecPerFrame: number;
maxBytesPerSecond: number;
paddingGranularity: number;
flags: number;
totalFrames: number;
initialFrames: number;
streams: number;
suggestedBufferSize: number;
width: number;
height: number;
hasIndex: boolean;
};
export type FccType = 'vids' | 'auds';
export type StrhBox = {
type: 'strh-box';
fccType: FccType;
handler: 'H264' | number;
flags: number;
priority: number;
initialFrames: number;
scale: number;
rate: number;
start: number;
length: number;
suggestedBufferSize: number;
quality: number;
sampleSize: number;
language: number;
strf: StrfBoxVideo | StrfBoxAudio;
};
export type StrfBoxVideo = {
type: 'strf-box-video';
biSize: number;
width: number;
height: number;
planes: number;
bitCount: number;
compression: string;
sizeImage: number;
xPelsPerMeter: number;
yPelsPerMeter: number;
clrUsed: number;
clrImportant: number;
};
export type StrfBoxAudio = {
type: 'strf-box-audio';
formatTag: number;
numberOfChannels: number;
sampleRate: number;
avgBytesPerSecond: number;
blockAlign: number;
bitsPerSample: number;
cbSize: number;
};
export type RiffHeader = {
type: 'riff-header';
fileSize: number;
fileType: string;
};
export type IsftBox = {
type: 'isft-box';
software: string;
};
export type Idx1Box = {
type: 'idx1-box';
entries: Idx1Entry[];
videoTrackIndex: number | null;
};
export type Idx1Entry = {
id: string;
flags: number;
offset: number;
size: number;
sampleCounts: Record<number, number>;
};
export type RiffBox = RiffRegularBox | RiffHeader | ListBox | AvihBox | StrhBox | StrfBoxVideo | StrfBoxAudio | Idx1Box | IsftBox;
export type RiffStructure = {
type: 'riff';
boxes: RiffBox[];
};

View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,18 @@
import type { MediaParserController } from '../../../controller/media-parser-controller';
import type { PrefetchCache } from '../../../fetch';
import { type MediaParserLogLevel } from '../../../log';
import type { ParseMediaSrc } from '../../../options';
import type { MediaParserReaderInterface } from '../../../readers/reader';
export declare const fetchIdx1: ({ src, readerInterface, controller, position, logLevel, prefetchCache, contentLength, }: {
src: ParseMediaSrc;
readerInterface: MediaParserReaderInterface;
controller: MediaParserController;
position: number;
logLevel: MediaParserLogLevel;
prefetchCache: PrefetchCache;
contentLength: number;
}) => Promise<{
entries: import("../riff-box").Idx1Entry[];
videoTrackIndex: number | null;
}>;
export type FetchIdx1Result = Awaited<ReturnType<typeof fetchIdx1>>;

View File

@@ -0,0 +1,47 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetchIdx1 = void 0;
const buffer_iterator_1 = require("../../../iterator/buffer-iterator");
const log_1 = require("../../../log");
const expect_riff_box_1 = require("../expect-riff-box");
const fetchIdx1 = async ({ src, readerInterface, controller, position, logLevel, prefetchCache, contentLength, }) => {
log_1.Log.verbose(logLevel, 'Making request to fetch idx1 from ', src, 'position', position);
const result = await readerInterface.read({
controller,
range: position,
src,
logLevel,
prefetchCache,
});
if (result.contentLength === null) {
throw new Error('Content length is null');
}
const iterator = (0, buffer_iterator_1.getArrayBufferIterator)({
initialData: new Uint8Array(),
maxBytes: contentLength - position + 1,
logLevel: 'error',
});
while (true) {
const res = await result.reader.reader.read();
if (res.value) {
iterator.addData(res.value);
}
if (res.done) {
break;
}
}
const box = await (0, expect_riff_box_1.expectRiffBox)({
iterator,
stateIfExpectingSideEffects: null,
});
iterator.destroy();
if (box === null || box.type !== 'idx1-box') {
throw new Error('Expected idx1-box');
}
// only store video chunks, those end with "dc", e.g. "01dc"
return {
entries: box.entries.filter((entry) => entry.id.endsWith('dc')),
videoTrackIndex: box.videoTrackIndex,
};
};
exports.fetchIdx1 = fetchIdx1;

View File

@@ -0,0 +1,23 @@
import type { ParserState } from '../../state/parser-state';
import type { RiffState } from '../../state/riff';
import type { RiffKeyframe } from '../../state/riff/riff-keyframes';
import type { StructureState } from '../../state/structure';
import type { MediaSectionState } from '../../state/video-section';
import type { FetchIdx1Result } from './seek/fetch-idx1';
export type RiffSeekingHints = {
type: 'riff-seeking-hints';
hasIndex: boolean;
idx1Entries: FetchIdx1Result | null;
samplesPerSecond: number | null;
moviOffset: number | null;
observedKeyframes: RiffKeyframe[];
};
export declare const getSeekingHintsForRiff: ({ structureState, riffState, mediaSectionState, }: {
structureState: StructureState;
riffState: RiffState;
mediaSectionState: MediaSectionState;
}) => RiffSeekingHints;
export declare const setSeekingHintsForRiff: ({ hints, state, }: {
hints: RiffSeekingHints;
state: ParserState;
}) => void;

View File

@@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setSeekingHintsForRiff = exports.getSeekingHintsForRiff = void 0;
const has_index_1 = require("./has-index");
const traversal_1 = require("./traversal");
const getSeekingHintsForRiff = ({ structureState, riffState, mediaSectionState, }) => {
var _a, _b;
const structure = structureState.getRiffStructure();
const strl = (0, traversal_1.getStrlBoxes)(structure);
let samplesPerSecond = null;
for (const s of strl) {
const strh = (0, traversal_1.getStrhBox)(s.children);
if (!strh) {
throw new Error('No strh box');
}
if (strh.strf.type !== 'strf-box-video') {
continue;
}
samplesPerSecond = strh.rate / strh.scale;
break;
}
return {
type: 'riff-seeking-hints',
hasIndex: (0, has_index_1.riffHasIndex)(structure),
idx1Entries: riffState.lazyIdx1.getIfAlreadyLoaded(),
samplesPerSecond,
moviOffset: (_b = (_a = mediaSectionState.getMediaSections()[0]) === null || _a === void 0 ? void 0 : _a.start) !== null && _b !== void 0 ? _b : null,
observedKeyframes: riffState.sampleCounter.riffKeys.getKeyframes(),
};
};
exports.getSeekingHintsForRiff = getSeekingHintsForRiff;
const setSeekingHintsForRiff = ({ hints, state, }) => {
state.riff.lazyIdx1.setFromSeekingHints(hints);
state.riff.sampleCounter.riffKeys.setFromSeekingHints(hints.observedKeyframes);
};
exports.setSeekingHintsForRiff = setSeekingHintsForRiff;

View File

@@ -0,0 +1 @@
export declare const MEDIA_PARSER_RIFF_TIMESCALE = 1000000;

View File

@@ -0,0 +1,4 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MEDIA_PARSER_RIFF_TIMESCALE = void 0;
exports.MEDIA_PARSER_RIFF_TIMESCALE = 1000000;

View File

@@ -0,0 +1,6 @@
import type { AvihBox, ListBox, RiffBox, RiffStructure, StrhBox } from './riff-box';
export declare const isRiffAvi: (structure: RiffStructure) => boolean;
export declare const getHdlrBox: (structure: RiffStructure) => ListBox | null;
export declare const getAvihBox: (structure: RiffStructure) => AvihBox | null;
export declare const getStrlBoxes: (structure: RiffStructure) => ListBox[];
export declare const getStrhBox: (strlBoxChildren: RiffBox[]) => StrhBox | null;

View File

@@ -0,0 +1,31 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getStrhBox = exports.getStrlBoxes = exports.getAvihBox = exports.getHdlrBox = exports.isRiffAvi = void 0;
const isRiffAvi = (structure) => {
return structure.boxes.some((box) => box.type === 'riff-header' && box.fileType === 'AVI');
};
exports.isRiffAvi = isRiffAvi;
const getHdlrBox = (structure) => {
return structure.boxes.find((box) => box.type === 'list-box' && box.listType === 'hdrl');
};
exports.getHdlrBox = getHdlrBox;
const getAvihBox = (structure) => {
const hdlrBox = (0, exports.getHdlrBox)(structure);
if (!hdlrBox) {
return null;
}
return hdlrBox.children.find((box) => box.type === 'avih-box');
};
exports.getAvihBox = getAvihBox;
const getStrlBoxes = (structure) => {
const hdlrBox = (0, exports.getHdlrBox)(structure);
if (!hdlrBox) {
return [];
}
return hdlrBox.children.filter((box) => box.type === 'list-box' && box.listType === 'strl');
};
exports.getStrlBoxes = getStrlBoxes;
const getStrhBox = (strlBoxChildren) => {
return strlBoxChildren.find((box) => box.type === 'strh-box');
};
exports.getStrhBox = getStrhBox;