Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / file_manager / foreground / js / metadata / id3_parser.js
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);