From d6c275509df2567d23f5ff73fa08bf10cac30986 Mon Sep 17 00:00:00 2001 From: Borewit Date: Fri, 12 Jul 2024 09:38:12 +0200 Subject: [PATCH] Remove remaining Buffer usages. Add lint rule to prevent global Buffer usage. --- .eslintrc.json | 10 +++++-- lib/common/RandomFileReader.ts | 4 +-- lib/dsf/DsfChunk.ts | 2 +- lib/flac/FlacParser.ts | 4 +-- lib/id3v1/ID3v1Parser.ts | 4 +-- lib/id3v2/ID3v2Token.ts | 2 +- lib/lyrics3/Lyrics3.ts | 8 ++--- lib/matroska/MatroskaParser.ts | 54 ++++++++++++++++++++-------------- lib/matroska/types.ts | 26 ++++++++-------- lib/mp4/AtomToken.ts | 43 ++++++++++++++------------- lib/mpeg/MpegParser.ts | 12 ++++---- lib/mpeg/XingTag.ts | 4 +-- lib/ogg/opus/OpusParser.ts | 6 ++-- lib/ogg/speex/Speex.ts | 2 +- lib/ogg/speex/SpeexParser.ts | 4 +-- lib/ogg/theora/Theora.ts | 2 +- lib/ogg/theora/TheoraParser.ts | 4 +-- lib/ogg/vorbis/VorbisParser.ts | 28 +++++++++++++----- lib/wav/WaveChunk.ts | 2 +- lib/wavpack/WavPackParser.ts | 6 ++-- package.json | 2 +- test/test-util.ts | 4 +-- 22 files changed, 131 insertions(+), 102 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 9bc877dfd..d857a9e0f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -48,10 +48,14 @@ }, "Symbol": { "message": "Avoid using the `Symbol` type. Did you mean `symbol`?" + }, + "Buffer": { + "message": "Do not use Node.js specific Buffer type, use Uint8Array" } } } ], + "@typescript-eslint/consistent-type-assertions": "error", "@typescript-eslint/dot-notation": "error", "@typescript-eslint/indent": [ @@ -176,11 +180,11 @@ "no-new-wrappers": "error", "no-redeclare": "error", "no-return-await": "error", - "no-restricted-syntax": [ + "no-restricted-globals": ["error", "Buffer"], + "no-restricted-imports": [ "error", { - "selector": "CallExpression[callee.object.name='Buffer'][callee.property.name='from']", - "message": "Use of Buffer.from is forbidden." + "paths": ["node:buffer"] } ], "no-sequences": "error", diff --git a/lib/common/RandomFileReader.ts b/lib/common/RandomFileReader.ts index 995495402..0849313d9 100644 --- a/lib/common/RandomFileReader.ts +++ b/lib/common/RandomFileReader.ts @@ -12,13 +12,13 @@ export class RandomFileReader implements IRandomReader { /** * Read from a given position of an abstracted file or buffer. - * @param buffer {Buffer} is the buffer that the data will be written to. + * @param buffer {Uint8Array} is the buffer that the data will be written to. * @param offset {number} is the offset in the buffer to start writing at. * @param length {number}is an integer specifying the number of bytes to read. * @param position {number} is an argument specifying where to begin reading from in the file. * @return {Promise} bytes read */ - public async randomRead(buffer: Buffer, offset: number, length: number, position: number): Promise { + public async randomRead(buffer: Uint8Array, offset: number, length: number, position: number): Promise { const result = await this.fileHandle.read(buffer, offset, length, position); return result.bytesRead; } diff --git a/lib/dsf/DsfChunk.ts b/lib/dsf/DsfChunk.ts index 78b17fb46..6d6684e79 100644 --- a/lib/dsf/DsfChunk.ts +++ b/lib/dsf/DsfChunk.ts @@ -123,7 +123,7 @@ export interface IFormatChunk { export const FormatChunk: IGetToken = { len: 40, - get: (buf: Buffer, off: number): IFormatChunk => { + get: (buf: Uint8Array, off: number): IFormatChunk => { return { formatVersion: Token.INT32_LE.get(buf, off), formatID: Token.INT32_LE.get(buf, off + 4), diff --git a/lib/flac/FlacParser.ts b/lib/flac/FlacParser.ts index 71afe9471..63aa71207 100644 --- a/lib/flac/FlacParser.ts +++ b/lib/flac/FlacParser.ts @@ -191,7 +191,7 @@ class Metadata { public static BlockHeader: IGetToken = { len: 4, - get: (buf: Buffer, off: number): IBlockHeader => { + get: (buf: Uint8Array, off: number): IBlockHeader => { return { lastBlock: util.getBit(buf, off, 7), type: util.getBitAllignedNumber(buf, off, 1, 7), @@ -207,7 +207,7 @@ class Metadata { public static BlockStreamInfo: IGetToken = { len: 34, - get: (buf: Buffer, off: number): IBlockStreamInfo => { + get: (buf: Uint8Array, off: number): IBlockStreamInfo => { return { // The minimum block size (in samples) used in the stream. minimumBlockSize: UINT16_BE.get(buf, off), diff --git a/lib/id3v1/ID3v1Parser.ts b/lib/id3v1/ID3v1Parser.ts index f97e3a641..26c8c3340 100644 --- a/lib/id3v1/ID3v1Parser.ts +++ b/lib/id3v1/ID3v1Parser.ts @@ -155,9 +155,9 @@ export class ID3v1Parser extends BasicParser { export async function hasID3v1Header(reader: IRandomReader): Promise { if (reader.fileSize >= 128) { - const tag = Buffer.alloc(3); + const tag = new Uint8Array(3); await reader.randomRead(tag, 0, tag.length, reader.fileSize - 128); - return tag.toString('latin1') === 'TAG'; + return new TextDecoder('latin1').decode(tag) === 'TAG'; } return false; } diff --git a/lib/id3v2/ID3v2Token.ts b/lib/id3v2/ID3v2Token.ts index a5af0fe10..36ca4d7d4 100644 --- a/lib/id3v2/ID3v2Token.ts +++ b/lib/id3v2/ID3v2Token.ts @@ -87,7 +87,7 @@ export interface IID3v2header { export const ID3v2Header: IGetToken = { len: 10, - get: (buf: Buffer, off): IID3v2header => { + get: (buf: Uint8Array, off): IID3v2header => { return { // ID3v2/file identifier "ID3" fileIdentifier: new Token.StringType(3, 'ascii').get(buf, off), diff --git a/lib/lyrics3/Lyrics3.ts b/lib/lyrics3/Lyrics3.ts index 7c4687bec..0641a55d6 100644 --- a/lib/lyrics3/Lyrics3.ts +++ b/lib/lyrics3/Lyrics3.ts @@ -4,12 +4,12 @@ export const endTag2 = 'LYRICS200'; export async function getLyricsHeaderLength(reader: IRandomReader): Promise { if (reader.fileSize >= 143) { - const buf = Buffer.alloc(15); + const buf = new Uint8Array(15); await reader.randomRead(buf, 0, buf.length, reader.fileSize - 143); - const txt = buf.toString('latin1'); - const tag = txt.substr(6); + const txt = new TextDecoder('latin1').decode(buf); + const tag = txt.slice(6); if (tag === endTag2) { - return parseInt(txt.substr(0, 6), 10) + 15; + return parseInt(txt.slice(0, 6), 10) + 15; } } return 0; diff --git a/lib/matroska/MatroskaParser.ts b/lib/matroska/MatroskaParser.ts index 8a6ba1cbb..923955eeb 100644 --- a/lib/matroska/MatroskaParser.ts +++ b/lib/matroska/MatroskaParser.ts @@ -9,6 +9,7 @@ import { DataType, IContainerType, IHeader, IMatroskaDoc, ITree, TargetType, Tra import { IOptions, ITrackInfo } from '../type.js'; import { ITokenParser } from '../ParserFactory.js'; +import * as Token from 'token-types'; const debug = initDebug('music-metadata:parser:matroska'); @@ -33,7 +34,7 @@ export class MatroskaParser extends BasicParser { this.parserMap.set(DataType.uint, e => this.readUint(e)); this.parserMap.set(DataType.string, e => this.readString(e)); this.parserMap.set(DataType.binary, e => this.readBuffer(e)); - this.parserMap.set(DataType.uid, async e => await this.readUint(e) === 1); + this.parserMap.set(DataType.uid, async e => this.readBuffer(e)); this.parserMap.set(DataType.bool, e => this.readFlag(e)); this.parserMap.set(DataType.float, e => this.readFloat(e)); } @@ -57,11 +58,11 @@ export class MatroskaParser extends BasicParser { const info = matroska.segment.info; if (info) { - const timecodeScale = info.timecodeScale ? info.timecodeScale : 1000000; + const timecodeScale = info.timecodeScale ? info.timecodeScale :1000000; if (typeof info.duration === 'number') { const duration = info.duration * timecodeScale / 1000000000; await this.addTag('segment:title', info.title); - this.metadata.setFormat('duration', duration); + this.metadata.setFormat('duration', Number(duration)); } } @@ -176,7 +177,7 @@ export class MatroskaParser extends BasicParser { return tree; } - private async readVintData(maxLength: number): Promise { + private async readVintData(maxLength: number): Promise { const msb = await this.tokenizer.peekNumber(UINT8); let mask = 0x80; let oc = 1; @@ -189,7 +190,7 @@ export class MatroskaParser extends BasicParser { ++oc; mask >>= 1; } - const id = Buffer.alloc(oc); + const id = new Uint8Array(oc); await this.tokenizer.readBuffer(id); return id; } @@ -198,23 +199,12 @@ export class MatroskaParser extends BasicParser { const id = await this.readVintData(this.ebmlMaxIDLength); const lenField = await this.readVintData(this.ebmlMaxSizeLength); lenField[0] ^= 0x80 >> (lenField.length - 1); - const nrLen = Math.min(6, lenField.length); // JavaScript can max read 6 bytes integer return { - id: id.readUIntBE(0, id.length), - len: lenField.readUIntBE(lenField.length - nrLen, nrLen) + id: MatroskaParser.readUIntBE(id, id.length), + len: MatroskaParser.readUIntBE(lenField, lenField.length) }; } - private isMaxValue(vintData: Buffer) { - if (vintData.length === this.ebmlMaxSizeLength) { - for (let n = 1; n < this.ebmlMaxSizeLength; ++n) { - if (vintData[n] !== 0xff) return false; - } - return true; - } - return false; - } - private async readFloat(e: IHeader) { switch (e.len) { case 0: @@ -236,8 +226,7 @@ export class MatroskaParser extends BasicParser { private async readUint(e: IHeader): Promise { const buf = await this.readBuffer(e); - const nrLen = Math.min(6, e.len); // JavaScript can max read 6 bytes integer - return buf.readUIntBE(e.len - nrLen, nrLen); + return MatroskaParser.readUIntBE(buf, e.len); } private async readString(e: IHeader): Promise { @@ -245,8 +234,8 @@ export class MatroskaParser extends BasicParser { return rawString.replace(/\x00.*$/g, ''); } - private async readBuffer(e: IHeader): Promise { - const buf = Buffer.alloc(e.len); + private async readBuffer(e: IHeader): Promise { + const buf = new Uint8Array(e.len); await this.tokenizer.readBuffer(buf); return buf; } @@ -254,4 +243,25 @@ export class MatroskaParser extends BasicParser { private async addTag(tagId: string, value: any): Promise { await this.metadata.addTag('matroska', tagId, value); } + + private static readUIntBE(buf: Uint8Array, len: number): number { + return Number(MatroskaParser.readUIntBeAsBigInt(buf, len)); + } + + /** + * Reeds an unsigned integer from a big endian buffer of length `len` + * @param buf Buffer to decode from + * @param len Number of bytes + * @private + */ + private static readUIntBeAsBigInt(buf: Uint8Array, len: number): bigint { + const normalizedNumber = new Uint8Array(8); + const cleanNumber = buf.subarray(0, len); + try { + normalizedNumber.set(cleanNumber, 8 - len); + return Token.UINT64_BE.get(normalizedNumber, 0); + } catch(error) { + return BigInt(-1); + } + } } diff --git a/lib/matroska/types.ts b/lib/matroska/types.ts index fe3b8225e..8d6decef4 100644 --- a/lib/matroska/types.ts +++ b/lib/matroska/types.ts @@ -12,12 +12,12 @@ export interface IElementType { readonly multiple?: boolean; } -export interface IContainerType { [id: number]: IElementType; } +export interface IContainerType { [id: number]: IElementType; } -export interface ITree { [name: string]: string | number | boolean | Buffer | ITree | ITree[]; } +export interface ITree { [name: string]: string | number | boolean | Uint8Array | ITree | ITree[]; } export interface ISeekHead { - id?: Buffer; + id?: Uint8Array; position?: number; } @@ -26,7 +26,7 @@ export interface IMetaSeekInformation { } export interface ISegmentInformation { - uid?: Buffer; + uid?: Uint8Array; timecodeScale?: number; duration?: number; dateUTC?: number; @@ -36,7 +36,7 @@ export interface ISegmentInformation { } export interface ITrackEntry { - uid?: Buffer; + uid?: Uint8Array; trackNumber?: number; trackType?: TrackType; audio?: ITrackAudio; @@ -49,7 +49,7 @@ export interface ITrackEntry { name?: string; language?: string; codecID?: string; - codecPrivate?: Buffer; + codecPrivate?: Uint8Array; codecName?: string; codecSettings?: string; codecInfoUrl?: string; @@ -67,7 +67,7 @@ export interface ITrackVideo { displayHeight?: number; displayUnit?: number; aspectRatioType?: number; - colourSpace?: Buffer; + colourSpace?: Uint8Array; gammaValue?: number; } @@ -75,7 +75,7 @@ export interface ITrackAudio { samplingFrequency?: number; outputSamplingFrequency?: number; channels?: number; - channelPositions?: Buffer; + channelPositions?: Uint8Array; bitDepth?: number; } @@ -102,7 +102,7 @@ export interface ICueReference { export interface ISimpleTag { name?: string; 'string'?: string; - binary?: Buffer; + binary?: Uint8Array; language?: string; default?: boolean; } @@ -128,9 +128,9 @@ export enum TrackType { } export interface ITarget { - trackUID?: Buffer; - chapterUID?: Buffer; - attachmentUID?: Buffer; + trackUID?: Uint8Array; + chapterUID?: Uint8Array; + attachmentUID?: Uint8Array; targetTypeValue?: TargetType; targetType?: string; } @@ -152,7 +152,7 @@ export interface IAttachmedFile { description?: string; name: string; mimeType: string; - data: Buffer; + data: Uint8Array; uid: string; } diff --git a/lib/mp4/AtomToken.ts b/lib/mp4/AtomToken.ts index f7ee6dcc0..6750082e5 100644 --- a/lib/mp4/AtomToken.ts +++ b/lib/mp4/AtomToken.ts @@ -137,7 +137,7 @@ export interface IMovieHeaderAtom extends IVersionAndFlags { export const Header: IToken = { len: 8, - get: (buf: Buffer, off: number): IAtomHeader => { + get: (buf: Uint8Array, off: number): IAtomHeader => { const length = Token.UINT32_BE.get(buf, off); if (length < 0) throw new Error('Invalid atom header length'); @@ -148,7 +148,7 @@ export const Header: IToken = { }; }, - put: (buf: Buffer, off: number, hdr: IAtomHeader) => { + put: (buf: Uint8Array, off: number, hdr: IAtomHeader) => { Token.UINT32_BE.put(buf, off, Number(hdr.length)); return FourCcToken.put(buf, off + 4, hdr.name); } @@ -162,7 +162,7 @@ export const ExtendedSize: IToken = Token.UINT64_BE; export const ftyp: IGetToken = { len: 4, - get: (buf: Buffer, off: number): IAtomFtyp => { + get: (buf: Uint8Array, off: number): IAtomFtyp => { return { type: new Token.StringType(4, 'ascii').get(buf, off) }; @@ -172,7 +172,7 @@ export const ftyp: IGetToken = { export const tkhd: IGetToken = { len: 4, - get: (buf: Buffer, off: number): IAtomFtyp => { + get: (buf: Uint8Array, off: number): IAtomFtyp => { return { type: new Token.StringType(4, 'ascii').get(buf, off) }; @@ -185,7 +185,7 @@ export const tkhd: IGetToken = { export const mhdr: IGetToken = { len: 8, - get: (buf: Buffer, off: number): IMovieHeaderAtom => { + get: (buf: Uint8Array, off: number): IMovieHeaderAtom => { return { version: Token.UINT8.get(buf, off), flags: Token.UINT24_BE.get(buf, off + 1), @@ -204,6 +204,7 @@ export abstract class FixedLengthAtom { * * @param {number} len Length as specified in the size field * @param {number} expLen Total length of sum of specified fields in the standard + * @param atomId Atom ID */ protected constructor(public len: number, expLen: number, atomId: string) { if (len < expLen) { @@ -235,7 +236,7 @@ export interface IAtomMdhd extends IAtomMxhd { const SecondsSinceMacEpoch: IGetToken = { len: 4, - get: (buf: Buffer, off: number): Date => { + get: (buf: Uint8Array, off: number): Date => { const secondsSinceUnixEpoch = Token.UINT32_BE.get(buf, off) - 2082844800; return new Date(secondsSinceUnixEpoch * 1000); }}; @@ -252,7 +253,7 @@ export class MdhdAtom extends FixedLengthAtom implements IGetToken { super(len, 24, 'mdhd'); } - public get(buf: Buffer, off: number): IAtomMdhd { + public get(buf: Uint8Array, off: number): IAtomMdhd { return { version: Token.UINT8.get(buf, off + 0), flags: Token.UINT24_BE.get(buf, off + 1), @@ -275,7 +276,7 @@ export class MvhdAtom extends FixedLengthAtom implements IGetToken { super(len, 100, 'mvhd'); } - public get(buf: Buffer, off: number): IAtomMvhd { + public get(buf: Uint8Array, off: number): IAtomMvhd { return { version: Token.UINT8.get(buf, off), flags: Token.UINT24_BE.get(buf, off + 1), @@ -367,7 +368,7 @@ export class NameAtom implements IGetToken { public constructor(public len: number) { } - public get(buf: Buffer, off: number): INameAtom { + public get(buf: Uint8Array, off: number): INameAtom { return { version: Token.UINT8.get(buf, off), flags: Token.UINT24_BE.get(buf, off + 1), @@ -438,7 +439,7 @@ export class TrackHeaderAtom implements IGetToken { public constructor(public len: number) { } - public get(buf: Buffer, off: number): ITrackHeaderAtom { + public get(buf: Uint8Array, off: number): ITrackHeaderAtom { return { version: Token.UINT8.get(buf, off), flags: Token.UINT24_BE.get(buf, off + 1), @@ -469,7 +470,7 @@ interface IAtomStsdHeader extends IVersionAndFlags { const stsdHeader: IGetToken = { len: 8, - get: (buf: Buffer, off: number): IAtomStsdHeader => { + get: (buf: Uint8Array, off: number): IAtomStsdHeader => { return { version: Token.UINT8.get(buf, off), flags: Token.UINT24_BE.get(buf, off + 1), @@ -501,7 +502,7 @@ class SampleDescriptionTable implements IGetToken { public constructor(public len: number) { } - public get(buf: Buffer, off: number): ISampleDescription { + public get(buf: Uint8Array, off: number): ISampleDescription { return { dataFormat: FourCcToken.get(buf, off), @@ -520,7 +521,7 @@ export class StsdAtom implements IGetToken { public constructor(public len: number) { } - public get(buf: Buffer, off: number): IAtomStsd { + public get(buf: Uint8Array, off: number): IAtomStsd { const header = stsdHeader.get(buf, off); off += stsdHeader.len; @@ -555,7 +556,7 @@ export const SoundSampleDescriptionVersion: IGetToken = { len: 12, - get(buf: Buffer, off: number): ISoundSampleDescriptionV0 { + get(buf: Uint8Array, off: number): ISoundSampleDescriptionV0 { return { numAudioChannels: Token.INT16_BE.get(buf, off + 0), sampleSize: Token.INT16_BE.get(buf, off + 2), @@ -609,7 +610,7 @@ class SimpleTableAtom implements IGetToken> { public constructor(public len: number, private token: IGetToken) { } - public get(buf: Buffer, off: number): ITableAtom { + public get(buf: Uint8Array, off: number): ITableAtom { const nrOfEntries = Token.INT32_BE.get(buf, off + 4); @@ -631,7 +632,7 @@ export const TimeToSampleToken: IGetToken = { len: 8, - get(buf: Buffer, off: number): ITimeToSampleToken { + get(buf: Uint8Array, off: number): ITimeToSampleToken { return { count: Token.INT32_BE.get(buf, off + 0), duration: Token.INT32_BE.get(buf, off + 4) @@ -663,7 +664,7 @@ export const SampleToChunkToken: IGetToken = { len: 12, - get(buf: Buffer, off: number): ISampleToChunk { + get(buf: Uint8Array, off: number): ISampleToChunk { return { firstChunk: Token.INT32_BE.get(buf, off), samplesPerChunk: Token.INT32_BE.get(buf, off + 4), @@ -698,7 +699,7 @@ export class StszAtom implements IGetToken { public constructor(public len: number) { } - public get(buf: Buffer, off: number): IStszAtom { + public get(buf: Uint8Array, off: number): IStszAtom { const nrOfEntries = Token.INT32_BE.get(buf, off + 8); @@ -730,14 +731,14 @@ export class ChapterText implements IGetToken { public constructor(public len: number) { } - public get(buf: Buffer, off: number): string { + public get(buf: Uint8Array, off: number): string { const titleLen = Token.INT16_BE.get(buf, off + 0); const str = new Token.StringType(titleLen, 'utf-8'); return str.get(buf, off + 2); } } -function readTokenTable(buf: Buffer, token: IGetToken, off: number, remainingLen: number, numberOfEntries: number): T[] { +function readTokenTable(buf: Uint8Array, token: IGetToken, off: number, remainingLen: number, numberOfEntries: number): T[] { debug(`remainingLen=${remainingLen}, numberOfEntries=${numberOfEntries} * token-len=${token.len}`); diff --git a/lib/mpeg/MpegParser.ts b/lib/mpeg/MpegParser.ts index b72605e29..3c6bcdad5 100644 --- a/lib/mpeg/MpegParser.ts +++ b/lib/mpeg/MpegParser.ts @@ -138,7 +138,7 @@ class MpegFrameHeader { public mp4ChannelConfig: MPEG4ChannelConfiguration; - public constructor(buf: Buffer, off: number) { + public constructor(buf: Uint8Array, off: number) { // B(20,19): MPEG Audio versionIndex ID this.versionIndex = common.getBitAllignedNumber(buf, off + 1, 3, 2); // C(18,17): Layer description @@ -184,7 +184,7 @@ class MpegFrameHeader { return [null, 4, 1, 1][this.layer]; } - private parseMpegHeader(buf: Buffer, off: number): void { + private parseMpegHeader(buf: Uint8Array, off: number): void { this.container = 'MPEG'; // E(15,12): Bitrate index this.bitrateIndex = common.getBitAllignedNumber(buf, off + 2, 0, 4); @@ -224,7 +224,7 @@ class MpegFrameHeader { } } - private parseAdtsHeader(buf: Buffer, off: number): void { + private parseAdtsHeader(buf: Uint8Array, off: number): void { debug(`layer=0 => ADTS`); this.version = this.versionIndex === 2 ? 4 : 2; this.container = 'ADTS/MPEG-' + this.version; @@ -291,7 +291,7 @@ export class MpegParser extends AbstractID3Parser { private calculateEofDuration: boolean = false; private samplesPerFrame; - private buf_frame_header = Buffer.alloc(4); + private buf_frame_header = new Uint8Array(4); /** * Number of bytes already parsed since beginning of stream / file @@ -299,7 +299,7 @@ export class MpegParser extends AbstractID3Parser { private mpegOffset: number; private syncPeek = { - buf: Buffer.alloc(maxPeekLen), + buf: new Uint8Array(maxPeekLen), len: 0 }; @@ -494,7 +494,7 @@ export class MpegParser extends AbstractID3Parser { } private async parseAdts(header: MpegFrameHeader): Promise { - const buf = Buffer.alloc(3); + const buf = new Uint8Array(3); await this.tokenizer.readBuffer(buf); header.frameLength += common.getBitAllignedNumber(buf, 0, 0, 11); this.totalDataLength += header.frameLength; diff --git a/lib/mpeg/XingTag.ts b/lib/mpeg/XingTag.ts index 839097afe..f655334c1 100644 --- a/lib/mpeg/XingTag.ts +++ b/lib/mpeg/XingTag.ts @@ -35,7 +35,7 @@ export interface IXingInfoTag { */ streamSize?: number, - toc?: Buffer; + toc?: Uint8Array; /** * the number of header data bytes (from original file) @@ -79,7 +79,7 @@ export async function readXingHeader(tokenizer: ITokenizer): Promise { + protected async parseFullPage(pageData: Uint8Array): Promise { const magicSignature = new Token.StringType(8, 'ascii').get(pageData, 0); switch (magicSignature) { diff --git a/lib/ogg/speex/Speex.ts b/lib/ogg/speex/Speex.ts index d56671e76..c46833f1d 100644 --- a/lib/ogg/speex/Speex.ts +++ b/lib/ogg/speex/Speex.ts @@ -42,7 +42,7 @@ export const Header: IGetToken = { len: 80, - get: (buf: Buffer, off) => { + get: (buf: Uint8Array, off) => { return { speex: new Token.StringType(8, 'ascii').get(buf, off + 0), diff --git a/lib/ogg/speex/SpeexParser.ts b/lib/ogg/speex/SpeexParser.ts index 1b15ecc3b..2f4083fa2 100644 --- a/lib/ogg/speex/SpeexParser.ts +++ b/lib/ogg/speex/SpeexParser.ts @@ -25,9 +25,9 @@ export class SpeexParser extends VorbisParser { /** * Parse first Speex Ogg page * @param {IPageHeader} header - * @param {Buffer} pageData + * @param {Uint8Array} pageData */ - protected parseFirstPage(header: IPageHeader, pageData: Buffer) { + protected parseFirstPage(header: IPageHeader, pageData: Uint8Array) { debug('First Ogg/Speex page'); const speexHeader = Speex.Header.get(pageData, 0); this.metadata.setFormat('codec', `Speex ${speexHeader.version}`); diff --git a/lib/ogg/theora/Theora.ts b/lib/ogg/theora/Theora.ts index 10dfdaf92..eaaac134d 100644 --- a/lib/ogg/theora/Theora.ts +++ b/lib/ogg/theora/Theora.ts @@ -31,7 +31,7 @@ export interface IIdentificationHeader { export const IdentificationHeader: IGetToken = { len: 42, - get: (buf: Buffer, off): IIdentificationHeader => { + get: (buf: Uint8Array, off): IIdentificationHeader => { return { id: new Token.StringType(7, 'ascii').get(buf, off), vmaj: Token.UINT8.get(buf, off + 7), diff --git a/lib/ogg/theora/TheoraParser.ts b/lib/ogg/theora/TheoraParser.ts index 870f893aa..db491b54c 100644 --- a/lib/ogg/theora/TheoraParser.ts +++ b/lib/ogg/theora/TheoraParser.ts @@ -22,7 +22,7 @@ export class TheoraParser implements Ogg.IPageConsumer { * @param header Ogg Page Header * @param pageData Page data */ - public async parsePage(header: Ogg.IPageHeader, pageData: Buffer): Promise { + public async parsePage(header: Ogg.IPageHeader, pageData: Uint8Array): Promise { if (header.headerType.firstPage) { await this.parseFirstPage(header, pageData); } @@ -41,7 +41,7 @@ export class TheoraParser implements Ogg.IPageConsumer { * @param {IPageHeader} header * @param {Buffer} pageData */ - protected async parseFirstPage(header: Ogg.IPageHeader, pageData: Buffer): Promise { + protected async parseFirstPage(header: Ogg.IPageHeader, pageData: Uint8Array): Promise { debug('First Ogg/Theora page'); this.metadata.setFormat('codec', 'Theora'); const idHeader = IdentificationHeader.get(pageData, 0); diff --git a/lib/ogg/vorbis/VorbisParser.ts b/lib/ogg/vorbis/VorbisParser.ts index 719d7ff9c..2b08785dd 100644 --- a/lib/ogg/vorbis/VorbisParser.ts +++ b/lib/ogg/vorbis/VorbisParser.ts @@ -16,7 +16,7 @@ const debug = debugInit('music-metadata:parser:ogg:vorbis1'); */ export class VorbisParser implements IPageConsumer { - private pageSegments: Buffer[] = []; + private pageSegments: Uint8Array[] = []; constructor(protected metadata: INativeMetadataCollector, protected options: IOptions) { } @@ -26,7 +26,7 @@ export class VorbisParser implements IPageConsumer { * @param header Ogg Page Header * @param pageData Page data */ - public async parsePage(header: IPageHeader, pageData: Buffer): Promise { + public async parsePage(header: IPageHeader, pageData: Uint8Array): Promise { if (header.headerType.firstPage) { this.parseFirstPage(header, pageData); } else { @@ -39,7 +39,7 @@ export class VorbisParser implements IPageConsumer { if (header.headerType.lastPage || !header.headerType.continued) { // Flush page segments if (this.pageSegments.length > 0) { - const fullPage = Buffer.concat(this.pageSegments); + const fullPage = VorbisParser.mergeUint8Arrays(this.pageSegments); await this.parseFullPage(fullPage); } // Reset page segments @@ -51,11 +51,23 @@ export class VorbisParser implements IPageConsumer { } } + private static mergeUint8Arrays(arrays: Uint8Array[]): Uint8Array { + const totalSize = arrays.reduce((acc, e) => acc + e.length, 0); + const merged = new Uint8Array(totalSize); + + arrays.forEach((array, i, _arrays) => { + const offset = _arrays.slice(0, i).reduce((acc, e) => acc + e.length, 0); + merged.set(array, offset); + }); + + return merged; + } + public async flush(): Promise { - await this.parseFullPage(Buffer.concat(this.pageSegments)); + await this.parseFullPage(VorbisParser.mergeUint8Arrays(this.pageSegments)); } - public async parseUserComment(pageData: Buffer, offset: number): Promise { + public async parseUserComment(pageData: Uint8Array, offset: number): Promise { const decoder = new VorbisDecoder(pageData, offset); const tag = decoder.parseUserComment(); @@ -92,7 +104,7 @@ export class VorbisParser implements IPageConsumer { * @param header * @param pageData */ - protected parseFirstPage(header: IPageHeader, pageData: Buffer) { + protected parseFirstPage(header: IPageHeader, pageData: Uint8Array) { this.metadata.setFormat('codec', 'Vorbis I'); debug('Parse first page'); // Parse Vorbis common header @@ -109,7 +121,7 @@ export class VorbisParser implements IPageConsumer { } else throw new Error('First Ogg page should be type 1: the identification header'); } - protected async parseFullPage(pageData: Buffer): Promise { + protected async parseFullPage(pageData: Uint8Array): Promise { // New page const commonHeader = CommonHeader.get(pageData, 0); debug('Parse full page: type=%s, byteLength=%s', commonHeader.packetType, pageData.byteLength); @@ -127,7 +139,7 @@ export class VorbisParser implements IPageConsumer { /** * Ref: https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-840005.2 */ - protected async parseUserCommentList(pageData: Buffer, offset: number): Promise { + protected async parseUserCommentList(pageData: Uint8Array, offset: number): Promise { const strLen = Token.UINT32_LE.get(pageData, offset); offset += 4; diff --git a/lib/wav/WaveChunk.ts b/lib/wav/WaveChunk.ts index 9f1dd46f6..647a1989f 100644 --- a/lib/wav/WaveChunk.ts +++ b/lib/wav/WaveChunk.ts @@ -91,7 +91,7 @@ export class FactChunk implements IGetToken { this.len = header.chunkSize; } - public get(buf: Buffer, off: number): IFactChunk { + public get(buf: Uint8Array, off: number): IFactChunk { return { dwSampleLength: Token.UINT32_LE.get(buf, off) }; diff --git a/lib/wavpack/WavPackParser.ts b/lib/wavpack/WavPackParser.ts index 359f0d3ef..36a337c98 100644 --- a/lib/wavpack/WavPackParser.ts +++ b/lib/wavpack/WavPackParser.ts @@ -6,6 +6,7 @@ import { BasicParser } from '../common/BasicParser.js'; import { IBlockHeader, IMetadataId, WavPack } from './WavPackToken.js'; import initDebug from 'debug'; +import { uint8ArrayToHex } from 'uint8array-extras'; const debug = initDebug('music-metadata:parser:WavPack'); @@ -67,13 +68,14 @@ export class WavPackParser extends BasicParser { /** * Ref: http://www.wavpack.com/WavPack5FileFormat.pdf, 3.0 Metadata Sub-blocks + * @param header Header * @param remainingLength */ private async parseMetadataSubBlock(header: IBlockHeader, remainingLength: number): Promise { while (remainingLength > WavPack.MetadataIdToken.len) { const id = await this.tokenizer.readToken(WavPack.MetadataIdToken); const dataSizeInWords = await this.tokenizer.readNumber(id.largeBlock ? Token.UINT24_LE : Token.UINT8); - const data = Buffer.alloc(dataSizeInWords * 2 - (id.isOddSize ? 1 : 0)); + const data = new Uint8Array(dataSizeInWords * 2 - (id.isOddSize ? 1 : 0)); await this.tokenizer.readBuffer(data); debug(`Metadata Sub-Blocks functionId=0x${id.functionId.toString(16)}, id.largeBlock=${id.largeBlock},data-size=${data.length}`); switch (id.functionId) { @@ -100,7 +102,7 @@ export class WavPackParser extends BasicParser { break; case 0x2f: // ID_BLOCK_CHECKSUM - debug(`ID_BLOCK_CHECKSUM: checksum=${data.toString('hex')}`); + debug(`ID_BLOCK_CHECKSUM: checksum=${uint8ArrayToHex(data)}`); break; default: diff --git a/package.json b/package.json index 52574fbfb..62267197a 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "compile-test": "tsc -p test", "compile-doc": "tsc -p doc-gen", "compile": "npm run compile-src && npm run compile-test && npm run compile-doc", - "eslint": "eslint lib/**/*.ts --ignore-pattern lib/**/*.d.ts example/typescript/**/*.ts test/**/*.ts doc-gen/**/*.ts", + "eslint": "npx eslint lib/**/*.ts --ignore-pattern lib/**/*.d.ts example/typescript/**/*.ts test/**/*.ts doc-gen/**/*.ts", "lint-md": "remark -u preset-lint-markdown-style-guide .", "lint": "npm run lint-md && npm run eslint", "test": "mocha", diff --git a/test/test-util.ts b/test/test-util.ts index 3902e9842..2a9673ee7 100644 --- a/test/test-util.ts +++ b/test/test-util.ts @@ -81,9 +81,9 @@ describe('shared utility functionality', () => { ]; it('should be able to encode FourCC token', () => { - const buffer = Buffer.alloc(4); + const buffer = new Uint8Array(4); FourCcToken.put(buffer, 0, 'abcd'); - t.deepEqual(buffer.toString('latin1'), 'abcd'); + t.deepEqual(new TextDecoder('latin1').decode(buffer), 'abcd'); }); });