music-metadata 12.2.0 → 12.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/lib/ParserFactory.d.ts +1 -1
- package/lib/ParserFactory.js +4 -3
- package/lib/aiff/AiffParser.js +2 -5
- package/lib/common/Allocation.js +2 -2
- package/lib/common/CombinedTagMapper.js +0 -1
- package/lib/common/MetadataCollector.d.ts +5 -5
- package/lib/common/MetadataCollector.js +12 -4
- package/lib/dsdiff/DsdiffLoader.js +1 -1
- package/lib/dsdiff/DsdiffParser.js +2 -6
- package/lib/dsf/DsfParser.js +1 -1
- package/lib/ebml/EbmlIterator.d.ts +20 -15
- package/lib/ebml/EbmlIterator.js +75 -38
- package/lib/ebml/types.d.ts +36 -8
- package/lib/ebml/types.js +2 -1
- package/lib/flac/FlacParser.js +4 -12
- package/lib/id3v1/ID3v1Parser.d.ts +1 -1
- package/lib/id3v1/ID3v1Parser.js +2 -1
- package/lib/id3v2/EmbeddedID3v2.d.ts +5 -0
- package/lib/id3v2/EmbeddedID3v2.js +29 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +6 -1
- package/lib/matroska/MatroskaDtd.d.ts +630 -2
- package/lib/matroska/MatroskaDtd.js +57 -40
- package/lib/matroska/MatroskaParser.js +119 -55
- package/lib/matroska/types.d.ts +31 -106
- package/lib/mp4/MP4Parser.js +13 -3
- package/lib/mpeg/MpegParser.js +2 -2
- package/lib/ogg/OggStreamMetadata.d.ts +2 -2
- package/lib/ogg/vorbis/VorbisDecoder.d.ts +10 -0
- package/lib/ogg/vorbis/VorbisDecoder.js +15 -0
- package/lib/ogg/vorbis/VorbisStream.js +3 -9
- package/lib/type.d.ts +4 -2
- package/lib/wav/WaveParser.js +2 -5
- package/package.json +5 -5
package/lib/mp4/MP4Parser.js
CHANGED
|
@@ -75,6 +75,10 @@ const encoderDict = {
|
|
|
75
75
|
function distinct(value, index, self) {
|
|
76
76
|
return self.indexOf(value) === index;
|
|
77
77
|
}
|
|
78
|
+
/** Chapter offsets can be revisited even when the total input size is unknown. */
|
|
79
|
+
function supportsSeeking(tokenizer) {
|
|
80
|
+
return tokenizer.supportsRandomAccess() && 'setPosition' in tokenizer && typeof tokenizer.setPosition === 'function';
|
|
81
|
+
}
|
|
78
82
|
/**
|
|
79
83
|
* Determine if a track carries audio.
|
|
80
84
|
*
|
|
@@ -173,7 +177,7 @@ export class MP4Parser extends BasicParser {
|
|
|
173
177
|
const rootAtom = await Atom.readAtom(this.tokenizer, (atom, remaining) => this.handleAtom(atom, remaining), null, remainingFileSize);
|
|
174
178
|
remainingFileSize = rootAtom.header.length === 0n ? 0 : remainingFileSize - Number(rootAtom.header.length);
|
|
175
179
|
}
|
|
176
|
-
if (this.mediaDataRanges.length > 0) {
|
|
180
|
+
if (this.mediaDataRanges.length > 0 && supportsSeeking(this.tokenizer)) {
|
|
177
181
|
// Track references and sample tables may follow the media data in a trailing moov box.
|
|
178
182
|
const tokenizer = this.tokenizer;
|
|
179
183
|
const position = tokenizer.position;
|
|
@@ -626,7 +630,13 @@ export class MP4Parser extends BasicParser {
|
|
|
626
630
|
case 'tkhd': // TrackHeaderBox
|
|
627
631
|
track.header = await this.readToken(new AtomToken.TrackHeaderAtom(payLoadLength));
|
|
628
632
|
break;
|
|
629
|
-
case 'hdlr': //
|
|
633
|
+
case 'hdlr': // Media handler; metadata and data handlers do not identify the track type.
|
|
634
|
+
// QuickTime File Format, Media atom: Handler reference atom ('hdlr').
|
|
635
|
+
// https://developer.apple.com/documentation/quicktime-file-format/media_atom/handler_reference_atom
|
|
636
|
+
if (child.parent?.header.name !== 'mdia' || child.parent.parent !== trakBox) {
|
|
637
|
+
await this.tokenizer.ignore(payLoadLength);
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
630
640
|
track.handler = await this.readToken(new AtomToken.HandlerBox(payLoadLength));
|
|
631
641
|
if (track.handler.handlerType === 'audi' || track.handler.handlerType === 'soun') {
|
|
632
642
|
this.hasAudioTrack = true;
|
|
@@ -782,7 +792,7 @@ export class MP4Parser extends BasicParser {
|
|
|
782
792
|
mdat: async (len) => {
|
|
783
793
|
const range = { offset: this.tokenizer.position, end: this.tokenizer.position + len };
|
|
784
794
|
if (this.options.includeChapters) {
|
|
785
|
-
if (this.tokenizer
|
|
795
|
+
if (supportsSeeking(this.tokenizer)) {
|
|
786
796
|
this.mediaDataRanges.push(range);
|
|
787
797
|
}
|
|
788
798
|
else {
|
package/lib/mpeg/MpegParser.js
CHANGED
|
@@ -160,7 +160,7 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
160
160
|
this.metadata.setFormat('container', header.container);
|
|
161
161
|
this.metadata.setFormat('codec', header.codec);
|
|
162
162
|
this.metadata.setFormat('lossless', false);
|
|
163
|
-
this.metadata.setFormat('sampleRate', header.samplingRate);
|
|
163
|
+
this.metadata.setFormat('sampleRate', header.samplingRate ?? undefined);
|
|
164
164
|
this.frameCount++;
|
|
165
165
|
return header instanceof AdtsFrameHeader ? this.parseAdts(header) : this.parseMpegFrame(header);
|
|
166
166
|
}
|
|
@@ -301,7 +301,7 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
301
301
|
if (infoTag.lame) {
|
|
302
302
|
this.metadata.setFormat('tool', `LAME ${common.stripNulls(infoTag.lame.version)}`);
|
|
303
303
|
if (infoTag.lame.extended) {
|
|
304
|
-
this.metadata.setFormat('trackPeakLevel', infoTag.lame.extended.track_peak);
|
|
304
|
+
this.metadata.setFormat('trackPeakLevel', infoTag.lame.extended.track_peak ?? undefined);
|
|
305
305
|
if (infoTag.lame.extended.track_gain) {
|
|
306
306
|
this.metadata.setFormat('trackGain', infoTag.lame.extended.track_gain.adjustment);
|
|
307
307
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { TagType } from '../common/GenericTagTypes.js';
|
|
2
2
|
import { type INativeMetadataCollector, MetadataCollector } from '../common/MetadataCollector.js';
|
|
3
|
-
import type { AnyTagValue,
|
|
3
|
+
import type { AnyTagValue, IFormat, INativeTags, IQualityInformation } from '../type.js';
|
|
4
4
|
/** Keep format values local to a logical stream while sharing tags and warnings. */
|
|
5
5
|
export declare class OggStreamMetadata extends MetadataCollector {
|
|
6
6
|
private readonly parent;
|
|
7
7
|
readonly native: INativeTags;
|
|
8
8
|
readonly quality: IQualityInformation;
|
|
9
9
|
constructor(parent: INativeMetadataCollector);
|
|
10
|
-
setFormat(key:
|
|
10
|
+
setFormat<K extends keyof IFormat>(key: K, value: IFormat[K]): void;
|
|
11
11
|
registerTagType(tagType: TagType): void;
|
|
12
12
|
addTag(tagType: TagType, tagId: string, value: AnyTagValue): Promise<void>;
|
|
13
13
|
addWarning(warning: string): void;
|
|
@@ -5,6 +5,16 @@ export declare class VorbisDecoder {
|
|
|
5
5
|
get remaining(): number;
|
|
6
6
|
readInt32(): number;
|
|
7
7
|
readStringUtf8(): string;
|
|
8
|
+
/** Decode the shared structure, leaving packet framing and metadata policy to callers. */
|
|
9
|
+
parseCommentList(createCountError?: (message: string) => Error): {
|
|
10
|
+
vendor: string;
|
|
11
|
+
comments: Generator<{
|
|
12
|
+
key: string;
|
|
13
|
+
value: string;
|
|
14
|
+
len: number;
|
|
15
|
+
}, void, unknown>;
|
|
16
|
+
};
|
|
17
|
+
private parseUserCommentList;
|
|
8
18
|
parseUserComment(): {
|
|
9
19
|
key: string;
|
|
10
20
|
value: string;
|
|
@@ -30,6 +30,21 @@ export class VorbisDecoder {
|
|
|
30
30
|
this.offset += len;
|
|
31
31
|
return value;
|
|
32
32
|
}
|
|
33
|
+
/** Decode the shared structure, leaving packet framing and metadata policy to callers. */
|
|
34
|
+
parseCommentList(createCountError = message => new VorbisCommentError(message)) {
|
|
35
|
+
const vendor = this.readStringUtf8();
|
|
36
|
+
return { vendor, comments: this.parseUserCommentList(createCountError) };
|
|
37
|
+
}
|
|
38
|
+
*parseUserCommentList(createCountError) {
|
|
39
|
+
const count = this.readInt32();
|
|
40
|
+
// Each entry needs at least its four-byte length, even for an empty comment.
|
|
41
|
+
if (count > Math.floor(this.remaining / 4)) {
|
|
42
|
+
throw createCountError(`Vorbis comment count ${count} exceeds remaining data`);
|
|
43
|
+
}
|
|
44
|
+
for (let i = 0; i < count; ++i) {
|
|
45
|
+
yield this.parseUserComment();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
33
48
|
parseUserComment() {
|
|
34
49
|
const offset0 = this.offset;
|
|
35
50
|
const v = this.readStringUtf8();
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import debugInit from 'debug';
|
|
2
|
-
import * as Token from 'token-types';
|
|
3
2
|
import { validateAllocation } from '../../common/Allocation.js';
|
|
4
3
|
import { makeUnexpectedFileContentError } from '../../ParseError.js';
|
|
5
4
|
import { CommonHeader, IdentificationHeader, VorbisPictureToken } from './Vorbis.js';
|
|
@@ -141,14 +140,9 @@ export class VorbisStream {
|
|
|
141
140
|
* Ref: https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-840005.2
|
|
142
141
|
*/
|
|
143
142
|
async parseUserCommentList(pageData, offset) {
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
offset += strLen;
|
|
148
|
-
let userCommentListLength = Token.UINT32_LE.get(pageData, offset);
|
|
149
|
-
offset += 4;
|
|
150
|
-
while (userCommentListLength-- > 0) {
|
|
151
|
-
offset += await this.parseUserComment(pageData, offset);
|
|
143
|
+
const { comments } = new VorbisDecoder(pageData, offset).parseCommentList();
|
|
144
|
+
for (const tag of comments) {
|
|
145
|
+
await this.addTag(tag.key, tag.value);
|
|
152
146
|
}
|
|
153
147
|
this.isMetadataComplete = true;
|
|
154
148
|
}
|
package/lib/type.d.ts
CHANGED
|
@@ -388,7 +388,7 @@ export interface IRatio {
|
|
|
388
388
|
*/
|
|
389
389
|
dB: number;
|
|
390
390
|
}
|
|
391
|
-
export type FormatId =
|
|
391
|
+
export type FormatId = keyof IFormat;
|
|
392
392
|
export interface IAudioTrack {
|
|
393
393
|
/** Number of decoded audio samples, when known. */
|
|
394
394
|
numberOfSamples?: number;
|
|
@@ -603,6 +603,7 @@ export interface IOptions {
|
|
|
603
603
|
/**
|
|
604
604
|
* Maximum bytes buffered for a single metadata payload, accumulated Ogg packet, or tokenizer stream lookahead.
|
|
605
605
|
* Defaults to 64 MiB (67108864 bytes). Must be a positive safe integer.
|
|
606
|
+
* Also caps file-type read buffers during media format and cover art MIME-type detection.
|
|
606
607
|
* This is not a limit on total process memory or the size of skipped audio/video data.
|
|
607
608
|
*/
|
|
608
609
|
maxMemoryAllocation?: number;
|
|
@@ -625,7 +626,8 @@ export interface IOptions {
|
|
|
625
626
|
*/
|
|
626
627
|
includeChapters?: boolean;
|
|
627
628
|
/**
|
|
628
|
-
*
|
|
629
|
+
* Called synchronously after common tag or format updates. Returned promises are not awaited.
|
|
630
|
+
* Unchanged primitive format values do not trigger a callback.
|
|
629
631
|
*/
|
|
630
632
|
observer?: Observer;
|
|
631
633
|
/**
|
package/lib/wav/WaveParser.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import initDebug from 'debug';
|
|
2
2
|
import * as strtok3 from 'strtok3';
|
|
3
3
|
import * as Token from 'token-types';
|
|
4
|
-
import { getTokenizerOptions } from '../common/Allocation.js';
|
|
5
4
|
import { BasicParser } from '../common/BasicParser.js';
|
|
6
5
|
import { FourCcToken } from '../common/FourCC.js';
|
|
7
|
-
import {
|
|
6
|
+
import { parseEmbeddedID3v2 } from '../id3v2/EmbeddedID3v2.js';
|
|
8
7
|
import * as riff from '../riff/RiffChunk.js';
|
|
9
8
|
import { BroadcastAudioExtensionChunk } from './BwfChunk.js';
|
|
10
9
|
import * as WaveChunk from './WaveChunk.js';
|
|
@@ -101,9 +100,7 @@ export class WaveParser extends BasicParser {
|
|
|
101
100
|
case 'id3 ': // The way Picard, FooBar currently stores, ID3 meta-data
|
|
102
101
|
case 'ID3 ': {
|
|
103
102
|
// The way Mp3Tags stores ID3 meta-data
|
|
104
|
-
|
|
105
|
-
const rst = strtok3.fromBuffer(id3_data, getTokenizerOptions(this.options));
|
|
106
|
-
await new ID3v2Parser().parse(this.metadata, rst, this.options);
|
|
103
|
+
await parseEmbeddedID3v2(this.metadata, this.tokenizer, this.options, header.chunkSize);
|
|
107
104
|
break;
|
|
108
105
|
}
|
|
109
106
|
case 'data': {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "music-metadata",
|
|
3
3
|
"description": "Music metadata parser for Node.js, supporting virtual any audio and tag format.",
|
|
4
|
-
"version": "12.2.
|
|
4
|
+
"version": "12.2.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Borewit",
|
|
7
7
|
"url": "https://github.com/Borewit"
|
|
@@ -122,9 +122,9 @@
|
|
|
122
122
|
"@tokenizer/token": "^0.3.0",
|
|
123
123
|
"content-type": "^3.1.1",
|
|
124
124
|
"debug": "^4.4.3",
|
|
125
|
-
"file-type": "^22.
|
|
125
|
+
"file-type": "^22.2.0",
|
|
126
126
|
"media-typer": "^2.0.0",
|
|
127
|
-
"strtok3": "^10.
|
|
127
|
+
"strtok3": "^10.6.0",
|
|
128
128
|
"token-types": "^6.1.2",
|
|
129
129
|
"uint8array-extras": "^1.6.0",
|
|
130
130
|
"win-guid": "^0.2.1"
|
|
@@ -136,12 +136,12 @@
|
|
|
136
136
|
"@types/debug": "^4.1.13",
|
|
137
137
|
"@types/media-typer": "^1.1.3",
|
|
138
138
|
"@types/mocha": "^10.0.10",
|
|
139
|
-
"@types/node": "^26.6.
|
|
139
|
+
"@types/node": "^26.6.5",
|
|
140
140
|
"c8": "^12.0.0",
|
|
141
141
|
"chai": "^6.3.0",
|
|
142
142
|
"chai-as-promised": "^8.0.2",
|
|
143
143
|
"del-cli": "^8.0.1",
|
|
144
|
-
"mime": "^4.1.
|
|
144
|
+
"mime": "^4.1.1",
|
|
145
145
|
"mocha": "^12.0.3",
|
|
146
146
|
"nodemon": "^3.1.14",
|
|
147
147
|
"remark-cli": "^12.0.1",
|