mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-29 03:43:50 +02:00
* add a test * recognize as input format * scaffold flac demuxer * implement getting metadata * Implement mime type * read all metadata + deduplicate stubs * Read first packet * copyright headers * read the first packet * Get entire first packet, work on advancing * iterate over all samples * testable with bun * stub out metadata support * parse descriptive metadata * All in 1 file seems more appropriate to the philosophy * some parameters are not needed anymore all within 1 class * skip over bytes we are sure are not the syncword * run prettier * timestamp is determined based on passed blocks, not maximumBlockSize * no binary search needed! * simplifications * Finish demuxer reading sequentially * more tests + add a file with a seektable * don't throw if (this.audioInfo.minimumBlockSize !== this.audioInfo.maximumBlockSize * Add docs * Update format compatibility table * Simplification * confirm conversion is working * Finish * Resolve TODO comment * Support images (read-only) * Returning description as Uint8Array * Cleanup of demuxer * Misc renames * Object on same line * Resolve first batch of comments * Throw errors on corrupt blocks, correctly use requestSlice() * Fix description field * Explain why last frame is a bit shorter * Add FLAC to README * Put track backings below demuxer * convert to methods * Reorder container checking * getBlockSize() -> readBlockSize() * bitStream -> bitstream * better naming for bytes * use .skip() * Don't return blockSize twice in readFlacFrameHeader * Use enum + switch to distinguish Flac block types * Use else-if * Use else-if * Compressed switch statement * readCodedNumber * Update flac-misc.ts * We don't need the bits variable at all * reorder functions in flac-demuxer * Handle gracefully not being able to load another sample * Update flac-demuxer.ts * blockingbit null * use async instead of promise.resolve * use binary search * Replace recursion with while loop * Add mutex to getPacket() * Load more data not in getPacketAtIndex, but outside * Apply suggestion from @Vanilagy Co-authored-by: David P. <[email protected]> * computeDuration() reads last packet * Update flac-demuxer.ts * share vorbis comment reading logic * reuse vorbis comment writing logic, set vendor always to "Mediabunny" * flush after writign * Use FileSlice.tempFromBytes * assert !== null * fixing nitpicks * apply suggestions * fix ogg * We are now muxing images * apply suggestion * Update src/flac/flac-muxer.ts Co-authored-by: David P. <[email protected]> * apply suggestion * readSampleRate() * seek outside writeHeader() * mention vorbis metadata + add to metadatatags comment * should be able to -> can * Add test in metadata tags * Add test for packets being byte identical after remuxing * compare to null * no casting to uint8array * make test pass * Update flac-muxer.ts * Call validateAudioChunkMetadata() and validateAndNormalizeTimestamp() * `onFrame` option * emit frames using onFrame * Run prettier over files * Fix FLAC PICTURE block logic, small other changes * Update docs * Remove .only modifier * fix remuxing and add test * Don't throw error if parsing fails in header, since hitting a syncword might just be coincidential * Fix remaining type errors --------- Co-authored-by: David P. <[email protected]>
165 lines
4.1 KiB
TypeScript
165 lines
4.1 KiB
TypeScript
/*!
|
|
* Copyright (c) 2025-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';
|
|
import { FileSlice, readBytes, readU16Be, readU8 } from '../reader';
|
|
|
|
type BlockSizeOrUncommon = number | 'uncommon-u16' | 'uncommon-u8';
|
|
type SampleRateOrUncommon =
|
|
| number
|
|
| 'uncommon-u8'
|
|
| 'uncommon-u16'
|
|
| 'uncommon-u16-10';
|
|
|
|
// https://www.rfc-editor.org/rfc/rfc9639.html#name-block-size-bits
|
|
export const getBlockSizeOrUncommon = (bits: number): BlockSizeOrUncommon | null => {
|
|
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: number,
|
|
streamInfoSampleRate: number,
|
|
): SampleRateOrUncommon | null => {
|
|
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: FileSlice): number => {
|
|
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: number[] = [];
|
|
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: FileSlice,
|
|
blockSizeBits: BlockSizeOrUncommon,
|
|
) => {
|
|
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: FileSlice,
|
|
sampleRateOrUncommon: 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: Uint8Array) => {
|
|
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;
|
|
};
|