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

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

Issue 39123003: [Files.app] Split the JavaScript files into subdirectories: common, background, and foreground (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fixed test failure. Created 7 years, 1 month 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 '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);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698