OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 'use strict'; | |
6 | |
7 importScripts('function_sequence.js'); | |
8 importScripts('function_parallel.js'); | |
9 | |
10 function Id3Parser(parent) { | |
11 MetadataParser.call(this, parent, 'id3', /\.(mp3)$/i); | |
12 } | |
13 | |
14 Id3Parser.prototype = {__proto__: MetadataParser.prototype}; | |
15 | |
16 /** | |
17 * Reads synchsafe integer. | |
18 * 'SynchSafe' term is taken from id3 documentation. | |
19 * | |
20 * @param {ByteReader} reader - reader to use. | |
21 * @param {number} length - bytes to read. | |
22 * @return {number} // TODO(JSDOC). | |
23 * @private | |
24 */ | |
25 Id3Parser.readSynchSafe_ = function(reader, length) { | |
26 var rv = 0; | |
27 | |
28 switch (length) { | |
29 case 4: | |
30 rv = reader.readScalar(1, false) << 21; | |
31 case 3: | |
32 rv |= reader.readScalar(1, false) << 14; | |
33 case 2: | |
34 rv |= reader.readScalar(1, false) << 7; | |
35 case 1: | |
36 rv |= reader.readScalar(1, false); | |
37 } | |
38 | |
39 return rv; | |
40 }; | |
41 | |
42 /** | |
43 * Reads 3bytes integer. | |
44 * | |
45 * @param {ByteReader} reader - reader to use. | |
46 * @return {number} // TODO(JSDOC). | |
47 * @private | |
48 */ | |
49 Id3Parser.readUInt24_ = function(reader) { | |
50 return reader.readScalar(2, false) << 16 | reader.readScalar(1, false); | |
51 }; | |
52 | |
53 /** | |
54 * Reads string from reader with specified encoding | |
55 * | |
56 * @param {ByteReader} reader reader to use. | |
57 * @param {number} encoding string encoding. | |
58 * @param {number} size maximum string size. Actual result may be shorter. | |
59 * @return {string} // TODO(JSDOC). | |
60 * @private | |
61 */ | |
62 Id3Parser.prototype.readString_ = function(reader, encoding, size) { | |
63 switch (encoding) { | |
64 case Id3Parser.v2.ENCODING.ISO_8859_1: | |
65 return reader.readNullTerminatedString(size); | |
66 | |
67 case Id3Parser.v2.ENCODING.UTF_16: | |
68 return reader.readNullTerminatedStringUTF16(true, size); | |
69 | |
70 case Id3Parser.v2.ENCODING.UTF_16BE: | |
71 return reader.readNullTerminatedStringUTF16(false, size); | |
72 | |
73 case Id3Parser.v2.ENCODING.UTF_8: | |
74 // TODO: implement UTF_8. | |
75 this.log('UTF8 encoding not supported, used ISO_8859_1 instead'); | |
76 return reader.readNullTerminatedString(size); | |
77 | |
78 default: { | |
79 this.log('Unsupported encoding in ID3 tag: ' + encoding); | |
80 return ''; | |
81 } | |
82 } | |
83 }; | |
84 | |
85 /** | |
86 * Reads text frame from reader. | |
87 * | |
88 * @param {ByteReader} reader reader to use. | |
89 * @param {number} majorVersion major id3 version to use. | |
90 * @param {Object} frame frame so store data at. | |
91 * @param {number} end frame end position in reader. | |
92 * @private | |
93 */ | |
94 Id3Parser.prototype.readTextFrame_ = function(reader, | |
95 majorVersion, | |
96 frame, | |
97 end) { | |
98 frame.encoding = reader.readScalar(1, false, end); | |
99 frame.value = this.readString_(reader, frame.encoding, end - reader.tell()); | |
100 }; | |
101 | |
102 /** | |
103 * Reads user defined text frame from reader. | |
104 * | |
105 * @param {ByteReader} reader reader to use. | |
106 * @param {number} majorVersion major id3 version to use. | |
107 * @param {Object} frame frame so store data at. | |
108 * @param {number} end frame end position in reader. | |
109 * @private | |
110 */ | |
111 Id3Parser.prototype.readUserDefinedTextFrame_ = function(reader, | |
112 majorVersion, | |
113 frame, | |
114 end) { | |
115 frame.encoding = reader.readScalar(1, false, end); | |
116 | |
117 frame.description = this.readString_( | |
118 reader, | |
119 frame.encoding, | |
120 end - reader.tell()); | |
121 | |
122 frame.value = this.readString_( | |
123 reader, | |
124 frame.encoding, | |
125 end - reader.tell()); | |
126 }; | |
127 | |
128 /** | |
129 * @param {ByteReader} reader Reader to use. | |
130 * @param {number} majorVersion Major id3 version to use. | |
131 * @param {Object} frame Frame so store data at. | |
132 * @param {number} end Frame end position in reader. | |
133 * @private | |
134 */ | |
135 Id3Parser.prototype.readPIC_ = function(reader, majorVersion, frame, end) { | |
136 frame.encoding = reader.readScalar(1, false, end); | |
137 frame.format = reader.readNullTerminatedString(3, end - reader.tell()); | |
138 frame.pictureType = reader.readScalar(1, false, end); | |
139 frame.description = this.readString_(reader, | |
140 frame.encoding, | |
141 end - reader.tell()); | |
142 | |
143 | |
144 if (frame.format == '-->') { | |
145 frame.imageUrl = reader.readNullTerminatedString(end - reader.tell()); | |
146 } else { | |
147 frame.imageUrl = reader.readImage(end - reader.tell()); | |
148 } | |
149 }; | |
150 | |
151 /** | |
152 * @param {ByteReader} reader Reader to use. | |
153 * @param {number} majorVersion Major id3 version to use. | |
154 * @param {Object} frame Frame so store data at. | |
155 * @param {number} end Frame end position in reader. | |
156 * @private | |
157 */ | |
158 Id3Parser.prototype.readAPIC_ = function(reader, majorVersion, frame, end) { | |
159 this.vlog('Extracting picture'); | |
160 frame.encoding = reader.readScalar(1, false, end); | |
161 frame.mime = reader.readNullTerminatedString(end - reader.tell()); | |
162 frame.pictureType = reader.readScalar(1, false, end); | |
163 frame.description = this.readString_( | |
164 reader, | |
165 frame.encoding, | |
166 end - reader.tell()); | |
167 | |
168 if (frame.mime == '-->') { | |
169 frame.imageUrl = reader.readNullTerminatedString(end - reader.tell()); | |
170 } else { | |
171 frame.imageUrl = reader.readImage(end - reader.tell()); | |
172 } | |
173 }; | |
174 | |
175 /** | |
176 * Reads string from reader with specified encoding | |
177 * | |
178 * @param {ByteReader} reader reader to use. | |
179 * @param {number} majorVersion // TODO(JSDOC). | |
180 * @return {Object} frame read. | |
181 * @private | |
182 */ | |
183 Id3Parser.prototype.readFrame_ = function(reader, majorVersion) { | |
184 if (reader.eof()) | |
185 return null; | |
186 | |
187 var frame = {}; | |
188 | |
189 reader.pushSeek(reader.tell(), ByteReader.SEEK_BEG); | |
190 | |
191 var position = reader.tell(); | |
192 | |
193 frame.name = (majorVersion == 2) ? reader.readNullTerminatedString(3) : | |
194 reader.readNullTerminatedString(4); | |
195 | |
196 if (frame.name == '') | |
197 return null; | |
198 | |
199 this.vlog('Found frame ' + (frame.name) + ' at position ' + position); | |
200 | |
201 switch (majorVersion) { | |
202 case 2: | |
203 frame.size = Id3Parser.readUInt24_(reader); | |
204 frame.headerSize = 6; | |
205 break; | |
206 case 3: | |
207 frame.size = reader.readScalar(4, false); | |
208 frame.headerSize = 10; | |
209 frame.flags = reader.readScalar(2, false); | |
210 break; | |
211 case 4: | |
212 frame.size = Id3Parser.readSynchSafe_(reader, 4); | |
213 frame.headerSize = 10; | |
214 frame.flags = reader.readScalar(2, false); | |
215 break; | |
216 } | |
217 | |
218 this.vlog('Found frame [' + frame.name + '] with size [' + frame.size + ']'); | |
219 | |
220 if (Id3Parser.v2.HANDLERS[frame.name]) { | |
221 Id3Parser.v2.HANDLERS[frame.name].call( | |
222 this, | |
223 reader, | |
224 majorVersion, | |
225 frame, | |
226 reader.tell() + frame.size); | |
227 } else if (frame.name.charAt(0) == 'T' || frame.name.charAt(0) == 'W') { | |
228 this.readTextFrame_( | |
229 reader, | |
230 majorVersion, | |
231 frame, | |
232 reader.tell() + frame.size); | |
233 } | |
234 | |
235 reader.popSeek(); | |
236 | |
237 reader.seek(frame.size + frame.headerSize, ByteReader.SEEK_CUR); | |
238 | |
239 return frame; | |
240 }; | |
241 | |
242 /** | |
243 * @param {File} file // TODO(JSDOC). | |
244 * @param {Object} metadata // TODO(JSDOC). | |
245 * @param {function(Object)} callback // TODO(JSDOC). | |
246 * @param {function(etring)} onError // TODO(JSDOC). | |
247 */ | |
248 Id3Parser.prototype.parse = function(file, metadata, callback, onError) { | |
249 var self = this; | |
250 | |
251 this.log('Starting id3 parser for ' + file.name); | |
252 | |
253 var id3v1Parser = new FunctionSequence( | |
254 'id3v1parser', | |
255 [ | |
256 /** | |
257 * Reads last 128 bytes of file in bytebuffer, | |
258 * which passes further. | |
259 * In last 128 bytes should be placed ID3v1 tag if available. | |
260 * @param {File} file File which bytes to read. | |
261 */ | |
262 function readTail(file) { | |
263 util.readFileBytes(file, file.size - 128, file.size, | |
264 this.nextStep, this.onError, this); | |
265 }, | |
266 | |
267 /** | |
268 * Attempts to extract ID3v1 tag from 128 bytes long ByteBuffer | |
269 * @param {File} file File which tags are being extracted. Could be used | |
270 * for logging purposes. | |
271 * @param {ByteReader} reader ByteReader of 128 bytes. | |
272 */ | |
273 function extractId3v1(file, reader) { | |
274 if (reader.readString(3) == 'TAG') { | |
275 this.logger.vlog('id3v1 found'); | |
276 var id3v1 = metadata.id3v1 = {}; | |
277 | |
278 var title = reader.readNullTerminatedString(30).trim(); | |
279 | |
280 if (title.length > 0) { | |
281 metadata.title = title; | |
282 } | |
283 | |
284 reader.seek(3 + 30, ByteReader.SEEK_BEG); | |
285 | |
286 var artist = reader.readNullTerminatedString(30).trim(); | |
287 if (artist.length > 0) { | |
288 metadata.artist = artist; | |
289 } | |
290 | |
291 reader.seek(3 + 30 + 30, ByteReader.SEEK_BEG); | |
292 | |
293 var album = reader.readNullTerminatedString(30).trim(); | |
294 if (album.length > 0) { | |
295 metadata.album = album; | |
296 } | |
297 } | |
298 this.nextStep(); | |
299 } | |
300 ], | |
301 this | |
302 ); | |
303 | |
304 var id3v2Parser = new FunctionSequence( | |
305 'id3v2parser', | |
306 [ | |
307 function readHead(file) { | |
308 util.readFileBytes(file, 0, 10, this.nextStep, this.onError, | |
309 this); | |
310 }, | |
311 | |
312 /** | |
313 * Check if passed array of 10 bytes contains ID3 header. | |
314 * @param {File} file File to check and continue reading if ID3 | |
315 * metadata found. | |
316 * @param {ByteReader} reader Reader to fill with stream bytes. | |
317 */ | |
318 function checkId3v2(file, reader) { | |
319 if (reader.readString(3) == 'ID3') { | |
320 this.logger.vlog('id3v2 found'); | |
321 var id3v2 = metadata.id3v2 = {}; | |
322 id3v2.major = reader.readScalar(1, false); | |
323 id3v2.minor = reader.readScalar(1, false); | |
324 id3v2.flags = reader.readScalar(1, false); | |
325 id3v2.size = Id3Parser.readSynchSafe_(reader, 4); | |
326 | |
327 util.readFileBytes(file, 10, 10 + id3v2.size, this.nextStep, | |
328 this.onError, this); | |
329 } else { | |
330 this.finish(); | |
331 } | |
332 }, | |
333 | |
334 /** | |
335 * Extracts all ID3v2 frames from given bytebuffer. | |
336 * @param {File} file File being parsed. | |
337 * @param {ByteReader} reader Reader to use for metadata extraction. | |
338 */ | |
339 function extractFrames(file, reader) { | |
340 var id3v2 = metadata.id3v2; | |
341 | |
342 if ((id3v2.major > 2) && | |
343 (id3v2.flags & Id3Parser.v2.FLAG_EXTENDED_HEADER != 0)) { | |
344 // Skip extended header if found | |
345 if (id3v2.major == 3) { | |
346 reader.seek(reader.readScalar(4, false) - 4); | |
347 } else if (id3v2.major == 4) { | |
348 reader.seek(Id3Parser.readSynchSafe_(reader, 4) - 4); | |
349 } | |
350 } | |
351 | |
352 var frame; | |
353 | |
354 while (frame = self.readFrame_(reader, id3v2.major)) { | |
355 metadata.id3v2[frame.name] = frame; | |
356 } | |
357 | |
358 this.nextStep(); | |
359 }, | |
360 | |
361 /** | |
362 * Adds 'description' object to metadata. | |
363 * 'description' used to unify different parsers and make | |
364 * metadata parser-aware. | |
365 * Description is array if value-type pairs. Type should be used | |
366 * to properly format value before displaying to user. | |
367 */ | |
368 function prepareDescription() { | |
369 var id3v2 = metadata.id3v2; | |
370 | |
371 if (id3v2['APIC']) | |
372 metadata.thumbnailURL = id3v2['APIC'].imageUrl; | |
373 else if (id3v2['PIC']) | |
374 metadata.thumbnailURL = id3v2['PIC'].imageUrl; | |
375 | |
376 metadata.description = []; | |
377 | |
378 for (var key in id3v2) { | |
379 if (typeof(Id3Parser.v2.MAPPERS[key]) != 'undefined' && | |
380 id3v2[key].value.trim().length > 0) { | |
381 metadata.description.push({ | |
382 key: Id3Parser.v2.MAPPERS[key], | |
383 value: id3v2[key].value.trim() | |
384 }); | |
385 } | |
386 } | |
387 | |
388 function extract(propName, tags) { | |
389 for (var i = 1; i != arguments.length; i++) { | |
390 var tag = id3v2[arguments[i]]; | |
391 if (tag && tag.value) { | |
392 metadata[propName] = tag.value; | |
393 break; | |
394 } | |
395 } | |
396 } | |
397 | |
398 extract('album', 'TALB', 'TAL'); | |
399 extract('title', 'TIT2', 'TT2'); | |
400 extract('artist', 'TPE1', 'TP1'); | |
401 | |
402 metadata.description.sort(function(a, b) { | |
403 return Id3Parser.METADATA_ORDER.indexOf(a.key) - | |
404 Id3Parser.METADATA_ORDER.indexOf(b.key); | |
405 }); | |
406 this.nextStep(); | |
407 } | |
408 ], | |
409 this | |
410 ); | |
411 | |
412 var metadataParser = new FunctionParallel( | |
413 'mp3metadataParser', | |
414 [id3v1Parser, id3v2Parser], | |
415 this, | |
416 function() { | |
417 callback.call(null, metadata); | |
418 }, | |
419 onError | |
420 ); | |
421 | |
422 id3v1Parser.setCallback(metadataParser.nextStep); | |
423 id3v2Parser.setCallback(metadataParser.nextStep); | |
424 | |
425 id3v1Parser.setFailureCallback(metadataParser.onError); | |
426 id3v2Parser.setFailureCallback(metadataParser.onError); | |
427 | |
428 this.vlog('Passed argument : ' + file); | |
429 | |
430 metadataParser.start(file); | |
431 }; | |
432 | |
433 | |
434 /** | |
435 * Metadata order to use for metadata generation | |
436 */ | |
437 Id3Parser.METADATA_ORDER = [ | |
438 'ID3_TITLE', | |
439 'ID3_LEAD_PERFORMER', | |
440 'ID3_YEAR', | |
441 'ID3_ALBUM', | |
442 'ID3_TRACK_NUMBER', | |
443 'ID3_BPM', | |
444 'ID3_COMPOSER', | |
445 'ID3_DATE', | |
446 'ID3_PLAYLIST_DELAY', | |
447 'ID3_LYRICIST', | |
448 'ID3_FILE_TYPE', | |
449 'ID3_TIME', | |
450 'ID3_LENGTH', | |
451 'ID3_FILE_OWNER', | |
452 'ID3_BAND', | |
453 'ID3_COPYRIGHT', | |
454 'ID3_OFFICIAL_AUDIO_FILE_WEBPAGE', | |
455 'ID3_OFFICIAL_ARTIST', | |
456 'ID3_OFFICIAL_AUDIO_SOURCE_WEBPAGE', | |
457 'ID3_PUBLISHERS_OFFICIAL_WEBPAGE' | |
458 ]; | |
459 | |
460 | |
461 /** | |
462 * id3v1 constants | |
463 */ | |
464 Id3Parser.v1 = { | |
465 /** | |
466 * Genres list as described in id3 documentation. We aren't going to | |
467 * localize this list, because at least in Russian (and I think most | |
468 * other languages), translation exists at least for 10% and most time | |
469 * translation would degrade to transliteration. | |
470 */ | |
471 GENRES: [ | |
472 'Blues', | |
473 'Classic Rock', | |
474 'Country', | |
475 'Dance', | |
476 'Disco', | |
477 'Funk', | |
478 'Grunge', | |
479 'Hip-Hop', | |
480 'Jazz', | |
481 'Metal', | |
482 'New Age', | |
483 'Oldies', | |
484 'Other', | |
485 'Pop', | |
486 'R&B', | |
487 'Rap', | |
488 'Reggae', | |
489 'Rock', | |
490 'Techno', | |
491 'Industrial', | |
492 'Alternative', | |
493 'Ska', | |
494 'Death Metal', | |
495 'Pranks', | |
496 'Soundtrack', | |
497 'Euro-Techno', | |
498 'Ambient', | |
499 'Trip-Hop', | |
500 'Vocal', | |
501 'Jazz+Funk', | |
502 'Fusion', | |
503 'Trance', | |
504 'Classical', | |
505 'Instrumental', | |
506 'Acid', | |
507 'House', | |
508 'Game', | |
509 'Sound Clip', | |
510 'Gospel', | |
511 'Noise', | |
512 'AlternRock', | |
513 'Bass', | |
514 'Soul', | |
515 'Punk', | |
516 'Space', | |
517 'Meditative', | |
518 'Instrumental Pop', | |
519 'Instrumental Rock', | |
520 'Ethnic', | |
521 'Gothic', | |
522 'Darkwave', | |
523 'Techno-Industrial', | |
524 'Electronic', | |
525 'Pop-Folk', | |
526 'Eurodance', | |
527 'Dream', | |
528 'Southern Rock', | |
529 'Comedy', | |
530 'Cult', | |
531 'Gangsta', | |
532 'Top 40', | |
533 'Christian Rap', | |
534 'Pop/Funk', | |
535 'Jungle', | |
536 'Native American', | |
537 'Cabaret', | |
538 'New Wave', | |
539 'Psychadelic', | |
540 'Rave', | |
541 'Showtunes', | |
542 'Trailer', | |
543 'Lo-Fi', | |
544 'Tribal', | |
545 'Acid Punk', | |
546 'Acid Jazz', | |
547 'Polka', | |
548 'Retro', | |
549 'Musical', | |
550 'Rock & Roll', | |
551 'Hard Rock', | |
552 'Folk', | |
553 'Folk-Rock', | |
554 'National Folk', | |
555 'Swing', | |
556 'Fast Fusion', | |
557 'Bebob', | |
558 'Latin', | |
559 'Revival', | |
560 'Celtic', | |
561 'Bluegrass', | |
562 'Avantgarde', | |
563 'Gothic Rock', | |
564 'Progressive Rock', | |
565 'Psychedelic Rock', | |
566 'Symphonic Rock', | |
567 'Slow Rock', | |
568 'Big Band', | |
569 'Chorus', | |
570 'Easy Listening', | |
571 'Acoustic', | |
572 'Humour', | |
573 'Speech', | |
574 'Chanson', | |
575 'Opera', | |
576 'Chamber Music', | |
577 'Sonata', | |
578 'Symphony', | |
579 'Booty Bass', | |
580 'Primus', | |
581 'Porn Groove', | |
582 'Satire', | |
583 'Slow Jam', | |
584 'Club', | |
585 'Tango', | |
586 'Samba', | |
587 'Folklore', | |
588 'Ballad', | |
589 'Power Ballad', | |
590 'Rhythmic Soul', | |
591 'Freestyle', | |
592 'Duet', | |
593 'Punk Rock', | |
594 'Drum Solo', | |
595 'A capella', | |
596 'Euro-House', | |
597 'Dance Hall', | |
598 'Goa', | |
599 'Drum & Bass', | |
600 'Club-House', | |
601 'Hardcore', | |
602 'Terror', | |
603 'Indie', | |
604 'BritPop', | |
605 'Negerpunk', | |
606 'Polsk Punk', | |
607 'Beat', | |
608 'Christian Gangsta Rap', | |
609 'Heavy Metal', | |
610 'Black Metal', | |
611 'Crossover', | |
612 'Contemporary Christian', | |
613 'Christian Rock', | |
614 'Merengue', | |
615 'Salsa', | |
616 'Thrash Metal', | |
617 'Anime', | |
618 'Jpop', | |
619 'Synthpop' | |
620 ] | |
621 }; | |
622 | |
623 /** | |
624 * id3v2 constants | |
625 */ | |
626 Id3Parser.v2 = { | |
627 FLAG_EXTENDED_HEADER: 1 << 5, | |
628 | |
629 ENCODING: { | |
630 /** | |
631 * ISO-8859-1 [ISO-8859-1]. Terminated with $00. | |
632 * | |
633 * @const | |
634 * @type {number} | |
635 */ | |
636 ISO_8859_1: 0, | |
637 | |
638 | |
639 /** | |
640 * [UTF-16] encoded Unicode [UNICODE] with BOM. All | |
641 * strings in the same frame SHALL have the same byteorder. | |
642 * Terminated with $00 00. | |
643 * | |
644 * @const | |
645 * @type {number} | |
646 */ | |
647 UTF_16: 1, | |
648 | |
649 /** | |
650 * UTF-16BE [UTF-16] encoded Unicode [UNICODE] without BOM. | |
651 * Terminated with $00 00. | |
652 * | |
653 * @const | |
654 * @type {number} | |
655 */ | |
656 UTF_16BE: 2, | |
657 | |
658 /** | |
659 * UTF-8 [UTF-8] encoded Unicode [UNICODE]. Terminated with $00. | |
660 * | |
661 * @const | |
662 * @type {number} | |
663 */ | |
664 UTF_8: 3 | |
665 }, | |
666 HANDLERS: { | |
667 //User defined text information frame | |
668 TXX: Id3Parser.prototype.readUserDefinedTextFrame_, | |
669 //User defined URL link frame | |
670 WXX: Id3Parser.prototype.readUserDefinedTextFrame_, | |
671 | |
672 //User defined text information frame | |
673 TXXX: Id3Parser.prototype.readUserDefinedTextFrame_, | |
674 | |
675 //User defined URL link frame | |
676 WXXX: Id3Parser.prototype.readUserDefinedTextFrame_, | |
677 | |
678 //User attached image | |
679 PIC: Id3Parser.prototype.readPIC_, | |
680 | |
681 //User attached image | |
682 APIC: Id3Parser.prototype.readAPIC_ | |
683 }, | |
684 MAPPERS: { | |
685 TALB: 'ID3_ALBUM', | |
686 TBPM: 'ID3_BPM', | |
687 TCOM: 'ID3_COMPOSER', | |
688 TDAT: 'ID3_DATE', | |
689 TDLY: 'ID3_PLAYLIST_DELAY', | |
690 TEXT: 'ID3_LYRICIST', | |
691 TFLT: 'ID3_FILE_TYPE', | |
692 TIME: 'ID3_TIME', | |
693 TIT2: 'ID3_TITLE', | |
694 TLEN: 'ID3_LENGTH', | |
695 TOWN: 'ID3_FILE_OWNER', | |
696 TPE1: 'ID3_LEAD_PERFORMER', | |
697 TPE2: 'ID3_BAND', | |
698 TRCK: 'ID3_TRACK_NUMBER', | |
699 TYER: 'ID3_YEAR', | |
700 WCOP: 'ID3_COPYRIGHT', | |
701 WOAF: 'ID3_OFFICIAL_AUDIO_FILE_WEBPAGE', | |
702 WOAR: 'ID3_OFFICIAL_ARTIST', | |
703 WOAS: 'ID3_OFFICIAL_AUDIO_SOURCE_WEBPAGE', | |
704 WPUB: 'ID3_PUBLISHERS_OFFICIAL_WEBPAGE' | |
705 } | |
706 }; | |
707 | |
708 MetadataDispatcher.registerParserClass(Id3Parser); | |
OLD | NEW |