Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(65)

Side by Side Diff: chrome/browser/resources/file_manager/js/id3_parser.js

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

Powered by Google App Engine
This is Rietveld 408576698