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,2 @@
import type { ParserState } from '../../state/parser-state';
export declare const getDurationFromWav: (state: ParserState) => number;

View File

@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getDurationFromWav = void 0;
const getDurationFromWav = (state) => {
const structure = state.structure.getWavStructure();
const fmt = structure.boxes.find((b) => b.type === 'wav-fmt');
if (!fmt) {
throw new Error('Expected fmt box');
}
const dataBox = structure.boxes.find((b) => b.type === 'wav-data');
if (!dataBox) {
throw new Error('Expected data box');
}
const durationInSeconds = dataBox.dataSize / (fmt.sampleRate * fmt.blockAlign);
return durationInSeconds;
};
exports.getDurationFromWav = getDurationFromWav;

View File

@@ -0,0 +1,3 @@
import type { MediaParserMetadataEntry } from '../../metadata/get-metadata';
import type { WavStructure } from './types';
export declare const getMetadataFromWav: (structure: WavStructure) => MediaParserMetadataEntry[] | null;

View File

@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMetadataFromWav = void 0;
const getMetadataFromWav = (structure) => {
const list = structure.boxes.find((b) => b.type === 'wav-list');
if (!list) {
return null;
}
return list.metadata;
};
exports.getMetadataFromWav = getMetadataFromWav;

View File

@@ -0,0 +1,7 @@
import type { WavSeekingHints } from '../../seeking-hints';
import type { SeekResolution } from '../../work-on-seek-request';
export declare const WAVE_SAMPLES_PER_SECOND = 25;
export declare const getSeekingByteFromWav: ({ info, time, }: {
info: WavSeekingHints;
time: number;
}) => Promise<SeekResolution>;

View File

@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSeekingByteFromWav = exports.WAVE_SAMPLES_PER_SECOND = void 0;
exports.WAVE_SAMPLES_PER_SECOND = 25;
const getSeekingByteFromWav = ({ info, time, }) => {
const bytesPerSecond = info.sampleRate * info.blockAlign;
const durationInSeconds = info.mediaSection.size / bytesPerSecond;
const timeRoundedDown = Math.floor(Math.min(time, durationInSeconds - 0.0000001) * exports.WAVE_SAMPLES_PER_SECOND) / exports.WAVE_SAMPLES_PER_SECOND;
const byteOffset = bytesPerSecond * timeRoundedDown;
return Promise.resolve({
type: 'do-seek',
byte: byteOffset + info.mediaSection.start,
timeInSeconds: timeRoundedDown,
});
};
exports.getSeekingByteFromWav = getSeekingByteFromWav;

View File

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

View File

@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseData = void 0;
const skip_1 = require("../../skip");
const may_skip_video_data_1 = require("../../state/may-skip-video-data");
const parseData = ({ state, }) => {
const { iterator } = state;
const ckSize = iterator.getUint32Le(); // chunkSize
const box = {
type: 'wav-data',
dataSize: ckSize,
};
state.structure.getWavStructure().boxes.push(box);
state.callbacks.tracks.setIsDone(state.logLevel);
state.mediaSection.addMediaSection({
size: ckSize,
start: iterator.counter.getOffset(),
});
if ((0, may_skip_video_data_1.maySkipVideoData)({ state })) {
// Skipping only in query mode
return Promise.resolve((0, skip_1.makeSkip)(iterator.counter.getOffset() + ckSize));
}
return Promise.resolve(null);
};
exports.parseData = parseData;

View File

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

View File

@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseFact = void 0;
const parseFact = ({ state, }) => {
const { iterator } = state;
const size = iterator.getUint32Le();
if (size !== 4) {
throw new Error(`Expected size 4 for fact box, got ${size}`);
}
const numberOfSamplesPerChannel = iterator.getUint32Le();
const factBox = {
type: 'wav-fact',
numberOfSamplesPerChannel,
};
state.structure.getWavStructure().boxes.push(factBox);
return Promise.resolve(null);
};
exports.parseFact = parseFact;

View File

@@ -0,0 +1,6 @@
import type { ParseResult } from '../../parse-result';
import type { ParserState } from '../../state/parser-state';
export declare function getChannelsFromMask(channelMask: number): string[];
export declare const parseFmt: ({ state, }: {
state: ParserState;
}) => Promise<ParseResult>;

View File

@@ -0,0 +1,124 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseFmt = void 0;
exports.getChannelsFromMask = getChannelsFromMask;
const register_track_1 = require("../../register-track");
const webcodecs_timescale_1 = require("../../webcodecs-timescale");
const subformats_1 = require("./subformats");
const CHANNELS = {
0: 'Front Left',
1: 'Front Right',
2: 'Front Center',
3: 'Low Frequency',
4: 'Back Left',
5: 'Back Right',
6: 'Front Left of Center',
7: 'Front Right of Center',
8: 'Back Center',
9: 'Side Left',
10: 'Side Right',
11: 'Top Center',
12: 'Top Front Left',
13: 'Top Front Center',
14: 'Top Front Right',
15: 'Top Back Left',
16: 'Top Back Center',
17: 'Top Back Right',
// Add more if needed as per the spec
};
function getChannelsFromMask(channelMask) {
const channels = [];
for (let bit = 0; bit < 18; bit++) {
if ((channelMask & (1 << bit)) !== 0) {
const channelName = CHANNELS[bit];
if (channelName) {
channels.push(channelName);
}
else {
channels.push(`Unknown Channel (bit ${bit})`);
}
}
}
return channels;
}
const parseFmt = async ({ state, }) => {
const { iterator } = state;
const ckSize = iterator.getUint32Le(); // chunkSize
const box = iterator.startBox(ckSize);
const audioFormat = iterator.getUint16Le();
if (audioFormat !== 1 && audioFormat !== 65534) {
throw new Error(`Only supporting WAVE with PCM audio format, but got ${audioFormat}`);
}
const numberOfChannels = iterator.getUint16Le();
const sampleRate = iterator.getUint32Le();
const byteRate = iterator.getUint32Le();
const blockAlign = iterator.getUint16Le();
const bitsPerSample = iterator.getUint16Le();
const format = bitsPerSample === 16
? 'pcm-s16'
: bitsPerSample === 32
? 'pcm-s32'
: bitsPerSample === 24
? 'pcm-s24'
: null;
if (format === null) {
throw new Error(`Unsupported bits per sample: ${bitsPerSample}`);
}
const wavHeader = {
bitsPerSample,
blockAlign,
byteRate,
numberOfChannels,
sampleRate,
type: 'wav-fmt',
};
state.structure.getWavStructure().boxes.push(wavHeader);
if (audioFormat === 65534) {
const extraSize = iterator.getUint16Le();
if (extraSize !== 22) {
throw new Error(`Only supporting WAVE with 22 extra bytes, but got ${extraSize} bytes extra size`);
}
iterator.getUint16Le(); // valid bits per sample
const channelMask = iterator.getUint32Le();
const subFormat = iterator.getSlice(16);
// check if same as [ 1, 0, 0, 0, 0, 0, 16, 0, 128, 0, 0, 170, 0, 56, 155, 113 ]
if (subFormat.length !== 16) {
throw new Error(`Only supporting WAVE with PCM audio format, but got ${subFormat.length}`);
}
if ((0, subformats_1.subformatIsPcm)(subFormat)) {
// is pcm
}
else if ((0, subformats_1.subformatIsIeeeFloat)(subFormat)) {
// is ieee float
}
else {
throw new Error(`Unsupported subformat: ${subFormat}`);
}
const channels = getChannelsFromMask(channelMask);
wavHeader.numberOfChannels = channels.length;
}
await (0, register_track_1.registerAudioTrack)({
track: {
type: 'audio',
codec: format,
codecData: null,
description: undefined,
codecEnum: format,
numberOfChannels,
sampleRate,
originalTimescale: 1000000,
trackId: 0,
startInSeconds: 0,
timescale: webcodecs_timescale_1.WEBCODECS_TIMESCALE,
trackMediaTimeOffsetInTrackTimescale: 0,
},
container: 'wav',
registerAudioSampleCallback: state.callbacks.registerAudioSampleCallback,
tracks: state.callbacks.tracks,
logLevel: state.logLevel,
onAudioTrack: state.onAudioTrack,
});
box.expectNoMoreBytes();
return Promise.resolve(null);
};
exports.parseFmt = parseFmt;

View File

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

View File

@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseHeader = void 0;
const parseHeader = ({ state, }) => {
const fileSize = state.iterator.getUint32Le();
const fileType = state.iterator.getByteString(4, false);
if (fileType !== 'WAVE') {
throw new Error(`Expected WAVE, got ${fileType}`);
}
const header = {
type: 'wav-header',
fileSize,
};
state.structure.getWavStructure().boxes.push(header);
return Promise.resolve(null);
};
exports.parseHeader = parseHeader;

View File

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

View File

@@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseId3 = void 0;
// non-standard, we discard it in favor of LIST boxes
const parseId3 = ({ state, }) => {
const { iterator } = state;
const id3Size = iterator.getUint32Le();
iterator.discard(id3Size);
const id3Box = {
type: 'wav-id3',
};
state.structure.getWavStructure().boxes.push(id3Box);
return Promise.resolve(null);
};
exports.parseId3 = parseId3;

View File

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

View File

@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseJunk = void 0;
const log_1 = require("../../log");
const parseJunk = ({ state, }) => {
const { iterator } = state;
const ckSize = iterator.getUint32Le(); // chunkSize
log_1.Log.trace(state.logLevel, `Skipping JUNK chunk of size ${ckSize}`);
iterator.discard(ckSize);
return Promise.resolve(null);
};
exports.parseJunk = parseJunk;

View File

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

View File

@@ -0,0 +1,42 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseList = void 0;
const parseList = ({ state, }) => {
const { iterator } = state;
const ckSize = iterator.getUint32Le(); // chunkSize
const box = iterator.startBox(ckSize);
const startOffset = iterator.counter.getOffset();
const type = iterator.getByteString(4, false);
if (type !== 'INFO') {
iterator.discard(ckSize - 4);
return Promise.resolve(null);
}
const metadata = [];
const remainingBytes = () => ckSize - (iterator.counter.getOffset() - startOffset);
while (remainingBytes() > 0) {
// Padding
// https://discord.com/channels/809501355504959528/1308803317480292482/1343979547246333983
// Indie_Hacker_Podcast (2).wav
const byte = iterator.getUint8();
if (byte === 0) {
continue;
}
iterator.counter.decrement(1);
const key = iterator.getByteString(4, false);
const size = iterator.getUint32Le();
const value = iterator.getByteString(size, true);
metadata.push({
key,
trackId: null,
value,
});
}
const wavList = {
type: 'wav-list',
metadata,
};
state.structure.getWavStructure().boxes.push(wavList);
box.expectNoMoreBytes();
return Promise.resolve(null);
};
exports.parseList = parseList;

View File

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

View File

@@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseMediaSection = void 0;
const convert_audio_or_video_sample_1 = require("../../convert-audio-or-video-sample");
const get_seeking_byte_1 = require("./get-seeking-byte");
const parseMediaSection = async ({ state, }) => {
const { iterator } = state;
const structure = state.structure.getWavStructure();
const videoSection = state.mediaSection.getMediaSectionAssertOnlyOne();
const maxOffset = videoSection.start + videoSection.size;
const maxRead = maxOffset - iterator.counter.getOffset();
const offset = iterator.counter.getOffset();
const fmtBox = structure.boxes.find((box) => box.type === 'wav-fmt');
if (!fmtBox) {
throw new Error('Expected fmt box');
}
const toRead = Math.min(maxRead, (fmtBox.sampleRate * fmtBox.blockAlign) / get_seeking_byte_1.WAVE_SAMPLES_PER_SECOND);
const duration = toRead / (fmtBox.sampleRate * fmtBox.blockAlign);
const timestamp = (offset - videoSection.start) / (fmtBox.sampleRate * fmtBox.blockAlign);
const data = iterator.getSlice(toRead);
const audioSample = (0, convert_audio_or_video_sample_1.convertAudioOrVideoSampleToWebCodecsTimestamps)({
sample: {
decodingTimestamp: timestamp,
data,
duration,
timestamp,
type: 'key',
offset,
},
timescale: 1,
});
await state.callbacks.onAudioSample({
audioSample,
trackId: 0,
});
return null;
};
exports.parseMediaSection = parseMediaSection;

View File

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

View File

@@ -0,0 +1,47 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseWav = void 0;
const log_1 = require("../../log");
const parse_data_1 = require("./parse-data");
const parse_fact_1 = require("./parse-fact");
const parse_fmt_1 = require("./parse-fmt");
const parse_header_1 = require("./parse-header");
const parse_id3_1 = require("./parse-id3");
const parse_junk_1 = require("./parse-junk");
const parse_list_1 = require("./parse-list");
const parse_media_section_1 = require("./parse-media-section");
const parseWav = (state) => {
const { iterator } = state;
const insideMediaSection = state.mediaSection.isCurrentByteInMediaSection(iterator);
if (insideMediaSection === 'in-section') {
return (0, parse_media_section_1.parseMediaSection)({ state });
}
const type = iterator.getByteString(4, false).toLowerCase();
log_1.Log.trace(state.logLevel, `Processing box type ${type}`);
if (type === 'riff') {
return (0, parse_header_1.parseHeader)({ state });
}
if (type === 'fmt') {
return (0, parse_fmt_1.parseFmt)({ state });
}
if (type === 'data') {
return (0, parse_data_1.parseData)({ state });
}
if (type === 'list') {
return (0, parse_list_1.parseList)({ state });
}
if (type === 'id3') {
return (0, parse_id3_1.parseId3)({ state });
}
if (type === 'junk' || type === 'fllr' || type === 'bext' || type === 'cue') {
return (0, parse_junk_1.parseJunk)({ state });
}
if (type === 'fact') {
return (0, parse_fact_1.parseFact)({ state });
}
if (type === '\u0000') {
return Promise.resolve(null);
}
throw new Error(`Unknown WAV box type ${type}`);
};
exports.parseWav = parseWav;

View File

@@ -0,0 +1,12 @@
import type { WavSeekingHints } from '../../seeking-hints';
import type { ParserState } from '../../state/parser-state';
import type { MediaSectionState } from '../../state/video-section';
import type { WavStructure } from './types';
export declare const getSeekingHintsFromWav: ({ structure, mediaSectionState, }: {
structure: WavStructure;
mediaSectionState: MediaSectionState;
}) => WavSeekingHints | null;
export declare const setSeekingHintsForWav: ({ hints, state, }: {
hints: WavSeekingHints;
state: ParserState;
}) => void;

View File

@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setSeekingHintsForWav = exports.getSeekingHintsFromWav = void 0;
const getSeekingHintsFromWav = ({ structure, mediaSectionState, }) => {
const fmtBox = structure.boxes.find((box) => box.type === 'wav-fmt');
if (!fmtBox) {
return null;
}
const mediaSection = mediaSectionState.getMediaSections();
if (mediaSection.length !== 1) {
return null;
}
return {
type: 'wav-seeking-hints',
sampleRate: fmtBox.sampleRate,
blockAlign: fmtBox.blockAlign,
mediaSection: mediaSection[0],
};
};
exports.getSeekingHintsFromWav = getSeekingHintsFromWav;
const setSeekingHintsForWav = ({ hints, state, }) => {
// abstaining from setting fmt box, usually it is at the very beginning
state.mediaSection.addMediaSection(hints.mediaSection);
};
exports.setSeekingHintsForWav = setSeekingHintsForWav;

View File

@@ -0,0 +1,4 @@
export declare const WMMEDIASUBTYPE_PCM: number[];
export declare const KSDATAFORMAT_SUBTYPE_IEEE_FLOAT: number[];
export declare const subformatIsPcm: (subformat: Uint8Array) => boolean;
export declare const subformatIsIeeeFloat: (subformat: Uint8Array) => boolean;

View File

@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.subformatIsIeeeFloat = exports.subformatIsPcm = exports.KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = exports.WMMEDIASUBTYPE_PCM = void 0;
exports.WMMEDIASUBTYPE_PCM = [
1, 0, 0, 0, 0, 0, 16, 0, 128, 0, 0, 170, 0, 56, 155, 113,
];
exports.KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = [
3, 0, 0, 0, 0, 0, 16, 0, 128, 0, 0, 170, 0, 56, 155, 113,
];
const subformatIsPcm = (subformat) => {
return subformat.every((value, index) => value === exports.WMMEDIASUBTYPE_PCM[index]);
};
exports.subformatIsPcm = subformatIsPcm;
const subformatIsIeeeFloat = (subformat) => {
return subformat.every((value, index) => value === exports.KSDATAFORMAT_SUBTYPE_IEEE_FLOAT[index]);
};
exports.subformatIsIeeeFloat = subformatIsIeeeFloat;

View File

@@ -0,0 +1,34 @@
import type { MediaParserMetadataEntry } from '../../metadata/get-metadata';
export type WavHeader = {
type: 'wav-header';
fileSize: number;
};
export type WavFmt = {
type: 'wav-fmt';
numberOfChannels: number;
sampleRate: number;
byteRate: number;
blockAlign: number;
bitsPerSample: number;
};
export type WavList = {
type: 'wav-list';
metadata: MediaParserMetadataEntry[];
};
export type WavId3 = {
type: 'wav-id3';
};
export type WavFact = {
type: 'wav-fact';
numberOfSamplesPerChannel: number;
};
export type WavData = {
type: 'wav-data';
dataSize: number;
};
type WavBox = WavHeader | WavFmt | WavList | WavId3 | WavData | WavFact;
export type WavStructure = {
type: 'wav';
boxes: WavBox[];
};
export {};

View File

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