1 | var trimString = function (string) {
|
---|
2 | return string.replace(/\0$/, "");
|
---|
3 | };
|
---|
4 |
|
---|
5 | var stringToEncoding = function(string, encoding) {
|
---|
6 | switch (encoding) {
|
---|
7 | case 0:
|
---|
8 | return trimString(string);
|
---|
9 | case 1:
|
---|
10 | var ix = 2, offset1 = 1, offset2 = 0;
|
---|
11 |
|
---|
12 | if (string.slice(0, 2) === "\xFE\xFF") {
|
---|
13 | offset1 = 0, offset2 = 1;
|
---|
14 | } else {
|
---|
15 | offset1 = 1, offset2 = 0;
|
---|
16 | }
|
---|
17 |
|
---|
18 | var result = "";
|
---|
19 |
|
---|
20 | for (var ix = 2; ix < string.length; ix += 2) {
|
---|
21 | var byte1 = string[ix + offset1].charCodeAt(0);
|
---|
22 | var byte2 = string[ix + offset2].charCodeAt(0);
|
---|
23 |
|
---|
24 | var word1 = (byte1 << 8) | byte2;
|
---|
25 |
|
---|
26 | if (byte1 < 0xD8 || byte1 >= 0xE0) {
|
---|
27 | result += String.fromCharCode(word1);
|
---|
28 | } else {
|
---|
29 | ix += 2;
|
---|
30 |
|
---|
31 | var byte3 = string[ix + offset1].charCodeAt(0);
|
---|
32 | var byte4 = string[ix + offset2].charCodeAt(0);
|
---|
33 |
|
---|
34 | var word2 = (byte3 << 8) | byte4;
|
---|
35 |
|
---|
36 | result += String.fromCharCode(word1, word2);
|
---|
37 | }
|
---|
38 | }
|
---|
39 |
|
---|
40 | return trimString(result);
|
---|
41 | default:
|
---|
42 | return string;
|
---|
43 | }
|
---|
44 | }
|
---|
45 |
|
---|
46 | var decodeIdentifierFrame = function(header, stream) {
|
---|
47 | var data = stream.read(header['length']);
|
---|
48 |
|
---|
49 | var array = data.split("\0", 2);
|
---|
50 |
|
---|
51 | return {
|
---|
52 | 'header': header,
|
---|
53 | 'owner': array[0],
|
---|
54 | 'identifier': array[1]
|
---|
55 | };
|
---|
56 | }
|
---|
57 |
|
---|
58 | var decodeTextFrame = function(header, stream) {
|
---|
59 | var encoding = stream.readU8();
|
---|
60 | var data = stream.read(header['length'] - 1);
|
---|
61 |
|
---|
62 | return {
|
---|
63 | 'header': header,
|
---|
64 | 'value': stringToEncoding(data, encoding)
|
---|
65 | };
|
---|
66 | }
|
---|
67 |
|
---|
68 | var decodeCommentFrame = function(header, stream) {
|
---|
69 | var encoding = stream.readU8();
|
---|
70 | var language = stream.read(3);
|
---|
71 |
|
---|
72 | var data = stream.read(header['length'] - 4);
|
---|
73 |
|
---|
74 | var array = data.split("\0", 2);
|
---|
75 |
|
---|
76 | return {
|
---|
77 | 'header': header,
|
---|
78 | 'language': stringToEncoding(language, 0),
|
---|
79 | 'description': stringToEncoding(array[0], 0),
|
---|
80 | 'value': stringToEncoding(array[1], encoding)
|
---|
81 | };
|
---|
82 | }
|
---|
83 |
|
---|
84 | var extendWithValueFrame = function(tags, frame) {
|
---|
85 | tags[frame['name']] = frame['value'];
|
---|
86 |
|
---|
87 | return tags;
|
---|
88 | }
|
---|
89 |
|
---|
90 |
|
---|
91 | var extendWithIdentifierFrame = function(tags, frame) {
|
---|
92 | tags[frame['name']] = {
|
---|
93 | 'owner': frame['value'],
|
---|
94 | 'identifier': frame['identifier']
|
---|
95 | };
|
---|
96 |
|
---|
97 | return tags;
|
---|
98 | }
|
---|
99 |
|
---|
100 | var extendWithCommentFrame = function(tags, frame) {
|
---|
101 | if (!tags[frame['name']]) {
|
---|
102 | tags[frame['name']] = []
|
---|
103 | }
|
---|
104 |
|
---|
105 | tags[frame['name']][frame['description']] = {
|
---|
106 | 'language': frame['language'],
|
---|
107 | 'value': frame['value']
|
---|
108 | };
|
---|
109 |
|
---|
110 | return tags;
|
---|
111 | }
|
---|
112 |
|
---|
113 | Mad.ID3v22Stream = function(header, stream) {
|
---|
114 | this.offset = 0;
|
---|
115 |
|
---|
116 | this.header = header;
|
---|
117 | this.stream = stream;
|
---|
118 |
|
---|
119 | this.decoders = {
|
---|
120 | 'UFI': decodeIdentifierFrame,
|
---|
121 | 'TT1': decodeTextFrame,
|
---|
122 | 'TT2': decodeTextFrame,
|
---|
123 | 'TT3': decodeTextFrame,
|
---|
124 | 'TP1': decodeTextFrame,
|
---|
125 | 'TP2': decodeTextFrame,
|
---|
126 | 'TP3': decodeTextFrame,
|
---|
127 | 'TP4': decodeTextFrame,
|
---|
128 | 'TCM': decodeTextFrame,
|
---|
129 | 'TXT': decodeTextFrame,
|
---|
130 | 'TLA': decodeTextFrame,
|
---|
131 | 'TCO': decodeTextFrame,
|
---|
132 | 'TAL': decodeTextFrame,
|
---|
133 | 'TPA': decodeTextFrame,
|
---|
134 | 'TRK': decodeTextFrame,
|
---|
135 | 'TRC': decodeTextFrame,
|
---|
136 | 'TYE': decodeTextFrame,
|
---|
137 | 'TDA': decodeTextFrame,
|
---|
138 | 'TIM': decodeTextFrame,
|
---|
139 | 'TRD': decodeTextFrame,
|
---|
140 | 'TMT': decodeTextFrame,
|
---|
141 | 'TFT': decodeTextFrame,
|
---|
142 | 'TBP': decodeTextFrame,
|
---|
143 | 'TCR': decodeTextFrame,
|
---|
144 | 'TPB': decodeTextFrame,
|
---|
145 | 'TEN': decodeTextFrame,
|
---|
146 | 'TSS': decodeTextFrame,
|
---|
147 | 'TOF': decodeTextFrame,
|
---|
148 | 'TLE': decodeTextFrame,
|
---|
149 | 'TSI': decodeTextFrame,
|
---|
150 | 'TDY': decodeTextFrame,
|
---|
151 | 'TKE': decodeTextFrame,
|
---|
152 | 'TOT': decodeTextFrame,
|
---|
153 | 'TOA': decodeTextFrame,
|
---|
154 | 'TOL': decodeTextFrame,
|
---|
155 | 'TOR': decodeTextFrame,
|
---|
156 |
|
---|
157 | 'COM': decodeCommentFrame
|
---|
158 | };
|
---|
159 |
|
---|
160 | this.names = {
|
---|
161 | /* Identification Frames */
|
---|
162 | 'TT1': 'Content group description',
|
---|
163 | 'TT2': 'Title/Songname/Content description',
|
---|
164 | 'TT3': 'Subtitle/Description refinement',
|
---|
165 | 'TAL': 'Album/Movie/Show title',
|
---|
166 | 'TOT': 'Original album/movie/show title',
|
---|
167 | 'TRK': 'Track number/Position in set',
|
---|
168 | 'TPA': 'Part of a set',
|
---|
169 | 'TRC': 'ISRC',
|
---|
170 |
|
---|
171 | /* Involved Persons Frames */
|
---|
172 | 'TP1': 'Lead artist/Lead performer/Soloist/Performing group',
|
---|
173 | 'TP2': 'Band/Orchestra/Accompaniment',
|
---|
174 | 'TP3': 'Conductor',
|
---|
175 | 'TP4': 'Interpreted, remixed, or otherwise modified by',
|
---|
176 | 'TOA': 'Original artist/performer',
|
---|
177 | 'TXT': 'Lyricist/Text writer',
|
---|
178 | 'TOL': 'Original lyricist/text writer',
|
---|
179 | 'TCO': 'Composer',
|
---|
180 | 'TEN': 'Encoded by',
|
---|
181 |
|
---|
182 | /* Derived and Subjective Properties Frames */
|
---|
183 | 'TBP': 'BPM',
|
---|
184 | 'TLE': 'Length',
|
---|
185 | 'TKE': 'Initial key',
|
---|
186 | 'TLA': 'Language',
|
---|
187 | 'TMT': 'Media type',
|
---|
188 |
|
---|
189 | /* Rights and Licence Frames */
|
---|
190 | 'TCR': 'Copyright message',
|
---|
191 | 'TPB': 'Publisher',
|
---|
192 |
|
---|
193 | /* Other Text Frames */
|
---|
194 | 'TOF': 'Original filename',
|
---|
195 | 'TDY': 'Playlist delay',
|
---|
196 | 'TSS': 'Software/Hardware and settings used for encoding',
|
---|
197 | 'TFT': 'File type',
|
---|
198 |
|
---|
199 | /* Buffering */
|
---|
200 | 'BUF': 'Recommended buffer size',
|
---|
201 |
|
---|
202 | /* Attached Picture Frame */
|
---|
203 | 'PIC': 'Attached picture',
|
---|
204 |
|
---|
205 | /* Unique Identifier Frame */
|
---|
206 | 'UFI': 'Unique identifier',
|
---|
207 |
|
---|
208 | /* Music CD Identifier Frame */
|
---|
209 | 'MCI': 'Music CD identifier',
|
---|
210 |
|
---|
211 | /* Comment Frame */
|
---|
212 | 'COM': 'Comment',
|
---|
213 |
|
---|
214 | /* User Defined URL Link Frame */
|
---|
215 | 'WXX': 'User defined URL link',
|
---|
216 |
|
---|
217 | /* Deprecated ID3v2 frames */
|
---|
218 | 'TDA': 'Date',
|
---|
219 | 'TIM': 'Time',
|
---|
220 | 'TOR': 'Original release year',
|
---|
221 | 'TRD': 'Recording dates',
|
---|
222 | 'TSI': 'Size',
|
---|
223 | 'TYE': 'Year'
|
---|
224 | };
|
---|
225 |
|
---|
226 | this.extenders = {
|
---|
227 | 'UFI': extendWithIdentifierFrame,
|
---|
228 | 'TT1': extendWithValueFrame,
|
---|
229 | 'TT2': extendWithValueFrame,
|
---|
230 | 'TT3': extendWithValueFrame,
|
---|
231 | 'TP1': extendWithValueFrame,
|
---|
232 | 'TP2': extendWithValueFrame,
|
---|
233 | 'TP3': extendWithValueFrame,
|
---|
234 | 'TP4': extendWithValueFrame,
|
---|
235 | 'TCM': extendWithValueFrame,
|
---|
236 | 'TXT': extendWithValueFrame,
|
---|
237 | 'TLA': extendWithValueFrame,
|
---|
238 | 'TCO': extendWithValueFrame,
|
---|
239 | 'TAL': extendWithValueFrame,
|
---|
240 | 'TPA': extendWithValueFrame,
|
---|
241 | 'TRK': extendWithValueFrame,
|
---|
242 | 'TRC': extendWithValueFrame,
|
---|
243 | 'TYE': extendWithValueFrame,
|
---|
244 | 'TDA': extendWithValueFrame,
|
---|
245 | 'TIM': extendWithValueFrame,
|
---|
246 | 'TRD': extendWithValueFrame,
|
---|
247 | 'TMT': extendWithValueFrame,
|
---|
248 | 'TFT': extendWithValueFrame,
|
---|
249 | 'TBP': extendWithValueFrame,
|
---|
250 | 'TCR': extendWithValueFrame,
|
---|
251 | 'TPB': extendWithValueFrame,
|
---|
252 | 'TEN': extendWithValueFrame,
|
---|
253 | 'TSS': extendWithValueFrame,
|
---|
254 | 'TOF': extendWithValueFrame,
|
---|
255 | 'TLE': extendWithValueFrame,
|
---|
256 | 'TSI': extendWithValueFrame,
|
---|
257 | 'TDY': extendWithValueFrame,
|
---|
258 | 'TKE': extendWithValueFrame,
|
---|
259 | 'TOT': extendWithValueFrame,
|
---|
260 | 'TOA': extendWithValueFrame,
|
---|
261 | 'TOL': extendWithValueFrame,
|
---|
262 | 'TOR': extendWithValueFrame,
|
---|
263 |
|
---|
264 | 'COM': extendWithCommentFrame
|
---|
265 | };
|
---|
266 | }
|
---|
267 |
|
---|
268 | Mad.ID3v22Stream.prototype.readFrame = function() {
|
---|
269 | if (this.offset >= this.header.length) {
|
---|
270 | return null;
|
---|
271 | }
|
---|
272 |
|
---|
273 | var identifier = this.stream.read(3);
|
---|
274 |
|
---|
275 | if (identifier.charCodeAt(0) === 0) {
|
---|
276 | this.offset = this.header.length + 1;
|
---|
277 |
|
---|
278 | return null;
|
---|
279 | }
|
---|
280 |
|
---|
281 | var length = this.stream.readU24(true);
|
---|
282 |
|
---|
283 | var header = {
|
---|
284 | identifier: identifier,
|
---|
285 | length: length
|
---|
286 | };
|
---|
287 |
|
---|
288 | if (this.decoders[identifier]) {
|
---|
289 | var result = this.decoders[identifier](header, this.stream);
|
---|
290 | } else {
|
---|
291 | var result = {
|
---|
292 | identifier: identifier,
|
---|
293 | header: header
|
---|
294 | };
|
---|
295 |
|
---|
296 | this.stream.read(length);
|
---|
297 | }
|
---|
298 |
|
---|
299 | result.name = this.names[identifier] ? this.names[identifier] : 'UNKNOWN';
|
---|
300 |
|
---|
301 | this.offset += 10 + length;
|
---|
302 |
|
---|
303 | return result;
|
---|
304 | }
|
---|
305 |
|
---|
306 | Mad.ID3v22Stream.prototype.read = function() {
|
---|
307 | if (!this.array) {
|
---|
308 | this.array = [];
|
---|
309 |
|
---|
310 | var frame = null;
|
---|
311 |
|
---|
312 | try {
|
---|
313 | while (frame = this.readFrame()) {
|
---|
314 | this.array.push(frame);
|
---|
315 | }
|
---|
316 | } catch (e) {
|
---|
317 | throw(e);
|
---|
318 | //console.log("ID3 Error: " + e);
|
---|
319 | }
|
---|
320 | }
|
---|
321 |
|
---|
322 | return this.array;
|
---|
323 | }
|
---|
324 |
|
---|
325 | Mad.ID3v22Stream.prototype.toHash = function() {
|
---|
326 | var frames = this.read();
|
---|
327 |
|
---|
328 | var hash = {};
|
---|
329 |
|
---|
330 | for (var i = 0; i < frames.length; i++) {
|
---|
331 | var frame = frames[i];
|
---|
332 |
|
---|
333 | var extender = this.extenders[frame['header']['identifier']];
|
---|
334 |
|
---|
335 | if (extender) {
|
---|
336 | hash = extender(hash, frames[i]);
|
---|
337 | }
|
---|
338 | }
|
---|
339 |
|
---|
340 | return hash;
|
---|
341 | }
|
---|