metadata-connect 1.1.0
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/LICENSE +21 -0
- package/README.md +196 -0
- package/dist/extract.d.ts +36 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +77 -0
- package/dist/extract.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/aiff.d.ts +9 -0
- package/dist/parsers/aiff.d.ts.map +1 -0
- package/dist/parsers/aiff.js +42 -0
- package/dist/parsers/aiff.js.map +1 -0
- package/dist/parsers/flac.d.ts +6 -0
- package/dist/parsers/flac.d.ts.map +1 -0
- package/dist/parsers/flac.js +172 -0
- package/dist/parsers/flac.js.map +1 -0
- package/dist/parsers/id3.d.ts +6 -0
- package/dist/parsers/id3.d.ts.map +1 -0
- package/dist/parsers/id3.js +329 -0
- package/dist/parsers/id3.js.map +1 -0
- package/dist/parsers/index.d.ts +6 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +6 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/mp4.d.ts +6 -0
- package/dist/parsers/mp4.d.ts.map +1 -0
- package/dist/parsers/mp4.js +295 -0
- package/dist/parsers/mp4.js.map +1 -0
- package/dist/parsers/utils.d.ts +40 -0
- package/dist/parsers/utils.d.ts.map +1 -0
- package/dist/parsers/utils.js +120 -0
- package/dist/parsers/utils.js.map +1 -0
- package/dist/reader.d.ts +6 -0
- package/dist/reader.d.ts.map +1 -0
- package/dist/reader.js +14 -0
- package/dist/reader.js.map +1 -0
- package/dist/types.d.ts +70 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +28 -0
- package/dist/types.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { PictureType } from '../types.js';
|
|
2
|
+
import { detectImageType, readSyncsafe, getTextEncoding, readNullTerminatedString, parseBpm, parseYear, cleanText, } from './utils.js';
|
|
3
|
+
/**
|
|
4
|
+
* ID3v2 frame IDs for metadata extraction
|
|
5
|
+
*/
|
|
6
|
+
const FRAME_IDS = {
|
|
7
|
+
// Text frames
|
|
8
|
+
TITLE: ['TIT2', 'TT2'], // Title
|
|
9
|
+
ARTIST: ['TPE1', 'TP1'], // Lead artist
|
|
10
|
+
ALBUM: ['TALB', 'TAL'], // Album
|
|
11
|
+
GENRE: ['TCON', 'TCO'], // Genre
|
|
12
|
+
YEAR: ['TYER', 'TYE', 'TDRC'], // Year (TYER for v2.3, TDRC for v2.4)
|
|
13
|
+
BPM: ['TBPM', 'TBP'], // BPM
|
|
14
|
+
KEY: ['TKEY', 'TKE'], // Musical key
|
|
15
|
+
// Picture frame
|
|
16
|
+
PICTURE: ['APIC', 'PIC'],
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Parse an APIC (attached picture) frame
|
|
20
|
+
*/
|
|
21
|
+
function parseApicFrame(data) {
|
|
22
|
+
if (data.length < 4)
|
|
23
|
+
return null;
|
|
24
|
+
let offset = 0;
|
|
25
|
+
const encodingByte = data[offset++];
|
|
26
|
+
let encoding = getTextEncoding(encodingByte);
|
|
27
|
+
// Check for BOM in UTF-16
|
|
28
|
+
if (encodingByte === 1 && data.length > offset + 2) {
|
|
29
|
+
const bom = data.readUInt16BE(offset);
|
|
30
|
+
if (bom === 0xfeff)
|
|
31
|
+
encoding = 'utf16be';
|
|
32
|
+
else if (bom === 0xfffe)
|
|
33
|
+
encoding = 'utf16le';
|
|
34
|
+
}
|
|
35
|
+
const mimeResult = readNullTerminatedString(data, offset, 'latin1');
|
|
36
|
+
const mimeType = mimeResult.value;
|
|
37
|
+
offset += mimeResult.bytesConsumed;
|
|
38
|
+
if (offset >= data.length)
|
|
39
|
+
return null;
|
|
40
|
+
const pictureType = data[offset++];
|
|
41
|
+
if (offset >= data.length)
|
|
42
|
+
return null;
|
|
43
|
+
const descResult = readNullTerminatedString(data, offset, encoding);
|
|
44
|
+
offset += descResult.bytesConsumed;
|
|
45
|
+
if (offset >= data.length)
|
|
46
|
+
return null;
|
|
47
|
+
const imageData = data.subarray(offset);
|
|
48
|
+
if (imageData.length === 0)
|
|
49
|
+
return null;
|
|
50
|
+
const detectedType = detectImageType(imageData);
|
|
51
|
+
const finalMimeType = detectedType ?? (mimeType.includes('png') ? 'image/png' : 'image/jpeg');
|
|
52
|
+
return { data: imageData, mimeType: finalMimeType, pictureType };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Parse a text frame (TIT2, TPE1, etc.)
|
|
56
|
+
*/
|
|
57
|
+
function parseTextFrame(data) {
|
|
58
|
+
if (data.length < 2)
|
|
59
|
+
return undefined;
|
|
60
|
+
const encodingByte = data[0];
|
|
61
|
+
let encoding = getTextEncoding(encodingByte);
|
|
62
|
+
let offset = 1;
|
|
63
|
+
// Check for BOM in UTF-16
|
|
64
|
+
if (encodingByte === 1 && data.length > offset + 2) {
|
|
65
|
+
const bom = data.readUInt16BE(offset);
|
|
66
|
+
if (bom === 0xfeff) {
|
|
67
|
+
encoding = 'utf16be';
|
|
68
|
+
offset += 2;
|
|
69
|
+
}
|
|
70
|
+
else if (bom === 0xfffe) {
|
|
71
|
+
encoding = 'utf16le';
|
|
72
|
+
offset += 2;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Read until null terminator or end of data
|
|
76
|
+
const textData = data.subarray(offset);
|
|
77
|
+
if (encoding === 'utf16le' || encoding === 'utf16be') {
|
|
78
|
+
// Find null terminator (two zeros)
|
|
79
|
+
let end = textData.length;
|
|
80
|
+
for (let i = 0; i < textData.length - 1; i += 2) {
|
|
81
|
+
if (textData[i] === 0 && textData[i + 1] === 0) {
|
|
82
|
+
end = i;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (encoding === 'utf16be') {
|
|
87
|
+
// Swap bytes for big-endian
|
|
88
|
+
const swapped = Buffer.alloc(end);
|
|
89
|
+
for (let i = 0; i < end; i += 2) {
|
|
90
|
+
swapped[i] = textData[i + 1];
|
|
91
|
+
swapped[i + 1] = textData[i];
|
|
92
|
+
}
|
|
93
|
+
return cleanText(swapped.toString('utf16le'));
|
|
94
|
+
}
|
|
95
|
+
return cleanText(textData.toString('utf16le', 0, end));
|
|
96
|
+
}
|
|
97
|
+
// Latin1 or UTF-8
|
|
98
|
+
let end = textData.indexOf(0);
|
|
99
|
+
if (end === -1)
|
|
100
|
+
end = textData.length;
|
|
101
|
+
return cleanText(textData.toString(encoding, 0, end));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Check if a frame ID matches any of the target IDs
|
|
105
|
+
*/
|
|
106
|
+
function matchesFrameId(frameId, targets) {
|
|
107
|
+
return targets.some((target) => frameId === target || frameId.startsWith(target));
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Extract metadata from an MP3 file with ID3v2 tags
|
|
111
|
+
*/
|
|
112
|
+
export async function extractFromMp3(reader) {
|
|
113
|
+
// Read ID3v2 header (10 bytes)
|
|
114
|
+
const header = await reader.read(0, 10);
|
|
115
|
+
if (header.length < 10 || header.toString('ascii', 0, 3) !== 'ID3') {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const majorVersion = header[3];
|
|
119
|
+
const flags = header[5];
|
|
120
|
+
const tagSize = readSyncsafe(header, 6);
|
|
121
|
+
// Handle extended header if present
|
|
122
|
+
let extendedHeaderSize = 0;
|
|
123
|
+
if (flags & 0x40) {
|
|
124
|
+
const extHeader = await reader.read(10, 4);
|
|
125
|
+
extendedHeaderSize =
|
|
126
|
+
majorVersion === 4 ? readSyncsafe(extHeader, 0) : extHeader.readUInt32BE(0);
|
|
127
|
+
}
|
|
128
|
+
// Read all tag data
|
|
129
|
+
const tagData = await reader.read(10 + extendedHeaderSize, tagSize - extendedHeaderSize);
|
|
130
|
+
const metadata = {};
|
|
131
|
+
let frontCover = null;
|
|
132
|
+
let anyArtwork = null;
|
|
133
|
+
// Parse frames
|
|
134
|
+
let offset = 0;
|
|
135
|
+
const frameHeaderSize = majorVersion >= 3 ? 10 : 6;
|
|
136
|
+
while (offset < tagData.length - frameHeaderSize) {
|
|
137
|
+
// End of frames (padding starts with zero)
|
|
138
|
+
if (tagData[offset] === 0)
|
|
139
|
+
break;
|
|
140
|
+
let frameId;
|
|
141
|
+
let frameSize;
|
|
142
|
+
if (majorVersion >= 3) {
|
|
143
|
+
// ID3v2.3 or ID3v2.4
|
|
144
|
+
frameId = tagData.toString('ascii', offset, offset + 4);
|
|
145
|
+
frameSize =
|
|
146
|
+
majorVersion === 4
|
|
147
|
+
? readSyncsafe(tagData, offset + 4)
|
|
148
|
+
: tagData.readUInt32BE(offset + 4);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// ID3v2.2
|
|
152
|
+
frameId = tagData.toString('ascii', offset, offset + 3);
|
|
153
|
+
frameSize =
|
|
154
|
+
(tagData[offset + 3] << 16) | (tagData[offset + 4] << 8) | tagData[offset + 5];
|
|
155
|
+
}
|
|
156
|
+
if (frameSize <= 0 || frameSize > tagData.length - offset)
|
|
157
|
+
break;
|
|
158
|
+
const frameData = tagData.subarray(offset + frameHeaderSize, offset + frameHeaderSize + frameSize);
|
|
159
|
+
// Normalize v2.2 frame IDs to v2.3/4 equivalents
|
|
160
|
+
const normalizedId = majorVersion >= 3 ? frameId : normalizeV22FrameId(frameId);
|
|
161
|
+
// Extract metadata from text frames
|
|
162
|
+
if (matchesFrameId(normalizedId, FRAME_IDS.TITLE)) {
|
|
163
|
+
metadata.title = metadata.title ?? parseTextFrame(frameData);
|
|
164
|
+
}
|
|
165
|
+
else if (matchesFrameId(normalizedId, FRAME_IDS.ARTIST)) {
|
|
166
|
+
metadata.artist = metadata.artist ?? parseTextFrame(frameData);
|
|
167
|
+
}
|
|
168
|
+
else if (matchesFrameId(normalizedId, FRAME_IDS.ALBUM)) {
|
|
169
|
+
metadata.album = metadata.album ?? parseTextFrame(frameData);
|
|
170
|
+
}
|
|
171
|
+
else if (matchesFrameId(normalizedId, FRAME_IDS.GENRE)) {
|
|
172
|
+
metadata.genre = metadata.genre ?? parseGenre(parseTextFrame(frameData));
|
|
173
|
+
}
|
|
174
|
+
else if (matchesFrameId(normalizedId, FRAME_IDS.YEAR)) {
|
|
175
|
+
metadata.year = metadata.year ?? parseYear(parseTextFrame(frameData) ?? '');
|
|
176
|
+
}
|
|
177
|
+
else if (matchesFrameId(normalizedId, FRAME_IDS.BPM)) {
|
|
178
|
+
metadata.bpm = metadata.bpm ?? parseBpm(parseTextFrame(frameData) ?? '');
|
|
179
|
+
}
|
|
180
|
+
else if (matchesFrameId(normalizedId, FRAME_IDS.KEY)) {
|
|
181
|
+
metadata.key = metadata.key ?? parseTextFrame(frameData);
|
|
182
|
+
}
|
|
183
|
+
else if (matchesFrameId(normalizedId, FRAME_IDS.PICTURE)) {
|
|
184
|
+
const artwork = parseApicFrame(frameData);
|
|
185
|
+
if (artwork) {
|
|
186
|
+
if (artwork.pictureType === PictureType.FrontCover) {
|
|
187
|
+
frontCover = artwork;
|
|
188
|
+
}
|
|
189
|
+
else if (!anyArtwork) {
|
|
190
|
+
anyArtwork = artwork;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
offset += frameHeaderSize + frameSize;
|
|
195
|
+
}
|
|
196
|
+
// Use front cover if available, otherwise any artwork
|
|
197
|
+
const artwork = frontCover ?? anyArtwork;
|
|
198
|
+
if (artwork) {
|
|
199
|
+
metadata.artwork = artwork.data;
|
|
200
|
+
metadata.artworkMimeType = artwork.mimeType;
|
|
201
|
+
}
|
|
202
|
+
// Return null if no metadata was found
|
|
203
|
+
if (Object.keys(metadata).length === 0) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
return metadata;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Normalize ID3v2.2 frame IDs to v2.3/4 equivalents
|
|
210
|
+
*/
|
|
211
|
+
function normalizeV22FrameId(frameId) {
|
|
212
|
+
const mapping = {
|
|
213
|
+
TT2: 'TIT2', // Title
|
|
214
|
+
TP1: 'TPE1', // Artist
|
|
215
|
+
TAL: 'TALB', // Album
|
|
216
|
+
TCO: 'TCON', // Genre
|
|
217
|
+
TYE: 'TYER', // Year
|
|
218
|
+
TBP: 'TBPM', // BPM
|
|
219
|
+
TKE: 'TKEY', // Key
|
|
220
|
+
PIC: 'APIC', // Picture
|
|
221
|
+
};
|
|
222
|
+
return mapping[frameId] ?? frameId;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Parse genre string, handling ID3v1 numeric references
|
|
226
|
+
* Format: "(17)Rock" or "(17)" or "Rock"
|
|
227
|
+
*/
|
|
228
|
+
function parseGenre(genre) {
|
|
229
|
+
if (!genre)
|
|
230
|
+
return undefined;
|
|
231
|
+
// Check for ID3v1 numeric reference
|
|
232
|
+
const match = genre.match(/^\((\d+)\)(.*)$/);
|
|
233
|
+
if (match) {
|
|
234
|
+
const id = parseInt(match[1], 10);
|
|
235
|
+
const textGenre = match[2].trim();
|
|
236
|
+
// If there's text after the number, use that
|
|
237
|
+
if (textGenre)
|
|
238
|
+
return textGenre;
|
|
239
|
+
// Otherwise look up the ID3v1 genre
|
|
240
|
+
return ID3V1_GENRES[id] ?? genre;
|
|
241
|
+
}
|
|
242
|
+
return genre;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* ID3v1 genre list
|
|
246
|
+
*/
|
|
247
|
+
const ID3V1_GENRES = {
|
|
248
|
+
0: 'Blues',
|
|
249
|
+
1: 'Classic Rock',
|
|
250
|
+
2: 'Country',
|
|
251
|
+
3: 'Dance',
|
|
252
|
+
4: 'Disco',
|
|
253
|
+
5: 'Funk',
|
|
254
|
+
6: 'Grunge',
|
|
255
|
+
7: 'Hip-Hop',
|
|
256
|
+
8: 'Jazz',
|
|
257
|
+
9: 'Metal',
|
|
258
|
+
10: 'New Age',
|
|
259
|
+
11: 'Oldies',
|
|
260
|
+
12: 'Other',
|
|
261
|
+
13: 'Pop',
|
|
262
|
+
14: 'R&B',
|
|
263
|
+
15: 'Rap',
|
|
264
|
+
16: 'Reggae',
|
|
265
|
+
17: 'Rock',
|
|
266
|
+
18: 'Techno',
|
|
267
|
+
19: 'Industrial',
|
|
268
|
+
20: 'Alternative',
|
|
269
|
+
21: 'Ska',
|
|
270
|
+
22: 'Death Metal',
|
|
271
|
+
23: 'Pranks',
|
|
272
|
+
24: 'Soundtrack',
|
|
273
|
+
25: 'Euro-Techno',
|
|
274
|
+
26: 'Ambient',
|
|
275
|
+
27: 'Trip-Hop',
|
|
276
|
+
28: 'Vocal',
|
|
277
|
+
29: 'Jazz+Funk',
|
|
278
|
+
30: 'Fusion',
|
|
279
|
+
31: 'Trance',
|
|
280
|
+
32: 'Classical',
|
|
281
|
+
33: 'Instrumental',
|
|
282
|
+
34: 'Acid',
|
|
283
|
+
35: 'House',
|
|
284
|
+
36: 'Game',
|
|
285
|
+
37: 'Sound Clip',
|
|
286
|
+
38: 'Gospel',
|
|
287
|
+
39: 'Noise',
|
|
288
|
+
40: 'Alternative Rock',
|
|
289
|
+
41: 'Bass',
|
|
290
|
+
42: 'Soul',
|
|
291
|
+
43: 'Punk',
|
|
292
|
+
44: 'Space',
|
|
293
|
+
45: 'Meditative',
|
|
294
|
+
46: 'Instrumental Pop',
|
|
295
|
+
47: 'Instrumental Rock',
|
|
296
|
+
48: 'Ethnic',
|
|
297
|
+
49: 'Gothic',
|
|
298
|
+
50: 'Darkwave',
|
|
299
|
+
51: 'Techno-Industrial',
|
|
300
|
+
52: 'Electronic',
|
|
301
|
+
53: 'Pop-Folk',
|
|
302
|
+
54: 'Eurodance',
|
|
303
|
+
55: 'Dream',
|
|
304
|
+
56: 'Southern Rock',
|
|
305
|
+
57: 'Comedy',
|
|
306
|
+
58: 'Cult',
|
|
307
|
+
59: 'Gangsta',
|
|
308
|
+
60: 'Top 40',
|
|
309
|
+
61: 'Christian Rap',
|
|
310
|
+
62: 'Pop/Funk',
|
|
311
|
+
63: 'Jungle',
|
|
312
|
+
64: 'Native US',
|
|
313
|
+
65: 'Cabaret',
|
|
314
|
+
66: 'New Wave',
|
|
315
|
+
67: 'Psychedelic',
|
|
316
|
+
68: 'Rave',
|
|
317
|
+
69: 'Showtunes',
|
|
318
|
+
70: 'Trailer',
|
|
319
|
+
71: 'Lo-Fi',
|
|
320
|
+
72: 'Tribal',
|
|
321
|
+
73: 'Acid Punk',
|
|
322
|
+
74: 'Acid Jazz',
|
|
323
|
+
75: 'Polka',
|
|
324
|
+
76: 'Retro',
|
|
325
|
+
77: 'Musical',
|
|
326
|
+
78: 'Rock & Roll',
|
|
327
|
+
79: 'Hard Rock',
|
|
328
|
+
};
|
|
329
|
+
//# sourceMappingURL=id3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id3.js","sourceRoot":"","sources":["../../src/parsers/id3.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { extractFromMp3 } from './id3.js';
|
|
2
|
+
export { extractFromMp4 } from './mp4.js';
|
|
3
|
+
export { extractFromFlac } from './flac.js';
|
|
4
|
+
export { extractFromAiff } from './aiff.js';
|
|
5
|
+
export { detectImageType, normalizeMimeType } from './utils.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parsers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { extractFromMp3 } from './id3.js';
|
|
2
|
+
export { extractFromMp4 } from './mp4.js';
|
|
3
|
+
export { extractFromFlac } from './flac.js';
|
|
4
|
+
export { extractFromAiff } from './aiff.js';
|
|
5
|
+
export { detectImageType, normalizeMimeType } from './utils.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/parsers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mp4.d.ts","sourceRoot":"","sources":["../../src/parsers/mp4.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAmB,MAAM,aAAa,CAAC;AAsLlF;;GAEG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAiG1F"}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { detectImageType, cleanText } from './utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* iTunes metadata atom types
|
|
4
|
+
*/
|
|
5
|
+
const ATOM_TYPES = {
|
|
6
|
+
TITLE: '\xa9nam', // Title
|
|
7
|
+
ARTIST: '\xa9ART', // Artist
|
|
8
|
+
ALBUM: '\xa9alb', // Album
|
|
9
|
+
GENRE: '\xa9gen', // Genre (text)
|
|
10
|
+
GENRE_ID: 'gnre', // Genre (ID3v1 numeric)
|
|
11
|
+
YEAR: '\xa9day', // Release date/year
|
|
12
|
+
BPM: 'tmpo', // BPM (tempo)
|
|
13
|
+
COVER: 'covr', // Cover artwork
|
|
14
|
+
// Additional useful atoms
|
|
15
|
+
ALBUM_ARTIST: 'aART', // Album artist
|
|
16
|
+
COMPOSER: '\xa9wrt', // Composer
|
|
17
|
+
COMMENT: '\xa9cmt', // Comment
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Read an MP4 atom header
|
|
21
|
+
*/
|
|
22
|
+
async function readAtomHeader(reader, offset) {
|
|
23
|
+
if (offset + 8 > reader.size)
|
|
24
|
+
return null;
|
|
25
|
+
const header = await reader.read(offset, 8);
|
|
26
|
+
const size = header.readUInt32BE(0);
|
|
27
|
+
const type = header.toString('ascii', 4, 8);
|
|
28
|
+
// Extended size (64-bit)
|
|
29
|
+
if (size === 1) {
|
|
30
|
+
if (offset + 16 > reader.size)
|
|
31
|
+
return null;
|
|
32
|
+
const extHeader = await reader.read(offset + 8, 8);
|
|
33
|
+
const extSize = Number(extHeader.readBigUInt64BE(0));
|
|
34
|
+
return { size: extSize, type, headerSize: 16 };
|
|
35
|
+
}
|
|
36
|
+
// Size 0 means atom extends to end of file
|
|
37
|
+
if (size === 0) {
|
|
38
|
+
return { size: reader.size - offset, type, headerSize: 8 };
|
|
39
|
+
}
|
|
40
|
+
return { size, type, headerSize: 8 };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Find an atom within a range
|
|
44
|
+
*/
|
|
45
|
+
async function findAtom(reader, startOffset, endOffset, targetType) {
|
|
46
|
+
let offset = startOffset;
|
|
47
|
+
while (offset < endOffset) {
|
|
48
|
+
const header = await readAtomHeader(reader, offset);
|
|
49
|
+
if (!header || header.size <= 0)
|
|
50
|
+
break;
|
|
51
|
+
if (header.type === targetType) {
|
|
52
|
+
return {
|
|
53
|
+
dataOffset: offset + header.headerSize,
|
|
54
|
+
dataSize: header.size - header.headerSize,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
offset += header.size;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Find the moov atom (contains all metadata)
|
|
63
|
+
*/
|
|
64
|
+
async function findMoovAtom(reader) {
|
|
65
|
+
return findAtom(reader, 0, reader.size, 'moov');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Navigate to ilst (iTunes metadata list) atom
|
|
69
|
+
* Path: moov -> udta -> meta -> ilst
|
|
70
|
+
*/
|
|
71
|
+
async function findIlstAtom(reader, moovOffset, moovSize) {
|
|
72
|
+
const udta = await findAtom(reader, moovOffset, moovOffset + moovSize, 'udta');
|
|
73
|
+
if (!udta)
|
|
74
|
+
return null;
|
|
75
|
+
const meta = await findAtom(reader, udta.dataOffset, udta.dataOffset + udta.dataSize, 'meta');
|
|
76
|
+
if (!meta)
|
|
77
|
+
return null;
|
|
78
|
+
// meta atom has 4 bytes of version/flags before child atoms
|
|
79
|
+
const metaDataStart = meta.dataOffset + 4;
|
|
80
|
+
const metaDataEnd = meta.dataOffset + meta.dataSize;
|
|
81
|
+
const ilst = await findAtom(reader, metaDataStart, metaDataEnd, 'ilst');
|
|
82
|
+
return ilst;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Read a text data atom value
|
|
86
|
+
*/
|
|
87
|
+
async function readTextDataAtom(reader, atomOffset, atomSize) {
|
|
88
|
+
const data = await findAtom(reader, atomOffset, atomOffset + atomSize, 'data');
|
|
89
|
+
if (!data || data.dataSize < 8)
|
|
90
|
+
return undefined;
|
|
91
|
+
// data atom: 4 bytes type + 4 bytes locale + actual data
|
|
92
|
+
const content = await reader.read(data.dataOffset + 8, data.dataSize - 8);
|
|
93
|
+
return cleanText(content.toString('utf8'));
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Read a numeric data atom value (for BPM, genre ID)
|
|
97
|
+
*/
|
|
98
|
+
async function readNumericDataAtom(reader, atomOffset, atomSize) {
|
|
99
|
+
const data = await findAtom(reader, atomOffset, atomOffset + atomSize, 'data');
|
|
100
|
+
if (!data || data.dataSize < 10)
|
|
101
|
+
return undefined;
|
|
102
|
+
// data atom: 4 bytes type + 4 bytes locale + actual data
|
|
103
|
+
const content = await reader.read(data.dataOffset + 8, data.dataSize - 8);
|
|
104
|
+
// BPM is typically stored as 16-bit big-endian
|
|
105
|
+
if (content.length >= 2) {
|
|
106
|
+
return content.readUInt16BE(0);
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Read cover artwork from covr atom
|
|
112
|
+
*/
|
|
113
|
+
async function readCoverArtwork(reader, atomOffset, atomSize) {
|
|
114
|
+
const data = await findAtom(reader, atomOffset, atomOffset + atomSize, 'data');
|
|
115
|
+
if (!data || data.dataSize < 8)
|
|
116
|
+
return null;
|
|
117
|
+
const imageData = await reader.read(data.dataOffset + 8, data.dataSize - 8);
|
|
118
|
+
if (imageData.length === 0)
|
|
119
|
+
return null;
|
|
120
|
+
const mimeType = detectImageType(imageData) ?? 'image/jpeg';
|
|
121
|
+
return { data: imageData, mimeType };
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Extract metadata from an MP4/M4A file
|
|
125
|
+
*/
|
|
126
|
+
export async function extractFromMp4(reader) {
|
|
127
|
+
// Verify this is an MP4 file by checking for ftyp atom
|
|
128
|
+
const ftypHeader = await reader.read(0, 8);
|
|
129
|
+
if (ftypHeader.length < 8 || ftypHeader.toString('ascii', 4, 8) !== 'ftyp') {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
// Find moov atom
|
|
133
|
+
const moov = await findMoovAtom(reader);
|
|
134
|
+
if (!moov)
|
|
135
|
+
return null;
|
|
136
|
+
// Find ilst atom (iTunes metadata)
|
|
137
|
+
const ilst = await findIlstAtom(reader, moov.dataOffset, moov.dataSize);
|
|
138
|
+
if (!ilst)
|
|
139
|
+
return null;
|
|
140
|
+
const metadata = {};
|
|
141
|
+
// Iterate through ilst child atoms
|
|
142
|
+
let offset = ilst.dataOffset;
|
|
143
|
+
const endOffset = ilst.dataOffset + ilst.dataSize;
|
|
144
|
+
while (offset < endOffset) {
|
|
145
|
+
const header = await readAtomHeader(reader, offset);
|
|
146
|
+
if (!header || header.size <= 0)
|
|
147
|
+
break;
|
|
148
|
+
const atomType = header.type;
|
|
149
|
+
const atomDataOffset = offset + header.headerSize;
|
|
150
|
+
const atomDataSize = header.size - header.headerSize;
|
|
151
|
+
// Extract metadata based on atom type
|
|
152
|
+
switch (atomType) {
|
|
153
|
+
case ATOM_TYPES.TITLE:
|
|
154
|
+
metadata.title = metadata.title ?? (await readTextDataAtom(reader, atomDataOffset, atomDataSize));
|
|
155
|
+
break;
|
|
156
|
+
case ATOM_TYPES.ARTIST:
|
|
157
|
+
metadata.artist = metadata.artist ?? (await readTextDataAtom(reader, atomDataOffset, atomDataSize));
|
|
158
|
+
break;
|
|
159
|
+
case ATOM_TYPES.ALBUM:
|
|
160
|
+
metadata.album = metadata.album ?? (await readTextDataAtom(reader, atomDataOffset, atomDataSize));
|
|
161
|
+
break;
|
|
162
|
+
case ATOM_TYPES.GENRE: {
|
|
163
|
+
const genre = await readTextDataAtom(reader, atomDataOffset, atomDataSize);
|
|
164
|
+
metadata.genre = metadata.genre ?? genre;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case ATOM_TYPES.GENRE_ID: {
|
|
168
|
+
const genreId = await readNumericDataAtom(reader, atomDataOffset, atomDataSize);
|
|
169
|
+
if (genreId !== undefined && !metadata.genre) {
|
|
170
|
+
metadata.genre = ID3V1_GENRES[genreId - 1]; // MP4 genre IDs are 1-based
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
case ATOM_TYPES.YEAR: {
|
|
175
|
+
const yearStr = await readTextDataAtom(reader, atomDataOffset, atomDataSize);
|
|
176
|
+
if (yearStr && !metadata.year) {
|
|
177
|
+
const match = yearStr.match(/\d{4}/);
|
|
178
|
+
if (match) {
|
|
179
|
+
metadata.year = parseInt(match[0], 10);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
case ATOM_TYPES.BPM: {
|
|
185
|
+
const bpm = await readNumericDataAtom(reader, atomDataOffset, atomDataSize);
|
|
186
|
+
if (bpm !== undefined && bpm > 0 && bpm < 500) {
|
|
187
|
+
metadata.bpm = metadata.bpm ?? bpm;
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case ATOM_TYPES.COVER: {
|
|
192
|
+
if (!metadata.artwork) {
|
|
193
|
+
const artwork = await readCoverArtwork(reader, atomDataOffset, atomDataSize);
|
|
194
|
+
if (artwork) {
|
|
195
|
+
metadata.artwork = artwork.data;
|
|
196
|
+
metadata.artworkMimeType = artwork.mimeType;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
offset += header.size;
|
|
203
|
+
}
|
|
204
|
+
// Return null if no metadata was found
|
|
205
|
+
if (Object.keys(metadata).length === 0) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
return metadata;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* ID3v1 genre list (used for gnre atom)
|
|
212
|
+
*/
|
|
213
|
+
const ID3V1_GENRES = {
|
|
214
|
+
0: 'Blues',
|
|
215
|
+
1: 'Classic Rock',
|
|
216
|
+
2: 'Country',
|
|
217
|
+
3: 'Dance',
|
|
218
|
+
4: 'Disco',
|
|
219
|
+
5: 'Funk',
|
|
220
|
+
6: 'Grunge',
|
|
221
|
+
7: 'Hip-Hop',
|
|
222
|
+
8: 'Jazz',
|
|
223
|
+
9: 'Metal',
|
|
224
|
+
10: 'New Age',
|
|
225
|
+
11: 'Oldies',
|
|
226
|
+
12: 'Other',
|
|
227
|
+
13: 'Pop',
|
|
228
|
+
14: 'R&B',
|
|
229
|
+
15: 'Rap',
|
|
230
|
+
16: 'Reggae',
|
|
231
|
+
17: 'Rock',
|
|
232
|
+
18: 'Techno',
|
|
233
|
+
19: 'Industrial',
|
|
234
|
+
20: 'Alternative',
|
|
235
|
+
21: 'Ska',
|
|
236
|
+
22: 'Death Metal',
|
|
237
|
+
23: 'Pranks',
|
|
238
|
+
24: 'Soundtrack',
|
|
239
|
+
25: 'Euro-Techno',
|
|
240
|
+
26: 'Ambient',
|
|
241
|
+
27: 'Trip-Hop',
|
|
242
|
+
28: 'Vocal',
|
|
243
|
+
29: 'Jazz+Funk',
|
|
244
|
+
30: 'Fusion',
|
|
245
|
+
31: 'Trance',
|
|
246
|
+
32: 'Classical',
|
|
247
|
+
33: 'Instrumental',
|
|
248
|
+
34: 'Acid',
|
|
249
|
+
35: 'House',
|
|
250
|
+
36: 'Game',
|
|
251
|
+
37: 'Sound Clip',
|
|
252
|
+
38: 'Gospel',
|
|
253
|
+
39: 'Noise',
|
|
254
|
+
40: 'Alternative Rock',
|
|
255
|
+
41: 'Bass',
|
|
256
|
+
42: 'Soul',
|
|
257
|
+
43: 'Punk',
|
|
258
|
+
44: 'Space',
|
|
259
|
+
45: 'Meditative',
|
|
260
|
+
46: 'Instrumental Pop',
|
|
261
|
+
47: 'Instrumental Rock',
|
|
262
|
+
48: 'Ethnic',
|
|
263
|
+
49: 'Gothic',
|
|
264
|
+
50: 'Darkwave',
|
|
265
|
+
51: 'Techno-Industrial',
|
|
266
|
+
52: 'Electronic',
|
|
267
|
+
53: 'Pop-Folk',
|
|
268
|
+
54: 'Eurodance',
|
|
269
|
+
55: 'Dream',
|
|
270
|
+
56: 'Southern Rock',
|
|
271
|
+
57: 'Comedy',
|
|
272
|
+
58: 'Cult',
|
|
273
|
+
59: 'Gangsta',
|
|
274
|
+
60: 'Top 40',
|
|
275
|
+
61: 'Christian Rap',
|
|
276
|
+
62: 'Pop/Funk',
|
|
277
|
+
63: 'Jungle',
|
|
278
|
+
64: 'Native US',
|
|
279
|
+
65: 'Cabaret',
|
|
280
|
+
66: 'New Wave',
|
|
281
|
+
67: 'Psychedelic',
|
|
282
|
+
68: 'Rave',
|
|
283
|
+
69: 'Showtunes',
|
|
284
|
+
70: 'Trailer',
|
|
285
|
+
71: 'Lo-Fi',
|
|
286
|
+
72: 'Tribal',
|
|
287
|
+
73: 'Acid Punk',
|
|
288
|
+
74: 'Acid Jazz',
|
|
289
|
+
75: 'Polka',
|
|
290
|
+
76: 'Retro',
|
|
291
|
+
77: 'Musical',
|
|
292
|
+
78: 'Rock & Roll',
|
|
293
|
+
79: 'Hard Rock',
|
|
294
|
+
};
|
|
295
|
+
//# sourceMappingURL=mp4.js.map
|