@openclaw/whatsapp 2026.9.34 → 2026.9.36
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/dist/.source-module-urls/c3803b0fa58e9b1fe1f7418a46193ebc53b9c744f6359f04d40d05af48c1dce2.json +1 -0
- package/node_modules/baileys/node_modules/music-metadata/README.md +35 -32
- package/node_modules/baileys/node_modules/music-metadata/lib/ParserFactory.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/ParserFactory.js +17 -13
- package/node_modules/baileys/node_modules/music-metadata/lib/aiff/AiffParser.js +19 -12
- package/node_modules/baileys/node_modules/music-metadata/lib/aiff/AiffToken.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/aiff/AiffToken.js +2 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/apev2/APEv2Parser.d.ts +4 -3
- package/node_modules/baileys/node_modules/music-metadata/lib/apev2/APEv2Parser.js +59 -16
- package/node_modules/baileys/node_modules/music-metadata/lib/apev2/APEv2TagMapper.js +1 -0
- package/node_modules/baileys/node_modules/music-metadata/lib/apev2/APEv2Token.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/apev2/APEv2Token.js +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/asf/AsfGuid.js +56 -50
- package/node_modules/baileys/node_modules/music-metadata/lib/asf/AsfObject.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/asf/AsfObject.js +3 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/asf/AsfParser.js +15 -7
- package/node_modules/baileys/node_modules/music-metadata/lib/asf/AsfTagMapper.d.ts +3 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/asf/AsfTagMapper.js +21 -4
- package/node_modules/baileys/node_modules/music-metadata/lib/common/CaseInsensitiveTagMap.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/common/CombinedTagMapper.d.ts +2 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/common/CombinedTagMapper.js +6 -6
- package/node_modules/baileys/node_modules/music-metadata/lib/common/FourCC.js +3 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/common/GenericTagMapper.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/common/GenericTagTypes.js +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/common/MetadataCollector.d.ts +3 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/common/MetadataCollector.js +28 -9
- package/node_modules/baileys/node_modules/music-metadata/lib/common/Util.js +25 -17
- package/node_modules/baileys/node_modules/music-metadata/lib/core.d.ts +4 -4
- package/node_modules/baileys/node_modules/music-metadata/lib/core.js +18 -11
- package/node_modules/baileys/node_modules/music-metadata/lib/dsdiff/DsdiffParser.js +29 -16
- package/node_modules/baileys/node_modules/music-metadata/lib/dsf/DsfChunk.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/dsf/DsfParser.js +3 -5
- package/node_modules/baileys/node_modules/music-metadata/lib/ebml/EbmlIterator.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/ebml/EbmlIterator.js +35 -6
- package/node_modules/baileys/node_modules/music-metadata/lib/ebml/types.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/ebml/types.js +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/flac/FlacParser.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/flac/FlacParser.js +6 -5
- package/node_modules/baileys/node_modules/music-metadata/lib/flac/FlacToken.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/flac/FlacToken.js +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/id3v1/ID3v1Parser.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/id3v1/ID3v1Parser.js +212 -42
- package/node_modules/baileys/node_modules/music-metadata/lib/id3v2/AbstractID3Parser.js +5 -5
- package/node_modules/baileys/node_modules/music-metadata/lib/id3v2/FrameHeader.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/id3v2/FrameHeader.js +8 -5
- package/node_modules/baileys/node_modules/music-metadata/lib/id3v2/FrameParser.d.ts +2 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/id3v2/FrameParser.js +53 -22
- package/node_modules/baileys/node_modules/music-metadata/lib/id3v2/ID3v22TagMapper.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/id3v2/ID3v22TagMapper.js +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/id3v2/ID3v24TagMapper.js +3 -3
- package/node_modules/baileys/node_modules/music-metadata/lib/id3v2/ID3v2ChapterToken.js +2 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/id3v2/ID3v2Parser.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/id3v2/ID3v2Parser.js +32 -16
- package/node_modules/baileys/node_modules/music-metadata/lib/id3v2/ID3v2Token.d.ts +2 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/id3v2/ID3v2Token.js +2 -3
- package/node_modules/baileys/node_modules/music-metadata/lib/index.js +6 -3
- package/node_modules/baileys/node_modules/music-metadata/lib/lrc/LyricsParser.js +3 -5
- package/node_modules/baileys/node_modules/music-metadata/lib/matroska/MatroskaDtd.js +7 -3
- package/node_modules/baileys/node_modules/music-metadata/lib/matroska/MatroskaParser.js +22 -11
- package/node_modules/baileys/node_modules/music-metadata/lib/matroska/types.d.ts +2 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/mp4/Atom.d.ts +4 -3
- package/node_modules/baileys/node_modules/music-metadata/lib/mp4/Atom.js +44 -11
- package/node_modules/baileys/node_modules/music-metadata/lib/mp4/AtomToken.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/mp4/AtomToken.js +30 -8
- package/node_modules/baileys/node_modules/music-metadata/lib/mp4/MP4Parser.d.ts +2 -0
- package/node_modules/baileys/node_modules/music-metadata/lib/mp4/MP4Parser.js +79 -54
- package/node_modules/baileys/node_modules/music-metadata/lib/mp4/MP4TagMapper.d.ts +2 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/mpeg/MpegLoader.js +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/mpeg/MpegParser.js +41 -19
- package/node_modules/baileys/node_modules/music-metadata/lib/mpeg/ReplayGainDataFormat.d.ts +2 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/mpeg/ReplayGainDataFormat.js +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/mpeg/XingTag.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/musepack/MusepackParser.js +2 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/musepack/sv7/BitReader.js +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/musepack/sv7/MpcSv7Parser.js +4 -3
- package/node_modules/baileys/node_modules/music-metadata/lib/musepack/sv7/StreamVersion7.js +3 -3
- package/node_modules/baileys/node_modules/music-metadata/lib/musepack/sv8/MpcSv8Parser.js +7 -5
- package/node_modules/baileys/node_modules/music-metadata/lib/musepack/sv8/StreamVersion8.js +2 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/ogg/OggParser.js +16 -12
- package/node_modules/baileys/node_modules/music-metadata/lib/ogg/OggToken.js +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/ogg/flac/FlacStream.d.ts +2 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/ogg/flac/FlacStream.js +2 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/ogg/opus/OpusStream.d.ts +2 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/ogg/opus/OpusStream.js +8 -4
- package/node_modules/baileys/node_modules/music-metadata/lib/ogg/speex/SpeexStream.d.ts +2 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/ogg/speex/SpeexStream.js +2 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/ogg/theora/TheoraStream.d.ts +2 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/ogg/vorbis/Vorbis.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/ogg/vorbis/VorbisDecoder.js +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/ogg/vorbis/VorbisStream.d.ts +3 -3
- package/node_modules/baileys/node_modules/music-metadata/lib/ogg/vorbis/VorbisStream.js +14 -9
- package/node_modules/baileys/node_modules/music-metadata/lib/ogg/vorbis/VorbisTagMapper.js +4 -0
- package/node_modules/baileys/node_modules/music-metadata/lib/riff/RiffChunk.d.ts +3 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/riff/RiffChunk.js +3 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/riff/RiffInfoTagMap.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/type.d.ts +3 -3
- package/node_modules/baileys/node_modules/music-metadata/lib/type.js +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/wav/WaveChunk.d.ts +1 -1
- package/node_modules/baileys/node_modules/music-metadata/lib/wav/WaveChunk.js +3 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/wav/WaveParser.d.ts +6 -2
- package/node_modules/baileys/node_modules/music-metadata/lib/wav/WaveParser.js +75 -22
- package/node_modules/baileys/node_modules/music-metadata/lib/wavpack/WavPackParser.js +22 -13
- package/node_modules/baileys/node_modules/music-metadata/lib/wavpack/WavPackToken.js +3 -2
- package/node_modules/baileys/node_modules/music-metadata/package.json +3 -2
- package/package.json +4 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as Token from 'token-types';
|
|
2
|
+
import { getFrameHeaderLength, readFrameHeader } from './FrameHeader.js';
|
|
2
3
|
import { FrameParser, Id3v2ContentError } from './FrameParser.js';
|
|
3
4
|
import { ExtendedHeader, ID3v2Header } from './ID3v2Token.js';
|
|
4
|
-
import { getFrameHeaderLength, readFrameHeader } from './FrameHeader.js';
|
|
5
5
|
export class ID3v2Parser {
|
|
6
6
|
constructor() {
|
|
7
7
|
this.tokenizer = undefined;
|
|
@@ -17,7 +17,7 @@ export class ID3v2Parser {
|
|
|
17
17
|
if (readI !== writeI) {
|
|
18
18
|
buffer[writeI] = buffer[readI];
|
|
19
19
|
}
|
|
20
|
-
readI +=
|
|
20
|
+
readI += buffer[readI] === 0xff && buffer[readI + 1] === 0 ? 2 : 1;
|
|
21
21
|
writeI++;
|
|
22
22
|
}
|
|
23
23
|
if (readI < buffer.length) {
|
|
@@ -58,10 +58,17 @@ export class ID3v2Parser {
|
|
|
58
58
|
this.options = options;
|
|
59
59
|
const id3Header = await this.tokenizer.readToken(ID3v2Header);
|
|
60
60
|
if (id3Header.fileIdentifier !== 'ID3') {
|
|
61
|
-
throw new Id3v2ContentError(
|
|
61
|
+
throw new Id3v2ContentError("expected ID3-header file-identifier 'ID3' was not found");
|
|
62
|
+
}
|
|
63
|
+
const fileSize = this.tokenizer.fileInfo.size;
|
|
64
|
+
if (fileSize !== undefined) {
|
|
65
|
+
const remainingBytes = fileSize - this.tokenizer.position;
|
|
66
|
+
if (id3Header.size > remainingBytes) {
|
|
67
|
+
throw new Id3v2ContentError(`ID3v2 tag size ${id3Header.size} exceeds remaining file size ${remainingBytes}`);
|
|
68
|
+
}
|
|
62
69
|
}
|
|
63
70
|
this.id3Header = id3Header;
|
|
64
|
-
this.headerType =
|
|
71
|
+
this.headerType = `ID3v2.${id3Header.version.major}`;
|
|
65
72
|
await (id3Header.flags.isExtendedHeader ? this.parseExtendedHeader() : this.parseId3Data(id3Header.size));
|
|
66
73
|
// Post process
|
|
67
74
|
const chapters = ID3v2Parser.mapId3v2Chapters(this.metadata.native[this.headerType]);
|
|
@@ -70,7 +77,9 @@ export class ID3v2Parser {
|
|
|
70
77
|
async parseExtendedHeader() {
|
|
71
78
|
const extendedHeader = await this.tokenizer.readToken(ExtendedHeader);
|
|
72
79
|
const dataRemaining = extendedHeader.size - ExtendedHeader.len;
|
|
73
|
-
return dataRemaining > 0
|
|
80
|
+
return dataRemaining > 0
|
|
81
|
+
? this.parseExtendedHeaderData(dataRemaining, extendedHeader.size)
|
|
82
|
+
: this.parseId3Data(this.id3Header.size - extendedHeader.size);
|
|
74
83
|
}
|
|
75
84
|
async parseExtendedHeaderData(dataRemaining, extendedHeaderSize) {
|
|
76
85
|
await this.tokenizer.ignore(dataRemaining);
|
|
@@ -86,7 +95,9 @@ export class ID3v2Parser {
|
|
|
86
95
|
}
|
|
87
96
|
break;
|
|
88
97
|
default:
|
|
89
|
-
await (Array.isArray(tag.value)
|
|
98
|
+
await (Array.isArray(tag.value)
|
|
99
|
+
? Promise.all(tag.value.map(value => this.addTag(tag.id, value)))
|
|
100
|
+
: this.addTag(tag.id, tag.value));
|
|
90
101
|
}
|
|
91
102
|
}
|
|
92
103
|
}
|
|
@@ -100,8 +111,9 @@ export class ID3v2Parser {
|
|
|
100
111
|
let offset = 0;
|
|
101
112
|
const tags = [];
|
|
102
113
|
while (true) {
|
|
103
|
-
if (offset === data.length)
|
|
114
|
+
if (offset === data.length) {
|
|
104
115
|
break;
|
|
116
|
+
}
|
|
105
117
|
const frameHeaderLength = getFrameHeaderLength(this.id3Header.version.major);
|
|
106
118
|
if (offset + frameHeaderLength > data.length) {
|
|
107
119
|
this.metadata.addWarning('Illegal ID3v2 tag length');
|
|
@@ -126,28 +138,32 @@ export class ID3v2Parser {
|
|
|
126
138
|
* as produced by `FrameParser.readData`.
|
|
127
139
|
*/
|
|
128
140
|
static mapId3v2Chapters(id3Tags) {
|
|
129
|
-
if (!id3Tags)
|
|
141
|
+
if (!id3Tags) {
|
|
130
142
|
return;
|
|
131
|
-
|
|
132
|
-
|
|
143
|
+
}
|
|
144
|
+
const chapFrames = id3Tags.filter(t => t.id === 'CHAP').map(t => t.value);
|
|
145
|
+
if (!chapFrames.length) {
|
|
133
146
|
return;
|
|
134
|
-
|
|
135
|
-
const
|
|
147
|
+
}
|
|
148
|
+
const tocFrames = id3Tags.filter(t => t.id === 'CTOC').map(t => t.value);
|
|
149
|
+
const topLevelToc = tocFrames.find(t => t.flags.topLevel);
|
|
136
150
|
const chapterById = new Map();
|
|
137
151
|
for (const chap of chapFrames) {
|
|
138
|
-
chapterById.set(chap.
|
|
152
|
+
chapterById.set(chap.label, chap);
|
|
139
153
|
}
|
|
140
|
-
const orderedIds = topLevelToc?.
|
|
154
|
+
const orderedIds = topLevelToc?.childElementIds;
|
|
141
155
|
const chapters = [];
|
|
142
156
|
const source = orderedIds ?? [...chapterById.keys()];
|
|
143
157
|
for (const id of source) {
|
|
144
158
|
const chap = chapterById.get(id);
|
|
145
|
-
if (!chap)
|
|
159
|
+
if (!chap) {
|
|
146
160
|
continue;
|
|
161
|
+
}
|
|
147
162
|
const frames = chap.frames;
|
|
148
163
|
const title = frames.get('TIT2');
|
|
149
|
-
if (!title)
|
|
164
|
+
if (!title) {
|
|
150
165
|
continue; // title is required
|
|
166
|
+
}
|
|
151
167
|
chapters.push({
|
|
152
168
|
id,
|
|
153
169
|
title,
|
|
@@ -46,13 +46,13 @@ export declare const LyricsContentType: {
|
|
|
46
46
|
chord: number;
|
|
47
47
|
trivia_pop: number;
|
|
48
48
|
};
|
|
49
|
-
export type LyricsContentType = typeof LyricsContentType[keyof typeof LyricsContentType];
|
|
49
|
+
export type LyricsContentType = (typeof LyricsContentType)[keyof typeof LyricsContentType];
|
|
50
50
|
export declare const TimestampFormat: {
|
|
51
51
|
notSynchronized: number;
|
|
52
52
|
mpegFrameNumber: number;
|
|
53
53
|
milliseconds: number;
|
|
54
54
|
};
|
|
55
|
-
export type TimestampFormat = typeof TimestampFormat[keyof typeof TimestampFormat];
|
|
55
|
+
export type TimestampFormat = (typeof TimestampFormat)[keyof typeof TimestampFormat];
|
|
56
56
|
/**
|
|
57
57
|
* 28 bits (representing up to 256MB) integer, the msb is 0 to avoid 'false syncsignals'.
|
|
58
58
|
* 4 * %0xxxxxxx
|
|
@@ -37,7 +37,7 @@ export const LyricsContentType = {
|
|
|
37
37
|
movement_part: 3,
|
|
38
38
|
events: 4,
|
|
39
39
|
chord: 5,
|
|
40
|
-
trivia_pop: 6
|
|
40
|
+
trivia_pop: 6
|
|
41
41
|
};
|
|
42
42
|
export const TimestampFormat = {
|
|
43
43
|
notSynchronized: 0,
|
|
@@ -50,8 +50,7 @@ export const TimestampFormat = {
|
|
|
50
50
|
*/
|
|
51
51
|
export const UINT32SYNCSAFE = {
|
|
52
52
|
get: (buf, off) => {
|
|
53
|
-
return buf[off + 3] & 0x7f | (
|
|
54
|
-
((buf[off + 1]) << 14) | ((buf[off]) << 21);
|
|
53
|
+
return (buf[off + 3] & 0x7f) | (buf[off + 2] << 7) | (buf[off + 1] << 14) | (buf[off] << 21);
|
|
55
54
|
},
|
|
56
55
|
len: 4
|
|
57
56
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node.js specific entry point.
|
|
3
3
|
*/
|
|
4
|
-
import { fromFile, fromStream } from 'strtok3';
|
|
5
4
|
import initDebug from 'debug';
|
|
5
|
+
import { fromFile, fromStream } from 'strtok3';
|
|
6
6
|
import { CouldNotDetermineFileTypeError, parseFromTokenizer, UnsupportedFileTypeError } from './core.js';
|
|
7
7
|
import { ParserFactory } from './ParserFactory.js';
|
|
8
8
|
export * from './core.js';
|
|
@@ -15,7 +15,9 @@ const debug = initDebug('music-metadata:parser');
|
|
|
15
15
|
* @returns Metadata
|
|
16
16
|
*/
|
|
17
17
|
export async function parseStream(stream, fileInfo, options = {}) {
|
|
18
|
-
const tokenizer = await fromStream(stream, {
|
|
18
|
+
const tokenizer = await fromStream(stream, {
|
|
19
|
+
fileInfo: typeof fileInfo === 'string' ? { mimeType: fileInfo } : fileInfo
|
|
20
|
+
});
|
|
19
21
|
try {
|
|
20
22
|
return await parseFromTokenizer(tokenizer, options);
|
|
21
23
|
}
|
|
@@ -35,8 +37,9 @@ export async function parseFile(filePath, options = {}) {
|
|
|
35
37
|
const parserFactory = new ParserFactory();
|
|
36
38
|
try {
|
|
37
39
|
const parserLoader = parserFactory.findLoaderForExtension(filePath);
|
|
38
|
-
if (!parserLoader)
|
|
40
|
+
if (!parserLoader) {
|
|
39
41
|
debug('Parser could not be determined by file extension');
|
|
42
|
+
}
|
|
40
43
|
try {
|
|
41
44
|
return await parserFactory.parse(fileTokenizer, parserLoader, options);
|
|
42
45
|
}
|
|
@@ -12,7 +12,7 @@ export function toUnsyncedLyrics(lyrics) {
|
|
|
12
12
|
contentType: LyricsContentType.lyrics,
|
|
13
13
|
timeStampFormat: TimestampFormat.notSynchronized,
|
|
14
14
|
text: lyrics.trim(),
|
|
15
|
-
syncText: []
|
|
15
|
+
syncText: []
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
@@ -28,9 +28,7 @@ export function parseLrc(lrcString) {
|
|
|
28
28
|
if (match) {
|
|
29
29
|
const minutes = Number.parseInt(match[1], 10);
|
|
30
30
|
const seconds = Number.parseInt(match[2], 10);
|
|
31
|
-
const ms = match[3].length === 3
|
|
32
|
-
? Number.parseInt(match[3], 10)
|
|
33
|
-
: Number.parseInt(match[3], 10) * 10;
|
|
31
|
+
const ms = match[3].length === 3 ? Number.parseInt(match[3], 10) : Number.parseInt(match[3], 10) * 10;
|
|
34
32
|
const timestamp = (minutes * 60 + seconds) * 1000 + ms;
|
|
35
33
|
const text = line.replace(TIMESTAMP_REGEX, '').trim();
|
|
36
34
|
syncText.push({ timestamp, text });
|
|
@@ -40,6 +38,6 @@ export function parseLrc(lrcString) {
|
|
|
40
38
|
contentType: LyricsContentType.lyrics,
|
|
41
39
|
timeStampFormat: TimestampFormat.milliseconds,
|
|
42
40
|
text: syncText.map(line => line.text).join('\n'),
|
|
43
|
-
syncText
|
|
41
|
+
syncText
|
|
44
42
|
};
|
|
45
43
|
}
|
|
@@ -9,6 +9,7 @@ export const matroskaDtd = {
|
|
|
9
9
|
name: 'dtd',
|
|
10
10
|
container: {
|
|
11
11
|
0x1a45dfa3: {
|
|
12
|
+
// 5.1
|
|
12
13
|
name: 'ebml',
|
|
13
14
|
container: {
|
|
14
15
|
0x4286: { name: 'ebmlVersion', value: DataType.uint }, // 5.1.1
|
|
@@ -178,7 +179,8 @@ export const matroskaDtd = {
|
|
|
178
179
|
0x5378: { name: 'blockNumber', value: DataType.uint },
|
|
179
180
|
0xea: { name: 'codecState', value: DataType.uint },
|
|
180
181
|
0xdb: {
|
|
181
|
-
name: 'reference',
|
|
182
|
+
name: 'reference',
|
|
183
|
+
container: {
|
|
182
184
|
0x96: { name: 'time', value: DataType.uint },
|
|
183
185
|
0x97: { name: 'cluster', value: DataType.uint },
|
|
184
186
|
0x535f: { name: 'number', value: DataType.uint },
|
|
@@ -225,10 +227,12 @@ export const matroskaDtd = {
|
|
|
225
227
|
0x98: { name: 'hidden', value: DataType.bool },
|
|
226
228
|
0x4598: { name: 'enabled', value: DataType.uid },
|
|
227
229
|
0x8f: {
|
|
228
|
-
name: 'track',
|
|
230
|
+
name: 'track',
|
|
231
|
+
container: {
|
|
229
232
|
0x89: { name: 'trackNumber', value: DataType.uid },
|
|
230
233
|
0x80: {
|
|
231
|
-
name: 'display',
|
|
234
|
+
name: 'display',
|
|
235
|
+
container: {
|
|
232
236
|
0x85: { name: 'string', value: DataType.string },
|
|
233
237
|
0x437c: { name: 'language ', value: DataType.string },
|
|
234
238
|
0x437e: { name: 'country ', value: DataType.string }
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import initDebug from 'debug';
|
|
2
2
|
import { BasicParser } from '../common/BasicParser.js';
|
|
3
|
+
import { EbmlIterator, ParseAction } from '../ebml/EbmlIterator.js';
|
|
3
4
|
import { matroskaDtd } from './MatroskaDtd.js';
|
|
4
5
|
import { TargetType, TrackType } from './types.js';
|
|
5
|
-
import { EbmlIterator, ParseAction } from '../ebml/EbmlIterator.js';
|
|
6
6
|
const debug = initDebug('music-metadata:parser:matroska');
|
|
7
7
|
/**
|
|
8
8
|
* Extensible Binary Meta Language (EBML) parser
|
|
@@ -26,7 +26,7 @@ export class MatroskaParser extends BasicParser {
|
|
|
26
26
|
const matroskaIterator = new EbmlIterator(this.tokenizer);
|
|
27
27
|
debug('Initializing DTD end MatroskaIterator');
|
|
28
28
|
await matroskaIterator.iterate(matroskaDtd, containerSize, {
|
|
29
|
-
startNext:
|
|
29
|
+
startNext: element => {
|
|
30
30
|
switch (element.id) {
|
|
31
31
|
// case 0x1f43b675: // cluster
|
|
32
32
|
case 0x1c53bb6b: // Cueing Data
|
|
@@ -59,18 +59,20 @@ export class MatroskaParser extends BasicParser {
|
|
|
59
59
|
this.seekHeadOffset = offset;
|
|
60
60
|
break;
|
|
61
61
|
case 0x1549a966:
|
|
62
|
-
{
|
|
62
|
+
{
|
|
63
|
+
// Info (Segment Information)
|
|
63
64
|
const info = value;
|
|
64
65
|
const timecodeScale = info.timecodeScale ? info.timecodeScale : 1000000;
|
|
65
66
|
if (typeof info.duration === 'number') {
|
|
66
|
-
const duration = info.duration * timecodeScale / 1000000000;
|
|
67
|
+
const duration = (info.duration * timecodeScale) / 1000000000;
|
|
67
68
|
await this.addTag('segment:title', info.title);
|
|
68
69
|
this.metadata.setFormat('duration', Number(duration));
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
break;
|
|
72
73
|
case 0x1654ae6b:
|
|
73
|
-
{
|
|
74
|
+
{
|
|
75
|
+
// tracks
|
|
74
76
|
const audioTracks = value;
|
|
75
77
|
if (audioTracks?.entries) {
|
|
76
78
|
audioTracks.entries.forEach(entry => {
|
|
@@ -91,12 +93,15 @@ export class MatroskaParser extends BasicParser {
|
|
|
91
93
|
const audioTrack = audioTracks.entries
|
|
92
94
|
.filter(entry => entry.trackType === TrackType.audio)
|
|
93
95
|
.reduce((acc, cur) => {
|
|
94
|
-
if (!acc)
|
|
96
|
+
if (!acc) {
|
|
95
97
|
return cur;
|
|
96
|
-
|
|
98
|
+
}
|
|
99
|
+
if (cur.flagDefault && !acc.flagDefault) {
|
|
97
100
|
return cur;
|
|
98
|
-
|
|
101
|
+
}
|
|
102
|
+
if (cur.trackNumber < acc.trackNumber) {
|
|
99
103
|
return cur;
|
|
104
|
+
}
|
|
100
105
|
return acc;
|
|
101
106
|
}, null);
|
|
102
107
|
if (audioTrack) {
|
|
@@ -108,11 +113,16 @@ export class MatroskaParser extends BasicParser {
|
|
|
108
113
|
}
|
|
109
114
|
break;
|
|
110
115
|
case 0x1254c367:
|
|
111
|
-
{
|
|
116
|
+
{
|
|
117
|
+
// tags
|
|
112
118
|
const tags = value;
|
|
113
119
|
await Promise.all(tags.tag.map(async (tag) => {
|
|
114
120
|
const target = tag.target;
|
|
115
|
-
const targetType = target?.targetTypeValue
|
|
121
|
+
const targetType = target?.targetTypeValue
|
|
122
|
+
? TargetType[target.targetTypeValue]
|
|
123
|
+
: target?.targetType
|
|
124
|
+
? target.targetType
|
|
125
|
+
: 'track';
|
|
116
126
|
await Promise.all(tag.simpleTags.map(async (simpleTag) => {
|
|
117
127
|
const value = simpleTag.string ? simpleTag.string : simpleTag.binary;
|
|
118
128
|
await this.addTag(`${targetType}:${simpleTag.name}`, value);
|
|
@@ -121,7 +131,8 @@ export class MatroskaParser extends BasicParser {
|
|
|
121
131
|
}
|
|
122
132
|
break;
|
|
123
133
|
case 0x1941a469:
|
|
124
|
-
{
|
|
134
|
+
{
|
|
135
|
+
// attachments
|
|
125
136
|
const attachments = value;
|
|
126
137
|
await Promise.all(attachments.attachedFiles
|
|
127
138
|
.filter(file => file.mimeType.startsWith('image/'))
|
|
@@ -75,7 +75,7 @@ export interface ICueReference {
|
|
|
75
75
|
}
|
|
76
76
|
export interface ISimpleTag {
|
|
77
77
|
name?: string;
|
|
78
|
-
|
|
78
|
+
string?: string;
|
|
79
79
|
binary?: Uint8Array;
|
|
80
80
|
language?: string;
|
|
81
81
|
default?: boolean;
|
|
@@ -98,7 +98,7 @@ export declare const TrackType: {
|
|
|
98
98
|
button: number;
|
|
99
99
|
control: number;
|
|
100
100
|
};
|
|
101
|
-
export type TrackType = typeof TrackType[keyof typeof TrackType];
|
|
101
|
+
export type TrackType = (typeof TrackType)[keyof typeof TrackType];
|
|
102
102
|
export type TrackTypeKey = keyof typeof TrackType;
|
|
103
103
|
export declare const TrackTypeValueToKeyMap: Record<TrackType, TrackTypeKey>;
|
|
104
104
|
export interface ITarget {
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import * as AtomToken from './AtomToken.js';
|
|
2
1
|
import type { ITokenizer } from 'strtok3';
|
|
2
|
+
import * as AtomToken from './AtomToken.js';
|
|
3
3
|
export type AtomDataHandler = (atom: Atom, remaining: number) => Promise<void>;
|
|
4
4
|
export declare class Atom {
|
|
5
|
+
private readonly payloadLength;
|
|
5
6
|
static readAtom(tokenizer: ITokenizer, dataHandler: AtomDataHandler, parent: Atom | null, remaining: number): Promise<Atom>;
|
|
6
7
|
readonly children: Atom[];
|
|
7
8
|
readonly atomPath: string;
|
|
8
9
|
readonly header: AtomToken.IAtomHeader;
|
|
9
10
|
extended: boolean;
|
|
10
11
|
readonly parent: Atom | null;
|
|
11
|
-
constructor(header: AtomToken.IAtomHeader, extended: boolean, parent: Atom | null);
|
|
12
|
+
constructor(header: AtomToken.IAtomHeader, extended: boolean, parent: Atom | null, payloadLength: number);
|
|
12
13
|
getHeaderLength(): number;
|
|
13
|
-
getPayloadLength(
|
|
14
|
+
getPayloadLength(): number;
|
|
14
15
|
readAtoms(tokenizer: ITokenizer, dataHandler: AtomDataHandler, size: number): Promise<void>;
|
|
15
16
|
private readData;
|
|
16
17
|
}
|
|
@@ -1,24 +1,43 @@
|
|
|
1
1
|
import initDebug from 'debug';
|
|
2
2
|
import * as AtomToken from './AtomToken.js';
|
|
3
|
-
import { Header } from './AtomToken.js';
|
|
3
|
+
import { Header, Mp4ContentError } from './AtomToken.js';
|
|
4
4
|
const debug = initDebug('music-metadata:parser:MP4:Atom');
|
|
5
5
|
export class Atom {
|
|
6
6
|
static async readAtom(tokenizer, dataHandler, parent, remaining) {
|
|
7
|
-
//
|
|
7
|
+
// Validate boundaries before reading either header or dispatching a payload parser.
|
|
8
8
|
const offset = tokenizer.position;
|
|
9
|
+
remaining = Math.min(remaining, (tokenizer.fileInfo.size ?? Number.POSITIVE_INFINITY) - offset);
|
|
10
|
+
if (remaining < AtomToken.Header.len) {
|
|
11
|
+
throw new Mp4ContentError('Truncated atom header');
|
|
12
|
+
}
|
|
9
13
|
debug(`Reading next token on offset=${offset}...`); // buf.toString('ascii')
|
|
10
14
|
const header = await tokenizer.readToken(AtomToken.Header);
|
|
11
15
|
const extended = header.length === 1n;
|
|
12
16
|
if (extended) {
|
|
17
|
+
if (remaining < 16) {
|
|
18
|
+
throw new Mp4ContentError('Truncated extended atom header');
|
|
19
|
+
}
|
|
13
20
|
header.length = await tokenizer.readToken(AtomToken.ExtendedSize);
|
|
14
21
|
}
|
|
15
|
-
const
|
|
16
|
-
|
|
22
|
+
const headerLength = extended ? 16 : 8;
|
|
23
|
+
if ((extended || header.length !== 0n) && header.length < BigInt(headerLength)) {
|
|
24
|
+
throw new Mp4ContentError(`Invalid atom size: ${header.length}`);
|
|
25
|
+
}
|
|
26
|
+
if (header.length > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
27
|
+
throw new Mp4ContentError(`Atom size exceeds Number.MAX_SAFE_INTEGER: ${header.length}`);
|
|
28
|
+
}
|
|
29
|
+
const length = header.length === 0n ? remaining : Number(header.length);
|
|
30
|
+
if (length > remaining) {
|
|
31
|
+
throw new Mp4ContentError(`Atom size exceeds remaining bytes: ${header.length} > ${remaining}`);
|
|
32
|
+
}
|
|
33
|
+
const payloadLength = length - headerLength;
|
|
34
|
+
const atomBean = new Atom(header, extended, parent, payloadLength);
|
|
17
35
|
debug(`parse atom name=${atomBean.atomPath}, extended=${atomBean.extended}, offset=${offset}, len=${atomBean.header.length}`); // buf.toString('ascii')
|
|
18
36
|
await atomBean.readData(tokenizer, dataHandler, payloadLength);
|
|
19
37
|
return atomBean;
|
|
20
38
|
}
|
|
21
|
-
constructor(header, extended, parent) {
|
|
39
|
+
constructor(header, extended, parent, payloadLength) {
|
|
40
|
+
this.payloadLength = payloadLength;
|
|
22
41
|
this.header = header;
|
|
23
42
|
this.extended = extended;
|
|
24
43
|
this.parent = parent;
|
|
@@ -28,14 +47,24 @@ export class Atom {
|
|
|
28
47
|
getHeaderLength() {
|
|
29
48
|
return this.extended ? 16 : 8;
|
|
30
49
|
}
|
|
31
|
-
getPayloadLength(
|
|
32
|
-
return
|
|
50
|
+
getPayloadLength() {
|
|
51
|
+
return this.payloadLength;
|
|
33
52
|
}
|
|
34
53
|
async readAtoms(tokenizer, dataHandler, size) {
|
|
35
54
|
while (size > 0) {
|
|
55
|
+
if (size === Number.POSITIVE_INFINITY) {
|
|
56
|
+
// An open-ended container ends only at clean EOF, before the next header.
|
|
57
|
+
const bytesRead = await tokenizer.peekBuffer(new Uint8Array(Header.len), { mayBeLess: true });
|
|
58
|
+
if (bytesRead === 0) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (bytesRead < Header.len) {
|
|
62
|
+
throw new Mp4ContentError('Truncated atom header');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
36
65
|
const atomBean = await Atom.readAtom(tokenizer, dataHandler, this, size);
|
|
37
66
|
this.children.push(atomBean);
|
|
38
|
-
size
|
|
67
|
+
size = atomBean.header.length === 0n ? 0 : size - Number(atomBean.header.length);
|
|
39
68
|
}
|
|
40
69
|
}
|
|
41
70
|
async readData(tokenizer, dataHandler, remaining) {
|
|
@@ -50,13 +79,17 @@ export class Atom {
|
|
|
50
79
|
case 'ilst':
|
|
51
80
|
case 'tref':
|
|
52
81
|
case 'moof':
|
|
53
|
-
return this.readAtoms(tokenizer, dataHandler,
|
|
54
|
-
case 'meta': {
|
|
82
|
+
return this.readAtoms(tokenizer, dataHandler, remaining);
|
|
83
|
+
case 'meta': {
|
|
84
|
+
// Metadata Atom, ref: https://developer.apple.com/library/content/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW8
|
|
85
|
+
if (remaining < 8) {
|
|
86
|
+
throw new Mp4ContentError('Truncated meta atom');
|
|
87
|
+
}
|
|
55
88
|
// meta has 4 bytes of padding, ignore
|
|
56
89
|
const peekHeader = await tokenizer.peekToken(Header);
|
|
57
90
|
const paddingLength = peekHeader.name === 'hdlr' ? 0 : 4;
|
|
58
91
|
await tokenizer.ignore(paddingLength);
|
|
59
|
-
return this.readAtoms(tokenizer, dataHandler,
|
|
92
|
+
return this.readAtoms(tokenizer, dataHandler, remaining - paddingLength);
|
|
60
93
|
}
|
|
61
94
|
default:
|
|
62
95
|
return dataHandler(this, remaining);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import * as Token from 'token-types';
|
|
2
1
|
import initDebug from 'debug';
|
|
2
|
+
import * as Token from 'token-types';
|
|
3
3
|
import { FourCcToken } from '../common/FourCC.js';
|
|
4
|
-
import { makeUnexpectedFileContentError } from '../ParseError.js';
|
|
5
4
|
import * as util from '../common/Util.js';
|
|
6
|
-
import { FieldDecodingError } from '../ParseError.js';
|
|
5
|
+
import { FieldDecodingError, makeUnexpectedFileContentError } from '../ParseError.js';
|
|
7
6
|
const debug = initDebug('music-metadata:parser:MP4:atom');
|
|
8
7
|
export class Mp4ContentError extends makeUnexpectedFileContentError('MP4') {
|
|
9
8
|
}
|
|
@@ -11,8 +10,9 @@ export const Header = {
|
|
|
11
10
|
len: 8,
|
|
12
11
|
get: (buf, off) => {
|
|
13
12
|
const length = Token.UINT32_BE.get(buf, off);
|
|
14
|
-
if (length < 0)
|
|
13
|
+
if (length < 0) {
|
|
15
14
|
throw new Mp4ContentError('Invalid atom header length');
|
|
15
|
+
}
|
|
16
16
|
return {
|
|
17
17
|
length: BigInt(length),
|
|
18
18
|
name: new Token.StringType(4, 'latin1').get(buf, off + 4)
|
|
@@ -226,7 +226,15 @@ export class TrackHeaderAtom {
|
|
|
226
226
|
this.len = len;
|
|
227
227
|
}
|
|
228
228
|
get(buf, off) {
|
|
229
|
+
const available = Math.min(this.len, buf.length - off);
|
|
230
|
+
if (available < 4) {
|
|
231
|
+
throw new Mp4ContentError('Truncated tkhd header');
|
|
232
|
+
}
|
|
229
233
|
const version = Token.UINT8.get(buf, off);
|
|
234
|
+
// Validate through the volume field, the last field currently decoded.
|
|
235
|
+
if ((version === 0 && available < 38) || (version === 1 && available < 50)) {
|
|
236
|
+
throw new Mp4ContentError('Truncated tkhd header');
|
|
237
|
+
}
|
|
230
238
|
const flags = Token.UINT24_BE.get(buf, off + 1);
|
|
231
239
|
switch (version) {
|
|
232
240
|
case 0:
|
|
@@ -306,11 +314,23 @@ export class StsdAtom {
|
|
|
306
314
|
this.len = len;
|
|
307
315
|
}
|
|
308
316
|
get(buf, off) {
|
|
317
|
+
const end = Math.min(off + this.len, buf.length);
|
|
318
|
+
if (end - off < stsdHeader.len) {
|
|
319
|
+
throw new Mp4ContentError('Truncated stsd header');
|
|
320
|
+
}
|
|
309
321
|
const header = stsdHeader.get(buf, off);
|
|
310
322
|
off += stsdHeader.len;
|
|
311
323
|
const table = [];
|
|
312
324
|
for (let n = 0; n < header.numberOfEntries; ++n) {
|
|
325
|
+
if (end - off < Token.UINT32_BE.len) {
|
|
326
|
+
throw new Mp4ContentError('Truncated stsd sample entry');
|
|
327
|
+
}
|
|
313
328
|
const size = Token.UINT32_BE.get(buf, off); // Sample description size
|
|
329
|
+
// A SampleEntry needs its size, format, reserved bytes and data reference index.
|
|
330
|
+
// Requiring it to fit also bounds the loop by the available payload, not entry_count.
|
|
331
|
+
if (size < 16 || size > end - off) {
|
|
332
|
+
throw new Mp4ContentError(`Invalid stsd sample entry size: ${size}`);
|
|
333
|
+
}
|
|
314
334
|
off += Token.UINT32_BE.len;
|
|
315
335
|
table.push(new SampleDescriptionTable(size - Token.UINT32_BE.len).get(buf, off));
|
|
316
336
|
// A SampleEntry extends Box, so its size covers the size field already stepped over
|
|
@@ -449,10 +469,12 @@ export class ChapterText {
|
|
|
449
469
|
}
|
|
450
470
|
function readTokenTable(buf, token, off, remainingLen, numberOfEntries) {
|
|
451
471
|
debug(`remainingLen=${remainingLen}, numberOfEntries=${numberOfEntries} * token-len=${token.len}`);
|
|
452
|
-
if (remainingLen === 0)
|
|
472
|
+
if (remainingLen === 0) {
|
|
453
473
|
return [];
|
|
454
|
-
|
|
474
|
+
}
|
|
475
|
+
if (remainingLen !== numberOfEntries * token.len) {
|
|
455
476
|
throw new Mp4ContentError('mismatch number-of-entries with remaining atom-length');
|
|
477
|
+
}
|
|
456
478
|
const entries = [];
|
|
457
479
|
// parse offset-table
|
|
458
480
|
for (let n = 0; n < numberOfEntries; ++n) {
|
|
@@ -539,7 +561,7 @@ export class TrackRunBox {
|
|
|
539
561
|
}
|
|
540
562
|
for (let n = 0; n < trun.sampleCount; ++n) {
|
|
541
563
|
if (dynOffset >= this.len) {
|
|
542
|
-
debug(
|
|
564
|
+
debug('TrackRunBox size mismatch');
|
|
543
565
|
break;
|
|
544
566
|
}
|
|
545
567
|
const sample = {};
|
|
@@ -579,7 +601,7 @@ export class HandlerBox {
|
|
|
579
601
|
flags: Token.UINT24_BE.get(buf, off + 1),
|
|
580
602
|
componentType: charTypeToken.get(buf, off + 4),
|
|
581
603
|
handlerType: charTypeToken.get(buf, off + 8),
|
|
582
|
-
componentName: new Token.StringType(this.len - 28, 'utf-8').get(buf, off + 28)
|
|
604
|
+
componentName: new Token.StringType(this.len - 28, 'utf-8').get(buf, off + 28)
|
|
583
605
|
};
|
|
584
606
|
}
|
|
585
607
|
}
|
|
@@ -2,6 +2,8 @@ import { BasicParser } from '../common/BasicParser.js';
|
|
|
2
2
|
import { Atom } from './Atom.js';
|
|
3
3
|
export declare class MP4Parser extends BasicParser {
|
|
4
4
|
private static read_BE_Integer;
|
|
5
|
+
private validatePayloadLength;
|
|
6
|
+
private readToken;
|
|
5
7
|
private tracks;
|
|
6
8
|
private hasVideoTrack;
|
|
7
9
|
private hasAudioTrack;
|