Add .gitignore to exclude all node packages and lock files
This commit is contained in:
+29216
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+112
File diff suppressed because one or more lines are too long
Generated
Vendored
+111
File diff suppressed because one or more lines are too long
+29203
File diff suppressed because it is too large
Load Diff
+3346
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/** Sample rates indexed by fscod (Table 4.1) */
|
||||
export declare const AC3_SAMPLE_RATES: number[];
|
||||
/** E-AC-3 reduced sample rates for fscod2 per ATSC A/52:2018 */
|
||||
export declare const EAC3_REDUCED_SAMPLE_RATES: number[];
|
||||
//# sourceMappingURL=ac3-misc.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ac3-misc.d.ts","sourceRoot":"","sources":["../../../shared/ac3-misc.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,gDAAgD;AAChD,eAAO,MAAM,gBAAgB,UAAwB,CAAC;AAEtD,gEAAgE;AAChE,eAAO,MAAM,yBAAyB,UAAwB,CAAC"}
|
||||
Generated
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/** Sample rates indexed by fscod (Table 4.1) */
|
||||
export const AC3_SAMPLE_RATES = [48000, 44100, 32000];
|
||||
/** E-AC-3 reduced sample rates for fscod2 per ATSC A/52:2018 */
|
||||
export const EAC3_REDUCED_SAMPLE_RATES = [24000, 22050, 16000];
|
||||
Generated
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
export declare const FRAME_HEADER_SIZE = 4;
|
||||
export declare const SAMPLING_RATES: number[];
|
||||
export declare const KILOBIT_RATES: number[];
|
||||
/** 'Xing' */
|
||||
export declare const XING = 1483304551;
|
||||
/** 'Info' */
|
||||
export declare const INFO = 1231971951;
|
||||
export type Mp3FrameHeader = {
|
||||
totalSize: number;
|
||||
mpegVersionId: number;
|
||||
layer: number;
|
||||
bitrate: number;
|
||||
frequencyIndex: number;
|
||||
sampleRate: number;
|
||||
channel: number;
|
||||
modeExtension: number;
|
||||
copyright: number;
|
||||
original: number;
|
||||
emphasis: number;
|
||||
audioSamplesInFrame: number;
|
||||
};
|
||||
export declare const computeMp3FrameSize: (lowSamplingFrequency: number, layer: number, bitrate: number, sampleRate: number, padding: number) => number;
|
||||
export declare const getXingOffset: (mpegVersionId: number, channel: number) => 13 | 21 | 36;
|
||||
export declare const readMp3FrameHeader: (word: number, remainingBytes: number | null) => {
|
||||
header: Mp3FrameHeader | null;
|
||||
bytesAdvanced: number;
|
||||
};
|
||||
export declare const encodeSynchsafe: (unsynchsafed: number) => number;
|
||||
export declare const decodeSynchsafe: (synchsafed: number) => number;
|
||||
//# sourceMappingURL=mp3-misc.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"mp3-misc.d.ts","sourceRoot":"","sources":["../../../shared/mp3-misc.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,cAAc,UAAwB,CAAC;AACpD,eAAO,MAAM,aAAa,UAYzB,CAAC;AAEF,aAAa;AACb,eAAO,MAAM,IAAI,aAAa,CAAC;AAC/B,aAAa;AACb,eAAO,MAAM,IAAI,aAAa,CAAC;AAE/B,MAAM,MAAM,cAAc,GAAG;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC/B,sBAAsB,MAAM,EAC5B,OAAO,MAAM,EACb,SAAS,MAAM,EACf,YAAY,MAAM,EAClB,SAAS,MAAM,WAWf,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,eAAe,MAAM,EAAE,SAAS,MAAM,iBAInE,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,EAAE,gBAAgB,MAAM,GAAG,IAAI,KAAG;IAChF,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;CAyFtB,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,cAAc,MAAM,WAcnD,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,YAAY,MAAM,WAWjD,CAAC"}
|
||||
Generated
Vendored
+147
@@ -0,0 +1,147 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
export const FRAME_HEADER_SIZE = 4;
|
||||
export const SAMPLING_RATES = [44100, 48000, 32000];
|
||||
export const KILOBIT_RATES = [
|
||||
// lowSamplingFrequency === 0
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // layer = 0
|
||||
-1, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1, // layer 1
|
||||
-1, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1, // layer = 2
|
||||
-1, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1, // layer = 3
|
||||
// lowSamplingFrequency === 1
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // layer = 0
|
||||
-1, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1, // layer = 1
|
||||
-1, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1, // layer = 2
|
||||
-1, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1, // layer = 3
|
||||
];
|
||||
/** 'Xing' */
|
||||
export const XING = 0x58696e67;
|
||||
/** 'Info' */
|
||||
export const INFO = 0x496e666f;
|
||||
export const computeMp3FrameSize = (lowSamplingFrequency, layer, bitrate, sampleRate, padding) => {
|
||||
if (layer === 0) {
|
||||
return 0; // Not expected that this is hit
|
||||
}
|
||||
else if (layer === 1) {
|
||||
return Math.floor(144 * bitrate / (sampleRate << lowSamplingFrequency)) + padding;
|
||||
}
|
||||
else if (layer === 2) {
|
||||
return Math.floor(144 * bitrate / sampleRate) + padding;
|
||||
}
|
||||
else { // layer === 3
|
||||
return (Math.floor(12 * bitrate / sampleRate) + padding) * 4;
|
||||
}
|
||||
};
|
||||
export const getXingOffset = (mpegVersionId, channel) => {
|
||||
return mpegVersionId === 3
|
||||
? (channel === 3 ? 21 : 36)
|
||||
: (channel === 3 ? 13 : 21);
|
||||
};
|
||||
export const readMp3FrameHeader = (word, remainingBytes) => {
|
||||
const firstByte = word >>> 24;
|
||||
const secondByte = (word >>> 16) & 0xff;
|
||||
const thirdByte = (word >>> 8) & 0xff;
|
||||
const fourthByte = word & 0xff;
|
||||
if (firstByte !== 0xff && secondByte !== 0xff && thirdByte !== 0xff && fourthByte !== 0xff) {
|
||||
return {
|
||||
header: null,
|
||||
bytesAdvanced: 4,
|
||||
};
|
||||
}
|
||||
if (firstByte !== 0xff) {
|
||||
return { header: null, bytesAdvanced: 1 };
|
||||
}
|
||||
if ((secondByte & 0xe0) !== 0xe0) {
|
||||
return { header: null, bytesAdvanced: 1 };
|
||||
}
|
||||
let lowSamplingFrequency = 0;
|
||||
let mpeg25 = 0;
|
||||
if (secondByte & (1 << 4)) {
|
||||
lowSamplingFrequency = (secondByte & (1 << 3)) ? 0 : 1;
|
||||
}
|
||||
else {
|
||||
lowSamplingFrequency = 1;
|
||||
mpeg25 = 1;
|
||||
}
|
||||
const mpegVersionId = (secondByte >> 3) & 0x3;
|
||||
const layer = (secondByte >> 1) & 0x3;
|
||||
const bitrateIndex = (thirdByte >> 4) & 0xf;
|
||||
const frequencyIndex = ((thirdByte >> 2) & 0x3) % 3;
|
||||
const padding = (thirdByte >> 1) & 0x1;
|
||||
const channel = (fourthByte >> 6) & 0x3;
|
||||
const modeExtension = (fourthByte >> 4) & 0x3;
|
||||
const copyright = (fourthByte >> 3) & 0x1;
|
||||
const original = (fourthByte >> 2) & 0x1;
|
||||
const emphasis = fourthByte & 0x3;
|
||||
const kilobitRate = KILOBIT_RATES[lowSamplingFrequency * 16 * 4 + layer * 16 + bitrateIndex];
|
||||
if (kilobitRate === -1) {
|
||||
return { header: null, bytesAdvanced: 1 };
|
||||
}
|
||||
const bitrate = kilobitRate * 1000;
|
||||
const sampleRate = SAMPLING_RATES[frequencyIndex] >> (lowSamplingFrequency + mpeg25);
|
||||
const frameLength = computeMp3FrameSize(lowSamplingFrequency, layer, bitrate, sampleRate, padding);
|
||||
if (remainingBytes !== null && remainingBytes < frameLength) {
|
||||
// The frame doesn't fit into the rest of the file
|
||||
return { header: null, bytesAdvanced: 1 };
|
||||
}
|
||||
let audioSamplesInFrame;
|
||||
if (mpegVersionId === 3) {
|
||||
audioSamplesInFrame = layer === 3 ? 384 : 1152;
|
||||
}
|
||||
else {
|
||||
if (layer === 3) {
|
||||
audioSamplesInFrame = 384;
|
||||
}
|
||||
else if (layer === 2) {
|
||||
audioSamplesInFrame = 1152;
|
||||
}
|
||||
else {
|
||||
audioSamplesInFrame = 576;
|
||||
}
|
||||
}
|
||||
return {
|
||||
header: {
|
||||
totalSize: frameLength,
|
||||
mpegVersionId,
|
||||
layer,
|
||||
bitrate,
|
||||
frequencyIndex,
|
||||
sampleRate,
|
||||
channel,
|
||||
modeExtension,
|
||||
copyright,
|
||||
original,
|
||||
emphasis,
|
||||
audioSamplesInFrame,
|
||||
},
|
||||
bytesAdvanced: 1,
|
||||
};
|
||||
};
|
||||
export const encodeSynchsafe = (unsynchsafed) => {
|
||||
let mask = 0x7f;
|
||||
let synchsafed = 0;
|
||||
let unsynchsafedRest = unsynchsafed;
|
||||
while ((mask ^ 0x7fffffff) !== 0) {
|
||||
synchsafed = unsynchsafedRest & ~mask;
|
||||
synchsafed <<= 1;
|
||||
synchsafed |= unsynchsafedRest & mask;
|
||||
mask = ((mask + 1) << 8) - 1;
|
||||
unsynchsafedRest = synchsafed;
|
||||
}
|
||||
return synchsafed;
|
||||
};
|
||||
export const decodeSynchsafe = (synchsafed) => {
|
||||
let mask = 0x7f000000;
|
||||
let unsynchsafed = 0;
|
||||
while (mask !== 0) {
|
||||
unsynchsafed >>= 1;
|
||||
unsynchsafed |= synchsafed & mask;
|
||||
mask >>= 8;
|
||||
}
|
||||
return unsynchsafed;
|
||||
};
|
||||
Generated
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { Demuxer } from '../demuxer.js';
|
||||
import { Input } from '../input.js';
|
||||
import { InputAudioTrack } from '../input-track.js';
|
||||
import { MetadataTags } from '../metadata.js';
|
||||
import { AsyncMutex } from '../misc.js';
|
||||
import { Reader } from '../reader.js';
|
||||
import { AdtsFrameHeader } from './adts-reader.js';
|
||||
export declare const SAMPLES_PER_AAC_FRAME = 1024;
|
||||
type Sample = {
|
||||
timestamp: number;
|
||||
duration: number;
|
||||
dataStart: number;
|
||||
dataSize: number;
|
||||
};
|
||||
export declare class AdtsDemuxer extends Demuxer {
|
||||
reader: Reader;
|
||||
metadataPromise: Promise<void> | null;
|
||||
firstFrameHeader: AdtsFrameHeader | null;
|
||||
loadedSamples: Sample[];
|
||||
metadataTags: MetadataTags | null;
|
||||
tracks: InputAudioTrack[];
|
||||
readingMutex: AsyncMutex;
|
||||
lastSampleLoaded: boolean;
|
||||
lastLoadedPos: number;
|
||||
nextTimestampInSamples: number;
|
||||
constructor(input: Input);
|
||||
readMetadata(): Promise<void>;
|
||||
advanceReader(): Promise<void>;
|
||||
getMimeType(): Promise<string>;
|
||||
getTracks(): Promise<InputAudioTrack[]>;
|
||||
computeDuration(): Promise<number>;
|
||||
getMetadataTags(): Promise<MetadataTags>;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=adts-demuxer.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"adts-demuxer.d.ts","sourceRoot":"","sources":["../../../../src/adts/adts-demuxer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAMrC,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,EAAE,eAAe,EAA0B,MAAM,gBAAgB,CAAC;AAEzE,OAAO,EAA6B,YAAY,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAEN,UAAU,EAIV,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAa,MAAM,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EACN,eAAe,EAIf,MAAM,eAAe,CAAC;AAEvB,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C,KAAK,MAAM,GAAG;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,qBAAa,WAAY,SAAQ,OAAO;IACvC,MAAM,EAAE,MAAM,CAAC;IAEf,eAAe,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAQ;IAC7C,gBAAgB,EAAE,eAAe,GAAG,IAAI,CAAQ;IAChD,aAAa,EAAE,MAAM,EAAE,CAAM;IAC7B,YAAY,EAAE,YAAY,GAAG,IAAI,CAAQ;IAEzC,MAAM,EAAE,eAAe,EAAE,CAAM;IAE/B,YAAY,aAAoB;IAChC,gBAAgB,UAAS;IACzB,aAAa,SAAK;IAClB,sBAAsB,SAAK;gBAEf,KAAK,EAAE,KAAK;IAMlB,YAAY;IAeZ,aAAa;IAgEb,WAAW;IAIX,SAAS;IAKT,eAAe;IASf,eAAe;CAqCrB"}
|
||||
Generated
Vendored
+277
@@ -0,0 +1,277 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { aacChannelMap, aacFrequencyTable } from '../codec.js';
|
||||
import { Demuxer } from '../demuxer.js';
|
||||
import { ID3_V2_HEADER_SIZE, parseId3V2Tag, readId3V2Header, } from '../id3.js';
|
||||
import { InputAudioTrack } from '../input-track.js';
|
||||
import { DEFAULT_TRACK_DISPOSITION } from '../metadata.js';
|
||||
import { assert, AsyncMutex, binarySearchExact, binarySearchLessOrEqual, UNDETERMINED_LANGUAGE, } from '../misc.js';
|
||||
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet.js';
|
||||
import { readBytes } from '../reader.js';
|
||||
import { MIN_ADTS_FRAME_HEADER_SIZE, MAX_ADTS_FRAME_HEADER_SIZE, readAdtsFrameHeader, } from './adts-reader.js';
|
||||
export const SAMPLES_PER_AAC_FRAME = 1024;
|
||||
export class AdtsDemuxer extends Demuxer {
|
||||
constructor(input) {
|
||||
super(input);
|
||||
this.metadataPromise = null;
|
||||
this.firstFrameHeader = null;
|
||||
this.loadedSamples = [];
|
||||
this.metadataTags = null;
|
||||
this.tracks = [];
|
||||
this.readingMutex = new AsyncMutex();
|
||||
this.lastSampleLoaded = false;
|
||||
this.lastLoadedPos = 0;
|
||||
this.nextTimestampInSamples = 0;
|
||||
this.reader = input._reader;
|
||||
}
|
||||
async readMetadata() {
|
||||
return this.metadataPromise ??= (async () => {
|
||||
// Keep loading until we find the first frame header
|
||||
while (!this.firstFrameHeader && !this.lastSampleLoaded) {
|
||||
await this.advanceReader();
|
||||
}
|
||||
// There has to be a frame if this demuxer got selected
|
||||
assert(this.firstFrameHeader);
|
||||
// Create the single audio track
|
||||
this.tracks = [new InputAudioTrack(this.input, new AdtsAudioTrackBacking(this))];
|
||||
})();
|
||||
}
|
||||
async advanceReader() {
|
||||
if (this.lastLoadedPos === 0) {
|
||||
// Skip all ID3v2 tags at the start of the file
|
||||
while (true) {
|
||||
let slice = this.reader.requestSlice(this.lastLoadedPos, ID3_V2_HEADER_SIZE);
|
||||
if (slice instanceof Promise)
|
||||
slice = await slice;
|
||||
if (!slice) {
|
||||
this.lastSampleLoaded = true;
|
||||
return;
|
||||
}
|
||||
const id3V2Header = readId3V2Header(slice);
|
||||
if (!id3V2Header) {
|
||||
break;
|
||||
}
|
||||
this.lastLoadedPos = slice.filePos + id3V2Header.size;
|
||||
}
|
||||
}
|
||||
let slice = this.reader.requestSliceRange(this.lastLoadedPos, MIN_ADTS_FRAME_HEADER_SIZE, MAX_ADTS_FRAME_HEADER_SIZE);
|
||||
if (slice instanceof Promise)
|
||||
slice = await slice;
|
||||
if (!slice) {
|
||||
this.lastSampleLoaded = true;
|
||||
return;
|
||||
}
|
||||
const header = readAdtsFrameHeader(slice);
|
||||
if (!header) {
|
||||
this.lastSampleLoaded = true;
|
||||
return;
|
||||
}
|
||||
if (this.reader.fileSize !== null && header.startPos + header.frameLength > this.reader.fileSize) {
|
||||
// Frame doesn't fit in the rest of the file
|
||||
this.lastSampleLoaded = true;
|
||||
return;
|
||||
}
|
||||
if (!this.firstFrameHeader) {
|
||||
this.firstFrameHeader = header;
|
||||
}
|
||||
const sampleRate = aacFrequencyTable[header.samplingFrequencyIndex];
|
||||
assert(sampleRate !== undefined);
|
||||
const sampleDuration = SAMPLES_PER_AAC_FRAME / sampleRate;
|
||||
const sample = {
|
||||
timestamp: this.nextTimestampInSamples / sampleRate,
|
||||
duration: sampleDuration,
|
||||
dataStart: header.startPos,
|
||||
dataSize: header.frameLength,
|
||||
};
|
||||
this.loadedSamples.push(sample);
|
||||
this.nextTimestampInSamples += SAMPLES_PER_AAC_FRAME;
|
||||
this.lastLoadedPos = header.startPos + header.frameLength;
|
||||
}
|
||||
async getMimeType() {
|
||||
return 'audio/aac';
|
||||
}
|
||||
async getTracks() {
|
||||
await this.readMetadata();
|
||||
return this.tracks;
|
||||
}
|
||||
async computeDuration() {
|
||||
await this.readMetadata();
|
||||
const track = this.tracks[0];
|
||||
assert(track);
|
||||
return track.computeDuration();
|
||||
}
|
||||
async getMetadataTags() {
|
||||
const release = await this.readingMutex.acquire();
|
||||
try {
|
||||
await this.readMetadata();
|
||||
if (this.metadataTags) {
|
||||
return this.metadataTags;
|
||||
}
|
||||
this.metadataTags = {};
|
||||
let currentPos = 0;
|
||||
while (true) {
|
||||
let headerSlice = this.reader.requestSlice(currentPos, ID3_V2_HEADER_SIZE);
|
||||
if (headerSlice instanceof Promise)
|
||||
headerSlice = await headerSlice;
|
||||
if (!headerSlice)
|
||||
break;
|
||||
const id3V2Header = readId3V2Header(headerSlice);
|
||||
if (!id3V2Header) {
|
||||
break;
|
||||
}
|
||||
let contentSlice = this.reader.requestSlice(headerSlice.filePos, id3V2Header.size);
|
||||
if (contentSlice instanceof Promise)
|
||||
contentSlice = await contentSlice;
|
||||
if (!contentSlice)
|
||||
break;
|
||||
parseId3V2Tag(contentSlice, id3V2Header, this.metadataTags);
|
||||
currentPos = headerSlice.filePos + id3V2Header.size;
|
||||
}
|
||||
return this.metadataTags;
|
||||
}
|
||||
finally {
|
||||
release();
|
||||
}
|
||||
}
|
||||
}
|
||||
class AdtsAudioTrackBacking {
|
||||
constructor(demuxer) {
|
||||
this.demuxer = demuxer;
|
||||
}
|
||||
getId() {
|
||||
return 1;
|
||||
}
|
||||
getNumber() {
|
||||
return 1;
|
||||
}
|
||||
async getFirstTimestamp() {
|
||||
return 0;
|
||||
}
|
||||
getTimeResolution() {
|
||||
const sampleRate = this.getSampleRate();
|
||||
return sampleRate / SAMPLES_PER_AAC_FRAME;
|
||||
}
|
||||
async computeDuration() {
|
||||
const lastPacket = await this.getPacket(Infinity, { metadataOnly: true });
|
||||
return (lastPacket?.timestamp ?? 0) + (lastPacket?.duration ?? 0);
|
||||
}
|
||||
getName() {
|
||||
return null;
|
||||
}
|
||||
getLanguageCode() {
|
||||
return UNDETERMINED_LANGUAGE;
|
||||
}
|
||||
getCodec() {
|
||||
return 'aac';
|
||||
}
|
||||
getInternalCodecId() {
|
||||
assert(this.demuxer.firstFrameHeader);
|
||||
return this.demuxer.firstFrameHeader.objectType;
|
||||
}
|
||||
getNumberOfChannels() {
|
||||
assert(this.demuxer.firstFrameHeader);
|
||||
const numberOfChannels = aacChannelMap[this.demuxer.firstFrameHeader.channelConfiguration];
|
||||
assert(numberOfChannels !== undefined);
|
||||
return numberOfChannels;
|
||||
}
|
||||
getSampleRate() {
|
||||
assert(this.demuxer.firstFrameHeader);
|
||||
const sampleRate = aacFrequencyTable[this.demuxer.firstFrameHeader.samplingFrequencyIndex];
|
||||
assert(sampleRate !== undefined);
|
||||
return sampleRate;
|
||||
}
|
||||
getDisposition() {
|
||||
return {
|
||||
...DEFAULT_TRACK_DISPOSITION,
|
||||
};
|
||||
}
|
||||
async getDecoderConfig() {
|
||||
assert(this.demuxer.firstFrameHeader);
|
||||
return {
|
||||
codec: `mp4a.40.${this.demuxer.firstFrameHeader.objectType}`,
|
||||
numberOfChannels: this.getNumberOfChannels(),
|
||||
sampleRate: this.getSampleRate(),
|
||||
};
|
||||
}
|
||||
async getPacketAtIndex(sampleIndex, options) {
|
||||
if (sampleIndex === -1) {
|
||||
return null;
|
||||
}
|
||||
const rawSample = this.demuxer.loadedSamples[sampleIndex];
|
||||
if (!rawSample) {
|
||||
return null;
|
||||
}
|
||||
let data;
|
||||
if (options.metadataOnly) {
|
||||
data = PLACEHOLDER_DATA;
|
||||
}
|
||||
else {
|
||||
let slice = this.demuxer.reader.requestSlice(rawSample.dataStart, rawSample.dataSize);
|
||||
if (slice instanceof Promise)
|
||||
slice = await slice;
|
||||
if (!slice) {
|
||||
return null; // Data didn't fit into the rest of the file
|
||||
}
|
||||
data = readBytes(slice, rawSample.dataSize);
|
||||
}
|
||||
return new EncodedPacket(data, 'key', rawSample.timestamp, rawSample.duration, sampleIndex, rawSample.dataSize);
|
||||
}
|
||||
getFirstPacket(options) {
|
||||
return this.getPacketAtIndex(0, options);
|
||||
}
|
||||
async getNextPacket(packet, options) {
|
||||
const release = await this.demuxer.readingMutex.acquire();
|
||||
try {
|
||||
const sampleIndex = binarySearchExact(this.demuxer.loadedSamples, packet.timestamp, x => x.timestamp);
|
||||
if (sampleIndex === -1) {
|
||||
throw new Error('Packet was not created from this track.');
|
||||
}
|
||||
const nextIndex = sampleIndex + 1;
|
||||
// Ensure the next sample exists
|
||||
while (nextIndex >= this.demuxer.loadedSamples.length
|
||||
&& !this.demuxer.lastSampleLoaded) {
|
||||
await this.demuxer.advanceReader();
|
||||
}
|
||||
return this.getPacketAtIndex(nextIndex, options);
|
||||
}
|
||||
finally {
|
||||
release();
|
||||
}
|
||||
}
|
||||
async getPacket(timestamp, options) {
|
||||
const release = await this.demuxer.readingMutex.acquire();
|
||||
try {
|
||||
while (true) {
|
||||
const index = binarySearchLessOrEqual(this.demuxer.loadedSamples, timestamp, x => x.timestamp);
|
||||
if (index === -1 && this.demuxer.loadedSamples.length > 0) {
|
||||
// We're before the first sample
|
||||
return null;
|
||||
}
|
||||
if (this.demuxer.lastSampleLoaded) {
|
||||
// All data is loaded, return what we found
|
||||
return this.getPacketAtIndex(index, options);
|
||||
}
|
||||
if (index >= 0 && index + 1 < this.demuxer.loadedSamples.length) {
|
||||
// The next packet also exists, we're done
|
||||
return this.getPacketAtIndex(index, options);
|
||||
}
|
||||
// Otherwise, keep loading data
|
||||
await this.demuxer.advanceReader();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
release();
|
||||
}
|
||||
}
|
||||
getKeyPacket(timestamp, options) {
|
||||
return this.getPacket(timestamp, options);
|
||||
}
|
||||
getNextKeyPacket(packet, options) {
|
||||
return this.getNextPacket(packet, options);
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { AacAudioSpecificConfig } from '../codec.js';
|
||||
import { Bitstream } from '../misc.js';
|
||||
export type AdtsHeaderTemplate = {
|
||||
header: Uint8Array;
|
||||
bitstream: Bitstream;
|
||||
};
|
||||
export declare const buildAdtsHeaderTemplate: (config: AacAudioSpecificConfig) => AdtsHeaderTemplate;
|
||||
export declare const writeAdtsFrameLength: (bitstream: Bitstream, frameLength: number) => void;
|
||||
//# sourceMappingURL=adts-misc.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"adts-misc.d.ts","sourceRoot":"","sources":["../../../../src/adts/adts-misc.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC,MAAM,MAAM,kBAAkB,GAAG;IAChC,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,QAAQ,sBAAsB,KAAG,kBAyBxE,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,WAAW,SAAS,EAAE,aAAa,MAAM,SAG7E,CAAC"}
|
||||
Generated
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { Bitstream } from '../misc.js';
|
||||
export const buildAdtsHeaderTemplate = (config) => {
|
||||
const header = new Uint8Array(7);
|
||||
const bitstream = new Bitstream(header);
|
||||
const { objectType, frequencyIndex, channelConfiguration } = config;
|
||||
const profile = objectType - 1;
|
||||
bitstream.writeBits(12, 0b1111_11111111); // Syncword
|
||||
bitstream.writeBits(1, 0); // MPEG Version
|
||||
bitstream.writeBits(2, 0); // Layer
|
||||
bitstream.writeBits(1, 1); // Protection absence
|
||||
bitstream.writeBits(2, profile); // Profile
|
||||
bitstream.writeBits(4, frequencyIndex); // MPEG-4 Sampling Frequency Index
|
||||
bitstream.writeBits(1, 0); // Private bit
|
||||
bitstream.writeBits(3, channelConfiguration); // MPEG-4 Channel Configuration
|
||||
bitstream.writeBits(1, 0); // Originality
|
||||
bitstream.writeBits(1, 0); // Home
|
||||
bitstream.writeBits(1, 0); // Copyright ID bit
|
||||
bitstream.writeBits(1, 0); // Copyright ID start
|
||||
bitstream.skipBits(13); // Frame length (to be filled per packet)
|
||||
bitstream.writeBits(11, 0x7ff); // Buffer fullness
|
||||
bitstream.writeBits(2, 0); // Number of AAC frames minus 1
|
||||
// Omit CRC check
|
||||
return { header, bitstream };
|
||||
};
|
||||
export const writeAdtsFrameLength = (bitstream, frameLength) => {
|
||||
bitstream.pos = 30;
|
||||
bitstream.writeBits(13, frameLength);
|
||||
};
|
||||
Generated
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { Muxer } from '../muxer.js';
|
||||
import { Output, OutputAudioTrack } from '../output.js';
|
||||
import { AdtsOutputFormat } from '../output-format.js';
|
||||
import { EncodedPacket } from '../packet.js';
|
||||
export declare class AdtsMuxer extends Muxer {
|
||||
private format;
|
||||
private writer;
|
||||
private header;
|
||||
private headerBitstream;
|
||||
private inputIsAdts;
|
||||
constructor(output: Output, format: AdtsOutputFormat);
|
||||
start(): Promise<void>;
|
||||
getMimeType(): Promise<string>;
|
||||
addEncodedVideoPacket(): Promise<void>;
|
||||
addEncodedAudioPacket(track: OutputAudioTrack, packet: EncodedPacket, meta?: EncodedAudioChunkMetadata): Promise<void>;
|
||||
addSubtitleCue(): Promise<void>;
|
||||
finalize(): Promise<void>;
|
||||
}
|
||||
//# sourceMappingURL=adts-muxer.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"adts-muxer.d.ts","sourceRoot":"","sources":["../../../../src/adts/adts-muxer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAI1C,qBAAa,SAAU,SAAQ,KAAK;IACnC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,eAAe,CAA0B;IACjD,OAAO,CAAC,WAAW,CAAwB;gBAE/B,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB;IAO9C,KAAK;IAOL,WAAW;IAIX,qBAAqB;IAIrB,qBAAqB,CAC1B,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,aAAa,EACrB,IAAI,CAAC,EAAE,yBAAyB;IA0D3B,cAAc;IAId,QAAQ;CACd"}
|
||||
Generated
Vendored
+85
@@ -0,0 +1,85 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { parseAacAudioSpecificConfig, validateAudioChunkMetadata } from '../codec.js';
|
||||
import { Id3V2Writer } from '../id3.js';
|
||||
import { metadataTagsAreEmpty } from '../metadata.js';
|
||||
import { assert, toUint8Array } from '../misc.js';
|
||||
import { Muxer } from '../muxer.js';
|
||||
import { buildAdtsHeaderTemplate, writeAdtsFrameLength } from './adts-misc.js';
|
||||
export class AdtsMuxer extends Muxer {
|
||||
constructor(output, format) {
|
||||
super(output);
|
||||
this.header = null;
|
||||
this.headerBitstream = null;
|
||||
this.inputIsAdts = null;
|
||||
this.format = format;
|
||||
this.writer = output._writer;
|
||||
}
|
||||
async start() {
|
||||
if (!metadataTagsAreEmpty(this.output._metadataTags)) {
|
||||
const id3Writer = new Id3V2Writer(this.writer);
|
||||
id3Writer.writeId3V2Tag(this.output._metadataTags);
|
||||
}
|
||||
}
|
||||
async getMimeType() {
|
||||
return 'audio/aac';
|
||||
}
|
||||
async addEncodedVideoPacket() {
|
||||
throw new Error('ADTS does not support video.');
|
||||
}
|
||||
async addEncodedAudioPacket(track, packet, meta) {
|
||||
const release = await this.mutex.acquire();
|
||||
try {
|
||||
this.validateAndNormalizeTimestamp(track, packet.timestamp, packet.type === 'key');
|
||||
// First packet - determine input format from metadata
|
||||
if (this.inputIsAdts === null) {
|
||||
validateAudioChunkMetadata(meta);
|
||||
const description = meta?.decoderConfig?.description;
|
||||
// Follows from the Mediabunny Codec Registry:
|
||||
this.inputIsAdts = !description;
|
||||
if (!this.inputIsAdts) {
|
||||
const config = parseAacAudioSpecificConfig(toUint8Array(description));
|
||||
const template = buildAdtsHeaderTemplate(config);
|
||||
this.header = template.header;
|
||||
this.headerBitstream = template.bitstream;
|
||||
}
|
||||
}
|
||||
if (this.inputIsAdts) {
|
||||
// Packets are already ADTS frames, write them directly
|
||||
const startPos = this.writer.getPos();
|
||||
this.writer.write(packet.data);
|
||||
if (this.format._options.onFrame) {
|
||||
this.format._options.onFrame(packet.data, startPos);
|
||||
}
|
||||
}
|
||||
else {
|
||||
assert(this.header);
|
||||
// Packets are raw AAC, we gotta turn it into ADTS
|
||||
const frameLength = packet.data.byteLength + this.header.byteLength;
|
||||
writeAdtsFrameLength(this.headerBitstream, frameLength);
|
||||
const startPos = this.writer.getPos();
|
||||
this.writer.write(this.header);
|
||||
this.writer.write(packet.data);
|
||||
if (this.format._options.onFrame) {
|
||||
const frameBytes = new Uint8Array(frameLength);
|
||||
frameBytes.set(this.header, 0);
|
||||
frameBytes.set(packet.data, this.header.byteLength);
|
||||
this.format._options.onFrame(frameBytes, startPos);
|
||||
}
|
||||
}
|
||||
await this.writer.flush();
|
||||
}
|
||||
finally {
|
||||
release();
|
||||
}
|
||||
}
|
||||
async addSubtitleCue() {
|
||||
throw new Error('ADTS does not support subtitles.');
|
||||
}
|
||||
async finalize() { }
|
||||
}
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { FileSlice } from '../reader.js';
|
||||
export declare const MIN_ADTS_FRAME_HEADER_SIZE = 7;
|
||||
export declare const MAX_ADTS_FRAME_HEADER_SIZE = 9;
|
||||
export type AdtsFrameHeader = {
|
||||
objectType: number;
|
||||
samplingFrequencyIndex: number;
|
||||
channelConfiguration: number;
|
||||
frameLength: number;
|
||||
numberOfAacFrames: number;
|
||||
crcCheck: number | null;
|
||||
startPos: number;
|
||||
};
|
||||
export declare const readAdtsFrameHeader: (slice: FileSlice) => AdtsFrameHeader | null;
|
||||
//# sourceMappingURL=adts-reader.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"adts-reader.d.ts","sourceRoot":"","sources":["../../../../src/adts/adts-reader.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,SAAS,EAAa,MAAM,WAAW,CAAC;AAEjD,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAC5C,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C,MAAM,MAAM,eAAe,GAAG;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,OAAO,SAAS,KAAG,eAAe,GAAG,IA4DxE,CAAC"}
|
||||
Generated
Vendored
+63
@@ -0,0 +1,63 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { Bitstream } from '../misc.js';
|
||||
import { readBytes } from '../reader.js';
|
||||
export const MIN_ADTS_FRAME_HEADER_SIZE = 7;
|
||||
export const MAX_ADTS_FRAME_HEADER_SIZE = 9;
|
||||
export const readAdtsFrameHeader = (slice) => {
|
||||
// https://wiki.multimedia.cx/index.php/ADTS (last visited: 2025/08/17)
|
||||
const startPos = slice.filePos;
|
||||
const bytes = readBytes(slice, 9); // 9 with CRC, 7 without CRC
|
||||
const bitstream = new Bitstream(bytes);
|
||||
const syncword = bitstream.readBits(12);
|
||||
if (syncword !== 0b1111_11111111) {
|
||||
return null;
|
||||
}
|
||||
bitstream.skipBits(1); // MPEG version
|
||||
const layer = bitstream.readBits(2);
|
||||
if (layer !== 0) {
|
||||
return null;
|
||||
}
|
||||
const protectionAbsence = bitstream.readBits(1);
|
||||
const objectType = bitstream.readBits(2) + 1;
|
||||
const samplingFrequencyIndex = bitstream.readBits(4);
|
||||
if (samplingFrequencyIndex === 15) {
|
||||
return null;
|
||||
}
|
||||
bitstream.skipBits(1); // Private bit
|
||||
const channelConfiguration = bitstream.readBits(3);
|
||||
if (channelConfiguration === 0) {
|
||||
throw new Error('ADTS frames with channel configuration 0 are not supported.');
|
||||
}
|
||||
bitstream.skipBits(1); // Originality
|
||||
bitstream.skipBits(1); // Home
|
||||
bitstream.skipBits(1); // Copyright ID bit
|
||||
bitstream.skipBits(1); // Copyright ID start
|
||||
const frameLength = bitstream.readBits(13);
|
||||
bitstream.skipBits(11); // Buffer fullness
|
||||
const numberOfAacFrames = bitstream.readBits(2) + 1;
|
||||
if (numberOfAacFrames !== 1) {
|
||||
throw new Error('ADTS frames with more than one AAC frame are not supported.');
|
||||
}
|
||||
let crcCheck = null;
|
||||
if (protectionAbsence === 1) { // No CRC
|
||||
slice.filePos -= 2;
|
||||
}
|
||||
else { // CRC
|
||||
crcCheck = bitstream.readBits(16);
|
||||
}
|
||||
return {
|
||||
objectType,
|
||||
samplingFrequencyIndex,
|
||||
channelConfiguration,
|
||||
frameLength,
|
||||
numberOfAacFrames,
|
||||
crcCheck,
|
||||
startPos,
|
||||
};
|
||||
};
|
||||
Generated
Vendored
+287
@@ -0,0 +1,287 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { VideoCodec } from './codec.js';
|
||||
import { PacketType } from './packet.js';
|
||||
import { MetadataTags } from './metadata.js';
|
||||
export declare enum AvcNalUnitType {
|
||||
NON_IDR_SLICE = 1,
|
||||
SLICE_DPA = 2,
|
||||
SLICE_DPB = 3,
|
||||
SLICE_DPC = 4,
|
||||
IDR = 5,
|
||||
SEI = 6,
|
||||
SPS = 7,
|
||||
PPS = 8,
|
||||
AUD = 9,
|
||||
SPS_EXT = 13
|
||||
}
|
||||
export declare enum HevcNalUnitType {
|
||||
RASL_N = 8,
|
||||
RASL_R = 9,
|
||||
BLA_W_LP = 16,
|
||||
RSV_IRAP_VCL23 = 23,
|
||||
VPS_NUT = 32,
|
||||
SPS_NUT = 33,
|
||||
PPS_NUT = 34,
|
||||
AUD_NUT = 35,
|
||||
PREFIX_SEI_NUT = 39,
|
||||
SUFFIX_SEI_NUT = 40
|
||||
}
|
||||
export type NalUnitLocation = {
|
||||
offset: number;
|
||||
length: number;
|
||||
};
|
||||
export declare const iterateNalUnitsInAnnexB: (packetData: Uint8Array) => Generator<NalUnitLocation>;
|
||||
export declare const iterateNalUnitsInLengthPrefixed: (packetData: Uint8Array, lengthSize: 1 | 2 | 3 | 4) => Generator<NalUnitLocation>;
|
||||
export declare const iterateAvcNalUnits: (packetData: Uint8Array, decoderConfig: VideoDecoderConfig) => Generator<NalUnitLocation, any, any>;
|
||||
export declare const extractNalUnitTypeForAvc: (byte: number) => number;
|
||||
export declare const concatNalUnitsInAnnexB: (nalUnits: Uint8Array[]) => Uint8Array<ArrayBuffer>;
|
||||
export declare const concatNalUnitsInLengthPrefixed: (nalUnits: Uint8Array[], lengthSize: 1 | 2 | 3 | 4) => Uint8Array<ArrayBuffer>;
|
||||
export type AvcDecoderConfigurationRecord = {
|
||||
configurationVersion: number;
|
||||
avcProfileIndication: number;
|
||||
profileCompatibility: number;
|
||||
avcLevelIndication: number;
|
||||
lengthSizeMinusOne: number;
|
||||
sequenceParameterSets: Uint8Array[];
|
||||
pictureParameterSets: Uint8Array[];
|
||||
chromaFormat: number | null;
|
||||
bitDepthLumaMinus8: number | null;
|
||||
bitDepthChromaMinus8: number | null;
|
||||
sequenceParameterSetExt: Uint8Array[] | null;
|
||||
};
|
||||
export declare const concatAvcNalUnits: (nalUnits: Uint8Array[], decoderConfig: VideoDecoderConfig) => Uint8Array<ArrayBuffer>;
|
||||
/** Builds an AvcDecoderConfigurationRecord from an AVC packet in Annex B format. */
|
||||
export declare const extractAvcDecoderConfigurationRecord: (packetData: Uint8Array) => AvcDecoderConfigurationRecord | null;
|
||||
/** Serializes an AvcDecoderConfigurationRecord into the format specified in Section 5.3.3.1 of ISO 14496-15. */
|
||||
export declare const serializeAvcDecoderConfigurationRecord: (record: AvcDecoderConfigurationRecord) => Uint8Array<ArrayBuffer>;
|
||||
/** Deserializes an AvcDecoderConfigurationRecord from the format specified in Section 5.3.3.1 of ISO 14496-15. */
|
||||
export declare const deserializeAvcDecoderConfigurationRecord: (data: Uint8Array) => AvcDecoderConfigurationRecord | null;
|
||||
export type AvcSpsInfo = {
|
||||
profileIdc: number;
|
||||
constraintFlags: number;
|
||||
levelIdc: number;
|
||||
frameMbsOnlyFlag: number;
|
||||
chromaFormatIdc: number;
|
||||
bitDepthLumaMinus8: number;
|
||||
bitDepthChromaMinus8: number;
|
||||
codedWidth: number;
|
||||
codedHeight: number;
|
||||
displayWidth: number;
|
||||
displayHeight: number;
|
||||
colourPrimaries: number;
|
||||
transferCharacteristics: number;
|
||||
matrixCoefficients: number;
|
||||
fullRangeFlag: number;
|
||||
numReorderFrames: number;
|
||||
maxDecFrameBuffering: number;
|
||||
};
|
||||
/** Parses an AVC SPS (Sequence Parameter Set) to extract basic information. */
|
||||
export declare const parseAvcSps: (sps: Uint8Array) => AvcSpsInfo | null;
|
||||
export type HevcDecoderConfigurationRecord = {
|
||||
configurationVersion: number;
|
||||
generalProfileSpace: number;
|
||||
generalTierFlag: number;
|
||||
generalProfileIdc: number;
|
||||
generalProfileCompatibilityFlags: number;
|
||||
generalConstraintIndicatorFlags: Uint8Array;
|
||||
generalLevelIdc: number;
|
||||
minSpatialSegmentationIdc: number;
|
||||
parallelismType: number;
|
||||
chromaFormatIdc: number;
|
||||
bitDepthLumaMinus8: number;
|
||||
bitDepthChromaMinus8: number;
|
||||
avgFrameRate: number;
|
||||
constantFrameRate: number;
|
||||
numTemporalLayers: number;
|
||||
temporalIdNested: number;
|
||||
lengthSizeMinusOne: number;
|
||||
arrays: {
|
||||
arrayCompleteness: number;
|
||||
nalUnitType: number;
|
||||
nalUnits: Uint8Array[];
|
||||
}[];
|
||||
};
|
||||
export type HevcSpsInfo = {
|
||||
displayWidth: number;
|
||||
displayHeight: number;
|
||||
colourPrimaries: number;
|
||||
transferCharacteristics: number;
|
||||
matrixCoefficients: number;
|
||||
fullRangeFlag: number;
|
||||
maxDecFrameBuffering: number;
|
||||
spsMaxSubLayersMinus1: number;
|
||||
spsTemporalIdNestingFlag: number;
|
||||
generalProfileSpace: number;
|
||||
generalTierFlag: number;
|
||||
generalProfileIdc: number;
|
||||
generalProfileCompatibilityFlags: number;
|
||||
generalConstraintIndicatorFlags: Uint8Array;
|
||||
generalLevelIdc: number;
|
||||
chromaFormatIdc: number;
|
||||
bitDepthLumaMinus8: number;
|
||||
bitDepthChromaMinus8: number;
|
||||
minSpatialSegmentationIdc: number;
|
||||
};
|
||||
export declare const iterateHevcNalUnits: (packetData: Uint8Array, decoderConfig: VideoDecoderConfig) => Generator<NalUnitLocation, any, any>;
|
||||
export declare const extractNalUnitTypeForHevc: (byte: number) => number;
|
||||
/** Parses an HEVC SPS (Sequence Parameter Set) to extract video information. */
|
||||
export declare const parseHevcSps: (sps: Uint8Array) => HevcSpsInfo | null;
|
||||
/** Builds a HevcDecoderConfigurationRecord from an HEVC packet in Annex B format. */
|
||||
export declare const extractHevcDecoderConfigurationRecord: (packetData: Uint8Array) => HevcDecoderConfigurationRecord | null;
|
||||
/** Serializes an HevcDecoderConfigurationRecord into the format specified in Section 8.3.3.1 of ISO 14496-15. */
|
||||
export declare const serializeHevcDecoderConfigurationRecord: (record: HevcDecoderConfigurationRecord) => Uint8Array<ArrayBuffer>;
|
||||
/** Deserializes an HevcDecoderConfigurationRecord from the format specified in Section 8.3.3.1 of ISO 14496-15. */
|
||||
export declare const deserializeHevcDecoderConfigurationRecord: (data: Uint8Array) => HevcDecoderConfigurationRecord | null;
|
||||
export type Vp9CodecInfo = {
|
||||
profile: number;
|
||||
level: number;
|
||||
bitDepth: number;
|
||||
chromaSubsampling: number;
|
||||
videoFullRangeFlag: number;
|
||||
colourPrimaries: number;
|
||||
transferCharacteristics: number;
|
||||
matrixCoefficients: number;
|
||||
};
|
||||
export declare const extractVp9CodecInfoFromPacket: (packet: Uint8Array) => Vp9CodecInfo | null;
|
||||
export type Av1CodecInfo = {
|
||||
profile: number;
|
||||
level: number;
|
||||
tier: number;
|
||||
bitDepth: number;
|
||||
monochrome: number;
|
||||
chromaSubsamplingX: number;
|
||||
chromaSubsamplingY: number;
|
||||
chromaSamplePosition: number;
|
||||
};
|
||||
/** Iterates over all OBUs in an AV1 packet bistream. */
|
||||
export declare const iterateAv1PacketObus: (packet: Uint8Array) => Generator<{
|
||||
type: number;
|
||||
data: Uint8Array<ArrayBufferLike>;
|
||||
}, void, unknown>;
|
||||
/**
|
||||
* When AV1 codec information is not provided by the container, we can still try to extract the information by digging
|
||||
* into the AV1 bitstream.
|
||||
*/
|
||||
export declare const extractAv1CodecInfoFromPacket: (packet: Uint8Array) => Av1CodecInfo | null;
|
||||
export declare const parseOpusIdentificationHeader: (bytes: Uint8Array) => {
|
||||
outputChannelCount: number;
|
||||
preSkip: number;
|
||||
inputSampleRate: number;
|
||||
outputGain: number;
|
||||
channelMappingFamily: number;
|
||||
channelMappingTable: Uint8Array<ArrayBufferLike> | null;
|
||||
};
|
||||
export declare const parseOpusTocByte: (packet: Uint8Array) => {
|
||||
durationInSamples: number;
|
||||
};
|
||||
export declare const parseModesFromVorbisSetupPacket: (setupHeader: Uint8Array) => {
|
||||
modeBlockflags: number[];
|
||||
};
|
||||
/** Determines a packet's type (key or delta) by digging into the packet bitstream. */
|
||||
export declare const determineVideoPacketType: (codec: VideoCodec, decoderConfig: VideoDecoderConfig, packetData: Uint8Array) => PacketType | null;
|
||||
export declare enum FlacBlockType {
|
||||
STREAMINFO = 0,
|
||||
VORBIS_COMMENT = 4,
|
||||
PICTURE = 6
|
||||
}
|
||||
export declare const readVorbisComments: (bytes: Uint8Array, metadataTags: MetadataTags) => void;
|
||||
export declare const createVorbisComments: (headerBytes: Uint8Array, tags: MetadataTags, writeImages: boolean) => Uint8Array<ArrayBuffer>;
|
||||
/**
|
||||
* Channel counts indexed by acmod (Table 4.3).
|
||||
* Does NOT include LFE - add lfeon to get total channel count.
|
||||
*/
|
||||
export declare const AC3_ACMOD_CHANNEL_COUNTS: number[];
|
||||
export interface Ac3FrameInfo {
|
||||
/** Sample rate code */
|
||||
fscod: number;
|
||||
/** Bitstream ID */
|
||||
bsid: number;
|
||||
/** Bitstream mode */
|
||||
bsmod: number;
|
||||
/** Audio coding mode */
|
||||
acmod: number;
|
||||
/** LFE channel on */
|
||||
lfeon: number;
|
||||
/** Bit rate code (0-18, maps to bitrate via Table F.4.1) */
|
||||
bitRateCode: number;
|
||||
}
|
||||
/**
|
||||
* Parse an AC-3 syncframe to extract BSI (Bit Stream Information) fields.
|
||||
* Section 4.3
|
||||
*/
|
||||
export declare const parseAc3SyncFrame: (data: Uint8Array) => Ac3FrameInfo | null;
|
||||
/**
|
||||
* AC-3 frame sizes in bytes, indexed by [3 * frmsizecod + fscod].
|
||||
* fscod: 0=48kHz, 1=44.1kHz, 2=32kHz
|
||||
* Values are 16-bit words * 2 (to convert to bytes).
|
||||
* Table 4.13
|
||||
*/
|
||||
export declare const AC3_FRAME_SIZES: number[];
|
||||
/** Number of samples per AC-3 syncframe (always 1536) */
|
||||
export declare const AC3_SAMPLES_PER_FRAME = 1536;
|
||||
/**
|
||||
* AC-3 registration_descriptor for MPEG-TS.
|
||||
* Section A.2.3
|
||||
*/
|
||||
export declare const AC3_REGISTRATION_DESCRIPTOR: Uint8Array<ArrayBuffer>;
|
||||
/** E-AC-3 registration_descriptor for MPEG-TS/ */
|
||||
export declare const EAC3_REGISTRATION_DESCRIPTOR: Uint8Array<ArrayBuffer>;
|
||||
/** Number of audio blocks per syncframe, indexed by numblkscod */
|
||||
export declare const EAC3_NUMBLKS_TABLE: number[];
|
||||
/**
|
||||
* E-AC-3 independent substream info.
|
||||
* Each independent substream represents a separate audio program.
|
||||
*/
|
||||
export interface Eac3SubstreamInfo {
|
||||
/** Sample rate code */
|
||||
fscod: number;
|
||||
/** Sample rate code 2 (ATSC A/52:2018) */
|
||||
fscod2: number | null;
|
||||
/** Bitstream ID */
|
||||
bsid: number;
|
||||
/** Bitstream mode */
|
||||
bsmod: number;
|
||||
/** Audio coding mode */
|
||||
acmod: number;
|
||||
/** LFE channel on */
|
||||
lfeon: number;
|
||||
/** Number of dependent substreams */
|
||||
numDepSub: number;
|
||||
/** Channel locations for dependent substreams */
|
||||
chanLoc: number;
|
||||
}
|
||||
/**
|
||||
* E-AC-3 decoder configuration (dec3 box contents).
|
||||
*/
|
||||
export interface Eac3FrameInfo {
|
||||
/** Data rate in kbps */
|
||||
dataRate: number;
|
||||
/** Independent substreams */
|
||||
substreams: Eac3SubstreamInfo[];
|
||||
}
|
||||
/**
|
||||
* Parse an E-AC-3 syncframe to extract BSI fields.
|
||||
* Section E.1.2
|
||||
*/
|
||||
export declare const parseEac3SyncFrame: (data: Uint8Array) => Eac3FrameInfo | null;
|
||||
/**
|
||||
* Parse a dec3 box to extract E-AC-3 parameters.
|
||||
* Section F.6
|
||||
*/
|
||||
export declare const parseEac3Config: (data: Uint8Array) => Eac3FrameInfo | null;
|
||||
/**
|
||||
* Get sample rate from E-AC-3 config.
|
||||
* See ATSC A/52:2018 for handling fscod2.
|
||||
*/
|
||||
export declare const getEac3SampleRate: (config: Eac3FrameInfo) => number | null;
|
||||
/**
|
||||
* Get channel count from E-AC-3 config (first independent substream only).
|
||||
*/
|
||||
export declare const getEac3ChannelCount: (config: Eac3FrameInfo) => number;
|
||||
//# sourceMappingURL=codec-data.d.ts.map
|
||||
Generated
Vendored
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+2449
File diff suppressed because it is too large
Load Diff
+165
@@ -0,0 +1,165 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { Av1CodecInfo, AvcDecoderConfigurationRecord, HevcDecoderConfigurationRecord, Vp9CodecInfo } from './codec-data.js';
|
||||
import { SubtitleMetadata } from './subtitles.js';
|
||||
/**
|
||||
* List of known video codecs, ordered by encoding preference.
|
||||
* @group Codecs
|
||||
* @public
|
||||
*/
|
||||
export declare const VIDEO_CODECS: readonly ["avc", "hevc", "vp9", "av1", "vp8"];
|
||||
/**
|
||||
* List of known PCM (uncompressed) audio codecs, ordered by encoding preference.
|
||||
* @group Codecs
|
||||
* @public
|
||||
*/
|
||||
export declare const PCM_AUDIO_CODECS: readonly ["pcm-s16", "pcm-s16be", "pcm-s24", "pcm-s24be", "pcm-s32", "pcm-s32be", "pcm-f32", "pcm-f32be", "pcm-f64", "pcm-f64be", "pcm-u8", "pcm-s8", "ulaw", "alaw"];
|
||||
/**
|
||||
* List of known compressed audio codecs, ordered by encoding preference.
|
||||
* @group Codecs
|
||||
* @public
|
||||
*/
|
||||
export declare const NON_PCM_AUDIO_CODECS: readonly ["aac", "opus", "mp3", "vorbis", "flac", "ac3", "eac3"];
|
||||
/**
|
||||
* List of known audio codecs, ordered by encoding preference.
|
||||
* @group Codecs
|
||||
* @public
|
||||
*/
|
||||
export declare const AUDIO_CODECS: readonly ["aac", "opus", "mp3", "vorbis", "flac", "ac3", "eac3", "pcm-s16", "pcm-s16be", "pcm-s24", "pcm-s24be", "pcm-s32", "pcm-s32be", "pcm-f32", "pcm-f32be", "pcm-f64", "pcm-f64be", "pcm-u8", "pcm-s8", "ulaw", "alaw"];
|
||||
/**
|
||||
* List of known subtitle codecs, ordered by encoding preference.
|
||||
* @group Codecs
|
||||
* @public
|
||||
*/
|
||||
export declare const SUBTITLE_CODECS: readonly ["webvtt"];
|
||||
/**
|
||||
* Union type of known video codecs.
|
||||
* @group Codecs
|
||||
* @public
|
||||
*/
|
||||
export type VideoCodec = typeof VIDEO_CODECS[number];
|
||||
/**
|
||||
* Union type of known audio codecs.
|
||||
* @group Codecs
|
||||
* @public
|
||||
*/
|
||||
export type AudioCodec = typeof AUDIO_CODECS[number];
|
||||
export type PcmAudioCodec = typeof PCM_AUDIO_CODECS[number];
|
||||
/**
|
||||
* Union type of known subtitle codecs.
|
||||
* @group Codecs
|
||||
* @public
|
||||
*/
|
||||
export type SubtitleCodec = typeof SUBTITLE_CODECS[number];
|
||||
/**
|
||||
* Union type of known media codecs.
|
||||
* @group Codecs
|
||||
* @public
|
||||
*/
|
||||
export type MediaCodec = VideoCodec | AudioCodec | SubtitleCodec;
|
||||
export declare const AVC_LEVEL_TABLE: {
|
||||
maxMacroblocks: number;
|
||||
maxBitrate: number;
|
||||
maxDpbMbs: number;
|
||||
level: number;
|
||||
}[];
|
||||
export declare const VP9_LEVEL_TABLE: {
|
||||
maxPictureSize: number;
|
||||
maxBitrate: number;
|
||||
level: number;
|
||||
}[];
|
||||
export declare const buildVideoCodecString: (codec: VideoCodec, width: number, height: number, bitrate: number) => string;
|
||||
export declare const generateVp9CodecConfigurationFromCodecString: (codecString: string) => number[];
|
||||
export declare const generateAv1CodecConfigurationFromCodecString: (codecString: string) => number[];
|
||||
export declare const extractVideoCodecString: (trackInfo: {
|
||||
width: number;
|
||||
height: number;
|
||||
codec: VideoCodec | null;
|
||||
codecDescription: Uint8Array | null;
|
||||
colorSpace: VideoColorSpaceInit | null;
|
||||
avcType: 1 | 3 | null;
|
||||
avcCodecInfo: AvcDecoderConfigurationRecord | null;
|
||||
hevcCodecInfo: HevcDecoderConfigurationRecord | null;
|
||||
vp9CodecInfo: Vp9CodecInfo | null;
|
||||
av1CodecInfo: Av1CodecInfo | null;
|
||||
}) => string;
|
||||
export declare const buildAudioCodecString: (codec: AudioCodec, numberOfChannels: number, sampleRate: number) => "pcm-s16" | "pcm-s16be" | "pcm-s24" | "pcm-s24be" | "pcm-s32" | "pcm-s32be" | "pcm-f32" | "pcm-f32be" | "pcm-f64" | "pcm-f64be" | "pcm-u8" | "pcm-s8" | "ulaw" | "alaw" | "opus" | "mp3" | "vorbis" | "flac" | "ac-3" | "ec-3" | "mp4a.40.29" | "mp4a.40.5" | "mp4a.40.2";
|
||||
export type AacCodecInfo = {
|
||||
isMpeg2: boolean;
|
||||
objectType: number | null;
|
||||
};
|
||||
export declare const extractAudioCodecString: (trackInfo: {
|
||||
codec: AudioCodec | null;
|
||||
codecDescription: Uint8Array | null;
|
||||
aacCodecInfo: AacCodecInfo | null;
|
||||
}) => string;
|
||||
export type AacAudioSpecificConfig = {
|
||||
objectType: number;
|
||||
frequencyIndex: number;
|
||||
sampleRate: number | null;
|
||||
channelConfiguration: number;
|
||||
numberOfChannels: number | null;
|
||||
};
|
||||
export declare const aacFrequencyTable: number[];
|
||||
export declare const aacChannelMap: number[];
|
||||
export declare const parseAacAudioSpecificConfig: (bytes: Uint8Array | null) => AacAudioSpecificConfig;
|
||||
export declare const buildAacAudioSpecificConfig: (config: {
|
||||
objectType: number;
|
||||
sampleRate: number;
|
||||
numberOfChannels: number;
|
||||
}) => Uint8Array<ArrayBuffer>;
|
||||
export declare const OPUS_SAMPLE_RATE = 48000;
|
||||
export declare const parsePcmCodec: (codec: PcmAudioCodec) => {
|
||||
dataType: "ulaw";
|
||||
sampleSize: 1;
|
||||
littleEndian: boolean;
|
||||
silentValue: number;
|
||||
} | {
|
||||
dataType: "alaw";
|
||||
sampleSize: 1;
|
||||
littleEndian: boolean;
|
||||
silentValue: number;
|
||||
} | {
|
||||
dataType: "unsigned" | "signed" | "float";
|
||||
sampleSize: 8 | 1 | 2 | 4 | 3;
|
||||
littleEndian: boolean;
|
||||
silentValue: number;
|
||||
};
|
||||
export declare const inferCodecFromCodecString: (codecString: string) => MediaCodec | null;
|
||||
export declare const getVideoEncoderConfigExtension: (codec: VideoCodec) => {
|
||||
avc: {
|
||||
format: "avc";
|
||||
};
|
||||
hevc?: undefined;
|
||||
} | {
|
||||
hevc: {
|
||||
format: "hevc";
|
||||
};
|
||||
avc?: undefined;
|
||||
} | {
|
||||
avc?: undefined;
|
||||
hevc?: undefined;
|
||||
};
|
||||
export declare const getAudioEncoderConfigExtension: (codec: AudioCodec) => {
|
||||
aac: {
|
||||
format: "aac";
|
||||
};
|
||||
opus?: undefined;
|
||||
} | {
|
||||
opus: {
|
||||
format: "opus";
|
||||
};
|
||||
aac?: undefined;
|
||||
} | {
|
||||
aac?: undefined;
|
||||
opus?: undefined;
|
||||
};
|
||||
export declare const validateVideoChunkMetadata: (metadata: EncodedVideoChunkMetadata | undefined) => void;
|
||||
export declare const validateAudioChunkMetadata: (metadata: EncodedAudioChunkMetadata | undefined) => void;
|
||||
export declare const validateSubtitleMetadata: (metadata: SubtitleMetadata | undefined) => void;
|
||||
//# sourceMappingURL=codec.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../../../src/codec.ts"],"names":[],"mappings":"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"}
|
||||
+895
@@ -0,0 +1,895 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { Bitstream, COLOR_PRIMARIES_MAP, MATRIX_COEFFICIENTS_MAP, TRANSFER_CHARACTERISTICS_MAP, assert, bytesToHexString, isAllowSharedBufferSource, last, reverseBitsU32, toDataView, } from './misc.js';
|
||||
/**
|
||||
* List of known video codecs, ordered by encoding preference.
|
||||
* @group Codecs
|
||||
* @public
|
||||
*/
|
||||
export const VIDEO_CODECS = [
|
||||
'avc',
|
||||
'hevc',
|
||||
'vp9',
|
||||
'av1',
|
||||
'vp8',
|
||||
];
|
||||
/**
|
||||
* List of known PCM (uncompressed) audio codecs, ordered by encoding preference.
|
||||
* @group Codecs
|
||||
* @public
|
||||
*/
|
||||
export const PCM_AUDIO_CODECS = [
|
||||
'pcm-s16', // We don't prefix 'le' so we're compatible with the WebCodecs-registered PCM codec strings
|
||||
'pcm-s16be',
|
||||
'pcm-s24',
|
||||
'pcm-s24be',
|
||||
'pcm-s32',
|
||||
'pcm-s32be',
|
||||
'pcm-f32',
|
||||
'pcm-f32be',
|
||||
'pcm-f64',
|
||||
'pcm-f64be',
|
||||
'pcm-u8',
|
||||
'pcm-s8',
|
||||
'ulaw',
|
||||
'alaw',
|
||||
];
|
||||
/**
|
||||
* List of known compressed audio codecs, ordered by encoding preference.
|
||||
* @group Codecs
|
||||
* @public
|
||||
*/
|
||||
export const NON_PCM_AUDIO_CODECS = [
|
||||
'aac',
|
||||
'opus',
|
||||
'mp3',
|
||||
'vorbis',
|
||||
'flac',
|
||||
'ac3',
|
||||
'eac3',
|
||||
];
|
||||
/**
|
||||
* List of known audio codecs, ordered by encoding preference.
|
||||
* @group Codecs
|
||||
* @public
|
||||
*/
|
||||
export const AUDIO_CODECS = [
|
||||
...NON_PCM_AUDIO_CODECS,
|
||||
...PCM_AUDIO_CODECS,
|
||||
];
|
||||
/**
|
||||
* List of known subtitle codecs, ordered by encoding preference.
|
||||
* @group Codecs
|
||||
* @public
|
||||
*/
|
||||
export const SUBTITLE_CODECS = [
|
||||
'webvtt',
|
||||
]; // TODO add the rest
|
||||
// https://en.wikipedia.org/wiki/Advanced_Video_Coding
|
||||
export const AVC_LEVEL_TABLE = [
|
||||
{ maxMacroblocks: 99, maxBitrate: 64000, maxDpbMbs: 396, level: 0x0A }, // Level 1
|
||||
{ maxMacroblocks: 396, maxBitrate: 192000, maxDpbMbs: 900, level: 0x0B }, // Level 1.1
|
||||
{ maxMacroblocks: 396, maxBitrate: 384000, maxDpbMbs: 2376, level: 0x0C }, // Level 1.2
|
||||
{ maxMacroblocks: 396, maxBitrate: 768000, maxDpbMbs: 2376, level: 0x0D }, // Level 1.3
|
||||
{ maxMacroblocks: 396, maxBitrate: 2000000, maxDpbMbs: 2376, level: 0x14 }, // Level 2
|
||||
{ maxMacroblocks: 792, maxBitrate: 4000000, maxDpbMbs: 4752, level: 0x15 }, // Level 2.1
|
||||
{ maxMacroblocks: 1620, maxBitrate: 4000000, maxDpbMbs: 8100, level: 0x16 }, // Level 2.2
|
||||
{ maxMacroblocks: 1620, maxBitrate: 10000000, maxDpbMbs: 8100, level: 0x1E }, // Level 3
|
||||
{ maxMacroblocks: 3600, maxBitrate: 14000000, maxDpbMbs: 18000, level: 0x1F }, // Level 3.1
|
||||
{ maxMacroblocks: 5120, maxBitrate: 20000000, maxDpbMbs: 20480, level: 0x20 }, // Level 3.2
|
||||
{ maxMacroblocks: 8192, maxBitrate: 20000000, maxDpbMbs: 32768, level: 0x28 }, // Level 4
|
||||
{ maxMacroblocks: 8192, maxBitrate: 50000000, maxDpbMbs: 32768, level: 0x29 }, // Level 4.1
|
||||
{ maxMacroblocks: 8704, maxBitrate: 50000000, maxDpbMbs: 34816, level: 0x2A }, // Level 4.2
|
||||
{ maxMacroblocks: 22080, maxBitrate: 135000000, maxDpbMbs: 110400, level: 0x32 }, // Level 5
|
||||
{ maxMacroblocks: 36864, maxBitrate: 240000000, maxDpbMbs: 184320, level: 0x33 }, // Level 5.1
|
||||
{ maxMacroblocks: 36864, maxBitrate: 240000000, maxDpbMbs: 184320, level: 0x34 }, // Level 5.2
|
||||
{ maxMacroblocks: 139264, maxBitrate: 240000000, maxDpbMbs: 696320, level: 0x3C }, // Level 6
|
||||
{ maxMacroblocks: 139264, maxBitrate: 480000000, maxDpbMbs: 696320, level: 0x3D }, // Level 6.1
|
||||
{ maxMacroblocks: 139264, maxBitrate: 800000000, maxDpbMbs: 696320, level: 0x3E }, // Level 6.2
|
||||
];
|
||||
// https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding
|
||||
const HEVC_LEVEL_TABLE = [
|
||||
{ maxPictureSize: 36864, maxBitrate: 128000, tier: 'L', level: 30 }, // Level 1 (Low Tier)
|
||||
{ maxPictureSize: 122880, maxBitrate: 1500000, tier: 'L', level: 60 }, // Level 2 (Low Tier)
|
||||
{ maxPictureSize: 245760, maxBitrate: 3000000, tier: 'L', level: 63 }, // Level 2.1 (Low Tier)
|
||||
{ maxPictureSize: 552960, maxBitrate: 6000000, tier: 'L', level: 90 }, // Level 3 (Low Tier)
|
||||
{ maxPictureSize: 983040, maxBitrate: 10000000, tier: 'L', level: 93 }, // Level 3.1 (Low Tier)
|
||||
{ maxPictureSize: 2228224, maxBitrate: 12000000, tier: 'L', level: 120 }, // Level 4 (Low Tier)
|
||||
{ maxPictureSize: 2228224, maxBitrate: 30000000, tier: 'H', level: 120 }, // Level 4 (High Tier)
|
||||
{ maxPictureSize: 2228224, maxBitrate: 20000000, tier: 'L', level: 123 }, // Level 4.1 (Low Tier)
|
||||
{ maxPictureSize: 2228224, maxBitrate: 50000000, tier: 'H', level: 123 }, // Level 4.1 (High Tier)
|
||||
{ maxPictureSize: 8912896, maxBitrate: 25000000, tier: 'L', level: 150 }, // Level 5 (Low Tier)
|
||||
{ maxPictureSize: 8912896, maxBitrate: 100000000, tier: 'H', level: 150 }, // Level 5 (High Tier)
|
||||
{ maxPictureSize: 8912896, maxBitrate: 40000000, tier: 'L', level: 153 }, // Level 5.1 (Low Tier)
|
||||
{ maxPictureSize: 8912896, maxBitrate: 160000000, tier: 'H', level: 153 }, // Level 5.1 (High Tier)
|
||||
{ maxPictureSize: 8912896, maxBitrate: 60000000, tier: 'L', level: 156 }, // Level 5.2 (Low Tier)
|
||||
{ maxPictureSize: 8912896, maxBitrate: 240000000, tier: 'H', level: 156 }, // Level 5.2 (High Tier)
|
||||
{ maxPictureSize: 35651584, maxBitrate: 60000000, tier: 'L', level: 180 }, // Level 6 (Low Tier)
|
||||
{ maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'H', level: 180 }, // Level 6 (High Tier)
|
||||
{ maxPictureSize: 35651584, maxBitrate: 120000000, tier: 'L', level: 183 }, // Level 6.1 (Low Tier)
|
||||
{ maxPictureSize: 35651584, maxBitrate: 480000000, tier: 'H', level: 183 }, // Level 6.1 (High Tier)
|
||||
{ maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'L', level: 186 }, // Level 6.2 (Low Tier)
|
||||
{ maxPictureSize: 35651584, maxBitrate: 800000000, tier: 'H', level: 186 }, // Level 6.2 (High Tier)
|
||||
];
|
||||
// https://en.wikipedia.org/wiki/VP9
|
||||
export const VP9_LEVEL_TABLE = [
|
||||
{ maxPictureSize: 36864, maxBitrate: 200000, level: 10 }, // Level 1
|
||||
{ maxPictureSize: 73728, maxBitrate: 800000, level: 11 }, // Level 1.1
|
||||
{ maxPictureSize: 122880, maxBitrate: 1800000, level: 20 }, // Level 2
|
||||
{ maxPictureSize: 245760, maxBitrate: 3600000, level: 21 }, // Level 2.1
|
||||
{ maxPictureSize: 552960, maxBitrate: 7200000, level: 30 }, // Level 3
|
||||
{ maxPictureSize: 983040, maxBitrate: 12000000, level: 31 }, // Level 3.1
|
||||
{ maxPictureSize: 2228224, maxBitrate: 18000000, level: 40 }, // Level 4
|
||||
{ maxPictureSize: 2228224, maxBitrate: 30000000, level: 41 }, // Level 4.1
|
||||
{ maxPictureSize: 8912896, maxBitrate: 60000000, level: 50 }, // Level 5
|
||||
{ maxPictureSize: 8912896, maxBitrate: 120000000, level: 51 }, // Level 5.1
|
||||
{ maxPictureSize: 8912896, maxBitrate: 180000000, level: 52 }, // Level 5.2
|
||||
{ maxPictureSize: 35651584, maxBitrate: 180000000, level: 60 }, // Level 6
|
||||
{ maxPictureSize: 35651584, maxBitrate: 240000000, level: 61 }, // Level 6.1
|
||||
{ maxPictureSize: 35651584, maxBitrate: 480000000, level: 62 }, // Level 6.2
|
||||
];
|
||||
// https://en.wikipedia.org/wiki/AV1
|
||||
const AV1_LEVEL_TABLE = [
|
||||
{ maxPictureSize: 147456, maxBitrate: 1500000, tier: 'M', level: 0 }, // Level 2.0 (Main Tier)
|
||||
{ maxPictureSize: 278784, maxBitrate: 3000000, tier: 'M', level: 1 }, // Level 2.1 (Main Tier)
|
||||
{ maxPictureSize: 665856, maxBitrate: 6000000, tier: 'M', level: 4 }, // Level 3.0 (Main Tier)
|
||||
{ maxPictureSize: 1065024, maxBitrate: 10000000, tier: 'M', level: 5 }, // Level 3.1 (Main Tier)
|
||||
{ maxPictureSize: 2359296, maxBitrate: 12000000, tier: 'M', level: 8 }, // Level 4.0 (Main Tier)
|
||||
{ maxPictureSize: 2359296, maxBitrate: 30000000, tier: 'H', level: 8 }, // Level 4.0 (High Tier)
|
||||
{ maxPictureSize: 2359296, maxBitrate: 20000000, tier: 'M', level: 9 }, // Level 4.1 (Main Tier)
|
||||
{ maxPictureSize: 2359296, maxBitrate: 50000000, tier: 'H', level: 9 }, // Level 4.1 (High Tier)
|
||||
{ maxPictureSize: 8912896, maxBitrate: 30000000, tier: 'M', level: 12 }, // Level 5.0 (Main Tier)
|
||||
{ maxPictureSize: 8912896, maxBitrate: 100000000, tier: 'H', level: 12 }, // Level 5.0 (High Tier)
|
||||
{ maxPictureSize: 8912896, maxBitrate: 40000000, tier: 'M', level: 13 }, // Level 5.1 (Main Tier)
|
||||
{ maxPictureSize: 8912896, maxBitrate: 160000000, tier: 'H', level: 13 }, // Level 5.1 (High Tier)
|
||||
{ maxPictureSize: 8912896, maxBitrate: 60000000, tier: 'M', level: 14 }, // Level 5.2 (Main Tier)
|
||||
{ maxPictureSize: 8912896, maxBitrate: 240000000, tier: 'H', level: 14 }, // Level 5.2 (High Tier)
|
||||
{ maxPictureSize: 35651584, maxBitrate: 60000000, tier: 'M', level: 15 }, // Level 5.3 (Main Tier)
|
||||
{ maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'H', level: 15 }, // Level 5.3 (High Tier)
|
||||
{ maxPictureSize: 35651584, maxBitrate: 60000000, tier: 'M', level: 16 }, // Level 6.0 (Main Tier)
|
||||
{ maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'H', level: 16 }, // Level 6.0 (High Tier)
|
||||
{ maxPictureSize: 35651584, maxBitrate: 100000000, tier: 'M', level: 17 }, // Level 6.1 (Main Tier)
|
||||
{ maxPictureSize: 35651584, maxBitrate: 480000000, tier: 'H', level: 17 }, // Level 6.1 (High Tier)
|
||||
{ maxPictureSize: 35651584, maxBitrate: 160000000, tier: 'M', level: 18 }, // Level 6.2 (Main Tier)
|
||||
{ maxPictureSize: 35651584, maxBitrate: 800000000, tier: 'H', level: 18 }, // Level 6.2 (High Tier)
|
||||
{ maxPictureSize: 35651584, maxBitrate: 160000000, tier: 'M', level: 19 }, // Level 6.3 (Main Tier)
|
||||
{ maxPictureSize: 35651584, maxBitrate: 800000000, tier: 'H', level: 19 }, // Level 6.3 (High Tier)
|
||||
];
|
||||
const VP9_DEFAULT_SUFFIX = '.01.01.01.01.00';
|
||||
const AV1_DEFAULT_SUFFIX = '.0.110.01.01.01.0';
|
||||
export const buildVideoCodecString = (codec, width, height, bitrate) => {
|
||||
if (codec === 'avc') {
|
||||
const profileIndication = 0x64; // High Profile
|
||||
const totalMacroblocks = Math.ceil(width / 16) * Math.ceil(height / 16);
|
||||
// Determine the level based on the table
|
||||
const levelInfo = AVC_LEVEL_TABLE.find(level => totalMacroblocks <= level.maxMacroblocks && bitrate <= level.maxBitrate) ?? last(AVC_LEVEL_TABLE);
|
||||
const levelIndication = levelInfo ? levelInfo.level : 0;
|
||||
const hexProfileIndication = profileIndication.toString(16).padStart(2, '0');
|
||||
const hexProfileCompatibility = '00';
|
||||
const hexLevelIndication = levelIndication.toString(16).padStart(2, '0');
|
||||
return `avc1.${hexProfileIndication}${hexProfileCompatibility}${hexLevelIndication}`;
|
||||
}
|
||||
else if (codec === 'hevc') {
|
||||
const profilePrefix = ''; // Profile space 0
|
||||
const profileIdc = 1; // Main Profile
|
||||
const compatibilityFlags = '6'; // Taken from the example in ISO 14496-15
|
||||
const pictureSize = width * height;
|
||||
const levelInfo = HEVC_LEVEL_TABLE.find(level => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate) ?? last(HEVC_LEVEL_TABLE);
|
||||
const constraintFlags = 'B0'; // Progressive source flag
|
||||
return 'hev1.'
|
||||
+ `${profilePrefix}${profileIdc}.`
|
||||
+ `${compatibilityFlags}.`
|
||||
+ `${levelInfo.tier}${levelInfo.level}.`
|
||||
+ `${constraintFlags}`;
|
||||
}
|
||||
else if (codec === 'vp8') {
|
||||
return 'vp8'; // Easy, this one
|
||||
}
|
||||
else if (codec === 'vp9') {
|
||||
const profile = '00'; // Profile 0
|
||||
const pictureSize = width * height;
|
||||
const levelInfo = VP9_LEVEL_TABLE.find(level => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate) ?? last(VP9_LEVEL_TABLE);
|
||||
const bitDepth = '08'; // 8-bit
|
||||
return `vp09.${profile}.${levelInfo.level.toString().padStart(2, '0')}.${bitDepth}`;
|
||||
}
|
||||
else if (codec === 'av1') {
|
||||
const profile = 0; // Main Profile, single digit
|
||||
const pictureSize = width * height;
|
||||
const levelInfo = AV1_LEVEL_TABLE.find(level => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate) ?? last(AV1_LEVEL_TABLE);
|
||||
const level = levelInfo.level.toString().padStart(2, '0');
|
||||
const bitDepth = '08'; // 8-bit
|
||||
return `av01.${profile}.${level}${levelInfo.tier}.${bitDepth}`;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
throw new TypeError(`Unhandled codec '${codec}'.`);
|
||||
};
|
||||
export const generateVp9CodecConfigurationFromCodecString = (codecString) => {
|
||||
// Reference: https://www.webmproject.org/docs/container/#vp9-codec-feature-metadata-codecprivate
|
||||
const parts = codecString.split('.'); // We can derive the required values from the codec string
|
||||
const profile = Number(parts[1]);
|
||||
const level = Number(parts[2]);
|
||||
const bitDepth = Number(parts[3]);
|
||||
const chromaSubsampling = parts[4] ? Number(parts[4]) : 1;
|
||||
return [
|
||||
1, 1, profile,
|
||||
2, 1, level,
|
||||
3, 1, bitDepth,
|
||||
4, 1, chromaSubsampling,
|
||||
];
|
||||
};
|
||||
export const generateAv1CodecConfigurationFromCodecString = (codecString) => {
|
||||
// Reference: https://aomediacodec.github.io/av1-isobmff/
|
||||
const parts = codecString.split('.'); // We can derive the required values from the codec string
|
||||
const marker = 1;
|
||||
const version = 1;
|
||||
const firstByte = (marker << 7) + version;
|
||||
const profile = Number(parts[1]);
|
||||
const levelAndTier = parts[2];
|
||||
const level = Number(levelAndTier.slice(0, -1));
|
||||
const secondByte = (profile << 5) + level;
|
||||
const tier = levelAndTier.slice(-1) === 'H' ? 1 : 0;
|
||||
const bitDepth = Number(parts[3]);
|
||||
const highBitDepth = bitDepth === 8 ? 0 : 1;
|
||||
const twelveBit = 0;
|
||||
const monochrome = parts[4] ? Number(parts[4]) : 0;
|
||||
const chromaSubsamplingX = parts[5] ? Number(parts[5][0]) : 1;
|
||||
const chromaSubsamplingY = parts[5] ? Number(parts[5][1]) : 1;
|
||||
const chromaSamplePosition = parts[5] ? Number(parts[5][2]) : 0; // CSP_UNKNOWN
|
||||
const thirdByte = (tier << 7)
|
||||
+ (highBitDepth << 6)
|
||||
+ (twelveBit << 5)
|
||||
+ (monochrome << 4)
|
||||
+ (chromaSubsamplingX << 3)
|
||||
+ (chromaSubsamplingY << 2)
|
||||
+ chromaSamplePosition;
|
||||
const initialPresentationDelayPresent = 0; // Should be fine
|
||||
const fourthByte = initialPresentationDelayPresent;
|
||||
return [firstByte, secondByte, thirdByte, fourthByte];
|
||||
};
|
||||
export const extractVideoCodecString = (trackInfo) => {
|
||||
const { codec, codecDescription, colorSpace, avcCodecInfo, hevcCodecInfo, vp9CodecInfo, av1CodecInfo } = trackInfo;
|
||||
if (codec === 'avc') {
|
||||
assert(trackInfo.avcType !== null);
|
||||
if (avcCodecInfo) {
|
||||
const bytes = new Uint8Array([
|
||||
avcCodecInfo.avcProfileIndication,
|
||||
avcCodecInfo.profileCompatibility,
|
||||
avcCodecInfo.avcLevelIndication,
|
||||
]);
|
||||
return `avc${trackInfo.avcType}.${bytesToHexString(bytes)}`;
|
||||
}
|
||||
if (!codecDescription || codecDescription.byteLength < 4) {
|
||||
throw new TypeError('AVC decoder description is not provided or is not at least 4 bytes long.');
|
||||
}
|
||||
return `avc${trackInfo.avcType}.${bytesToHexString(codecDescription.subarray(1, 4))}`;
|
||||
}
|
||||
else if (codec === 'hevc') {
|
||||
let generalProfileSpace;
|
||||
let generalProfileIdc;
|
||||
let compatibilityFlags;
|
||||
let generalTierFlag;
|
||||
let generalLevelIdc;
|
||||
let constraintFlags;
|
||||
if (hevcCodecInfo) {
|
||||
generalProfileSpace = hevcCodecInfo.generalProfileSpace;
|
||||
generalProfileIdc = hevcCodecInfo.generalProfileIdc;
|
||||
compatibilityFlags = reverseBitsU32(hevcCodecInfo.generalProfileCompatibilityFlags);
|
||||
generalTierFlag = hevcCodecInfo.generalTierFlag;
|
||||
generalLevelIdc = hevcCodecInfo.generalLevelIdc;
|
||||
constraintFlags = [...hevcCodecInfo.generalConstraintIndicatorFlags];
|
||||
}
|
||||
else {
|
||||
if (!codecDescription || codecDescription.byteLength < 23) {
|
||||
throw new TypeError('HEVC decoder description is not provided or is not at least 23 bytes long.');
|
||||
}
|
||||
const view = toDataView(codecDescription);
|
||||
const profileByte = view.getUint8(1);
|
||||
generalProfileSpace = (profileByte >> 6) & 0x03;
|
||||
generalProfileIdc = profileByte & 0x1F;
|
||||
compatibilityFlags = reverseBitsU32(view.getUint32(2));
|
||||
generalTierFlag = (profileByte >> 5) & 0x01;
|
||||
generalLevelIdc = view.getUint8(12);
|
||||
constraintFlags = [];
|
||||
for (let i = 0; i < 6; i++) {
|
||||
constraintFlags.push(view.getUint8(6 + i));
|
||||
}
|
||||
}
|
||||
let codecString = 'hev1.';
|
||||
codecString += ['', 'A', 'B', 'C'][generalProfileSpace] + generalProfileIdc;
|
||||
codecString += '.';
|
||||
codecString += compatibilityFlags.toString(16).toUpperCase();
|
||||
codecString += '.';
|
||||
codecString += generalTierFlag === 0 ? 'L' : 'H';
|
||||
codecString += generalLevelIdc;
|
||||
while (constraintFlags.length > 0 && constraintFlags[constraintFlags.length - 1] === 0) {
|
||||
constraintFlags.pop();
|
||||
}
|
||||
if (constraintFlags.length > 0) {
|
||||
codecString += '.';
|
||||
codecString += constraintFlags.map(x => x.toString(16).toUpperCase()).join('.');
|
||||
}
|
||||
return codecString;
|
||||
}
|
||||
else if (codec === 'vp8') {
|
||||
return 'vp8'; // Easy, this one
|
||||
}
|
||||
else if (codec === 'vp9') {
|
||||
if (!vp9CodecInfo) {
|
||||
// Calculate level based on dimensions
|
||||
const pictureSize = trackInfo.width * trackInfo.height;
|
||||
let level = last(VP9_LEVEL_TABLE).level; // Default to highest level
|
||||
for (const entry of VP9_LEVEL_TABLE) {
|
||||
if (pictureSize <= entry.maxPictureSize) {
|
||||
level = entry.level;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// We don't really know better, so let's return a general-purpose, common codec string and hope for the best
|
||||
return `vp09.00.${level.toString().padStart(2, '0')}.08`;
|
||||
}
|
||||
const profile = vp9CodecInfo.profile.toString().padStart(2, '0');
|
||||
const level = vp9CodecInfo.level.toString().padStart(2, '0');
|
||||
const bitDepth = vp9CodecInfo.bitDepth.toString().padStart(2, '0');
|
||||
const chromaSubsampling = vp9CodecInfo.chromaSubsampling.toString().padStart(2, '0');
|
||||
const colourPrimaries = vp9CodecInfo.colourPrimaries.toString().padStart(2, '0');
|
||||
const transferCharacteristics = vp9CodecInfo.transferCharacteristics.toString().padStart(2, '0');
|
||||
const matrixCoefficients = vp9CodecInfo.matrixCoefficients.toString().padStart(2, '0');
|
||||
const videoFullRangeFlag = vp9CodecInfo.videoFullRangeFlag.toString().padStart(2, '0');
|
||||
let string = `vp09.${profile}.${level}.${bitDepth}.${chromaSubsampling}`;
|
||||
string += `.${colourPrimaries}.${transferCharacteristics}.${matrixCoefficients}.${videoFullRangeFlag}`;
|
||||
if (string.endsWith(VP9_DEFAULT_SUFFIX)) {
|
||||
string = string.slice(0, -VP9_DEFAULT_SUFFIX.length);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
else if (codec === 'av1') {
|
||||
if (!av1CodecInfo) {
|
||||
// Calculate level based on dimensions
|
||||
const pictureSize = trackInfo.width * trackInfo.height;
|
||||
let level = last(VP9_LEVEL_TABLE).level; // Default to highest level
|
||||
for (const entry of VP9_LEVEL_TABLE) {
|
||||
if (pictureSize <= entry.maxPictureSize) {
|
||||
level = entry.level;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// We don't really know better, so let's return a general-purpose, common codec string and hope for the best
|
||||
return `av01.0.${level.toString().padStart(2, '0')}M.08`;
|
||||
}
|
||||
// https://aomediacodec.github.io/av1-isobmff/#codecsparam
|
||||
const profile = av1CodecInfo.profile; // Single digit
|
||||
const level = av1CodecInfo.level.toString().padStart(2, '0');
|
||||
const tier = av1CodecInfo.tier ? 'H' : 'M';
|
||||
const bitDepth = av1CodecInfo.bitDepth.toString().padStart(2, '0');
|
||||
const monochrome = av1CodecInfo.monochrome ? '1' : '0';
|
||||
const chromaSubsampling = 100 * av1CodecInfo.chromaSubsamplingX
|
||||
+ 10 * av1CodecInfo.chromaSubsamplingY
|
||||
+ 1 * (av1CodecInfo.chromaSubsamplingX && av1CodecInfo.chromaSubsamplingY
|
||||
? av1CodecInfo.chromaSamplePosition
|
||||
: 0);
|
||||
// The defaults are 1 (ITU-R BT.709)
|
||||
const colorPrimaries = colorSpace?.primaries ? COLOR_PRIMARIES_MAP[colorSpace.primaries] : 1;
|
||||
const transferCharacteristics = colorSpace?.transfer ? TRANSFER_CHARACTERISTICS_MAP[colorSpace.transfer] : 1;
|
||||
const matrixCoefficients = colorSpace?.matrix ? MATRIX_COEFFICIENTS_MAP[colorSpace.matrix] : 1;
|
||||
const videoFullRangeFlag = colorSpace?.fullRange ? 1 : 0;
|
||||
let string = `av01.${profile}.${level}${tier}.${bitDepth}`;
|
||||
string += `.${monochrome}.${chromaSubsampling.toString().padStart(3, '0')}`;
|
||||
string += `.${colorPrimaries.toString().padStart(2, '0')}`;
|
||||
string += `.${transferCharacteristics.toString().padStart(2, '0')}`;
|
||||
string += `.${matrixCoefficients.toString().padStart(2, '0')}`;
|
||||
string += `.${videoFullRangeFlag}`;
|
||||
if (string.endsWith(AV1_DEFAULT_SUFFIX)) {
|
||||
string = string.slice(0, -AV1_DEFAULT_SUFFIX.length);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
throw new TypeError(`Unhandled codec '${codec}'.`);
|
||||
};
|
||||
export const buildAudioCodecString = (codec, numberOfChannels, sampleRate) => {
|
||||
if (codec === 'aac') {
|
||||
// If stereo or higher channels and lower sample rate, likely using HE-AAC v2 with PS
|
||||
if (numberOfChannels >= 2 && sampleRate <= 24000) {
|
||||
return 'mp4a.40.29'; // HE-AAC v2 (AAC LC + SBR + PS)
|
||||
}
|
||||
// If sample rate is low, likely using HE-AAC v1 with SBR
|
||||
if (sampleRate <= 24000) {
|
||||
return 'mp4a.40.5'; // HE-AAC v1 (AAC LC + SBR)
|
||||
}
|
||||
// Default to standard AAC-LC for higher sample rates
|
||||
return 'mp4a.40.2'; // AAC-LC
|
||||
}
|
||||
else if (codec === 'mp3') {
|
||||
return 'mp3';
|
||||
}
|
||||
else if (codec === 'opus') {
|
||||
return 'opus';
|
||||
}
|
||||
else if (codec === 'vorbis') {
|
||||
return 'vorbis';
|
||||
}
|
||||
else if (codec === 'flac') {
|
||||
return 'flac';
|
||||
}
|
||||
else if (codec === 'ac3') {
|
||||
return 'ac-3';
|
||||
}
|
||||
else if (codec === 'eac3') {
|
||||
return 'ec-3';
|
||||
}
|
||||
else if (PCM_AUDIO_CODECS.includes(codec)) {
|
||||
return codec;
|
||||
}
|
||||
throw new TypeError(`Unhandled codec '${codec}'.`);
|
||||
};
|
||||
export const extractAudioCodecString = (trackInfo) => {
|
||||
const { codec, codecDescription, aacCodecInfo } = trackInfo;
|
||||
if (codec === 'aac') {
|
||||
if (!aacCodecInfo) {
|
||||
throw new TypeError('AAC codec info must be provided.');
|
||||
}
|
||||
if (aacCodecInfo.isMpeg2) {
|
||||
return 'mp4a.67';
|
||||
}
|
||||
else {
|
||||
let objectType;
|
||||
if (aacCodecInfo.objectType !== null) {
|
||||
objectType = aacCodecInfo.objectType;
|
||||
}
|
||||
else {
|
||||
const audioSpecificConfig = parseAacAudioSpecificConfig(codecDescription);
|
||||
objectType = audioSpecificConfig.objectType;
|
||||
}
|
||||
return `mp4a.40.${objectType}`;
|
||||
}
|
||||
}
|
||||
else if (codec === 'mp3') {
|
||||
return 'mp3';
|
||||
}
|
||||
else if (codec === 'opus') {
|
||||
return 'opus';
|
||||
}
|
||||
else if (codec === 'vorbis') {
|
||||
return 'vorbis';
|
||||
}
|
||||
else if (codec === 'flac') {
|
||||
return 'flac';
|
||||
}
|
||||
else if (codec === 'ac3') {
|
||||
return 'ac-3';
|
||||
}
|
||||
else if (codec === 'eac3') {
|
||||
return 'ec-3';
|
||||
}
|
||||
else if (codec && PCM_AUDIO_CODECS.includes(codec)) {
|
||||
return codec;
|
||||
}
|
||||
throw new TypeError(`Unhandled codec '${codec}'.`);
|
||||
};
|
||||
export const aacFrequencyTable = [
|
||||
96000, 88200, 64000, 48000, 44100, 32000,
|
||||
24000, 22050, 16000, 12000, 11025, 8000, 7350,
|
||||
];
|
||||
export const aacChannelMap = [-1, 1, 2, 3, 4, 5, 6, 8];
|
||||
export const parseAacAudioSpecificConfig = (bytes) => {
|
||||
if (!bytes || bytes.byteLength < 2) {
|
||||
throw new TypeError('AAC description must be at least 2 bytes long.');
|
||||
}
|
||||
const bitstream = new Bitstream(bytes);
|
||||
let objectType = bitstream.readBits(5);
|
||||
if (objectType === 31) {
|
||||
objectType = 32 + bitstream.readBits(6);
|
||||
}
|
||||
const frequencyIndex = bitstream.readBits(4);
|
||||
let sampleRate = null;
|
||||
if (frequencyIndex === 15) {
|
||||
sampleRate = bitstream.readBits(24);
|
||||
}
|
||||
else {
|
||||
if (frequencyIndex < aacFrequencyTable.length) {
|
||||
sampleRate = aacFrequencyTable[frequencyIndex];
|
||||
}
|
||||
}
|
||||
const channelConfiguration = bitstream.readBits(4);
|
||||
let numberOfChannels = null;
|
||||
if (channelConfiguration >= 1 && channelConfiguration <= 7) {
|
||||
numberOfChannels = aacChannelMap[channelConfiguration];
|
||||
}
|
||||
return {
|
||||
objectType,
|
||||
frequencyIndex,
|
||||
sampleRate,
|
||||
channelConfiguration,
|
||||
numberOfChannels,
|
||||
};
|
||||
};
|
||||
export const buildAacAudioSpecificConfig = (config) => {
|
||||
let frequencyIndex = aacFrequencyTable.indexOf(config.sampleRate);
|
||||
let customSampleRate = null;
|
||||
if (frequencyIndex === -1) {
|
||||
frequencyIndex = 15;
|
||||
customSampleRate = config.sampleRate;
|
||||
}
|
||||
const channelConfiguration = aacChannelMap.indexOf(config.numberOfChannels);
|
||||
if (channelConfiguration === -1) {
|
||||
throw new TypeError(`Unsupported number of channels: ${config.numberOfChannels}`);
|
||||
}
|
||||
let bitCount = 5 + 4 + 4;
|
||||
if (config.objectType >= 32) {
|
||||
bitCount += 6;
|
||||
}
|
||||
if (frequencyIndex === 15) {
|
||||
bitCount += 24;
|
||||
}
|
||||
const byteCount = Math.ceil(bitCount / 8);
|
||||
const bytes = new Uint8Array(byteCount);
|
||||
const bitstream = new Bitstream(bytes);
|
||||
if (config.objectType < 32) {
|
||||
bitstream.writeBits(5, config.objectType);
|
||||
}
|
||||
else {
|
||||
bitstream.writeBits(5, 31);
|
||||
bitstream.writeBits(6, config.objectType - 32);
|
||||
}
|
||||
bitstream.writeBits(4, frequencyIndex);
|
||||
if (frequencyIndex === 15) {
|
||||
bitstream.writeBits(24, customSampleRate);
|
||||
}
|
||||
bitstream.writeBits(4, channelConfiguration);
|
||||
return bytes;
|
||||
};
|
||||
export const OPUS_SAMPLE_RATE = 48_000;
|
||||
const PCM_CODEC_REGEX = /^pcm-([usf])(\d+)+(be)?$/;
|
||||
export const parsePcmCodec = (codec) => {
|
||||
assert(PCM_AUDIO_CODECS.includes(codec));
|
||||
if (codec === 'ulaw') {
|
||||
return { dataType: 'ulaw', sampleSize: 1, littleEndian: true, silentValue: 255 };
|
||||
}
|
||||
else if (codec === 'alaw') {
|
||||
return { dataType: 'alaw', sampleSize: 1, littleEndian: true, silentValue: 213 };
|
||||
}
|
||||
const match = PCM_CODEC_REGEX.exec(codec);
|
||||
assert(match);
|
||||
let dataType;
|
||||
if (match[1] === 'u') {
|
||||
dataType = 'unsigned';
|
||||
}
|
||||
else if (match[1] === 's') {
|
||||
dataType = 'signed';
|
||||
}
|
||||
else {
|
||||
dataType = 'float';
|
||||
}
|
||||
const sampleSize = (Number(match[2]) / 8);
|
||||
const littleEndian = match[3] !== 'be';
|
||||
const silentValue = codec === 'pcm-u8' ? 2 ** 7 : 0;
|
||||
return { dataType, sampleSize, littleEndian, silentValue };
|
||||
};
|
||||
export const inferCodecFromCodecString = (codecString) => {
|
||||
// Video codecs
|
||||
if (codecString.startsWith('avc1') || codecString.startsWith('avc3')) {
|
||||
return 'avc';
|
||||
}
|
||||
else if (codecString.startsWith('hev1') || codecString.startsWith('hvc1')) {
|
||||
return 'hevc';
|
||||
}
|
||||
else if (codecString === 'vp8') {
|
||||
return 'vp8';
|
||||
}
|
||||
else if (codecString.startsWith('vp09')) {
|
||||
return 'vp9';
|
||||
}
|
||||
else if (codecString.startsWith('av01')) {
|
||||
return 'av1';
|
||||
}
|
||||
// Audio codecs
|
||||
if (codecString.startsWith('mp4a.40') || codecString === 'mp4a.67') {
|
||||
return 'aac';
|
||||
}
|
||||
else if (codecString === 'mp3'
|
||||
|| codecString === 'mp4a.69'
|
||||
|| codecString === 'mp4a.6B'
|
||||
|| codecString === 'mp4a.6b') {
|
||||
return 'mp3';
|
||||
}
|
||||
else if (codecString === 'opus') {
|
||||
return 'opus';
|
||||
}
|
||||
else if (codecString === 'vorbis') {
|
||||
return 'vorbis';
|
||||
}
|
||||
else if (codecString === 'flac') {
|
||||
return 'flac';
|
||||
}
|
||||
else if (codecString === 'ac-3' || codecString === 'ac3') {
|
||||
return 'ac3';
|
||||
}
|
||||
else if (codecString === 'ec-3' || codecString === 'eac3') {
|
||||
return 'eac3';
|
||||
}
|
||||
else if (codecString === 'ulaw') {
|
||||
return 'ulaw';
|
||||
}
|
||||
else if (codecString === 'alaw') {
|
||||
return 'alaw';
|
||||
}
|
||||
else if (PCM_CODEC_REGEX.test(codecString)) {
|
||||
return codecString;
|
||||
}
|
||||
// Subtitle codecs
|
||||
if (codecString === 'webvtt') {
|
||||
return 'webvtt';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
export const getVideoEncoderConfigExtension = (codec) => {
|
||||
if (codec === 'avc') {
|
||||
return {
|
||||
avc: {
|
||||
format: 'avc', // Ensure the format is not Annex B
|
||||
},
|
||||
};
|
||||
}
|
||||
else if (codec === 'hevc') {
|
||||
return {
|
||||
hevc: {
|
||||
format: 'hevc', // Ensure the format is not Annex B
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
export const getAudioEncoderConfigExtension = (codec) => {
|
||||
if (codec === 'aac') {
|
||||
return {
|
||||
aac: {
|
||||
format: 'aac', // Ensure the format is not ADTS
|
||||
},
|
||||
};
|
||||
}
|
||||
else if (codec === 'opus') {
|
||||
return {
|
||||
opus: {
|
||||
format: 'opus',
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
const VALID_VIDEO_CODEC_STRING_PREFIXES = ['avc1', 'avc3', 'hev1', 'hvc1', 'vp8', 'vp09', 'av01'];
|
||||
const AVC_CODEC_STRING_REGEX = /^(avc1|avc3)\.[0-9a-fA-F]{6}$/;
|
||||
const HEVC_CODEC_STRING_REGEX = /^(hev1|hvc1)\.(?:[ABC]?\d+)\.[0-9a-fA-F]{1,8}\.[LH]\d+(?:\.[0-9a-fA-F]{1,2}){0,6}$/;
|
||||
const VP9_CODEC_STRING_REGEX = /^vp09(?:\.\d{2}){3}(?:(?:\.\d{2}){5})?$/;
|
||||
const AV1_CODEC_STRING_REGEX = /^av01\.\d\.\d{2}[MH]\.\d{2}(?:\.\d\.\d{3}\.\d{2}\.\d{2}\.\d{2}\.\d)?$/;
|
||||
export const validateVideoChunkMetadata = (metadata) => {
|
||||
if (!metadata) {
|
||||
throw new TypeError('Video chunk metadata must be provided.');
|
||||
}
|
||||
if (typeof metadata !== 'object') {
|
||||
throw new TypeError('Video chunk metadata must be an object.');
|
||||
}
|
||||
if (!metadata.decoderConfig) {
|
||||
throw new TypeError('Video chunk metadata must include a decoder configuration.');
|
||||
}
|
||||
if (typeof metadata.decoderConfig !== 'object') {
|
||||
throw new TypeError('Video chunk metadata decoder configuration must be an object.');
|
||||
}
|
||||
if (typeof metadata.decoderConfig.codec !== 'string') {
|
||||
throw new TypeError('Video chunk metadata decoder configuration must specify a codec string.');
|
||||
}
|
||||
if (!VALID_VIDEO_CODEC_STRING_PREFIXES.some(prefix => metadata.decoderConfig.codec.startsWith(prefix))) {
|
||||
throw new TypeError('Video chunk metadata decoder configuration codec string must be a valid video codec string as specified in'
|
||||
+ ' the Mediabunny Codec Registry.');
|
||||
}
|
||||
if (!Number.isInteger(metadata.decoderConfig.codedWidth) || metadata.decoderConfig.codedWidth <= 0) {
|
||||
throw new TypeError('Video chunk metadata decoder configuration must specify a valid codedWidth (positive integer).');
|
||||
}
|
||||
if (!Number.isInteger(metadata.decoderConfig.codedHeight) || metadata.decoderConfig.codedHeight <= 0) {
|
||||
throw new TypeError('Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer).');
|
||||
}
|
||||
if (metadata.decoderConfig.description !== undefined) {
|
||||
if (!isAllowSharedBufferSource(metadata.decoderConfig.description)) {
|
||||
throw new TypeError('Video chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an'
|
||||
+ ' ArrayBuffer view.');
|
||||
}
|
||||
}
|
||||
if (metadata.decoderConfig.colorSpace !== undefined) {
|
||||
const { colorSpace } = metadata.decoderConfig;
|
||||
if (typeof colorSpace !== 'object') {
|
||||
throw new TypeError('Video chunk metadata decoder configuration colorSpace, when provided, must be an object.');
|
||||
}
|
||||
const primariesValues = Object.keys(COLOR_PRIMARIES_MAP);
|
||||
if (colorSpace.primaries != null && !primariesValues.includes(colorSpace.primaries)) {
|
||||
throw new TypeError(`Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of`
|
||||
+ ` ${primariesValues.join(', ')}.`);
|
||||
}
|
||||
const transferValues = Object.keys(TRANSFER_CHARACTERISTICS_MAP);
|
||||
if (colorSpace.transfer != null && !transferValues.includes(colorSpace.transfer)) {
|
||||
throw new TypeError(`Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of`
|
||||
+ ` ${transferValues.join(', ')}.`);
|
||||
}
|
||||
const matrixValues = Object.keys(MATRIX_COEFFICIENTS_MAP);
|
||||
if (colorSpace.matrix != null && !matrixValues.includes(colorSpace.matrix)) {
|
||||
throw new TypeError(`Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of`
|
||||
+ ` ${matrixValues.join(', ')}.`);
|
||||
}
|
||||
if (colorSpace.fullRange != null && typeof colorSpace.fullRange !== 'boolean') {
|
||||
throw new TypeError('Video chunk metadata decoder configuration colorSpace fullRange, when defined, must be a boolean.');
|
||||
}
|
||||
}
|
||||
if (metadata.decoderConfig.codec.startsWith('avc1') || metadata.decoderConfig.codec.startsWith('avc3')) {
|
||||
// AVC-specific validation
|
||||
if (!AVC_CODEC_STRING_REGEX.test(metadata.decoderConfig.codec)) {
|
||||
throw new TypeError('Video chunk metadata decoder configuration codec string for AVC must be a valid AVC codec string as'
|
||||
+ ' specified in Section 3.4 of RFC 6381.');
|
||||
}
|
||||
// `description` may or may not be set, depending on if the format is AVCC or Annex B, so don't perform any
|
||||
// validation for it.
|
||||
// https://www.w3.org/TR/webcodecs-avc-codec-registration
|
||||
}
|
||||
else if (metadata.decoderConfig.codec.startsWith('hev1') || metadata.decoderConfig.codec.startsWith('hvc1')) {
|
||||
// HEVC-specific validation
|
||||
if (!HEVC_CODEC_STRING_REGEX.test(metadata.decoderConfig.codec)) {
|
||||
throw new TypeError('Video chunk metadata decoder configuration codec string for HEVC must be a valid HEVC codec string as'
|
||||
+ ' specified in Section E.3 of ISO 14496-15.');
|
||||
}
|
||||
// `description` may or may not be set, depending on if the format is HEVC or Annex B, so don't perform any
|
||||
// validation for it.
|
||||
// https://www.w3.org/TR/webcodecs-hevc-codec-registration
|
||||
}
|
||||
else if (metadata.decoderConfig.codec.startsWith('vp8')) {
|
||||
// VP8-specific validation
|
||||
if (metadata.decoderConfig.codec !== 'vp8') {
|
||||
throw new TypeError('Video chunk metadata decoder configuration codec string for VP8 must be "vp8".');
|
||||
}
|
||||
}
|
||||
else if (metadata.decoderConfig.codec.startsWith('vp09')) {
|
||||
// VP9-specific validation
|
||||
if (!VP9_CODEC_STRING_REGEX.test(metadata.decoderConfig.codec)) {
|
||||
throw new TypeError('Video chunk metadata decoder configuration codec string for VP9 must be a valid VP9 codec string as'
|
||||
+ ' specified in Section "Codecs Parameter String" of https://www.webmproject.org/vp9/mp4/.');
|
||||
}
|
||||
}
|
||||
else if (metadata.decoderConfig.codec.startsWith('av01')) {
|
||||
// AV1-specific validation
|
||||
if (!AV1_CODEC_STRING_REGEX.test(metadata.decoderConfig.codec)) {
|
||||
throw new TypeError('Video chunk metadata decoder configuration codec string for AV1 must be a valid AV1 codec string as'
|
||||
+ ' specified in Section "Codecs Parameter String" of https://aomediacodec.github.io/av1-isobmff/.');
|
||||
}
|
||||
}
|
||||
};
|
||||
const VALID_AUDIO_CODEC_STRING_PREFIXES = [
|
||||
'mp4a', 'mp3', 'opus', 'vorbis', 'flac', 'ulaw', 'alaw', 'pcm', 'ac-3', 'ec-3',
|
||||
];
|
||||
export const validateAudioChunkMetadata = (metadata) => {
|
||||
if (!metadata) {
|
||||
throw new TypeError('Audio chunk metadata must be provided.');
|
||||
}
|
||||
if (typeof metadata !== 'object') {
|
||||
throw new TypeError('Audio chunk metadata must be an object.');
|
||||
}
|
||||
if (!metadata.decoderConfig) {
|
||||
throw new TypeError('Audio chunk metadata must include a decoder configuration.');
|
||||
}
|
||||
if (typeof metadata.decoderConfig !== 'object') {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration must be an object.');
|
||||
}
|
||||
if (typeof metadata.decoderConfig.codec !== 'string') {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration must specify a codec string.');
|
||||
}
|
||||
if (!VALID_AUDIO_CODEC_STRING_PREFIXES.some(prefix => metadata.decoderConfig.codec.startsWith(prefix))) {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration codec string must be a valid audio codec string as specified in'
|
||||
+ ' the Mediabunny Codec Registry.');
|
||||
}
|
||||
if (!Number.isInteger(metadata.decoderConfig.sampleRate) || metadata.decoderConfig.sampleRate <= 0) {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration must specify a valid sampleRate (positive integer).');
|
||||
}
|
||||
if (!Number.isInteger(metadata.decoderConfig.numberOfChannels) || metadata.decoderConfig.numberOfChannels <= 0) {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration must specify a valid numberOfChannels (positive integer).');
|
||||
}
|
||||
if (metadata.decoderConfig.description !== undefined) {
|
||||
if (!isAllowSharedBufferSource(metadata.decoderConfig.description)) {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an'
|
||||
+ ' ArrayBuffer view.');
|
||||
}
|
||||
}
|
||||
if (metadata.decoderConfig.codec.startsWith('mp4a')
|
||||
// These three refer to MP3:
|
||||
&& metadata.decoderConfig.codec !== 'mp4a.69'
|
||||
&& metadata.decoderConfig.codec !== 'mp4a.6B'
|
||||
&& metadata.decoderConfig.codec !== 'mp4a.6b') {
|
||||
// AAC-specific validation
|
||||
const validStrings = ['mp4a.40.2', 'mp4a.40.02', 'mp4a.40.5', 'mp4a.40.05', 'mp4a.40.29', 'mp4a.67'];
|
||||
if (!validStrings.includes(metadata.decoderConfig.codec)) {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration codec string for AAC must be a valid AAC codec string as'
|
||||
+ ' specified in https://www.w3.org/TR/webcodecs-aac-codec-registration/.');
|
||||
}
|
||||
// `description` may or may not be set, depending on if the format is AAC or ADTS, so don't perform any
|
||||
// validation for it.
|
||||
// https://www.w3.org/TR/webcodecs-aac-codec-registration
|
||||
}
|
||||
else if (metadata.decoderConfig.codec.startsWith('mp3') || metadata.decoderConfig.codec.startsWith('mp4a')) {
|
||||
// MP3-specific validation
|
||||
if (metadata.decoderConfig.codec !== 'mp3'
|
||||
&& metadata.decoderConfig.codec !== 'mp4a.69'
|
||||
&& metadata.decoderConfig.codec !== 'mp4a.6B'
|
||||
&& metadata.decoderConfig.codec !== 'mp4a.6b') {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration codec string for MP3 must be "mp3", "mp4a.69" or'
|
||||
+ ' "mp4a.6B".');
|
||||
}
|
||||
}
|
||||
else if (metadata.decoderConfig.codec.startsWith('opus')) {
|
||||
// Opus-specific validation
|
||||
if (metadata.decoderConfig.codec !== 'opus') {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration codec string for Opus must be "opus".');
|
||||
}
|
||||
if (metadata.decoderConfig.description && metadata.decoderConfig.description.byteLength < 18) {
|
||||
// Description is optional for Opus per-spec, so we shouldn't enforce it
|
||||
throw new TypeError('Audio chunk metadata decoder configuration description, when specified, is expected to be an'
|
||||
+ ' Identification Header as specified in Section 5.1 of RFC 7845.');
|
||||
}
|
||||
}
|
||||
else if (metadata.decoderConfig.codec.startsWith('vorbis')) {
|
||||
// Vorbis-specific validation
|
||||
if (metadata.decoderConfig.codec !== 'vorbis') {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration codec string for Vorbis must be "vorbis".');
|
||||
}
|
||||
if (!metadata.decoderConfig.description) {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration for Vorbis must include a description, which is expected to'
|
||||
+ ' adhere to the format described in https://www.w3.org/TR/webcodecs-vorbis-codec-registration/.');
|
||||
}
|
||||
}
|
||||
else if (metadata.decoderConfig.codec.startsWith('flac')) {
|
||||
// FLAC-specific validation
|
||||
if (metadata.decoderConfig.codec !== 'flac') {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration codec string for FLAC must be "flac".');
|
||||
}
|
||||
const minDescriptionSize = 4 + 4 + 34; // 'fLaC' + metadata block header + STREAMINFO block
|
||||
if (!metadata.decoderConfig.description || metadata.decoderConfig.description.byteLength < minDescriptionSize) {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration for FLAC must include a description, which is expected to'
|
||||
+ ' adhere to the format described in https://www.w3.org/TR/webcodecs-flac-codec-registration/.');
|
||||
}
|
||||
}
|
||||
else if (metadata.decoderConfig.codec.startsWith('ac-3') || metadata.decoderConfig.codec.startsWith('ac3')) {
|
||||
// AC3-specific validation
|
||||
if (metadata.decoderConfig.codec !== 'ac-3') {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration codec string for AC-3 must be "ac-3".');
|
||||
}
|
||||
}
|
||||
else if (metadata.decoderConfig.codec.startsWith('ec-3') || metadata.decoderConfig.codec.startsWith('eac3')) {
|
||||
// EAC3-specific validation
|
||||
if (metadata.decoderConfig.codec !== 'ec-3') {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration codec string for EC-3 must be "ec-3".');
|
||||
}
|
||||
}
|
||||
else if (metadata.decoderConfig.codec.startsWith('pcm')
|
||||
|| metadata.decoderConfig.codec.startsWith('ulaw')
|
||||
|| metadata.decoderConfig.codec.startsWith('alaw')) {
|
||||
// PCM-specific validation
|
||||
if (!PCM_AUDIO_CODECS.includes(metadata.decoderConfig.codec)) {
|
||||
throw new TypeError('Audio chunk metadata decoder configuration codec string for PCM must be one of the supported PCM'
|
||||
+ ` codecs (${PCM_AUDIO_CODECS.join(', ')}).`);
|
||||
}
|
||||
}
|
||||
};
|
||||
export const validateSubtitleMetadata = (metadata) => {
|
||||
if (!metadata) {
|
||||
throw new TypeError('Subtitle metadata must be provided.');
|
||||
}
|
||||
if (typeof metadata !== 'object') {
|
||||
throw new TypeError('Subtitle metadata must be an object.');
|
||||
}
|
||||
if (!metadata.config) {
|
||||
throw new TypeError('Subtitle metadata must include a config object.');
|
||||
}
|
||||
if (typeof metadata.config !== 'object') {
|
||||
throw new TypeError('Subtitle metadata config must be an object.');
|
||||
}
|
||||
if (typeof metadata.config.description !== 'string') {
|
||||
throw new TypeError('Subtitle metadata config description must be a string.');
|
||||
}
|
||||
};
|
||||
Generated
Vendored
+334
@@ -0,0 +1,334 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { AudioCodec, VideoCodec } from './codec.js';
|
||||
import { Quality } from './encode.js';
|
||||
import { Input } from './input.js';
|
||||
import { InputAudioTrack, InputTrack, InputVideoTrack } from './input-track.js';
|
||||
import { MaybePromise, Rotation } from './misc.js';
|
||||
import { Output } from './output.js';
|
||||
import { AudioSample, VideoSample } from './sample.js';
|
||||
import { MetadataTags } from './metadata.js';
|
||||
/**
|
||||
* The options for media file conversion.
|
||||
* @group Conversion
|
||||
* @public
|
||||
*/
|
||||
export type ConversionOptions = {
|
||||
/** The input file. */
|
||||
input: Input;
|
||||
/** The output file. */
|
||||
output: Output;
|
||||
/**
|
||||
* Video-specific options. When passing an object, the same options are applied to all video tracks. When passing a
|
||||
* function, it will be invoked for each video track and is expected to return or resolve to the options
|
||||
* for that specific track. The function is passed an instance of {@link InputVideoTrack} as well as a number `n`,
|
||||
* which is the 1-based index of the track in the list of all video tracks. Using `n` is deprecated, prefer the
|
||||
* identical `track.number` instead.
|
||||
*/
|
||||
video?: ConversionVideoOptions | ((track: InputVideoTrack, n: number) => MaybePromise<ConversionVideoOptions | undefined>);
|
||||
/**
|
||||
* Audio-specific options. When passing an object, the same options are applied to all audio tracks. When passing a
|
||||
* function, it will be invoked for each audio track and is expected to return or resolve to the options
|
||||
* for that specific track. The function is passed an instance of {@link InputAudioTrack} as well as a number `n`,
|
||||
* which is the 1-based index of the track in the list of all audio tracks. Using `n` is deprecated, prefer the
|
||||
* identical `track.number` instead.
|
||||
*/
|
||||
audio?: ConversionAudioOptions | ((track: InputAudioTrack, n: number) => MaybePromise<ConversionAudioOptions | undefined>);
|
||||
/** Options to trim the input file. */
|
||||
trim?: {
|
||||
/**
|
||||
* The time in the input file in seconds at which the output file should start. Must be less than `end`.
|
||||
* When omitted, defaults to the start timestamp of the input or to 0, whichever is higher.
|
||||
*/
|
||||
start?: number;
|
||||
/**
|
||||
* The time in the input file in seconds at which the output file should end. Must be greater than `start`.
|
||||
* Defaults to the duration of the input when omitted.
|
||||
*/
|
||||
end?: number;
|
||||
};
|
||||
/**
|
||||
* An object or a callback that returns or resolves to an object containing the descriptive metadata tags that
|
||||
* should be written to the output file. If a function is passed, it will be passed the tags of the input file as
|
||||
* its first argument, allowing you to modify, augment or extend them.
|
||||
*
|
||||
* If no function is set, the input's metadata tags will be copied to the output.
|
||||
*/
|
||||
tags?: MetadataTags | ((inputTags: MetadataTags) => MaybePromise<MetadataTags>);
|
||||
/**
|
||||
* Whether to show potential console warnings about discarded tracks after calling `Conversion.init()`, defaults to
|
||||
* `true`. Set this to `false` if you're properly handling the `discardedTracks` and `isValid` fields already and
|
||||
* want to keep the console output clean.
|
||||
*/
|
||||
showWarnings?: boolean;
|
||||
};
|
||||
/**
|
||||
* Video-specific options.
|
||||
* @group Conversion
|
||||
* @public
|
||||
*/
|
||||
export type ConversionVideoOptions = {
|
||||
/** If `true`, all video tracks will be discarded and will not be present in the output. */
|
||||
discard?: boolean;
|
||||
/**
|
||||
* The desired width of the output video in pixels, defaulting to the video's natural display width. If height
|
||||
* is not set, it will be deduced automatically based on aspect ratio.
|
||||
*/
|
||||
width?: number;
|
||||
/**
|
||||
* The desired height of the output video in pixels, defaulting to the video's natural display height. If width
|
||||
* is not set, it will be deduced automatically based on aspect ratio.
|
||||
*/
|
||||
height?: number;
|
||||
/**
|
||||
* The fitting algorithm in case both width and height are set, or if the input video changes its size over time.
|
||||
*
|
||||
* - `'fill'` will stretch the image to fill the entire box, potentially altering aspect ratio.
|
||||
* - `'contain'` will contain the entire image within the box while preserving aspect ratio. This may lead to
|
||||
* letterboxing.
|
||||
* - `'cover'` will scale the image until the entire box is filled, while preserving aspect ratio.
|
||||
*/
|
||||
fit?: 'fill' | 'contain' | 'cover';
|
||||
/**
|
||||
* The angle in degrees to rotate the input video by, clockwise. Rotation is applied before cropping and resizing.
|
||||
* This rotation is _in addition to_ the natural rotation of the input video as specified in input file's metadata.
|
||||
*/
|
||||
rotate?: Rotation;
|
||||
/**
|
||||
* Defaults to `true`. When enabaled, Mediabunny will use the rotation metadata in the output file to perform video
|
||||
* rotation whenever possible. Set this field to `false` if you want to ensure the output file does not make use of
|
||||
* rotation metadata and that any rotation is baked into the video frames directly.
|
||||
*/
|
||||
allowRotationMetadata?: boolean;
|
||||
/**
|
||||
* Specifies the rectangular region of the input video to crop to. The crop region will automatically be clamped to
|
||||
* the dimensions of the input video track. Cropping is performed after rotation but before resizing.
|
||||
*/
|
||||
crop?: {
|
||||
/** The distance in pixels from the left edge of the source frame to the left edge of the crop rectangle. */
|
||||
left: number;
|
||||
/** The distance in pixels from the top edge of the source frame to the top edge of the crop rectangle. */
|
||||
top: number;
|
||||
/** The width in pixels of the crop rectangle. */
|
||||
width: number;
|
||||
/** The height in pixels of the crop rectangle. */
|
||||
height: number;
|
||||
};
|
||||
/**
|
||||
* The desired frame rate of the output video, in hertz. If not specified, the original input frame rate will
|
||||
* be used (which may be variable).
|
||||
*/
|
||||
frameRate?: number;
|
||||
/** The desired output video codec. */
|
||||
codec?: VideoCodec;
|
||||
/** The desired bitrate of the output video. */
|
||||
bitrate?: number | Quality;
|
||||
/**
|
||||
* Whether to discard or keep the transparency information of the input video. The default is `'discard'`. Note that
|
||||
* for `'keep'` to produce a transparent video, you must use an output config that supports it, such as WebM with
|
||||
* VP9.
|
||||
*/
|
||||
alpha?: 'discard' | 'keep';
|
||||
/**
|
||||
* The interval, in seconds, of how often frames are encoded as a key frame. The default is 5 seconds. Frequent key
|
||||
* frames improve seeking behavior but increase file size. When using multiple video tracks, you should give them
|
||||
* all the same key frame interval.
|
||||
*
|
||||
* Setting this fields forces a transcode.
|
||||
*/
|
||||
keyFrameInterval?: number;
|
||||
/**
|
||||
* A hint that configures the hardware acceleration method used when transcoding. This is best left on
|
||||
* `'no-preference'`, the default.
|
||||
*/
|
||||
hardwareAcceleration?: 'no-preference' | 'prefer-hardware' | 'prefer-software';
|
||||
/** When `true`, video will always be re-encoded instead of directly copying over the encoded samples. */
|
||||
forceTranscode?: boolean;
|
||||
/**
|
||||
* Allows for custom user-defined processing of video frames, e.g. for applying overlays, color transformations, or
|
||||
* timestamp modifications. Will be called for each input video sample after transformations and frame rate
|
||||
* corrections.
|
||||
*
|
||||
* Must return a {@link VideoSample} or a `CanvasImageSource`, an array of them, or `null` for dropping the frame.
|
||||
* When non-timestamped data is returned, the timestamp and duration from the source sample will be used. Rotation
|
||||
* metadata of the returned sample will be ignored.
|
||||
*
|
||||
* This function can also be used to manually resize frames. When doing so, you should signal the post-process
|
||||
* dimensions using the `processedWidth` and `processedHeight` fields, which enables the encoder to better know what
|
||||
* to expect. If these fields aren't set, Mediabunny will assume you won't perform any resizing.
|
||||
*/
|
||||
process?: (sample: VideoSample) => MaybePromise<CanvasImageSource | VideoSample | (CanvasImageSource | VideoSample)[] | null>;
|
||||
/**
|
||||
* An optional hint specifying the width of video samples returned by the `process` function, for better
|
||||
* encoder configuration.
|
||||
*/
|
||||
processedWidth?: number;
|
||||
/**
|
||||
* An optional hint specifying the height of video samples returned by the `process` function, for better
|
||||
* encoder configuration.
|
||||
*/
|
||||
processedHeight?: number;
|
||||
};
|
||||
/**
|
||||
* Audio-specific options.
|
||||
* @group Conversion
|
||||
* @public
|
||||
*/
|
||||
export type ConversionAudioOptions = {
|
||||
/** If `true`, all audio tracks will be discarded and will not be present in the output. */
|
||||
discard?: boolean;
|
||||
/** The desired channel count of the output audio. */
|
||||
numberOfChannels?: number;
|
||||
/** The desired sample rate of the output audio, in hertz. */
|
||||
sampleRate?: number;
|
||||
/** The desired output audio codec. */
|
||||
codec?: AudioCodec;
|
||||
/** The desired bitrate of the output audio. */
|
||||
bitrate?: number | Quality;
|
||||
/** When `true`, audio will always be re-encoded instead of directly copying over the encoded samples. */
|
||||
forceTranscode?: boolean;
|
||||
/**
|
||||
* Allows for custom user-defined processing of audio samples, e.g. for applying audio effects, transformations, or
|
||||
* timestamp modifications. Will be called for each input audio sample after remixing and resampling.
|
||||
*
|
||||
* Must return an {@link AudioSample}, an array of them, or `null` for dropping the sample.
|
||||
*
|
||||
* This function can also be used to manually perform remixing or resampling. When doing so, you should signal the
|
||||
* post-process parameters using the `processedNumberOfChannels` and `processedSampleRate` fields, which enables the
|
||||
* encoder to better know what to expect. If these fields aren't set, Mediabunny will assume you won't perform
|
||||
* remixing or resampling.
|
||||
*/
|
||||
process?: (sample: AudioSample) => MaybePromise<AudioSample | AudioSample[] | null>;
|
||||
/**
|
||||
* An optional hint specifying the channel count of audio samples returned by the `process` function, for better
|
||||
* encoder configuration.
|
||||
*/
|
||||
processedNumberOfChannels?: number;
|
||||
/**
|
||||
* An optional hint specifying the sample rate of audio samples returned by the `process` function, for better
|
||||
* encoder configuration.
|
||||
*/
|
||||
processedSampleRate?: number;
|
||||
};
|
||||
/**
|
||||
* An input track that was discarded (excluded) from a {@link Conversion} alongside the discard reason.
|
||||
* @group Conversion
|
||||
* @public
|
||||
*/
|
||||
export type DiscardedTrack = {
|
||||
/** The track that was discarded. */
|
||||
track: InputTrack;
|
||||
/**
|
||||
* The reason for discarding the track.
|
||||
*
|
||||
* - `'discarded_by_user'`: You discarded this track by setting `discard: true`.
|
||||
* - `'max_track_count_reached'`: The output had no more room for another track.
|
||||
* - `'max_track_count_of_type_reached'`: The output had no more room for another track of this type, or the output
|
||||
* doesn't support this track type at all.
|
||||
* - `'unknown_source_codec'`: We don't know the codec of the input track and therefore don't know what to do
|
||||
* with it.
|
||||
* - `'undecodable_source_codec'`: The input track's codec is known, but we are unable to decode it.
|
||||
* - `'no_encodable_target_codec'`: We can't find a codec that we are able to encode and that can be contained
|
||||
* within the output format. This reason can be hit if the environment doesn't support the necessary encoders, or if
|
||||
* you requested a codec that cannot be contained within the output format.
|
||||
*/
|
||||
reason: 'discarded_by_user' | 'max_track_count_reached' | 'max_track_count_of_type_reached' | 'unknown_source_codec' | 'undecodable_source_codec' | 'no_encodable_target_codec';
|
||||
};
|
||||
/**
|
||||
* Represents a media file conversion process, used to convert one media file into another. In addition to conversion,
|
||||
* this class can be used to resize and rotate video, resample audio, drop tracks, or trim to a specific time range.
|
||||
* @group Conversion
|
||||
* @public
|
||||
*/
|
||||
export declare class Conversion {
|
||||
/** The input file. */
|
||||
readonly input: Input;
|
||||
/** The output file. */
|
||||
readonly output: Output;
|
||||
/**
|
||||
* A callback that is fired whenever the conversion progresses. Returns a number between 0 and 1, indicating the
|
||||
* completion of the conversion. Note that a progress of 1 doesn't necessarily mean the conversion is complete;
|
||||
* the conversion is complete once `execute()` resolves.
|
||||
*
|
||||
* In order for progress to be computed, this property must be set before `execute` is called.
|
||||
*/
|
||||
onProgress?: (progress: number) => unknown;
|
||||
/**
|
||||
* Whether this conversion, as it has been configured, is valid and can be executed. If this field is `false`, check
|
||||
* the `discardedTracks` field for reasons.
|
||||
*/
|
||||
isValid: boolean;
|
||||
/** The list of tracks that are included in the output file. */
|
||||
readonly utilizedTracks: InputTrack[];
|
||||
/** The list of tracks from the input file that have been discarded, alongside the discard reason. */
|
||||
readonly discardedTracks: DiscardedTrack[];
|
||||
/** Initializes a new conversion process without starting the conversion. */
|
||||
static init(options: ConversionOptions): Promise<Conversion>;
|
||||
/** Creates a new Conversion instance (duh). */
|
||||
private constructor();
|
||||
/**
|
||||
* Executes the conversion process. Resolves once conversion is complete.
|
||||
*
|
||||
* Will throw if `isValid` is `false`.
|
||||
*/
|
||||
execute(): Promise<void>;
|
||||
/**
|
||||
* Cancels the conversion process, causing any ongoing `execute` call to throw a `ConversionCanceledError`.
|
||||
* Does nothing if the conversion is already complete.
|
||||
*/
|
||||
cancel(): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* Thrown when a conversion couldn't complete due to being canceled.
|
||||
* @group Conversion
|
||||
* @public
|
||||
*/
|
||||
export declare class ConversionCanceledError extends Error {
|
||||
/** Creates a new {@link ConversionCanceledError}. */
|
||||
constructor(message?: string);
|
||||
}
|
||||
/**
|
||||
* Utility class to handle audio resampling, handling both sample rate resampling as well as channel up/downmixing.
|
||||
* The advantage over doing this manually rather than using OfflineAudioContext to do it for us is the artifact-free
|
||||
* handling of putting multiple resampled audio samples back to back, which produces flaky results using
|
||||
* OfflineAudioContext.
|
||||
*/
|
||||
export declare class AudioResampler {
|
||||
sourceSampleRate: number | null;
|
||||
targetSampleRate: number;
|
||||
sourceNumberOfChannels: number | null;
|
||||
targetNumberOfChannels: number;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
onSample: (sample: AudioSample) => Promise<void>;
|
||||
bufferSizeInFrames: number;
|
||||
bufferSizeInSamples: number;
|
||||
outputBuffer: Float32Array;
|
||||
/** Start frame of current buffer */
|
||||
bufferStartFrame: number;
|
||||
/** The highest index written to in the current buffer */
|
||||
maxWrittenFrame: number;
|
||||
channelMixer: (sourceData: Float32Array, sourceFrameIndex: number, targetChannelIndex: number) => number;
|
||||
tempSourceBuffer: Float32Array;
|
||||
constructor(options: {
|
||||
targetSampleRate: number;
|
||||
targetNumberOfChannels: number;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
onSample: (sample: AudioSample) => Promise<void>;
|
||||
});
|
||||
/**
|
||||
* Sets up the channel mixer to handle up/downmixing in the case where input and output channel counts don't match.
|
||||
*/
|
||||
doChannelMixerSetup(): void;
|
||||
ensureTempBufferSize(requiredSamples: number): void;
|
||||
add(audioSample: AudioSample): Promise<void>;
|
||||
finalizeCurrentBuffer(): Promise<void>;
|
||||
finalize(): Promise<void>;
|
||||
}
|
||||
//# sourceMappingURL=conversion.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"conversion.d.ts","sourceRoot":"","sources":["../../../src/conversion.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+1347
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+126
@@ -0,0 +1,126 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { AudioCodec, VideoCodec } from './codec.js';
|
||||
import { MaybePromise } from './misc.js';
|
||||
import { EncodedPacket } from './packet.js';
|
||||
import { AudioSample, VideoSample } from './sample.js';
|
||||
/**
|
||||
* Base class for custom video decoders. To add your own custom video decoder, extend this class, implement the
|
||||
* abstract methods and static `supports` method, and register the decoder using {@link registerDecoder}.
|
||||
* @group Custom coders
|
||||
* @public
|
||||
*/
|
||||
export declare abstract class CustomVideoDecoder {
|
||||
/** The input video's codec. */
|
||||
readonly codec: VideoCodec;
|
||||
/** The input video's decoder config. */
|
||||
readonly config: VideoDecoderConfig;
|
||||
/** The callback to call when a decoded VideoSample is available. */
|
||||
readonly onSample: (sample: VideoSample) => unknown;
|
||||
/** Returns true if and only if the decoder can decode the given codec configuration. */
|
||||
static supports(codec: VideoCodec, config: VideoDecoderConfig): boolean;
|
||||
/** Called after decoder creation; can be used for custom initialization logic. */
|
||||
abstract init(): MaybePromise<void>;
|
||||
/** Decodes the provided encoded packet. */
|
||||
abstract decode(packet: EncodedPacket): MaybePromise<void>;
|
||||
/** Decodes all remaining packets and then resolves. */
|
||||
abstract flush(): MaybePromise<void>;
|
||||
/** Called when the decoder is no longer needed and its resources can be freed. */
|
||||
abstract close(): MaybePromise<void>;
|
||||
}
|
||||
/**
|
||||
* Base class for custom audio decoders. To add your own custom audio decoder, extend this class, implement the
|
||||
* abstract methods and static `supports` method, and register the decoder using {@link registerDecoder}.
|
||||
* @group Custom coders
|
||||
* @public
|
||||
*/
|
||||
export declare abstract class CustomAudioDecoder {
|
||||
/** The input audio's codec. */
|
||||
readonly codec: AudioCodec;
|
||||
/** The input audio's decoder config. */
|
||||
readonly config: AudioDecoderConfig;
|
||||
/** The callback to call when a decoded AudioSample is available. */
|
||||
readonly onSample: (sample: AudioSample) => unknown;
|
||||
/** Returns true if and only if the decoder can decode the given codec configuration. */
|
||||
static supports(codec: AudioCodec, config: AudioDecoderConfig): boolean;
|
||||
/** Called after decoder creation; can be used for custom initialization logic. */
|
||||
abstract init(): MaybePromise<void>;
|
||||
/** Decodes the provided encoded packet. */
|
||||
abstract decode(packet: EncodedPacket): MaybePromise<void>;
|
||||
/** Decodes all remaining packets and then resolves. */
|
||||
abstract flush(): MaybePromise<void>;
|
||||
/** Called when the decoder is no longer needed and its resources can be freed. */
|
||||
abstract close(): MaybePromise<void>;
|
||||
}
|
||||
/**
|
||||
* Base class for custom video encoders. To add your own custom video encoder, extend this class, implement the
|
||||
* abstract methods and static `supports` method, and register the encoder using {@link registerEncoder}.
|
||||
* @group Custom coders
|
||||
* @public
|
||||
*/
|
||||
export declare abstract class CustomVideoEncoder {
|
||||
/** The codec with which to encode the video. */
|
||||
readonly codec: VideoCodec;
|
||||
/** Config for the encoder. */
|
||||
readonly config: VideoEncoderConfig;
|
||||
/** The callback to call when an EncodedPacket is available. */
|
||||
readonly onPacket: (packet: EncodedPacket, meta?: EncodedVideoChunkMetadata) => unknown;
|
||||
/** Returns true if and only if the encoder can encode the given codec configuration. */
|
||||
static supports(codec: VideoCodec, config: VideoEncoderConfig): boolean;
|
||||
/** Called after encoder creation; can be used for custom initialization logic. */
|
||||
abstract init(): MaybePromise<void>;
|
||||
/** Encodes the provided video sample. */
|
||||
abstract encode(videoSample: VideoSample, options: VideoEncoderEncodeOptions): MaybePromise<void>;
|
||||
/** Encodes all remaining video samples and then resolves. */
|
||||
abstract flush(): MaybePromise<void>;
|
||||
/** Called when the encoder is no longer needed and its resources can be freed. */
|
||||
abstract close(): MaybePromise<void>;
|
||||
}
|
||||
/**
|
||||
* Base class for custom audio encoders. To add your own custom audio encoder, extend this class, implement the
|
||||
* abstract methods and static `supports` method, and register the encoder using {@link registerEncoder}.
|
||||
* @group Custom coders
|
||||
* @public
|
||||
*/
|
||||
export declare abstract class CustomAudioEncoder {
|
||||
/** The codec with which to encode the audio. */
|
||||
readonly codec: AudioCodec;
|
||||
/** Config for the encoder. */
|
||||
readonly config: AudioEncoderConfig;
|
||||
/** The callback to call when an EncodedPacket is available. */
|
||||
readonly onPacket: (packet: EncodedPacket, meta?: EncodedAudioChunkMetadata) => unknown;
|
||||
/** Returns true if and only if the encoder can encode the given codec configuration. */
|
||||
static supports(codec: AudioCodec, config: AudioEncoderConfig): boolean;
|
||||
/** Called after encoder creation; can be used for custom initialization logic. */
|
||||
abstract init(): MaybePromise<void>;
|
||||
/** Encodes the provided audio sample. */
|
||||
abstract encode(audioSample: AudioSample): MaybePromise<void>;
|
||||
/** Encodes all remaining audio samples and then resolves. */
|
||||
abstract flush(): MaybePromise<void>;
|
||||
/** Called when the encoder is no longer needed and its resources can be freed. */
|
||||
abstract close(): MaybePromise<void>;
|
||||
}
|
||||
export declare const customVideoDecoders: typeof CustomVideoDecoder[];
|
||||
export declare const customAudioDecoders: typeof CustomAudioDecoder[];
|
||||
export declare const customVideoEncoders: typeof CustomVideoEncoder[];
|
||||
export declare const customAudioEncoders: typeof CustomAudioEncoder[];
|
||||
/**
|
||||
* Registers a custom video or audio decoder. Registered decoders will automatically be used for decoding whenever
|
||||
* possible.
|
||||
* @group Custom coders
|
||||
* @public
|
||||
*/
|
||||
export declare const registerDecoder: (decoder: typeof CustomVideoDecoder | typeof CustomAudioDecoder) => void;
|
||||
/**
|
||||
* Registers a custom video or audio encoder. Registered encoders will automatically be used for encoding whenever
|
||||
* possible.
|
||||
* @group Custom coders
|
||||
* @public
|
||||
*/
|
||||
export declare const registerEncoder: (encoder: typeof CustomVideoEncoder | typeof CustomAudioEncoder) => void;
|
||||
//# sourceMappingURL=custom-coder.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"custom-coder.d.ts","sourceRoot":"","sources":["../../../src/custom-coder.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+117
@@ -0,0 +1,117 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/**
|
||||
* Base class for custom video decoders. To add your own custom video decoder, extend this class, implement the
|
||||
* abstract methods and static `supports` method, and register the decoder using {@link registerDecoder}.
|
||||
* @group Custom coders
|
||||
* @public
|
||||
*/
|
||||
export class CustomVideoDecoder {
|
||||
/** Returns true if and only if the decoder can decode the given codec configuration. */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
static supports(codec, config) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Base class for custom audio decoders. To add your own custom audio decoder, extend this class, implement the
|
||||
* abstract methods and static `supports` method, and register the decoder using {@link registerDecoder}.
|
||||
* @group Custom coders
|
||||
* @public
|
||||
*/
|
||||
export class CustomAudioDecoder {
|
||||
/** Returns true if and only if the decoder can decode the given codec configuration. */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
static supports(codec, config) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Base class for custom video encoders. To add your own custom video encoder, extend this class, implement the
|
||||
* abstract methods and static `supports` method, and register the encoder using {@link registerEncoder}.
|
||||
* @group Custom coders
|
||||
* @public
|
||||
*/
|
||||
export class CustomVideoEncoder {
|
||||
/** Returns true if and only if the encoder can encode the given codec configuration. */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
static supports(codec, config) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Base class for custom audio encoders. To add your own custom audio encoder, extend this class, implement the
|
||||
* abstract methods and static `supports` method, and register the encoder using {@link registerEncoder}.
|
||||
* @group Custom coders
|
||||
* @public
|
||||
*/
|
||||
export class CustomAudioEncoder {
|
||||
/** Returns true if and only if the encoder can encode the given codec configuration. */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
static supports(codec, config) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export const customVideoDecoders = [];
|
||||
export const customAudioDecoders = [];
|
||||
export const customVideoEncoders = [];
|
||||
export const customAudioEncoders = [];
|
||||
/**
|
||||
* Registers a custom video or audio decoder. Registered decoders will automatically be used for decoding whenever
|
||||
* possible.
|
||||
* @group Custom coders
|
||||
* @public
|
||||
*/
|
||||
export const registerDecoder = (decoder) => {
|
||||
if (decoder.prototype instanceof CustomVideoDecoder) {
|
||||
const casted = decoder;
|
||||
if (customVideoDecoders.includes(casted)) {
|
||||
console.warn('Video decoder already registered.');
|
||||
return;
|
||||
}
|
||||
customVideoDecoders.push(casted);
|
||||
}
|
||||
else if (decoder.prototype instanceof CustomAudioDecoder) {
|
||||
const casted = decoder;
|
||||
if (customAudioDecoders.includes(casted)) {
|
||||
console.warn('Audio decoder already registered.');
|
||||
return;
|
||||
}
|
||||
customAudioDecoders.push(casted);
|
||||
}
|
||||
else {
|
||||
throw new TypeError('Decoder must be a CustomVideoDecoder or CustomAudioDecoder.');
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Registers a custom video or audio encoder. Registered encoders will automatically be used for encoding whenever
|
||||
* possible.
|
||||
* @group Custom coders
|
||||
* @public
|
||||
*/
|
||||
export const registerEncoder = (encoder) => {
|
||||
if (encoder.prototype instanceof CustomVideoEncoder) {
|
||||
const casted = encoder;
|
||||
if (customVideoEncoders.includes(casted)) {
|
||||
console.warn('Video encoder already registered.');
|
||||
return;
|
||||
}
|
||||
customVideoEncoders.push(casted);
|
||||
}
|
||||
else if (encoder.prototype instanceof CustomAudioEncoder) {
|
||||
const casted = encoder;
|
||||
if (customAudioEncoders.includes(casted)) {
|
||||
console.warn('Audio encoder already registered.');
|
||||
return;
|
||||
}
|
||||
customAudioEncoders.push(casted);
|
||||
}
|
||||
else {
|
||||
throw new TypeError('Encoder must be a CustomVideoEncoder or CustomAudioEncoder.');
|
||||
}
|
||||
};
|
||||
Generated
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { Input } from './input.js';
|
||||
import { InputTrack } from './input-track.js';
|
||||
import { MetadataTags } from './metadata.js';
|
||||
export declare abstract class Demuxer {
|
||||
input: Input;
|
||||
constructor(input: Input);
|
||||
abstract computeDuration(): Promise<number>;
|
||||
abstract getTracks(): Promise<InputTrack[]>;
|
||||
abstract getMimeType(): Promise<string>;
|
||||
abstract getMetadataTags(): Promise<MetadataTags>;
|
||||
}
|
||||
//# sourceMappingURL=demuxer.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"demuxer.d.ts","sourceRoot":"","sources":["../../../src/demuxer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,8BAAsB,OAAO;IAC5B,KAAK,EAAE,KAAK,CAAC;gBAED,KAAK,EAAE,KAAK;IAIxB,QAAQ,CAAC,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;IAC3C,QAAQ,CAAC,SAAS,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAC3C,QAAQ,CAAC,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IACvC,QAAQ,CAAC,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC;CACjD"}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
export class Demuxer {
|
||||
constructor(input) {
|
||||
this.input = input;
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+274
@@ -0,0 +1,274 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { AudioCodec, MediaCodec, SubtitleCodec, VideoCodec } from './codec.js';
|
||||
import { EncodedPacket } from './packet.js';
|
||||
/**
|
||||
* Configuration object that controls video encoding. Can be used to set codec, quality, and more.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export type VideoEncodingConfig = {
|
||||
/** The video codec that should be used for encoding the video samples (frames). */
|
||||
codec: VideoCodec;
|
||||
/**
|
||||
* The target bitrate for the encoded video, in bits per second. Alternatively, a subjective {@link Quality} can
|
||||
* be provided.
|
||||
*/
|
||||
bitrate: number | Quality;
|
||||
/**
|
||||
* The interval, in seconds, of how often frames are encoded as a key frame. The default is 5 seconds. Frequent key
|
||||
* frames improve seeking behavior but increase file size. When using multiple video tracks, you should give them
|
||||
* all the same key frame interval.
|
||||
*/
|
||||
keyFrameInterval?: number;
|
||||
/**
|
||||
* Video frames may change size over time. This field controls the behavior in case this happens.
|
||||
*
|
||||
* - `'deny'` (default) will throw an error, requiring all frames to have the exact same dimensions.
|
||||
* - `'passThrough'` will allow the change and directly pass the frame to the encoder.
|
||||
* - `'fill'` will stretch the image to fill the entire original box, potentially altering aspect ratio.
|
||||
* - `'contain'` will contain the entire image within the original box while preserving aspect ratio. This may lead
|
||||
* to letterboxing.
|
||||
* - `'cover'` will scale the image until the entire original box is filled, while preserving aspect ratio.
|
||||
*
|
||||
* The "original box" refers to the dimensions of the first encoded frame.
|
||||
*/
|
||||
sizeChangeBehavior?: 'deny' | 'passThrough' | 'fill' | 'contain' | 'cover';
|
||||
/** Called for each successfully encoded packet. Both the packet and the encoding metadata are passed. */
|
||||
onEncodedPacket?: (packet: EncodedPacket, meta: EncodedVideoChunkMetadata | undefined) => unknown;
|
||||
/**
|
||||
* Called when the internal [encoder config](https://www.w3.org/TR/webcodecs/#video-encoder-config), as used by the
|
||||
* WebCodecs API, is created.
|
||||
*/
|
||||
onEncoderConfig?: (config: VideoEncoderConfig) => unknown;
|
||||
} & VideoEncodingAdditionalOptions;
|
||||
export declare const validateVideoEncodingConfig: (config: VideoEncodingConfig) => void;
|
||||
/**
|
||||
* Additional options that control audio encoding.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export type VideoEncodingAdditionalOptions = {
|
||||
/**
|
||||
* What to do with alpha data contained in the video samples.
|
||||
*
|
||||
* - `'discard'` (default): Only the samples' color data is kept; the video is opaque.
|
||||
* - `'keep'`: The samples' alpha data is also encoded as side data. Make sure to pair this mode with a container
|
||||
* format that supports transparency (such as WebM or Matroska).
|
||||
*/
|
||||
alpha?: 'discard' | 'keep';
|
||||
/** Configures the bitrate mode; defaults to `'variable'`. */
|
||||
bitrateMode?: 'constant' | 'variable';
|
||||
/**
|
||||
* The latency mode used by the encoder; controls the performance-quality tradeoff.
|
||||
*
|
||||
* - `'quality'` (default): The encoder prioritizes quality over latency, and no frames can be dropped.
|
||||
* - `'realtime'`: The encoder prioritizes low latency over quality, and may drop frames if the encoder becomes
|
||||
* overloaded to keep up with real-time requirements.
|
||||
*/
|
||||
latencyMode?: 'quality' | 'realtime';
|
||||
/**
|
||||
* The full codec string as specified in the Mediabunny Codec Registry. This string must match the codec
|
||||
* specified in `codec`. When not set, a fitting codec string will be constructed automatically by the library.
|
||||
*/
|
||||
fullCodecString?: string;
|
||||
/**
|
||||
* A hint that configures the hardware acceleration method of this codec. This is best left on `'no-preference'`,
|
||||
* the default.
|
||||
*/
|
||||
hardwareAcceleration?: 'no-preference' | 'prefer-hardware' | 'prefer-software';
|
||||
/**
|
||||
* An encoding scalability mode identifier as defined by
|
||||
* [WebRTC-SVC](https://w3c.github.io/webrtc-svc/#scalabilitymodes*).
|
||||
*/
|
||||
scalabilityMode?: string;
|
||||
/**
|
||||
* An encoding video content hint as defined by
|
||||
* [mst-content-hint](https://w3c.github.io/mst-content-hint/#video-content-hints).
|
||||
*/
|
||||
contentHint?: string;
|
||||
};
|
||||
export declare const validateVideoEncodingAdditionalOptions: (codec: VideoCodec, options: VideoEncodingAdditionalOptions) => void;
|
||||
export declare const buildVideoEncoderConfig: (options: {
|
||||
codec: VideoCodec;
|
||||
width: number;
|
||||
height: number;
|
||||
bitrate: number | Quality;
|
||||
framerate: number | undefined;
|
||||
} & VideoEncodingAdditionalOptions) => VideoEncoderConfig;
|
||||
/**
|
||||
* Configuration object that controls audio encoding. Can be used to set codec, quality, and more.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export type AudioEncodingConfig = {
|
||||
/** The audio codec that should be used for encoding the audio samples. */
|
||||
codec: AudioCodec;
|
||||
/**
|
||||
* The target bitrate for the encoded audio, in bits per second. Alternatively, a subjective {@link Quality} can
|
||||
* be provided. Required for compressed audio codecs, unused for PCM codecs.
|
||||
*/
|
||||
bitrate?: number | Quality;
|
||||
/** Called for each successfully encoded packet. Both the packet and the encoding metadata are passed. */
|
||||
onEncodedPacket?: (packet: EncodedPacket, meta: EncodedAudioChunkMetadata | undefined) => unknown;
|
||||
/**
|
||||
* Called when the internal [encoder config](https://www.w3.org/TR/webcodecs/#audio-encoder-config), as used by the
|
||||
* WebCodecs API, is created.
|
||||
*/
|
||||
onEncoderConfig?: (config: AudioEncoderConfig) => unknown;
|
||||
} & AudioEncodingAdditionalOptions;
|
||||
export declare const validateAudioEncodingConfig: (config: AudioEncodingConfig) => void;
|
||||
/**
|
||||
* Additional options that control audio encoding.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export type AudioEncodingAdditionalOptions = {
|
||||
/** Configures the bitrate mode. */
|
||||
bitrateMode?: 'constant' | 'variable';
|
||||
/**
|
||||
* The full codec string as specified in the Mediabunny Codec Registry. This string must match the codec
|
||||
* specified in `codec`. When not set, a fitting codec string will be constructed automatically by the library.
|
||||
*/
|
||||
fullCodecString?: string;
|
||||
};
|
||||
export declare const validateAudioEncodingAdditionalOptions: (codec: AudioCodec, options: AudioEncodingAdditionalOptions) => void;
|
||||
export declare const buildAudioEncoderConfig: (options: {
|
||||
codec: AudioCodec;
|
||||
numberOfChannels: number;
|
||||
sampleRate: number;
|
||||
bitrate?: number | Quality;
|
||||
} & AudioEncodingAdditionalOptions) => AudioEncoderConfig;
|
||||
/**
|
||||
* Represents a subjective media quality level.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare class Quality {
|
||||
}
|
||||
/**
|
||||
* Represents a very low media quality.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare const QUALITY_VERY_LOW: Quality;
|
||||
/**
|
||||
* Represents a low media quality.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare const QUALITY_LOW: Quality;
|
||||
/**
|
||||
* Represents a medium media quality.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare const QUALITY_MEDIUM: Quality;
|
||||
/**
|
||||
* Represents a high media quality.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare const QUALITY_HIGH: Quality;
|
||||
/**
|
||||
* Represents a very high media quality.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare const QUALITY_VERY_HIGH: Quality;
|
||||
/**
|
||||
* Checks if the browser is able to encode the given codec.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare const canEncode: (codec: MediaCodec) => Promise<boolean>;
|
||||
/**
|
||||
* Checks if the browser is able to encode the given video codec with the given parameters.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare const canEncodeVideo: (codec: VideoCodec, options?: {
|
||||
width?: number;
|
||||
height?: number;
|
||||
bitrate?: number | Quality;
|
||||
} & VideoEncodingAdditionalOptions) => Promise<boolean>;
|
||||
/**
|
||||
* Checks if the browser is able to encode the given audio codec with the given parameters.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare const canEncodeAudio: (codec: AudioCodec, options?: {
|
||||
numberOfChannels?: number;
|
||||
sampleRate?: number;
|
||||
bitrate?: number | Quality;
|
||||
} & AudioEncodingAdditionalOptions) => Promise<boolean>;
|
||||
/**
|
||||
* Checks if the browser is able to encode the given subtitle codec.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare const canEncodeSubtitles: (codec: SubtitleCodec) => Promise<boolean>;
|
||||
/**
|
||||
* Returns the list of all media codecs that can be encoded by the browser.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare const getEncodableCodecs: () => Promise<MediaCodec[]>;
|
||||
/**
|
||||
* Returns the list of all video codecs that can be encoded by the browser.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare const getEncodableVideoCodecs: (checkedCodecs?: VideoCodec[], options?: {
|
||||
width?: number;
|
||||
height?: number;
|
||||
bitrate?: number | Quality;
|
||||
}) => Promise<VideoCodec[]>;
|
||||
/**
|
||||
* Returns the list of all audio codecs that can be encoded by the browser.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare const getEncodableAudioCodecs: (checkedCodecs?: AudioCodec[], options?: {
|
||||
numberOfChannels?: number;
|
||||
sampleRate?: number;
|
||||
bitrate?: number | Quality;
|
||||
}) => Promise<AudioCodec[]>;
|
||||
/**
|
||||
* Returns the list of all subtitle codecs that can be encoded by the browser.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare const getEncodableSubtitleCodecs: (checkedCodecs?: SubtitleCodec[]) => Promise<SubtitleCodec[]>;
|
||||
/**
|
||||
* Returns the first video codec from the given list that can be encoded by the browser.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare const getFirstEncodableVideoCodec: (checkedCodecs: VideoCodec[], options?: {
|
||||
width?: number;
|
||||
height?: number;
|
||||
bitrate?: number | Quality;
|
||||
}) => Promise<VideoCodec | null>;
|
||||
/**
|
||||
* Returns the first audio codec from the given list that can be encoded by the browser.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare const getFirstEncodableAudioCodec: (checkedCodecs: AudioCodec[], options?: {
|
||||
numberOfChannels?: number;
|
||||
sampleRate?: number;
|
||||
bitrate?: number | Quality;
|
||||
}) => Promise<AudioCodec | null>;
|
||||
/**
|
||||
* Returns the first subtitle codec from the given list that can be encoded by the browser.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export declare const getFirstEncodableSubtitleCodec: (checkedCodecs: SubtitleCodec[]) => Promise<SubtitleCodec | null>;
|
||||
//# sourceMappingURL=encode.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"encode.d.ts","sourceRoot":"","sources":["../../../src/encode.ts"],"names":[],"mappings":"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"}
|
||||
+479
@@ -0,0 +1,479 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { AUDIO_CODECS, buildAudioCodecString, buildVideoCodecString, getAudioEncoderConfigExtension, getVideoEncoderConfigExtension, inferCodecFromCodecString, PCM_AUDIO_CODECS, SUBTITLE_CODECS, VIDEO_CODECS, } from './codec.js';
|
||||
import { customAudioEncoders, customVideoEncoders } from './custom-coder.js';
|
||||
import { isFirefox } from './misc.js';
|
||||
export const validateVideoEncodingConfig = (config) => {
|
||||
if (!config || typeof config !== 'object') {
|
||||
throw new TypeError('Encoding config must be an object.');
|
||||
}
|
||||
if (!VIDEO_CODECS.includes(config.codec)) {
|
||||
throw new TypeError(`Invalid video codec '${config.codec}'. Must be one of: ${VIDEO_CODECS.join(', ')}.`);
|
||||
}
|
||||
if (!(config.bitrate instanceof Quality) && (!Number.isInteger(config.bitrate) || config.bitrate <= 0)) {
|
||||
throw new TypeError('config.bitrate must be a positive integer or a quality.');
|
||||
}
|
||||
if (config.keyFrameInterval !== undefined
|
||||
&& (!Number.isFinite(config.keyFrameInterval) || config.keyFrameInterval < 0)) {
|
||||
throw new TypeError('config.keyFrameInterval, when provided, must be a non-negative number.');
|
||||
}
|
||||
if (config.sizeChangeBehavior !== undefined
|
||||
&& !['deny', 'passThrough', 'fill', 'contain', 'cover'].includes(config.sizeChangeBehavior)) {
|
||||
throw new TypeError('config.sizeChangeBehavior, when provided, must be \'deny\', \'passThrough\', \'fill\', \'contain\''
|
||||
+ ' or \'cover\'.');
|
||||
}
|
||||
if (config.onEncodedPacket !== undefined && typeof config.onEncodedPacket !== 'function') {
|
||||
throw new TypeError('config.onEncodedChunk, when provided, must be a function.');
|
||||
}
|
||||
if (config.onEncoderConfig !== undefined && typeof config.onEncoderConfig !== 'function') {
|
||||
throw new TypeError('config.onEncoderConfig, when provided, must be a function.');
|
||||
}
|
||||
validateVideoEncodingAdditionalOptions(config.codec, config);
|
||||
};
|
||||
export const validateVideoEncodingAdditionalOptions = (codec, options) => {
|
||||
if (!options || typeof options !== 'object') {
|
||||
throw new TypeError('Encoding options must be an object.');
|
||||
}
|
||||
if (options.alpha !== undefined && !['discard', 'keep'].includes(options.alpha)) {
|
||||
throw new TypeError('options.alpha, when provided, must be \'discard\' or \'keep\'.');
|
||||
}
|
||||
if (options.bitrateMode !== undefined && !['constant', 'variable'].includes(options.bitrateMode)) {
|
||||
throw new TypeError('bitrateMode, when provided, must be \'constant\' or \'variable\'.');
|
||||
}
|
||||
if (options.latencyMode !== undefined && !['quality', 'realtime'].includes(options.latencyMode)) {
|
||||
throw new TypeError('latencyMode, when provided, must be \'quality\' or \'realtime\'.');
|
||||
}
|
||||
if (options.fullCodecString !== undefined && typeof options.fullCodecString !== 'string') {
|
||||
throw new TypeError('fullCodecString, when provided, must be a string.');
|
||||
}
|
||||
if (options.fullCodecString !== undefined && inferCodecFromCodecString(options.fullCodecString) !== codec) {
|
||||
throw new TypeError(`fullCodecString, when provided, must be a string that matches the specified codec (${codec}).`);
|
||||
}
|
||||
if (options.hardwareAcceleration !== undefined
|
||||
&& !['no-preference', 'prefer-hardware', 'prefer-software'].includes(options.hardwareAcceleration)) {
|
||||
throw new TypeError('hardwareAcceleration, when provided, must be \'no-preference\', \'prefer-hardware\' or'
|
||||
+ ' \'prefer-software\'.');
|
||||
}
|
||||
if (options.scalabilityMode !== undefined && typeof options.scalabilityMode !== 'string') {
|
||||
throw new TypeError('scalabilityMode, when provided, must be a string.');
|
||||
}
|
||||
if (options.contentHint !== undefined && typeof options.contentHint !== 'string') {
|
||||
throw new TypeError('contentHint, when provided, must be a string.');
|
||||
}
|
||||
};
|
||||
export const buildVideoEncoderConfig = (options) => {
|
||||
const resolvedBitrate = options.bitrate instanceof Quality
|
||||
? options.bitrate._toVideoBitrate(options.codec, options.width, options.height)
|
||||
: options.bitrate;
|
||||
return {
|
||||
codec: options.fullCodecString ?? buildVideoCodecString(options.codec, options.width, options.height, resolvedBitrate),
|
||||
width: options.width,
|
||||
height: options.height,
|
||||
bitrate: resolvedBitrate,
|
||||
bitrateMode: options.bitrateMode,
|
||||
alpha: options.alpha ?? 'discard',
|
||||
framerate: options.framerate,
|
||||
latencyMode: options.latencyMode,
|
||||
hardwareAcceleration: options.hardwareAcceleration,
|
||||
scalabilityMode: options.scalabilityMode,
|
||||
contentHint: options.contentHint,
|
||||
...getVideoEncoderConfigExtension(options.codec),
|
||||
};
|
||||
};
|
||||
export const validateAudioEncodingConfig = (config) => {
|
||||
if (!config || typeof config !== 'object') {
|
||||
throw new TypeError('Encoding config must be an object.');
|
||||
}
|
||||
if (!AUDIO_CODECS.includes(config.codec)) {
|
||||
throw new TypeError(`Invalid audio codec '${config.codec}'. Must be one of: ${AUDIO_CODECS.join(', ')}.`);
|
||||
}
|
||||
if (config.bitrate === undefined
|
||||
&& (!PCM_AUDIO_CODECS.includes(config.codec) || config.codec === 'flac')) {
|
||||
throw new TypeError('config.bitrate must be provided for compressed audio codecs.');
|
||||
}
|
||||
if (config.bitrate !== undefined
|
||||
&& !(config.bitrate instanceof Quality)
|
||||
&& (!Number.isInteger(config.bitrate) || config.bitrate <= 0)) {
|
||||
throw new TypeError('config.bitrate, when provided, must be a positive integer or a quality.');
|
||||
}
|
||||
if (config.onEncodedPacket !== undefined && typeof config.onEncodedPacket !== 'function') {
|
||||
throw new TypeError('config.onEncodedChunk, when provided, must be a function.');
|
||||
}
|
||||
if (config.onEncoderConfig !== undefined && typeof config.onEncoderConfig !== 'function') {
|
||||
throw new TypeError('config.onEncoderConfig, when provided, must be a function.');
|
||||
}
|
||||
validateAudioEncodingAdditionalOptions(config.codec, config);
|
||||
};
|
||||
export const validateAudioEncodingAdditionalOptions = (codec, options) => {
|
||||
if (!options || typeof options !== 'object') {
|
||||
throw new TypeError('Encoding options must be an object.');
|
||||
}
|
||||
if (options.bitrateMode !== undefined && !['constant', 'variable'].includes(options.bitrateMode)) {
|
||||
throw new TypeError('bitrateMode, when provided, must be \'constant\' or \'variable\'.');
|
||||
}
|
||||
if (options.fullCodecString !== undefined && typeof options.fullCodecString !== 'string') {
|
||||
throw new TypeError('fullCodecString, when provided, must be a string.');
|
||||
}
|
||||
if (options.fullCodecString !== undefined && inferCodecFromCodecString(options.fullCodecString) !== codec) {
|
||||
throw new TypeError(`fullCodecString, when provided, must be a string that matches the specified codec (${codec}).`);
|
||||
}
|
||||
};
|
||||
export const buildAudioEncoderConfig = (options) => {
|
||||
const resolvedBitrate = options.bitrate instanceof Quality
|
||||
? options.bitrate._toAudioBitrate(options.codec)
|
||||
: options.bitrate;
|
||||
return {
|
||||
codec: options.fullCodecString ?? buildAudioCodecString(options.codec, options.numberOfChannels, options.sampleRate),
|
||||
numberOfChannels: options.numberOfChannels,
|
||||
sampleRate: options.sampleRate,
|
||||
bitrate: resolvedBitrate,
|
||||
bitrateMode: options.bitrateMode,
|
||||
...getAudioEncoderConfigExtension(options.codec),
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Represents a subjective media quality level.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export class Quality {
|
||||
/** @internal */
|
||||
constructor(factor) {
|
||||
this._factor = factor;
|
||||
}
|
||||
/** @internal */
|
||||
_toVideoBitrate(codec, width, height) {
|
||||
const pixels = width * height;
|
||||
const codecEfficiencyFactors = {
|
||||
avc: 1.0, // H.264/AVC (baseline)
|
||||
hevc: 0.6, // H.265/HEVC (~40% more efficient than AVC)
|
||||
vp9: 0.6, // Similar to HEVC
|
||||
av1: 0.4, // ~60% more efficient than AVC
|
||||
vp8: 1.2, // Slightly less efficient than AVC
|
||||
};
|
||||
const referencePixels = 1920 * 1080;
|
||||
const referenceBitrate = 3000000;
|
||||
const scaleFactor = Math.pow(pixels / referencePixels, 0.95); // Slight non-linear scaling
|
||||
const baseBitrate = referenceBitrate * scaleFactor;
|
||||
const codecAdjustedBitrate = baseBitrate * codecEfficiencyFactors[codec];
|
||||
const finalBitrate = codecAdjustedBitrate * this._factor;
|
||||
return Math.ceil(finalBitrate / 1000) * 1000;
|
||||
}
|
||||
/** @internal */
|
||||
_toAudioBitrate(codec) {
|
||||
if (PCM_AUDIO_CODECS.includes(codec) || codec === 'flac') {
|
||||
return undefined;
|
||||
}
|
||||
const baseRates = {
|
||||
aac: 128000, // 128kbps base for AAC
|
||||
opus: 64000, // 64kbps base for Opus
|
||||
mp3: 160000, // 160kbps base for MP3
|
||||
vorbis: 64000, // 64kbps base for Vorbis
|
||||
ac3: 384000, // 384kbps base for AC-3
|
||||
eac3: 192000, // 192kbps base for E-AC-3
|
||||
};
|
||||
const baseBitrate = baseRates[codec];
|
||||
if (!baseBitrate) {
|
||||
throw new Error(`Unhandled codec: ${codec}`);
|
||||
}
|
||||
let finalBitrate = baseBitrate * this._factor;
|
||||
if (codec === 'aac') {
|
||||
// AAC only works with specific bitrates, let's find the closest
|
||||
const validRates = [96000, 128000, 160000, 192000];
|
||||
finalBitrate = validRates.reduce((prev, curr) => Math.abs(curr - finalBitrate) < Math.abs(prev - finalBitrate) ? curr : prev);
|
||||
}
|
||||
else if (codec === 'opus' || codec === 'vorbis') {
|
||||
finalBitrate = Math.max(6000, finalBitrate);
|
||||
}
|
||||
else if (codec === 'mp3') {
|
||||
const validRates = [
|
||||
8000, 16000, 24000, 32000, 40000, 48000, 64000, 80000,
|
||||
96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000,
|
||||
];
|
||||
finalBitrate = validRates.reduce((prev, curr) => Math.abs(curr - finalBitrate) < Math.abs(prev - finalBitrate) ? curr : prev);
|
||||
}
|
||||
return Math.round(finalBitrate / 1000) * 1000;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Represents a very low media quality.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export const QUALITY_VERY_LOW = /* #__PURE__ */ new Quality(0.3);
|
||||
/**
|
||||
* Represents a low media quality.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export const QUALITY_LOW = /* #__PURE__ */ new Quality(0.6);
|
||||
/**
|
||||
* Represents a medium media quality.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export const QUALITY_MEDIUM = /* #__PURE__ */ new Quality(1);
|
||||
/**
|
||||
* Represents a high media quality.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export const QUALITY_HIGH = /* #__PURE__ */ new Quality(2);
|
||||
/**
|
||||
* Represents a very high media quality.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export const QUALITY_VERY_HIGH = /* #__PURE__ */ new Quality(4);
|
||||
/**
|
||||
* Checks if the browser is able to encode the given codec.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export const canEncode = (codec) => {
|
||||
if (VIDEO_CODECS.includes(codec)) {
|
||||
return canEncodeVideo(codec);
|
||||
}
|
||||
else if (AUDIO_CODECS.includes(codec)) {
|
||||
return canEncodeAudio(codec);
|
||||
}
|
||||
else if (SUBTITLE_CODECS.includes(codec)) {
|
||||
return canEncodeSubtitles(codec);
|
||||
}
|
||||
throw new TypeError(`Unknown codec '${codec}'.`);
|
||||
};
|
||||
/**
|
||||
* Checks if the browser is able to encode the given video codec with the given parameters.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export const canEncodeVideo = async (codec, options = {}) => {
|
||||
const { width = 1280, height = 720, bitrate = 1e6, ...restOptions } = options;
|
||||
if (!VIDEO_CODECS.includes(codec)) {
|
||||
return false;
|
||||
}
|
||||
if (!Number.isInteger(width) || width <= 0) {
|
||||
throw new TypeError('width must be a positive integer.');
|
||||
}
|
||||
if (!Number.isInteger(height) || height <= 0) {
|
||||
throw new TypeError('height must be a positive integer.');
|
||||
}
|
||||
if (!(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) {
|
||||
throw new TypeError('bitrate must be a positive integer or a quality.');
|
||||
}
|
||||
validateVideoEncodingAdditionalOptions(codec, restOptions);
|
||||
let encoderConfig = null;
|
||||
if (customVideoEncoders.length > 0) {
|
||||
encoderConfig ??= buildVideoEncoderConfig({
|
||||
codec,
|
||||
width,
|
||||
height,
|
||||
bitrate,
|
||||
framerate: undefined,
|
||||
...restOptions,
|
||||
});
|
||||
if (customVideoEncoders.some(x => x.supports(codec, encoderConfig))) {
|
||||
// There's a custom encoder
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (typeof VideoEncoder === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
const hasOddDimension = width % 2 === 1 || height % 2 === 1;
|
||||
if (hasOddDimension
|
||||
&& (codec === 'avc' || codec === 'hevc')) {
|
||||
// Disallow odd dimensions for certain codecs
|
||||
return false;
|
||||
}
|
||||
encoderConfig ??= buildVideoEncoderConfig({
|
||||
codec,
|
||||
width,
|
||||
height,
|
||||
bitrate,
|
||||
framerate: undefined,
|
||||
...restOptions,
|
||||
alpha: 'discard', // Since we handle alpha ourselves
|
||||
});
|
||||
const support = await VideoEncoder.isConfigSupported(encoderConfig);
|
||||
if (!support.supported) {
|
||||
return false;
|
||||
}
|
||||
if (isFirefox()) {
|
||||
// isConfigSupported on Firefox appears to unreliably indicate if encoding will actually succeed. Therefore, we
|
||||
// just try encoding a frame to see if it actually works.
|
||||
// https://github.com/Vanilagy/mediabunny/issues/222
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises, no-async-promise-executor
|
||||
return new Promise(async (resolve) => {
|
||||
try {
|
||||
const encoder = new VideoEncoder({
|
||||
output: () => { },
|
||||
error: () => resolve(false),
|
||||
});
|
||||
encoder.configure(encoderConfig);
|
||||
const frameData = new Uint8Array(width * height * 4);
|
||||
const frame = new VideoFrame(frameData, {
|
||||
format: 'RGBA',
|
||||
codedWidth: width,
|
||||
codedHeight: height,
|
||||
timestamp: 0,
|
||||
});
|
||||
encoder.encode(frame);
|
||||
frame.close();
|
||||
await encoder.flush();
|
||||
resolve(true);
|
||||
}
|
||||
catch {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Checks if the browser is able to encode the given audio codec with the given parameters.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export const canEncodeAudio = async (codec, options = {}) => {
|
||||
const { numberOfChannels = 2, sampleRate = 48000, bitrate = 128e3, ...restOptions } = options;
|
||||
if (!AUDIO_CODECS.includes(codec)) {
|
||||
return false;
|
||||
}
|
||||
if (!Number.isInteger(numberOfChannels) || numberOfChannels <= 0) {
|
||||
throw new TypeError('numberOfChannels must be a positive integer.');
|
||||
}
|
||||
if (!Number.isInteger(sampleRate) || sampleRate <= 0) {
|
||||
throw new TypeError('sampleRate must be a positive integer.');
|
||||
}
|
||||
if (!(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) {
|
||||
throw new TypeError('bitrate must be a positive integer.');
|
||||
}
|
||||
validateAudioEncodingAdditionalOptions(codec, restOptions);
|
||||
let encoderConfig = null;
|
||||
if (customAudioEncoders.length > 0) {
|
||||
encoderConfig ??= buildAudioEncoderConfig({
|
||||
codec,
|
||||
numberOfChannels,
|
||||
sampleRate,
|
||||
bitrate,
|
||||
...restOptions,
|
||||
});
|
||||
if (customAudioEncoders.some(x => x.supports(codec, encoderConfig))) {
|
||||
// There's a custom encoder
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (PCM_AUDIO_CODECS.includes(codec)) {
|
||||
return true; // Because we encode these ourselves
|
||||
}
|
||||
if (typeof AudioEncoder === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
encoderConfig ??= buildAudioEncoderConfig({
|
||||
codec,
|
||||
numberOfChannels,
|
||||
sampleRate,
|
||||
bitrate,
|
||||
...restOptions,
|
||||
});
|
||||
const support = await AudioEncoder.isConfigSupported(encoderConfig);
|
||||
return support.supported === true;
|
||||
};
|
||||
/**
|
||||
* Checks if the browser is able to encode the given subtitle codec.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export const canEncodeSubtitles = async (codec) => {
|
||||
if (!SUBTITLE_CODECS.includes(codec)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
* Returns the list of all media codecs that can be encoded by the browser.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export const getEncodableCodecs = async () => {
|
||||
const [videoCodecs, audioCodecs, subtitleCodecs] = await Promise.all([
|
||||
getEncodableVideoCodecs(),
|
||||
getEncodableAudioCodecs(),
|
||||
getEncodableSubtitleCodecs(),
|
||||
]);
|
||||
return [...videoCodecs, ...audioCodecs, ...subtitleCodecs];
|
||||
};
|
||||
/**
|
||||
* Returns the list of all video codecs that can be encoded by the browser.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export const getEncodableVideoCodecs = async (checkedCodecs = VIDEO_CODECS, options) => {
|
||||
const bools = await Promise.all(checkedCodecs.map(codec => canEncodeVideo(codec, options)));
|
||||
return checkedCodecs.filter((_, i) => bools[i]);
|
||||
};
|
||||
/**
|
||||
* Returns the list of all audio codecs that can be encoded by the browser.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export const getEncodableAudioCodecs = async (checkedCodecs = AUDIO_CODECS, options) => {
|
||||
const bools = await Promise.all(checkedCodecs.map(codec => canEncodeAudio(codec, options)));
|
||||
return checkedCodecs.filter((_, i) => bools[i]);
|
||||
};
|
||||
/**
|
||||
* Returns the list of all subtitle codecs that can be encoded by the browser.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export const getEncodableSubtitleCodecs = async (checkedCodecs = SUBTITLE_CODECS) => {
|
||||
const bools = await Promise.all(checkedCodecs.map(canEncodeSubtitles));
|
||||
return checkedCodecs.filter((_, i) => bools[i]);
|
||||
};
|
||||
/**
|
||||
* Returns the first video codec from the given list that can be encoded by the browser.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export const getFirstEncodableVideoCodec = async (checkedCodecs, options) => {
|
||||
for (const codec of checkedCodecs) {
|
||||
if (await canEncodeVideo(codec, options)) {
|
||||
return codec;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
/**
|
||||
* Returns the first audio codec from the given list that can be encoded by the browser.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export const getFirstEncodableAudioCodec = async (checkedCodecs, options) => {
|
||||
for (const codec of checkedCodecs) {
|
||||
if (await canEncodeAudio(codec, options)) {
|
||||
return codec;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
/**
|
||||
* Returns the first subtitle codec from the given list that can be encoded by the browser.
|
||||
* @group Encoding
|
||||
* @public
|
||||
*/
|
||||
export const getFirstEncodableSubtitleCodec = async (checkedCodecs) => {
|
||||
for (const codec of checkedCodecs) {
|
||||
if (await canEncodeSubtitles(codec)) {
|
||||
return codec;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
Generated
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { Demuxer } from '../demuxer.js';
|
||||
import { Input } from '../input.js';
|
||||
import { InputAudioTrack } from '../input-track.js';
|
||||
import { AsyncMutex } from '../misc.js';
|
||||
import { FileSlice, Reader } from '../reader.js';
|
||||
import { MetadataTags } from '../metadata.js';
|
||||
type FlacAudioInfo = {
|
||||
numberOfChannels: number;
|
||||
sampleRate: number;
|
||||
totalSamples: number;
|
||||
minimumBlockSize: number;
|
||||
maximumBlockSize: number;
|
||||
minimumFrameSize: number;
|
||||
maximumFrameSize: number;
|
||||
description: Uint8Array;
|
||||
};
|
||||
type Sample = {
|
||||
blockOffset: number;
|
||||
blockSize: number;
|
||||
byteOffset: number;
|
||||
byteSize: number;
|
||||
};
|
||||
type NextFlacFrameResult = {
|
||||
num: number;
|
||||
blockSize: number;
|
||||
sampleRate: number;
|
||||
size: number;
|
||||
isLastFrame: boolean;
|
||||
};
|
||||
export declare class FlacDemuxer extends Demuxer {
|
||||
reader: Reader;
|
||||
loadedSamples: Sample[];
|
||||
metadataPromise: Promise<void> | null;
|
||||
track: InputAudioTrack | null;
|
||||
metadataTags: MetadataTags;
|
||||
audioInfo: FlacAudioInfo | null;
|
||||
lastLoadedPos: number | null;
|
||||
blockingBit: number | null;
|
||||
readingMutex: AsyncMutex;
|
||||
lastSampleLoaded: boolean;
|
||||
constructor(input: Input);
|
||||
computeDuration(): Promise<number>;
|
||||
getMetadataTags(): Promise<MetadataTags>;
|
||||
getTracks(): Promise<InputAudioTrack[]>;
|
||||
getMimeType(): Promise<string>;
|
||||
readMetadata(): Promise<void>;
|
||||
readNextFlacFrame({ startPos, isFirstPacket, }: {
|
||||
startPos: number;
|
||||
isFirstPacket: boolean;
|
||||
}): Promise<NextFlacFrameResult | null>;
|
||||
readFlacFrameHeader({ slice, isFirstPacket, }: {
|
||||
slice: FileSlice;
|
||||
isFirstPacket: boolean;
|
||||
}): {
|
||||
num: number;
|
||||
blockSize: number;
|
||||
sampleRate: number;
|
||||
} | null;
|
||||
advanceReader(): Promise<void>;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=flac-demuxer.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"flac-demuxer.d.ts","sourceRoot":"","sources":["../../../../src/flac/flac-demuxer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,EAAE,eAAe,EAA0B,MAAM,gBAAgB,CAAC;AAEzE,OAAO,EAEN,UAAU,EAKV,MAAM,SAAS,CAAC;AAEjB,OAAO,EACN,SAAS,EAET,MAAM,EAIN,MAAM,WAAW,CAAC;AACnB,OAAO,EAA6B,YAAY,EAAE,MAAM,aAAa,CAAC;AAUtE,KAAK,aAAa,GAAG;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,UAAU,CAAC;CACxB,CAAC;AAEF,KAAK,MAAM,GAAG;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,KAAK,mBAAmB,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,qBAAa,WAAY,SAAQ,OAAO;IACvC,MAAM,EAAE,MAAM,CAAC;IAEf,aAAa,EAAE,MAAM,EAAE,CAAM;IAE7B,eAAe,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAQ;IAC7C,KAAK,EAAE,eAAe,GAAG,IAAI,CAAQ;IACrC,YAAY,EAAE,YAAY,CAAM;IAEhC,SAAS,EAAE,aAAa,GAAG,IAAI,CAAQ;IACvC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAQ;IACpC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAQ;IAElC,YAAY,aAAoB;IAChC,gBAAgB,UAAS;gBAEb,KAAK,EAAE,KAAK;IAMT,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;IAMlC,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC;IAKjD,SAAS;IAMT,WAAW;IAIX,YAAY;IA0JZ,iBAAiB,CAAC,EACvB,QAAQ,EACR,aAAa,GACb,EAAE;QACF,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,OAAO,CAAC;KACvB,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAiHvC,mBAAmB,CAAC,EACnB,KAAK,EACL,aAAa,GACb,EAAE;QACF,KAAK,EAAE,SAAS,CAAC;QACjB,aAAa,EAAE,OAAO,CAAC;KACvB;;;;;IAiGK,aAAa;CAqCnB"}
|
||||
Generated
Vendored
+507
@@ -0,0 +1,507 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { FlacBlockType, readVorbisComments } from '../codec-data.js';
|
||||
import { Demuxer } from '../demuxer.js';
|
||||
import { InputAudioTrack } from '../input-track.js';
|
||||
import { assert, AsyncMutex, binarySearchLessOrEqual, Bitstream, textDecoder, UNDETERMINED_LANGUAGE, } from '../misc.js';
|
||||
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet.js';
|
||||
import { readBytes, readU24Be, readU32Be, readU8, } from '../reader.js';
|
||||
import { DEFAULT_TRACK_DISPOSITION } from '../metadata.js';
|
||||
import { calculateCrc8, readBlockSize, getBlockSizeOrUncommon, readCodedNumber, readSampleRate, getSampleRateOrUncommon, } from './flac-misc.js';
|
||||
export class FlacDemuxer extends Demuxer {
|
||||
constructor(input) {
|
||||
super(input);
|
||||
this.loadedSamples = []; // All samples from the start of the file to lastLoadedPos
|
||||
this.metadataPromise = null;
|
||||
this.track = null;
|
||||
this.metadataTags = {};
|
||||
this.audioInfo = null;
|
||||
this.lastLoadedPos = null;
|
||||
this.blockingBit = null;
|
||||
this.readingMutex = new AsyncMutex();
|
||||
this.lastSampleLoaded = false;
|
||||
this.reader = input._reader;
|
||||
}
|
||||
async computeDuration() {
|
||||
await this.readMetadata();
|
||||
assert(this.track);
|
||||
return this.track.computeDuration();
|
||||
}
|
||||
async getMetadataTags() {
|
||||
await this.readMetadata();
|
||||
return this.metadataTags;
|
||||
}
|
||||
async getTracks() {
|
||||
await this.readMetadata();
|
||||
assert(this.track);
|
||||
return [this.track];
|
||||
}
|
||||
async getMimeType() {
|
||||
return 'audio/flac';
|
||||
}
|
||||
async readMetadata() {
|
||||
let currentPos = 4; // Skip 'fLaC'
|
||||
return (this.metadataPromise ??= (async () => {
|
||||
while (this.reader.fileSize === null
|
||||
|| currentPos < this.reader.fileSize) {
|
||||
let sizeSlice = this.reader.requestSlice(currentPos, 4);
|
||||
if (sizeSlice instanceof Promise)
|
||||
sizeSlice = await sizeSlice;
|
||||
currentPos += 4;
|
||||
if (sizeSlice === null) {
|
||||
throw new Error(`Metadata block at position ${currentPos} is too small! Corrupted file.`);
|
||||
}
|
||||
assert(sizeSlice);
|
||||
const byte = readU8(sizeSlice); // first bit: isLastMetadata, remaining 7 bits: metaBlockType
|
||||
const size = readU24Be(sizeSlice);
|
||||
const isLastMetadata = (byte & 0x80) !== 0;
|
||||
const metaBlockType = byte & 0x7f;
|
||||
switch (metaBlockType) {
|
||||
case FlacBlockType.STREAMINFO: {
|
||||
// Parse streaminfo block
|
||||
// https://www.rfc-editor.org/rfc/rfc9639.html#section-8.2
|
||||
let streamInfoBlock = this.reader.requestSlice(currentPos, size);
|
||||
if (streamInfoBlock instanceof Promise)
|
||||
streamInfoBlock = await streamInfoBlock;
|
||||
assert(streamInfoBlock);
|
||||
if (streamInfoBlock === null) {
|
||||
throw new Error(`StreamInfo block at position ${currentPos} is too small! Corrupted file.`);
|
||||
}
|
||||
const streamInfoBytes = readBytes(streamInfoBlock, 34);
|
||||
const bitstream = new Bitstream(streamInfoBytes);
|
||||
const minimumBlockSize = bitstream.readBits(16);
|
||||
const maximumBlockSize = bitstream.readBits(16);
|
||||
const minimumFrameSize = bitstream.readBits(24);
|
||||
const maximumFrameSize = bitstream.readBits(24);
|
||||
const sampleRate = bitstream.readBits(20);
|
||||
const numberOfChannels = bitstream.readBits(3) + 1;
|
||||
bitstream.readBits(5); // bitsPerSample - 1
|
||||
const totalSamples = bitstream.readBits(36);
|
||||
// https://www.w3.org/TR/webcodecs-flac-codec-registration/#audiodecoderconfig-description
|
||||
// description is required, and has to be the following:
|
||||
// 1. The bytes 0x66 0x4C 0x61 0x43 ("fLaC" in ASCII)
|
||||
// 2. A metadata block (called the STREAMINFO block) as described in section 7 of [FLAC]
|
||||
// 3. Optionaly (sic) other metadata blocks, that are not used by the specification
|
||||
bitstream.skipBits(16 * 8); // md5 hash
|
||||
const description = new Uint8Array(42);
|
||||
// 1. "fLaC"
|
||||
description.set(new Uint8Array([0x66, 0x4c, 0x61, 0x43]), 0);
|
||||
// 2. STREAMINFO block
|
||||
description.set(new Uint8Array([128, 0, 0, 34]), 4);
|
||||
// 3. Other metadata blocks
|
||||
description.set(streamInfoBytes, 8);
|
||||
this.audioInfo = {
|
||||
numberOfChannels,
|
||||
sampleRate,
|
||||
totalSamples,
|
||||
minimumBlockSize,
|
||||
maximumBlockSize,
|
||||
minimumFrameSize,
|
||||
maximumFrameSize,
|
||||
description,
|
||||
};
|
||||
this.track = new InputAudioTrack(this.input, new FlacAudioTrackBacking(this));
|
||||
break;
|
||||
}
|
||||
case FlacBlockType.VORBIS_COMMENT: {
|
||||
// Parse vorbis comment block
|
||||
// https://www.rfc-editor.org/rfc/rfc9639.html#name-vorbis-comment
|
||||
let vorbisCommentBlock = this.reader.requestSlice(currentPos, size);
|
||||
if (vorbisCommentBlock instanceof Promise)
|
||||
vorbisCommentBlock = await vorbisCommentBlock;
|
||||
assert(vorbisCommentBlock);
|
||||
readVorbisComments(readBytes(vorbisCommentBlock, size), this.metadataTags);
|
||||
break;
|
||||
}
|
||||
case FlacBlockType.PICTURE: {
|
||||
// Parse picture block
|
||||
// https://www.rfc-editor.org/rfc/rfc9639.html#name-picture
|
||||
let pictureBlock = this.reader.requestSlice(currentPos, size);
|
||||
if (pictureBlock instanceof Promise)
|
||||
pictureBlock = await pictureBlock;
|
||||
assert(pictureBlock);
|
||||
const pictureType = readU32Be(pictureBlock);
|
||||
const mediaTypeLength = readU32Be(pictureBlock);
|
||||
const mediaType = textDecoder.decode(readBytes(pictureBlock, mediaTypeLength));
|
||||
const descriptionLength = readU32Be(pictureBlock);
|
||||
const description = textDecoder.decode(readBytes(pictureBlock, descriptionLength));
|
||||
pictureBlock.skip(4 + 4 + 4 + 4); // Skip width, height, color depth, number of indexed colors
|
||||
const dataLength = readU32Be(pictureBlock);
|
||||
const data = readBytes(pictureBlock, dataLength);
|
||||
this.metadataTags.images ??= [];
|
||||
this.metadataTags.images.push({
|
||||
data,
|
||||
mimeType: mediaType,
|
||||
// https://www.rfc-editor.org/rfc/rfc9639.html#table13
|
||||
kind: pictureType === 3
|
||||
? 'coverFront'
|
||||
: pictureType === 4
|
||||
? 'coverBack'
|
||||
: 'unknown',
|
||||
description,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
currentPos += size;
|
||||
if (isLastMetadata) {
|
||||
this.lastLoadedPos = currentPos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
})());
|
||||
}
|
||||
async readNextFlacFrame({ startPos, isFirstPacket, }) {
|
||||
assert(this.audioInfo);
|
||||
// we expect that there are at least `minimumFrameSize` bytes left in the file
|
||||
// Ideally we also want to validate the next header is valid
|
||||
// to throw out an accidential sync word
|
||||
// The shortest valid FLAC header I can think of, based off the code
|
||||
// of readFlacFrameHeader:
|
||||
// 4 bytes used for bitstream from syncword to bit depth
|
||||
// 1 byte coded number
|
||||
// (uncommon values, no bytes read)
|
||||
// 1 byte crc
|
||||
// --> 6 bytes
|
||||
const minimumHeaderLength = 6;
|
||||
// If we read everything in readFlacFrameHeader, we read 16 bytes
|
||||
const maximumHeaderSize = 16;
|
||||
const maximumSliceLength = this.audioInfo.maximumFrameSize + maximumHeaderSize;
|
||||
const slice = await this.reader.requestSliceRange(startPos, this.audioInfo.minimumFrameSize, maximumSliceLength);
|
||||
if (!slice) {
|
||||
return null;
|
||||
}
|
||||
const frameHeader = this.readFlacFrameHeader({
|
||||
slice,
|
||||
isFirstPacket: isFirstPacket,
|
||||
});
|
||||
if (!frameHeader) {
|
||||
return null;
|
||||
}
|
||||
// We don't know exactly how long the packet is, we only know the `minimumFrameSize` and `maximumFrameSize`
|
||||
// The packet is over if the next 2 bytes are the sync word followed by a valid header
|
||||
// or the end of the file is reached
|
||||
// The next sync word is expected at earliest when `minimumFrameSize` is reached,
|
||||
// we can skip over anything before that
|
||||
slice.filePos = startPos + this.audioInfo.minimumFrameSize;
|
||||
while (true) {
|
||||
// Reached end of the file, packet is over
|
||||
if (slice.filePos > slice.end - minimumHeaderLength) {
|
||||
return {
|
||||
num: frameHeader.num,
|
||||
blockSize: frameHeader.blockSize,
|
||||
sampleRate: frameHeader.sampleRate,
|
||||
size: slice.end - startPos,
|
||||
isLastFrame: true,
|
||||
};
|
||||
}
|
||||
const nextByte = readU8(slice);
|
||||
if (nextByte === 0xff) {
|
||||
const positionBeforeReading = slice.filePos;
|
||||
const byteAfterNextByte = readU8(slice);
|
||||
const expected = this.blockingBit === 1 ? 0b1111_1001 : 0b1111_1000;
|
||||
if (byteAfterNextByte !== expected) {
|
||||
slice.filePos = positionBeforeReading;
|
||||
continue;
|
||||
}
|
||||
slice.skip(-2);
|
||||
const lengthIfNextFlacFrameHeaderIsLegit = slice.filePos - startPos;
|
||||
const nextFrameHeader = this.readFlacFrameHeader({
|
||||
slice,
|
||||
isFirstPacket: false,
|
||||
});
|
||||
if (!nextFrameHeader) {
|
||||
slice.filePos = positionBeforeReading;
|
||||
continue;
|
||||
}
|
||||
// Ensure the frameOrSampleNum is consecutive.
|
||||
// https://github.com/Vanilagy/mediabunny/issues/194
|
||||
if (this.blockingBit === 0) {
|
||||
// Case A: If the stream is fixed block size, this is the frame number, which increments by 1
|
||||
if (nextFrameHeader.num - frameHeader.num !== 1) {
|
||||
slice.filePos = positionBeforeReading;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Case B: If the stream is variable block size, this is the sample number, which increments by
|
||||
// amount of samples in a frame.
|
||||
if (nextFrameHeader.num - frameHeader.num !== frameHeader.blockSize) {
|
||||
slice.filePos = positionBeforeReading;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return {
|
||||
num: frameHeader.num,
|
||||
blockSize: frameHeader.blockSize,
|
||||
sampleRate: frameHeader.sampleRate,
|
||||
size: lengthIfNextFlacFrameHeaderIsLegit,
|
||||
isLastFrame: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
readFlacFrameHeader({ slice, isFirstPacket, }) {
|
||||
// In this function, generally it is not safe to throw errors.
|
||||
// We might end up here because we stumbled upon a syncword,
|
||||
// but the data might not actually be a FLAC frame, it might be random bitstream
|
||||
// data, in that case we should return null and continue.
|
||||
const startOffset = slice.filePos;
|
||||
// https://www.rfc-editor.org/rfc/rfc9639.html#section-9.1
|
||||
// Each frame MUST start on a byte boundary and start with the 15-bit frame
|
||||
// sync code 0b111111111111100. Following the sync code is the blocking strategy
|
||||
// bit, which MUST NOT change during the audio stream.
|
||||
const bytes = readBytes(slice, 4);
|
||||
const bitstream = new Bitstream(bytes);
|
||||
const bits = bitstream.readBits(15);
|
||||
if (bits !== 0b111111111111100) {
|
||||
// This cannot be a valid FLAC frame, must start with the syncword
|
||||
return null;
|
||||
}
|
||||
if (this.blockingBit === null) {
|
||||
assert(isFirstPacket);
|
||||
const newBlockingBit = bitstream.readBits(1);
|
||||
this.blockingBit = newBlockingBit;
|
||||
}
|
||||
else if (this.blockingBit === 1) {
|
||||
assert(!isFirstPacket);
|
||||
const newBlockingBit = bitstream.readBits(1);
|
||||
if (newBlockingBit !== 1) {
|
||||
// This cannot be a valid FLAC frame, expected 1 but got 0
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (this.blockingBit === 0) {
|
||||
assert(!isFirstPacket);
|
||||
const newBlockingBit = bitstream.readBits(1);
|
||||
if (newBlockingBit !== 0) {
|
||||
// This cannot be a valid FLAC frame, expected 0 but got 1
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error('Invalid blocking bit');
|
||||
}
|
||||
const blockSizeOrUncommon = getBlockSizeOrUncommon(bitstream.readBits(4));
|
||||
if (!blockSizeOrUncommon) {
|
||||
// This cannot be a valid FLAC frame, the syncword was just coincidental
|
||||
return null;
|
||||
}
|
||||
assert(this.audioInfo);
|
||||
const sampleRateOrUncommon = getSampleRateOrUncommon(bitstream.readBits(4), this.audioInfo.sampleRate);
|
||||
if (!sampleRateOrUncommon) {
|
||||
// This cannot be a valid FLAC frame, the syncword was just coincidental
|
||||
return null;
|
||||
}
|
||||
bitstream.readBits(4); // channel count
|
||||
bitstream.readBits(3); // bit depth
|
||||
const reservedZero = bitstream.readBits(1); // reserved zero
|
||||
if (reservedZero !== 0) {
|
||||
// This cannot be a valid FLAC frame, the syncword was just coincidental
|
||||
return null;
|
||||
}
|
||||
const num = readCodedNumber(slice);
|
||||
const blockSize = readBlockSize(slice, blockSizeOrUncommon);
|
||||
const sampleRate = readSampleRate(slice, sampleRateOrUncommon);
|
||||
if (sampleRate === null) {
|
||||
// This cannot be a valid FLAC frame, the syncword was just coincidental
|
||||
return null;
|
||||
}
|
||||
if (sampleRate !== this.audioInfo.sampleRate) {
|
||||
// This cannot be a valid FLAC frame, the sample rate is not the same as in the stream info
|
||||
return null;
|
||||
}
|
||||
const size = slice.filePos - startOffset;
|
||||
const crc = readU8(slice);
|
||||
slice.skip(-size);
|
||||
slice.skip(-1);
|
||||
const crcCalculated = calculateCrc8(readBytes(slice, size));
|
||||
if (crc !== crcCalculated) {
|
||||
// Maybe this wasn't a FLAC frame at all, the syncword was just coincidentally
|
||||
// in the bitstream
|
||||
return null;
|
||||
}
|
||||
return { num, blockSize, sampleRate };
|
||||
}
|
||||
async advanceReader() {
|
||||
await this.readMetadata();
|
||||
assert(this.lastLoadedPos !== null);
|
||||
assert(this.audioInfo);
|
||||
const startPos = this.lastLoadedPos;
|
||||
const frame = await this.readNextFlacFrame({
|
||||
startPos,
|
||||
isFirstPacket: this.loadedSamples.length === 0,
|
||||
});
|
||||
if (!frame) {
|
||||
// Unexpected case, failed to read next FLAC frame
|
||||
// handling gracefully
|
||||
this.lastSampleLoaded = true;
|
||||
return;
|
||||
}
|
||||
const lastSample = this.loadedSamples[this.loadedSamples.length - 1];
|
||||
const blockOffset = lastSample
|
||||
? lastSample.blockOffset + lastSample.blockSize
|
||||
: 0;
|
||||
const sample = {
|
||||
blockOffset,
|
||||
blockSize: frame.blockSize,
|
||||
byteOffset: startPos,
|
||||
byteSize: frame.size,
|
||||
};
|
||||
this.lastLoadedPos = this.lastLoadedPos + frame.size;
|
||||
this.loadedSamples.push(sample);
|
||||
if (frame.isLastFrame) {
|
||||
this.lastSampleLoaded = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
class FlacAudioTrackBacking {
|
||||
constructor(demuxer) {
|
||||
this.demuxer = demuxer;
|
||||
}
|
||||
getId() {
|
||||
return 1;
|
||||
}
|
||||
getNumber() {
|
||||
return 1;
|
||||
}
|
||||
getCodec() {
|
||||
return 'flac';
|
||||
}
|
||||
getInternalCodecId() {
|
||||
return null;
|
||||
}
|
||||
getNumberOfChannels() {
|
||||
assert(this.demuxer.audioInfo);
|
||||
return this.demuxer.audioInfo.numberOfChannels;
|
||||
}
|
||||
async computeDuration() {
|
||||
const lastPacket = await this.getPacket(Infinity, { metadataOnly: true });
|
||||
return (lastPacket?.timestamp ?? 0) + (lastPacket?.duration ?? 0);
|
||||
}
|
||||
getSampleRate() {
|
||||
assert(this.demuxer.audioInfo);
|
||||
return this.demuxer.audioInfo.sampleRate;
|
||||
}
|
||||
getName() {
|
||||
return null;
|
||||
}
|
||||
getLanguageCode() {
|
||||
return UNDETERMINED_LANGUAGE;
|
||||
}
|
||||
getTimeResolution() {
|
||||
assert(this.demuxer.audioInfo);
|
||||
return this.demuxer.audioInfo.sampleRate;
|
||||
}
|
||||
getDisposition() {
|
||||
return {
|
||||
...DEFAULT_TRACK_DISPOSITION,
|
||||
};
|
||||
}
|
||||
async getFirstTimestamp() {
|
||||
return 0;
|
||||
}
|
||||
async getDecoderConfig() {
|
||||
assert(this.demuxer.audioInfo);
|
||||
return {
|
||||
codec: 'flac',
|
||||
numberOfChannels: this.demuxer.audioInfo.numberOfChannels,
|
||||
sampleRate: this.demuxer.audioInfo.sampleRate,
|
||||
description: this.demuxer.audioInfo.description,
|
||||
};
|
||||
}
|
||||
async getPacket(timestamp, options) {
|
||||
assert(this.demuxer.audioInfo);
|
||||
if (timestamp < 0) {
|
||||
throw new Error('Timestamp cannot be negative');
|
||||
}
|
||||
const release = await this.demuxer.readingMutex.acquire();
|
||||
try {
|
||||
while (true) {
|
||||
const packetIndex = binarySearchLessOrEqual(this.demuxer.loadedSamples, timestamp, x => x.blockOffset / this.demuxer.audioInfo.sampleRate);
|
||||
if (packetIndex === -1) {
|
||||
await this.demuxer.advanceReader();
|
||||
continue;
|
||||
}
|
||||
const packet = this.demuxer.loadedSamples[packetIndex];
|
||||
const sampleTimestamp = packet.blockOffset / this.demuxer.audioInfo.sampleRate;
|
||||
const sampleDuration = packet.blockSize / this.demuxer.audioInfo.sampleRate;
|
||||
if (sampleTimestamp + sampleDuration <= timestamp) {
|
||||
if (this.demuxer.lastSampleLoaded) {
|
||||
return this.getPacketAtIndex(this.demuxer.loadedSamples.length - 1, options);
|
||||
}
|
||||
await this.demuxer.advanceReader();
|
||||
continue;
|
||||
}
|
||||
return this.getPacketAtIndex(packetIndex, options);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
release();
|
||||
}
|
||||
}
|
||||
async getNextPacket(packet, options) {
|
||||
const release = await this.demuxer.readingMutex.acquire();
|
||||
try {
|
||||
const nextIndex = packet.sequenceNumber + 1;
|
||||
if (this.demuxer.lastSampleLoaded
|
||||
&& nextIndex >= this.demuxer.loadedSamples.length) {
|
||||
return null;
|
||||
}
|
||||
// Ensure the next sample exists
|
||||
while (nextIndex >= this.demuxer.loadedSamples.length
|
||||
&& !this.demuxer.lastSampleLoaded) {
|
||||
await this.demuxer.advanceReader();
|
||||
}
|
||||
return this.getPacketAtIndex(nextIndex, options);
|
||||
}
|
||||
finally {
|
||||
release();
|
||||
}
|
||||
}
|
||||
getKeyPacket(timestamp, options) {
|
||||
return this.getPacket(timestamp, options);
|
||||
}
|
||||
getNextKeyPacket(packet, options) {
|
||||
return this.getNextPacket(packet, options);
|
||||
}
|
||||
async getPacketAtIndex(sampleIndex, options) {
|
||||
const rawSample = this.demuxer.loadedSamples[sampleIndex];
|
||||
if (!rawSample) {
|
||||
return null;
|
||||
}
|
||||
let data;
|
||||
if (options.metadataOnly) {
|
||||
data = PLACEHOLDER_DATA;
|
||||
}
|
||||
else {
|
||||
let slice = this.demuxer.reader.requestSlice(rawSample.byteOffset, rawSample.byteSize);
|
||||
if (slice instanceof Promise)
|
||||
slice = await slice;
|
||||
if (!slice) {
|
||||
return null; // Data didn't fit into the rest of the file
|
||||
}
|
||||
data = readBytes(slice, rawSample.byteSize);
|
||||
}
|
||||
assert(this.demuxer.audioInfo);
|
||||
const timestamp = rawSample.blockOffset / this.demuxer.audioInfo.sampleRate;
|
||||
const duration = rawSample.blockSize / this.demuxer.audioInfo.sampleRate;
|
||||
return new EncodedPacket(data, 'key', timestamp, duration, sampleIndex, rawSample.byteSize);
|
||||
}
|
||||
async getFirstPacket(options) {
|
||||
// Ensure the next sample exists
|
||||
while (this.demuxer.loadedSamples.length === 0
|
||||
&& !this.demuxer.lastSampleLoaded) {
|
||||
await this.demuxer.advanceReader();
|
||||
}
|
||||
return this.getPacketAtIndex(0, options);
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { FileSlice } from '../reader.js';
|
||||
type BlockSizeOrUncommon = number | 'uncommon-u16' | 'uncommon-u8';
|
||||
type SampleRateOrUncommon = number | 'uncommon-u8' | 'uncommon-u16' | 'uncommon-u16-10';
|
||||
export declare const getBlockSizeOrUncommon: (bits: number) => BlockSizeOrUncommon | null;
|
||||
export declare const getSampleRateOrUncommon: (sampleRateBits: number, streamInfoSampleRate: number) => SampleRateOrUncommon | null;
|
||||
export declare const readCodedNumber: (fileSlice: FileSlice) => number;
|
||||
export declare const readBlockSize: (slice: FileSlice, blockSizeBits: BlockSizeOrUncommon) => number;
|
||||
export declare const readSampleRate: (slice: FileSlice, sampleRateOrUncommon: SampleRateOrUncommon) => number | null;
|
||||
export declare const calculateCrc8: (data: Uint8Array) => number;
|
||||
export {};
|
||||
//# sourceMappingURL=flac-misc.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"flac-misc.d.ts","sourceRoot":"","sources":["../../../../src/flac/flac-misc.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,SAAS,EAAgC,MAAM,WAAW,CAAC;AAEpE,KAAK,mBAAmB,GAAG,MAAM,GAAG,cAAc,GAAG,aAAa,CAAC;AACnE,KAAK,oBAAoB,GACtB,MAAM,GACN,aAAa,GACb,cAAc,GACd,iBAAiB,CAAC;AAGrB,eAAO,MAAM,sBAAsB,GAAI,MAAM,MAAM,KAAG,mBAAmB,GAAG,IAgB3E,CAAC;AAGF,eAAO,MAAM,uBAAuB,GACnC,gBAAgB,MAAM,EACtB,sBAAsB,MAAM,KAC1B,oBAAoB,GAAG,IAmBzB,CAAC;AAGF,eAAO,MAAM,eAAe,GAAI,WAAW,SAAS,KAAG,MAqCtD,CAAC;AAEF,eAAO,MAAM,aAAa,GACzB,OAAO,SAAS,EAChB,eAAe,mBAAmB,WAYlC,CAAC;AAEF,eAAO,MAAM,cAAc,GAC1B,OAAO,SAAS,EAChB,sBAAsB,oBAAoB,kBAmB1C,CAAC;AAGF,eAAO,MAAM,aAAa,GAAI,MAAM,UAAU,WAqB7C,CAAC"}
|
||||
Generated
Vendored
+135
@@ -0,0 +1,135 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { assert, assertNever, Bitstream } from '../misc.js';
|
||||
import { readBytes, readU16Be, readU8 } from '../reader.js';
|
||||
// https://www.rfc-editor.org/rfc/rfc9639.html#name-block-size-bits
|
||||
export const getBlockSizeOrUncommon = (bits) => {
|
||||
if (bits === 0b0000) {
|
||||
return null;
|
||||
}
|
||||
else if (bits === 0b0001) {
|
||||
return 192;
|
||||
}
|
||||
else if (bits >= 0b0010 && bits <= 0b0101) {
|
||||
return 144 * 2 ** bits;
|
||||
}
|
||||
else if (bits === 0b0110) {
|
||||
return 'uncommon-u8';
|
||||
}
|
||||
else if (bits === 0b0111) {
|
||||
return 'uncommon-u16';
|
||||
}
|
||||
else if (bits >= 0b1000 && bits <= 0b1111) {
|
||||
return 2 ** bits;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
// https://www.rfc-editor.org/rfc/rfc9639.html#name-sample-rate-bits
|
||||
export const getSampleRateOrUncommon = (sampleRateBits, streamInfoSampleRate) => {
|
||||
switch (sampleRateBits) {
|
||||
case 0b0000: return streamInfoSampleRate;
|
||||
case 0b0001: return 88200;
|
||||
case 0b0010: return 176400;
|
||||
case 0b0011: return 192000;
|
||||
case 0b0100: return 8000;
|
||||
case 0b0101: return 16000;
|
||||
case 0b0110: return 22050;
|
||||
case 0b0111: return 24000;
|
||||
case 0b1000: return 32000;
|
||||
case 0b1001: return 44100;
|
||||
case 0b1010: return 48000;
|
||||
case 0b1011: return 96000;
|
||||
case 0b1100: return 'uncommon-u8';
|
||||
case 0b1101: return 'uncommon-u16';
|
||||
case 0b1110: return 'uncommon-u16-10';
|
||||
default: return null;
|
||||
}
|
||||
};
|
||||
// https://www.rfc-editor.org/rfc/rfc9639.html#name-coded-number
|
||||
export const readCodedNumber = (fileSlice) => {
|
||||
let ones = 0;
|
||||
const bitstream1 = new Bitstream(readBytes(fileSlice, 1));
|
||||
while (bitstream1.readBits(1) === 1) {
|
||||
ones++;
|
||||
}
|
||||
if (ones === 0) {
|
||||
return bitstream1.readBits(7);
|
||||
}
|
||||
const bitArray = [];
|
||||
const extraBytes = ones - 1;
|
||||
const bitstream2 = new Bitstream(readBytes(fileSlice, extraBytes));
|
||||
const firstByteBits = 8 - ones - 1;
|
||||
for (let i = 0; i < firstByteBits; i++) {
|
||||
bitArray.unshift(bitstream1.readBits(1));
|
||||
}
|
||||
for (let i = 0; i < extraBytes; i++) {
|
||||
for (let j = 0; j < 8; j++) {
|
||||
const val = bitstream2.readBits(1);
|
||||
if (j < 2) {
|
||||
continue;
|
||||
}
|
||||
bitArray.unshift(val);
|
||||
}
|
||||
}
|
||||
const encoded = bitArray.reduce((acc, bit, index) => {
|
||||
return acc | (bit << index);
|
||||
}, 0);
|
||||
return encoded;
|
||||
};
|
||||
export const readBlockSize = (slice, blockSizeBits) => {
|
||||
if (blockSizeBits === 'uncommon-u16') {
|
||||
return readU16Be(slice) + 1;
|
||||
}
|
||||
else if (blockSizeBits === 'uncommon-u8') {
|
||||
return readU8(slice) + 1;
|
||||
}
|
||||
else if (typeof blockSizeBits === 'number') {
|
||||
return blockSizeBits;
|
||||
}
|
||||
else {
|
||||
assertNever(blockSizeBits);
|
||||
assert(false);
|
||||
}
|
||||
};
|
||||
export const readSampleRate = (slice, sampleRateOrUncommon) => {
|
||||
if (sampleRateOrUncommon === 'uncommon-u16') {
|
||||
return readU16Be(slice);
|
||||
}
|
||||
if (sampleRateOrUncommon === 'uncommon-u16-10') {
|
||||
return readU16Be(slice) * 10;
|
||||
}
|
||||
if (sampleRateOrUncommon === 'uncommon-u8') {
|
||||
return readU8(slice);
|
||||
}
|
||||
if (typeof sampleRateOrUncommon === 'number') {
|
||||
return sampleRateOrUncommon;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
// https://www.rfc-editor.org/rfc/rfc9639.html#section-9.1.1
|
||||
export const calculateCrc8 = (data) => {
|
||||
const polynomial = 0x07; // x^8 + x^2 + x^1 + x^0
|
||||
let crc = 0x00; // Initialize CRC to 0
|
||||
for (const byte of data) {
|
||||
crc ^= byte; // XOR byte into least significant byte of crc
|
||||
for (let i = 0; i < 8; i++) {
|
||||
// For each bit in the byte
|
||||
if ((crc & 0x80) !== 0) {
|
||||
// If the leftmost bit (MSB) is set
|
||||
crc = (crc << 1) ^ polynomial; // Shift left and XOR with polynomial
|
||||
}
|
||||
else {
|
||||
crc <<= 1; // Just shift left
|
||||
}
|
||||
crc &= 0xff; // Ensure CRC remains 8-bit
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
};
|
||||
Generated
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { Muxer } from '../muxer.js';
|
||||
import { Output, OutputAudioTrack } from '../output.js';
|
||||
import { FlacOutputFormat } from '../output-format.js';
|
||||
import { EncodedPacket } from '../packet.js';
|
||||
import { AttachedImage } from '../metadata.js';
|
||||
export declare class FlacMuxer extends Muxer {
|
||||
private writer;
|
||||
private metadataWritten;
|
||||
private blockSizes;
|
||||
private frameSizes;
|
||||
private sampleRate;
|
||||
private channels;
|
||||
private bitsPerSample;
|
||||
private format;
|
||||
constructor(output: Output, format: FlacOutputFormat);
|
||||
start(): Promise<void>;
|
||||
writeHeader({ bitsPerSample, minimumBlockSize, maximumBlockSize, minimumFrameSize, maximumFrameSize, sampleRate, channels, totalSamples, }: {
|
||||
minimumBlockSize: number;
|
||||
maximumBlockSize: number;
|
||||
minimumFrameSize: number;
|
||||
maximumFrameSize: number;
|
||||
sampleRate: number;
|
||||
channels: number;
|
||||
bitsPerSample: number;
|
||||
totalSamples: number;
|
||||
}): void;
|
||||
writePictureBlock(picture: AttachedImage): void;
|
||||
writeVorbisCommentAndPictureBlock(): void;
|
||||
getMimeType(): Promise<string>;
|
||||
addEncodedVideoPacket(): Promise<void>;
|
||||
addEncodedAudioPacket(track: OutputAudioTrack, packet: EncodedPacket, meta?: EncodedAudioChunkMetadata): Promise<void>;
|
||||
addSubtitleCue(): Promise<void>;
|
||||
finalize(): Promise<void>;
|
||||
}
|
||||
//# sourceMappingURL=flac-muxer.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"flac-muxer.d.ts","sourceRoot":"","sources":["../../../../src/flac/flac-muxer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,aAAa,EAAwB,MAAM,aAAa,CAAC;AAYlE,qBAAa,SAAU,SAAQ,KAAK;IACnC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,eAAe,CAAS;IAEhC,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,UAAU,CAAgB;IAElC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,aAAa,CAAuB;IAE5C,OAAO,CAAC,MAAM,CAAmB;gBAErB,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB;IAO9C,KAAK;IAIX,WAAW,CAAC,EACX,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACR,YAAY,GACZ,EAAE;QACF,gBAAgB,EAAE,MAAM,CAAC;QACzB,gBAAgB,EAAE,MAAM,CAAC;QACzB,gBAAgB,EAAE,MAAM,CAAC;QACzB,gBAAgB,EAAE,MAAM,CAAC;QACzB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;KACrB;IAsCD,iBAAiB,CAAC,OAAO,EAAE,aAAa;IAoDxC,iCAAiC;IA4B3B,WAAW;IAIX,qBAAqB;IAIrB,qBAAqB,CAC1B,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,aAAa,EACrB,IAAI,CAAC,EAAE,yBAAyB,GAC9B,OAAO,CAAC,IAAI,CAAC;IAmEP,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAIlC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CA0C/B"}
|
||||
Generated
Vendored
+222
@@ -0,0 +1,222 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { validateAudioChunkMetadata } from '../codec.js';
|
||||
import { createVorbisComments, FlacBlockType } from '../codec-data.js';
|
||||
import { assert, Bitstream, textEncoder, toDataView, toUint8Array, } from '../misc.js';
|
||||
import { Muxer } from '../muxer.js';
|
||||
import { FileSlice, readBytes } from '../reader.js';
|
||||
import { metadataTagsAreEmpty } from '../metadata.js';
|
||||
import { readBlockSize, getBlockSizeOrUncommon, readCodedNumber, } from './flac-misc.js';
|
||||
const FLAC_HEADER = /* #__PURE__ */ new Uint8Array([0x66, 0x4c, 0x61, 0x43]); // 'fLaC'
|
||||
const STREAMINFO_SIZE = 38;
|
||||
const STREAMINFO_BLOCK_SIZE = 34;
|
||||
export class FlacMuxer extends Muxer {
|
||||
constructor(output, format) {
|
||||
super(output);
|
||||
this.metadataWritten = false;
|
||||
this.blockSizes = [];
|
||||
this.frameSizes = [];
|
||||
this.sampleRate = null;
|
||||
this.channels = null;
|
||||
this.bitsPerSample = null;
|
||||
this.writer = output._writer;
|
||||
this.format = format;
|
||||
}
|
||||
async start() {
|
||||
this.writer.write(FLAC_HEADER);
|
||||
}
|
||||
writeHeader({ bitsPerSample, minimumBlockSize, maximumBlockSize, minimumFrameSize, maximumFrameSize, sampleRate, channels, totalSamples, }) {
|
||||
assert(this.writer.getPos() === 4);
|
||||
const hasMetadata = !metadataTagsAreEmpty(this.output._metadataTags);
|
||||
const headerBitstream = new Bitstream(new Uint8Array(4));
|
||||
headerBitstream.writeBits(1, Number(!hasMetadata)); // isLastMetadata
|
||||
headerBitstream.writeBits(7, FlacBlockType.STREAMINFO); // metaBlockType = streaminfo
|
||||
headerBitstream.writeBits(24, STREAMINFO_BLOCK_SIZE); // size
|
||||
this.writer.write(headerBitstream.bytes);
|
||||
const contentBitstream = new Bitstream(new Uint8Array(18));
|
||||
contentBitstream.writeBits(16, minimumBlockSize);
|
||||
contentBitstream.writeBits(16, maximumBlockSize);
|
||||
contentBitstream.writeBits(24, minimumFrameSize);
|
||||
contentBitstream.writeBits(24, maximumFrameSize);
|
||||
contentBitstream.writeBits(20, sampleRate);
|
||||
contentBitstream.writeBits(3, channels - 1);
|
||||
contentBitstream.writeBits(5, bitsPerSample - 1);
|
||||
// Bitstream operations are only safe until 32bit, breaks when using 36 bits
|
||||
// Splitting up into writing 4 0 bits and then 32 bits is safe
|
||||
// This is safe for audio up to (2 ** 32 / 44100 / 3600) -> 27 hours
|
||||
// Not implementing support for more than 32 bits now
|
||||
if (totalSamples >= 2 ** 32) {
|
||||
throw new Error('This muxer only supports writing up to 2 ** 32 samples');
|
||||
}
|
||||
contentBitstream.writeBits(4, 0);
|
||||
contentBitstream.writeBits(32, totalSamples);
|
||||
this.writer.write(contentBitstream.bytes);
|
||||
// The MD5 hash is calculated from decoded audio data, but we do not have access
|
||||
// to it here. We are allowed to set 0:
|
||||
// "A value of 0 signifies that the value is not known."
|
||||
// https://www.rfc-editor.org/rfc/rfc9639.html#name-streaminfo
|
||||
this.writer.write(new Uint8Array(16));
|
||||
}
|
||||
writePictureBlock(picture) {
|
||||
// Header size:
|
||||
// 4 bytes: picture type
|
||||
// 4 bytes: media type length
|
||||
// x bytes: media type
|
||||
// 4 bytes: description length
|
||||
// y bytes: description
|
||||
// 1 bytes: width
|
||||
// 1 bytes: height
|
||||
// 1 bytes: color depth
|
||||
// 1 bytes: number of indexed colors
|
||||
// 4 bytes: picture data length
|
||||
// z bytes: picture data
|
||||
// Total: 20 + x + y + z
|
||||
const headerSize = 32
|
||||
+ picture.mimeType.length
|
||||
+ (picture.description?.length ?? 0)
|
||||
+ picture.data.length;
|
||||
const header = new Uint8Array(headerSize);
|
||||
let offset = 0;
|
||||
const dataView = toDataView(header);
|
||||
dataView.setUint32(offset, picture.kind === 'coverFront' ? 3 : picture.kind === 'coverBack' ? 4 : 0);
|
||||
offset += 4;
|
||||
dataView.setUint32(offset, picture.mimeType.length);
|
||||
offset += 4;
|
||||
header.set(textEncoder.encode(picture.mimeType), 8);
|
||||
offset += picture.mimeType.length;
|
||||
dataView.setUint32(offset, picture.description?.length ?? 0);
|
||||
offset += 4;
|
||||
header.set(textEncoder.encode(picture.description ?? ''), offset);
|
||||
offset += picture.description?.length ?? 0;
|
||||
offset += 4 + 4 + 4 + 4; // setting width, height, color depth, number of indexed colors to 0
|
||||
dataView.setUint32(offset, picture.data.length);
|
||||
offset += 4;
|
||||
header.set(picture.data, offset);
|
||||
offset += picture.data.length;
|
||||
assert(offset === headerSize);
|
||||
const headerBitstream = new Bitstream(new Uint8Array(4));
|
||||
headerBitstream.writeBits(1, 0); // Last metadata block -> false, will be continued by vorbis comment
|
||||
headerBitstream.writeBits(7, FlacBlockType.PICTURE); // Type -> Picture
|
||||
headerBitstream.writeBits(24, headerSize);
|
||||
this.writer.write(headerBitstream.bytes);
|
||||
this.writer.write(header);
|
||||
}
|
||||
writeVorbisCommentAndPictureBlock() {
|
||||
this.writer.seek(STREAMINFO_SIZE + FLAC_HEADER.byteLength);
|
||||
if (metadataTagsAreEmpty(this.output._metadataTags)) {
|
||||
this.metadataWritten = true;
|
||||
return;
|
||||
}
|
||||
const pictures = this.output._metadataTags.images ?? [];
|
||||
for (const picture of pictures) {
|
||||
this.writePictureBlock(picture);
|
||||
}
|
||||
const vorbisComment = createVorbisComments(new Uint8Array(0), this.output._metadataTags, false);
|
||||
const headerBitstream = new Bitstream(new Uint8Array(4));
|
||||
headerBitstream.writeBits(1, 1); // Last metadata block -> true
|
||||
headerBitstream.writeBits(7, FlacBlockType.VORBIS_COMMENT); // Type -> Vorbis comment
|
||||
headerBitstream.writeBits(24, vorbisComment.length);
|
||||
this.writer.write(headerBitstream.bytes);
|
||||
this.writer.write(vorbisComment);
|
||||
this.metadataWritten = true;
|
||||
}
|
||||
async getMimeType() {
|
||||
return 'audio/flac';
|
||||
}
|
||||
async addEncodedVideoPacket() {
|
||||
throw new Error('FLAC does not support video.');
|
||||
}
|
||||
async addEncodedAudioPacket(track, packet, meta) {
|
||||
const release = await this.mutex.acquire();
|
||||
validateAudioChunkMetadata(meta);
|
||||
assert(meta);
|
||||
assert(meta.decoderConfig);
|
||||
assert(meta.decoderConfig.description);
|
||||
try {
|
||||
this.validateAndNormalizeTimestamp(track, packet.timestamp, packet.type === 'key');
|
||||
if (this.sampleRate === null) {
|
||||
this.sampleRate = meta.decoderConfig.sampleRate;
|
||||
}
|
||||
if (this.channels === null) {
|
||||
this.channels = meta.decoderConfig.numberOfChannels;
|
||||
}
|
||||
if (this.bitsPerSample === null) {
|
||||
const descriptionBitstream = new Bitstream(toUint8Array(meta.decoderConfig.description));
|
||||
// skip 'fLaC' + block size + frame size + sample rate + number of channels
|
||||
// See demuxer for the exact structure
|
||||
descriptionBitstream.skipBits(103 + 64);
|
||||
const bitsPerSample = descriptionBitstream.readBits(5) + 1;
|
||||
this.bitsPerSample = bitsPerSample;
|
||||
}
|
||||
if (!this.metadataWritten) {
|
||||
this.writeVorbisCommentAndPictureBlock();
|
||||
}
|
||||
const slice = FileSlice.tempFromBytes(packet.data);
|
||||
readBytes(slice, 2);
|
||||
const bytes = readBytes(slice, 2);
|
||||
const bitstream = new Bitstream(bytes);
|
||||
const blockSizeOrUncommon = getBlockSizeOrUncommon(bitstream.readBits(4));
|
||||
if (blockSizeOrUncommon === null) {
|
||||
throw new Error('Invalid FLAC frame: Invalid block size.');
|
||||
}
|
||||
readCodedNumber(slice); // num
|
||||
const blockSize = readBlockSize(slice, blockSizeOrUncommon);
|
||||
this.blockSizes.push(blockSize);
|
||||
this.frameSizes.push(packet.data.length);
|
||||
const startPos = this.writer.getPos();
|
||||
this.writer.write(packet.data);
|
||||
if (this.format._options.onFrame) {
|
||||
this.format._options.onFrame(packet.data, startPos);
|
||||
}
|
||||
await this.writer.flush();
|
||||
}
|
||||
finally {
|
||||
release();
|
||||
}
|
||||
}
|
||||
addSubtitleCue() {
|
||||
throw new Error('FLAC does not support subtitles.');
|
||||
}
|
||||
async finalize() {
|
||||
const release = await this.mutex.acquire();
|
||||
let minimumBlockSize = Infinity;
|
||||
let maximumBlockSize = 0;
|
||||
let minimumFrameSize = Infinity;
|
||||
let maximumFrameSize = 0;
|
||||
let totalSamples = 0;
|
||||
for (let i = 0; i < this.blockSizes.length; i++) {
|
||||
minimumFrameSize = Math.min(minimumFrameSize, this.frameSizes[i]);
|
||||
maximumFrameSize = Math.max(maximumFrameSize, this.frameSizes[i]);
|
||||
maximumBlockSize = Math.max(maximumBlockSize, this.blockSizes[i]);
|
||||
totalSamples += this.blockSizes[i];
|
||||
// Excluding the last frame from block size calculation
|
||||
// https://www.rfc-editor.org/rfc/rfc9639.html#name-streaminfo
|
||||
// "The minimum block size (in samples) used in the stream, excluding the last block."
|
||||
const isLastFrame = i === this.blockSizes.length - 1;
|
||||
if (isLastFrame) {
|
||||
continue;
|
||||
}
|
||||
minimumBlockSize = Math.min(minimumBlockSize, this.blockSizes[i]);
|
||||
}
|
||||
assert(this.sampleRate !== null);
|
||||
assert(this.channels !== null);
|
||||
assert(this.bitsPerSample !== null);
|
||||
this.writer.seek(4);
|
||||
this.writeHeader({
|
||||
minimumBlockSize,
|
||||
maximumBlockSize,
|
||||
minimumFrameSize,
|
||||
maximumFrameSize,
|
||||
sampleRate: this.sampleRate,
|
||||
channels: this.channels,
|
||||
bitsPerSample: this.bitsPerSample,
|
||||
totalSamples,
|
||||
});
|
||||
release();
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { MetadataTags } from './metadata.js';
|
||||
import { FileSlice } from './reader.js';
|
||||
import { Writer } from './writer.js';
|
||||
export type Id3V2Header = {
|
||||
majorVersion: number;
|
||||
revision: number;
|
||||
flags: number;
|
||||
size: number;
|
||||
};
|
||||
export declare enum Id3V2HeaderFlags {
|
||||
Unsynchronisation = 128,
|
||||
ExtendedHeader = 64,
|
||||
ExperimentalIndicator = 32,
|
||||
Footer = 16
|
||||
}
|
||||
export declare enum Id3V2TextEncoding {
|
||||
ISO_8859_1 = 0,
|
||||
UTF_16_WITH_BOM = 1,
|
||||
UTF_16_BE_NO_BOM = 2,
|
||||
UTF_8 = 3
|
||||
}
|
||||
export declare const ID3_V1_TAG_SIZE = 128;
|
||||
export declare const ID3_V2_HEADER_SIZE = 10;
|
||||
export declare const ID3_V1_GENRES: string[];
|
||||
export declare const parseId3V1Tag: (slice: FileSlice, tags: MetadataTags) => void;
|
||||
export declare const readId3V1String: (slice: FileSlice, length: number) => string;
|
||||
export declare const readId3V2Header: (slice: FileSlice) => Id3V2Header | null;
|
||||
export declare const parseId3V2Tag: (slice: FileSlice, header: Id3V2Header, tags: MetadataTags) => void;
|
||||
export declare class Id3V2Reader {
|
||||
header: Id3V2Header;
|
||||
bytes: Uint8Array;
|
||||
pos: number;
|
||||
view: DataView;
|
||||
constructor(header: Id3V2Header, bytes: Uint8Array);
|
||||
frameHeaderSize(): 6 | 10;
|
||||
ununsynchronizeAll(): void;
|
||||
ununsynchronizeRegion(start: number, end: number): void;
|
||||
removeFooter(): void;
|
||||
readBytes(length: number): Uint8Array<ArrayBufferLike>;
|
||||
readU8(): number;
|
||||
readU16(): number;
|
||||
readU24(): number;
|
||||
readU32(): number;
|
||||
readAscii(length: number): string;
|
||||
readId3V2Frame(): {
|
||||
id: string;
|
||||
size: number;
|
||||
flags: number;
|
||||
} | null;
|
||||
readId3V2TextEncoding(): Id3V2TextEncoding;
|
||||
readId3V2Text(encoding: Id3V2TextEncoding, until: number): string;
|
||||
readId3V2EncodingAndText(until: number): string;
|
||||
}
|
||||
export declare class Id3V2Writer {
|
||||
writer: Writer;
|
||||
helper: Uint8Array<ArrayBuffer>;
|
||||
helperView: DataView<ArrayBufferLike>;
|
||||
constructor(writer: Writer);
|
||||
writeId3V2Tag(metadata: MetadataTags): number;
|
||||
writeU8(value: number): void;
|
||||
writeU16(value: number): void;
|
||||
writeU32(value: number): void;
|
||||
writeAscii(text: string): void;
|
||||
writeSynchsafeU32(value: number): void;
|
||||
writeIsoString(text: string): void;
|
||||
writeUtf8String(text: string): void;
|
||||
writeId3V2TextFrame(frameId: string, text: string): void;
|
||||
writeId3V2LyricsFrame(lyrics: string): void;
|
||||
writeId3V2CommentFrame(comment: string): void;
|
||||
writeId3V2ApicFrame(mimeType: string, pictureType: number, description: string, imageData: Uint8Array): void;
|
||||
}
|
||||
//# sourceMappingURL=id3.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"id3.d.ts","sourceRoot":"","sources":["../../../src/id3.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAU1C,OAAO,EAAE,SAAS,EAA2C,MAAM,UAAU,CAAC;AAC9E,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,MAAM,MAAM,WAAW,GAAG;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,oBAAY,gBAAgB;IAC3B,iBAAiB,MAAS;IAC1B,cAAc,KAAS;IACvB,qBAAqB,KAAS;IAC9B,MAAM,KAAS;CACf;AAED,oBAAY,iBAAiB;IAC5B,UAAU,IAAA;IACV,eAAe,IAAA;IACf,gBAAgB,IAAA;IAChB,KAAK,IAAA;CACL;AAED,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,eAAO,MAAM,aAAa,UA4BzB,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,OAAO,SAAS,EAAE,MAAM,YAAY,SA8CjE,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,OAAO,SAAS,EAAE,QAAQ,MAAM,WAa/D,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,OAAO,SAAS,KAAG,WAAW,GAAG,IAiBhE,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,OAAO,SAAS,EAAE,QAAQ,WAAW,EAAE,MAAM,YAAY,SA+OtF,CAAC;AAGF,qBAAa,WAAW;IAIJ,MAAM,EAAE,WAAW;IAAS,KAAK,EAAE,UAAU;IAHhE,GAAG,SAAK;IACR,IAAI,EAAE,QAAQ,CAAC;gBAEI,MAAM,EAAE,WAAW,EAAS,KAAK,EAAE,UAAU;IAIhE,eAAe;IAIf,kBAAkB;IAmBlB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;IA0BhD,YAAY;IAKZ,SAAS,CAAC,MAAM,EAAE,MAAM;IAMxB,MAAM;IAMN,OAAO;IAMP,OAAO;IAOP,OAAO;IAMP,SAAS,CAAC,MAAM,EAAE,MAAM;IASxB,cAAc;;;;;IA6Dd,qBAAqB,IAAI,iBAAiB;IAQ1C,aAAa,CAAC,QAAQ,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IA+DjE,wBAAwB,CAAC,KAAK,EAAE,MAAM;CAQtC;AAED,qBAAa,WAAW;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,0BAAqB;IAC3B,UAAU,4BAA2B;gBAEzB,MAAM,EAAE,MAAM;IAI1B,aAAa,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM;IA2I7C,OAAO,CAAC,KAAK,EAAE,MAAM;IAKrB,QAAQ,CAAC,KAAK,EAAE,MAAM;IAKtB,QAAQ,CAAC,KAAK,EAAE,MAAM;IAKtB,UAAU,CAAC,IAAI,EAAE,MAAM;IAOvB,iBAAiB,CAAC,KAAK,EAAE,MAAM;IAI/B,cAAc,CAAC,IAAI,EAAE,MAAM;IAS3B,eAAe,CAAC,IAAI,EAAE,MAAM;IAM5B,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAiBjD,qBAAqB,CAAC,MAAM,EAAE,MAAM;IAqBpC,sBAAsB,CAAC,OAAO,EAAE,MAAM;IAwBtC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU;CA6BrG"}
|
||||
+848
@@ -0,0 +1,848 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { decodeSynchsafe, encodeSynchsafe } from '../shared/mp3-misc.js';
|
||||
import { coalesceIndex, textDecoder, textEncoder, isIso88591Compatible, assertNever, keyValueIterator, toDataView, } from './misc.js';
|
||||
import { readAscii, readBytes, readU32Be, readU8 } from './reader.js';
|
||||
export var Id3V2HeaderFlags;
|
||||
(function (Id3V2HeaderFlags) {
|
||||
Id3V2HeaderFlags[Id3V2HeaderFlags["Unsynchronisation"] = 128] = "Unsynchronisation";
|
||||
Id3V2HeaderFlags[Id3V2HeaderFlags["ExtendedHeader"] = 64] = "ExtendedHeader";
|
||||
Id3V2HeaderFlags[Id3V2HeaderFlags["ExperimentalIndicator"] = 32] = "ExperimentalIndicator";
|
||||
Id3V2HeaderFlags[Id3V2HeaderFlags["Footer"] = 16] = "Footer";
|
||||
})(Id3V2HeaderFlags || (Id3V2HeaderFlags = {}));
|
||||
export var Id3V2TextEncoding;
|
||||
(function (Id3V2TextEncoding) {
|
||||
Id3V2TextEncoding[Id3V2TextEncoding["ISO_8859_1"] = 0] = "ISO_8859_1";
|
||||
Id3V2TextEncoding[Id3V2TextEncoding["UTF_16_WITH_BOM"] = 1] = "UTF_16_WITH_BOM";
|
||||
Id3V2TextEncoding[Id3V2TextEncoding["UTF_16_BE_NO_BOM"] = 2] = "UTF_16_BE_NO_BOM";
|
||||
Id3V2TextEncoding[Id3V2TextEncoding["UTF_8"] = 3] = "UTF_8";
|
||||
})(Id3V2TextEncoding || (Id3V2TextEncoding = {}));
|
||||
export const ID3_V1_TAG_SIZE = 128;
|
||||
export const ID3_V2_HEADER_SIZE = 10;
|
||||
export const ID3_V1_GENRES = [
|
||||
'Blues', 'Classic rock', 'Country', 'Dance', 'Disco', 'Funk', 'Grunge', 'Hip-hop', 'Jazz',
|
||||
'Metal', 'New age', 'Oldies', 'Other', 'Pop', 'Rhythm and blues', 'Rap', 'Reggae', 'Rock',
|
||||
'Techno', 'Industrial', 'Alternative', 'Ska', 'Death metal', 'Pranks', 'Soundtrack',
|
||||
'Euro-techno', 'Ambient', 'Trip-hop', 'Vocal', 'Jazz & funk', 'Fusion', 'Trance', 'Classical',
|
||||
'Instrumental', 'Acid', 'House', 'Game', 'Sound clip', 'Gospel', 'Noise', 'Alternative rock',
|
||||
'Bass', 'Soul', 'Punk', 'Space', 'Meditative', 'Instrumental pop', 'Instrumental rock',
|
||||
'Ethnic', 'Gothic', 'Darkwave', 'Techno-industrial', 'Electronic', 'Pop-folk', 'Eurodance',
|
||||
'Dream', 'Southern rock', 'Comedy', 'Cult', 'Gangsta', 'Top 40', 'Christian rap', 'Pop/funk',
|
||||
'Jungle music', 'Native US', 'Cabaret', 'New wave', 'Psychedelic', 'Rave', 'Showtunes',
|
||||
'Trailer', 'Lo-fi', 'Tribal', 'Acid punk', 'Acid jazz', 'Polka', 'Retro', 'Musical',
|
||||
'Rock \'n\' roll', 'Hard rock', 'Folk', 'Folk rock', 'National folk', 'Swing', 'Fast fusion',
|
||||
'Bebop', 'Latin', 'Revival', 'Celtic', 'Bluegrass', 'Avantgarde', 'Gothic rock',
|
||||
'Progressive rock', 'Psychedelic rock', 'Symphonic rock', 'Slow rock', 'Big band', 'Chorus',
|
||||
'Easy listening', 'Acoustic', 'Humour', 'Speech', 'Chanson', 'Opera', 'Chamber music',
|
||||
'Sonata', 'Symphony', 'Booty bass', 'Primus', 'Porn groove', 'Satire', 'Slow jam', 'Club',
|
||||
'Tango', 'Samba', 'Folklore', 'Ballad', 'Power ballad', 'Rhythmic Soul', 'Freestyle', 'Duet',
|
||||
'Punk rock', 'Drum solo', 'A cappella', 'Euro-house', 'Dance hall', 'Goa music', 'Drum & bass',
|
||||
'Club-house', 'Hardcore techno', 'Terror', 'Indie', 'Britpop', 'Negerpunk', 'Polsk punk',
|
||||
'Beat', 'Christian gangsta rap', 'Heavy metal', 'Black metal', 'Crossover',
|
||||
'Contemporary Christian', 'Christian rock', 'Merengue', 'Salsa', 'Thrash metal', 'Anime',
|
||||
'Jpop', 'Synthpop', 'Christmas', 'Art rock', 'Baroque', 'Bhangra', 'Big beat', 'Breakbeat',
|
||||
'Chillout', 'Downtempo', 'Dub', 'EBM', 'Eclectic', 'Electro', 'Electroclash', 'Emo',
|
||||
'Experimental', 'Garage', 'Global', 'IDM', 'Illbient', 'Industro-Goth', 'Jam Band',
|
||||
'Krautrock', 'Leftfield', 'Lounge', 'Math rock', 'New romantic', 'Nu-breakz', 'Post-punk',
|
||||
'Post-rock', 'Psytrance', 'Shoegaze', 'Space rock', 'Trop rock', 'World music', 'Neoclassical',
|
||||
'Audiobook', 'Audio theatre', 'Neue Deutsche Welle', 'Podcast', 'Indie rock', 'G-Funk',
|
||||
'Dubstep', 'Garage rock', 'Psybient',
|
||||
];
|
||||
export const parseId3V1Tag = (slice, tags) => {
|
||||
const startPos = slice.filePos;
|
||||
tags.raw ??= {};
|
||||
tags.raw['TAG'] ??= readBytes(slice, ID3_V1_TAG_SIZE - 3); // Dump the whole tag into the raw metadata
|
||||
slice.filePos = startPos;
|
||||
const title = readId3V1String(slice, 30);
|
||||
if (title)
|
||||
tags.title ??= title;
|
||||
const artist = readId3V1String(slice, 30);
|
||||
if (artist)
|
||||
tags.artist ??= artist;
|
||||
const album = readId3V1String(slice, 30);
|
||||
if (album)
|
||||
tags.album ??= album;
|
||||
const yearText = readId3V1String(slice, 4);
|
||||
const year = Number.parseInt(yearText, 10);
|
||||
if (Number.isInteger(year) && year > 0) {
|
||||
tags.date ??= new Date(year, 0, 1);
|
||||
}
|
||||
const commentBytes = readBytes(slice, 30);
|
||||
let comment;
|
||||
// Check for the ID3v1.1 track number format:
|
||||
// The 29th byte (index 28) is a null terminator, and the 30th byte is the track number.
|
||||
if (commentBytes[28] === 0 && commentBytes[29] !== 0) {
|
||||
const trackNum = commentBytes[29];
|
||||
if (trackNum > 0) {
|
||||
tags.trackNumber ??= trackNum;
|
||||
}
|
||||
slice.skip(-30);
|
||||
comment = readId3V1String(slice, 28);
|
||||
slice.skip(2);
|
||||
}
|
||||
else {
|
||||
slice.skip(-30);
|
||||
comment = readId3V1String(slice, 30);
|
||||
}
|
||||
if (comment)
|
||||
tags.comment ??= comment;
|
||||
const genreIndex = readU8(slice);
|
||||
if (genreIndex < ID3_V1_GENRES.length) {
|
||||
tags.genre ??= ID3_V1_GENRES[genreIndex];
|
||||
}
|
||||
};
|
||||
export const readId3V1String = (slice, length) => {
|
||||
const bytes = readBytes(slice, length);
|
||||
const endIndex = coalesceIndex(bytes.indexOf(0), bytes.length);
|
||||
const relevantBytes = bytes.subarray(0, endIndex);
|
||||
// Decode as ISO-8859-1
|
||||
let str = '';
|
||||
for (let i = 0; i < relevantBytes.length; i++) {
|
||||
str += String.fromCharCode(relevantBytes[i]);
|
||||
}
|
||||
return str.trimEnd(); // String also may be padded with spaces
|
||||
};
|
||||
export const readId3V2Header = (slice) => {
|
||||
const startPos = slice.filePos;
|
||||
const tag = readAscii(slice, 3);
|
||||
const majorVersion = readU8(slice);
|
||||
const revision = readU8(slice);
|
||||
const flags = readU8(slice);
|
||||
const sizeRaw = readU32Be(slice);
|
||||
if (tag !== 'ID3' || majorVersion === 0xff || revision === 0xff || (sizeRaw & 0x80808080) !== 0) {
|
||||
slice.filePos = startPos;
|
||||
return null;
|
||||
}
|
||||
const size = decodeSynchsafe(sizeRaw);
|
||||
return { majorVersion, revision, flags, size };
|
||||
};
|
||||
export const parseId3V2Tag = (slice, header, tags) => {
|
||||
// https://id3.org/id3v2.3.0
|
||||
if (![2, 3, 4].includes(header.majorVersion)) {
|
||||
console.warn(`Unsupported ID3v2 major version: ${header.majorVersion}`);
|
||||
return;
|
||||
}
|
||||
const bytes = readBytes(slice, header.size);
|
||||
const reader = new Id3V2Reader(header, bytes);
|
||||
if (header.flags & Id3V2HeaderFlags.Footer) {
|
||||
reader.removeFooter();
|
||||
}
|
||||
if ((header.flags & Id3V2HeaderFlags.Unsynchronisation) && header.majorVersion === 3) {
|
||||
reader.ununsynchronizeAll();
|
||||
}
|
||||
if (header.flags & Id3V2HeaderFlags.ExtendedHeader) {
|
||||
const extendedHeaderSize = reader.readU32();
|
||||
if (header.majorVersion === 3) {
|
||||
reader.pos += extendedHeaderSize; // The extended header size excludes itself
|
||||
}
|
||||
else {
|
||||
reader.pos += extendedHeaderSize - 4; // The extended header size includes itself
|
||||
}
|
||||
}
|
||||
while (reader.pos <= reader.bytes.length - reader.frameHeaderSize()) {
|
||||
const frame = reader.readId3V2Frame();
|
||||
if (!frame) {
|
||||
break;
|
||||
}
|
||||
const frameStartPos = reader.pos;
|
||||
const frameEndPos = reader.pos + frame.size;
|
||||
let frameEncrypted = false;
|
||||
let frameCompressed = false;
|
||||
let frameUnsynchronized = false;
|
||||
if (header.majorVersion === 3) {
|
||||
frameEncrypted = !!(frame.flags & (1 << 6));
|
||||
frameCompressed = !!(frame.flags & (1 << 7));
|
||||
}
|
||||
else if (header.majorVersion === 4) {
|
||||
frameEncrypted = !!(frame.flags & (1 << 2));
|
||||
frameCompressed = !!(frame.flags & (1 << 3));
|
||||
frameUnsynchronized = !!(frame.flags & (1 << 1))
|
||||
|| !!(header.flags & Id3V2HeaderFlags.Unsynchronisation);
|
||||
}
|
||||
if (frameEncrypted) {
|
||||
console.warn(`Skipping encrypted ID3v2 frame ${frame.id}`);
|
||||
reader.pos = frameEndPos;
|
||||
continue;
|
||||
}
|
||||
if (frameCompressed) {
|
||||
console.warn(`Skipping compressed ID3v2 frame ${frame.id}`); // Maybe someday? Idk
|
||||
reader.pos = frameEndPos;
|
||||
continue;
|
||||
}
|
||||
if (frameUnsynchronized) {
|
||||
reader.ununsynchronizeRegion(reader.pos, frameEndPos);
|
||||
}
|
||||
tags.raw ??= {};
|
||||
if (frame.id[0] === 'T') {
|
||||
// It's a text frame, let's decode as text
|
||||
tags.raw[frame.id] ??= reader.readId3V2EncodingAndText(frameEndPos);
|
||||
}
|
||||
else {
|
||||
// For the others, let's just get the bytes
|
||||
tags.raw[frame.id] ??= reader.readBytes(frame.size);
|
||||
}
|
||||
reader.pos = frameStartPos;
|
||||
switch (frame.id) {
|
||||
case 'TIT2':
|
||||
case 'TT2':
|
||||
{
|
||||
tags.title ??= reader.readId3V2EncodingAndText(frameEndPos);
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'TIT3':
|
||||
case 'TT3':
|
||||
{
|
||||
tags.description ??= reader.readId3V2EncodingAndText(frameEndPos);
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'TPE1':
|
||||
case 'TP1':
|
||||
{
|
||||
tags.artist ??= reader.readId3V2EncodingAndText(frameEndPos);
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'TALB':
|
||||
case 'TAL':
|
||||
{
|
||||
tags.album ??= reader.readId3V2EncodingAndText(frameEndPos);
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'TPE2':
|
||||
case 'TP2':
|
||||
{
|
||||
tags.albumArtist ??= reader.readId3V2EncodingAndText(frameEndPos);
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'TRCK':
|
||||
case 'TRK':
|
||||
{
|
||||
const trackText = reader.readId3V2EncodingAndText(frameEndPos);
|
||||
const parts = trackText.split('/');
|
||||
const trackNum = Number.parseInt(parts[0], 10);
|
||||
const tracksTotal = parts[1] && Number.parseInt(parts[1], 10);
|
||||
if (Number.isInteger(trackNum) && trackNum > 0) {
|
||||
tags.trackNumber ??= trackNum;
|
||||
}
|
||||
if (tracksTotal && Number.isInteger(tracksTotal) && tracksTotal > 0) {
|
||||
tags.tracksTotal ??= tracksTotal;
|
||||
}
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'TPOS':
|
||||
case 'TPA':
|
||||
{
|
||||
const discText = reader.readId3V2EncodingAndText(frameEndPos);
|
||||
const parts = discText.split('/');
|
||||
const discNum = Number.parseInt(parts[0], 10);
|
||||
const discsTotal = parts[1] && Number.parseInt(parts[1], 10);
|
||||
if (Number.isInteger(discNum) && discNum > 0) {
|
||||
tags.discNumber ??= discNum;
|
||||
}
|
||||
if (discsTotal && Number.isInteger(discsTotal) && discsTotal > 0) {
|
||||
tags.discsTotal ??= discsTotal;
|
||||
}
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'TCON':
|
||||
case 'TCO':
|
||||
{
|
||||
const genreText = reader.readId3V2EncodingAndText(frameEndPos);
|
||||
let match = /^\((\d+)\)/.exec(genreText);
|
||||
if (match) {
|
||||
const genreNumber = Number.parseInt(match[1]);
|
||||
if (ID3_V1_GENRES[genreNumber] !== undefined) {
|
||||
tags.genre ??= ID3_V1_GENRES[genreNumber];
|
||||
break;
|
||||
}
|
||||
}
|
||||
match = /^\d+$/.exec(genreText);
|
||||
if (match) {
|
||||
const genreNumber = Number.parseInt(match[0]);
|
||||
if (ID3_V1_GENRES[genreNumber] !== undefined) {
|
||||
tags.genre ??= ID3_V1_GENRES[genreNumber];
|
||||
break;
|
||||
}
|
||||
}
|
||||
tags.genre ??= genreText;
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'TDRC':
|
||||
case 'TDAT':
|
||||
{
|
||||
const dateText = reader.readId3V2EncodingAndText(frameEndPos);
|
||||
const date = new Date(dateText);
|
||||
if (!Number.isNaN(date.getTime())) {
|
||||
tags.date ??= date;
|
||||
}
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'TYER':
|
||||
case 'TYE':
|
||||
{
|
||||
const yearText = reader.readId3V2EncodingAndText(frameEndPos);
|
||||
const year = Number.parseInt(yearText, 10);
|
||||
if (Number.isInteger(year)) {
|
||||
tags.date ??= new Date(year, 0, 1);
|
||||
}
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'USLT':
|
||||
case 'ULT':
|
||||
{
|
||||
const encoding = reader.readU8();
|
||||
reader.pos += 3; // Skip language
|
||||
reader.readId3V2Text(encoding, frameEndPos); // Short content description
|
||||
tags.lyrics ??= reader.readId3V2Text(encoding, frameEndPos);
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'COMM':
|
||||
case 'COM':
|
||||
{
|
||||
const encoding = reader.readU8();
|
||||
reader.pos += 3; // Skip language
|
||||
reader.readId3V2Text(encoding, frameEndPos); // Short content description
|
||||
tags.comment ??= reader.readId3V2Text(encoding, frameEndPos);
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'APIC':
|
||||
case 'PIC':
|
||||
{
|
||||
const encoding = reader.readId3V2TextEncoding();
|
||||
let mimeType;
|
||||
if (header.majorVersion === 2) {
|
||||
const imageFormat = reader.readAscii(3);
|
||||
mimeType = imageFormat === 'PNG'
|
||||
? 'image/png'
|
||||
: imageFormat === 'JPG'
|
||||
? 'image/jpeg'
|
||||
: 'image/*';
|
||||
}
|
||||
else {
|
||||
mimeType = reader.readId3V2Text(encoding, frameEndPos);
|
||||
}
|
||||
const pictureType = reader.readU8();
|
||||
const description = reader.readId3V2Text(encoding, frameEndPos).trimEnd(); // Trim ending spaces
|
||||
const imageDataSize = frameEndPos - reader.pos;
|
||||
if (imageDataSize >= 0) {
|
||||
const imageData = reader.readBytes(imageDataSize);
|
||||
if (!tags.images)
|
||||
tags.images = [];
|
||||
tags.images.push({
|
||||
data: imageData,
|
||||
mimeType,
|
||||
kind: pictureType === 3
|
||||
? 'coverFront'
|
||||
: pictureType === 4
|
||||
? 'coverBack'
|
||||
: 'unknown',
|
||||
description,
|
||||
});
|
||||
}
|
||||
}
|
||||
;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
reader.pos += frame.size;
|
||||
}
|
||||
;
|
||||
break;
|
||||
}
|
||||
reader.pos = frameEndPos;
|
||||
}
|
||||
};
|
||||
// https://id3.org/id3v2.3.0
|
||||
export class Id3V2Reader {
|
||||
constructor(header, bytes) {
|
||||
this.header = header;
|
||||
this.bytes = bytes;
|
||||
this.pos = 0;
|
||||
this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
||||
}
|
||||
frameHeaderSize() {
|
||||
return this.header.majorVersion === 2 ? 6 : 10;
|
||||
}
|
||||
ununsynchronizeAll() {
|
||||
const newBytes = [];
|
||||
for (let i = 0; i < this.bytes.length; i++) {
|
||||
const value1 = this.bytes[i];
|
||||
newBytes.push(value1);
|
||||
if (value1 === 0xff && i !== this.bytes.length - 1) {
|
||||
const value2 = this.bytes[i];
|
||||
if (value2 === 0x00) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.bytes = new Uint8Array(newBytes);
|
||||
this.view = new DataView(this.bytes.buffer);
|
||||
}
|
||||
ununsynchronizeRegion(start, end) {
|
||||
const newBytes = [];
|
||||
for (let i = start; i < end; i++) {
|
||||
const value1 = this.bytes[i];
|
||||
newBytes.push(value1);
|
||||
if (value1 === 0xff && i !== end - 1) {
|
||||
const value2 = this.bytes[i + 1];
|
||||
if (value2 === 0x00) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
const before = this.bytes.subarray(0, start);
|
||||
const after = this.bytes.subarray(end);
|
||||
this.bytes = new Uint8Array(before.length + newBytes.length + after.length);
|
||||
this.bytes.set(before, 0);
|
||||
this.bytes.set(newBytes, before.length);
|
||||
this.bytes.set(after, before.length + newBytes.length);
|
||||
this.view = new DataView(this.bytes.buffer);
|
||||
}
|
||||
removeFooter() {
|
||||
this.bytes = this.bytes.subarray(0, this.bytes.length - ID3_V2_HEADER_SIZE);
|
||||
this.view = new DataView(this.bytes.buffer);
|
||||
}
|
||||
readBytes(length) {
|
||||
const slice = this.bytes.subarray(this.pos, this.pos + length);
|
||||
this.pos += length;
|
||||
return slice;
|
||||
}
|
||||
readU8() {
|
||||
const value = this.view.getUint8(this.pos);
|
||||
this.pos += 1;
|
||||
return value;
|
||||
}
|
||||
readU16() {
|
||||
const value = this.view.getUint16(this.pos, false);
|
||||
this.pos += 2;
|
||||
return value;
|
||||
}
|
||||
readU24() {
|
||||
const high = this.view.getUint16(this.pos, false);
|
||||
const low = this.view.getUint8(this.pos + 1);
|
||||
this.pos += 3;
|
||||
return high * 0x100 + low;
|
||||
}
|
||||
readU32() {
|
||||
const value = this.view.getUint32(this.pos, false);
|
||||
this.pos += 4;
|
||||
return value;
|
||||
}
|
||||
readAscii(length) {
|
||||
let str = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
str += String.fromCharCode(this.view.getUint8(this.pos + i));
|
||||
}
|
||||
this.pos += length;
|
||||
return str;
|
||||
}
|
||||
readId3V2Frame() {
|
||||
if (this.header.majorVersion === 2) {
|
||||
const id = this.readAscii(3);
|
||||
if (id === '\x00\x00\x00') {
|
||||
return null;
|
||||
}
|
||||
const size = this.readU24();
|
||||
return { id, size, flags: 0 };
|
||||
}
|
||||
else {
|
||||
const id = this.readAscii(4);
|
||||
if (id === '\x00\x00\x00\x00') {
|
||||
// We've landed in the padding section
|
||||
return null;
|
||||
}
|
||||
const sizeRaw = this.readU32();
|
||||
let size = this.header.majorVersion === 4
|
||||
? decodeSynchsafe(sizeRaw)
|
||||
: sizeRaw;
|
||||
const flags = this.readU16();
|
||||
const headerEndPos = this.pos;
|
||||
// Some files may have incorrectly synchsafed/unsynchsafed sizes. To validate which interpretation is valid,
|
||||
// we validate a size by skipping ahead and seeing if we land at a valid frame header (or at the end of the
|
||||
// tag.
|
||||
const isSizeValid = (size) => {
|
||||
const nextPos = this.pos + size;
|
||||
if (nextPos > this.bytes.length) {
|
||||
return false;
|
||||
}
|
||||
if (nextPos <= this.bytes.length - this.frameHeaderSize()) {
|
||||
this.pos += size;
|
||||
const nextId = this.readAscii(4);
|
||||
if (nextId !== '\x00\x00\x00\x00' && !/[0-9A-Z]{4}/.test(nextId)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (!isSizeValid(size)) {
|
||||
// Flip the synchsafing, and try if this one makes more sense
|
||||
const otherSize = this.header.majorVersion === 4
|
||||
? sizeRaw
|
||||
: decodeSynchsafe(sizeRaw);
|
||||
if (isSizeValid(otherSize)) {
|
||||
size = otherSize;
|
||||
}
|
||||
}
|
||||
this.pos = headerEndPos;
|
||||
return { id, size, flags };
|
||||
}
|
||||
}
|
||||
readId3V2TextEncoding() {
|
||||
const number = this.readU8();
|
||||
if (number > 3) {
|
||||
throw new Error(`Unsupported text encoding: ${number}`);
|
||||
}
|
||||
return number;
|
||||
}
|
||||
readId3V2Text(encoding, until) {
|
||||
const startPos = this.pos;
|
||||
const data = this.readBytes(until - this.pos);
|
||||
switch (encoding) {
|
||||
case Id3V2TextEncoding.ISO_8859_1: {
|
||||
let str = '';
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const value = data[i];
|
||||
if (value === 0) {
|
||||
this.pos = startPos + i + 1;
|
||||
break;
|
||||
}
|
||||
str += String.fromCharCode(value);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
case Id3V2TextEncoding.UTF_16_WITH_BOM: {
|
||||
if (data[0] === 0xff && data[1] === 0xfe) {
|
||||
const decoder = new TextDecoder('utf-16le');
|
||||
const endIndex = coalesceIndex(data.findIndex((x, i) => x === 0 && data[i + 1] === 0 && i % 2 === 0), data.length);
|
||||
this.pos = startPos + Math.min(endIndex + 2, data.length);
|
||||
return decoder.decode(data.subarray(2, endIndex));
|
||||
}
|
||||
else if (data[0] === 0xfe && data[1] === 0xff) {
|
||||
const decoder = new TextDecoder('utf-16be');
|
||||
const endIndex = coalesceIndex(data.findIndex((x, i) => x === 0 && data[i + 1] === 0 && i % 2 === 0), data.length);
|
||||
this.pos = startPos + Math.min(endIndex + 2, data.length);
|
||||
return decoder.decode(data.subarray(2, endIndex));
|
||||
}
|
||||
else {
|
||||
// Treat it like UTF-8, some files do this
|
||||
const endIndex = coalesceIndex(data.findIndex(x => x === 0), data.length);
|
||||
this.pos = startPos + Math.min(endIndex + 1, data.length);
|
||||
return textDecoder.decode(data.subarray(0, endIndex));
|
||||
}
|
||||
}
|
||||
case Id3V2TextEncoding.UTF_16_BE_NO_BOM: {
|
||||
const decoder = new TextDecoder('utf-16be');
|
||||
const endIndex = coalesceIndex(data.findIndex((x, i) => x === 0 && data[i + 1] === 0 && i % 2 === 0), data.length);
|
||||
this.pos = startPos + Math.min(endIndex + 2, data.length);
|
||||
return decoder.decode(data.subarray(0, endIndex));
|
||||
}
|
||||
case Id3V2TextEncoding.UTF_8: {
|
||||
const endIndex = coalesceIndex(data.findIndex(x => x === 0), data.length);
|
||||
this.pos = startPos + Math.min(endIndex + 1, data.length);
|
||||
return textDecoder.decode(data.subarray(0, endIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
readId3V2EncodingAndText(until) {
|
||||
if (this.pos >= until) {
|
||||
return '';
|
||||
}
|
||||
const encoding = this.readId3V2TextEncoding();
|
||||
return this.readId3V2Text(encoding, until);
|
||||
}
|
||||
}
|
||||
export class Id3V2Writer {
|
||||
constructor(writer) {
|
||||
this.helper = new Uint8Array(8);
|
||||
this.helperView = toDataView(this.helper);
|
||||
this.writer = writer;
|
||||
}
|
||||
writeId3V2Tag(metadata) {
|
||||
const tagStartPos = this.writer.getPos();
|
||||
// Write ID3v2.4 header
|
||||
this.writeAscii('ID3');
|
||||
this.writeU8(0x04); // Version 2.4
|
||||
this.writeU8(0x00); // Revision 0
|
||||
this.writeU8(0x00); // Flags
|
||||
this.writeSynchsafeU32(0); // Size placeholder
|
||||
const framesStartPos = this.writer.getPos();
|
||||
const writtenTags = new Set();
|
||||
// Write all metadata frames
|
||||
for (const { key, value } of keyValueIterator(metadata)) {
|
||||
switch (key) {
|
||||
case 'title':
|
||||
{
|
||||
this.writeId3V2TextFrame('TIT2', value);
|
||||
writtenTags.add('TIT2');
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'description':
|
||||
{
|
||||
this.writeId3V2TextFrame('TIT3', value);
|
||||
writtenTags.add('TIT3');
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'artist':
|
||||
{
|
||||
this.writeId3V2TextFrame('TPE1', value);
|
||||
writtenTags.add('TPE1');
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'album':
|
||||
{
|
||||
this.writeId3V2TextFrame('TALB', value);
|
||||
writtenTags.add('TALB');
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'albumArtist':
|
||||
{
|
||||
this.writeId3V2TextFrame('TPE2', value);
|
||||
writtenTags.add('TPE2');
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'trackNumber':
|
||||
{
|
||||
const string = metadata.tracksTotal !== undefined
|
||||
? `${value}/${metadata.tracksTotal}`
|
||||
: value.toString();
|
||||
this.writeId3V2TextFrame('TRCK', string);
|
||||
writtenTags.add('TRCK');
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'discNumber':
|
||||
{
|
||||
const string = metadata.discsTotal !== undefined
|
||||
? `${value}/${metadata.discsTotal}`
|
||||
: value.toString();
|
||||
this.writeId3V2TextFrame('TPOS', string);
|
||||
writtenTags.add('TPOS');
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'genre':
|
||||
{
|
||||
this.writeId3V2TextFrame('TCON', value);
|
||||
writtenTags.add('TCON');
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'date':
|
||||
{
|
||||
this.writeId3V2TextFrame('TDRC', value.toISOString().slice(0, 10));
|
||||
writtenTags.add('TDRC');
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'lyrics':
|
||||
{
|
||||
this.writeId3V2LyricsFrame(value);
|
||||
writtenTags.add('USLT');
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'comment':
|
||||
{
|
||||
this.writeId3V2CommentFrame(value);
|
||||
writtenTags.add('COMM');
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'images':
|
||||
{
|
||||
const pictureTypeMap = { coverFront: 0x03, coverBack: 0x04, unknown: 0x00 };
|
||||
for (const image of value) {
|
||||
const pictureType = pictureTypeMap[image.kind] ?? 0x00;
|
||||
const description = image.description ?? '';
|
||||
this.writeId3V2ApicFrame(image.mimeType, pictureType, description, image.data);
|
||||
}
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'tracksTotal':
|
||||
case 'discsTotal':
|
||||
{
|
||||
// Handled with trackNumber and discNumber respectively
|
||||
}
|
||||
;
|
||||
break;
|
||||
case 'raw':
|
||||
{
|
||||
// Handled later
|
||||
}
|
||||
;
|
||||
break;
|
||||
default: {
|
||||
assertNever(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (metadata.raw) {
|
||||
for (const key in metadata.raw) {
|
||||
const value = metadata.raw[key];
|
||||
if (value == null || key.length !== 4 || writtenTags.has(key)) {
|
||||
continue;
|
||||
}
|
||||
let bytes;
|
||||
if (typeof value === 'string') {
|
||||
const encoded = textEncoder.encode(value);
|
||||
bytes = new Uint8Array(encoded.byteLength + 2);
|
||||
bytes[0] = Id3V2TextEncoding.UTF_8;
|
||||
bytes.set(encoded, 1);
|
||||
// Last byte is the null terminator
|
||||
}
|
||||
else if (value instanceof Uint8Array) {
|
||||
bytes = value;
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
this.writeAscii(key);
|
||||
this.writeSynchsafeU32(bytes.byteLength);
|
||||
this.writeU16(0x0000);
|
||||
this.writer.write(bytes);
|
||||
}
|
||||
}
|
||||
const framesEndPos = this.writer.getPos();
|
||||
const framesSize = framesEndPos - framesStartPos;
|
||||
// Update the size field in the header (synchsafe)
|
||||
this.writer.seek(tagStartPos + 6); // Skip 'ID3' + version + revision + flags
|
||||
this.writeSynchsafeU32(framesSize);
|
||||
this.writer.seek(framesEndPos);
|
||||
return framesSize + 10; // +10 for the header size
|
||||
}
|
||||
writeU8(value) {
|
||||
this.helper[0] = value;
|
||||
this.writer.write(this.helper.subarray(0, 1));
|
||||
}
|
||||
writeU16(value) {
|
||||
this.helperView.setUint16(0, value, false);
|
||||
this.writer.write(this.helper.subarray(0, 2));
|
||||
}
|
||||
writeU32(value) {
|
||||
this.helperView.setUint32(0, value, false);
|
||||
this.writer.write(this.helper.subarray(0, 4));
|
||||
}
|
||||
writeAscii(text) {
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
this.helper[i] = text.charCodeAt(i);
|
||||
}
|
||||
this.writer.write(this.helper.subarray(0, text.length));
|
||||
}
|
||||
writeSynchsafeU32(value) {
|
||||
this.writeU32(encodeSynchsafe(value));
|
||||
}
|
||||
writeIsoString(text) {
|
||||
const bytes = new Uint8Array(text.length + 1);
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
bytes[i] = text.charCodeAt(i);
|
||||
}
|
||||
bytes[text.length] = 0x00;
|
||||
this.writer.write(bytes);
|
||||
}
|
||||
writeUtf8String(text) {
|
||||
const utf8Data = textEncoder.encode(text);
|
||||
this.writer.write(utf8Data);
|
||||
this.writeU8(0x00);
|
||||
}
|
||||
writeId3V2TextFrame(frameId, text) {
|
||||
const useIso88591 = isIso88591Compatible(text);
|
||||
const textDataLength = useIso88591 ? text.length : textEncoder.encode(text).byteLength;
|
||||
const frameSize = 1 + textDataLength + 1;
|
||||
this.writeAscii(frameId);
|
||||
this.writeSynchsafeU32(frameSize);
|
||||
this.writeU16(0x0000);
|
||||
this.writeU8(useIso88591 ? Id3V2TextEncoding.ISO_8859_1 : Id3V2TextEncoding.UTF_8);
|
||||
if (useIso88591) {
|
||||
this.writeIsoString(text);
|
||||
}
|
||||
else {
|
||||
this.writeUtf8String(text);
|
||||
}
|
||||
}
|
||||
writeId3V2LyricsFrame(lyrics) {
|
||||
const useIso88591 = isIso88591Compatible(lyrics);
|
||||
const shortDescription = '';
|
||||
const frameSize = 1 + 3 + shortDescription.length + 1 + lyrics.length + 1;
|
||||
this.writeAscii('USLT');
|
||||
this.writeSynchsafeU32(frameSize);
|
||||
this.writeU16(0x0000);
|
||||
this.writeU8(useIso88591 ? Id3V2TextEncoding.ISO_8859_1 : Id3V2TextEncoding.UTF_8);
|
||||
this.writeAscii('und');
|
||||
if (useIso88591) {
|
||||
this.writeIsoString(shortDescription);
|
||||
this.writeIsoString(lyrics);
|
||||
}
|
||||
else {
|
||||
this.writeUtf8String(shortDescription);
|
||||
this.writeUtf8String(lyrics);
|
||||
}
|
||||
}
|
||||
writeId3V2CommentFrame(comment) {
|
||||
const useIso88591 = isIso88591Compatible(comment);
|
||||
const textDataLength = useIso88591 ? comment.length : textEncoder.encode(comment).byteLength;
|
||||
const shortDescription = '';
|
||||
const frameSize = 1 + 3 + shortDescription.length + 1 + textDataLength + 1;
|
||||
this.writeAscii('COMM');
|
||||
this.writeSynchsafeU32(frameSize);
|
||||
this.writeU16(0x0000);
|
||||
this.writeU8(useIso88591 ? Id3V2TextEncoding.ISO_8859_1 : Id3V2TextEncoding.UTF_8);
|
||||
this.writeU8(0x75); // 'u'
|
||||
this.writeU8(0x6E); // 'n'
|
||||
this.writeU8(0x64); // 'd'
|
||||
if (useIso88591) {
|
||||
this.writeIsoString(shortDescription);
|
||||
this.writeIsoString(comment);
|
||||
}
|
||||
else {
|
||||
this.writeUtf8String(shortDescription);
|
||||
this.writeUtf8String(comment);
|
||||
}
|
||||
}
|
||||
writeId3V2ApicFrame(mimeType, pictureType, description, imageData) {
|
||||
const useIso88591 = isIso88591Compatible(mimeType) && isIso88591Compatible(description);
|
||||
const descriptionDataLength = useIso88591
|
||||
? description.length
|
||||
: textEncoder.encode(description).byteLength;
|
||||
const frameSize = 1 + mimeType.length + 1 + 1 + descriptionDataLength + 1 + imageData.byteLength;
|
||||
this.writeAscii('APIC');
|
||||
this.writeSynchsafeU32(frameSize);
|
||||
this.writeU16(0x0000);
|
||||
this.writeU8(useIso88591 ? Id3V2TextEncoding.ISO_8859_1 : Id3V2TextEncoding.UTF_8);
|
||||
if (useIso88591) {
|
||||
this.writeIsoString(mimeType);
|
||||
}
|
||||
else {
|
||||
this.writeUtf8String(mimeType);
|
||||
}
|
||||
this.writeU8(pictureType);
|
||||
if (useIso88591) {
|
||||
this.writeIsoString(description);
|
||||
}
|
||||
else {
|
||||
this.writeUtf8String(description);
|
||||
}
|
||||
this.writer.write(imageData);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/// <reference types="dom-mediacapture-transform" preserve="true" />
|
||||
/// <reference types="dom-webcodecs" preserve="true" />
|
||||
export { Output, OutputOptions, BaseTrackMetadata, VideoTrackMetadata, AudioTrackMetadata, SubtitleTrackMetadata, } from './output.js';
|
||||
export { OutputFormat, AdtsOutputFormat, AdtsOutputFormatOptions, FlacOutputFormat, FlacOutputFormatOptions, IsobmffOutputFormat, IsobmffOutputFormatOptions, MkvOutputFormat, MkvOutputFormatOptions, MovOutputFormat, Mp3OutputFormat, Mp3OutputFormatOptions, Mp4OutputFormat, MpegTsOutputFormat, MpegTsOutputFormatOptions, OggOutputFormat, OggOutputFormatOptions, WavOutputFormat, WavOutputFormatOptions, WebMOutputFormat, WebMOutputFormatOptions, InclusiveIntegerRange, TrackCountLimits, } from './output-format.js';
|
||||
export { MediaSource, VideoSource, AudioSource, SubtitleSource, AudioBufferSource, AudioSampleSource, CanvasSource, EncodedAudioPacketSource, EncodedVideoPacketSource, MediaStreamAudioTrackSource, MediaStreamVideoTrackSource, TextSubtitleSource, VideoSampleSource, } from './media-source.js';
|
||||
export { MediaCodec, VideoCodec, AudioCodec, SubtitleCodec, VIDEO_CODECS, AUDIO_CODECS, PCM_AUDIO_CODECS, NON_PCM_AUDIO_CODECS, SUBTITLE_CODECS, } from './codec.js';
|
||||
export { VideoEncodingConfig, VideoEncodingAdditionalOptions, AudioEncodingConfig, AudioEncodingAdditionalOptions, canEncode, canEncodeVideo, canEncodeAudio, canEncodeSubtitles, getEncodableCodecs, getEncodableVideoCodecs, getEncodableAudioCodecs, getEncodableSubtitleCodecs, getFirstEncodableVideoCodec, getFirstEncodableAudioCodec, getFirstEncodableSubtitleCodec, Quality, QUALITY_VERY_LOW, QUALITY_LOW, QUALITY_MEDIUM, QUALITY_HIGH, QUALITY_VERY_HIGH, } from './encode.js';
|
||||
export { Target, BufferTarget, FilePathTarget, FilePathTargetOptions, NullTarget, StreamTarget, StreamTargetOptions, StreamTargetChunk, } from './target.js';
|
||||
export { AnyIterable, MaybePromise, Rotation, SetRequired, } from './misc.js';
|
||||
export { TrackType, ALL_TRACK_TYPES, } from './output.js';
|
||||
export { Source, BlobSource, BlobSourceOptions, BufferSource, FilePathSource, FilePathSourceOptions, StreamSource, StreamSourceOptions, ReadableStreamSource, ReadableStreamSourceOptions, UrlSource, UrlSourceOptions, } from './source.js';
|
||||
export { InputFormat, AdtsInputFormat, FlacInputFormat, IsobmffInputFormat, MatroskaInputFormat, Mp3InputFormat, Mp4InputFormat, MpegTsInputFormat, OggInputFormat, QuickTimeInputFormat, WaveInputFormat, WebMInputFormat, ALL_FORMATS, ADTS, FLAC, MATROSKA, MP3, MP4, MPEG_TS, OGG, QTFF, WAVE, WEBM, } from './input-format.js';
|
||||
export { Input, InputOptions, InputDisposedError, } from './input.js';
|
||||
export { InputTrack, InputVideoTrack, InputAudioTrack, PacketStats, } from './input-track.js';
|
||||
export { EncodedPacket, EncodedPacketSideData, PacketType, } from './packet.js';
|
||||
export { AudioSample, AudioSampleInit, AudioSampleCopyToOptions, VideoSample, VideoSampleInit, VideoSamplePixelFormat, VideoSampleColorSpace, CropRectangle, VIDEO_SAMPLE_PIXEL_FORMATS, } from './sample.js';
|
||||
export { AudioBufferSink, AudioSampleSink, BaseMediaSampleSink, CanvasSink, CanvasSinkOptions, EncodedPacketSink, PacketRetrievalOptions, VideoSampleSink, WrappedAudioBuffer, WrappedCanvas, } from './media-sink.js';
|
||||
export { Conversion, ConversionOptions, ConversionVideoOptions, ConversionAudioOptions, ConversionCanceledError, DiscardedTrack, } from './conversion.js';
|
||||
export { CustomVideoDecoder, CustomVideoEncoder, CustomAudioDecoder, CustomAudioEncoder, registerDecoder, registerEncoder, } from './custom-coder.js';
|
||||
export { MetadataTags, AttachedImage, RichImageData, AttachedFile, TrackDisposition, } from './metadata.js';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"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"}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/// <reference types="dom-mediacapture-transform" preserve="true" />
|
||||
/// <reference types="dom-webcodecs" preserve="true" />
|
||||
const MEDIABUNNY_LOADED_SYMBOL = Symbol.for('mediabunny loaded');
|
||||
if (globalThis[MEDIABUNNY_LOADED_SYMBOL]) {
|
||||
console.error('[WARNING]\nMediabunny was loaded twice.'
|
||||
+ ' This will likely cause Mediabunny not to work correctly.'
|
||||
+ ' Check if multiple dependencies are importing different versions of Mediabunny,'
|
||||
+ ' or if something is being bundled incorrectly.');
|
||||
}
|
||||
globalThis[MEDIABUNNY_LOADED_SYMBOL] = true;
|
||||
export { Output, } from './output.js';
|
||||
export { OutputFormat, AdtsOutputFormat, FlacOutputFormat, IsobmffOutputFormat, MkvOutputFormat, MovOutputFormat, Mp3OutputFormat, Mp4OutputFormat, MpegTsOutputFormat, OggOutputFormat, WavOutputFormat, WebMOutputFormat, } from './output-format.js';
|
||||
export { MediaSource, VideoSource, AudioSource, SubtitleSource, AudioBufferSource, AudioSampleSource, CanvasSource, EncodedAudioPacketSource, EncodedVideoPacketSource, MediaStreamAudioTrackSource, MediaStreamVideoTrackSource, TextSubtitleSource, VideoSampleSource, } from './media-source.js';
|
||||
export { VIDEO_CODECS, AUDIO_CODECS, PCM_AUDIO_CODECS, NON_PCM_AUDIO_CODECS, SUBTITLE_CODECS, } from './codec.js';
|
||||
export { canEncode, canEncodeVideo, canEncodeAudio, canEncodeSubtitles, getEncodableCodecs, getEncodableVideoCodecs, getEncodableAudioCodecs, getEncodableSubtitleCodecs, getFirstEncodableVideoCodec, getFirstEncodableAudioCodec, getFirstEncodableSubtitleCodec, Quality, QUALITY_VERY_LOW, QUALITY_LOW, QUALITY_MEDIUM, QUALITY_HIGH, QUALITY_VERY_HIGH, } from './encode.js';
|
||||
export { Target, BufferTarget, FilePathTarget, NullTarget, StreamTarget, } from './target.js';
|
||||
export { ALL_TRACK_TYPES, } from './output.js';
|
||||
export { Source, BlobSource, BufferSource, FilePathSource, StreamSource, ReadableStreamSource, UrlSource, } from './source.js';
|
||||
export { InputFormat, AdtsInputFormat, FlacInputFormat, IsobmffInputFormat, MatroskaInputFormat, Mp3InputFormat, Mp4InputFormat, MpegTsInputFormat, OggInputFormat, QuickTimeInputFormat, WaveInputFormat, WebMInputFormat, ALL_FORMATS, ADTS, FLAC, MATROSKA, MP3, MP4, MPEG_TS, OGG, QTFF, WAVE, WEBM, } from './input-format.js';
|
||||
export { Input, InputDisposedError, } from './input.js';
|
||||
export { InputTrack, InputVideoTrack, InputAudioTrack, } from './input-track.js';
|
||||
export { EncodedPacket, } from './packet.js';
|
||||
export { AudioSample, VideoSample, VideoSampleColorSpace, VIDEO_SAMPLE_PIXEL_FORMATS, } from './sample.js';
|
||||
export { AudioBufferSink, AudioSampleSink, BaseMediaSampleSink, CanvasSink, EncodedPacketSink, VideoSampleSink, } from './media-sink.js';
|
||||
export { Conversion, ConversionCanceledError, } from './conversion.js';
|
||||
export { CustomVideoDecoder, CustomVideoEncoder, CustomAudioDecoder, CustomAudioEncoder, registerDecoder, registerEncoder, } from './custom-coder.js';
|
||||
export { RichImageData, AttachedFile, } from './metadata.js';
|
||||
// 🐡🦔
|
||||
Generated
Vendored
+213
@@ -0,0 +1,213 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/**
|
||||
* Base class representing an input media file format.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare abstract class InputFormat {
|
||||
/** Returns the name of the input format. */
|
||||
abstract get name(): string;
|
||||
/** Returns the typical base MIME type of the input format. */
|
||||
abstract get mimeType(): string;
|
||||
}
|
||||
/**
|
||||
* Format representing files compatible with the ISO base media file format (ISOBMFF), like MP4 or MOV files.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare abstract class IsobmffInputFormat extends InputFormat {
|
||||
}
|
||||
/**
|
||||
* MPEG-4 Part 14 (MP4) file format.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link MP4} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare class Mp4InputFormat extends IsobmffInputFormat {
|
||||
get name(): string;
|
||||
get mimeType(): string;
|
||||
}
|
||||
/**
|
||||
* QuickTime File Format (QTFF), often called MOV.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link QTFF} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare class QuickTimeInputFormat extends IsobmffInputFormat {
|
||||
get name(): string;
|
||||
get mimeType(): string;
|
||||
}
|
||||
/**
|
||||
* Matroska file format.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link MATROSKA} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare class MatroskaInputFormat extends InputFormat {
|
||||
get name(): string;
|
||||
get mimeType(): string;
|
||||
}
|
||||
/**
|
||||
* WebM file format, based on Matroska.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link WEBM} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare class WebMInputFormat extends MatroskaInputFormat {
|
||||
get name(): string;
|
||||
get mimeType(): string;
|
||||
}
|
||||
/**
|
||||
* MP3 file format.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link MP3} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare class Mp3InputFormat extends InputFormat {
|
||||
get name(): string;
|
||||
get mimeType(): string;
|
||||
}
|
||||
/**
|
||||
* WAVE file format, based on RIFF.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link WAVE} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare class WaveInputFormat extends InputFormat {
|
||||
get name(): string;
|
||||
get mimeType(): string;
|
||||
}
|
||||
/**
|
||||
* Ogg file format.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link OGG} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare class OggInputFormat extends InputFormat {
|
||||
get name(): string;
|
||||
get mimeType(): string;
|
||||
}
|
||||
/**
|
||||
* FLAC file format.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link FLAC} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare class FlacInputFormat extends InputFormat {
|
||||
get name(): string;
|
||||
get mimeType(): string;
|
||||
}
|
||||
/**
|
||||
* ADTS file format.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link ADTS} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare class AdtsInputFormat extends InputFormat {
|
||||
get name(): string;
|
||||
get mimeType(): string;
|
||||
}
|
||||
/**
|
||||
* MPEG Transport Stream (MPEG-TS) file format.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link MPEG_TS} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare class MpegTsInputFormat extends InputFormat {
|
||||
get name(): string;
|
||||
get mimeType(): string;
|
||||
}
|
||||
/**
|
||||
* MP4 input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare const MP4: Mp4InputFormat;
|
||||
/**
|
||||
* QuickTime File Format input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare const QTFF: QuickTimeInputFormat;
|
||||
/**
|
||||
* Matroska input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare const MATROSKA: MatroskaInputFormat;
|
||||
/**
|
||||
* WebM input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare const WEBM: WebMInputFormat;
|
||||
/**
|
||||
* MP3 input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare const MP3: Mp3InputFormat;
|
||||
/**
|
||||
* WAVE input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare const WAVE: WaveInputFormat;
|
||||
/**
|
||||
* Ogg input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare const OGG: OggInputFormat;
|
||||
/**
|
||||
* ADTS input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare const ADTS: AdtsInputFormat;
|
||||
/**
|
||||
* FLAC input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare const FLAC: FlacInputFormat;
|
||||
/**
|
||||
* MPEG-TS input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare const MPEG_TS: MpegTsInputFormat;
|
||||
/**
|
||||
* List of all input format singletons. If you don't need to support all input formats, you should specify the
|
||||
* formats individually for better tree shaking.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export declare const ALL_FORMATS: InputFormat[];
|
||||
//# sourceMappingURL=input-format.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"input-format.d.ts","sourceRoot":"","sources":["../../../src/input-format.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA6BH;;;;GAIG;AACH,8BAAsB,WAAW;IAOhC,4CAA4C;IAC5C,QAAQ,KAAK,IAAI,IAAI,MAAM,CAAC;IAC5B,8DAA8D;IAC9D,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC;CAChC;AAED;;;;GAIG;AACH,8BAAsB,kBAAmB,SAAQ,WAAW;CAqB3D;AAED;;;;;;;GAOG;AACH,qBAAa,cAAe,SAAQ,kBAAkB;IAOrD,IAAI,IAAI,WAEP;IAED,IAAI,QAAQ,WAEX;CACD;AAED;;;;;;;GAOG;AACH,qBAAa,oBAAqB,SAAQ,kBAAkB;IAO3D,IAAI,IAAI,WAEP;IAED,IAAI,QAAQ,WAEX;CACD;AAED;;;;;;;GAOG;AACH,qBAAa,mBAAoB,SAAQ,WAAW;IAmFnD,IAAI,IAAI,WAEP;IAED,IAAI,QAAQ,WAEX;CACD;AAED;;;;;;;GAOG;AACH,qBAAa,eAAgB,SAAQ,mBAAmB;IAMvD,IAAa,IAAI,WAEhB;IAED,IAAa,QAAQ,WAEpB;CACD;AAED;;;;;;;GAOG;AACH,qBAAa,cAAe,SAAQ,WAAW;IA+D9C,IAAI,IAAI,WAEP;IAED,IAAI,QAAQ,WAEX;CACD;AAED;;;;;;;GAOG;AACH,qBAAa,eAAgB,SAAQ,WAAW;IAuB/C,IAAI,IAAI,WAEP;IAED,IAAI,QAAQ,WAEX;CACD;AAED;;;;;;;GAOG;AACH,qBAAa,cAAe,SAAQ,WAAW;IAe9C,IAAI,IAAI,WAEP;IAED,IAAI,QAAQ,WAEX;CACD;AACD;;;;;;;GAOG;AACH,qBAAa,eAAgB,SAAQ,WAAW;IAU/C,IAAI,IAAI,WAEP;IAED,IAAI,QAAQ,WAEX;CAMD;AAED;;;;;;;GAOG;AACH,qBAAa,eAAgB,SAAQ,WAAW;IAwD/C,IAAI,IAAI,WAEP;IAED,IAAI,QAAQ,WAEX;CACD;AAED;;;;;;;GAOG;AACH,qBAAa,iBAAkB,SAAQ,WAAW;IA6BjD,IAAI,IAAI,WAEP;IAED,IAAI,QAAQ,WAEX;CACD;AAED;;;;GAIG;AACH,eAAO,MAAM,GAAG,gBAAuC,CAAC;AACxD;;;;GAIG;AACH,eAAO,MAAM,IAAI,sBAA6C,CAAC;AAC/D;;;;GAIG;AACH,eAAO,MAAM,QAAQ,qBAA4C,CAAC;AAClE;;;;GAIG;AACH,eAAO,MAAM,IAAI,iBAAwC,CAAC;AAC1D;;;;GAIG;AACH,eAAO,MAAM,GAAG,gBAAuC,CAAC;AACxD;;;;GAIG;AACH,eAAO,MAAM,IAAI,iBAAwC,CAAC;AAC1D;;;;GAIG;AACH,eAAO,MAAM,GAAG,gBAAuC,CAAC;AACxD;;;;GAIG;AACH,eAAO,MAAM,IAAI,iBAAwC,CAAC;AAE1D;;;;GAIG;AACH,eAAO,MAAM,IAAI,iBAAwC,CAAC;AAE1D;;;;GAIG;AACH,eAAO,MAAM,OAAO,mBAA0C,CAAC;AAE/D;;;;;GAKG;AACH,eAAO,MAAM,WAAW,EAAE,WAAW,EAAqE,CAAC"}
|
||||
Generated
Vendored
+546
@@ -0,0 +1,546 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { IsobmffDemuxer } from './isobmff/isobmff-demuxer.js';
|
||||
import { EBMLId, MAX_HEADER_SIZE, MIN_HEADER_SIZE, readAsciiString, readElementHeader, readElementSize, readUnsignedInt, readVarIntSize, } from './matroska/ebml.js';
|
||||
import { MatroskaDemuxer } from './matroska/matroska-demuxer.js';
|
||||
import { Mp3Demuxer } from './mp3/mp3-demuxer.js';
|
||||
import { FRAME_HEADER_SIZE, getXingOffset, INFO, XING } from '../shared/mp3-misc.js';
|
||||
import { ID3_V2_HEADER_SIZE, readId3V2Header } from './id3.js';
|
||||
import { readNextMp3FrameHeader } from './mp3/mp3-reader.js';
|
||||
import { OggDemuxer } from './ogg/ogg-demuxer.js';
|
||||
import { WaveDemuxer } from './wave/wave-demuxer.js';
|
||||
import { MAX_ADTS_FRAME_HEADER_SIZE, MIN_ADTS_FRAME_HEADER_SIZE, readAdtsFrameHeader } from './adts/adts-reader.js';
|
||||
import { AdtsDemuxer } from './adts/adts-demuxer.js';
|
||||
import { readAscii, readBytes, readU32Be } from './reader.js';
|
||||
import { FlacDemuxer } from './flac/flac-demuxer.js';
|
||||
import { MpegTsDemuxer } from './mpeg-ts/mpeg-ts-demuxer.js';
|
||||
import { TS_PACKET_SIZE } from './mpeg-ts/mpeg-ts-misc.js';
|
||||
/**
|
||||
* Base class representing an input media file format.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export class InputFormat {
|
||||
}
|
||||
/**
|
||||
* Format representing files compatible with the ISO base media file format (ISOBMFF), like MP4 or MOV files.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export class IsobmffInputFormat extends InputFormat {
|
||||
/** @internal */
|
||||
async _getMajorBrand(input) {
|
||||
let slice = input._reader.requestSlice(0, 12);
|
||||
if (slice instanceof Promise)
|
||||
slice = await slice;
|
||||
if (!slice)
|
||||
return null;
|
||||
slice.skip(4);
|
||||
const fourCc = readAscii(slice, 4);
|
||||
if (fourCc !== 'ftyp') {
|
||||
return null;
|
||||
}
|
||||
return readAscii(slice, 4);
|
||||
}
|
||||
/** @internal */
|
||||
_createDemuxer(input) {
|
||||
return new IsobmffDemuxer(input);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* MPEG-4 Part 14 (MP4) file format.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link MP4} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export class Mp4InputFormat extends IsobmffInputFormat {
|
||||
/** @internal */
|
||||
async _canReadInput(input) {
|
||||
const majorBrand = await this._getMajorBrand(input);
|
||||
return !!majorBrand && majorBrand !== 'qt ';
|
||||
}
|
||||
get name() {
|
||||
return 'MP4';
|
||||
}
|
||||
get mimeType() {
|
||||
return 'video/mp4';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* QuickTime File Format (QTFF), often called MOV.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link QTFF} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export class QuickTimeInputFormat extends IsobmffInputFormat {
|
||||
/** @internal */
|
||||
async _canReadInput(input) {
|
||||
const majorBrand = await this._getMajorBrand(input);
|
||||
return majorBrand === 'qt ';
|
||||
}
|
||||
get name() {
|
||||
return 'QuickTime File Format';
|
||||
}
|
||||
get mimeType() {
|
||||
return 'video/quicktime';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Matroska file format.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link MATROSKA} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export class MatroskaInputFormat extends InputFormat {
|
||||
/** @internal */
|
||||
async isSupportedEBMLOfDocType(input, desiredDocType) {
|
||||
let headerSlice = input._reader.requestSlice(0, MAX_HEADER_SIZE);
|
||||
if (headerSlice instanceof Promise)
|
||||
headerSlice = await headerSlice;
|
||||
if (!headerSlice)
|
||||
return false;
|
||||
const varIntSize = readVarIntSize(headerSlice);
|
||||
if (varIntSize === null) {
|
||||
return false;
|
||||
}
|
||||
if (varIntSize < 1 || varIntSize > 8) {
|
||||
return false;
|
||||
}
|
||||
const id = readUnsignedInt(headerSlice, varIntSize);
|
||||
if (id !== EBMLId.EBML) {
|
||||
return false;
|
||||
}
|
||||
const dataSize = readElementSize(headerSlice);
|
||||
if (typeof dataSize !== 'number') {
|
||||
return false; // Miss me with that shit
|
||||
}
|
||||
let dataSlice = input._reader.requestSlice(headerSlice.filePos, dataSize);
|
||||
if (dataSlice instanceof Promise)
|
||||
dataSlice = await dataSlice;
|
||||
if (!dataSlice)
|
||||
return false;
|
||||
const startPos = headerSlice.filePos;
|
||||
while (dataSlice.filePos <= startPos + dataSize - MIN_HEADER_SIZE) {
|
||||
const header = readElementHeader(dataSlice);
|
||||
if (!header)
|
||||
break;
|
||||
const { id, size } = header;
|
||||
const dataStartPos = dataSlice.filePos;
|
||||
if (size === undefined)
|
||||
return false;
|
||||
switch (id) {
|
||||
case EBMLId.EBMLVersion:
|
||||
{
|
||||
const ebmlVersion = readUnsignedInt(dataSlice, size);
|
||||
if (ebmlVersion !== 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
;
|
||||
break;
|
||||
case EBMLId.EBMLReadVersion:
|
||||
{
|
||||
const ebmlReadVersion = readUnsignedInt(dataSlice, size);
|
||||
if (ebmlReadVersion !== 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
;
|
||||
break;
|
||||
case EBMLId.DocType:
|
||||
{
|
||||
const docType = readAsciiString(dataSlice, size);
|
||||
if (docType !== desiredDocType) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
;
|
||||
break;
|
||||
case EBMLId.DocTypeVersion:
|
||||
{
|
||||
const docTypeVersion = readUnsignedInt(dataSlice, size);
|
||||
if (docTypeVersion > 4) { // Support up to Matroska v4
|
||||
return false;
|
||||
}
|
||||
}
|
||||
;
|
||||
break;
|
||||
}
|
||||
dataSlice.filePos = dataStartPos + size;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/** @internal */
|
||||
_canReadInput(input) {
|
||||
return this.isSupportedEBMLOfDocType(input, 'matroska');
|
||||
}
|
||||
/** @internal */
|
||||
_createDemuxer(input) {
|
||||
return new MatroskaDemuxer(input);
|
||||
}
|
||||
get name() {
|
||||
return 'Matroska';
|
||||
}
|
||||
get mimeType() {
|
||||
return 'video/x-matroska';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* WebM file format, based on Matroska.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link WEBM} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export class WebMInputFormat extends MatroskaInputFormat {
|
||||
/** @internal */
|
||||
_canReadInput(input) {
|
||||
return this.isSupportedEBMLOfDocType(input, 'webm');
|
||||
}
|
||||
get name() {
|
||||
return 'WebM';
|
||||
}
|
||||
get mimeType() {
|
||||
return 'video/webm';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* MP3 file format.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link MP3} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export class Mp3InputFormat extends InputFormat {
|
||||
/** @internal */
|
||||
async _canReadInput(input) {
|
||||
let currentPos = 0;
|
||||
while (true) {
|
||||
let slice = input._reader.requestSlice(currentPos, ID3_V2_HEADER_SIZE);
|
||||
if (slice instanceof Promise)
|
||||
slice = await slice;
|
||||
if (!slice)
|
||||
break;
|
||||
const id3V2Header = readId3V2Header(slice);
|
||||
if (!id3V2Header) {
|
||||
break;
|
||||
}
|
||||
currentPos = slice.filePos + id3V2Header.size;
|
||||
}
|
||||
const firstResult = await readNextMp3FrameHeader(input._reader, currentPos, currentPos + 4096);
|
||||
if (!firstResult) {
|
||||
return false;
|
||||
}
|
||||
const firstHeader = firstResult.header;
|
||||
const xingOffset = getXingOffset(firstHeader.mpegVersionId, firstHeader.channel);
|
||||
let slice = input._reader.requestSlice(firstResult.startPos + xingOffset, 4);
|
||||
if (slice instanceof Promise)
|
||||
slice = await slice;
|
||||
if (!slice)
|
||||
return false;
|
||||
const word = readU32Be(slice);
|
||||
const isXing = word === XING || word === INFO;
|
||||
if (isXing) {
|
||||
// Gotta be MP3
|
||||
return true;
|
||||
}
|
||||
currentPos = firstResult.startPos + firstResult.header.totalSize;
|
||||
// Fine, we found one frame header, but we're still not entirely sure this is MP3. Let's check if we can find
|
||||
// another header right after it:
|
||||
const secondResult = await readNextMp3FrameHeader(input._reader, currentPos, currentPos + FRAME_HEADER_SIZE);
|
||||
if (!secondResult) {
|
||||
return false;
|
||||
}
|
||||
const secondHeader = secondResult.header;
|
||||
// In a well-formed MP3 file, we'd expect these two frames to share some similarities:
|
||||
if (firstHeader.channel !== secondHeader.channel || firstHeader.sampleRate !== secondHeader.sampleRate) {
|
||||
return false;
|
||||
}
|
||||
// We have found two matching consecutive MP3 frames, a strong indicator that this is an MP3 file
|
||||
return true;
|
||||
}
|
||||
/** @internal */
|
||||
_createDemuxer(input) {
|
||||
return new Mp3Demuxer(input);
|
||||
}
|
||||
get name() {
|
||||
return 'MP3';
|
||||
}
|
||||
get mimeType() {
|
||||
return 'audio/mpeg';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* WAVE file format, based on RIFF.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link WAVE} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export class WaveInputFormat extends InputFormat {
|
||||
/** @internal */
|
||||
async _canReadInput(input) {
|
||||
let slice = input._reader.requestSlice(0, 12);
|
||||
if (slice instanceof Promise)
|
||||
slice = await slice;
|
||||
if (!slice)
|
||||
return false;
|
||||
const riffType = readAscii(slice, 4);
|
||||
if (riffType !== 'RIFF' && riffType !== 'RIFX' && riffType !== 'RF64') {
|
||||
return false;
|
||||
}
|
||||
slice.skip(4);
|
||||
const format = readAscii(slice, 4);
|
||||
return format === 'WAVE';
|
||||
}
|
||||
/** @internal */
|
||||
_createDemuxer(input) {
|
||||
return new WaveDemuxer(input);
|
||||
}
|
||||
get name() {
|
||||
return 'WAVE';
|
||||
}
|
||||
get mimeType() {
|
||||
return 'audio/wav';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Ogg file format.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link OGG} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export class OggInputFormat extends InputFormat {
|
||||
/** @internal */
|
||||
async _canReadInput(input) {
|
||||
let slice = input._reader.requestSlice(0, 4);
|
||||
if (slice instanceof Promise)
|
||||
slice = await slice;
|
||||
if (!slice)
|
||||
return false;
|
||||
return readAscii(slice, 4) === 'OggS';
|
||||
}
|
||||
/** @internal */
|
||||
_createDemuxer(input) {
|
||||
return new OggDemuxer(input);
|
||||
}
|
||||
get name() {
|
||||
return 'Ogg';
|
||||
}
|
||||
get mimeType() {
|
||||
return 'application/ogg';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* FLAC file format.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link FLAC} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export class FlacInputFormat extends InputFormat {
|
||||
/** @internal */
|
||||
async _canReadInput(input) {
|
||||
let slice = input._reader.requestSlice(0, 4);
|
||||
if (slice instanceof Promise)
|
||||
slice = await slice;
|
||||
if (!slice)
|
||||
return false;
|
||||
return readAscii(slice, 4) === 'fLaC';
|
||||
}
|
||||
get name() {
|
||||
return 'FLAC';
|
||||
}
|
||||
get mimeType() {
|
||||
return 'audio/flac';
|
||||
}
|
||||
/** @internal */
|
||||
_createDemuxer(input) {
|
||||
return new FlacDemuxer(input);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ADTS file format.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link ADTS} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export class AdtsInputFormat extends InputFormat {
|
||||
/** @internal */
|
||||
async _canReadInput(input) {
|
||||
let currentPos = 0;
|
||||
while (true) {
|
||||
let slice = input._reader.requestSlice(currentPos, ID3_V2_HEADER_SIZE);
|
||||
if (slice instanceof Promise)
|
||||
slice = await slice;
|
||||
if (!slice)
|
||||
break;
|
||||
const id3V2Header = readId3V2Header(slice);
|
||||
if (!id3V2Header) {
|
||||
break;
|
||||
}
|
||||
currentPos = slice.filePos + id3V2Header.size;
|
||||
}
|
||||
let slice = input._reader.requestSliceRange(currentPos, MIN_ADTS_FRAME_HEADER_SIZE, MAX_ADTS_FRAME_HEADER_SIZE);
|
||||
if (slice instanceof Promise)
|
||||
slice = await slice;
|
||||
if (!slice)
|
||||
return false;
|
||||
const firstHeader = readAdtsFrameHeader(slice);
|
||||
if (!firstHeader) {
|
||||
return false;
|
||||
}
|
||||
currentPos += firstHeader.frameLength;
|
||||
slice = input._reader.requestSliceRange(currentPos, MIN_ADTS_FRAME_HEADER_SIZE, MAX_ADTS_FRAME_HEADER_SIZE);
|
||||
if (slice instanceof Promise)
|
||||
slice = await slice;
|
||||
if (!slice)
|
||||
return false;
|
||||
const secondHeader = readAdtsFrameHeader(slice);
|
||||
if (!secondHeader) {
|
||||
return false;
|
||||
}
|
||||
return firstHeader.objectType === secondHeader.objectType
|
||||
&& firstHeader.samplingFrequencyIndex === secondHeader.samplingFrequencyIndex
|
||||
&& firstHeader.channelConfiguration === secondHeader.channelConfiguration;
|
||||
}
|
||||
/** @internal */
|
||||
_createDemuxer(input) {
|
||||
return new AdtsDemuxer(input);
|
||||
}
|
||||
get name() {
|
||||
return 'ADTS';
|
||||
}
|
||||
get mimeType() {
|
||||
return 'audio/aac';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* MPEG Transport Stream (MPEG-TS) file format.
|
||||
*
|
||||
* Do not instantiate this class; use the {@link MPEG_TS} singleton instead.
|
||||
*
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export class MpegTsInputFormat extends InputFormat {
|
||||
/** @internal */
|
||||
async _canReadInput(input) {
|
||||
const lengthToCheck = TS_PACKET_SIZE + 16 + 1;
|
||||
let slice = input._reader.requestSlice(0, lengthToCheck);
|
||||
if (slice instanceof Promise)
|
||||
slice = await slice;
|
||||
if (!slice)
|
||||
return false;
|
||||
const bytes = readBytes(slice, lengthToCheck);
|
||||
if (bytes[0] === 0x47 && bytes[TS_PACKET_SIZE] === 0x47) {
|
||||
// Regular MPEG-TS
|
||||
return true;
|
||||
}
|
||||
else if (bytes[0] === 0x47 && bytes[TS_PACKET_SIZE + 16] === 0x47) {
|
||||
// MPEG-TS with Forward Error Correction
|
||||
return true;
|
||||
}
|
||||
else if (bytes[4] === 0x47 && bytes[4 + TS_PACKET_SIZE] === 0x47) {
|
||||
// MPEG-2-TS (DVHS)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/** @internal */
|
||||
_createDemuxer(input) {
|
||||
return new MpegTsDemuxer(input);
|
||||
}
|
||||
get name() {
|
||||
return 'MPEG Transport Stream';
|
||||
}
|
||||
get mimeType() {
|
||||
return 'video/MP2T';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* MP4 input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export const MP4 = /* #__PURE__ */ new Mp4InputFormat();
|
||||
/**
|
||||
* QuickTime File Format input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export const QTFF = /* #__PURE__ */ new QuickTimeInputFormat();
|
||||
/**
|
||||
* Matroska input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export const MATROSKA = /* #__PURE__ */ new MatroskaInputFormat();
|
||||
/**
|
||||
* WebM input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export const WEBM = /* #__PURE__ */ new WebMInputFormat();
|
||||
/**
|
||||
* MP3 input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export const MP3 = /* #__PURE__ */ new Mp3InputFormat();
|
||||
/**
|
||||
* WAVE input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export const WAVE = /* #__PURE__ */ new WaveInputFormat();
|
||||
/**
|
||||
* Ogg input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export const OGG = /* #__PURE__ */ new OggInputFormat();
|
||||
/**
|
||||
* ADTS input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export const ADTS = /* #__PURE__ */ new AdtsInputFormat();
|
||||
/**
|
||||
* FLAC input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export const FLAC = /* #__PURE__ */ new FlacInputFormat();
|
||||
/**
|
||||
* MPEG-TS input format singleton.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export const MPEG_TS = /* #__PURE__ */ new MpegTsInputFormat();
|
||||
/**
|
||||
* List of all input format singletons. If you don't need to support all input formats, you should specify the
|
||||
* formats individually for better tree shaking.
|
||||
* @group Input formats
|
||||
* @public
|
||||
*/
|
||||
export const ALL_FORMATS = [MP4, QTFF, MATROSKA, WEBM, WAVE, OGG, FLAC, MP3, ADTS, MPEG_TS];
|
||||
Generated
Vendored
+194
@@ -0,0 +1,194 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { AudioCodec, MediaCodec, VideoCodec } from './codec.js';
|
||||
import { Input } from './input.js';
|
||||
import { PacketRetrievalOptions } from './media-sink.js';
|
||||
import { Rotation } from './misc.js';
|
||||
import { TrackType } from './output.js';
|
||||
import { EncodedPacket, PacketType } from './packet.js';
|
||||
import { TrackDisposition } from './metadata.js';
|
||||
/**
|
||||
* Contains aggregate statistics about the encoded packets of a track.
|
||||
* @group Input files & tracks
|
||||
* @public
|
||||
*/
|
||||
export type PacketStats = {
|
||||
/** The total number of packets. */
|
||||
packetCount: number;
|
||||
/** The average number of packets per second. For video tracks, this will equal the average frame rate (FPS). */
|
||||
averagePacketRate: number;
|
||||
/** The average number of bits per second. */
|
||||
averageBitrate: number;
|
||||
};
|
||||
export interface InputTrackBacking {
|
||||
getId(): number;
|
||||
getNumber(): number;
|
||||
getCodec(): MediaCodec | null;
|
||||
getInternalCodecId(): string | number | Uint8Array | null;
|
||||
getName(): string | null;
|
||||
getLanguageCode(): string;
|
||||
getTimeResolution(): number;
|
||||
getDisposition(): TrackDisposition;
|
||||
getFirstTimestamp(): Promise<number>;
|
||||
computeDuration(): Promise<number>;
|
||||
getFirstPacket(options: PacketRetrievalOptions): Promise<EncodedPacket | null>;
|
||||
getPacket(timestamp: number, options: PacketRetrievalOptions): Promise<EncodedPacket | null>;
|
||||
getNextPacket(packet: EncodedPacket, options: PacketRetrievalOptions): Promise<EncodedPacket | null>;
|
||||
getKeyPacket(timestamp: number, options: PacketRetrievalOptions): Promise<EncodedPacket | null>;
|
||||
getNextKeyPacket(packet: EncodedPacket, options: PacketRetrievalOptions): Promise<EncodedPacket | null>;
|
||||
}
|
||||
/**
|
||||
* Represents a media track in an input file.
|
||||
* @group Input files & tracks
|
||||
* @public
|
||||
*/
|
||||
export declare abstract class InputTrack {
|
||||
/** The input file this track belongs to. */
|
||||
readonly input: Input;
|
||||
/** The type of the track. */
|
||||
abstract get type(): TrackType;
|
||||
/** The codec of the track's packets. */
|
||||
abstract get codec(): MediaCodec | null;
|
||||
/** Returns the full codec parameter string for this track. */
|
||||
abstract getCodecParameterString(): Promise<string | null>;
|
||||
/** Checks if this track's packets can be decoded by the browser. */
|
||||
abstract canDecode(): Promise<boolean>;
|
||||
/**
|
||||
* For a given packet of this track, this method determines the actual type of this packet (key/delta) by looking
|
||||
* into its bitstream. Returns null if the type couldn't be determined.
|
||||
*/
|
||||
abstract determinePacketType(packet: EncodedPacket): Promise<PacketType | null>;
|
||||
/** Returns true if and only if this track is a video track. */
|
||||
isVideoTrack(): this is InputVideoTrack;
|
||||
/** Returns true if and only if this track is an audio track. */
|
||||
isAudioTrack(): this is InputAudioTrack;
|
||||
/** The unique ID of this track in the input file. */
|
||||
get id(): number;
|
||||
/**
|
||||
* The 1-based index of this track among all tracks of the same type in the input file. For example, the first
|
||||
* video track has number 1, the second video track has number 2, and so on. The index refers to the order in
|
||||
* which the tracks are returned by {@link Input.getTracks}.
|
||||
*/
|
||||
get number(): number;
|
||||
/**
|
||||
* The identifier of the codec used internally by the container. It is not homogenized by Mediabunny
|
||||
* and depends entirely on the container format.
|
||||
*
|
||||
* This field can be used to determine the codec of a track in case Mediabunny doesn't know that codec.
|
||||
*
|
||||
* - For ISOBMFF files, this field returns the name of the Sample Description Box (e.g. `'avc1'`).
|
||||
* - For Matroska files, this field returns the value of the `CodecID` element.
|
||||
* - For WAVE files, this field returns the value of the format tag in the `'fmt '` chunk.
|
||||
* - For ADTS files, this field contains the `MPEG-4 Audio Object Type`.
|
||||
* - For MPEG-TS files, this field contains the `streamType` value from the Program Map Table.
|
||||
* - In all other cases, this field is `null`.
|
||||
*/
|
||||
get internalCodecId(): string | number | Uint8Array<ArrayBufferLike> | null;
|
||||
/**
|
||||
* The ISO 639-2/T language code for this track. If the language is unknown, this field is `'und'` (undetermined).
|
||||
*/
|
||||
get languageCode(): string;
|
||||
/** A user-defined name for this track. */
|
||||
get name(): string | null;
|
||||
/**
|
||||
* A positive number x such that all timestamps and durations of all packets of this track are
|
||||
* integer multiples of 1/x.
|
||||
*/
|
||||
get timeResolution(): number;
|
||||
/** The track's disposition, i.e. information about its intended usage. */
|
||||
get disposition(): TrackDisposition;
|
||||
/**
|
||||
* Returns the start timestamp of the first packet of this track, in seconds. While often near zero, this value
|
||||
* may be positive or even negative. A negative starting timestamp means the track's timing has been offset. Samples
|
||||
* with a negative timestamp should not be presented.
|
||||
*/
|
||||
getFirstTimestamp(): Promise<number>;
|
||||
/** Returns the end timestamp of the last packet of this track, in seconds. */
|
||||
computeDuration(): Promise<number>;
|
||||
/**
|
||||
* Computes aggregate packet statistics for this track, such as average packet rate or bitrate.
|
||||
*
|
||||
* @param targetPacketCount - This optional parameter sets a target for how many packets this method must have
|
||||
* looked at before it can return early; this means, you can use it to aggregate only a subset (prefix) of all
|
||||
* packets. This is very useful for getting a great estimate of video frame rate without having to scan through the
|
||||
* entire file.
|
||||
*/
|
||||
computePacketStats(targetPacketCount?: number): Promise<PacketStats>;
|
||||
}
|
||||
export interface InputVideoTrackBacking extends InputTrackBacking {
|
||||
getCodec(): VideoCodec | null;
|
||||
getCodedWidth(): number;
|
||||
getCodedHeight(): number;
|
||||
getRotation(): Rotation;
|
||||
getColorSpace(): Promise<VideoColorSpaceInit>;
|
||||
canBeTransparent(): Promise<boolean>;
|
||||
getDecoderConfig(): Promise<VideoDecoderConfig | null>;
|
||||
}
|
||||
/**
|
||||
* Represents a video track in an input file.
|
||||
* @group Input files & tracks
|
||||
* @public
|
||||
*/
|
||||
export declare class InputVideoTrack extends InputTrack {
|
||||
get type(): TrackType;
|
||||
get codec(): VideoCodec | null;
|
||||
/** The width in pixels of the track's coded samples, before any transformations or rotations. */
|
||||
get codedWidth(): number;
|
||||
/** The height in pixels of the track's coded samples, before any transformations or rotations. */
|
||||
get codedHeight(): number;
|
||||
/** The angle in degrees by which the track's frames should be rotated (clockwise). */
|
||||
get rotation(): Rotation;
|
||||
/** The width in pixels of the track's frames after rotation. */
|
||||
get displayWidth(): number;
|
||||
/** The height in pixels of the track's frames after rotation. */
|
||||
get displayHeight(): number;
|
||||
/** Returns the color space of the track's samples. */
|
||||
getColorSpace(): Promise<VideoColorSpaceInit>;
|
||||
/** If this method returns true, the track's samples use a high dynamic range (HDR). */
|
||||
hasHighDynamicRange(): Promise<boolean>;
|
||||
/** Checks if this track may contain transparent samples with alpha data. */
|
||||
canBeTransparent(): Promise<boolean>;
|
||||
/**
|
||||
* Returns the [decoder configuration](https://www.w3.org/TR/webcodecs/#video-decoder-config) for decoding the
|
||||
* track's packets using a [`VideoDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/VideoDecoder). Returns
|
||||
* null if the track's codec is unknown.
|
||||
*/
|
||||
getDecoderConfig(): Promise<VideoDecoderConfig | null>;
|
||||
getCodecParameterString(): Promise<string | null>;
|
||||
canDecode(): Promise<boolean>;
|
||||
determinePacketType(packet: EncodedPacket): Promise<PacketType | null>;
|
||||
}
|
||||
export interface InputAudioTrackBacking extends InputTrackBacking {
|
||||
getCodec(): AudioCodec | null;
|
||||
getNumberOfChannels(): number;
|
||||
getSampleRate(): number;
|
||||
getDecoderConfig(): Promise<AudioDecoderConfig | null>;
|
||||
}
|
||||
/**
|
||||
* Represents an audio track in an input file.
|
||||
* @group Input files & tracks
|
||||
* @public
|
||||
*/
|
||||
export declare class InputAudioTrack extends InputTrack {
|
||||
get type(): TrackType;
|
||||
get codec(): AudioCodec | null;
|
||||
/** The number of audio channels in the track. */
|
||||
get numberOfChannels(): number;
|
||||
/** The track's audio sample rate in hertz. */
|
||||
get sampleRate(): number;
|
||||
/**
|
||||
* Returns the [decoder configuration](https://www.w3.org/TR/webcodecs/#audio-decoder-config) for decoding the
|
||||
* track's packets using an [`AudioDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/AudioDecoder). Returns
|
||||
* null if the track's codec is unknown.
|
||||
*/
|
||||
getDecoderConfig(): Promise<AudioDecoderConfig | null>;
|
||||
getCodecParameterString(): Promise<string | null>;
|
||||
canDecode(): Promise<boolean>;
|
||||
determinePacketType(packet: EncodedPacket): Promise<PacketType | null>;
|
||||
}
|
||||
//# sourceMappingURL=input-track.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"input-track.d.ts","sourceRoot":"","sources":["../../../src/input-track.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+305
@@ -0,0 +1,305 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { determineVideoPacketType } from './codec-data.js';
|
||||
import { customAudioDecoders, customVideoDecoders } from './custom-coder.js';
|
||||
import { EncodedPacketSink } from './media-sink.js';
|
||||
import { assert } from './misc.js';
|
||||
import { EncodedPacket } from './packet.js';
|
||||
/**
|
||||
* Represents a media track in an input file.
|
||||
* @group Input files & tracks
|
||||
* @public
|
||||
*/
|
||||
export class InputTrack {
|
||||
/** @internal */
|
||||
constructor(input, backing) {
|
||||
this.input = input;
|
||||
this._backing = backing;
|
||||
}
|
||||
/** Returns true if and only if this track is a video track. */
|
||||
isVideoTrack() {
|
||||
return this instanceof InputVideoTrack;
|
||||
}
|
||||
/** Returns true if and only if this track is an audio track. */
|
||||
isAudioTrack() {
|
||||
return this instanceof InputAudioTrack;
|
||||
}
|
||||
/** The unique ID of this track in the input file. */
|
||||
get id() {
|
||||
return this._backing.getId();
|
||||
}
|
||||
/**
|
||||
* The 1-based index of this track among all tracks of the same type in the input file. For example, the first
|
||||
* video track has number 1, the second video track has number 2, and so on. The index refers to the order in
|
||||
* which the tracks are returned by {@link Input.getTracks}.
|
||||
*/
|
||||
get number() {
|
||||
return this._backing.getNumber();
|
||||
}
|
||||
/**
|
||||
* The identifier of the codec used internally by the container. It is not homogenized by Mediabunny
|
||||
* and depends entirely on the container format.
|
||||
*
|
||||
* This field can be used to determine the codec of a track in case Mediabunny doesn't know that codec.
|
||||
*
|
||||
* - For ISOBMFF files, this field returns the name of the Sample Description Box (e.g. `'avc1'`).
|
||||
* - For Matroska files, this field returns the value of the `CodecID` element.
|
||||
* - For WAVE files, this field returns the value of the format tag in the `'fmt '` chunk.
|
||||
* - For ADTS files, this field contains the `MPEG-4 Audio Object Type`.
|
||||
* - For MPEG-TS files, this field contains the `streamType` value from the Program Map Table.
|
||||
* - In all other cases, this field is `null`.
|
||||
*/
|
||||
get internalCodecId() {
|
||||
return this._backing.getInternalCodecId();
|
||||
}
|
||||
/**
|
||||
* The ISO 639-2/T language code for this track. If the language is unknown, this field is `'und'` (undetermined).
|
||||
*/
|
||||
get languageCode() {
|
||||
return this._backing.getLanguageCode();
|
||||
}
|
||||
/** A user-defined name for this track. */
|
||||
get name() {
|
||||
return this._backing.getName();
|
||||
}
|
||||
/**
|
||||
* A positive number x such that all timestamps and durations of all packets of this track are
|
||||
* integer multiples of 1/x.
|
||||
*/
|
||||
get timeResolution() {
|
||||
return this._backing.getTimeResolution();
|
||||
}
|
||||
/** The track's disposition, i.e. information about its intended usage. */
|
||||
get disposition() {
|
||||
return this._backing.getDisposition();
|
||||
}
|
||||
/**
|
||||
* Returns the start timestamp of the first packet of this track, in seconds. While often near zero, this value
|
||||
* may be positive or even negative. A negative starting timestamp means the track's timing has been offset. Samples
|
||||
* with a negative timestamp should not be presented.
|
||||
*/
|
||||
getFirstTimestamp() {
|
||||
return this._backing.getFirstTimestamp();
|
||||
}
|
||||
/** Returns the end timestamp of the last packet of this track, in seconds. */
|
||||
computeDuration() {
|
||||
return this._backing.computeDuration();
|
||||
}
|
||||
/**
|
||||
* Computes aggregate packet statistics for this track, such as average packet rate or bitrate.
|
||||
*
|
||||
* @param targetPacketCount - This optional parameter sets a target for how many packets this method must have
|
||||
* looked at before it can return early; this means, you can use it to aggregate only a subset (prefix) of all
|
||||
* packets. This is very useful for getting a great estimate of video frame rate without having to scan through the
|
||||
* entire file.
|
||||
*/
|
||||
async computePacketStats(targetPacketCount = Infinity) {
|
||||
const sink = new EncodedPacketSink(this);
|
||||
let startTimestamp = Infinity;
|
||||
let endTimestamp = -Infinity;
|
||||
let packetCount = 0;
|
||||
let totalPacketBytes = 0;
|
||||
for await (const packet of sink.packets(undefined, undefined, { metadataOnly: true })) {
|
||||
if (packetCount >= targetPacketCount
|
||||
// This additional condition is needed to produce correct results with out-of-presentation-order packets
|
||||
&& packet.timestamp >= endTimestamp) {
|
||||
break;
|
||||
}
|
||||
startTimestamp = Math.min(startTimestamp, packet.timestamp);
|
||||
endTimestamp = Math.max(endTimestamp, packet.timestamp + packet.duration);
|
||||
packetCount++;
|
||||
totalPacketBytes += packet.byteLength;
|
||||
}
|
||||
return {
|
||||
packetCount,
|
||||
averagePacketRate: packetCount
|
||||
? Number((packetCount / (endTimestamp - startTimestamp)).toPrecision(16))
|
||||
: 0,
|
||||
averageBitrate: packetCount
|
||||
? Number((8 * totalPacketBytes / (endTimestamp - startTimestamp)).toPrecision(16))
|
||||
: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Represents a video track in an input file.
|
||||
* @group Input files & tracks
|
||||
* @public
|
||||
*/
|
||||
export class InputVideoTrack extends InputTrack {
|
||||
/** @internal */
|
||||
constructor(input, backing) {
|
||||
super(input, backing);
|
||||
this._backing = backing;
|
||||
}
|
||||
get type() {
|
||||
return 'video';
|
||||
}
|
||||
get codec() {
|
||||
return this._backing.getCodec();
|
||||
}
|
||||
/** The width in pixels of the track's coded samples, before any transformations or rotations. */
|
||||
get codedWidth() {
|
||||
return this._backing.getCodedWidth();
|
||||
}
|
||||
/** The height in pixels of the track's coded samples, before any transformations or rotations. */
|
||||
get codedHeight() {
|
||||
return this._backing.getCodedHeight();
|
||||
}
|
||||
/** The angle in degrees by which the track's frames should be rotated (clockwise). */
|
||||
get rotation() {
|
||||
return this._backing.getRotation();
|
||||
}
|
||||
/** The width in pixels of the track's frames after rotation. */
|
||||
get displayWidth() {
|
||||
const rotation = this._backing.getRotation();
|
||||
return rotation % 180 === 0 ? this._backing.getCodedWidth() : this._backing.getCodedHeight();
|
||||
}
|
||||
/** The height in pixels of the track's frames after rotation. */
|
||||
get displayHeight() {
|
||||
const rotation = this._backing.getRotation();
|
||||
return rotation % 180 === 0 ? this._backing.getCodedHeight() : this._backing.getCodedWidth();
|
||||
}
|
||||
/** Returns the color space of the track's samples. */
|
||||
getColorSpace() {
|
||||
return this._backing.getColorSpace();
|
||||
}
|
||||
/** If this method returns true, the track's samples use a high dynamic range (HDR). */
|
||||
async hasHighDynamicRange() {
|
||||
const colorSpace = await this._backing.getColorSpace();
|
||||
return colorSpace.primaries === 'bt2020' || colorSpace.primaries === 'smpte432'
|
||||
|| colorSpace.transfer === 'pg' || colorSpace.transfer === 'hlg'
|
||||
|| colorSpace.matrix === 'bt2020-ncl';
|
||||
}
|
||||
/** Checks if this track may contain transparent samples with alpha data. */
|
||||
canBeTransparent() {
|
||||
return this._backing.canBeTransparent();
|
||||
}
|
||||
/**
|
||||
* Returns the [decoder configuration](https://www.w3.org/TR/webcodecs/#video-decoder-config) for decoding the
|
||||
* track's packets using a [`VideoDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/VideoDecoder). Returns
|
||||
* null if the track's codec is unknown.
|
||||
*/
|
||||
getDecoderConfig() {
|
||||
return this._backing.getDecoderConfig();
|
||||
}
|
||||
async getCodecParameterString() {
|
||||
const decoderConfig = await this._backing.getDecoderConfig();
|
||||
return decoderConfig?.codec ?? null;
|
||||
}
|
||||
async canDecode() {
|
||||
try {
|
||||
const decoderConfig = await this._backing.getDecoderConfig();
|
||||
if (!decoderConfig) {
|
||||
return false;
|
||||
}
|
||||
const codec = this._backing.getCodec();
|
||||
assert(codec !== null);
|
||||
if (customVideoDecoders.some(x => x.supports(codec, decoderConfig))) {
|
||||
return true;
|
||||
}
|
||||
if (typeof VideoDecoder === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
const support = await VideoDecoder.isConfigSupported(decoderConfig);
|
||||
return support.supported === true;
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error during decodability check:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
async determinePacketType(packet) {
|
||||
if (!(packet instanceof EncodedPacket)) {
|
||||
throw new TypeError('packet must be an EncodedPacket.');
|
||||
}
|
||||
if (packet.isMetadataOnly) {
|
||||
throw new TypeError('packet must not be metadata-only to determine its type.');
|
||||
}
|
||||
if (this.codec === null) {
|
||||
return null;
|
||||
}
|
||||
const decoderConfig = await this.getDecoderConfig();
|
||||
assert(decoderConfig);
|
||||
return determineVideoPacketType(this.codec, decoderConfig, packet.data);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Represents an audio track in an input file.
|
||||
* @group Input files & tracks
|
||||
* @public
|
||||
*/
|
||||
export class InputAudioTrack extends InputTrack {
|
||||
/** @internal */
|
||||
constructor(input, backing) {
|
||||
super(input, backing);
|
||||
this._backing = backing;
|
||||
}
|
||||
get type() {
|
||||
return 'audio';
|
||||
}
|
||||
get codec() {
|
||||
return this._backing.getCodec();
|
||||
}
|
||||
/** The number of audio channels in the track. */
|
||||
get numberOfChannels() {
|
||||
return this._backing.getNumberOfChannels();
|
||||
}
|
||||
/** The track's audio sample rate in hertz. */
|
||||
get sampleRate() {
|
||||
return this._backing.getSampleRate();
|
||||
}
|
||||
/**
|
||||
* Returns the [decoder configuration](https://www.w3.org/TR/webcodecs/#audio-decoder-config) for decoding the
|
||||
* track's packets using an [`AudioDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/AudioDecoder). Returns
|
||||
* null if the track's codec is unknown.
|
||||
*/
|
||||
getDecoderConfig() {
|
||||
return this._backing.getDecoderConfig();
|
||||
}
|
||||
async getCodecParameterString() {
|
||||
const decoderConfig = await this._backing.getDecoderConfig();
|
||||
return decoderConfig?.codec ?? null;
|
||||
}
|
||||
async canDecode() {
|
||||
try {
|
||||
const decoderConfig = await this._backing.getDecoderConfig();
|
||||
if (!decoderConfig) {
|
||||
return false;
|
||||
}
|
||||
const codec = this._backing.getCodec();
|
||||
assert(codec !== null);
|
||||
if (customAudioDecoders.some(x => x.supports(codec, decoderConfig))) {
|
||||
return true;
|
||||
}
|
||||
if (decoderConfig.codec.startsWith('pcm-')) {
|
||||
return true; // Since we decode it ourselves
|
||||
}
|
||||
else {
|
||||
if (typeof AudioDecoder === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
const support = await AudioDecoder.isConfigSupported(decoderConfig);
|
||||
return support.supported === true;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error during decodability check:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
async determinePacketType(packet) {
|
||||
if (!(packet instanceof EncodedPacket)) {
|
||||
throw new TypeError('packet must be an EncodedPacket.');
|
||||
}
|
||||
if (this.codec === null) {
|
||||
return null;
|
||||
}
|
||||
return 'key'; // No audio codec with delta packets
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { InputFormat } from './input-format.js';
|
||||
import { Source } from './source.js';
|
||||
/**
|
||||
* The options for creating an Input object.
|
||||
* @group Input files & tracks
|
||||
* @public
|
||||
*/
|
||||
export type InputOptions<S extends Source = Source> = {
|
||||
/** A list of supported formats. If the source file is not of one of these formats, then it cannot be read. */
|
||||
formats: InputFormat[];
|
||||
/** The source from which data will be read. */
|
||||
source: S;
|
||||
};
|
||||
/**
|
||||
* Represents an input media file. This is the root object from which all media read operations start.
|
||||
* @group Input files & tracks
|
||||
* @public
|
||||
*/
|
||||
export declare class Input<S extends Source = Source> implements Disposable {
|
||||
/** True if the input has been disposed. */
|
||||
get disposed(): boolean;
|
||||
/**
|
||||
* Creates a new input file from the specified options. No reading operations will be performed until methods are
|
||||
* called on this instance.
|
||||
*/
|
||||
constructor(options: InputOptions<S>);
|
||||
/**
|
||||
* Returns the source from which this input file reads its data. This is the same source that was passed to the
|
||||
* constructor.
|
||||
*/
|
||||
get source(): S;
|
||||
/**
|
||||
* Returns the format of the input file. You can compare this result directly to the {@link InputFormat} singletons
|
||||
* or use `instanceof` checks for subset-aware logic (for example, `format instanceof MatroskaInputFormat` is true
|
||||
* for both MKV and WebM).
|
||||
*/
|
||||
getFormat(): Promise<InputFormat>;
|
||||
/**
|
||||
* Computes the duration of the input file, in seconds. More precisely, returns the largest end timestamp among
|
||||
* all tracks.
|
||||
*/
|
||||
computeDuration(): Promise<number>;
|
||||
/**
|
||||
* Returns the timestamp at which the input file starts. More precisely, returns the smallest starting timestamp
|
||||
* among all tracks.
|
||||
*/
|
||||
getFirstTimestamp(): Promise<number>;
|
||||
/** Returns the list of all tracks of this input file. */
|
||||
getTracks(): Promise<import("./input-track.js").InputTrack[]>;
|
||||
/** Returns the list of all video tracks of this input file. */
|
||||
getVideoTracks(): Promise<import("./input-track.js").InputVideoTrack[]>;
|
||||
/** Returns the list of all audio tracks of this input file. */
|
||||
getAudioTracks(): Promise<import("./input-track.js").InputAudioTrack[]>;
|
||||
/** Returns the primary video track of this input file, or null if there are no video tracks. */
|
||||
getPrimaryVideoTrack(): Promise<import("./input-track.js").InputVideoTrack | null>;
|
||||
/** Returns the primary audio track of this input file, or null if there are no audio tracks. */
|
||||
getPrimaryAudioTrack(): Promise<import("./input-track.js").InputAudioTrack | null>;
|
||||
/** Returns the full MIME type of this input file, including track codecs. */
|
||||
getMimeType(): Promise<string>;
|
||||
/**
|
||||
* Returns descriptive metadata tags about the media file, such as title, author, date, cover art, or other
|
||||
* attached files.
|
||||
*/
|
||||
getMetadataTags(): Promise<import("./metadata.js").MetadataTags>;
|
||||
/**
|
||||
* Disposes this input and frees connected resources. When an input is disposed, ongoing read operations will be
|
||||
* canceled, all future read operations will fail, any open decoders will be closed, and all ongoing media sink
|
||||
* operations will be canceled. Disallowed and canceled operations will throw an {@link InputDisposedError}.
|
||||
*
|
||||
* You are expected not to use an input after disposing it. While some operations may still work, it is not
|
||||
* specified and may change in any future update.
|
||||
*/
|
||||
dispose(): void;
|
||||
/**
|
||||
* Calls `.dispose()` on the input, implementing the `Disposable` interface for use with
|
||||
* JavaScript Explicit Resource Management features.
|
||||
*/
|
||||
[Symbol.dispose](): void;
|
||||
}
|
||||
/**
|
||||
* Thrown when an operation was prevented because the corresponding {@link Input} has been disposed.
|
||||
* @group Input files & tracks
|
||||
* @public
|
||||
*/
|
||||
export declare class InputDisposedError extends Error {
|
||||
/** Creates a new {@link InputDisposedError}. */
|
||||
constructor(message?: string);
|
||||
}
|
||||
//# sourceMappingURL=input.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../src/input.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAG7C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAIlC;;;;GAIG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI;IACrD,8GAA8G;IAC9G,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,+CAA+C;IAC/C,MAAM,EAAE,CAAC,CAAC;CACV,CAAC;AAEF;;;;GAIG;AACH,qBAAa,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,YAAW,UAAU;IAclE,2CAA2C;IAC3C,IAAI,QAAQ,YAEX;IAED;;;OAGG;gBACS,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;IAoCpC;;;OAGG;IACH,IAAI,MAAM,MAET;IAED;;;;OAIG;IACG,SAAS;IAMf;;;OAGG;IACG,eAAe;IAKrB;;;OAGG;IACG,iBAAiB;IAUvB,yDAAyD;IACnD,SAAS;IAKf,+DAA+D;IACzD,cAAc;IAKpB,+DAA+D;IACzD,cAAc;IAKpB,gGAAgG;IAC1F,oBAAoB;IAK1B,gGAAgG;IAC1F,oBAAoB;IAK1B,6EAA6E;IACvE,WAAW;IAKjB;;;OAGG;IACG,eAAe;IAKrB;;;;;;;OAOG;IACH,OAAO;IAWP;;;OAGG;IACH,CAAC,MAAM,CAAC,OAAO,CAAC;CAGhB;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC5C,gDAAgD;gBACpC,OAAO,SAA6B;CAIhD"}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { InputFormat } from './input-format.js';
|
||||
import { assert, polyfillSymbolDispose } from './misc.js';
|
||||
import { Reader } from './reader.js';
|
||||
import { Source } from './source.js';
|
||||
polyfillSymbolDispose();
|
||||
/**
|
||||
* Represents an input media file. This is the root object from which all media read operations start.
|
||||
* @group Input files & tracks
|
||||
* @public
|
||||
*/
|
||||
export class Input {
|
||||
/** True if the input has been disposed. */
|
||||
get disposed() {
|
||||
return this._disposed;
|
||||
}
|
||||
/**
|
||||
* Creates a new input file from the specified options. No reading operations will be performed until methods are
|
||||
* called on this instance.
|
||||
*/
|
||||
constructor(options) {
|
||||
/** @internal */
|
||||
this._demuxerPromise = null;
|
||||
/** @internal */
|
||||
this._format = null;
|
||||
/** @internal */
|
||||
this._disposed = false;
|
||||
if (!options || typeof options !== 'object') {
|
||||
throw new TypeError('options must be an object.');
|
||||
}
|
||||
if (!Array.isArray(options.formats) || options.formats.some(x => !(x instanceof InputFormat))) {
|
||||
throw new TypeError('options.formats must be an array of InputFormat.');
|
||||
}
|
||||
if (!(options.source instanceof Source)) {
|
||||
throw new TypeError('options.source must be a Source.');
|
||||
}
|
||||
if (options.source._disposed) {
|
||||
throw new Error('options.source must not be disposed.');
|
||||
}
|
||||
this._formats = options.formats;
|
||||
this._source = options.source;
|
||||
this._reader = new Reader(options.source);
|
||||
}
|
||||
/** @internal */
|
||||
_getDemuxer() {
|
||||
return this._demuxerPromise ??= (async () => {
|
||||
this._reader.fileSize = await this._source.getSizeOrNull();
|
||||
for (const format of this._formats) {
|
||||
const canRead = await format._canReadInput(this);
|
||||
if (canRead) {
|
||||
this._format = format;
|
||||
return format._createDemuxer(this);
|
||||
}
|
||||
}
|
||||
throw new Error('Input has an unsupported or unrecognizable format.');
|
||||
})();
|
||||
}
|
||||
/**
|
||||
* Returns the source from which this input file reads its data. This is the same source that was passed to the
|
||||
* constructor.
|
||||
*/
|
||||
get source() {
|
||||
return this._source;
|
||||
}
|
||||
/**
|
||||
* Returns the format of the input file. You can compare this result directly to the {@link InputFormat} singletons
|
||||
* or use `instanceof` checks for subset-aware logic (for example, `format instanceof MatroskaInputFormat` is true
|
||||
* for both MKV and WebM).
|
||||
*/
|
||||
async getFormat() {
|
||||
await this._getDemuxer();
|
||||
assert(this._format);
|
||||
return this._format;
|
||||
}
|
||||
/**
|
||||
* Computes the duration of the input file, in seconds. More precisely, returns the largest end timestamp among
|
||||
* all tracks.
|
||||
*/
|
||||
async computeDuration() {
|
||||
const demuxer = await this._getDemuxer();
|
||||
return demuxer.computeDuration();
|
||||
}
|
||||
/**
|
||||
* Returns the timestamp at which the input file starts. More precisely, returns the smallest starting timestamp
|
||||
* among all tracks.
|
||||
*/
|
||||
async getFirstTimestamp() {
|
||||
const tracks = await this.getTracks();
|
||||
if (tracks.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const firstTimestamps = await Promise.all(tracks.map(x => x.getFirstTimestamp()));
|
||||
return Math.min(...firstTimestamps);
|
||||
}
|
||||
/** Returns the list of all tracks of this input file. */
|
||||
async getTracks() {
|
||||
const demuxer = await this._getDemuxer();
|
||||
return demuxer.getTracks();
|
||||
}
|
||||
/** Returns the list of all video tracks of this input file. */
|
||||
async getVideoTracks() {
|
||||
const tracks = await this.getTracks();
|
||||
return tracks.filter(x => x.isVideoTrack());
|
||||
}
|
||||
/** Returns the list of all audio tracks of this input file. */
|
||||
async getAudioTracks() {
|
||||
const tracks = await this.getTracks();
|
||||
return tracks.filter(x => x.isAudioTrack());
|
||||
}
|
||||
/** Returns the primary video track of this input file, or null if there are no video tracks. */
|
||||
async getPrimaryVideoTrack() {
|
||||
const tracks = await this.getTracks();
|
||||
return tracks.find(x => x.isVideoTrack()) ?? null;
|
||||
}
|
||||
/** Returns the primary audio track of this input file, or null if there are no audio tracks. */
|
||||
async getPrimaryAudioTrack() {
|
||||
const tracks = await this.getTracks();
|
||||
return tracks.find(x => x.isAudioTrack()) ?? null;
|
||||
}
|
||||
/** Returns the full MIME type of this input file, including track codecs. */
|
||||
async getMimeType() {
|
||||
const demuxer = await this._getDemuxer();
|
||||
return demuxer.getMimeType();
|
||||
}
|
||||
/**
|
||||
* Returns descriptive metadata tags about the media file, such as title, author, date, cover art, or other
|
||||
* attached files.
|
||||
*/
|
||||
async getMetadataTags() {
|
||||
const demuxer = await this._getDemuxer();
|
||||
return demuxer.getMetadataTags();
|
||||
}
|
||||
/**
|
||||
* Disposes this input and frees connected resources. When an input is disposed, ongoing read operations will be
|
||||
* canceled, all future read operations will fail, any open decoders will be closed, and all ongoing media sink
|
||||
* operations will be canceled. Disallowed and canceled operations will throw an {@link InputDisposedError}.
|
||||
*
|
||||
* You are expected not to use an input after disposing it. While some operations may still work, it is not
|
||||
* specified and may change in any future update.
|
||||
*/
|
||||
dispose() {
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
this._disposed = true;
|
||||
this._source._disposed = true;
|
||||
this._source._dispose();
|
||||
}
|
||||
/**
|
||||
* Calls `.dispose()` on the input, implementing the `Disposable` interface for use with
|
||||
* JavaScript Explicit Resource Management features.
|
||||
*/
|
||||
[Symbol.dispose]() {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Thrown when an operation was prevented because the corresponding {@link Input} has been disposed.
|
||||
* @group Input files & tracks
|
||||
* @public
|
||||
*/
|
||||
export class InputDisposedError extends Error {
|
||||
/** Creates a new {@link InputDisposedError}. */
|
||||
constructor(message = 'Input has been disposed.') {
|
||||
super(message);
|
||||
this.name = 'InputDisposedError';
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+204
@@ -0,0 +1,204 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { Writer } from '../writer.js';
|
||||
import { IsobmffAudioTrackData, IsobmffMuxer, IsobmffSubtitleTrackData, IsobmffTrackData, IsobmffVideoTrackData } from './isobmff-muxer.js';
|
||||
export declare class IsobmffBoxWriter {
|
||||
private writer;
|
||||
private helper;
|
||||
private helperView;
|
||||
/**
|
||||
* Stores the position from the start of the file to where boxes elements have been written. This is used to
|
||||
* rewrite/edit elements that were already added before, and to measure sizes of things.
|
||||
*/
|
||||
offsets: WeakMap<Box, number>;
|
||||
constructor(writer: Writer);
|
||||
writeU32(value: number): void;
|
||||
writeU64(value: number): void;
|
||||
writeAscii(text: string): void;
|
||||
writeBox(box: Box): void;
|
||||
writeBoxHeader(box: Box, size: number): void;
|
||||
measureBoxHeader(box: Box): number;
|
||||
patchBox(box: Box): void;
|
||||
measureBox(box: Box): number;
|
||||
}
|
||||
export interface Box {
|
||||
type: string;
|
||||
contents?: Uint8Array;
|
||||
children?: (Box | null)[];
|
||||
size?: number;
|
||||
largeSize?: boolean;
|
||||
}
|
||||
type NestedNumberArray = (number | NestedNumberArray)[];
|
||||
export declare const box: (type: string, contents?: NestedNumberArray, children?: (Box | null)[]) => Box;
|
||||
/** A FullBox always starts with a version byte, followed by three flag bytes. */
|
||||
export declare const fullBox: (type: string, version: number, flags: number, contents?: NestedNumberArray, children?: Box[]) => Box;
|
||||
/**
|
||||
* File Type Compatibility Box: Allows the reader to determine whether this is a type of file that the
|
||||
* reader understands.
|
||||
*/
|
||||
export declare const ftyp: (details: {
|
||||
isQuickTime: boolean;
|
||||
holdsAvc: boolean;
|
||||
fragmented: boolean;
|
||||
}) => Box;
|
||||
/** Movie Sample Data Box. Contains the actual frames/samples of the media. */
|
||||
export declare const mdat: (reserveLargeSize: boolean) => Box;
|
||||
/** Free Space Box: A box that designates unused space in the movie data file. */
|
||||
export declare const free: (size: number) => Box;
|
||||
/**
|
||||
* Movie Box: Used to specify the information that defines a movie - that is, the information that allows
|
||||
* an application to interpret the sample data that is stored elsewhere.
|
||||
*/
|
||||
export declare const moov: (muxer: IsobmffMuxer) => Box;
|
||||
/** Movie Header Box: Used to specify the characteristics of the entire movie, such as timescale and duration. */
|
||||
export declare const mvhd: (creationTime: number, trackDatas: IsobmffTrackData[]) => Box;
|
||||
/**
|
||||
* Track Box: Defines a single track of a movie. A movie may consist of one or more tracks. Each track is
|
||||
* independent of the other tracks in the movie and carries its own temporal and spatial information. Each Track Box
|
||||
* contains its associated Media Box.
|
||||
*/
|
||||
export declare const trak: (trackData: IsobmffTrackData, creationTime: number) => Box;
|
||||
/** Track Header Box: Specifies the characteristics of a single track within a movie. */
|
||||
export declare const tkhd: (trackData: IsobmffTrackData, creationTime: number) => Box;
|
||||
/** Media Box: Describes and define a track's media type and sample data. */
|
||||
export declare const mdia: (trackData: IsobmffTrackData, creationTime: number) => Box;
|
||||
/** Media Header Box: Specifies the characteristics of a media, including timescale and duration. */
|
||||
export declare const mdhd: (trackData: IsobmffTrackData, creationTime: number) => Box;
|
||||
/** Handler Reference Box. */
|
||||
export declare const hdlr: (hasComponentType: boolean, handlerType: string, name: string, manufacturer?: string) => Box;
|
||||
/**
|
||||
* Media Information Box: Stores handler-specific information for a track's media data. The media handler uses this
|
||||
* information to map from media time to media data and to process the media data.
|
||||
*/
|
||||
export declare const minf: (trackData: IsobmffTrackData) => Box;
|
||||
/** Video Media Information Header Box: Defines specific color and graphics mode information. */
|
||||
export declare const vmhd: () => Box;
|
||||
/** Sound Media Information Header Box: Stores the sound media's control information, such as balance. */
|
||||
export declare const smhd: () => Box;
|
||||
/** Null Media Header Box. */
|
||||
export declare const nmhd: () => Box;
|
||||
/**
|
||||
* Data Information Box: Contains information specifying the data handler component that provides access to the
|
||||
* media data. The data handler component uses the Data Information Box to interpret the media's data.
|
||||
*/
|
||||
export declare const dinf: () => Box;
|
||||
/**
|
||||
* Data Reference Box: Contains tabular data that instructs the data handler component how to access the media's data.
|
||||
*/
|
||||
export declare const dref: () => Box;
|
||||
export declare const url: () => Box;
|
||||
/**
|
||||
* Sample Table Box: Contains information for converting from media time to sample number to sample location. This box
|
||||
* also indicates how to interpret the sample (for example, whether to decompress the video data and, if so, how).
|
||||
*/
|
||||
export declare const stbl: (trackData: IsobmffTrackData) => Box;
|
||||
/**
|
||||
* Sample Description Box: Stores information that allows you to decode samples in the media. The data stored in the
|
||||
* sample description varies, depending on the media type.
|
||||
*/
|
||||
export declare const stsd: (trackData: IsobmffTrackData) => Box;
|
||||
/** Video Sample Description Box: Contains information that defines how to interpret video media data. */
|
||||
export declare const videoSampleDescription: (compressionType: string, trackData: IsobmffVideoTrackData) => Box;
|
||||
/** Colour Information Box: Specifies the color space of the video. */
|
||||
export declare const colr: (trackData: IsobmffVideoTrackData) => Box;
|
||||
/** AVC Configuration Box: Provides additional information to the decoder. */
|
||||
export declare const avcC: (trackData: IsobmffVideoTrackData) => Box;
|
||||
/** HEVC Configuration Box: Provides additional information to the decoder. */
|
||||
export declare const hvcC: (trackData: IsobmffVideoTrackData) => Box;
|
||||
/** VP Configuration Box: Provides additional information to the decoder. */
|
||||
export declare const vpcC: (trackData: IsobmffVideoTrackData) => Box | null;
|
||||
/** AV1 Configuration Box: Provides additional information to the decoder. */
|
||||
export declare const av1C: (trackData: IsobmffVideoTrackData) => Box;
|
||||
/** Sound Sample Description Box: Contains information that defines how to interpret sound media data. */
|
||||
export declare const soundSampleDescription: (compressionType: string, trackData: IsobmffAudioTrackData) => Box;
|
||||
/** MPEG-4 Elementary Stream Descriptor Box. */
|
||||
export declare const esds: (trackData: IsobmffAudioTrackData) => Box;
|
||||
export declare const wave: (trackData: IsobmffAudioTrackData) => Box;
|
||||
export declare const frma: (trackData: IsobmffAudioTrackData) => Box;
|
||||
export declare const enda: (trackData: IsobmffAudioTrackData) => Box;
|
||||
/** Opus Specific Box. */
|
||||
export declare const dOps: (trackData: IsobmffAudioTrackData) => Box;
|
||||
/** FLAC specific box. */
|
||||
export declare const dfLa: (trackData: IsobmffAudioTrackData) => Box;
|
||||
export declare const subtitleSampleDescription: (compressionType: string, trackData: IsobmffSubtitleTrackData) => Box;
|
||||
export declare const vttC: (trackData: IsobmffSubtitleTrackData) => Box;
|
||||
export declare const txtC: (textConfig: Uint8Array) => Box;
|
||||
/**
|
||||
* Time-To-Sample Box: Stores duration information for a media's samples, providing a mapping from a time in a media
|
||||
* to the corresponding data sample. The table is compact, meaning that consecutive samples with the same time delta
|
||||
* will be grouped.
|
||||
*/
|
||||
export declare const stts: (trackData: IsobmffTrackData) => Box;
|
||||
/** Sync Sample Box: Identifies the key frames in the media, marking the random access points within a stream. */
|
||||
export declare const stss: (trackData: IsobmffTrackData) => Box | null;
|
||||
/**
|
||||
* Sample-To-Chunk Box: As samples are added to a media, they are collected into chunks that allow optimized data
|
||||
* access. A chunk contains one or more samples. Chunks in a media may have different sizes, and the samples within a
|
||||
* chunk may have different sizes. The Sample-To-Chunk Box stores chunk information for the samples in a media, stored
|
||||
* in a compactly-coded fashion.
|
||||
*/
|
||||
export declare const stsc: (trackData: IsobmffTrackData) => Box;
|
||||
/** Sample Size Box: Specifies the byte size of each sample in the media. */
|
||||
export declare const stsz: (trackData: IsobmffTrackData) => Box;
|
||||
/** Chunk Offset Box: Identifies the location of each chunk of data in the media's data stream, relative to the file. */
|
||||
export declare const stco: (trackData: IsobmffTrackData) => Box;
|
||||
/**
|
||||
* Composition Time to Sample Box: Stores composition time offset information (PTS-DTS) for a
|
||||
* media's samples. The table is compact, meaning that consecutive samples with the same time
|
||||
* composition time offset will be grouped.
|
||||
*/
|
||||
export declare const ctts: (trackData: IsobmffTrackData) => Box;
|
||||
/**
|
||||
* Composition to Decode Box: Stores information about the composition and display times of the media samples.
|
||||
*/
|
||||
export declare const cslg: (trackData: IsobmffTrackData) => Box | null;
|
||||
/**
|
||||
* Movie Extends Box: This box signals to readers that the file is fragmented. Contains a single Track Extends Box
|
||||
* for each track in the movie.
|
||||
*/
|
||||
export declare const mvex: (trackDatas: IsobmffTrackData[]) => Box;
|
||||
/** Track Extends Box: Contains the default values used by the movie fragments. */
|
||||
export declare const trex: (trackData: IsobmffTrackData) => Box;
|
||||
/**
|
||||
* Movie Fragment Box: The movie fragments extend the presentation in time. They provide the information that would
|
||||
* previously have been in the Movie Box.
|
||||
*/
|
||||
export declare const moof: (sequenceNumber: number, trackDatas: IsobmffTrackData[]) => Box;
|
||||
/** Movie Fragment Header Box: Contains a sequence number as a safety check. */
|
||||
export declare const mfhd: (sequenceNumber: number) => Box;
|
||||
/** Track Fragment Box */
|
||||
export declare const traf: (trackData: IsobmffTrackData) => Box;
|
||||
/** Track Fragment Header Box: Provides a reference to the extended track, and flags. */
|
||||
export declare const tfhd: (trackData: IsobmffTrackData) => Box;
|
||||
/**
|
||||
* Track Fragment Decode Time Box: Provides the absolute decode time of the first sample of the fragment. This is
|
||||
* useful for performing random access on the media file.
|
||||
*/
|
||||
export declare const tfdt: (trackData: IsobmffTrackData) => Box;
|
||||
/** Track Run Box: Specifies a run of contiguous samples for a given track. */
|
||||
export declare const trun: (trackData: IsobmffTrackData) => Box;
|
||||
/**
|
||||
* Movie Fragment Random Access Box: For each track, provides pointers to sync samples within the file
|
||||
* for random access.
|
||||
*/
|
||||
export declare const mfra: (trackDatas: IsobmffTrackData[]) => Box;
|
||||
/** Track Fragment Random Access Box: Provides pointers to sync samples within the file for random access. */
|
||||
export declare const tfra: (trackData: IsobmffTrackData, trackIndex: number) => Box;
|
||||
/**
|
||||
* Movie Fragment Random Access Offset Box: Provides the size of the enclosing mfra box. This box can be used by readers
|
||||
* to quickly locate the mfra box by searching from the end of the file.
|
||||
*/
|
||||
export declare const mfro: () => Box;
|
||||
/** VTT Empty Cue Box */
|
||||
export declare const vtte: () => Box;
|
||||
/** VTT Cue Box */
|
||||
export declare const vttc: (payload: string, timestamp: number | null, identifier: string | null, settings: string | null, sourceId: number | null) => Box;
|
||||
/** VTT Additional Text Box */
|
||||
export declare const vtta: (notes: string) => Box;
|
||||
export {};
|
||||
//# sourceMappingURL=isobmff-boxes.d.ts.map
|
||||
skills/remotion-prompt-video/node_modules/mediabunny/dist/modules/src/isobmff/isobmff-boxes.d.ts.map
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"isobmff-boxes.d.ts","sourceRoot":"","sources":["../../../../src/isobmff/isobmff-boxes.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+1530
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+189
@@ -0,0 +1,189 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { AacCodecInfo, AudioCodec, VideoCodec } from '../codec.js';
|
||||
import { Av1CodecInfo, AvcDecoderConfigurationRecord, HevcDecoderConfigurationRecord, Vp9CodecInfo } from '../codec-data.js';
|
||||
import { Demuxer } from '../demuxer.js';
|
||||
import { Input } from '../input.js';
|
||||
import { InputTrack } from '../input-track.js';
|
||||
import { Rotation } from '../misc.js';
|
||||
import { FileSlice, Reader } from '../reader.js';
|
||||
import { MetadataTags, TrackDisposition } from '../metadata.js';
|
||||
type InternalTrack = {
|
||||
id: number;
|
||||
demuxer: IsobmffDemuxer;
|
||||
inputTrack: InputTrack | null;
|
||||
disposition: TrackDisposition;
|
||||
timescale: number;
|
||||
durationInMovieTimescale: number;
|
||||
durationInMediaTimescale: number;
|
||||
rotation: Rotation;
|
||||
internalCodecId: string | null;
|
||||
name: string | null;
|
||||
languageCode: string;
|
||||
sampleTableByteOffset: number;
|
||||
sampleTable: SampleTable | null;
|
||||
fragmentLookupTable: FragmentLookupTableEntry[];
|
||||
currentFragmentState: FragmentTrackState | null;
|
||||
/**
|
||||
* List of all encountered fragment offsets alongside their timestamps. This list never gets truncated, but memory
|
||||
* consumption should be negligible.
|
||||
*/
|
||||
fragmentPositionCache: {
|
||||
moofOffset: number;
|
||||
startTimestamp: number;
|
||||
endTimestamp: number;
|
||||
}[];
|
||||
/** The segment durations of all edit list entries leading up to the main one (from which the offset is taken.) */
|
||||
editListPreviousSegmentDurations: number;
|
||||
/** The media time offset of the main edit list entry (with media time !== -1) */
|
||||
editListOffset: number;
|
||||
} & ({
|
||||
info: null;
|
||||
} | {
|
||||
info: {
|
||||
type: 'video';
|
||||
width: number;
|
||||
height: number;
|
||||
codec: VideoCodec | null;
|
||||
codecDescription: Uint8Array | null;
|
||||
colorSpace: VideoColorSpaceInit | null;
|
||||
avcType: 1 | 3 | null;
|
||||
avcCodecInfo: AvcDecoderConfigurationRecord | null;
|
||||
hevcCodecInfo: HevcDecoderConfigurationRecord | null;
|
||||
vp9CodecInfo: Vp9CodecInfo | null;
|
||||
av1CodecInfo: Av1CodecInfo | null;
|
||||
};
|
||||
} | {
|
||||
info: {
|
||||
type: 'audio';
|
||||
numberOfChannels: number;
|
||||
sampleRate: number;
|
||||
codec: AudioCodec | null;
|
||||
codecDescription: Uint8Array | null;
|
||||
aacCodecInfo: AacCodecInfo | null;
|
||||
};
|
||||
});
|
||||
type SampleTable = {
|
||||
sampleTimingEntries: SampleTimingEntry[];
|
||||
sampleCompositionTimeOffsets: SampleCompositionTimeOffsetEntry[];
|
||||
sampleSizes: number[];
|
||||
keySampleIndices: number[] | null;
|
||||
chunkOffsets: number[];
|
||||
sampleToChunk: SampleToChunkEntry[];
|
||||
presentationTimestamps: {
|
||||
presentationTimestamp: number;
|
||||
sampleIndex: number;
|
||||
}[] | null;
|
||||
/**
|
||||
* Provides a fast map from sample index to index in the sorted presentation timestamps array - so, a fast map from
|
||||
* decode order to presentation order.
|
||||
*/
|
||||
presentationTimestampIndexMap: number[] | null;
|
||||
};
|
||||
type SampleTimingEntry = {
|
||||
startIndex: number;
|
||||
startDecodeTimestamp: number;
|
||||
count: number;
|
||||
delta: number;
|
||||
};
|
||||
type SampleCompositionTimeOffsetEntry = {
|
||||
startIndex: number;
|
||||
count: number;
|
||||
offset: number;
|
||||
};
|
||||
type SampleToChunkEntry = {
|
||||
startSampleIndex: number;
|
||||
startChunkIndex: number;
|
||||
samplesPerChunk: number;
|
||||
sampleDescriptionIndex: number;
|
||||
};
|
||||
type FragmentTrackDefaults = {
|
||||
trackId: number;
|
||||
defaultSampleDescriptionIndex: number;
|
||||
defaultSampleDuration: number;
|
||||
defaultSampleSize: number;
|
||||
defaultSampleFlags: number;
|
||||
};
|
||||
type FragmentLookupTableEntry = {
|
||||
timestamp: number;
|
||||
moofOffset: number;
|
||||
};
|
||||
type FragmentTrackState = {
|
||||
baseDataOffset: number;
|
||||
sampleDescriptionIndex: number | null;
|
||||
defaultSampleDuration: number | null;
|
||||
defaultSampleSize: number | null;
|
||||
defaultSampleFlags: number | null;
|
||||
startTimestamp: number | null;
|
||||
};
|
||||
type FragmentTrackData = {
|
||||
track: InternalTrack;
|
||||
startTimestamp: number;
|
||||
endTimestamp: number;
|
||||
firstKeyFrameTimestamp: number | null;
|
||||
samples: FragmentTrackSample[];
|
||||
presentationTimestamps: {
|
||||
presentationTimestamp: number;
|
||||
sampleIndex: number;
|
||||
}[];
|
||||
startTimestampIsFinal: boolean;
|
||||
};
|
||||
type FragmentTrackSample = {
|
||||
presentationTimestamp: number;
|
||||
duration: number;
|
||||
byteOffset: number;
|
||||
byteSize: number;
|
||||
isKeyFrame: boolean;
|
||||
};
|
||||
type Fragment = {
|
||||
moofOffset: number;
|
||||
moofSize: number;
|
||||
implicitBaseDataOffset: number;
|
||||
trackData: Map<InternalTrack['id'], FragmentTrackData>;
|
||||
};
|
||||
export declare class IsobmffDemuxer extends Demuxer {
|
||||
reader: Reader;
|
||||
moovSlice: FileSlice | null;
|
||||
currentTrack: InternalTrack | null;
|
||||
tracks: InternalTrack[];
|
||||
metadataPromise: Promise<void> | null;
|
||||
movieTimescale: number;
|
||||
movieDurationInTimescale: number;
|
||||
isQuickTime: boolean;
|
||||
metadataTags: MetadataTags;
|
||||
currentMetadataKeys: Map<number, string> | null;
|
||||
isFragmented: boolean;
|
||||
fragmentTrackDefaults: FragmentTrackDefaults[];
|
||||
currentFragment: Fragment | null;
|
||||
/**
|
||||
* Caches the last fragment that was read. Based on the assumption that there will be multiple reads to the
|
||||
* same fragment in quick succession.
|
||||
*/
|
||||
lastReadFragment: Fragment | null;
|
||||
constructor(input: Input);
|
||||
computeDuration(): Promise<number>;
|
||||
getTracks(): Promise<InputTrack[]>;
|
||||
getMimeType(): Promise<string>;
|
||||
getMetadataTags(): Promise<MetadataTags>;
|
||||
readMetadata(): Promise<void>;
|
||||
getSampleTableForTrack(internalTrack: InternalTrack): SampleTable;
|
||||
readFragment(startPos: number): Promise<Fragment>;
|
||||
readContiguousBoxes(slice: FileSlice): void;
|
||||
iterateContiguousBoxes(slice: FileSlice): Generator<{
|
||||
boxInfo: {
|
||||
name: string;
|
||||
totalSize: number;
|
||||
headerSize: number;
|
||||
contentSize: number;
|
||||
};
|
||||
slice: FileSlice;
|
||||
}, void, unknown>;
|
||||
traverseBox(slice: FileSlice): boolean;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=isobmff-demuxer.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"isobmff-demuxer.d.ts","sourceRoot":"","sources":["../../../../src/isobmff/isobmff-demuxer.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+2594
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
export declare const buildIsobmffMimeType: (info: {
|
||||
isQuickTime: boolean;
|
||||
hasVideo: boolean;
|
||||
hasAudio: boolean;
|
||||
codecStrings: string[];
|
||||
}) => string;
|
||||
//# sourceMappingURL=isobmff-misc.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"isobmff-misc.d.ts","sourceRoot":"","sources":["../../../../src/isobmff/isobmff-misc.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,oBAAoB,GAAI,MAAM;IAC1C,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;CACvB,WAeA,CAAC"}
|
||||
Generated
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
export const buildIsobmffMimeType = (info) => {
|
||||
const base = info.hasVideo
|
||||
? 'video/'
|
||||
: info.hasAudio
|
||||
? 'audio/'
|
||||
: 'application/';
|
||||
let string = base + (info.isQuickTime ? 'quicktime' : 'mp4');
|
||||
if (info.codecStrings.length > 0) {
|
||||
const uniqueCodecMimeTypes = [...new Set(info.codecStrings)];
|
||||
string += `; codecs="${uniqueCodecMimeTypes.join(', ')}"`;
|
||||
}
|
||||
return string;
|
||||
};
|
||||
Generated
Vendored
+155
@@ -0,0 +1,155 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { Muxer } from '../muxer.js';
|
||||
import { Output, OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from '../output.js';
|
||||
import { IsobmffOutputFormat } from '../output-format.js';
|
||||
import { SubtitleConfig, SubtitleCue, SubtitleMetadata } from '../subtitles.js';
|
||||
import { EncodedPacket, PacketType } from '../packet.js';
|
||||
export declare const GLOBAL_TIMESCALE = 1000;
|
||||
export type Sample = {
|
||||
timestamp: number;
|
||||
decodeTimestamp: number;
|
||||
duration: number;
|
||||
data: Uint8Array | null;
|
||||
size: number;
|
||||
type: PacketType;
|
||||
timescaleUnitsToNextSample: number;
|
||||
};
|
||||
type Chunk = {
|
||||
/** The lowest presentation timestamp in this chunk */
|
||||
startTimestamp: number;
|
||||
samples: Sample[];
|
||||
offset: number | null;
|
||||
moofOffset: number | null;
|
||||
};
|
||||
export type IsobmffTrackData = {
|
||||
muxer: IsobmffMuxer;
|
||||
timescale: number;
|
||||
samples: Sample[];
|
||||
sampleQueue: Sample[];
|
||||
timestampProcessingQueue: Sample[];
|
||||
timeToSampleTable: {
|
||||
sampleCount: number;
|
||||
sampleDelta: number;
|
||||
}[];
|
||||
compositionTimeOffsetTable: {
|
||||
sampleCount: number;
|
||||
sampleCompositionTimeOffset: number;
|
||||
}[];
|
||||
lastTimescaleUnits: number | null;
|
||||
lastSample: Sample | null;
|
||||
finalizedChunks: Chunk[];
|
||||
currentChunk: Chunk | null;
|
||||
compactlyCodedChunkTable: {
|
||||
firstChunk: number;
|
||||
samplesPerChunk: number;
|
||||
}[];
|
||||
} & ({
|
||||
track: OutputVideoTrack;
|
||||
type: 'video';
|
||||
info: {
|
||||
width: number;
|
||||
height: number;
|
||||
decoderConfig: VideoDecoderConfig;
|
||||
/**
|
||||
* The "Annex B transformation" involves converting the raw packet data from Annex B to
|
||||
* "MP4" (length-prefixed) format.
|
||||
* https://stackoverflow.com/questions/24884827
|
||||
*/
|
||||
requiresAnnexBTransformation: boolean;
|
||||
};
|
||||
} | {
|
||||
track: OutputAudioTrack;
|
||||
type: 'audio';
|
||||
info: {
|
||||
numberOfChannels: number;
|
||||
sampleRate: number;
|
||||
decoderConfig: AudioDecoderConfig;
|
||||
/**
|
||||
* The "PCM transformation" is making every sample in the sample table be exactly one PCM audio sample long.
|
||||
* Some players expect this for PCM audio.
|
||||
*/
|
||||
requiresPcmTransformation: boolean;
|
||||
/**
|
||||
* The "ADTS stripping" involves removing the ADTS header from each AAC packet. SOBMFF stores raw AAC data, not
|
||||
* ADTS-wrapped data.
|
||||
*/
|
||||
requiresAdtsStripping: boolean;
|
||||
firstPacket: EncodedPacket;
|
||||
};
|
||||
} | {
|
||||
track: OutputSubtitleTrack;
|
||||
type: 'subtitle';
|
||||
info: {
|
||||
config: SubtitleConfig;
|
||||
};
|
||||
lastCueEndTimestamp: number;
|
||||
cueQueue: SubtitleCue[];
|
||||
nextSourceId: number;
|
||||
cueToSourceId: WeakMap<SubtitleCue, number>;
|
||||
});
|
||||
export type IsobmffVideoTrackData = IsobmffTrackData & {
|
||||
type: 'video';
|
||||
};
|
||||
export type IsobmffAudioTrackData = IsobmffTrackData & {
|
||||
type: 'audio';
|
||||
};
|
||||
export type IsobmffSubtitleTrackData = IsobmffTrackData & {
|
||||
type: 'subtitle';
|
||||
};
|
||||
export type IsobmffMetadata = {
|
||||
name?: string;
|
||||
};
|
||||
export declare const getTrackMetadata: (trackData: IsobmffTrackData) => IsobmffMetadata;
|
||||
export declare const intoTimescale: (timeInSeconds: number, timescale: number, round?: boolean) => number;
|
||||
export declare class IsobmffMuxer extends Muxer {
|
||||
format: IsobmffOutputFormat;
|
||||
private writer;
|
||||
private boxWriter;
|
||||
private fastStart;
|
||||
isFragmented: boolean;
|
||||
isQuickTime: boolean;
|
||||
private auxTarget;
|
||||
private auxWriter;
|
||||
private auxBoxWriter;
|
||||
private mdat;
|
||||
private ftypSize;
|
||||
trackDatas: IsobmffTrackData[];
|
||||
private allTracksKnown;
|
||||
creationTime: number;
|
||||
private finalizedChunks;
|
||||
private nextFragmentNumber;
|
||||
private maxWrittenTimestamp;
|
||||
private minimumFragmentDuration;
|
||||
constructor(output: Output, format: IsobmffOutputFormat);
|
||||
start(): Promise<void>;
|
||||
private allTracksAreKnown;
|
||||
getMimeType(): Promise<string>;
|
||||
private getVideoTrackData;
|
||||
private getAudioTrackData;
|
||||
private getSubtitleTrackData;
|
||||
addEncodedVideoPacket(track: OutputVideoTrack, packet: EncodedPacket, meta?: EncodedVideoChunkMetadata): Promise<void>;
|
||||
addEncodedAudioPacket(track: OutputAudioTrack, packet: EncodedPacket, meta?: EncodedAudioChunkMetadata): Promise<void>;
|
||||
private maybePadWithSilence;
|
||||
addSubtitleCue(track: OutputSubtitleTrack, cue: SubtitleCue, meta?: SubtitleMetadata): Promise<void>;
|
||||
private processWebVTTCues;
|
||||
private createSampleForTrack;
|
||||
private processTimestamps;
|
||||
private registerSample;
|
||||
private addSampleToTrack;
|
||||
private finalizeCurrentChunk;
|
||||
private interleaveSamples;
|
||||
private finalizeFragment;
|
||||
private registerSampleFastStartReserve;
|
||||
private computeSampleTableSizeUpperBound;
|
||||
onTrackClose(track: OutputTrack): Promise<void>;
|
||||
/** Finalizes the file, making it ready for use. Must be called after all video and audio chunks have been added. */
|
||||
finalize(): Promise<void>;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=isobmff-muxer.d.ts.map
|
||||
skills/remotion-prompt-video/node_modules/mediabunny/dist/modules/src/isobmff/isobmff-muxer.d.ts.map
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"isobmff-muxer.d.ts","sourceRoot":"","sources":["../../../../src/isobmff/isobmff-muxer.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+1004
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { RichImageData } from '../metadata.js';
|
||||
import { FileSlice } from '../reader.js';
|
||||
export declare const MIN_BOX_HEADER_SIZE = 8;
|
||||
export declare const MAX_BOX_HEADER_SIZE = 16;
|
||||
export declare const readBoxHeader: (slice: FileSlice) => {
|
||||
name: string;
|
||||
totalSize: number;
|
||||
headerSize: number;
|
||||
contentSize: number;
|
||||
} | null;
|
||||
export declare const readFixed_16_16: (slice: FileSlice) => number;
|
||||
export declare const readFixed_2_30: (slice: FileSlice) => number;
|
||||
export declare const readIsomVariableInteger: (slice: FileSlice) => number;
|
||||
export declare const readMetadataStringShort: (slice: FileSlice) => string;
|
||||
export declare const readDataBox: (slice: FileSlice) => string | Uint8Array<ArrayBufferLike> | RichImageData | null;
|
||||
//# sourceMappingURL=isobmff-reader.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"isobmff-reader.d.ts","sourceRoot":"","sources":["../../../../src/isobmff/isobmff-reader.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,SAAS,EAA4E,MAAM,WAAW,CAAC;AAEhH,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,eAAO,MAAM,aAAa,GAAI,OAAO,SAAS;;;;;QAiB7C,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,OAAO,SAAS,WAE/C,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,OAAO,SAAS,WAE9C,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,OAAO,SAAS,WAcvD,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,OAAO,SAAS,WAMvD,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,OAAO,SAAS,gEAwB3C,CAAC"}
|
||||
Generated
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { RichImageData } from '../metadata.js';
|
||||
import { textDecoder } from '../misc.js';
|
||||
import { readAscii, readBytes, readI32Be, readU16Be, readU32Be, readU64Be, readU8 } from '../reader.js';
|
||||
export const MIN_BOX_HEADER_SIZE = 8;
|
||||
export const MAX_BOX_HEADER_SIZE = 16;
|
||||
export const readBoxHeader = (slice) => {
|
||||
let totalSize = readU32Be(slice);
|
||||
const name = readAscii(slice, 4);
|
||||
let headerSize = 8;
|
||||
const hasLargeSize = totalSize === 1;
|
||||
if (hasLargeSize) {
|
||||
totalSize = readU64Be(slice);
|
||||
headerSize = 16;
|
||||
}
|
||||
const contentSize = totalSize - headerSize;
|
||||
if (contentSize < 0) {
|
||||
return null; // Hardly a box is it
|
||||
}
|
||||
return { name, totalSize, headerSize, contentSize };
|
||||
};
|
||||
export const readFixed_16_16 = (slice) => {
|
||||
return readI32Be(slice) / 0x10000;
|
||||
};
|
||||
export const readFixed_2_30 = (slice) => {
|
||||
return readI32Be(slice) / 0x40000000;
|
||||
};
|
||||
export const readIsomVariableInteger = (slice) => {
|
||||
let result = 0;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
result <<= 7;
|
||||
const nextByte = readU8(slice);
|
||||
result |= nextByte & 0x7f;
|
||||
if ((nextByte & 0x80) === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
export const readMetadataStringShort = (slice) => {
|
||||
let stringLength = readU16Be(slice);
|
||||
slice.skip(2); // Language
|
||||
stringLength = Math.min(stringLength, slice.remainingLength);
|
||||
return textDecoder.decode(readBytes(slice, stringLength));
|
||||
};
|
||||
export const readDataBox = (slice) => {
|
||||
const header = readBoxHeader(slice);
|
||||
if (!header || header.name !== 'data') {
|
||||
return null;
|
||||
}
|
||||
if (slice.remainingLength < 8) {
|
||||
// Box is too small
|
||||
return null;
|
||||
}
|
||||
const typeIndicator = readU32Be(slice);
|
||||
slice.skip(4); // Locale indicator
|
||||
const data = readBytes(slice, header.contentSize - 8);
|
||||
switch (typeIndicator) {
|
||||
case 1: return textDecoder.decode(data); // UTF-8
|
||||
case 2: return new TextDecoder('utf-16be').decode(data); // UTF-16-BE
|
||||
case 13: return new RichImageData(data, 'image/jpeg');
|
||||
case 14: return new RichImageData(data, 'image/png');
|
||||
case 27: return new RichImageData(data, 'image/bmp');
|
||||
default: return data;
|
||||
}
|
||||
};
|
||||
Generated
Vendored
+198
@@ -0,0 +1,198 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { MediaCodec } from '../codec.js';
|
||||
import { FileSlice, Reader } from '../reader.js';
|
||||
import { Writer } from '../writer.js';
|
||||
export interface EBMLElement {
|
||||
id: number;
|
||||
size?: number;
|
||||
data: number | bigint | string | Uint8Array | EBMLFloat32 | EBMLFloat64 | EBMLSignedInt | EBMLUnicodeString | (EBML | null)[];
|
||||
}
|
||||
export type EBML = EBMLElement | Uint8Array | (EBML | null)[];
|
||||
/** Wrapper around a number to be able to differentiate it in the writer. */
|
||||
export declare class EBMLFloat32 {
|
||||
value: number;
|
||||
constructor(value: number);
|
||||
}
|
||||
/** Wrapper around a number to be able to differentiate it in the writer. */
|
||||
export declare class EBMLFloat64 {
|
||||
value: number;
|
||||
constructor(value: number);
|
||||
}
|
||||
/** Wrapper around a number to be able to differentiate it in the writer. */
|
||||
export declare class EBMLSignedInt {
|
||||
value: number;
|
||||
constructor(value: number);
|
||||
}
|
||||
export declare class EBMLUnicodeString {
|
||||
value: string;
|
||||
constructor(value: string);
|
||||
}
|
||||
/** Defines some of the EBML IDs used by Matroska files. */
|
||||
export declare enum EBMLId {
|
||||
EBML = 440786851,
|
||||
EBMLVersion = 17030,
|
||||
EBMLReadVersion = 17143,
|
||||
EBMLMaxIDLength = 17138,
|
||||
EBMLMaxSizeLength = 17139,
|
||||
DocType = 17026,
|
||||
DocTypeVersion = 17031,
|
||||
DocTypeReadVersion = 17029,
|
||||
Void = 236,
|
||||
Segment = 408125543,
|
||||
SeekHead = 290298740,
|
||||
Seek = 19899,
|
||||
SeekID = 21419,
|
||||
SeekPosition = 21420,
|
||||
Duration = 17545,
|
||||
Info = 357149030,
|
||||
TimestampScale = 2807729,
|
||||
MuxingApp = 19840,
|
||||
WritingApp = 22337,
|
||||
Tracks = 374648427,
|
||||
TrackEntry = 174,
|
||||
TrackNumber = 215,
|
||||
TrackUID = 29637,
|
||||
TrackType = 131,
|
||||
FlagEnabled = 185,
|
||||
FlagDefault = 136,
|
||||
FlagForced = 21930,
|
||||
FlagOriginal = 21934,
|
||||
FlagHearingImpaired = 21931,
|
||||
FlagVisualImpaired = 21932,
|
||||
FlagCommentary = 21935,
|
||||
FlagLacing = 156,
|
||||
Name = 21358,
|
||||
Language = 2274716,
|
||||
LanguageBCP47 = 2274717,
|
||||
CodecID = 134,
|
||||
CodecPrivate = 25506,
|
||||
CodecDelay = 22186,
|
||||
SeekPreRoll = 22203,
|
||||
DefaultDuration = 2352003,
|
||||
Video = 224,
|
||||
PixelWidth = 176,
|
||||
PixelHeight = 186,
|
||||
AlphaMode = 21440,
|
||||
Audio = 225,
|
||||
SamplingFrequency = 181,
|
||||
Channels = 159,
|
||||
BitDepth = 25188,
|
||||
SimpleBlock = 163,
|
||||
BlockGroup = 160,
|
||||
Block = 161,
|
||||
BlockAdditions = 30113,
|
||||
BlockMore = 166,
|
||||
BlockAdditional = 165,
|
||||
BlockAddID = 238,
|
||||
BlockDuration = 155,
|
||||
ReferenceBlock = 251,
|
||||
Cluster = 524531317,
|
||||
Timestamp = 231,
|
||||
Cues = 475249515,
|
||||
CuePoint = 187,
|
||||
CueTime = 179,
|
||||
CueTrackPositions = 183,
|
||||
CueTrack = 247,
|
||||
CueClusterPosition = 241,
|
||||
Colour = 21936,
|
||||
MatrixCoefficients = 21937,
|
||||
TransferCharacteristics = 21946,
|
||||
Primaries = 21947,
|
||||
Range = 21945,
|
||||
Projection = 30320,
|
||||
ProjectionType = 30321,
|
||||
ProjectionPoseRoll = 30325,
|
||||
Attachments = 423732329,
|
||||
AttachedFile = 24999,
|
||||
FileDescription = 18046,
|
||||
FileName = 18030,
|
||||
FileMediaType = 18016,
|
||||
FileData = 18012,
|
||||
FileUID = 18094,
|
||||
Chapters = 272869232,
|
||||
Tags = 307544935,
|
||||
Tag = 29555,
|
||||
Targets = 25536,
|
||||
TargetTypeValue = 26826,
|
||||
TargetType = 25546,
|
||||
TagTrackUID = 25541,
|
||||
TagEditionUID = 25545,
|
||||
TagChapterUID = 25540,
|
||||
TagAttachmentUID = 25542,
|
||||
SimpleTag = 26568,
|
||||
TagName = 17827,
|
||||
TagLanguage = 17530,
|
||||
TagString = 17543,
|
||||
TagBinary = 17541,
|
||||
ContentEncodings = 28032,
|
||||
ContentEncoding = 25152,
|
||||
ContentEncodingOrder = 20529,
|
||||
ContentEncodingScope = 20530,
|
||||
ContentCompression = 20532,
|
||||
ContentCompAlgo = 16980,
|
||||
ContentCompSettings = 16981,
|
||||
ContentEncryption = 20533
|
||||
}
|
||||
export declare const LEVEL_0_EBML_IDS: EBMLId[];
|
||||
export declare const LEVEL_1_EBML_IDS: EBMLId[];
|
||||
export declare const LEVEL_0_AND_1_EBML_IDS: EBMLId[];
|
||||
export declare const measureUnsignedInt: (value: number) => 1 | 5 | 6 | 2 | 4 | 3;
|
||||
export declare const measureUnsignedBigInt: (value: bigint) => 8 | 7 | 1 | 5 | 6 | 2 | 4 | 3;
|
||||
export declare const measureSignedInt: (value: number) => 1 | 5 | 6 | 2 | 4 | 3;
|
||||
export declare const measureVarInt: (value: number) => 1 | 5 | 6 | 2 | 4 | 3;
|
||||
export declare class EBMLWriter {
|
||||
private writer;
|
||||
helper: Uint8Array<ArrayBuffer>;
|
||||
helperView: DataView<ArrayBuffer>;
|
||||
/**
|
||||
* Stores the position from the start of the file to where EBML elements have been written. This is used to
|
||||
* rewrite/edit elements that were already added before, and to measure sizes of things.
|
||||
*/
|
||||
offsets: WeakMap<EBML, number>;
|
||||
/** Same as offsets, but stores position where the element's data starts (after ID and size fields). */
|
||||
dataOffsets: WeakMap<EBML, number>;
|
||||
constructor(writer: Writer);
|
||||
writeByte(value: number): void;
|
||||
writeFloat32(value: number): void;
|
||||
writeFloat64(value: number): void;
|
||||
writeUnsignedInt(value: number, width?: number): void;
|
||||
writeUnsignedBigInt(value: bigint, width?: number): void;
|
||||
writeSignedInt(value: number, width?: number): void;
|
||||
writeVarInt(value: number, width?: number): void;
|
||||
writeAsciiString(str: string): void;
|
||||
writeEBML(data: EBML | null): void;
|
||||
}
|
||||
export declare const MAX_VAR_INT_SIZE = 8;
|
||||
export declare const MIN_HEADER_SIZE = 2;
|
||||
export declare const MAX_HEADER_SIZE: number;
|
||||
export declare const readVarIntSize: (slice: FileSlice) => number | null;
|
||||
export declare const readVarInt: (slice: FileSlice) => number | null;
|
||||
export declare const readUnsignedInt: (slice: FileSlice, width: number) => number;
|
||||
export declare const readUnsignedBigInt: (slice: FileSlice, width: number) => bigint;
|
||||
export declare const readSignedInt: (slice: FileSlice, width: number) => number;
|
||||
export declare const readElementId: (slice: FileSlice) => number | null;
|
||||
/** Returns `undefined` to indicate the EBML undefined size. Returns `null` if the size couldn't be read. */
|
||||
export declare const readElementSize: (slice: FileSlice) => number | undefined | null;
|
||||
export declare const readElementHeader: (slice: FileSlice) => {
|
||||
id: number;
|
||||
size: number | undefined;
|
||||
} | null;
|
||||
export declare const readAsciiString: (slice: FileSlice, length: number) => string;
|
||||
export declare const readUnicodeString: (slice: FileSlice, length: number) => string;
|
||||
export declare const readFloat: (slice: FileSlice, width: number) => number;
|
||||
/** Returns the byte offset in the file of the next element with a matching ID. */
|
||||
export declare const searchForNextElementId: (reader: Reader, startPos: number, ids: EBMLId[], until: number | null) => Promise<{
|
||||
pos: number;
|
||||
found: boolean;
|
||||
}>;
|
||||
/** Searches for the next occurrence of an element ID using a naive byte-wise search. */
|
||||
export declare const resync: (reader: Reader, startPos: number, ids: EBMLId[], until: number) => Promise<number | null>;
|
||||
export declare const CODEC_STRING_MAP: Partial<Record<MediaCodec, string>>;
|
||||
export declare function assertDefinedSize(size: number | undefined): asserts size is number;
|
||||
//# sourceMappingURL=ebml.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ebml.d.ts","sourceRoot":"","sources":["../../../../src/matroska/ebml.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+679
@@ -0,0 +1,679 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { assert, assertNever, textDecoder, textEncoder } from '../misc.js';
|
||||
import { readBytes, readF32Be, readF64Be, readU8 } from '../reader.js';
|
||||
/** Wrapper around a number to be able to differentiate it in the writer. */
|
||||
export class EBMLFloat32 {
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
/** Wrapper around a number to be able to differentiate it in the writer. */
|
||||
export class EBMLFloat64 {
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
/** Wrapper around a number to be able to differentiate it in the writer. */
|
||||
export class EBMLSignedInt {
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
export class EBMLUnicodeString {
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
/** Defines some of the EBML IDs used by Matroska files. */
|
||||
export var EBMLId;
|
||||
(function (EBMLId) {
|
||||
EBMLId[EBMLId["EBML"] = 440786851] = "EBML";
|
||||
EBMLId[EBMLId["EBMLVersion"] = 17030] = "EBMLVersion";
|
||||
EBMLId[EBMLId["EBMLReadVersion"] = 17143] = "EBMLReadVersion";
|
||||
EBMLId[EBMLId["EBMLMaxIDLength"] = 17138] = "EBMLMaxIDLength";
|
||||
EBMLId[EBMLId["EBMLMaxSizeLength"] = 17139] = "EBMLMaxSizeLength";
|
||||
EBMLId[EBMLId["DocType"] = 17026] = "DocType";
|
||||
EBMLId[EBMLId["DocTypeVersion"] = 17031] = "DocTypeVersion";
|
||||
EBMLId[EBMLId["DocTypeReadVersion"] = 17029] = "DocTypeReadVersion";
|
||||
EBMLId[EBMLId["Void"] = 236] = "Void";
|
||||
EBMLId[EBMLId["Segment"] = 408125543] = "Segment";
|
||||
EBMLId[EBMLId["SeekHead"] = 290298740] = "SeekHead";
|
||||
EBMLId[EBMLId["Seek"] = 19899] = "Seek";
|
||||
EBMLId[EBMLId["SeekID"] = 21419] = "SeekID";
|
||||
EBMLId[EBMLId["SeekPosition"] = 21420] = "SeekPosition";
|
||||
EBMLId[EBMLId["Duration"] = 17545] = "Duration";
|
||||
EBMLId[EBMLId["Info"] = 357149030] = "Info";
|
||||
EBMLId[EBMLId["TimestampScale"] = 2807729] = "TimestampScale";
|
||||
EBMLId[EBMLId["MuxingApp"] = 19840] = "MuxingApp";
|
||||
EBMLId[EBMLId["WritingApp"] = 22337] = "WritingApp";
|
||||
EBMLId[EBMLId["Tracks"] = 374648427] = "Tracks";
|
||||
EBMLId[EBMLId["TrackEntry"] = 174] = "TrackEntry";
|
||||
EBMLId[EBMLId["TrackNumber"] = 215] = "TrackNumber";
|
||||
EBMLId[EBMLId["TrackUID"] = 29637] = "TrackUID";
|
||||
EBMLId[EBMLId["TrackType"] = 131] = "TrackType";
|
||||
EBMLId[EBMLId["FlagEnabled"] = 185] = "FlagEnabled";
|
||||
EBMLId[EBMLId["FlagDefault"] = 136] = "FlagDefault";
|
||||
EBMLId[EBMLId["FlagForced"] = 21930] = "FlagForced";
|
||||
EBMLId[EBMLId["FlagOriginal"] = 21934] = "FlagOriginal";
|
||||
EBMLId[EBMLId["FlagHearingImpaired"] = 21931] = "FlagHearingImpaired";
|
||||
EBMLId[EBMLId["FlagVisualImpaired"] = 21932] = "FlagVisualImpaired";
|
||||
EBMLId[EBMLId["FlagCommentary"] = 21935] = "FlagCommentary";
|
||||
EBMLId[EBMLId["FlagLacing"] = 156] = "FlagLacing";
|
||||
EBMLId[EBMLId["Name"] = 21358] = "Name";
|
||||
EBMLId[EBMLId["Language"] = 2274716] = "Language";
|
||||
EBMLId[EBMLId["LanguageBCP47"] = 2274717] = "LanguageBCP47";
|
||||
EBMLId[EBMLId["CodecID"] = 134] = "CodecID";
|
||||
EBMLId[EBMLId["CodecPrivate"] = 25506] = "CodecPrivate";
|
||||
EBMLId[EBMLId["CodecDelay"] = 22186] = "CodecDelay";
|
||||
EBMLId[EBMLId["SeekPreRoll"] = 22203] = "SeekPreRoll";
|
||||
EBMLId[EBMLId["DefaultDuration"] = 2352003] = "DefaultDuration";
|
||||
EBMLId[EBMLId["Video"] = 224] = "Video";
|
||||
EBMLId[EBMLId["PixelWidth"] = 176] = "PixelWidth";
|
||||
EBMLId[EBMLId["PixelHeight"] = 186] = "PixelHeight";
|
||||
EBMLId[EBMLId["AlphaMode"] = 21440] = "AlphaMode";
|
||||
EBMLId[EBMLId["Audio"] = 225] = "Audio";
|
||||
EBMLId[EBMLId["SamplingFrequency"] = 181] = "SamplingFrequency";
|
||||
EBMLId[EBMLId["Channels"] = 159] = "Channels";
|
||||
EBMLId[EBMLId["BitDepth"] = 25188] = "BitDepth";
|
||||
EBMLId[EBMLId["SimpleBlock"] = 163] = "SimpleBlock";
|
||||
EBMLId[EBMLId["BlockGroup"] = 160] = "BlockGroup";
|
||||
EBMLId[EBMLId["Block"] = 161] = "Block";
|
||||
EBMLId[EBMLId["BlockAdditions"] = 30113] = "BlockAdditions";
|
||||
EBMLId[EBMLId["BlockMore"] = 166] = "BlockMore";
|
||||
EBMLId[EBMLId["BlockAdditional"] = 165] = "BlockAdditional";
|
||||
EBMLId[EBMLId["BlockAddID"] = 238] = "BlockAddID";
|
||||
EBMLId[EBMLId["BlockDuration"] = 155] = "BlockDuration";
|
||||
EBMLId[EBMLId["ReferenceBlock"] = 251] = "ReferenceBlock";
|
||||
EBMLId[EBMLId["Cluster"] = 524531317] = "Cluster";
|
||||
EBMLId[EBMLId["Timestamp"] = 231] = "Timestamp";
|
||||
EBMLId[EBMLId["Cues"] = 475249515] = "Cues";
|
||||
EBMLId[EBMLId["CuePoint"] = 187] = "CuePoint";
|
||||
EBMLId[EBMLId["CueTime"] = 179] = "CueTime";
|
||||
EBMLId[EBMLId["CueTrackPositions"] = 183] = "CueTrackPositions";
|
||||
EBMLId[EBMLId["CueTrack"] = 247] = "CueTrack";
|
||||
EBMLId[EBMLId["CueClusterPosition"] = 241] = "CueClusterPosition";
|
||||
EBMLId[EBMLId["Colour"] = 21936] = "Colour";
|
||||
EBMLId[EBMLId["MatrixCoefficients"] = 21937] = "MatrixCoefficients";
|
||||
EBMLId[EBMLId["TransferCharacteristics"] = 21946] = "TransferCharacteristics";
|
||||
EBMLId[EBMLId["Primaries"] = 21947] = "Primaries";
|
||||
EBMLId[EBMLId["Range"] = 21945] = "Range";
|
||||
EBMLId[EBMLId["Projection"] = 30320] = "Projection";
|
||||
EBMLId[EBMLId["ProjectionType"] = 30321] = "ProjectionType";
|
||||
EBMLId[EBMLId["ProjectionPoseRoll"] = 30325] = "ProjectionPoseRoll";
|
||||
EBMLId[EBMLId["Attachments"] = 423732329] = "Attachments";
|
||||
EBMLId[EBMLId["AttachedFile"] = 24999] = "AttachedFile";
|
||||
EBMLId[EBMLId["FileDescription"] = 18046] = "FileDescription";
|
||||
EBMLId[EBMLId["FileName"] = 18030] = "FileName";
|
||||
EBMLId[EBMLId["FileMediaType"] = 18016] = "FileMediaType";
|
||||
EBMLId[EBMLId["FileData"] = 18012] = "FileData";
|
||||
EBMLId[EBMLId["FileUID"] = 18094] = "FileUID";
|
||||
EBMLId[EBMLId["Chapters"] = 272869232] = "Chapters";
|
||||
EBMLId[EBMLId["Tags"] = 307544935] = "Tags";
|
||||
EBMLId[EBMLId["Tag"] = 29555] = "Tag";
|
||||
EBMLId[EBMLId["Targets"] = 25536] = "Targets";
|
||||
EBMLId[EBMLId["TargetTypeValue"] = 26826] = "TargetTypeValue";
|
||||
EBMLId[EBMLId["TargetType"] = 25546] = "TargetType";
|
||||
EBMLId[EBMLId["TagTrackUID"] = 25541] = "TagTrackUID";
|
||||
EBMLId[EBMLId["TagEditionUID"] = 25545] = "TagEditionUID";
|
||||
EBMLId[EBMLId["TagChapterUID"] = 25540] = "TagChapterUID";
|
||||
EBMLId[EBMLId["TagAttachmentUID"] = 25542] = "TagAttachmentUID";
|
||||
EBMLId[EBMLId["SimpleTag"] = 26568] = "SimpleTag";
|
||||
EBMLId[EBMLId["TagName"] = 17827] = "TagName";
|
||||
EBMLId[EBMLId["TagLanguage"] = 17530] = "TagLanguage";
|
||||
EBMLId[EBMLId["TagString"] = 17543] = "TagString";
|
||||
EBMLId[EBMLId["TagBinary"] = 17541] = "TagBinary";
|
||||
EBMLId[EBMLId["ContentEncodings"] = 28032] = "ContentEncodings";
|
||||
EBMLId[EBMLId["ContentEncoding"] = 25152] = "ContentEncoding";
|
||||
EBMLId[EBMLId["ContentEncodingOrder"] = 20529] = "ContentEncodingOrder";
|
||||
EBMLId[EBMLId["ContentEncodingScope"] = 20530] = "ContentEncodingScope";
|
||||
EBMLId[EBMLId["ContentCompression"] = 20532] = "ContentCompression";
|
||||
EBMLId[EBMLId["ContentCompAlgo"] = 16980] = "ContentCompAlgo";
|
||||
EBMLId[EBMLId["ContentCompSettings"] = 16981] = "ContentCompSettings";
|
||||
EBMLId[EBMLId["ContentEncryption"] = 20533] = "ContentEncryption";
|
||||
})(EBMLId || (EBMLId = {}));
|
||||
export const LEVEL_0_EBML_IDS = [
|
||||
EBMLId.EBML,
|
||||
EBMLId.Segment,
|
||||
];
|
||||
// All the stuff that can appear in a segment, basically
|
||||
export const LEVEL_1_EBML_IDS = [
|
||||
EBMLId.SeekHead,
|
||||
EBMLId.Info,
|
||||
EBMLId.Cluster,
|
||||
EBMLId.Tracks,
|
||||
EBMLId.Cues,
|
||||
EBMLId.Attachments,
|
||||
EBMLId.Chapters,
|
||||
EBMLId.Tags,
|
||||
];
|
||||
export const LEVEL_0_AND_1_EBML_IDS = [
|
||||
...LEVEL_0_EBML_IDS,
|
||||
...LEVEL_1_EBML_IDS,
|
||||
];
|
||||
export const measureUnsignedInt = (value) => {
|
||||
if (value < (1 << 8)) {
|
||||
return 1;
|
||||
}
|
||||
else if (value < (1 << 16)) {
|
||||
return 2;
|
||||
}
|
||||
else if (value < (1 << 24)) {
|
||||
return 3;
|
||||
}
|
||||
else if (value < 2 ** 32) {
|
||||
return 4;
|
||||
}
|
||||
else if (value < 2 ** 40) {
|
||||
return 5;
|
||||
}
|
||||
else {
|
||||
return 6;
|
||||
}
|
||||
};
|
||||
export const measureUnsignedBigInt = (value) => {
|
||||
if (value < (1n << 8n)) {
|
||||
return 1;
|
||||
}
|
||||
else if (value < (1n << 16n)) {
|
||||
return 2;
|
||||
}
|
||||
else if (value < (1n << 24n)) {
|
||||
return 3;
|
||||
}
|
||||
else if (value < (1n << 32n)) {
|
||||
return 4;
|
||||
}
|
||||
else if (value < (1n << 40n)) {
|
||||
return 5;
|
||||
}
|
||||
else if (value < (1n << 48n)) {
|
||||
return 6;
|
||||
}
|
||||
else if (value < (1n << 56n)) {
|
||||
return 7;
|
||||
}
|
||||
else {
|
||||
return 8;
|
||||
}
|
||||
};
|
||||
export const measureSignedInt = (value) => {
|
||||
if (value >= -(1 << 6) && value < (1 << 6)) {
|
||||
return 1;
|
||||
}
|
||||
else if (value >= -(1 << 13) && value < (1 << 13)) {
|
||||
return 2;
|
||||
}
|
||||
else if (value >= -(1 << 20) && value < (1 << 20)) {
|
||||
return 3;
|
||||
}
|
||||
else if (value >= -(1 << 27) && value < (1 << 27)) {
|
||||
return 4;
|
||||
}
|
||||
else if (value >= -(2 ** 34) && value < 2 ** 34) {
|
||||
return 5;
|
||||
}
|
||||
else {
|
||||
return 6;
|
||||
}
|
||||
};
|
||||
export const measureVarInt = (value) => {
|
||||
if (value < (1 << 7) - 1) {
|
||||
/** Top bit is set, leaving 7 bits to hold the integer, but we can't store
|
||||
* 127 because "all bits set to one" is a reserved value. Same thing for the
|
||||
* other cases below:
|
||||
*/
|
||||
return 1;
|
||||
}
|
||||
else if (value < (1 << 14) - 1) {
|
||||
return 2;
|
||||
}
|
||||
else if (value < (1 << 21) - 1) {
|
||||
return 3;
|
||||
}
|
||||
else if (value < (1 << 28) - 1) {
|
||||
return 4;
|
||||
}
|
||||
else if (value < 2 ** 35 - 1) {
|
||||
return 5;
|
||||
}
|
||||
else if (value < 2 ** 42 - 1) {
|
||||
return 6;
|
||||
}
|
||||
else {
|
||||
throw new Error('EBML varint size not supported ' + value);
|
||||
}
|
||||
};
|
||||
export class EBMLWriter {
|
||||
constructor(writer) {
|
||||
this.writer = writer;
|
||||
this.helper = new Uint8Array(8);
|
||||
this.helperView = new DataView(this.helper.buffer);
|
||||
/**
|
||||
* Stores the position from the start of the file to where EBML elements have been written. This is used to
|
||||
* rewrite/edit elements that were already added before, and to measure sizes of things.
|
||||
*/
|
||||
this.offsets = new WeakMap();
|
||||
/** Same as offsets, but stores position where the element's data starts (after ID and size fields). */
|
||||
this.dataOffsets = new WeakMap();
|
||||
}
|
||||
writeByte(value) {
|
||||
this.helperView.setUint8(0, value);
|
||||
this.writer.write(this.helper.subarray(0, 1));
|
||||
}
|
||||
writeFloat32(value) {
|
||||
this.helperView.setFloat32(0, value, false);
|
||||
this.writer.write(this.helper.subarray(0, 4));
|
||||
}
|
||||
writeFloat64(value) {
|
||||
this.helperView.setFloat64(0, value, false);
|
||||
this.writer.write(this.helper);
|
||||
}
|
||||
writeUnsignedInt(value, width = measureUnsignedInt(value)) {
|
||||
let pos = 0;
|
||||
// Each case falls through:
|
||||
switch (width) {
|
||||
case 6:
|
||||
// Need to use division to access >32 bits of floating point var
|
||||
this.helperView.setUint8(pos++, (value / 2 ** 40) | 0);
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 5:
|
||||
this.helperView.setUint8(pos++, (value / 2 ** 32) | 0);
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 4:
|
||||
this.helperView.setUint8(pos++, value >> 24);
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 3:
|
||||
this.helperView.setUint8(pos++, value >> 16);
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 2:
|
||||
this.helperView.setUint8(pos++, value >> 8);
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 1:
|
||||
this.helperView.setUint8(pos++, value);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Bad unsigned int size ' + width);
|
||||
}
|
||||
this.writer.write(this.helper.subarray(0, pos));
|
||||
}
|
||||
writeUnsignedBigInt(value, width = measureUnsignedBigInt(value)) {
|
||||
let pos = 0;
|
||||
for (let i = width - 1; i >= 0; i--) {
|
||||
this.helperView.setUint8(pos++, Number((value >> BigInt(i * 8)) & 0xffn));
|
||||
}
|
||||
this.writer.write(this.helper.subarray(0, pos));
|
||||
}
|
||||
writeSignedInt(value, width = measureSignedInt(value)) {
|
||||
if (value < 0) {
|
||||
// Two's complement stuff
|
||||
value += 2 ** (width * 8);
|
||||
}
|
||||
this.writeUnsignedInt(value, width);
|
||||
}
|
||||
writeVarInt(value, width = measureVarInt(value)) {
|
||||
let pos = 0;
|
||||
switch (width) {
|
||||
case 1:
|
||||
this.helperView.setUint8(pos++, (1 << 7) | value);
|
||||
break;
|
||||
case 2:
|
||||
this.helperView.setUint8(pos++, (1 << 6) | (value >> 8));
|
||||
this.helperView.setUint8(pos++, value);
|
||||
break;
|
||||
case 3:
|
||||
this.helperView.setUint8(pos++, (1 << 5) | (value >> 16));
|
||||
this.helperView.setUint8(pos++, value >> 8);
|
||||
this.helperView.setUint8(pos++, value);
|
||||
break;
|
||||
case 4:
|
||||
this.helperView.setUint8(pos++, (1 << 4) | (value >> 24));
|
||||
this.helperView.setUint8(pos++, value >> 16);
|
||||
this.helperView.setUint8(pos++, value >> 8);
|
||||
this.helperView.setUint8(pos++, value);
|
||||
break;
|
||||
case 5:
|
||||
/**
|
||||
* JavaScript converts its doubles to 32-bit integers for bitwise
|
||||
* operations, so we need to do a division by 2^32 instead of a
|
||||
* right-shift of 32 to retain those top 3 bits
|
||||
*/
|
||||
this.helperView.setUint8(pos++, (1 << 3) | ((value / 2 ** 32) & 0x7));
|
||||
this.helperView.setUint8(pos++, value >> 24);
|
||||
this.helperView.setUint8(pos++, value >> 16);
|
||||
this.helperView.setUint8(pos++, value >> 8);
|
||||
this.helperView.setUint8(pos++, value);
|
||||
break;
|
||||
case 6:
|
||||
this.helperView.setUint8(pos++, (1 << 2) | ((value / 2 ** 40) & 0x3));
|
||||
this.helperView.setUint8(pos++, (value / 2 ** 32) | 0);
|
||||
this.helperView.setUint8(pos++, value >> 24);
|
||||
this.helperView.setUint8(pos++, value >> 16);
|
||||
this.helperView.setUint8(pos++, value >> 8);
|
||||
this.helperView.setUint8(pos++, value);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Bad EBML varint size ' + width);
|
||||
}
|
||||
this.writer.write(this.helper.subarray(0, pos));
|
||||
}
|
||||
writeAsciiString(str) {
|
||||
this.writer.write(new Uint8Array(str.split('').map(x => x.charCodeAt(0))));
|
||||
}
|
||||
writeEBML(data) {
|
||||
if (data === null)
|
||||
return;
|
||||
if (data instanceof Uint8Array) {
|
||||
this.writer.write(data);
|
||||
}
|
||||
else if (Array.isArray(data)) {
|
||||
for (const elem of data) {
|
||||
this.writeEBML(elem);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.offsets.set(data, this.writer.getPos());
|
||||
this.writeUnsignedInt(data.id); // ID field
|
||||
if (Array.isArray(data.data)) {
|
||||
const sizePos = this.writer.getPos();
|
||||
const sizeSize = data.size === -1 ? 1 : (data.size ?? 4);
|
||||
if (data.size === -1) {
|
||||
// Write the reserved all-one-bits marker for unknown/unbounded size.
|
||||
this.writeByte(0xff);
|
||||
}
|
||||
else {
|
||||
this.writer.seek(this.writer.getPos() + sizeSize);
|
||||
}
|
||||
const startPos = this.writer.getPos();
|
||||
this.dataOffsets.set(data, startPos);
|
||||
this.writeEBML(data.data);
|
||||
if (data.size !== -1) {
|
||||
const size = this.writer.getPos() - startPos;
|
||||
const endPos = this.writer.getPos();
|
||||
this.writer.seek(sizePos);
|
||||
this.writeVarInt(size, sizeSize);
|
||||
this.writer.seek(endPos);
|
||||
}
|
||||
}
|
||||
else if (typeof data.data === 'number') {
|
||||
const size = data.size ?? measureUnsignedInt(data.data);
|
||||
this.writeVarInt(size);
|
||||
this.writeUnsignedInt(data.data, size);
|
||||
}
|
||||
else if (typeof data.data === 'bigint') {
|
||||
const size = data.size ?? measureUnsignedBigInt(data.data);
|
||||
this.writeVarInt(size);
|
||||
this.writeUnsignedBigInt(data.data, size);
|
||||
}
|
||||
else if (typeof data.data === 'string') {
|
||||
this.writeVarInt(data.data.length);
|
||||
this.writeAsciiString(data.data);
|
||||
}
|
||||
else if (data.data instanceof Uint8Array) {
|
||||
this.writeVarInt(data.data.byteLength, data.size);
|
||||
this.writer.write(data.data);
|
||||
}
|
||||
else if (data.data instanceof EBMLFloat32) {
|
||||
this.writeVarInt(4);
|
||||
this.writeFloat32(data.data.value);
|
||||
}
|
||||
else if (data.data instanceof EBMLFloat64) {
|
||||
this.writeVarInt(8);
|
||||
this.writeFloat64(data.data.value);
|
||||
}
|
||||
else if (data.data instanceof EBMLSignedInt) {
|
||||
const size = data.size ?? measureSignedInt(data.data.value);
|
||||
this.writeVarInt(size);
|
||||
this.writeSignedInt(data.data.value, size);
|
||||
}
|
||||
else if (data.data instanceof EBMLUnicodeString) {
|
||||
const bytes = textEncoder.encode(data.data.value);
|
||||
this.writeVarInt(bytes.length);
|
||||
this.writer.write(bytes);
|
||||
}
|
||||
else {
|
||||
assertNever(data.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export const MAX_VAR_INT_SIZE = 8;
|
||||
export const MIN_HEADER_SIZE = 2; // 1-byte ID and 1-byte size
|
||||
export const MAX_HEADER_SIZE = 2 * MAX_VAR_INT_SIZE; // 8-byte ID and 8-byte size
|
||||
export const readVarIntSize = (slice) => {
|
||||
if (slice.remainingLength < 1) {
|
||||
return null;
|
||||
}
|
||||
const firstByte = readU8(slice);
|
||||
slice.skip(-1);
|
||||
if (firstByte === 0) {
|
||||
return null; // Invalid VINT
|
||||
}
|
||||
let width = 1;
|
||||
let mask = 0x80;
|
||||
while ((firstByte & mask) === 0) {
|
||||
width++;
|
||||
mask >>= 1;
|
||||
}
|
||||
// Check if we have enough bytes to read the full varint
|
||||
if (slice.remainingLength < width) {
|
||||
return null;
|
||||
}
|
||||
return width;
|
||||
};
|
||||
export const readVarInt = (slice) => {
|
||||
if (slice.remainingLength < 1) {
|
||||
return null;
|
||||
}
|
||||
// Read the first byte to determine the width of the variable-length integer
|
||||
const firstByte = readU8(slice);
|
||||
if (firstByte === 0) {
|
||||
return null; // Invalid VINT
|
||||
}
|
||||
// Find the position of VINT_MARKER, which determines the width
|
||||
let width = 1;
|
||||
let mask = 1 << 7;
|
||||
while ((firstByte & mask) === 0) {
|
||||
width++;
|
||||
mask >>= 1;
|
||||
}
|
||||
if (slice.remainingLength < width - 1) {
|
||||
// Not enough bytes
|
||||
return null;
|
||||
}
|
||||
// First byte's value needs the marker bit cleared
|
||||
let value = firstByte & (mask - 1);
|
||||
// Read remaining bytes
|
||||
for (let i = 1; i < width; i++) {
|
||||
value *= 1 << 8;
|
||||
value += readU8(slice);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
export const readUnsignedInt = (slice, width) => {
|
||||
if (width < 1 || width > 8) {
|
||||
throw new Error('Bad unsigned int size ' + width);
|
||||
}
|
||||
let value = 0;
|
||||
// Read bytes from most significant to least significant
|
||||
for (let i = 0; i < width; i++) {
|
||||
value *= 1 << 8;
|
||||
value += readU8(slice);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
export const readUnsignedBigInt = (slice, width) => {
|
||||
if (width < 1) {
|
||||
throw new Error('Bad unsigned int size ' + width);
|
||||
}
|
||||
let value = 0n;
|
||||
for (let i = 0; i < width; i++) {
|
||||
value <<= 8n;
|
||||
value += BigInt(readU8(slice));
|
||||
}
|
||||
return value;
|
||||
};
|
||||
export const readSignedInt = (slice, width) => {
|
||||
let value = readUnsignedInt(slice, width);
|
||||
// If the highest bit is set, convert from two's complement
|
||||
if (value & (1 << (width * 8 - 1))) {
|
||||
value -= 2 ** (width * 8);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
export const readElementId = (slice) => {
|
||||
const size = readVarIntSize(slice);
|
||||
if (size === null) {
|
||||
return null;
|
||||
}
|
||||
if (slice.remainingLength < size) {
|
||||
return null; // It don't fit
|
||||
}
|
||||
const id = readUnsignedInt(slice, size);
|
||||
return id;
|
||||
};
|
||||
/** Returns `undefined` to indicate the EBML undefined size. Returns `null` if the size couldn't be read. */
|
||||
export const readElementSize = (slice) => {
|
||||
// Need at least 1 byte to read the size
|
||||
if (slice.remainingLength < 1) {
|
||||
return null;
|
||||
}
|
||||
const firstByte = readU8(slice);
|
||||
if (firstByte === 0xff) {
|
||||
return undefined;
|
||||
}
|
||||
slice.skip(-1);
|
||||
const size = readVarInt(slice);
|
||||
if (size === null) {
|
||||
return null;
|
||||
}
|
||||
// In some (livestreamed) files, this is the value of the size field. While this technically is just a very
|
||||
// large number, it is intended to behave like the reserved size 0xFF, meaning the size is undefined. We
|
||||
// catch the number here. Note that it cannot be perfectly represented as a double, but the comparison works
|
||||
// nonetheless.
|
||||
// eslint-disable-next-line no-loss-of-precision
|
||||
if (size === 0x00ffffffffffffff) {
|
||||
return undefined;
|
||||
}
|
||||
return size;
|
||||
};
|
||||
export const readElementHeader = (slice) => {
|
||||
assert(slice.remainingLength >= MIN_HEADER_SIZE);
|
||||
const id = readElementId(slice);
|
||||
if (id === null) {
|
||||
return null;
|
||||
}
|
||||
const size = readElementSize(slice);
|
||||
if (size === null) {
|
||||
return null;
|
||||
}
|
||||
return { id, size };
|
||||
};
|
||||
export const readAsciiString = (slice, length) => {
|
||||
const bytes = readBytes(slice, length);
|
||||
// Actual string length might be shorter due to null terminators
|
||||
let strLength = 0;
|
||||
while (strLength < length && bytes[strLength] !== 0) {
|
||||
strLength += 1;
|
||||
}
|
||||
return String.fromCharCode(...bytes.subarray(0, strLength));
|
||||
};
|
||||
export const readUnicodeString = (slice, length) => {
|
||||
const bytes = readBytes(slice, length);
|
||||
// Actual string length might be shorter due to null terminators
|
||||
let strLength = 0;
|
||||
while (strLength < length && bytes[strLength] !== 0) {
|
||||
strLength += 1;
|
||||
}
|
||||
return textDecoder.decode(bytes.subarray(0, strLength));
|
||||
};
|
||||
export const readFloat = (slice, width) => {
|
||||
if (width === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (width !== 4 && width !== 8) {
|
||||
throw new Error('Bad float size ' + width);
|
||||
}
|
||||
return width === 4 ? readF32Be(slice) : readF64Be(slice);
|
||||
};
|
||||
/** Returns the byte offset in the file of the next element with a matching ID. */
|
||||
export const searchForNextElementId = async (reader, startPos, ids, until) => {
|
||||
const idsSet = new Set(ids);
|
||||
let currentPos = startPos;
|
||||
while (until === null || currentPos < until) {
|
||||
let slice = reader.requestSliceRange(currentPos, MIN_HEADER_SIZE, MAX_HEADER_SIZE);
|
||||
if (slice instanceof Promise)
|
||||
slice = await slice;
|
||||
if (!slice)
|
||||
break;
|
||||
const elementHeader = readElementHeader(slice);
|
||||
if (!elementHeader) {
|
||||
break;
|
||||
}
|
||||
if (idsSet.has(elementHeader.id)) {
|
||||
return { pos: currentPos, found: true };
|
||||
}
|
||||
assertDefinedSize(elementHeader.size);
|
||||
currentPos = slice.filePos + elementHeader.size;
|
||||
}
|
||||
return { pos: (until !== null && until > currentPos) ? until : currentPos, found: false };
|
||||
};
|
||||
/** Searches for the next occurrence of an element ID using a naive byte-wise search. */
|
||||
export const resync = async (reader, startPos, ids, until) => {
|
||||
const CHUNK_SIZE = 2 ** 16; // So we don't need to grab thousands of slices
|
||||
const idsSet = new Set(ids);
|
||||
let currentPos = startPos;
|
||||
while (currentPos < until) {
|
||||
let slice = reader.requestSliceRange(currentPos, 0, Math.min(CHUNK_SIZE, until - currentPos));
|
||||
if (slice instanceof Promise)
|
||||
slice = await slice;
|
||||
if (!slice)
|
||||
break;
|
||||
if (slice.length < MAX_VAR_INT_SIZE)
|
||||
break;
|
||||
for (let i = 0; i < slice.length - MAX_VAR_INT_SIZE; i++) {
|
||||
slice.filePos = currentPos;
|
||||
const elementId = readElementId(slice);
|
||||
if (elementId !== null && idsSet.has(elementId)) {
|
||||
return currentPos;
|
||||
}
|
||||
currentPos++;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
export const CODEC_STRING_MAP = {
|
||||
'avc': 'V_MPEG4/ISO/AVC',
|
||||
'hevc': 'V_MPEGH/ISO/HEVC',
|
||||
'vp8': 'V_VP8',
|
||||
'vp9': 'V_VP9',
|
||||
'av1': 'V_AV1',
|
||||
'aac': 'A_AAC',
|
||||
'mp3': 'A_MPEG/L3',
|
||||
'opus': 'A_OPUS',
|
||||
'vorbis': 'A_VORBIS',
|
||||
'flac': 'A_FLAC',
|
||||
'ac3': 'A_AC3',
|
||||
'eac3': 'A_EAC3',
|
||||
'pcm-u8': 'A_PCM/INT/LIT',
|
||||
'pcm-s16': 'A_PCM/INT/LIT',
|
||||
'pcm-s16be': 'A_PCM/INT/BIG',
|
||||
'pcm-s24': 'A_PCM/INT/LIT',
|
||||
'pcm-s24be': 'A_PCM/INT/BIG',
|
||||
'pcm-s32': 'A_PCM/INT/LIT',
|
||||
'pcm-s32be': 'A_PCM/INT/BIG',
|
||||
'pcm-f32': 'A_PCM/FLOAT/IEEE',
|
||||
'pcm-f64': 'A_PCM/FLOAT/IEEE',
|
||||
'webvtt': 'S_TEXT/WEBVTT',
|
||||
};
|
||||
export function assertDefinedSize(size) {
|
||||
if (size === undefined) {
|
||||
throw new Error('Undefined element size is used in a place where it is not supported.');
|
||||
}
|
||||
}
|
||||
;
|
||||
skills/remotion-prompt-video/node_modules/mediabunny/dist/modules/src/matroska/matroska-demuxer.d.ts
Generated
Vendored
+186
@@ -0,0 +1,186 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { AacCodecInfo, AudioCodec, VideoCodec } from '../codec.js';
|
||||
import { Demuxer } from '../demuxer.js';
|
||||
import { Input } from '../input.js';
|
||||
import { InputTrack } from '../input-track.js';
|
||||
import { MetadataTags, TrackDisposition } from '../metadata.js';
|
||||
import { Rotation } from '../misc.js';
|
||||
import { FileSlice, Reader } from '../reader.js';
|
||||
type Segment = {
|
||||
seekHeadSeen: boolean;
|
||||
infoSeen: boolean;
|
||||
tracksSeen: boolean;
|
||||
cuesSeen: boolean;
|
||||
attachmentsSeen: boolean;
|
||||
tagsSeen: boolean;
|
||||
timestampScale: number;
|
||||
timestampFactor: number;
|
||||
duration: number;
|
||||
seekEntries: SeekEntry[];
|
||||
tracks: InternalTrack[];
|
||||
cuePoints: CuePoint[];
|
||||
dataStartPos: number;
|
||||
elementEndPos: number | null;
|
||||
clusterSeekStartPos: number;
|
||||
/**
|
||||
* Caches the last cluster that was read. Based on the assumption that there will be multiple reads to the
|
||||
* same cluster in quick succession.
|
||||
*/
|
||||
lastReadCluster: Cluster | null;
|
||||
metadataTags: MetadataTags;
|
||||
metadataTagsCollected: boolean;
|
||||
};
|
||||
type SeekEntry = {
|
||||
id: number;
|
||||
segmentPosition: number;
|
||||
};
|
||||
type Cluster = {
|
||||
segment: Segment;
|
||||
elementStartPos: number;
|
||||
elementEndPos: number;
|
||||
dataStartPos: number;
|
||||
timestamp: number;
|
||||
trackData: Map<number, ClusterTrackData>;
|
||||
};
|
||||
type ClusterTrackData = {
|
||||
track: InternalTrack;
|
||||
startTimestamp: number;
|
||||
endTimestamp: number;
|
||||
firstKeyFrameTimestamp: number | null;
|
||||
blocks: ClusterBlock[];
|
||||
presentationTimestamps: {
|
||||
timestamp: number;
|
||||
blockIndex: number;
|
||||
}[];
|
||||
};
|
||||
declare enum BlockLacing {
|
||||
None = 0,
|
||||
Xiph = 1,
|
||||
FixedSize = 2,
|
||||
Ebml = 3
|
||||
}
|
||||
type ClusterBlock = {
|
||||
timestamp: number;
|
||||
duration: number;
|
||||
isKeyFrame: boolean;
|
||||
data: Uint8Array;
|
||||
lacing: BlockLacing;
|
||||
decoded: boolean;
|
||||
mainAdditional: Uint8Array | null;
|
||||
};
|
||||
type CuePoint = {
|
||||
time: number;
|
||||
trackId: number;
|
||||
clusterPosition: number;
|
||||
};
|
||||
declare enum ContentEncodingScope {
|
||||
Block = 1,
|
||||
Private = 2,
|
||||
Next = 4
|
||||
}
|
||||
declare enum ContentCompAlgo {
|
||||
Zlib = 0,
|
||||
Bzlib = 1,
|
||||
lzo1x = 2,
|
||||
HeaderStripping = 3
|
||||
}
|
||||
type DecodingInstruction = {
|
||||
order: number;
|
||||
scope: ContentEncodingScope;
|
||||
data: {
|
||||
type: 'decompress';
|
||||
algorithm: ContentCompAlgo | null;
|
||||
settings: Uint8Array | null;
|
||||
} | {
|
||||
type: 'decrypt';
|
||||
} | null;
|
||||
};
|
||||
type InternalTrack = {
|
||||
id: number;
|
||||
demuxer: MatroskaDemuxer;
|
||||
segment: Segment;
|
||||
/**
|
||||
* List of all encountered cluster offsets alongside their timestamps. This list never gets truncated, but memory
|
||||
* consumption should be negligible.
|
||||
*/
|
||||
clusterPositionCache: {
|
||||
elementStartPos: number;
|
||||
startTimestamp: number;
|
||||
}[];
|
||||
cuePoints: CuePoint[];
|
||||
disposition: TrackDisposition;
|
||||
inputTrack: InputTrack | null;
|
||||
codecId: string | null;
|
||||
codecPrivate: Uint8Array | null;
|
||||
defaultDuration: number | null;
|
||||
defaultDurationNs: number | null;
|
||||
name: string | null;
|
||||
languageCode: string;
|
||||
decodingInstructions: DecodingInstruction[];
|
||||
info: null | {
|
||||
type: 'video';
|
||||
width: number;
|
||||
height: number;
|
||||
rotation: Rotation;
|
||||
codec: VideoCodec | null;
|
||||
codecDescription: Uint8Array | null;
|
||||
colorSpace: VideoColorSpaceInit | null;
|
||||
alphaMode: boolean;
|
||||
} | {
|
||||
type: 'audio';
|
||||
numberOfChannels: number;
|
||||
sampleRate: number;
|
||||
bitDepth: number;
|
||||
codec: AudioCodec | null;
|
||||
codecDescription: Uint8Array | null;
|
||||
aacCodecInfo: AacCodecInfo | null;
|
||||
};
|
||||
};
|
||||
export declare class MatroskaDemuxer extends Demuxer {
|
||||
reader: Reader;
|
||||
readMetadataPromise: Promise<void> | null;
|
||||
segments: Segment[];
|
||||
currentSegment: Segment | null;
|
||||
currentTrack: InternalTrack | null;
|
||||
currentCluster: Cluster | null;
|
||||
currentBlock: ClusterBlock | null;
|
||||
currentBlockAdditional: {
|
||||
addId: number;
|
||||
data: Uint8Array | null;
|
||||
} | null;
|
||||
currentCueTime: number | null;
|
||||
currentDecodingInstruction: DecodingInstruction | null;
|
||||
currentTagTargetIsMovie: boolean;
|
||||
currentSimpleTagName: string | null;
|
||||
currentAttachedFile: {
|
||||
fileUid: bigint | null;
|
||||
fileName: string | null;
|
||||
fileMediaType: string | null;
|
||||
fileData: Uint8Array | null;
|
||||
fileDescription: string | null;
|
||||
} | null;
|
||||
isWebM: boolean;
|
||||
constructor(input: Input);
|
||||
computeDuration(): Promise<number>;
|
||||
getTracks(): Promise<InputTrack[]>;
|
||||
getMimeType(): Promise<string>;
|
||||
getMetadataTags(): Promise<MetadataTags>;
|
||||
readMetadata(): Promise<void>;
|
||||
readSegment(segmentDataStart: number, dataSize: number | undefined): Promise<void>;
|
||||
readCluster(startPos: number, segment: Segment): Promise<Cluster>;
|
||||
getTrackDataInCluster(cluster: Cluster, trackNumber: number): ClusterTrackData | null;
|
||||
expandLacedBlocks(blocks: ClusterBlock[], track: InternalTrack): void;
|
||||
loadSegmentMetadata(segment: Segment): Promise<void>;
|
||||
readContiguousElements(slice: FileSlice, stopIds?: number[]): number;
|
||||
traverseElement(slice: FileSlice, stopIds?: number[]): boolean;
|
||||
decodeBlockData(track: InternalTrack, rawData: Uint8Array): Uint8Array<ArrayBufferLike>;
|
||||
processTagValue(name: string, value: string | Uint8Array): void;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=matroska-demuxer.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"matroska-demuxer.d.ts","sourceRoot":"","sources":["../../../../src/matroska/matroska-demuxer.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+2108
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
export declare const buildMatroskaMimeType: (info: {
|
||||
isWebM: boolean;
|
||||
hasVideo: boolean;
|
||||
hasAudio: boolean;
|
||||
codecStrings: string[];
|
||||
}) => string;
|
||||
//# sourceMappingURL=matroska-misc.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"matroska-misc.d.ts","sourceRoot":"","sources":["../../../../src/matroska/matroska-misc.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,qBAAqB,GAAI,MAAM;IAC3C,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;CACvB,WAeA,CAAC"}
|
||||
Generated
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
export const buildMatroskaMimeType = (info) => {
|
||||
const base = info.hasVideo
|
||||
? 'video/'
|
||||
: info.hasAudio
|
||||
? 'audio/'
|
||||
: 'application/';
|
||||
let string = base + (info.isWebM ? 'webm' : 'x-matroska');
|
||||
if (info.codecStrings.length > 0) {
|
||||
const uniqueCodecMimeTypes = [...new Set(info.codecStrings.filter(Boolean))];
|
||||
string += `; codecs="${uniqueCodecMimeTypes.join(', ')}"`;
|
||||
}
|
||||
return string;
|
||||
};
|
||||
Generated
Vendored
+75
@@ -0,0 +1,75 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { MkvOutputFormat } from '../output-format.js';
|
||||
import { Output, OutputAudioTrack, OutputSubtitleTrack, OutputVideoTrack } from '../output.js';
|
||||
import { SubtitleCue, SubtitleMetadata } from '../subtitles.js';
|
||||
import { Muxer } from '../muxer.js';
|
||||
import { EncodedPacket } from '../packet.js';
|
||||
export declare class MatroskaMuxer extends Muxer {
|
||||
private writer;
|
||||
private ebmlWriter;
|
||||
private format;
|
||||
private trackDatas;
|
||||
private allTracksKnown;
|
||||
private segment;
|
||||
private segmentInfo;
|
||||
private seekHead;
|
||||
private tracksElement;
|
||||
private tagsElement;
|
||||
private attachmentsElement;
|
||||
private segmentDuration;
|
||||
private cues;
|
||||
private currentCluster;
|
||||
private currentClusterStartMsTimestamp;
|
||||
private currentClusterMaxMsTimestamp;
|
||||
private trackDatasInCurrentCluster;
|
||||
private duration;
|
||||
constructor(output: Output, format: MkvOutputFormat);
|
||||
start(): Promise<void>;
|
||||
private writeEBMLHeader;
|
||||
/**
|
||||
* Creates a SeekHead element which is positioned near the start of the file and allows the media player to seek to
|
||||
* relevant sections more easily. Since we don't know the positions of those sections yet, we'll set them later.
|
||||
*/
|
||||
private maybeCreateSeekHead;
|
||||
private createSegmentInfo;
|
||||
private createTracks;
|
||||
private videoSpecificTrackInfo;
|
||||
private audioSpecificTrackInfo;
|
||||
private subtitleSpecificTrackInfo;
|
||||
private maybeCreateTags;
|
||||
private maybeCreateAttachments;
|
||||
private createSegment;
|
||||
private createCues;
|
||||
private get segmentDataOffset();
|
||||
private allTracksAreKnown;
|
||||
getMimeType(): Promise<string>;
|
||||
private getVideoTrackData;
|
||||
private getAudioTrackData;
|
||||
private getSubtitleTrackData;
|
||||
addEncodedVideoPacket(track: OutputVideoTrack, packet: EncodedPacket, meta?: EncodedVideoChunkMetadata): Promise<void>;
|
||||
addEncodedAudioPacket(track: OutputAudioTrack, packet: EncodedPacket, meta?: EncodedAudioChunkMetadata): Promise<void>;
|
||||
addSubtitleCue(track: OutputSubtitleTrack, cue: SubtitleCue, meta?: SubtitleMetadata): Promise<void>;
|
||||
private interleaveChunks;
|
||||
/**
|
||||
* Due to [a bug in Chromium](https://bugs.chromium.org/p/chromium/issues/detail?id=1377842), VP9 streams often
|
||||
* lack color space information. This method patches in that information.
|
||||
*/
|
||||
private fixVP9ColorSpace;
|
||||
/** Converts a read-only external chunk into an internal one for easier use. */
|
||||
private createInternalChunk;
|
||||
/** Writes a block containing media data to the file. */
|
||||
private writeBlock;
|
||||
/** Creates a new Cluster element to contain media chunks. */
|
||||
private createNewCluster;
|
||||
private finalizeCurrentCluster;
|
||||
onTrackClose(): Promise<void>;
|
||||
/** Finalizes the file, making it ready for use. Must be called after all media chunks have been added. */
|
||||
finalize(): Promise<void>;
|
||||
}
|
||||
//# sourceMappingURL=matroska-muxer.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"matroska-muxer.d.ts","sourceRoot":"","sources":["../../../../src/matroska/matroska-muxer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAiCH,OAAO,EAAE,eAAe,EAAoB,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,mBAAmB,EAAe,gBAAgB,EAAE,MAAM,WAAW,CAAC;AACzG,OAAO,EAEN,WAAW,EACX,gBAAgB,EAIhB,MAAM,cAAc,CAAC;AAkBtB,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEjC,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AA6D1C,qBAAa,aAAc,SAAQ,KAAK;IACvC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,MAAM,CAAqC;IAEnD,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,cAAc,CAA0B;IAEhD,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,kBAAkB,CAA4B;IACtD,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,IAAI,CAA4B;IAExC,OAAO,CAAC,cAAc,CAA4B;IAClD,OAAO,CAAC,8BAA8B,CAAuB;IAC7D,OAAO,CAAC,4BAA4B,CAAuB;IAC3D,OAAO,CAAC,0BAA0B,CAE7B;IAEL,OAAO,CAAC,QAAQ,CAAK;gBAET,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe;IAa7C,KAAK;IAaX,OAAO,CAAC,eAAe;IAsBvB;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAsE3B,OAAO,CAAC,iBAAiB;IAazB,OAAO,CAAC,YAAY;IA2DpB,OAAO,CAAC,sBAAsB;IAuE9B,OAAO,CAAC,sBAAsB;IAoB9B,OAAO,CAAC,yBAAyB;IAMjC,OAAO,CAAC,eAAe;IA2HvB,OAAO,CAAC,sBAAsB;IAkF9B,OAAO,CAAC,aAAa;IAiCrB,OAAO,CAAC,UAAU;IAIlB,OAAO,KAAK,iBAAiB,GAG5B;IAED,OAAO,CAAC,iBAAiB;IAUnB,WAAW;IAwBjB,OAAO,CAAC,iBAAiB;IAyDzB,OAAO,CAAC,iBAAiB;IAiEzB,OAAO,CAAC,oBAAoB;IA+BtB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,yBAAyB;IA8BtG,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,yBAAyB;IA8BtG,cAAc,CAAC,KAAK,EAAE,mBAAmB,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,gBAAgB;YAsC5E,gBAAgB;IAkC9B;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA4CxB,+EAA+E;IAC/E,OAAO,CAAC,mBAAmB;IAkB3B,wDAAwD;IACxD,OAAO,CAAC,UAAU;IA6GlB,6DAA6D;IAC7D,OAAO,CAAC,gBAAgB;IAuBxB,OAAO,CAAC,sBAAsB;IAmDf,YAAY;IAa3B,0GAA0G;IACpG,QAAQ;CA2Cd"}
|
||||
Generated
Vendored
+1046
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+310
@@ -0,0 +1,310 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { InputAudioTrack, InputTrack, InputVideoTrack } from './input-track.js';
|
||||
import { AnyIterable, Rotation } from './misc.js';
|
||||
import { EncodedPacket } from './packet.js';
|
||||
import { AudioSample, CropRectangle, VideoSample } from './sample.js';
|
||||
/**
|
||||
* Additional options for controlling packet retrieval.
|
||||
* @group Media sinks
|
||||
* @public
|
||||
*/
|
||||
export type PacketRetrievalOptions = {
|
||||
/**
|
||||
* When set to `true`, only packet metadata (like timestamp) will be retrieved - the actual packet data will not
|
||||
* be loaded.
|
||||
*/
|
||||
metadataOnly?: boolean;
|
||||
/**
|
||||
* When set to true, key packets will be verified upon retrieval by looking into the packet's bitstream.
|
||||
* If not enabled, the packet types will be determined solely by what's stored in the containing file and may be
|
||||
* incorrect, potentially leading to decoder errors. Since determining a packet's actual type requires looking into
|
||||
* its data, this option cannot be enabled together with `metadataOnly`.
|
||||
*/
|
||||
verifyKeyPackets?: boolean;
|
||||
};
|
||||
/**
|
||||
* Sink for retrieving encoded packets from an input track.
|
||||
* @group Media sinks
|
||||
* @public
|
||||
*/
|
||||
export declare class EncodedPacketSink {
|
||||
/** Creates a new {@link EncodedPacketSink} for the given {@link InputTrack}. */
|
||||
constructor(track: InputTrack);
|
||||
/**
|
||||
* Retrieves the track's first packet (in decode order), or null if it has no packets. The first packet is very
|
||||
* likely to be a key packet.
|
||||
*/
|
||||
getFirstPacket(options?: PacketRetrievalOptions): Promise<EncodedPacket | null>;
|
||||
/**
|
||||
* Retrieves the packet corresponding to the given timestamp, in seconds. More specifically, returns the last packet
|
||||
* (in presentation order) with a start timestamp less than or equal to the given timestamp. This method can be
|
||||
* used to retrieve a track's last packet using `getPacket(Infinity)`. The method returns null if the timestamp
|
||||
* is before the first packet in the track.
|
||||
*
|
||||
* @param timestamp - The timestamp used for retrieval, in seconds.
|
||||
*/
|
||||
getPacket(timestamp: number, options?: PacketRetrievalOptions): Promise<EncodedPacket | null>;
|
||||
/**
|
||||
* Retrieves the packet following the given packet (in decode order), or null if the given packet is the
|
||||
* last packet.
|
||||
*/
|
||||
getNextPacket(packet: EncodedPacket, options?: PacketRetrievalOptions): Promise<EncodedPacket | null>;
|
||||
/**
|
||||
* Retrieves the key packet corresponding to the given timestamp, in seconds. More specifically, returns the last
|
||||
* key packet (in presentation order) with a start timestamp less than or equal to the given timestamp. A key packet
|
||||
* is a packet that doesn't require previous packets to be decoded. This method can be used to retrieve a track's
|
||||
* last key packet using `getKeyPacket(Infinity)`. The method returns null if the timestamp is before the first
|
||||
* key packet in the track.
|
||||
*
|
||||
* To ensure that the returned packet is guaranteed to be a real key frame, enable `options.verifyKeyPackets`.
|
||||
*
|
||||
* @param timestamp - The timestamp used for retrieval, in seconds.
|
||||
*/
|
||||
getKeyPacket(timestamp: number, options?: PacketRetrievalOptions): Promise<EncodedPacket | null>;
|
||||
/**
|
||||
* Retrieves the key packet following the given packet (in decode order), or null if the given packet is the last
|
||||
* key packet.
|
||||
*
|
||||
* To ensure that the returned packet is guaranteed to be a real key frame, enable `options.verifyKeyPackets`.
|
||||
*/
|
||||
getNextKeyPacket(packet: EncodedPacket, options?: PacketRetrievalOptions): Promise<EncodedPacket | null>;
|
||||
/**
|
||||
* Creates an async iterator that yields the packets in this track in decode order. To enable fast iteration, this
|
||||
* method will intelligently preload packets based on the speed of the consumer.
|
||||
*
|
||||
* @param startPacket - (optional) The packet from which iteration should begin. This packet will also be yielded.
|
||||
* @param endTimestamp - (optional) The timestamp at which iteration should end. This packet will _not_ be yielded.
|
||||
*/
|
||||
packets(startPacket?: EncodedPacket, endPacket?: EncodedPacket, options?: PacketRetrievalOptions): AsyncGenerator<EncodedPacket, void, unknown>;
|
||||
}
|
||||
/**
|
||||
* Base class for decoded media sample sinks.
|
||||
* @group Media sinks
|
||||
* @public
|
||||
*/
|
||||
export declare abstract class BaseMediaSampleSink<MediaSample extends VideoSample | AudioSample> {
|
||||
}
|
||||
/**
|
||||
* A sink that retrieves decoded video samples (video frames) from a video track.
|
||||
* @group Media sinks
|
||||
* @public
|
||||
*/
|
||||
export declare class VideoSampleSink extends BaseMediaSampleSink<VideoSample> {
|
||||
/** Creates a new {@link VideoSampleSink} for the given {@link InputVideoTrack}. */
|
||||
constructor(videoTrack: InputVideoTrack);
|
||||
/**
|
||||
* Retrieves the video sample (frame) corresponding to the given timestamp, in seconds. More specifically, returns
|
||||
* the last video sample (in presentation order) with a start timestamp less than or equal to the given timestamp.
|
||||
* Returns null if the timestamp is before the track's first timestamp.
|
||||
*
|
||||
* @param timestamp - The timestamp used for retrieval, in seconds.
|
||||
*/
|
||||
getSample(timestamp: number): Promise<VideoSample | null>;
|
||||
/**
|
||||
* Creates an async iterator that yields the video samples (frames) of this track in presentation order. This method
|
||||
* will intelligently pre-decode a few frames ahead to enable fast iteration.
|
||||
*
|
||||
* @param startTimestamp - The timestamp in seconds at which to start yielding samples (inclusive).
|
||||
* @param endTimestamp - The timestamp in seconds at which to stop yielding samples (exclusive).
|
||||
*/
|
||||
samples(startTimestamp?: number, endTimestamp?: number): AsyncGenerator<VideoSample, void, unknown>;
|
||||
/**
|
||||
* Creates an async iterator that yields a video sample (frame) for each timestamp in the argument. This method
|
||||
* uses an optimized decoding pipeline if these timestamps are monotonically sorted, decoding each packet at most
|
||||
* once, and is therefore more efficient than manually getting the sample for every timestamp. The iterator may
|
||||
* yield null if no frame is available for a given timestamp.
|
||||
*
|
||||
* @param timestamps - An iterable or async iterable of timestamps in seconds.
|
||||
*/
|
||||
samplesAtTimestamps(timestamps: AnyIterable<number>): AsyncGenerator<VideoSample | null, void, unknown>;
|
||||
}
|
||||
/**
|
||||
* A canvas with additional timing information (timestamp & duration).
|
||||
* @group Media sinks
|
||||
* @public
|
||||
*/
|
||||
export type WrappedCanvas = {
|
||||
/** A canvas element or offscreen canvas. */
|
||||
canvas: HTMLCanvasElement | OffscreenCanvas;
|
||||
/** The timestamp of the corresponding video sample, in seconds. */
|
||||
timestamp: number;
|
||||
/** The duration of the corresponding video sample, in seconds. */
|
||||
duration: number;
|
||||
};
|
||||
/**
|
||||
* Options for constructing a CanvasSink.
|
||||
* @group Media sinks
|
||||
* @public
|
||||
*/
|
||||
export type CanvasSinkOptions = {
|
||||
/**
|
||||
* Whether the output canvases should have transparency instead of a black background. Defaults to `false`. Set
|
||||
* this to `true` when using this sink to read transparent videos.
|
||||
*/
|
||||
alpha?: boolean;
|
||||
/**
|
||||
* The width of the output canvas in pixels, defaulting to the display width of the video track. If height is not
|
||||
* set, it will be deduced automatically based on aspect ratio.
|
||||
*/
|
||||
width?: number;
|
||||
/**
|
||||
* The height of the output canvas in pixels, defaulting to the display height of the video track. If width is not
|
||||
* set, it will be deduced automatically based on aspect ratio.
|
||||
*/
|
||||
height?: number;
|
||||
/**
|
||||
* The fitting algorithm in case both width and height are set.
|
||||
*
|
||||
* - `'fill'` will stretch the image to fill the entire box, potentially altering aspect ratio.
|
||||
* - `'contain'` will contain the entire image within the box while preserving aspect ratio. This may lead to
|
||||
* letterboxing.
|
||||
* - `'cover'` will scale the image until the entire box is filled, while preserving aspect ratio.
|
||||
*/
|
||||
fit?: 'fill' | 'contain' | 'cover';
|
||||
/**
|
||||
* The clockwise rotation by which to rotate the raw video frame. Defaults to the rotation set in the file metadata.
|
||||
* Rotation is applied before resizing.
|
||||
*/
|
||||
rotation?: Rotation;
|
||||
/**
|
||||
* Specifies the rectangular region of the input video to crop to. The crop region will automatically be clamped to
|
||||
* the dimensions of the input video track. Cropping is performed after rotation but before resizing.
|
||||
*/
|
||||
crop?: CropRectangle;
|
||||
/**
|
||||
* When set, specifies the number of canvases in the pool. These canvases will be reused in a ring buffer /
|
||||
* round-robin type fashion. This keeps the amount of allocated VRAM constant and relieves the browser from
|
||||
* constantly allocating/deallocating canvases. A pool size of 0 or `undefined` disables the pool and means a new
|
||||
* canvas is created each time.
|
||||
*/
|
||||
poolSize?: number;
|
||||
};
|
||||
/**
|
||||
* A sink that renders video samples (frames) of the given video track to canvases. This is often more useful than
|
||||
* directly retrieving frames, as it comes with common preprocessing steps such as resizing or applying rotation
|
||||
* metadata.
|
||||
*
|
||||
* This sink will yield `HTMLCanvasElement`s when in a DOM context, and `OffscreenCanvas`es otherwise.
|
||||
*
|
||||
* @group Media sinks
|
||||
* @public
|
||||
*/
|
||||
export declare class CanvasSink {
|
||||
/** Creates a new {@link CanvasSink} for the given {@link InputVideoTrack}. */
|
||||
constructor(videoTrack: InputVideoTrack, options?: CanvasSinkOptions);
|
||||
/**
|
||||
* Retrieves a canvas with the video frame corresponding to the given timestamp, in seconds. More specifically,
|
||||
* returns the last video frame (in presentation order) with a start timestamp less than or equal to the given
|
||||
* timestamp. Returns null if the timestamp is before the track's first timestamp.
|
||||
*
|
||||
* @param timestamp - The timestamp used for retrieval, in seconds.
|
||||
*/
|
||||
getCanvas(timestamp: number): Promise<WrappedCanvas | null>;
|
||||
/**
|
||||
* Creates an async iterator that yields canvases with the video frames of this track in presentation order. This
|
||||
* method will intelligently pre-decode a few frames ahead to enable fast iteration.
|
||||
*
|
||||
* @param startTimestamp - The timestamp in seconds at which to start yielding canvases (inclusive).
|
||||
* @param endTimestamp - The timestamp in seconds at which to stop yielding canvases (exclusive).
|
||||
*/
|
||||
canvases(startTimestamp?: number, endTimestamp?: number): AsyncGenerator<WrappedCanvas, void, unknown>;
|
||||
/**
|
||||
* Creates an async iterator that yields a canvas for each timestamp in the argument. This method uses an optimized
|
||||
* decoding pipeline if these timestamps are monotonically sorted, decoding each packet at most once, and is
|
||||
* therefore more efficient than manually getting the canvas for every timestamp. The iterator may yield null if
|
||||
* no frame is available for a given timestamp.
|
||||
*
|
||||
* @param timestamps - An iterable or async iterable of timestamps in seconds.
|
||||
*/
|
||||
canvasesAtTimestamps(timestamps: AnyIterable<number>): AsyncGenerator<WrappedCanvas | null, void, unknown>;
|
||||
}
|
||||
/**
|
||||
* Sink for retrieving decoded audio samples from an audio track.
|
||||
* @group Media sinks
|
||||
* @public
|
||||
*/
|
||||
export declare class AudioSampleSink extends BaseMediaSampleSink<AudioSample> {
|
||||
/** Creates a new {@link AudioSampleSink} for the given {@link InputAudioTrack}. */
|
||||
constructor(audioTrack: InputAudioTrack);
|
||||
/**
|
||||
* Retrieves the audio sample corresponding to the given timestamp, in seconds. More specifically, returns
|
||||
* the last audio sample (in presentation order) with a start timestamp less than or equal to the given timestamp.
|
||||
* Returns null if the timestamp is before the track's first timestamp.
|
||||
*
|
||||
* @param timestamp - The timestamp used for retrieval, in seconds.
|
||||
*/
|
||||
getSample(timestamp: number): Promise<AudioSample | null>;
|
||||
/**
|
||||
* Creates an async iterator that yields the audio samples of this track in presentation order. This method
|
||||
* will intelligently pre-decode a few samples ahead to enable fast iteration.
|
||||
*
|
||||
* @param startTimestamp - The timestamp in seconds at which to start yielding samples (inclusive).
|
||||
* @param endTimestamp - The timestamp in seconds at which to stop yielding samples (exclusive).
|
||||
*/
|
||||
samples(startTimestamp?: number, endTimestamp?: number): AsyncGenerator<AudioSample, void, unknown>;
|
||||
/**
|
||||
* Creates an async iterator that yields an audio sample for each timestamp in the argument. This method
|
||||
* uses an optimized decoding pipeline if these timestamps are monotonically sorted, decoding each packet at most
|
||||
* once, and is therefore more efficient than manually getting the sample for every timestamp. The iterator may
|
||||
* yield null if no sample is available for a given timestamp.
|
||||
*
|
||||
* @param timestamps - An iterable or async iterable of timestamps in seconds.
|
||||
*/
|
||||
samplesAtTimestamps(timestamps: AnyIterable<number>): AsyncGenerator<AudioSample | null, void, unknown>;
|
||||
}
|
||||
/**
|
||||
* An AudioBuffer with additional timing information (timestamp & duration).
|
||||
* @group Media sinks
|
||||
* @public
|
||||
*/
|
||||
export type WrappedAudioBuffer = {
|
||||
/** An AudioBuffer. */
|
||||
buffer: AudioBuffer;
|
||||
/** The timestamp of the corresponding audio sample, in seconds. */
|
||||
timestamp: number;
|
||||
/** The duration of the corresponding audio sample, in seconds. */
|
||||
duration: number;
|
||||
};
|
||||
/**
|
||||
* A sink that retrieves decoded audio samples from an audio track and converts them to `AudioBuffer` instances. This is
|
||||
* often more useful than directly retrieving audio samples, as audio buffers can be directly used with the
|
||||
* Web Audio API.
|
||||
* @group Media sinks
|
||||
* @public
|
||||
*/
|
||||
export declare class AudioBufferSink {
|
||||
/** Creates a new {@link AudioBufferSink} for the given {@link InputAudioTrack}. */
|
||||
constructor(audioTrack: InputAudioTrack);
|
||||
/**
|
||||
* Retrieves the audio buffer corresponding to the given timestamp, in seconds. More specifically, returns
|
||||
* the last audio buffer (in presentation order) with a start timestamp less than or equal to the given timestamp.
|
||||
* Returns null if the timestamp is before the track's first timestamp.
|
||||
*
|
||||
* @param timestamp - The timestamp used for retrieval, in seconds.
|
||||
*/
|
||||
getBuffer(timestamp: number): Promise<WrappedAudioBuffer | null>;
|
||||
/**
|
||||
* Creates an async iterator that yields audio buffers of this track in presentation order. This method
|
||||
* will intelligently pre-decode a few buffers ahead to enable fast iteration.
|
||||
*
|
||||
* @param startTimestamp - The timestamp in seconds at which to start yielding buffers (inclusive).
|
||||
* @param endTimestamp - The timestamp in seconds at which to stop yielding buffers (exclusive).
|
||||
*/
|
||||
buffers(startTimestamp?: number, endTimestamp?: number): AsyncGenerator<WrappedAudioBuffer, void, unknown>;
|
||||
/**
|
||||
* Creates an async iterator that yields an audio buffer for each timestamp in the argument. This method
|
||||
* uses an optimized decoding pipeline if these timestamps are monotonically sorted, decoding each packet at most
|
||||
* once, and is therefore more efficient than manually getting the buffer for every timestamp. The iterator may
|
||||
* yield null if no buffer is available for a given timestamp.
|
||||
*
|
||||
* @param timestamps - An iterable or async iterable of timestamps in seconds.
|
||||
*/
|
||||
buffersAtTimestamps(timestamps: AnyIterable<number>): AsyncGenerator<WrappedAudioBuffer | null, void, unknown>;
|
||||
}
|
||||
//# sourceMappingURL=media-sink.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"media-sink.d.ts","sourceRoot":"","sources":["../../../src/media-sink.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgBH,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EACN,WAAW,EAcX,QAAQ,EAKR,MAAM,QAAQ,CAAC;AAChB,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,WAAW,EAAsB,aAAa,EAAyB,WAAW,EAAE,MAAM,UAAU,CAAC;AAE9G;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACpC;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AA+CF;;;;GAIG;AACH,qBAAa,iBAAiB;IAI7B,gFAAgF;gBACpE,KAAK,EAAE,UAAU;IAQ7B;;;OAGG;IACH,cAAc,CAAC,OAAO,GAAE,sBAA2B;IAUnD;;;;;;;OAOG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B;IAWjE;;;OAGG;IACH,aAAa,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,GAAE,sBAA2B;IAazE;;;;;;;;;;OAUG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IA2B1G;;;;;OAKG;IACG,gBAAgB,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IA6BlH;;;;;;OAMG;IACH,OAAO,CACN,WAAW,CAAC,EAAE,aAAa,EAC3B,SAAS,CAAC,EAAE,aAAa,EACzB,OAAO,GAAE,sBAA2B,GAClC,cAAc,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC;CA6G/C;AAgBD;;;;GAIG;AACH,8BAAsB,mBAAmB,CACxC,WAAW,SAAS,WAAW,GAAG,WAAW;CAyY7C;AAqjBD;;;;GAIG;AACH,qBAAa,eAAgB,SAAQ,mBAAmB,CAAC,WAAW,CAAC;IAIpE,mFAAmF;gBACvE,UAAU,EAAE,eAAe;IAoCvC;;;;;;OAMG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM;IASjC;;;;;;OAMG;IACH,OAAO,CAAC,cAAc,SAAI,EAAE,YAAY,SAAW;IAInD;;;;;;;OAOG;IACH,mBAAmB,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC;CAGnD;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG;IAC3B,4CAA4C;IAC5C,MAAM,EAAE,iBAAiB,GAAG,eAAe,CAAC;IAC5C,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC/B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;OAGG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;GASG;AACH,qBAAa,UAAU;IAsBtB,8EAA8E;gBAClE,UAAU,EAAE,eAAe,EAAE,OAAO,GAAE,iBAAsB;IA0IxE;;;;;;OAMG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM;IAOjC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,SAAI,EAAE,YAAY,SAAW;IAOpD;;;;;;;OAOG;IACH,oBAAoB,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC;CAMpD;AAsTD;;;;GAIG;AACH,qBAAa,eAAgB,SAAQ,mBAAmB,CAAC,WAAW,CAAC;IAIpE,mFAAmF;gBACvE,UAAU,EAAE,eAAe;IAsCvC;;;;;;OAMG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM;IASjC;;;;;;OAMG;IACH,OAAO,CAAC,cAAc,SAAI,EAAE,YAAY,SAAW;IAInD;;;;;;;OAOG;IACH,mBAAmB,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC;CAGnD;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAChC,sBAAsB;IACtB,MAAM,EAAE,WAAW,CAAC;IACpB,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;;GAMG;AACH,qBAAa,eAAe;IAI3B,mFAAmF;gBACvE,UAAU,EAAE,eAAe;IAoBvC;;;;;;OAMG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM;IAOjC;;;;;;OAMG;IACH,OAAO,CAAC,cAAc,SAAI,EAAE,YAAY,SAAW;IAOnD;;;;;;;OAOG;IACH,mBAAmB,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC;CAMnD"}
|
||||
Generated
Vendored
+1751
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+248
@@ -0,0 +1,248 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { AudioCodec, SubtitleCodec, VideoCodec } from './codec.js';
|
||||
import { EncodedPacket } from './packet.js';
|
||||
import { AudioSample, VideoSample } from './sample.js';
|
||||
import { AudioEncodingConfig, VideoEncodingConfig } from './encode.js';
|
||||
/**
|
||||
* Base class for media sources. Media sources are used to add media samples to an output file.
|
||||
* @group Media sources
|
||||
* @public
|
||||
*/
|
||||
export declare abstract class MediaSource {
|
||||
/**
|
||||
* Closes this source. This prevents future samples from being added and signals to the output file that no further
|
||||
* samples will come in for this track. Calling `.close()` is optional but recommended after adding the
|
||||
* last sample - for improved performance and reduced memory usage.
|
||||
*/
|
||||
close(): void;
|
||||
}
|
||||
/**
|
||||
* Base class for video sources - sources for video tracks.
|
||||
* @group Media sources
|
||||
* @public
|
||||
*/
|
||||
export declare abstract class VideoSource extends MediaSource {
|
||||
/** Internal constructor. */
|
||||
constructor(codec: VideoCodec);
|
||||
}
|
||||
/**
|
||||
* The most basic video source; can be used to directly pipe encoded packets into the output file.
|
||||
* @group Media sources
|
||||
* @public
|
||||
*/
|
||||
export declare class EncodedVideoPacketSource extends VideoSource {
|
||||
/** Creates a new {@link EncodedVideoPacketSource} whose packets are encoded using `codec`. */
|
||||
constructor(codec: VideoCodec);
|
||||
/**
|
||||
* Adds an encoded packet to the output video track. Packets must be added in *decode order*, while a packet's
|
||||
* timestamp must be its *presentation timestamp*. B-frames are handled automatically.
|
||||
*
|
||||
* @param meta - Additional metadata from the encoder. You should pass this for the first call, including a valid
|
||||
* decoder config.
|
||||
*
|
||||
* @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
|
||||
* to respect writer and encoder backpressure.
|
||||
*/
|
||||
add(packet: EncodedPacket, meta?: EncodedVideoChunkMetadata): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* This source can be used to add raw, unencoded video samples (frames) to an output video track. These frames will
|
||||
* automatically be encoded and then piped into the output.
|
||||
* @group Media sources
|
||||
* @public
|
||||
*/
|
||||
export declare class VideoSampleSource extends VideoSource {
|
||||
/**
|
||||
* Creates a new {@link VideoSampleSource} whose samples are encoded according to the specified
|
||||
* {@link VideoEncodingConfig}.
|
||||
*/
|
||||
constructor(encodingConfig: VideoEncodingConfig);
|
||||
/**
|
||||
* Encodes a video sample (frame) and then adds it to the output.
|
||||
*
|
||||
* @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
|
||||
* to respect writer and encoder backpressure.
|
||||
*/
|
||||
add(videoSample: VideoSample, encodeOptions?: VideoEncoderEncodeOptions): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* This source can be used to add video frames to the output track from a fixed canvas element. Since canvases are often
|
||||
* used for rendering, this source provides a convenient wrapper around {@link VideoSampleSource}.
|
||||
* @group Media sources
|
||||
* @public
|
||||
*/
|
||||
export declare class CanvasSource extends VideoSource {
|
||||
/**
|
||||
* Creates a new {@link CanvasSource} from a canvas element or `OffscreenCanvas` whose samples are encoded
|
||||
* according to the specified {@link VideoEncodingConfig}.
|
||||
*/
|
||||
constructor(canvas: HTMLCanvasElement | OffscreenCanvas, encodingConfig: VideoEncodingConfig);
|
||||
/**
|
||||
* Captures the current canvas state as a video sample (frame), encodes it and adds it to the output.
|
||||
*
|
||||
* @param timestamp - The timestamp of the sample, in seconds.
|
||||
* @param duration - The duration of the sample, in seconds.
|
||||
*
|
||||
* @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
|
||||
* to respect writer and encoder backpressure.
|
||||
*/
|
||||
add(timestamp: number, duration?: number, encodeOptions?: VideoEncoderEncodeOptions): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* Video source that encodes the frames of a
|
||||
* [`MediaStreamVideoTrack`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) and pipes them into the
|
||||
* output. This is useful for capturing live or real-time data such as webcams or screen captures. Frames will
|
||||
* automatically start being captured once the connected {@link Output} is started, and will keep being captured until
|
||||
* the {@link Output} is finalized or this source is closed.
|
||||
* @group Media sources
|
||||
* @public
|
||||
*/
|
||||
export declare class MediaStreamVideoTrackSource extends VideoSource {
|
||||
/** A promise that rejects upon any error within this source. This promise never resolves. */
|
||||
get errorPromise(): Promise<void>;
|
||||
/** Whether this source is currently paused as a result of calling `.pause()`. */
|
||||
get paused(): boolean;
|
||||
/**
|
||||
* Creates a new {@link MediaStreamVideoTrackSource} from a
|
||||
* [`MediaStreamVideoTrack`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack), which will pull
|
||||
* video samples from the stream in real time and encode them according to {@link VideoEncodingConfig}.
|
||||
*/
|
||||
constructor(track: MediaStreamVideoTrack, encodingConfig: VideoEncodingConfig);
|
||||
/**
|
||||
* Pauses the capture of video frames - any video frames emitted by the underlying media stream will be ignored
|
||||
* while paused. This does *not* close the underlying `MediaStreamVideoTrack`, it just ignores its output.
|
||||
*/
|
||||
pause(): void;
|
||||
/** Resumes the capture of video frames after being paused. */
|
||||
resume(): void;
|
||||
}
|
||||
/**
|
||||
* Base class for audio sources - sources for audio tracks.
|
||||
* @group Media sources
|
||||
* @public
|
||||
*/
|
||||
export declare abstract class AudioSource extends MediaSource {
|
||||
/** Internal constructor. */
|
||||
constructor(codec: AudioCodec);
|
||||
}
|
||||
/**
|
||||
* The most basic audio source; can be used to directly pipe encoded packets into the output file.
|
||||
* @group Media sources
|
||||
* @public
|
||||
*/
|
||||
export declare class EncodedAudioPacketSource extends AudioSource {
|
||||
/** Creates a new {@link EncodedAudioPacketSource} whose packets are encoded using `codec`. */
|
||||
constructor(codec: AudioCodec);
|
||||
/**
|
||||
* Adds an encoded packet to the output audio track. Packets must be added in *decode order*.
|
||||
*
|
||||
* @param meta - Additional metadata from the encoder. You should pass this for the first call, including a valid
|
||||
* decoder config.
|
||||
*
|
||||
* @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
|
||||
* to respect writer and encoder backpressure.
|
||||
*/
|
||||
add(packet: EncodedPacket, meta?: EncodedAudioChunkMetadata): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* This source can be used to add raw, unencoded audio samples to an output audio track. These samples will
|
||||
* automatically be encoded and then piped into the output.
|
||||
* @group Media sources
|
||||
* @public
|
||||
*/
|
||||
export declare class AudioSampleSource extends AudioSource {
|
||||
/**
|
||||
* Creates a new {@link AudioSampleSource} whose samples are encoded according to the specified
|
||||
* {@link AudioEncodingConfig}.
|
||||
*/
|
||||
constructor(encodingConfig: AudioEncodingConfig);
|
||||
/**
|
||||
* Encodes an audio sample and then adds it to the output.
|
||||
*
|
||||
* @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
|
||||
* to respect writer and encoder backpressure.
|
||||
*/
|
||||
add(audioSample: AudioSample): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* This source can be used to add audio data from an AudioBuffer to the output track. This is useful when working with
|
||||
* the Web Audio API.
|
||||
* @group Media sources
|
||||
* @public
|
||||
*/
|
||||
export declare class AudioBufferSource extends AudioSource {
|
||||
/**
|
||||
* Creates a new {@link AudioBufferSource} whose `AudioBuffer` instances are encoded according to the specified
|
||||
* {@link AudioEncodingConfig}.
|
||||
*/
|
||||
constructor(encodingConfig: AudioEncodingConfig);
|
||||
/**
|
||||
* Converts an AudioBuffer to audio samples, encodes them and adds them to the output. The first AudioBuffer will
|
||||
* be played at timestamp 0, and any subsequent AudioBuffer will have a timestamp equal to the total duration of
|
||||
* all previous AudioBuffers.
|
||||
*
|
||||
* @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
|
||||
* to respect writer and encoder backpressure.
|
||||
*/
|
||||
add(audioBuffer: AudioBuffer): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* Audio source that encodes the data of a
|
||||
* [`MediaStreamAudioTrack`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) and pipes it into the
|
||||
* output. This is useful for capturing live or real-time audio such as microphones or audio from other media elements.
|
||||
* Audio will automatically start being captured once the connected {@link Output} is started, and will keep being
|
||||
* captured until the {@link Output} is finalized or this source is closed.
|
||||
* @group Media sources
|
||||
* @public
|
||||
*/
|
||||
export declare class MediaStreamAudioTrackSource extends AudioSource {
|
||||
/** A promise that rejects upon any error within this source. This promise never resolves. */
|
||||
get errorPromise(): Promise<void>;
|
||||
/** Whether this source is currently paused as a result of calling `.pause()`. */
|
||||
get paused(): boolean;
|
||||
/**
|
||||
* Creates a new {@link MediaStreamAudioTrackSource} from a `MediaStreamAudioTrack`, which will pull audio samples
|
||||
* from the stream in real time and encode them according to {@link AudioEncodingConfig}.
|
||||
*/
|
||||
constructor(track: MediaStreamAudioTrack, encodingConfig: AudioEncodingConfig);
|
||||
/**
|
||||
* Pauses the capture of audio data - any audio data emitted by the underlying media stream will be ignored
|
||||
* while paused. This does *not* close the underlying `MediaStreamAudioTrack`, it just ignores its output.
|
||||
*/
|
||||
pause(): void;
|
||||
/** Resumes the capture of audio data after being paused. */
|
||||
resume(): void;
|
||||
}
|
||||
/**
|
||||
* Base class for subtitle sources - sources for subtitle tracks.
|
||||
* @group Media sources
|
||||
* @public
|
||||
*/
|
||||
export declare abstract class SubtitleSource extends MediaSource {
|
||||
/** Internal constructor. */
|
||||
constructor(codec: SubtitleCodec);
|
||||
}
|
||||
/**
|
||||
* This source can be used to add subtitles from a subtitle text file.
|
||||
* @group Media sources
|
||||
* @public
|
||||
*/
|
||||
export declare class TextSubtitleSource extends SubtitleSource {
|
||||
/** Creates a new {@link TextSubtitleSource} where added text chunks are in the specified `codec`. */
|
||||
constructor(codec: SubtitleCodec);
|
||||
/**
|
||||
* Parses the subtitle text according to the specified codec and adds it to the output track. You don't have to
|
||||
* add the entire subtitle file at once here; you can provide it in chunks.
|
||||
*
|
||||
* @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
|
||||
* to respect writer and encoder backpressure.
|
||||
*/
|
||||
add(text: string): Promise<void>;
|
||||
}
|
||||
//# sourceMappingURL=media-source.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"media-source.d.ts","sourceRoot":"","sources":["../../../src/media-source.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAEN,UAAU,EAOV,aAAa,EAEb,UAAU,EACV,MAAM,SAAS,CAAC;AAuBjB,OAAO,EAAE,aAAa,EAAyB,MAAM,UAAU,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EACN,mBAAmB,EAKnB,mBAAmB,EACnB,MAAM,UAAU,CAAC;AAElB;;;;GAIG;AACH,8BAAsB,WAAW;IA0ChC;;;;OAIG;IACH,KAAK;CAmCL;AAED;;;;GAIG;AACH,8BAAsB,WAAY,SAAQ,WAAW;IAMpD,4BAA4B;gBAChB,KAAK,EAAE,UAAU;CAS7B;AAED;;;;GAIG;AACH,qBAAa,wBAAyB,SAAQ,WAAW;IACxD,8FAA8F;gBAClF,KAAK,EAAE,UAAU;IAI7B;;;;;;;;;OASG;IACH,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,yBAAyB;CAc3D;AAwtBD;;;;;GAKG;AACH,qBAAa,iBAAkB,SAAQ,WAAW;IAIjD;;;OAGG;gBACS,cAAc,EAAE,mBAAmB;IAO/C;;;;;OAKG;IACH,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,aAAa,CAAC,EAAE,yBAAyB;CAYvE;AAED;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,WAAW;IAM5C;;;OAGG;gBACS,MAAM,EAAE,iBAAiB,GAAG,eAAe,EAAE,cAAc,EAAE,mBAAmB;IAc5F;;;;;;;;OAQG;IACH,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,SAAI,EAAE,aAAa,CAAC,EAAE,yBAAyB;CAgB9E;AAED;;;;;;;;GAQG;AACH,qBAAa,2BAA4B,SAAQ,WAAW;IAsB3D,6FAA6F;IAC7F,IAAI,YAAY,kBAGf;IAED,iFAAiF;IACjF,IAAI,MAAM,YAET;IAED;;;;OAIG;gBACS,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAE,mBAAmB;IA8I7E;;;OAGG;IACH,KAAK;IAIL,8DAA8D;IAC9D,MAAM;CAuCN;AAED;;;;GAIG;AACH,8BAAsB,WAAY,SAAQ,WAAW;IAMpD,4BAA4B;gBAChB,KAAK,EAAE,UAAU;CAS7B;AAED;;;;GAIG;AACH,qBAAa,wBAAyB,SAAQ,WAAW;IACxD,8FAA8F;gBAClF,KAAK,EAAE,UAAU;IAI7B;;;;;;;;OAQG;IACH,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,yBAAyB;CAc3D;AA4cD;;;;;GAKG;AACH,qBAAa,iBAAkB,SAAQ,WAAW;IAIjD;;;OAGG;gBACS,cAAc,EAAE,mBAAmB;IAO/C;;;;;OAKG;IACH,GAAG,CAAC,WAAW,EAAE,WAAW;CAY5B;AAED;;;;;GAKG;AACH,qBAAa,iBAAkB,SAAQ,WAAW;IAMjD;;;OAGG;gBACS,cAAc,EAAE,mBAAmB;IAO/C;;;;;;;OAOG;IACG,GAAG,CAAC,WAAW,EAAE,WAAW;CAiBlC;AAED;;;;;;;;GAQG;AACH,qBAAa,2BAA4B,SAAQ,WAAW;IAsB3D,6FAA6F;IAC7F,IAAI,YAAY,kBAGf;IAED,iFAAiF;IACjF,IAAI,MAAM,YAET;IAED;;;OAGG;gBACS,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAE,mBAAmB;IAkI7E;;;OAGG;IACH,KAAK;IAIL,4DAA4D;IAC5D,MAAM;CAoBN;AAoKD;;;;GAIG;AACH,8BAAsB,cAAe,SAAQ,WAAW;IAMvD,4BAA4B;gBAChB,KAAK,EAAE,aAAa;CAShC;AAED;;;;GAIG;AACH,qBAAa,kBAAmB,SAAQ,cAAc;IAMrD,qGAAqG;gBACzF,KAAK,EAAE,aAAa;IAchC;;;;;;OAMG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM;CA0BhB"}
|
||||
Generated
Vendored
+1889
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+173
@@ -0,0 +1,173 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-present, Vanilagy and contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
/**
|
||||
* Represents descriptive (non-technical) metadata about a media file, such as title, author, date, cover art, or other
|
||||
* attached files. Common tags are normalized by Mediabunny into a uniform format, while the `raw` field can be used to
|
||||
* directly read or write the underlying metadata tags (which differ by format).
|
||||
*
|
||||
* - For MP4/QuickTime files, the metadata refers to the data in `'moov'`-level `'udta'` and `'meta'` atoms.
|
||||
* - For WebM/Matroska files, the metadata refers to the Tags and Attachments elements whose target is 50 (MOVIE).
|
||||
* - For MP3 files, the metadata refers to the ID3v2 or ID3v1 tags.
|
||||
* - For Ogg files, there is no global metadata so instead, the metadata refers to the combined metadata of all tracks,
|
||||
* in Vorbis-style comment headers.
|
||||
* - For WAVE files, the metadata refers to the chunks within the RIFF INFO chunk.
|
||||
* - For ADTS files, the metadata refers to the ID3v2 tags.
|
||||
* - For FLAC files, the metadata lives in Vorbis style in the Vorbis comment block.
|
||||
* - For MPEG-TS files, metadata tags are currently not supported.
|
||||
*
|
||||
* @group Metadata tags
|
||||
* @public
|
||||
*/
|
||||
export type MetadataTags = {
|
||||
/** Title of the media (e.g. Gangnam Style, Titanic, etc.) */
|
||||
title?: string;
|
||||
/** Short description or subtitle of the media. */
|
||||
description?: string;
|
||||
/** Primary artist(s) or creator(s) of the work. */
|
||||
artist?: string;
|
||||
/** Album, collection, or compilation the media belongs to. */
|
||||
album?: string;
|
||||
/** Main credited artist for the album/collection as a whole. */
|
||||
albumArtist?: string;
|
||||
/** Position of this track within its album or collection (1-based). */
|
||||
trackNumber?: number;
|
||||
/** Total number of tracks in the album or collection. */
|
||||
tracksTotal?: number;
|
||||
/** Disc index if the release spans multiple discs (1-based). */
|
||||
discNumber?: number;
|
||||
/** Total number of discs in the release. */
|
||||
discsTotal?: number;
|
||||
/** Genre or category describing the media's style or content (e.g. Metal, Horror, etc.) */
|
||||
genre?: string;
|
||||
/** Release, recording or creation date of the media. */
|
||||
date?: Date;
|
||||
/** Full text lyrics or transcript associated with the media. */
|
||||
lyrics?: string;
|
||||
/** Freeform notes, remarks or commentary about the media. */
|
||||
comment?: string;
|
||||
/** Embedded images such as cover art, booklet scans, artwork or preview frames. */
|
||||
images?: AttachedImage[];
|
||||
/**
|
||||
* The raw, underlying metadata tags.
|
||||
*
|
||||
* This field can be used for both reading and writing. When reading, it represents the original tags that were used
|
||||
* to derive the normalized fields, and any additional metadata that Mediabunny doesn't understand. When writing, it
|
||||
* can be used to set arbitrary metadata tags in the output file.
|
||||
*
|
||||
* The format of these tags differs per format:
|
||||
* - MP4/QuickTime: By default, the keys refer to the names of the individual atoms in the `'ilst'` atom inside the
|
||||
* `'meta'` atom, and the values are derived from the content of the `'data'` atom inside them. When a `'keys'` atom
|
||||
* is also used, then the keys reflect the keys specified there (such as `'com.apple.quicktime.version'`).
|
||||
* Additionally, any atoms within the `'udta'` atom are dumped into here, however with unknown internal format
|
||||
* (`Uint8Array`).
|
||||
* - WebM/Matroska: `SimpleTag` elements whose target is 50 (MOVIE), either containing string or `Uint8Array`
|
||||
* values. Additionally, all attached files (such as font files) are included here, where the key corresponds to
|
||||
* the FileUID and the value is an {@link AttachedFile}.
|
||||
* - MP3: The ID3v2 tags, or a single `'TAG'` key with the contents of the ID3v1 tag.
|
||||
* - ADTS: The ID3v2 tags.
|
||||
* - Ogg: The key-value string pairs from the Vorbis-style comment header (see RFC 7845, Section 5.2).
|
||||
* Additionally, the `'vendor'` key refers to the vendor string within this header.
|
||||
* - WAVE: The individual metadata chunks within the RIFF INFO chunk. Values are always ISO 8859-1 strings.
|
||||
* - FLAC: The key-value string pairs from the vorbis metadata block (see RFC 9639, Section D.2.3).
|
||||
* Additionally, the `'vendor'` key refers to the vendor string within this header.
|
||||
* - MPEG-TS: Not supported.
|
||||
*/
|
||||
raw?: Record<string, string | Uint8Array | RichImageData | AttachedFile | null>;
|
||||
};
|
||||
/**
|
||||
* An embedded image such as cover art, booklet scan, artwork or preview frame.
|
||||
*
|
||||
* @group Metadata tags
|
||||
* @public
|
||||
*/
|
||||
export type AttachedImage = {
|
||||
/** The raw image data. */
|
||||
data: Uint8Array;
|
||||
/** An RFC 6838 MIME type (e.g. image/jpeg, image/png, etc.) */
|
||||
mimeType: string;
|
||||
/** The kind or purpose of the image. */
|
||||
kind: 'coverFront' | 'coverBack' | 'unknown';
|
||||
/** The name of the image file. */
|
||||
name?: string;
|
||||
/** A description of the image. */
|
||||
description?: string;
|
||||
};
|
||||
/**
|
||||
* Image data with additional metadata.
|
||||
*
|
||||
* @group Metadata tags
|
||||
* @public
|
||||
*/
|
||||
export declare class RichImageData {
|
||||
/** The raw image data. */
|
||||
data: Uint8Array;
|
||||
/** An RFC 6838 MIME type (e.g. image/jpeg, image/png, etc.) */
|
||||
mimeType: string;
|
||||
/** Creates a new {@link RichImageData}. */
|
||||
constructor(
|
||||
/** The raw image data. */
|
||||
data: Uint8Array,
|
||||
/** An RFC 6838 MIME type (e.g. image/jpeg, image/png, etc.) */
|
||||
mimeType: string);
|
||||
}
|
||||
/**
|
||||
* A file attached to a media file.
|
||||
*
|
||||
* @group Metadata tags
|
||||
* @public
|
||||
*/
|
||||
export declare class AttachedFile {
|
||||
/** The raw file data. */
|
||||
data: Uint8Array;
|
||||
/** An RFC 6838 MIME type (e.g. image/jpeg, image/png, font/ttf, etc.) */
|
||||
mimeType?: string | undefined;
|
||||
/** The name of the file. */
|
||||
name?: string | undefined;
|
||||
/** A description of the file. */
|
||||
description?: string | undefined;
|
||||
/** Creates a new {@link AttachedFile}. */
|
||||
constructor(
|
||||
/** The raw file data. */
|
||||
data: Uint8Array,
|
||||
/** An RFC 6838 MIME type (e.g. image/jpeg, image/png, font/ttf, etc.) */
|
||||
mimeType?: string | undefined,
|
||||
/** The name of the file. */
|
||||
name?: string | undefined,
|
||||
/** A description of the file. */
|
||||
description?: string | undefined);
|
||||
}
|
||||
export declare const validateMetadataTags: (tags: MetadataTags) => void;
|
||||
export declare const metadataTagsAreEmpty: (tags: MetadataTags) => boolean;
|
||||
/**
|
||||
* Specifies a track's disposition, i.e. information about its intended usage.
|
||||
* @public
|
||||
* @group Miscellaneous
|
||||
*/
|
||||
export type TrackDisposition = {
|
||||
/**
|
||||
* Indicates that this track is eligible for automatic selection by a player; that it is the main track among other,
|
||||
* non-default tracks of the same type.
|
||||
*/
|
||||
default: boolean;
|
||||
/**
|
||||
* Indicates that players should always display this track by default, even if it goes against the user's default
|
||||
* preferences. For example, a subtitle track only containing translations of foreign-language audio.
|
||||
*/
|
||||
forced: boolean;
|
||||
/** Indicates that this track is in the content's original language. */
|
||||
original: boolean;
|
||||
/** Indicates that this track contains commentary. */
|
||||
commentary: boolean;
|
||||
/** Indicates that this track is intended for hearing-impaired users. */
|
||||
hearingImpaired: boolean;
|
||||
/** Indicates that this track is intended for visually-impaired users. */
|
||||
visuallyImpaired: boolean;
|
||||
};
|
||||
export declare const DEFAULT_TRACK_DISPOSITION: TrackDisposition;
|
||||
export declare const validateTrackDisposition: (disposition: Partial<TrackDisposition>) => void;
|
||||
//# sourceMappingURL=metadata.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../../src/metadata.ts"],"names":[],"mappings":"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"}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user