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