music-metadata
Advanced tools
Comparing version 10.1.0 to 10.2.0
@@ -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; |
@@ -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 |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
486212
161
11766
536
Updatedfile-type@^19.4.1
Updatedstrtok3@^8.1.0