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.
6 * Protocol + host parts of extension URL.
10 var FILE_MANAGER_HOST = 'chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj';
13 FILE_MANAGER_HOST + '/foreground/js/metadata/function_sequence.js');
15 FILE_MANAGER_HOST + '/foreground/js/metadata/function_parallel.js');
17 function Id3Parser(parent) {
18 MetadataParser.call(this, parent, 'id3', /\.(mp3)$/i);
21 Id3Parser.prototype = {__proto__: MetadataParser.prototype};
24 * Reads synchsafe integer.
25 * 'SynchSafe' term is taken from id3 documentation.
27 * @param {ByteReader} reader Reader to use.
28 * @param {number} length Rytes to read.
29 * @return {number} Synchsafe value.
32 Id3Parser.readSynchSafe_ = function(reader, length) {
37 rv = reader.readScalar(1, false) << 21;
39 rv |= reader.readScalar(1, false) << 14;
41 rv |= reader.readScalar(1, false) << 7;
43 rv |= reader.readScalar(1, false);
50 * Reads 3bytes integer.
52 * @param {ByteReader} reader Reader to use.
53 * @return {number} Uint24 value.
56 Id3Parser.readUInt24_ = function(reader) {
57 return reader.readScalar(2, false) << 16 | reader.readScalar(1, false);
61 * Reads string from reader with specified encoding
63 * @param {ByteReader} reader Reader to use.
64 * @param {number} encoding String encoding.
65 * @param {number} size Maximum string size. Actual result may be shorter.
66 * @return {string} String value.
69 Id3Parser.prototype.readString_ = function(reader, encoding, size) {
71 case Id3Parser.v2.ENCODING.ISO_8859_1:
72 return reader.readNullTerminatedString(size);
74 case Id3Parser.v2.ENCODING.UTF_16:
75 return reader.readNullTerminatedStringUTF16(true, size);
77 case Id3Parser.v2.ENCODING.UTF_16BE:
78 return reader.readNullTerminatedStringUTF16(false, size);
80 case Id3Parser.v2.ENCODING.UTF_8:
81 // TODO: implement UTF_8.
82 this.log('UTF8 encoding not supported, used ISO_8859_1 instead');
83 return reader.readNullTerminatedString(size);
86 this.log('Unsupported encoding in ID3 tag: ' + encoding);
93 * Reads text frame from reader.
95 * @param {ByteReader} reader Reader to use.
96 * @param {number} majorVersion Major id3 version to use.
97 * @param {Object} frame Frame so store data at.
98 * @param {number} end Frame end position in reader.
101 Id3Parser.prototype.readTextFrame_ = function(reader,
105 frame.encoding = reader.readScalar(1, false, end);
106 frame.value = this.readString_(reader, frame.encoding, end - reader.tell());
110 * Reads user defined text frame from reader.
112 * @param {ByteReader} reader Reader to use.
113 * @param {number} majorVersion Major id3 version to use.
114 * @param {Object} frame Frame so store data at.
115 * @param {number} end Frame end position in reader.
118 Id3Parser.prototype.readUserDefinedTextFrame_ = function(reader,
122 frame.encoding = reader.readScalar(1, false, end);
124 frame.description = this.readString_(
127 end - reader.tell());
129 frame.value = this.readString_(
132 end - reader.tell());
136 * @param {ByteReader} reader Reader to use.
137 * @param {number} majorVersion Major id3 version to use.
138 * @param {Object} frame Frame so store data at.
139 * @param {number} end Frame end position in reader.
142 Id3Parser.prototype.readPIC_ = function(reader, majorVersion, frame, end) {
143 frame.encoding = reader.readScalar(1, false, end);
144 frame.format = reader.readNullTerminatedString(3, end - reader.tell());
145 frame.pictureType = reader.readScalar(1, false, end);
146 frame.description = this.readString_(reader,
148 end - reader.tell());
151 if (frame.format == '-->') {
152 frame.imageUrl = reader.readNullTerminatedString(end - reader.tell());
154 frame.imageUrl = reader.readImage(end - reader.tell());
159 * @param {ByteReader} reader Reader to use.
160 * @param {number} majorVersion Major id3 version to use.
161 * @param {Object} frame Frame so store data at.
162 * @param {number} end Frame end position in reader.
165 Id3Parser.prototype.readAPIC_ = function(reader, majorVersion, frame, end) {
166 this.vlog('Extracting picture');
167 frame.encoding = reader.readScalar(1, false, end);
168 frame.mime = reader.readNullTerminatedString(end - reader.tell());
169 frame.pictureType = reader.readScalar(1, false, end);
170 frame.description = this.readString_(
173 end - reader.tell());
175 if (frame.mime == '-->') {
176 frame.imageUrl = reader.readNullTerminatedString(end - reader.tell());
178 frame.imageUrl = reader.readImage(end - reader.tell());
183 * Reads string from reader with specified encoding
185 * @param {ByteReader} reader Reader to use.
186 * @param {number} majorVersion Major id3 version to use.
187 * @return {Object} Frame read.
190 Id3Parser.prototype.readFrame_ = function(reader, majorVersion) {
196 reader.pushSeek(reader.tell(), ByteReader.SEEK_BEG);
198 var position = reader.tell();
200 frame.name = (majorVersion == 2) ? reader.readNullTerminatedString(3) :
201 reader.readNullTerminatedString(4);
203 if (frame.name == '')
206 this.vlog('Found frame ' + (frame.name) + ' at position ' + position);
208 switch (majorVersion) {
210 frame.size = Id3Parser.readUInt24_(reader);
211 frame.headerSize = 6;
214 frame.size = reader.readScalar(4, false);
215 frame.headerSize = 10;
216 frame.flags = reader.readScalar(2, false);
219 frame.size = Id3Parser.readSynchSafe_(reader, 4);
220 frame.headerSize = 10;
221 frame.flags = reader.readScalar(2, false);
225 this.vlog('Found frame [' + frame.name + '] with size [' + frame.size + ']');
227 if (Id3Parser.v2.HANDLERS[frame.name]) {
228 Id3Parser.v2.HANDLERS[frame.name].call(
233 reader.tell() + frame.size);
234 } else if (frame.name.charAt(0) == 'T' || frame.name.charAt(0) == 'W') {
239 reader.tell() + frame.size);
244 reader.seek(frame.size + frame.headerSize, ByteReader.SEEK_CUR);
250 * @param {File} file File object to parse.
251 * @param {Object} metadata Metadata object of the file.
252 * @param {function(Object)} callback Success callback.
253 * @param {function(string)} onError Error callback.
255 Id3Parser.prototype.parse = function(file, metadata, callback, onError) {
258 this.log('Starting id3 parser for ' + file.name);
260 var id3v1Parser = new FunctionSequence(
264 * Reads last 128 bytes of file in bytebuffer,
265 * which passes further.
266 * In last 128 bytes should be placed ID3v1 tag if available.
267 * @param {File} file File which bytes to read.
269 function readTail(file) {
270 util.readFileBytes(file, file.size - 128, file.size,
271 this.nextStep, this.onError);
275 * Attempts to extract ID3v1 tag from 128 bytes long ByteBuffer
276 * @param {File} file File which tags are being extracted. Could be used
277 * for logging purposes.
278 * @param {ByteReader} reader ByteReader of 128 bytes.
280 function extractId3v1(file, reader) {
281 if (reader.readString(3) == 'TAG') {
282 this.logger.vlog('id3v1 found');
283 var id3v1 = metadata.id3v1 = {};
285 var title = reader.readNullTerminatedString(30).trim();
287 if (title.length > 0) {
288 metadata.title = title;
291 reader.seek(3 + 30, ByteReader.SEEK_BEG);
293 var artist = reader.readNullTerminatedString(30).trim();
294 if (artist.length > 0) {
295 metadata.artist = artist;
298 reader.seek(3 + 30 + 30, ByteReader.SEEK_BEG);
300 var album = reader.readNullTerminatedString(30).trim();
301 if (album.length > 0) {
302 metadata.album = album;
310 var id3v2Parser = new FunctionSequence(
313 function readHead(file) {
314 util.readFileBytes(file, 0, 10, this.nextStep, this.onError);
318 * Check if passed array of 10 bytes contains ID3 header.
319 * @param {File} file File to check and continue reading if ID3
321 * @param {ByteReader} reader Reader to fill with stream bytes.
323 function checkId3v2(file, reader) {
324 if (reader.readString(3) == 'ID3') {
325 this.logger.vlog('id3v2 found');
326 var id3v2 = metadata.id3v2 = {};
327 id3v2.major = reader.readScalar(1, false);
328 id3v2.minor = reader.readScalar(1, false);
329 id3v2.flags = reader.readScalar(1, false);
330 id3v2.size = Id3Parser.readSynchSafe_(reader, 4);
332 util.readFileBytes(file, 10, 10 + id3v2.size, this.nextStep,
340 * Extracts all ID3v2 frames from given bytebuffer.
341 * @param {File} file File being parsed.
342 * @param {ByteReader} reader Reader to use for metadata extraction.
344 function extractFrames(file, reader) {
345 var id3v2 = metadata.id3v2;
347 if ((id3v2.major > 2) &&
348 (id3v2.flags & Id3Parser.v2.FLAG_EXTENDED_HEADER != 0)) {
349 // Skip extended header if found
350 if (id3v2.major == 3) {
351 reader.seek(reader.readScalar(4, false) - 4);
352 } else if (id3v2.major == 4) {
353 reader.seek(Id3Parser.readSynchSafe_(reader, 4) - 4);
359 while (frame = self.readFrame_(reader, id3v2.major)) {
360 metadata.id3v2[frame.name] = frame;
367 * Adds 'description' object to metadata.
368 * 'description' used to unify different parsers and make
369 * metadata parser-aware.
370 * Description is array if value-type pairs. Type should be used
371 * to properly format value before displaying to user.
373 function prepareDescription() {
374 var id3v2 = metadata.id3v2;
377 metadata.thumbnailURL = id3v2['APIC'].imageUrl;
378 else if (id3v2['PIC'])
379 metadata.thumbnailURL = id3v2['PIC'].imageUrl;
381 metadata.description = [];
383 for (var key in id3v2) {
384 if (typeof(Id3Parser.v2.MAPPERS[key]) != 'undefined' &&
385 id3v2[key].value.trim().length > 0) {
386 metadata.description.push({
387 key: Id3Parser.v2.MAPPERS[key],
388 value: id3v2[key].value.trim()
393 function extract(propName, tags) {
394 for (var i = 1; i != arguments.length; i++) {
395 var tag = id3v2[arguments[i]];
396 if (tag && tag.value) {
397 metadata[propName] = tag.value;
403 extract('album', 'TALB', 'TAL');
404 extract('title', 'TIT2', 'TT2');
405 extract('artist', 'TPE1', 'TP1');
407 metadata.description.sort(function(a, b) {
408 return Id3Parser.METADATA_ORDER.indexOf(a.key) -
409 Id3Parser.METADATA_ORDER.indexOf(b.key);
416 var metadataParser = new FunctionParallel(
418 [id3v1Parser, id3v2Parser],
421 callback.call(null, metadata);
425 id3v1Parser.setCallback(metadataParser.nextStep);
426 id3v2Parser.setCallback(metadataParser.nextStep);
428 id3v1Parser.setFailureCallback(metadataParser.onError);
429 id3v2Parser.setFailureCallback(metadataParser.onError);
431 this.vlog('Passed argument : ' + file);
433 metadataParser.start(file);
438 * Metadata order to use for metadata generation
439 * @type {Array.<string>}
442 Id3Parser.METADATA_ORDER = [
444 'ID3_LEAD_PERFORMER',
451 'ID3_PLAYLIST_DELAY',
459 'ID3_OFFICIAL_AUDIO_FILE_WEBPAGE',
460 'ID3_OFFICIAL_ARTIST',
461 'ID3_OFFICIAL_AUDIO_SOURCE_WEBPAGE',
462 'ID3_PUBLISHERS_OFFICIAL_WEBPAGE'
472 * Genres list as described in id3 documentation. We aren't going to
473 * localize this list, because at least in Russian (and I think most
474 * other languages), translation exists at least for 10% and most time
475 * translation would degrade to transliteration.
614 'Christian Gangsta Rap',
618 'Contemporary Christian',
634 FLAG_EXTENDED_HEADER: 1 << 5,
638 * ISO-8859-1 [ISO-8859-1]. Terminated with $00.
647 * [UTF-16] encoded Unicode [UNICODE] with BOM. All
648 * strings in the same frame SHALL have the same byteorder.
649 * Terminated with $00 00.
657 * UTF-16BE [UTF-16] encoded Unicode [UNICODE] without BOM.
658 * Terminated with $00 00.
666 * UTF-8 [UTF-8] encoded Unicode [UNICODE]. Terminated with $00.
674 //User defined text information frame
675 TXX: Id3Parser.prototype.readUserDefinedTextFrame_,
676 //User defined URL link frame
677 WXX: Id3Parser.prototype.readUserDefinedTextFrame_,
679 //User defined text information frame
680 TXXX: Id3Parser.prototype.readUserDefinedTextFrame_,
682 //User defined URL link frame
683 WXXX: Id3Parser.prototype.readUserDefinedTextFrame_,
685 //User attached image
686 PIC: Id3Parser.prototype.readPIC_,
688 //User attached image
689 APIC: Id3Parser.prototype.readAPIC_
694 TCOM: 'ID3_COMPOSER',
696 TDLY: 'ID3_PLAYLIST_DELAY',
697 TEXT: 'ID3_LYRICIST',
698 TFLT: 'ID3_FILE_TYPE',
702 TOWN: 'ID3_FILE_OWNER',
703 TPE1: 'ID3_LEAD_PERFORMER',
705 TRCK: 'ID3_TRACK_NUMBER',
707 WCOP: 'ID3_COPYRIGHT',
708 WOAF: 'ID3_OFFICIAL_AUDIO_FILE_WEBPAGE',
709 WOAR: 'ID3_OFFICIAL_ARTIST',
710 WOAS: 'ID3_OFFICIAL_AUDIO_SOURCE_WEBPAGE',
711 WPUB: 'ID3_PUBLISHERS_OFFICIAL_WEBPAGE'
715 MetadataDispatcher.registerParserClass(Id3Parser);