forked from bignerdranch/id3-meta
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
128 lines (102 loc) · 3.04 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import Cursor from './cursor';
export { mapSongMeta, mapAlbumMeta } from './map';
let decode = (format, string) => new TextDecoder(format).decode(string);
let synchToInt = synch => {
const mask = 0b01111111;
let b1 = synch & mask;
let b2 = (synch >> 8) & mask;
let b3 = (synch >> 16) & mask;
let b4 = (synch >> 24) & mask;
return b1 | (b2 << 7) | (b3 << 14) | (b4 << 21);
};
const HEADER_SIZE = 10;
const ID3_ENCODINGS = [
'ascii',
'utf-16',
'utf-16be',
'utf-8'
];
let decodeLang = (cursor) => {
return decode('ascii', new Uint8Array(...cursor(3)));
};
let decodeEncoding = (cursor) => {
let view = new DataView(...cursor(1));
return ID3_ENCODINGS[view.getUint8()];
};
let decodeText = (cursor, size, encoding) => {
return decode(encoding,
new Uint8Array(...cursor(size)));
};
let decodeNullString = (cursor, limit, encoding = 'ascii') => {
let offset = cursor.offset();
let array = new Uint8Array(...cursor(limit));
let index = array.indexOf(0);
if (index < 0) { return ''; }
cursor.skip(index - limit + 1);
return decode(encoding, array.slice(0, index));
};
let decodeImage = (cursor, size) => {
let [buffer, offset] = cursor(size);
return buffer.slice(offset, offset+size);
};
let defaultFrameDecoder = (cursor, size) => {
let encoding = decodeEncoding(cursor);
return {
text: decodeText(cursor, size-1, encoding)
};
};
let langFrameDecoder = (cursor, size) => {
let encoding = decodeEncoding(cursor);
return {
lang: decodeLang(cursor),
text: decodeText(cursor, size-4, encoding)
};
};
let picFrameDecoder = (cursor, size) => {
let offset = cursor.offset();
let remaining = () => size + offset - cursor.offset();
let encoding = decodeEncoding(cursor);
let mime = decodeNullString(cursor, remaining());
cursor.skip(1);
let text = decodeNullString(cursor, remaining(), encoding);
let image = decodeImage(cursor, remaining());
let blob = new Blob([image], { type: mime });
return { mime, text, blob };
};
let frameDecoders = {
APIC: picFrameDecoder,
USLT: langFrameDecoder,
SYLT: langFrameDecoder,
COMM: langFrameDecoder,
USER: langFrameDecoder,
};
let decodeFrame = (buffer, offset) => {
let cursor = Cursor(buffer, offset);
let header = new DataView(...cursor(HEADER_SIZE));
if (header.getUint8(0) === 0) { return; }
let id = decode('ascii', new Uint8Array(buffer, offset, 4));
let size = header.getUint32(4);
let decoder = frameDecoders[id] || defaultFrameDecoder;
return {
id, size: size + HEADER_SIZE,
...decoder(cursor, size)
};
};
export default (buffer) => {
let header = new DataView(buffer, 0, HEADER_SIZE);
let major = header.getUint8(3);
let minor = header.getUint8(4);
let version = `ID3v2.${major}.${minor}`;
// console.log(version);
let size = synchToInt(header.getUint32(6));
let offset = HEADER_SIZE;
let id3Size = HEADER_SIZE + size;
let frames = {};
while (offset < id3Size) {
let frame = decodeFrame(buffer, offset);
if (!frame) { break; }
frames[frame.id] = frame;
offset += frame.size;
}
return frames;
}