Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

music-metadata

Package Overview
Dependencies
Maintainers
1
Versions
252
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

music-metadata - npm Package Compare versions

Comparing version 10.1.0 to 10.2.0

lib/ebml/EbmlIterator.d.ts

5

lib/aiff/AiffParser.js

@@ -60,3 +60,2 @@ import * as Token from 'token-types';

async readData(header) {
var _a;
switch (header.chunkID) {

@@ -74,3 +73,3 @@ case 'COMM': { // The Common Chunk

if (common.compressionName || common.compressionType) {
this.metadata.setFormat('codec', (_a = common.compressionName) !== null && _a !== void 0 ? _a : compressionTypes[common.compressionType]);
this.metadata.setFormat('codec', common.compressionName ?? compressionTypes[common.compressionType]);
}

@@ -102,3 +101,3 @@ return header.chunkSize;

const value = await this.tokenizer.readToken(new Token.StringType(header.chunkSize, 'ascii'));
const values = value.split('\0').map(v => v.trim()).filter(v => v === null || v === void 0 ? void 0 : v.length);
const values = value.split('\0').map(v => v.trim()).filter(v => v?.length);
await Promise.all(values.map(v => this.metadata.addTag('AIFF', header.chunkID, v)));

@@ -105,0 +104,0 @@ return header.chunkSize;

6

lib/common/MetadataCollector.js

@@ -57,6 +57,5 @@ import { TrackType } from '../type.js';

setFormat(key, value) {
var _a;
debug(`format: ${key} = ${value}`);
this.format[key] = value; // as any to override readonly
if ((_a = this.opts) === null || _a === void 0 ? void 0 : _a.observer) {
if (this.opts?.observer) {
this.opts.observer({ metadata: this, tag: { type: 'format', id: key, value } });

@@ -243,3 +242,2 @@ }

setGenericTag(tagType, tag) {
var _a;
debug(`common.${tag.id} = ${tag.value}`);

@@ -275,3 +273,3 @@ const prio0 = this.commonOrigin[tag.id] || 1000;

}
if ((_a = this.opts) === null || _a === void 0 ? void 0 : _a.observer) {
if (this.opts?.observer) {
this.opts.observer({ metadata: this, tag: { type: 'common', id: tag.id, value: tag.value } });

@@ -278,0 +276,0 @@ }

@@ -57,3 +57,2 @@ import * as Token from 'token-types';

static readFrameData(uint8Array, frameHeader, majorVer, includeCovers, warningCollector) {
var _a, _b;
const frameParser = new FrameParser(majorVer, warningCollector);

@@ -65,6 +64,6 @@ switch (majorVer) {

case 4:
if ((_a = frameHeader.flags) === null || _a === void 0 ? void 0 : _a.format.unsynchronisation) {
if (frameHeader.flags?.format.unsynchronisation) {
uint8Array = ID3v2Parser.removeUnsyncBytes(uint8Array);
}
if ((_b = frameHeader.flags) === null || _b === void 0 ? void 0 : _b.format.data_length_indicator) {
if (frameHeader.flags?.format.data_length_indicator) {
uint8Array = uint8Array.slice(4, uint8Array.length);

@@ -71,0 +70,0 @@ }

@@ -1,2 +0,2 @@

import { type IContainerType } from './types.js';
import { type IElementType } from '../ebml/types.js';
/**

@@ -8,2 +8,2 @@ * Elements of document type description

*/
export declare const elements: IContainerType;
export declare const matroskaDtd: IElementType;

@@ -1,2 +0,2 @@

import { DataType } from './types.js';
import { DataType } from '../ebml/types.js';
/**

@@ -8,146 +8,152 @@ * Elements of document type description

*/
export const elements = {
440786851: {
name: 'ebml',
container: {
17030: { name: 'ebmlVersion', value: DataType.uint }, // 5.1.1
17143: { name: 'ebmlReadVersion', value: DataType.uint }, // 5.1.2
17138: { name: 'ebmlMaxIDWidth', value: DataType.uint }, // 5.1.3
17139: { name: 'ebmlMaxSizeWidth', value: DataType.uint }, // 5.1.4
17026: { name: 'docType', value: DataType.string }, // 5.1.5
17031: { name: 'docTypeVersion', value: DataType.uint }, // 5.1.6
17029: { name: 'docTypeReadVersion', value: DataType.uint } // 5.1.7
}
},
// Matroska segments
408125543: {
name: 'segment',
container: {
// Meta Seek Information
290298740: {
name: 'seekHead',
container: {
19899: {
name: 'seek',
container: {
21419: { name: 'seekId', value: DataType.binary },
21420: { name: 'seekPosition', value: DataType.uint }
export const matroskaDtd = {
name: 'dtd',
container: {
0x1a45dfa3: {
name: 'ebml',
container: {
0x4286: { name: 'ebmlVersion', value: DataType.uint }, // 5.1.1
0x42f7: { name: 'ebmlReadVersion', value: DataType.uint }, // 5.1.2
0x42f2: { name: 'ebmlMaxIDWidth', value: DataType.uint }, // 5.1.3
0x42f3: { name: 'ebmlMaxSizeWidth', value: DataType.uint }, // 5.1.4
0x4282: { name: 'docType', value: DataType.string }, // 5.1.5
0x4287: { name: 'docTypeVersion', value: DataType.uint }, // 5.1.6
0x4285: { name: 'docTypeReadVersion', value: DataType.uint } // 5.1.7
}
},
// Matroska segments
0x18538067: {
name: 'segment',
container: {
// Meta Seek Information (also known as MetaSeek)
0x114d9b74: {
name: 'seekHead',
container: {
0x4dbb: {
name: 'seek',
multiple: true,
container: {
0x53ab: { name: 'id', value: DataType.binary },
0x53ac: { name: 'position', value: DataType.uint }
}
}
}
}
},
// Segment Information
357149030: {
name: 'info',
container: {
29604: { name: 'uid', value: DataType.uid },
29572: { name: 'filename', value: DataType.string },
3979555: { name: 'prevUID', value: DataType.uid },
3965867: { name: 'prevFilename', value: DataType.string },
4110627: { name: 'nextUID', value: DataType.uid },
4096955: { name: 'nextFilename', value: DataType.string },
2807729: { name: 'timecodeScale', value: DataType.uint },
17545: { name: 'duration', value: DataType.float },
17505: { name: 'dateUTC', value: DataType.uint },
31657: { name: 'title', value: DataType.string },
19840: { name: 'muxingApp', value: DataType.string },
22337: { name: 'writingApp', value: DataType.string }
}
},
// Cluster
524531317: {
name: 'cluster',
multiple: true,
container: {
231: { name: 'timecode', value: DataType.uid },
163: { name: 'unknown', value: DataType.binary },
167: { name: 'position', value: DataType.uid },
171: { name: 'prevSize', value: DataType.uid }
}
},
// Track
374648427: {
name: 'tracks',
container: {
174: {
name: 'entries',
multiple: true,
container: {
215: { name: 'trackNumber', value: DataType.uint },
29637: { name: 'uid', value: DataType.uid },
131: { name: 'trackType', value: DataType.uint },
185: { name: 'flagEnabled', value: DataType.bool },
136: { name: 'flagDefault', value: DataType.bool },
21930: { name: 'flagForced', value: DataType.bool }, // extended
156: { name: 'flagLacing', value: DataType.bool },
28135: { name: 'minCache', value: DataType.uint },
28136: { name: 'maxCache', value: DataType.uint },
2352003: { name: 'defaultDuration', value: DataType.uint },
2306383: { name: 'timecodeScale', value: DataType.float },
21358: { name: 'name', value: DataType.string },
2274716: { name: 'language', value: DataType.string },
134: { name: 'codecID', value: DataType.string },
25506: { name: 'codecPrivate', value: DataType.binary },
2459272: { name: 'codecName', value: DataType.string },
3839639: { name: 'codecSettings', value: DataType.string },
3883072: { name: 'codecInfoUrl', value: DataType.string },
2536000: { name: 'codecDownloadUrl', value: DataType.string },
170: { name: 'codecDecodeAll', value: DataType.bool },
28587: { name: 'trackOverlay', value: DataType.uint },
// Video
224: {
name: 'video',
container: {
154: { name: 'flagInterlaced', value: DataType.bool },
21432: { name: 'stereoMode', value: DataType.uint },
176: { name: 'pixelWidth', value: DataType.uint },
186: { name: 'pixelHeight', value: DataType.uint },
21680: { name: 'displayWidth', value: DataType.uint },
21690: { name: 'displayHeight', value: DataType.uint },
21683: { name: 'aspectRatioType', value: DataType.uint },
3061028: { name: 'colourSpace', value: DataType.uint },
3126563: { name: 'gammaValue', value: DataType.float }
}
},
// Audio
225: {
name: 'audio',
container: {
181: { name: 'samplingFrequency', value: DataType.float },
30901: { name: 'outputSamplingFrequency', value: DataType.float },
159: { name: 'channels', value: DataType.uint }, // https://www.matroska.org/technical/specs/index.html
148: { name: 'channels', value: DataType.uint },
32123: { name: 'channelPositions', value: DataType.binary },
25188: { name: 'bitDepth', value: DataType.uint }
}
},
// Content Encoding
28032: {
name: 'contentEncodings',
container: {
25152: {
name: 'contentEncoding',
container: {
20529: { name: 'order', value: DataType.uint },
20530: { name: 'scope', value: DataType.bool },
20531: { name: 'type', value: DataType.uint },
20532: {
name: 'contentEncoding',
container: {
16980: { name: 'contentCompAlgo', value: DataType.uint },
16981: { name: 'contentCompSettings', value: DataType.binary }
}
},
20533: {
name: 'contentEncoding',
container: {
18401: { name: 'contentEncAlgo', value: DataType.uint },
18402: { name: 'contentEncKeyID', value: DataType.binary },
18403: { name: 'contentSignature ', value: DataType.binary },
18404: { name: 'ContentSigKeyID ', value: DataType.binary },
18405: { name: 'contentSigAlgo ', value: DataType.uint },
18406: { name: 'contentSigHashAlgo ', value: DataType.uint }
}
},
25188: { name: 'bitDepth', value: DataType.uint }
},
// Segment Information
0x1549a966: {
name: 'info',
container: {
0x73a4: { name: 'uid', value: DataType.uid },
0x7384: { name: 'filename', value: DataType.string },
0x3cb923: { name: 'prevUID', value: DataType.uid },
0x3c83ab: { name: 'prevFilename', value: DataType.string },
0x3eb923: { name: 'nextUID', value: DataType.uid },
0x3e83bb: { name: 'nextFilename', value: DataType.string },
0x2ad7b1: { name: 'timecodeScale', value: DataType.uint },
0x4489: { name: 'duration', value: DataType.float },
0x4461: { name: 'dateUTC', value: DataType.uint },
0x7ba9: { name: 'title', value: DataType.string },
0x4d80: { name: 'muxingApp', value: DataType.string },
0x5741: { name: 'writingApp', value: DataType.string }
}
},
// Cluster
0x1f43b675: {
name: 'cluster',
multiple: true,
container: {
0xe7: { name: 'timecode', value: DataType.uid },
0x58d7: { name: 'silentTracks ', multiple: true },
0xa7: { name: 'position', value: DataType.uid },
0xab: { name: 'prevSize', value: DataType.uid },
0xa0: { name: 'blockGroup' },
0xa3: { name: 'simpleBlock' }
}
},
// Track
0x1654ae6b: {
name: 'tracks',
container: {
0xae: {
name: 'entries',
multiple: true,
container: {
0xd7: { name: 'trackNumber', value: DataType.uint },
0x73c5: { name: 'uid', value: DataType.uid },
0x83: { name: 'trackType', value: DataType.uint },
0xb9: { name: 'flagEnabled', value: DataType.bool },
0x88: { name: 'flagDefault', value: DataType.bool },
0x55aa: { name: 'flagForced', value: DataType.bool }, // extended
0x9c: { name: 'flagLacing', value: DataType.bool },
0x6de7: { name: 'minCache', value: DataType.uint },
0x6de8: { name: 'maxCache', value: DataType.uint },
0x23e383: { name: 'defaultDuration', value: DataType.uint },
0x23314f: { name: 'timecodeScale', value: DataType.float },
0x536e: { name: 'name', value: DataType.string },
0x22b59c: { name: 'language', value: DataType.string },
0x86: { name: 'codecID', value: DataType.string },
0x63a2: { name: 'codecPrivate', value: DataType.binary },
0x258688: { name: 'codecName', value: DataType.string },
0x3a9697: { name: 'codecSettings', value: DataType.string },
0x3b4040: { name: 'codecInfoUrl', value: DataType.string },
0x26b240: { name: 'codecDownloadUrl', value: DataType.string },
0xaa: { name: 'codecDecodeAll', value: DataType.bool },
0x6fab: { name: 'trackOverlay', value: DataType.uint },
// Video
0xe0: {
name: 'video',
container: {
0x9a: { name: 'flagInterlaced', value: DataType.bool },
0x53b8: { name: 'stereoMode', value: DataType.uint },
0xb0: { name: 'pixelWidth', value: DataType.uint },
0xba: { name: 'pixelHeight', value: DataType.uint },
0x54b0: { name: 'displayWidth', value: DataType.uint },
0x54ba: { name: 'displayHeight', value: DataType.uint },
0x54b3: { name: 'aspectRatioType', value: DataType.uint },
0x2eb524: { name: 'colourSpace', value: DataType.uint },
0x2fb523: { name: 'gammaValue', value: DataType.float }
}
},
// Audio
0xe1: {
name: 'audio',
container: {
0xb5: { name: 'samplingFrequency', value: DataType.float },
0x78b5: { name: 'outputSamplingFrequency', value: DataType.float },
0x9f: { name: 'channels', value: DataType.uint }, // https://www.matroska.org/technical/specs/index.html
0x94: { name: 'channels', value: DataType.uint },
0x7d7b: { name: 'channelPositions', value: DataType.binary },
0x6264: { name: 'bitDepth', value: DataType.uint }
}
},
// Content Encoding
0x6d80: {
name: 'contentEncodings',
container: {
0x6240: {
name: 'contentEncoding',
container: {
0x5031: { name: 'order', value: DataType.uint },
0x5032: { name: 'scope', value: DataType.bool },
0x5033: { name: 'type', value: DataType.uint },
0x5034: {
name: 'contentEncoding',
container: {
0x4254: { name: 'contentCompAlgo', value: DataType.uint },
0x4255: { name: 'contentCompSettings', value: DataType.binary }
}
},
0x5035: {
name: 'contentEncoding',
container: {
0x47e1: { name: 'contentEncAlgo', value: DataType.uint },
0x47e2: { name: 'contentEncKeyID', value: DataType.binary },
0x47e3: { name: 'contentSignature ', value: DataType.binary },
0x47e4: { name: 'ContentSigKeyID ', value: DataType.binary },
0x47e5: { name: 'contentSigAlgo ', value: DataType.uint },
0x47e6: { name: 'contentSigHashAlgo ', value: DataType.uint }
}
},
0x6264: { name: 'bitDepth', value: DataType.uint }
}
}

@@ -159,28 +165,28 @@ }

}
}
},
// Cueing Data
475249515: {
name: 'cues',
container: {
187: {
name: 'cuePoint',
container: {
179: { name: 'cueTime', value: DataType.uid },
183: {
name: 'positions',
container: {
247: { name: 'track', value: DataType.uint },
241: { name: 'clusterPosition', value: DataType.uint },
21368: { name: 'blockNumber', value: DataType.uint },
234: { name: 'codecState', value: DataType.uint },
219: {
name: 'reference', container: {
150: { name: 'time', value: DataType.uint },
151: { name: 'cluster', value: DataType.uint },
21343: { name: 'number', value: DataType.uint },
235: { name: 'codecState', value: DataType.uint }
}
},
240: { name: 'relativePosition', value: DataType.uint } // extended
},
// Cueing Data
0x1c53bb6b: {
name: 'cues',
container: {
0xbb: {
name: 'cuePoint',
container: {
0xb3: { name: 'cueTime', value: DataType.uid },
0xb7: {
name: 'positions',
container: {
0xf7: { name: 'track', value: DataType.uint },
0xf1: { name: 'clusterPosition', value: DataType.uint },
0x5378: { name: 'blockNumber', value: DataType.uint },
0xea: { name: 'codecState', value: DataType.uint },
0xdb: {
name: 'reference', container: {
0x96: { name: 'time', value: DataType.uint },
0x97: { name: 'cluster', value: DataType.uint },
0x535f: { name: 'number', value: DataType.uint },
0xeb: { name: 'codecState', value: DataType.uint }
}
},
0xf0: { name: 'relativePosition', value: DataType.uint } // extended
}
}

@@ -190,43 +196,44 @@ }

}
}
},
// Attachment
423732329: {
name: 'attachments',
container: {
24999: {
name: 'attachedFiles',
multiple: true,
container: {
18046: { name: 'description', value: DataType.string },
18030: { name: 'name', value: DataType.string },
18016: { name: 'mimeType', value: DataType.string },
18012: { name: 'data', value: DataType.binary },
18094: { name: 'uid', value: DataType.uid }
},
// Attachment
0x1941a469: {
name: 'attachments',
container: {
0x61a7: {
name: 'attachedFiles',
multiple: true,
container: {
0x467e: { name: 'description', value: DataType.string },
0x466e: { name: 'name', value: DataType.string },
0x4660: { name: 'mimeType', value: DataType.string },
0x465c: { name: 'data', value: DataType.binary },
0x46ae: { name: 'uid', value: DataType.uid }
}
}
}
}
},
// Chapters
272869232: {
name: 'chapters',
container: {
17849: {
name: 'editionEntry',
container: {
182: {
name: 'chapterAtom',
container: {
29636: { name: 'uid', value: DataType.uid },
145: { name: 'timeStart', value: DataType.uint },
146: { name: 'timeEnd', value: DataType.uid },
152: { name: 'hidden', value: DataType.bool },
17816: { name: 'enabled', value: DataType.uid },
143: { name: 'track', container: {
137: { name: 'trackNumber', value: DataType.uid },
128: {
name: 'display', container: {
133: { name: 'string', value: DataType.string },
17276: { name: 'language ', value: DataType.string },
17278: { name: 'country ', value: DataType.string }
},
// Chapters
0x1043a770: {
name: 'chapters',
container: {
0x45b9: {
name: 'editionEntry',
container: {
0xb6: {
name: 'chapterAtom',
container: {
0x73c4: { name: 'uid', value: DataType.uid },
0x91: { name: 'timeStart', value: DataType.uint },
0x92: { name: 'timeEnd', value: DataType.uid },
0x98: { name: 'hidden', value: DataType.bool },
0x4598: { name: 'enabled', value: DataType.uid },
0x8f: {
name: 'track', container: {
0x89: { name: 'trackNumber', value: DataType.uid },
0x80: {
name: 'display', container: {
0x85: { name: 'string', value: DataType.string },
0x437c: { name: 'language ', value: DataType.string },
0x437e: { name: 'country ', value: DataType.string }
}
}

@@ -240,34 +247,34 @@ }

}
}
},
// Tagging
307544935: {
name: 'tags',
container: {
29555: {
name: 'tag',
multiple: true,
container: {
25536: {
name: 'target',
container: {
25541: { name: 'tagTrackUID', value: DataType.uid },
25540: { name: 'tagChapterUID', value: DataType.uint },
25542: { name: 'tagAttachmentUID', value: DataType.uid },
25546: { name: 'targetType', value: DataType.string }, // extended
26826: { name: 'targetTypeValue', value: DataType.uint }, // extended
25545: { name: 'tagEditionUID', value: DataType.uid } // extended
},
// Tagging
0x1254c367: {
name: 'tags',
container: {
0x7373: {
name: 'tag',
multiple: true,
container: {
0x63c0: {
name: 'target',
container: {
0x63c5: { name: 'tagTrackUID', value: DataType.uid },
0x63c4: { name: 'tagChapterUID', value: DataType.uint },
0x63c6: { name: 'tagAttachmentUID', value: DataType.uid },
0x63ca: { name: 'targetType', value: DataType.string }, // extended
0x68ca: { name: 'targetTypeValue', value: DataType.uint }, // extended
0x63c9: { name: 'tagEditionUID', value: DataType.uid } // extended
}
},
0x67c8: {
name: 'simpleTags',
multiple: true,
container: {
0x45a3: { name: 'name', value: DataType.string },
0x4487: { name: 'string', value: DataType.string },
0x4485: { name: 'binary', value: DataType.binary },
0x447a: { name: 'language', value: DataType.string }, // extended
0x447b: { name: 'languageIETF', value: DataType.string }, // extended
0x4484: { name: 'default', value: DataType.bool } // extended
}
}
},
26568: {
name: 'simpleTags',
multiple: true,
container: {
17827: { name: 'name', value: DataType.string },
17543: { name: 'string', value: DataType.string },
17541: { name: 'binary', value: DataType.binary },
17530: { name: 'language', value: DataType.string }, // extended
17531: { name: 'languageIETF', value: DataType.string }, // extended
17540: { name: 'default', value: DataType.bool } // extended
}
}

@@ -274,0 +281,0 @@ }

@@ -1,2 +0,2 @@

import { type ITokenizer } from 'strtok3';
import type { ITokenizer } from 'strtok3';
import type { INativeMetadataCollector } from '../common/MetadataCollector.js';

@@ -14,8 +14,10 @@ import { BasicParser } from '../common/BasicParser.js';

export declare class MatroskaParser extends BasicParser {
private padding;
private parserMap;
private ebmlMaxIDLength;
private ebmlMaxSizeLength;
constructor();
private seekHead;
private seekHeadOffset;
/**
* Use index to skip multiple segment/cluster elements at once.
* Significant performance impact
*/
private flagUseIndexToSkipClusters;
/**
* Initialize parser with output (metadata), input (tokenizer) & parsing options (options).

@@ -28,19 +30,3 @@ * @param {INativeMetadataCollector} metadata Output

parse(): Promise<void>;
private parseContainer;
private readVintData;
private readElement;
private readFloat;
private readFlag;
private readUint;
private readString;
private readBuffer;
private addTag;
private static readUIntBE;
/**
* 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;
}

@@ -1,8 +0,6 @@

import { Float32_BE, Float64_BE, StringType, UINT8 } from 'token-types';
import initDebug from 'debug';
import { EndOfStreamError } from 'strtok3';
import { BasicParser } from '../common/BasicParser.js';
import * as matroskaDtd from './MatroskaDtd.js';
import { DataType, TargetType, TrackType } from './types.js';
import * as Token from 'token-types';
import { matroskaDtd } from './MatroskaDtd.js';
import { TargetType, TrackType } from './types.js';
import { EbmlIterator, ParseAction } from '../ebml/EbmlIterator.js';
const debug = initDebug('music-metadata:parser:matroska');

@@ -18,13 +16,9 @@ /**

constructor() {
super();
this.padding = 0;
this.parserMap = new Map();
this.ebmlMaxIDLength = 4;
this.ebmlMaxSizeLength = 8;
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) => this.readBuffer(e));
this.parserMap.set(DataType.bool, e => this.readFlag(e));
this.parserMap.set(DataType.float, e => this.readFloat(e));
super(...arguments);
this.seekHeadOffset = 0;
/**
* Use index to skip multiple segment/cluster elements at once.
* Significant performance impact
*/
this.flagUseIndexToSkipClusters = false;
}

@@ -39,204 +33,125 @@ /**

super.init(metadata, tokenizer, options);
this.flagUseIndexToSkipClusters = options.mkvUseIndex ?? false;
return this;
}
async parse() {
var _a;
const containerSize = (_a = this.tokenizer.fileInfo.size) !== null && _a !== void 0 ? _a : Number.MAX_SAFE_INTEGER;
const matroska = await this.parseContainer(matroskaDtd.elements, containerSize, []);
this.metadata.setFormat('container', `EBML/${matroska.ebml.docType}`);
if (matroska.segment) {
const info = matroska.segment.info;
if (info) {
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', Number(duration));
}
}
const audioTracks = matroska.segment.tracks;
if (audioTracks === null || audioTracks === void 0 ? void 0 : audioTracks.entries) {
audioTracks.entries.forEach(entry => {
const stream = {
codecName: entry.codecID.replace('A_', '').replace('V_', ''),
codecSettings: entry.codecSettings,
flagDefault: entry.flagDefault,
flagLacing: entry.flagLacing,
flagEnabled: entry.flagEnabled,
language: entry.language,
name: entry.name,
type: entry.trackType,
audio: entry.audio,
video: entry.video
};
this.metadata.addStreamInfo(stream);
});
const audioTrack = audioTracks.entries
.filter(entry => entry.trackType === TrackType.audio)
.reduce((acc, cur) => {
if (!acc)
return cur;
if (cur.flagDefault && !acc.flagDefault)
return cur;
if (cur.trackNumber < acc.trackNumber)
return cur;
return acc;
}, null);
if (audioTrack) {
this.metadata.setFormat('codec', audioTrack.codecID.replace('A_', ''));
this.metadata.setFormat('sampleRate', audioTrack.audio.samplingFrequency);
this.metadata.setFormat('numberOfChannels', audioTrack.audio.channels);
}
if (matroska.segment.tags) {
await Promise.all(matroska.segment.tags.tag.map(async (tag) => {
const target = tag.target;
const targetType = (target === null || target === void 0 ? void 0 : target.targetTypeValue) ? TargetType[target.targetTypeValue] : ((target === null || target === void 0 ? void 0 : target.targetType) ? target.targetType : 'track');
await Promise.all(tag.simpleTags.map(async (simpleTag) => {
const value = simpleTag.string ? simpleTag.string : simpleTag.binary;
await this.addTag(`${targetType}:${simpleTag.name}`, value);
}));
}));
}
if (matroska.segment.attachments) {
await Promise.all(matroska.segment.attachments.attachedFiles
.filter(file => file.mimeType.startsWith('image/'))
.map(file => this.addTag('picture', {
data: file.data,
format: file.mimeType,
description: file.description,
name: file.name
})));
}
}
}
}
async parseContainer(container, posDone, path) {
const tree = {};
while (this.tokenizer.position < posDone) {
let element;
try {
element = await this.readElement();
}
catch (error) {
if (error instanceof EndOfStreamError) {
break;
}
throw error;
}
const type = container[element.id];
if (type) {
debug(`Element: name=${type.name}, container=${!!type.container}`);
if (type.container) {
const res = await this.parseContainer(type.container, element.len >= 0 ? this.tokenizer.position + element.len : -1, path.concat([type.name]));
if (type.multiple) {
if (!tree[type.name]) {
tree[type.name] = [];
const containerSize = this.tokenizer.fileInfo.size ?? Number.MAX_SAFE_INTEGER;
const matroskaIterator = new EbmlIterator(this.tokenizer);
debug('Initializing DTD end MatroskaIterator');
await matroskaIterator.iterate(matroskaDtd, containerSize, {
startNext: (element) => {
switch (element.id) {
// case 0x1f43b675: // cluster
case 0x1c53bb6b: // Cueing Data
debug(`Skip element: name=${element.name}, id=0x${element.id.toString(16)}`);
return ParseAction.IgnoreElement;
case 0x1f43b675: // cluster
if (this.flagUseIndexToSkipClusters && this.seekHead) {
const index = this.seekHead.seek.find(index => index.position + this.seekHeadOffset > this.tokenizer.position);
if (index) {
// Go to next index position
const ignoreSize = index.position + this.seekHeadOffset - this.tokenizer.position;
debug(`Use index to go to next position, ignoring ${ignoreSize} bytes`);
this.tokenizer.ignore(ignoreSize);
return ParseAction.SkipElement;
}
}
tree[type.name].push(res);
}
else {
tree[type.name] = res;
}
return ParseAction.IgnoreElement;
default:
return ParseAction.ReadNext;
}
else {
const parser = this.parserMap.get(type.value);
if (typeof parser === 'function') {
tree[type.name] = await parser(element);
}
}
}
else {
},
elementValue: async (element, value, offset) => {
debug(`Received: name=${element.name}, value=${value}`);
switch (element.id) {
case 0xec: // void
this.padding += element.len;
await this.tokenizer.ignore(element.len);
case 0x4282: // docType
this.metadata.setFormat('container', `EBML/${value}`);
break;
default:
debug(`parseEbml: path=${path.join('/')}, unknown element: id=${element.id.toString(16)}`);
this.padding += element.len;
await this.tokenizer.ignore(element.len);
case 0x114d9b74:
this.seekHead = value;
this.seekHeadOffset = offset;
break;
case 0x1549a966:
{ // Info (Segment Information)
const info = value;
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', Number(duration));
}
}
break;
case 0x1654ae6b:
{ // tracks
const audioTracks = value;
if (audioTracks?.entries) {
audioTracks.entries.forEach(entry => {
const stream = {
codecName: entry.codecID.replace('A_', '').replace('V_', ''),
codecSettings: entry.codecSettings,
flagDefault: entry.flagDefault,
flagLacing: entry.flagLacing,
flagEnabled: entry.flagEnabled,
language: entry.language,
name: entry.name,
type: entry.trackType,
audio: entry.audio,
video: entry.video
};
this.metadata.addStreamInfo(stream);
});
const audioTrack = audioTracks.entries
.filter(entry => entry.trackType === TrackType.audio)
.reduce((acc, cur) => {
if (!acc)
return cur;
if (cur.flagDefault && !acc.flagDefault)
return cur;
if (cur.trackNumber < acc.trackNumber)
return cur;
return acc;
}, null);
if (audioTrack) {
this.metadata.setFormat('codec', audioTrack.codecID.replace('A_', ''));
this.metadata.setFormat('sampleRate', audioTrack.audio.samplingFrequency);
this.metadata.setFormat('numberOfChannels', audioTrack.audio.channels);
}
}
}
break;
case 0x1254c367:
{ // tags
const tags = value;
await Promise.all(tags.tag.map(async (tag) => {
const target = tag.target;
const targetType = target?.targetTypeValue ? TargetType[target.targetTypeValue] : (target?.targetType ? target.targetType : 'track');
await Promise.all(tag.simpleTags.map(async (simpleTag) => {
const value = simpleTag.string ? simpleTag.string : simpleTag.binary;
await this.addTag(`${targetType}:${simpleTag.name}`, value);
}));
}));
}
break;
case 0x1941a469:
{ // attachments
const attachments = value;
await Promise.all(attachments.attachedFiles
.filter(file => file.mimeType.startsWith('image/'))
.map(file => this.addTag('picture', {
data: file.data,
format: file.mimeType,
description: file.description,
name: file.name
})));
}
break;
}
}
}
return tree;
});
}
async readVintData(maxLength) {
const msb = await this.tokenizer.peekNumber(UINT8);
let mask = 0x80;
let oc = 1;
// Calculate VINT_WIDTH
while ((msb & mask) === 0) {
if (oc > maxLength) {
throw new Error('VINT value exceeding maximum size');
}
++oc;
mask >>= 1;
}
const id = new Uint8Array(oc);
await this.tokenizer.readBuffer(id);
return id;
}
async readElement() {
const id = await this.readVintData(this.ebmlMaxIDLength);
const lenField = await this.readVintData(this.ebmlMaxSizeLength);
lenField[0] ^= 0x80 >> (lenField.length - 1);
return {
id: MatroskaParser.readUIntBE(id, id.length),
len: MatroskaParser.readUIntBE(lenField, lenField.length)
};
}
async readFloat(e) {
switch (e.len) {
case 0:
return 0.0;
case 4:
return this.tokenizer.readNumber(Float32_BE);
case 8:
return this.tokenizer.readNumber(Float64_BE);
case 10:
return this.tokenizer.readNumber(Float64_BE);
default:
throw new Error(`Invalid IEEE-754 float length: ${e.len}`);
}
}
async readFlag(e) {
return (await this.readUint(e)) === 1;
}
async readUint(e) {
const buf = await this.readBuffer(e);
return MatroskaParser.readUIntBE(buf, e.len);
}
async readString(e) {
const rawString = await this.tokenizer.readToken(new StringType(e.len, 'utf-8'));
return rawString.replace(/\x00.*$/g, '');
}
async readBuffer(e) {
const buf = new Uint8Array(e.len);
await this.tokenizer.readBuffer(buf);
return buf;
}
async addTag(tagId, value) {
await this.metadata.addTag('matroska', tagId, value);
}
static readUIntBE(buf, len) {
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
*/
static readUIntBeAsBigInt(buf, len) {
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);
}
}
}
//# sourceMappingURL=MatroskaParser.js.map

@@ -1,33 +0,9 @@

export interface IHeader {
id: number;
len: number;
import type { IEbmlDoc } from '../ebml/types.js';
export interface ISeek {
id: Uint8Array;
position: number;
}
export declare enum DataType {
'string' = 0,
uint = 1,
uid = 2,
bool = 3,
binary = 4,
float = 5
}
export interface IElementType<T> {
readonly name: string;
readonly value?: DataType;
readonly container?: IContainerType;
readonly multiple?: boolean;
}
export interface IContainerType {
[id: number]: IElementType<string | number | boolean | Uint8Array>;
}
export interface ITree {
[name: string]: string | number | boolean | Uint8Array | ITree | ITree[];
}
export type ValueType = string | number | Uint8Array | boolean | ITree | ITree[];
export interface ISeekHead {
id?: Uint8Array;
position?: number;
seek: ISeek[];
}
export interface IMetaSeekInformation {
seekHeads: ISeekHead[];
}
export interface ISegmentInformation {

@@ -154,4 +130,4 @@ uid?: Uint8Array;

export interface IMatroskaSegment {
metaSeekInfo?: IMetaSeekInformation;
seekHeads?: ISeekHead[];
metaSeekInfo?: ISeekHead;
seekHeads?: ISeek[];
info?: ISegmentInformation;

@@ -163,16 +139,4 @@ tracks?: ITrackElement;

}
export interface IEbmlElements {
version?: number;
readVersion?: number;
maxIDWidth?: number;
maxSizeWidth?: number;
docType?: string;
docTypeVersion?: number;
docTypeReadVersion?: number;
}
export interface IEbmlDoc {
ebml: IEbmlElements;
}
export interface IMatroskaDoc extends IEbmlDoc {
segment: IMatroskaSegment;
}

@@ -1,10 +0,1 @@

export var DataType;
(function (DataType) {
DataType[DataType["string"] = 0] = "string";
DataType[DataType["uint"] = 1] = "uint";
DataType[DataType["uid"] = 2] = "uid";
DataType[DataType["bool"] = 3] = "bool";
DataType[DataType["binary"] = 4] = "binary";
DataType[DataType["float"] = 5] = "float";
})(DataType || (DataType = {}));
export var TargetType;

@@ -11,0 +2,0 @@ (function (TargetType) {

@@ -69,3 +69,3 @@ import { fileTypeFromBuffer } from 'file-type';

const metadata = new MetadataCollector(opts);
await parser.init(metadata, tokenizer, opts !== null && opts !== void 0 ? opts : {}).parse();
await parser.init(metadata, tokenizer, opts ?? {}).parse();
return metadata.toCommonMetadata();

@@ -72,0 +72,0 @@ }

@@ -547,2 +547,11 @@ import type { TagType } from './common/GenericTagTypes.js';

observer?: Observer;
/**
* In Matroska based files, use the _SeekHead_ element index to skip _segment/cluster_ elements.
* By default, disabled
* Can have a significant performance impact if enabled.
* Possible side effect can be that certain metadata maybe skipped, depending on the index.
* If there is no _SeekHead_ element present in the Matroska file, this flag has no effect
* Ref: https://www.matroska.org/technical/diagram.html
*/
mkvUseIndex?: boolean;
}

@@ -549,0 +558,0 @@ export interface IApeHeader extends IOptions {

{
"name": "music-metadata",
"description": "Music metadata parser for Node.js, supporting virtual any audio and tag format.",
"version": "10.1.0",
"version": "10.2.0",
"author": {

@@ -88,3 +88,3 @@ "name": "Borewit",

"lint-ts": "biome check",
"lint-md": "yarn run remark -u preset-lint-markdown-style-guide .",
"lint-md": "yarn run remark -u remark-preset-lint-consistent .",
"lint": "yarn run lint-ts && yarn run lint-md",

@@ -101,5 +101,5 @@ "test": "mocha",

"debug": "^4.3.4",
"file-type": "^19.4.0",
"file-type": "^19.4.1",
"media-typer": "^1.1.0",
"strtok3": "^8.0.5",
"strtok3": "^8.1.0",
"token-types": "^6.0.0",

@@ -116,3 +116,3 @@ "uint8array-extras": "^1.4.0"

"@types/mocha": "^10.0.7",
"@types/node": "^22.2.0",
"@types/node": "^22.3.0",
"c8": "^10.1.2",

@@ -126,3 +126,3 @@ "chai": "^5.1.1",

"remark-cli": "^12.0.1",
"remark-preset-lint-markdown-style-guide": "^6.0.0",
"remark-preset-lint-consistent": "^6.0.0",
"ts-node": "^10.9.2",

@@ -129,0 +129,0 @@ "typescript": "^5.5.3"

@@ -14,12 +14,28 @@ [![Node.js CI](https://github.com/Borewit/music-metadata/actions/workflows/nodejs-ci.yml/badge.svg?branch=master)](https://github.com/Borewit/music-metadata/actions?query=branch%3Amaster)

Stream and file based music metadata parser for [node.js](https://nodejs.org/) and browser projects.
Supports any common audio and tagging format.
Key features:
* **Comprehensive Format Support**: Supports popular audio formats like MP3, MP4, FLAC, Ogg, WAV, AIFF, and more.
* **Extensive Metadata Extraction**: Extracts detailed metadata, including ID3v1, ID3v2, APE, Vorbis, and iTunes/MP4 tags.
* **Streaming Support**: Efficiently handles large audio files by reading metadata from streams, making it suitable for server-side and browser-based applications.
* **Promise-Based API**: Provides a modern, promise-based API for easy integration into asynchronous workflows.
* **Cross-Platform**: Works in both [Node.js](https://nodejs.org/) and browser environments with the help of bundlers like [Webpack](https://webpack.js.org/) or [Rollup](https://rollupjs.org/introduction/).
The [`music-metadata`](https://github.com/Borewit/music-metadata) module is ideal for developers working on media applications, music players, or any project that requires access to detailed audio file metadata.
## Compatibility
Module: version 8 migrated from [CommonJS](https://en.wikipedia.org/wiki/CommonJS) to [pure ECMAScript Module (ESM)](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).
JavaScript is compliant with [ECMAScript 2019 (ES10)](https://en.wikipedia.org/wiki/ECMAScript#10th_Edition_%E2%80%93_ECMAScript_2019).
Requires Node.js ≥ 16 engine.
Primarily designed for [Node.js](https://nodejs.org/), but has also been designed for browser compatibility.
The distributed JavaScript codebase is compliant with the [ECMAScript 2020 (11th Edition)](https://en.wikipedia.org/wiki/ECMAScript_version_history#11th_Edition_%E2%80%93_ECMAScript_2020) standard.
This module requires a [Node.js ≥ 16](https://nodejs.org/en/about/previous-releases) engine.
It can also be used in a browser environment when bundled with a module bundler.
## Sponsor
If you appreciate my work and want to support the development of open-source projects like [music-metadata](https://github.com/Borewit/music-metadata), [file-type](https://github.com/sindresorhus/file-type), and [listFix()](https://github.com/Borewit/listFix), consider becoming a sponsor or making a small contribution.
Your support helps sustain ongoing development and improvements.
[Become a sponsor to Borewit](https://github.com/sponsors/Borewit)
or
<a href="https://www.buymeacoffee.com/borewit" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy me A coffee" height="41" width="174"></a>
## Features

@@ -85,7 +101,2 @@

### Sponsor
[Become a sponsor to Borewit](https://github.com/sponsors/Borewit)
<a href="https://www.buymeacoffee.com/borewit" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy me A coffee" height="41" width="174"></a>
## Dependencies

@@ -197,3 +208,3 @@

```ts
parseStream(stream: Stream.Readable, fileInfo?: IFileInfo | string, opts?: IOptions = {}): Promise<IAudioMetadata>`
parseStream(stream: Stream.Readable, fileInfo?: IFileInfo | string, opts?: IOptions = {}): Promise<IAudioMetadata>
```

@@ -333,3 +344,7 @@

- `skipPostHeaders? boolean` default: `false`, if set to `true`, it will not search all the entire track for additional headers. Only recommenced to use in combination with streams.
- `includeChapters` default: `false`, if set to `true`, it will parse chapters (currently only MP4 files). _experimental functionality_
- `mkvUseIndex` default: `false`, if set to `true`, in Matroska based files, use the _SeekHead_ element index to skip _segment/cluster_ elements..
_experimental functionality_
Can have a significant performance impact if enabled.
Possible side effect can be that certain metadata maybe skipped, depending on the index.
If there is no _SeekHead_ element present in the Matroska file, this flag has no effect.

@@ -369,3 +384,3 @@ Although in most cases duration is included, in some cases it requires `music-metadata` parsing the entire file.

To support advanced containers like [Matroska](https://wikipedia.org/wiki/Matroska) or [MPEG-4](https://en.wikipedia.org/wiki/MPEG-4), which may contain multiple audio and video tracks, the **experimental*- `metadata.trackInfo` has been added,
To support advanced containers like [Matroska](https://wikipedia.org/wiki/Matroska) or [MPEG-4](https://en.wikipedia.org/wiki/MPEG-4), which may contain multiple audio and video tracks, the **experimental**- `metadata.trackInfo` has been added,

@@ -491,25 +506,25 @@ `metadata.trackInfo` is either `undefined` or has an **array** of [trackInfo](#trackinfo)

2. Use async/await
1. Use async/await
Use [async/await](https://javascript.info/async-await)
Use [async/await](https://javascript.info/async-await)
```js
import { parseFile } from 'music-metadata';
```js
import { parseFile } from 'music-metadata';
// it is required to declare the function 'async' to allow the use of await
async function parseFiles(audioFiles) {
// it is required to declare the function 'async' to allow the use of await
async function parseFiles(audioFiles) {
for (const audioFile of audioFiles) {
for (const audioFile of audioFiles) {
// await will ensure the metadata parsing is completed before we move on to the next file
const metadata = await parseFile(audioFile);
// Do great things with the metadata
}
}
```
// await will ensure the metadata parsing is completed before we move on to the next file
const metadata = await parseFile(audioFile);
// Do great things with the metadata
}
}
```
3. Use a specialized module to traverse files
1. Use a specialized module to traverse files
There are specialized modules to traversing (walking) files and directory,
like [walk](https://www.npmjs.com/package/walk).
There are specialized modules to traversing (walking) files and directory,
like [walk](https://www.npmjs.com/package/walk).

@@ -516,0 +531,0 @@ ## Licence

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc