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.
8 * Protocol + host parts of extension URL.
12 var FILE_MANAGER_HOST = 'chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj';
15 FILE_MANAGER_HOST + '/foreground/js/metadata/function_sequence.js');
17 FILE_MANAGER_HOST + '/foreground/js/metadata/function_parallel.js');
19 function Id3Parser(parent) {
20 MetadataParser.call(this, parent, 'id3', /\.(mp3)$/i);
23 Id3Parser.prototype = {__proto__: MetadataParser.prototype};
26 * Reads synchsafe integer.
27 * 'SynchSafe' term is taken from id3 documentation.
29 * @param {ByteReader} reader Reader to use.
30 * @param {number} length Rytes to read.
31 * @return {number} Synchsafe value.
34 Id3Parser.readSynchSafe_ = function(reader, length) {
39 rv = reader.readScalar(1, false) << 21;
41 rv |= reader.readScalar(1, false) << 14;
43 rv |= reader.readScalar(1, false) << 7;
45 rv |= reader.readScalar(1, false);
52 * Reads 3bytes integer.
54 * @param {ByteReader} reader Reader to use.
55 * @return {number} Uint24 value.
58 Id3Parser.readUInt24_ = function(reader) {
59 return reader.readScalar(2, false) << 16 | reader.readScalar(1, false);
63 * Reads string from reader with specified encoding
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.
71 Id3Parser.prototype.readString_ = function(reader, encoding, size) {
73 case Id3Parser.v2.ENCODING.ISO_8859_1:
74 return reader.readNullTerminatedString(size);
76 case Id3Parser.v2.ENCODING.UTF_16:
77 return reader.readNullTerminatedStringUTF16(true, size);
79 case Id3Parser.v2.ENCODING.UTF_16BE:
80 return reader.readNullTerminatedStringUTF16(false, size);
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);
88 this.log('Unsupported encoding in ID3 tag: ' + encoding);
95 * Reads text frame from reader.
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.
103 Id3Parser.prototype.readTextFrame_ = function(reader,
107 frame.encoding = reader.readScalar(1, false, end);
108 frame.value = this.readString_(reader, frame.encoding, end - reader.tell());
112 * Reads user defined text frame from reader.
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.
120 Id3Parser.prototype.readUserDefinedTextFrame_ = function(reader,
124 frame.encoding = reader.readScalar(1, false, end);
126 frame.description = this.readString_(
129 end - reader.tell());
131 frame.value = this.readString_(
134 end - reader.tell());
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.
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,
150 end - reader.tell());
153 if (frame.format == '-->') {
154 frame.imageUrl = reader.readNullTerminatedString(end - reader.tell());
156 frame.imageUrl = reader.readImage(end - reader.tell());
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.
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_(
175 end - reader.tell());
177 if (frame.mime == '-->') {
178 frame.imageUrl = reader.readNullTerminatedString(end - reader.tell());
180 frame.imageUrl = reader.readImage(end - reader.tell());
185 * Reads string from reader with specified encoding
187 * @param {ByteReader} reader Reader to use.
188 * @param {number} majorVersion Major id3 version to use.
189 * @return {Object} Frame read.
192 Id3Parser.prototype.readFrame_ = function(reader, majorVersion) {
198 reader.pushSeek(reader.tell(), ByteReader.SEEK_BEG);
200 var position = reader.tell();
202 frame.name = (majorVersion == 2) ? reader.readNullTerminatedString(3) :
203 reader.readNullTerminatedString(4);
205 if (frame.name == '')
208 this.vlog('Found frame ' + (frame.name) + ' at position ' + position);
210 switch (majorVersion) {
212 frame.size = Id3Parser.readUInt24_(reader);
213 frame.headerSize = 6;
216 frame.size = reader.readScalar(4, false);
217 frame.headerSize = 10;
218 frame.flags = reader.readScalar(2, false);
221 frame.size = Id3Parser.readSynchSafe_(reader, 4);
222 frame.headerSize = 10;
223 frame.flags = reader.readScalar(2, false);
227 this.vlog('Found frame [' + frame.name + '] with size [' + frame.size + ']');
229 if (Id3Parser.v2.HANDLERS[frame.name]) {
230 Id3Parser.v2.HANDLERS[frame.name].call(
235 reader.tell() + frame.size);
236 } else if (frame.name.charAt(0) == 'T' || frame.name.charAt(0) == 'W') {
241 reader.tell() + frame.size);
246 reader.seek(frame.size + frame.headerSize, ByteReader.SEEK_CUR);
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.
257 Id3Parser.prototype.parse = function(file, metadata, callback, onError) {
260 this.log('Starting id3 parser for ' + file.name);
262 var id3v1Parser = new FunctionSequence(
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.
271 function readTail(file) {
272 util.readFileBytes(file, file.size - 128, file.size,
273 this.nextStep, this.onError, this);
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.
282 function extractId3v1(file, reader) {
283 if (reader.readString(3) == 'TAG') {
284 this.logger.vlog('id3v1 found');
285 var id3v1 = metadata.id3v1 = {};
287 var title = reader.readNullTerminatedString(30).trim();
289 if (title.length > 0) {
290 metadata.title = title;
293 reader.seek(3 + 30, ByteReader.SEEK_BEG);
295 var artist = reader.readNullTerminatedString(30).trim();
296 if (artist.length > 0) {
297 metadata.artist = artist;
300 reader.seek(3 + 30 + 30, ByteReader.SEEK_BEG);
302 var album = reader.readNullTerminatedString(30).trim();
303 if (album.length > 0) {
304 metadata.album = album;
312 var id3v2Parser = new FunctionSequence(
315 function readHead(file) {
316 util.readFileBytes(file, 0, 10, this.nextStep, this.onError,
321 * Check if passed array of 10 bytes contains ID3 header.
322 * @param {File} file File to check and continue reading if ID3
324 * @param {ByteReader} reader Reader to fill with stream bytes.
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);
335 util.readFileBytes(file, 10, 10 + id3v2.size, this.nextStep,
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.
347 function extractFrames(file, reader) {
348 var id3v2 = metadata.id3v2;
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);
362 while (frame = self.readFrame_(reader, id3v2.major)) {
363 metadata.id3v2[frame.name] = frame;
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.
376 function prepareDescription() {
377 var id3v2 = metadata.id3v2;
380 metadata.thumbnailURL = id3v2['APIC'].imageUrl;
381 else if (id3v2['PIC'])
382 metadata.thumbnailURL = id3v2['PIC'].imageUrl;
384 metadata.description = [];
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()
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;
406 extract('album', 'TALB', 'TAL');
407 extract('title', 'TIT2', 'TT2');
408 extract('artist', 'TPE1', 'TP1');
410 metadata.description.sort(function(a, b) {
411 return Id3Parser.METADATA_ORDER.indexOf(a.key) -
412 Id3Parser.METADATA_ORDER.indexOf(b.key);
419 var metadataParser = new FunctionParallel(
421 [id3v1Parser, id3v2Parser],
424 callback.call(null, metadata);
428 id3v1Parser.setCallback(metadataParser.nextStep);
429 id3v2Parser.setCallback(metadataParser.nextStep);
431 id3v1Parser.setFailureCallback(metadataParser.onError);
432 id3v2Parser.setFailureCallback(metadataParser.onError);
434 this.vlog('Passed argument : ' + file);
436 metadataParser.start(file);
441 * Metadata order to use for metadata generation
442 * @type {Array.<string>}
445 Id3Parser.METADATA_ORDER = [
447 'ID3_LEAD_PERFORMER',
454 'ID3_PLAYLIST_DELAY',
462 'ID3_OFFICIAL_AUDIO_FILE_WEBPAGE',
463 'ID3_OFFICIAL_ARTIST',
464 'ID3_OFFICIAL_AUDIO_SOURCE_WEBPAGE',
465 'ID3_PUBLISHERS_OFFICIAL_WEBPAGE'
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.
617 'Christian Gangsta Rap',
621 'Contemporary Christian',
637 FLAG_EXTENDED_HEADER: 1 << 5,
641 * ISO-8859-1 [ISO-8859-1]. Terminated with $00.
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.
660 * UTF-16BE [UTF-16] encoded Unicode [UNICODE] without BOM.
661 * Terminated with $00 00.
669 * UTF-8 [UTF-8] encoded Unicode [UNICODE]. Terminated with $00.
677 //User defined text information frame
678 TXX: Id3Parser.prototype.readUserDefinedTextFrame_,
679 //User defined URL link frame
680 WXX: Id3Parser.prototype.readUserDefinedTextFrame_,
682 //User defined text information frame
683 TXXX: Id3Parser.prototype.readUserDefinedTextFrame_,
685 //User defined URL link frame
686 WXXX: Id3Parser.prototype.readUserDefinedTextFrame_,
688 //User attached image
689 PIC: Id3Parser.prototype.readPIC_,
691 //User attached image
692 APIC: Id3Parser.prototype.readAPIC_
697 TCOM: 'ID3_COMPOSER',
699 TDLY: 'ID3_PLAYLIST_DELAY',
700 TEXT: 'ID3_LYRICIST',
701 TFLT: 'ID3_FILE_TYPE',
705 TOWN: 'ID3_FILE_OWNER',
706 TPE1: 'ID3_LEAD_PERFORMER',
708 TRCK: 'ID3_TRACK_NUMBER',
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'
718 MetadataDispatcher.registerParserClass(Id3Parser);