summaryrefslogtreecommitdiffstats
path: root/chrome/browser/resources/file_manager/js/id3_parser.js
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/resources/file_manager/js/id3_parser.js')
-rw-r--r--chrome/browser/resources/file_manager/js/id3_parser.js583
1 files changed, 583 insertions, 0 deletions
diff --git a/chrome/browser/resources/file_manager/js/id3_parser.js b/chrome/browser/resources/file_manager/js/id3_parser.js
new file mode 100644
index 0000000..89243d0
--- /dev/null
+++ b/chrome/browser/resources/file_manager/js/id3_parser.js
@@ -0,0 +1,583 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+importScripts('function_sequence.js');
+importScripts('function_parallel.js');
+importScripts('util.js');
+
+function Id3Parser(parent) {
+ MetadataParser.apply(this, [parent]);
+ this.verbose = true;
+}
+
+Id3Parser.parserType = 'mpeg';
+
+Id3Parser.prototype = {__proto__: MetadataParser.prototype};
+
+Id3Parser.prototype.urlFilter = /\.(mp3)$/i;
+
+/**
+ * id3v1 constants
+ */
+
+Id3Parser.v1 = {
+ /**
+ * Genres list as described in id3 documentation. We aren't going to
+ * localize this list, because at least in Russian (and I think most
+ * other languages), translation exists at least fo 10% and most time
+ * translation would degrade to transliteration.
+ */
+ GENRES : [
+ 'Blues',
+ 'Classic Rock',
+ 'Country',
+ 'Dance',
+ 'Disco',
+ 'Funk',
+ 'Grunge',
+ 'Hip-Hop',
+ 'Jazz',
+ 'Metal',
+ 'New Age',
+ 'Oldies',
+ 'Other',
+ 'Pop',
+ 'R&B',
+ 'Rap',
+ 'Reggae',
+ 'Rock',
+ 'Techno',
+ 'Industrial',
+ 'Alternative',
+ 'Ska',
+ 'Death Metal',
+ 'Pranks',
+ 'Soundtrack',
+ 'Euro-Techno',
+ 'Ambient',
+ 'Trip-Hop',
+ 'Vocal',
+ 'Jazz+Funk',
+ 'Fusion',
+ 'Trance',
+ 'Classical',
+ 'Instrumental',
+ 'Acid',
+ 'House',
+ 'Game',
+ 'Sound Clip',
+ 'Gospel',
+ 'Noise',
+ 'AlternRock',
+ 'Bass',
+ 'Soul',
+ 'Punk',
+ 'Space',
+ 'Meditative',
+ 'Instrumental Pop',
+ 'Instrumental Rock',
+ 'Ethnic',
+ 'Gothic',
+ 'Darkwave',
+ 'Techno-Industrial',
+ 'Electronic',
+ 'Pop-Folk',
+ 'Eurodance',
+ 'Dream',
+ 'Southern Rock',
+ 'Comedy',
+ 'Cult',
+ 'Gangsta',
+ 'Top 40',
+ 'Christian Rap',
+ 'Pop/Funk',
+ 'Jungle',
+ 'Native American',
+ 'Cabaret',
+ 'New Wave',
+ 'Psychadelic',
+ 'Rave',
+ 'Showtunes',
+ 'Trailer',
+ 'Lo-Fi',
+ 'Tribal',
+ 'Acid Punk',
+ 'Acid Jazz',
+ 'Polka',
+ 'Retro',
+ 'Musical',
+ 'Rock & Roll',
+ 'Hard Rock',
+ 'Folk',
+ 'Folk-Rock',
+ 'National Folk',
+ 'Swing',
+ 'Fast Fusion',
+ 'Bebob',
+ 'Latin',
+ 'Revival',
+ 'Celtic',
+ 'Bluegrass',
+ 'Avantgarde',
+ 'Gothic Rock',
+ 'Progressive Rock',
+ 'Psychedelic Rock',
+ 'Symphonic Rock',
+ 'Slow Rock',
+ 'Big Band',
+ 'Chorus',
+ 'Easy Listening',
+ 'Acoustic',
+ 'Humour',
+ 'Speech',
+ 'Chanson',
+ 'Opera',
+ 'Chamber Music',
+ 'Sonata',
+ 'Symphony',
+ 'Booty Bass',
+ 'Primus',
+ 'Porn Groove',
+ 'Satire',
+ 'Slow Jam',
+ 'Club',
+ 'Tango',
+ 'Samba',
+ 'Folklore',
+ 'Ballad',
+ 'Power Ballad',
+ 'Rhythmic Soul',
+ 'Freestyle',
+ 'Duet',
+ 'Punk Rock',
+ 'Drum Solo',
+ 'A capella',
+ 'Euro-House',
+ 'Dance Hall',
+ 'Goa',
+ 'Drum & Bass',
+ 'Club-House',
+ 'Hardcore',
+ 'Terror',
+ 'Indie',
+ 'BritPop',
+ 'Negerpunk',
+ 'Polsk Punk',
+ 'Beat',
+ 'Christian Gangsta Rap',
+ 'Heavy Metal',
+ 'Black Metal',
+ 'Crossover',
+ 'Contemporary Christian',
+ 'Christian Rock',
+ 'Merengue',
+ 'Salsa',
+ 'Thrash Metal',
+ 'Anime',
+ 'Jpop',
+ 'Synthpop'
+ ]
+};
+
+/**
+ * Reads synchsafe integer.
+ * 'SynchSafe' term is taken from id3 documentation.
+ *
+ * @param {ByteReader} reader - reader to use
+ * @param {int} length - bytes to read
+ * @return {int}
+ */
+Id3Parser.readSynchSafe_ = function(reader, length) {
+ var rv = 0;
+
+ switch (length) {
+ case 4:
+ rv = reader.readScalar(1, false) << 21;
+ case 3:
+ rv |= reader.readScalar(1, false) << 14;
+ case 2:
+ rv |= reader.readScalar(1, false) << 7;
+ case 1:
+ rv |= reader.readScalar(1, false);
+ }
+
+ return rv;
+};
+
+/**
+ * Reads 3bytes integer.
+ *
+ * @param {ByteReader} reader - reader to use
+ * @return {int}
+ */
+Id3Parser.readUInt24_ = function(reader) {
+ return reader.readScalar(2, false) << 16 | reader.readScalar(1, false);
+};
+
+/**
+ * Reads string from reader with specified encoding
+ *
+ * @param {ByteReader} reader reader to use
+ * @param {int} encoding string encoding.
+ * @param {int} size maximum string size. Actual result may be shorter.
+ *
+ */
+Id3Parser.prototype.readString_ = function(reader, encoding, size) {
+ switch (encoding) {
+ case Id3Parser.v2.ENCODING.ISO_8859_1:
+ return reader.readNullTerminatedString(size);
+ case Id3Parser.v2.ENCODING.UTF_16BE:
+ case Id3Parser.v2.ENCODING.UTF_16:
+ case Id3Parser.v2.ENCODING.UTF_8:
+ default: {
+ // TODO: implement reading of unicode strings
+ this.error('Reading of unicode strings in ID3 tags is unimplemented');
+ return '';
+ }
+ }
+};
+
+/**
+ * Reads text frame from reader.
+ *
+ * @param {ByteReader} reader reader to use
+ * @param {int} majorVersion major id3 version to use
+ * @param {Object} frame frame so store data at
+ * @param {int} end frame end position in reader
+ */
+Id3Parser.prototype.readTextFrame_ = function(reader,
+ majorVersion,
+ frame,
+ end) {
+ frame.encoding = reader.readScalar(1, false, end);
+ frame.value = this.readString_(reader, frame.encoding, end - reader.tell());
+};
+
+/**
+ * Reads user defined text frame from reader.
+ *
+ * @param {ByteReader} reader reader to use
+ * @param {int} majorVersion major id3 version to use
+ * @param {Object} frame frame so store data at
+ * @param {int} end frame end position in reader
+ */
+Id3Parser.prototype.readUserDefinedTextFrame_ = function(reader,
+ majorVersion,
+ frame,
+ end) {
+ frame.encoding = reader.readScalar(1, false, end);
+
+ frame.description = this.readString_(
+ reader,
+ frame.encoding,
+ end - reader.tell());
+
+ frame.value = this.readString_(
+ reader,
+ frame.encoding,
+ end - reader.tell());
+};
+
+Id3Parser.prototype.readPIC_ = function(reader, majorVersion, frame, end) {
+ frame.encoding = reader.readScalar(1, false, end);
+ frame.format = reader.readNullTerminatedString(3, end - reader.tell());
+ frame.pictureType = reader.readScalar(1, false, end);
+ frame.description = this.readString_(reader,
+ frame.encoding,
+ end - reader.tell());
+
+
+ if (frame.format == '-->') {
+ frame.imageUrl = reader.readNullTerminatedString(end - reader.tell());
+ } else {
+ frame.imageUrl = reader.readImage(end - reader.tell());
+ }
+};
+
+Id3Parser.prototype.readAPIC_ = function(reader, majorVersion, frame, end) {
+ this.vlog('Extracting picture');
+ frame.encoding = reader.readScalar(1, false, end);
+ frame.mime = reader.readNullTerminatedString(end - reader.tell());
+ frame.pictureType = reader.readScalar(1, false, end);
+ frame.description = this.readString_(
+ reader,
+ frame.encoding,
+ end - reader.tell());
+
+ if (frame.mime == '-->') {
+ frame.imageUrl = reader.readNullTerminatedString(end - reader.tell());
+ } else {
+ frame.imageUrl = reader.readImage(end - reader.tell());
+ }
+};
+
+/**
+ * Reads string from reader with specified encoding
+ *
+ * @param {ByteReader} reader reader to use
+ * @return {Object} frame read
+ */
+Id3Parser.prototype.readFrame_ = function(reader, majorVersion) {
+ var frame = {};
+
+ reader.pushSeek(reader.tell(), ByteReader.SEEK_BEG);
+
+ var position = reader.tell();
+
+ frame.name = (majorVersion == 2)
+ ? reader.readNullTerminatedString(3)
+ : reader.readNullTerminatedString(4);
+
+ if (frame.name == '')
+ return null;
+
+ this.vlog('Found frame ' + (frame.name) + ' at position ' + position );
+
+ var dataSize = 0;
+
+ switch (majorVersion) {
+ case 2:
+ frame.size = Id3Parser.readUInt24_(reader);
+ frame.headerSize = 6;
+ break;
+ case 3:
+ frame.size = reader.readScalar(4, false);
+ frame.headerSize = 10;
+ frame.flags = reader.readScalar(2, false);
+ break;
+ case 4:
+ frame.size = Id3Parser.readSynchSafe_(reader, 4);
+ frame.headerSize = 10;
+ frame.flags = reader.readScalar(2, false);
+ break;
+ }
+
+ this.vlog('Found frame [' + frame.name + '] with size ['+frame.size+']');
+
+ if (Id3Parser.v2.HANDLERS[frame.name]) {
+ Id3Parser.v2.HANDLERS[frame.name].call(
+ this,
+ reader,
+ majorVersion,
+ frame,
+ reader.tell() + frame.size);
+ } else if (frame.name.charAt(0) == 'T' || frame.name.charAt(0) == 'W') {
+ this.readTextFrame_(
+ reader,
+ majorVersion,
+ frame,
+ reader.tell() + frame.size);
+ }
+
+ reader.popSeek();
+
+ reader.seek(frame.size + frame.headerSize, ByteReader.SEEK_CUR);
+
+ return frame;
+};
+
+Id3Parser.prototype.parse = function (file, callback, onError) {
+ var metadata = {};
+
+ var self = this;
+
+ this.log('Starting id3 parser for ' + file.name);
+
+ var id3v1Parser = new FunctionSequence(
+ 'id3v1parser',
+ [
+ /**
+ * Reads last 128 bytes of file in bytebuffer,
+ * which passes further.
+ * In last 128 bytes should be placed ID3v1 tag if available.
+ * @param file - file which bytes to read.
+ */
+ function readTail(file) {
+ util.readFileBytes(file, file.size - 128, file.size,
+ this.nextStep, this.onError, this);
+ },
+
+ /**
+ * Attempts to extract ID3v1 tag from 128 bytes long ByteBuffer
+ * @param file file which tags are being extracted.
+ * Could be used for logging purposes.
+ * @param {ByteReader} reader ByteReader of 128 bytes.
+ */
+ function extractId3v1(file, reader) {
+ if ( reader.readString(3) == 'TAG') {
+ this.logger.vlog('id3v1 found');
+ var id3v1 = metadata.id3v1 = {};
+
+ var title = reader.readNullTerminatedString(30).trim();
+
+ if (title.length > 0) {
+ id3v1.title = title;
+ }
+
+ reader.seek(3 + 30, ByteReader.SEEK_BEG);
+
+ var artist = reader.readNullTerminatedString(30).trim();
+ if (artist.length > 0) {
+ id3v1.artist = artist;
+ }
+
+ reader.seek(3 + 30 + 30, ByteReader.SEEK_BEG);
+
+ var album = reader.readNullTerminatedString(30).trim();
+ if (album.length > 0) {
+ id3v1.album = album;
+ }
+ }
+ this.nextStep();
+ }
+ ],
+ this
+ );
+
+ var id3v2Parser = new FunctionSequence(
+ 'id3v2parser',
+ [
+ function readHead(file) {
+ util.readFileBytes(file, 0, 10, this.nextStep, this.onError,
+ this);
+ },
+
+ /**
+ * Check if passed array of 10 bytes contains ID3 header.
+ * @param file to check and continue reading if ID3 metadata found
+ * @param {ByteReader} reader reader to fill with stream bytes.
+ */
+ function checkId3v2(file, reader) {
+ if (reader.readString(3) == 'ID3') {
+ this.logger.vlog('id3v2 found');
+ var id3v2 = metadata.id3v2 = {};
+ id3v2.major = reader.readScalar(1, false);
+ id3v2.minor = reader.readScalar(1, false);
+ id3v2.flags = reader.readScalar(1, false);
+ id3v2.size = Id3Parser.readSynchSafe_(reader, 4);
+
+ util.readFileBytes(file, 10, 10 + id3v2.size, this.nextStep,
+ this.onError, this);
+ } else {
+ this.finish();
+ }
+ },
+
+ /**
+ * Extracts all ID3v2 frames from given bytebuffer.
+ * @param file being parsed.
+ * @param {ByteReader} reader to use for metadata extraction.
+ */
+ function extractFrames(file, reader) {
+ var frameStart = 0;
+
+ var id3v2 = metadata.id3v2;
+
+ if ((id3v2.major > 2)
+ && (id3v2.flags & Id3Parser.v2.FLAG_EXTENDED_HEADER != 0)) {
+ // Skip extended header if found
+ if (id3v2.major == 3) {
+ reader.seek(reader.readScalar(4, false) - 4);
+ } else if (id3v2.major == 4) {
+ reader.seek(Id3Parser.readSynchSafe_(reader, 4) - 4);
+ }
+ }
+
+ var frame;
+
+ while (frame = self.readFrame_(reader, id3v2.major)) {
+ metadata.id3v2[frame.name] = frame;
+ }
+
+ this.nextStep();
+ }
+ ],
+ this
+ );
+
+ var metadataParser = new FunctionParallel(
+ 'mp3metadataParser',
+ [id3v1Parser, id3v2Parser],
+ this,
+ function() {
+ callback.call(null, metadata);
+ },
+ onError
+ );
+
+ id3v1Parser.setCallback(metadataParser.nextStep);
+ id3v2Parser.setCallback(metadataParser.nextStep);
+
+ id3v1Parser.setFailureCallback(metadataParser.onError);
+ id3v2Parser.setFailureCallback(metadataParser.onError);
+
+ this.vlog('Passed argument : ' + file);
+
+ metadataParser.start(file);
+};
+
+/**
+ * id3v2 constants
+ */
+Id3Parser.v2 = {
+ FLAG_EXTENDED_HEADER: 1 << 5,
+
+ ENCODING: {
+ /**
+ * ISO-8859-1 [ISO-8859-1]. Terminated with $00.
+ *
+ * @const
+ * @type {int}
+ */
+ ISO_8859_1 : 0,
+
+
+ /**
+ * UTF-16 [UTF-16] encoded Unicode [UNICODE] with BOM. All
+ * strings in the same frame SHALL have the same byteorder.
+ * Terminated with $00 00.
+ *
+ * @const
+ * @type {int}
+ */
+ UTF_16BE : 1,
+
+ /**
+ * UTF-16BE [UTF-16] encoded Unicode [UNICODE] without BOM.
+ * Terminated with $00 00.
+ *
+ * @const
+ * @type {int}
+ */
+ UTF_16 : 2,
+
+ /**
+ * UTF-8 [UTF-8] encoded Unicode [UNICODE]. Terminated with $00.
+ *
+ * @const
+ * @type {int}
+ */
+ UTF_8 : 3
+ },
+ HANDLERS: {
+ //User defined text information frame
+ TXX: Id3Parser.prototype.readUserDefinedTextFrame_,
+ //User defined URL link frame
+ WXX: Id3Parser.prototype.readUserDefinedTextFrame_,
+
+ //User defined text information frame
+ TXXX: Id3Parser.prototype.readUserDefinedTextFrame_,
+
+ //User defined URL link frame
+ WXXX: Id3Parser.prototype.readUserDefinedTextFrame_,
+
+ //User attached image
+ PIC: Id3Parser.prototype.readPIC_,
+
+ //User attached image
+ APIC: Id3Parser.prototype.readAPIC_
+ }
+};
+
+MetadataDispatcher.registerParserClass(Id3Parser);