summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/resources/component_extension_resources.grd3
-rw-r--r--chrome/browser/resources/file_manager/js/exif_reader.js159
-rw-r--r--chrome/browser/resources/file_manager/js/file_manager.js102
-rw-r--r--chrome/browser/resources/file_manager/js/metadata_reader.js111
-rw-r--r--chrome/browser/resources/file_manager/js/metadata_util.js107
-rw-r--r--chrome/browser/resources/file_manager/js/mpeg_reader.js251
6 files changed, 550 insertions, 183 deletions
diff --git a/chrome/browser/resources/component_extension_resources.grd b/chrome/browser/resources/component_extension_resources.grd
index c5701bc..e6c3357 100644
--- a/chrome/browser/resources/component_extension_resources.grd
+++ b/chrome/browser/resources/component_extension_resources.grd
@@ -26,7 +26,10 @@
<include name="IDR_FILE_MANAGER_SLIDESHOW" file="file_manager/slideshow.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_FILE_MANAGER_MEDIAPLAYER" file="file_manager/mediaplayer.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_FILE_MANAGER_PLAYLIST" file="file_manager/playlist.html" flattenhtml="true" type="BINDATA" />
+ <include name="IDR_FILE_MANAGER_METADATA_READER" file="file_manager/js/metadata_reader.js" type="BINDATA" />
+ <include name="IDR_FILE_MANAGER_METADATA_UTIL" file="file_manager/js/metadata_util.js" type="BINDATA" />
<include name="IDR_FILE_MANAGER_EXIF_READER" file="file_manager/js/exif_reader.js" type="BINDATA" />
+ <include name="IDR_FILE_MANAGER_MPEG_READER" file="file_manager/js/mpeg_reader.js" type="BINDATA" />
<include name="IDR_FILE_MANAGER_PREVIEW_ICON" file="file_manager/images/icon_preview_16x16.png" type="BINDATA" />
<include name="IDR_FILE_MANAGER_MEDIA_PLAY_ICON" file="file_manager/images/icon_play_16x16.png" type="BINDATA" />
<include name="IDR_FILE_MANAGER_MEDIA_ENQUEUE_ICON" file="file_manager/images/icon_add_to_queue_16x16.png" type="BINDATA" />
diff --git a/chrome/browser/resources/file_manager/js/exif_reader.js b/chrome/browser/resources/file_manager/js/exif_reader.js
index c7c43d1..624247b 100644
--- a/chrome/browser/resources/file_manager/js/exif_reader.js
+++ b/chrome/browser/resources/file_manager/js/exif_reader.js
@@ -3,60 +3,30 @@
// found in the LICENSE file.
var exif = {
- verbose: false,
-
- messageHandlers: {
- "init": function() {
- this.log('thumbnailer initialized');
- },
-
- "get-exif": function(fileURL) {
- this.processOneFile(fileURL, function callback(metadata) {
- postMessage({verb: 'give-exif',
- arguments: [fileURL, metadata]});
- });
- },
- },
+ urlFilter: /\.jpe?g$/i,
- processOneFile: function(fileURL, callback) {
- var self = this;
+ parse : function (file, callback, error_closure, logger) {
var currentStep = -1;
function nextStep(var_args) {
- self.vlog('nextStep: ' + steps[currentStep + 1].name);
- steps[++currentStep].apply(self, arguments);
+ logger.vlog('nextStep: ' + steps[currentStep + 1].name);
+ steps[++currentStep].apply(null, arguments);
}
function onError(err) {
- self.vlog('Error processing: ' + fileURL + ': step: ' +
- steps[currentStep].name + ": " + err);
-
- postMessage({verb: 'give-exif-error',
- arguments: [fileURL, steps[currentStep].name, err]});
+ error_closure(err, steps[currentStep].name);
}
var steps =
- [ // Step one, turn the url into an entry.
- function getEntry() {
- webkitResolveLocalFileSystemURL(fileURL,
- function(entry) { nextStep(entry) },
- onError);
- },
-
- // Step two, turn the entry into a file.
- function getFile(entry) {
- entry.file(function(file) { nextStep(file) }, onError);
- },
-
- // Step three, read the file header into a byte array.
+ [ // Step one, read the file header into a byte array.
function readHeader(file) {
- var reader = new FileReader(file.webkitSlice(0, 1024));
+ var reader = new FileReader();
reader.onerror = onError;
reader.onload = function(event) { nextStep(file, reader.result) };
- reader.readAsArrayBuffer(file);
+ reader.readAsArrayBuffer(file.webkitSlice(0, 1024));
},
- // Step four, find the exif marker and read all exif data.
+ // Step two, find the exif marker and read all exif data.
function findExif(file, buf) {
var br = new exif.BufferReader(buf);
var mark = br.readMark();
@@ -91,7 +61,7 @@ var exif = {
}
},
- // Step five, parse the exif data.
+ // Step three, parse the exif data.
function parseExif(file, buf) {
var br = new exif.BufferReader(buf);
var order = br.readScalar(2);
@@ -129,7 +99,7 @@ var exif = {
var b64 = br.readBase64(tags[exif.TAG_JPG_THUMB_LENGTH].value);
metadata.thumbnailURL = 'data:image/jpeg;base64,' + b64;
} else {
- self.vlog('Image has EXIF data, but no JPG thumbnail.');
+ logger.vlog('Image has EXIF data, but no JPG thumbnail.');
}
if (exif.TAG_EXIF_IMAGE_WIDTH in tags)
@@ -141,32 +111,11 @@ var exif = {
nextStep(metadata);
},
- // Step six, we're done.
+ // Step four, we're done.
callback
];
- nextStep();
- },
-
- onMessage: function(event) {
- var data = event.data;
-
- if (this.messageHandlers.hasOwnProperty(data.verb)) {
- //this.log('dispatching: ' + data.verb + ': ' + data.arguments);
- this.messageHandlers[data.verb].apply(this, data.arguments);
- } else {
- this.log('Unknown message from client: ' + data.verb, data);
- }
- },
-
- log: function(var_args) {
- var ary = Array.apply(null, arguments);
- postMessage({verb: 'log', arguments: ary});
- },
-
- vlog: function(var_args) {
- if (this.verbose)
- this.log.apply(this, arguments);
+ nextStep(file);
}
};
@@ -213,90 +162,26 @@ exif.BufferReader.prototype = {
* Big endian read. Most significant bytes come first.
*/
readBig: function(width) {
- var rv = 0;
- switch(width) {
- case 4:
- rv = this.ary_[this.pos_++] << 24;
- case 3:
- rv |= this.ary_[this.pos_++] << 16;
- case 2:
- rv |= this.ary_[this.pos_++] << 8;
- case 1:
- rv |= this.ary_[this.pos_++];
- }
-
- return rv;
+ this.skip(width);
+ return parseUtil.parseBig(this.ary_, this.pos_ - width, width);
},
/**
* Little endian read. Least significant bytes come first.
*/
readLittle: function(width) {
- var rv = 0;
- switch(width) {
- case 4:
- rv = this.ary_[this.pos_ + 3] << 24;
- case 3:
- rv |= this.ary_[this.pos_ + 2] << 16;
- case 2:
- rv |= this.ary_[this.pos_+ 1] << 8;
- case 1:
- rv |= this.ary_[this.pos_];
- }
-
- this.pos_ += width;
- return rv;
+ this.skip(width);
+ return parseUtil.parseLittle(this.ary_, this.pos_ - width, width);
},
readString: function(length) {
- var chars = [];
- for (var i = 0; i < length; i++) {
- chars[i] = String.fromCharCode(this.ary_[this.pos_++]);
- }
-
- return chars.join('');
+ this.skip(length);
+ return parseUtil.parseString(this.ary_, this.pos_ - length, this.pos_);
},
- base64Alphabet_: ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
- 'abcdefghijklmnopqrstuvwxyz' +
- '0123456789+/').split(''),
-
readBase64: function(length) {
- var rv = [];
- var chars = [];
- var padding = 0;
-
- for (var i = 0; i < length; /* incremented inside */) {
- var bits = this.ary_[this.pos_ + i++] << 16;
-
- if (i < length) {
- bits |= this.ary_[this.pos_ + i++] << 8;
-
- if (i < length) {
- bits |= this.ary_[this.pos_ + i++];
- } else {
- padding = 1;
- }
- } else {
- padding = 2;
- }
-
- chars[3] = this.base64Alphabet_[bits & 63];
- chars[2] = this.base64Alphabet_[(bits >> 6) & 63];
- chars[1] = this.base64Alphabet_[(bits >> 12) & 63];
- chars[0] = this.base64Alphabet_[(bits >> 18) & 63];
-
- rv.push.apply(rv, chars);
- }
-
- this.pos_ += i;
-
- if (padding > 0)
- chars[chars.length - 1] = '=';
- if (padding > 1)
- chars[chars.length - 2] = '=';
-
- return rv.join('');
+ this.skip(length);
+ return parseUtil.parseBase64(this.ary_, this.pos_ - length, this.pos_);
},
readMark: function() {
@@ -335,4 +220,4 @@ exif.BufferReader.prototype = {
}
};
-var onmessage = exif.onMessage.bind(exif);
+if (metadataReader) metadataReader.registerParser(exif);
diff --git a/chrome/browser/resources/file_manager/js/file_manager.js b/chrome/browser/resources/file_manager/js/file_manager.js
index e3b69f6..cb1bb3c 100644
--- a/chrome/browser/resources/file_manager/js/file_manager.js
+++ b/chrome/browser/resources/file_manager/js/file_manager.js
@@ -4,10 +4,10 @@
// WK Bug 55728 is fixed on the chrome 12 branch but not on the trunk.
// TODO(rginda): Enable this everywhere once we have a trunk-worthy fix.
-const ENABLE_EXIF_READER = navigator.userAgent.match(/chrome\/12\.0/i);
+const ENABLE_METADATA = true;
// Thumbnail view is painful without the exif reader.
-const ENABLE_THUMBNAIL_VIEW = ENABLE_EXIF_READER;
+const ENABLE_THUMBNAIL_VIEW = ENABLE_METADATA;
var g_slideshow_data = null;
@@ -41,7 +41,7 @@ function FileManager(dialogDom, rootEntries, params) {
this.listType_ = null;
- this.exifCache_ = {};
+ this.metadataCache_ = {};
// True if we should filter out files that start with a dot.
this.filterFiles_ = true;
@@ -79,10 +79,16 @@ function FileManager(dialogDom, rootEntries, params) {
this.table_.list_.focus();
- if (ENABLE_EXIF_READER) {
- this.exifReader = new Worker('js/exif_reader.js');
- this.exifReader.onmessage = this.onExifReaderMessage_.bind(this);
- this.exifReader.postMessage({verb: 'init'});
+ if (ENABLE_METADATA) {
+ // Pass all URLs to the metadata reader until we have a correct filter.
+ this.metadataUrlFilter_ = /.*/;
+
+ this.metadataReader_ = new Worker('js/metadata_reader.js');
+ this.metadataReader_.onmessage = this.onMetadataMessage_.bind(this);
+ this.metadataReader_.postMessage({verb: 'init'});
+ } else {
+ // This regexp never matches anything, so the metadata reader is bypassed.
+ this.metadataUrlFilter_ = /$./;
}
}
@@ -1132,10 +1138,10 @@ FileManager.prototype = {
cacheNextFile();
};
- FileManager.prototype.onExifGiven_ = function(fileURL, metadata) {
- var observers = this.exifCache_[fileURL];
+ FileManager.prototype.onMetadataReceived_ = function(fileURL, metadata) {
+ var observers = this.metadataCache_[fileURL];
if (!observers || !(observers instanceof Array)) {
- console.error('Missing or invalid exif observers: ' + fileURL + ': ' +
+ console.error('Missing or invalid metadata observers: ' + fileURL + ': ' +
observers);
return;
}
@@ -1144,15 +1150,19 @@ FileManager.prototype = {
observers[i](metadata);
}
- this.exifCache_[fileURL] = metadata;
+ this.metadataCache_[fileURL] = metadata;
+ };
+
+ FileManager.prototype.onMetadataError_ = function(fileURL, step, error) {
+ console.warn('metadata: ' + fileURL + ': ' + step + ': ' + error);
+ this.onMetadataReceived_(fileURL, {});
};
- FileManager.prototype.onExifError_ = function(fileURL, step, error) {
- console.warn('Exif error: ' + fileURL + ': ' + step + ': ' + error);
- this.onExifGiven_(fileURL, {});
+ FileManager.prototype.onMetadataUrlFilter_ = function(regexp) {
+ this.metadataUrlFilter_ = regexp;
};
- FileManager.prototype.onExifReaderMessage_ = function(event) {
+ FileManager.prototype.onMetadataMessage_ = function(event) {
var data = event.data;
var self = this;
@@ -1160,19 +1170,23 @@ FileManager.prototype = {
switch (data.verb) {
case 'log':
- console.log.apply(console, ['exif:'].concat(data.arguments));
+ console.log.apply(console, ['metadata:'].concat(data.arguments));
+ break;
+
+ case 'result':
+ fwd('onMetadataReceived_', data.arguments);
break;
- case 'give-exif':
- fwd('onExifGiven_', data.arguments);
+ case 'error':
+ fwd('onMetadataError_', data.arguments);
break;
- case 'give-exif-error':
- fwd('onExifError_', data.arguments);
+ case 'filter':
+ fwd('onMetadataUrlFilter_', data.arguments);
break;
default:
- console.log('Unknown message from exif reader: ' + data.verb, data);
+ console.log('Unknown message from metadata reader: ' + data.verb, data);
break;
}
};
@@ -1335,15 +1349,20 @@ FileManager.prototype = {
});
};
- FileManager.prototype.cacheExifMetadata_ = function(entry, callback) {
+ FileManager.prototype.cacheMetadata_ = function(entry, callback) {
var url = entry.toURL();
- var cacheValue = this.exifCache_[url];
+ var cacheValue = this.metadataCache_[url];
if (!cacheValue) {
// This is the first time anyone's asked, go get it.
- this.exifCache_[url] = [callback];
- this.exifReader.postMessage({verb: 'get-exif',
- arguments: [entry.toURL()]});
+ if (entry.name.match(this.metadataUrlFilter_)) {
+ this.metadataCache_[url] = [callback];
+ this.metadataReader_.postMessage(
+ {verb: 'request', arguments: [entry.toURL()]});
+ return;
+ }
+ // Cannot extract metadata for this file, return an empty map.
+ setTimeout(function() { callback({}) });
return;
}
@@ -1359,7 +1378,7 @@ FileManager.prototype = {
return;
}
- console.error('Unexpected exif cache value:' + cacheValue);
+ console.error('Unexpected metadata cache value:' + cacheValue);
};
FileManager.prototype.getThumbnailURL = function(entry, callback) {
@@ -1367,28 +1386,19 @@ FileManager.prototype = {
return;
var iconType = getIconType(entry);
- if (iconType != 'image') {
- // Not an image, display a canned clip-art graphic.
- if (!(iconType in previewArt))
- iconType = 'unknown';
-
- setTimeout(function() { callback(iconType, previewArt[iconType]) });
- return;
- }
- if (ENABLE_EXIF_READER) {
- if (entry.name.match(/\.jpe?g$/i)) {
- // File is a jpg image, fetch the exif thumbnail.
- this.cacheExifMetadata_(entry, function(metadata) {
- callback(iconType, metadata.thumbnailURL || entry.toURL());
- });
- return;
+ this.cacheMetadata_(entry, function (metadata) {
+ var url = metadata.thumbnailURL;
+ if (!url) {
+ if (iconType == 'image') {
+ url = entry.toURL();
+ } else {
+ url = previewArt[iconType] || previewArt['unknown'];
+ }
}
- }
- // File is some other kind of image, just return the url to the whole
- // thing.
- setTimeout(function() { callback(iconType, entry.toURL()) });
+ callback(iconType, url);
+ });
};
/**
diff --git a/chrome/browser/resources/file_manager/js/metadata_reader.js b/chrome/browser/resources/file_manager/js/metadata_reader.js
new file mode 100644
index 0000000..aa7b133
--- /dev/null
+++ b/chrome/browser/resources/file_manager/js/metadata_reader.js
@@ -0,0 +1,111 @@
+// 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.
+
+var metadataReader = {
+ verbose: false,
+
+ parsers: [],
+
+ registerParser : function(parser) {
+ this.parsers.push(parser);
+ },
+
+ messageHandlers: {
+ "init": function() {
+ // Combine url filters from all parsers and build a single regexp.
+ var sources = [];
+ for (var i = 0; i != this.parsers.length; i++) {
+ sources.push(this.parsers[i].urlFilter.source);
+ }
+ var regexp = new RegExp('(' + sources.join('|') + ')', 'i');
+ postMessage({verb: 'filter', arguments: [regexp]});
+ this.log('initialized with URL filter ' + regexp);
+ },
+
+ "request": function(fileURL) {
+ this.processOneFile(fileURL, function callback(metadata) {
+ postMessage({verb: 'result', arguments: [fileURL, metadata]});
+ });
+ }
+ },
+
+ onMessage: function(event) {
+ var data = event.data;
+
+ if (this.messageHandlers.hasOwnProperty(data.verb)) {
+ this.messageHandlers[data.verb].apply(this, data.arguments);
+ } else {
+ this.log('Unknown message from client: ' + data.verb, data);
+ }
+ },
+
+ error: function(var_args) {
+ var ary = Array.apply(null, arguments);
+ postMessage({verb: 'error', arguments: ary});
+ },
+
+ log: function(var_args) {
+ var ary = Array.apply(null, arguments);
+ postMessage({verb: 'log', arguments: ary});
+ },
+
+ vlog: function(var_args) {
+ if (this.verbose)
+ this.log.apply(this, arguments);
+ },
+
+ processOneFile: function(fileURL, callback) {
+ var self = this;
+ var currentStep = -1;
+
+ function nextStep(var_args) {
+ self.vlog('nextStep: ' + steps[currentStep + 1].name);
+ steps[++currentStep].apply(self, arguments);
+ }
+
+ function onError(err, stepName) {
+ self.error(fileURL, stepName || steps[currentStep].name, err);
+ }
+
+ var steps =
+ [ // Step one, find the parser matching the url.
+ function detectFormat() {
+ for (var i = 0; i != self.parsers.length; i++) {
+ var parser = self.parsers[i];
+ if (fileURL.match(parser.urlFilter)) {
+ nextStep(parser.parse);
+ return;
+ }
+ }
+ onError('unsupported format');
+ },
+
+ // Step two, turn the url into an entry.
+ function getEntry(parseFunc) {
+ webkitResolveLocalFileSystemURL(
+ fileURL,
+ function(entry) { nextStep(entry, parseFunc) },
+ onError);
+ },
+
+ // Step three, turn the entry into a file.
+ function getFile(entry, parseFunc) {
+ entry.file(function(file) { nextStep(file, parseFunc) }, onError);
+ },
+
+ // Step four, parse the file content.
+ function parseContent(file, parseFunc) {
+ parseFunc(file, callback, onError, self);
+ }
+ ];
+
+ nextStep();
+ }
+};
+
+var onmessage = metadataReader.onMessage.bind(metadataReader);
+
+importScripts("metadata_util.js");
+importScripts("exif_reader.js");
+importScripts("mpeg_reader.js"); \ No newline at end of file
diff --git a/chrome/browser/resources/file_manager/js/metadata_util.js b/chrome/browser/resources/file_manager/js/metadata_util.js
new file mode 100644
index 0000000..89231b1
--- /dev/null
+++ b/chrome/browser/resources/file_manager/js/metadata_util.js
@@ -0,0 +1,107 @@
+// 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.
+
+
+var parseUtil = {
+ /**
+ * Big endian read. Most significant bytes come first.
+ */
+ parseBig: function(bytes, pos, width) {
+ var rv = 0;
+ switch(width) {
+ case 4:
+ rv = bytes[pos++] << 24;
+ case 3:
+ rv |= bytes[pos++] << 16;
+ case 2:
+ rv |= bytes[pos++] << 8;
+ case 1:
+ rv |= bytes[pos++];
+ }
+ return rv;
+ },
+
+ /**
+ * Little endian read. Least significant bytes come first.
+ */
+ parseLittle: function(bytes, pos, width) {
+ var rv = 0;
+ switch(width) {
+ case 4:
+ rv = bytes[pos + 3] << 24;
+ case 3:
+ rv |= bytes[pos + 2] << 16;
+ case 2:
+ rv |= bytes[pos + 1] << 8;
+ case 1:
+ rv |= bytes[pos];
+ }
+ return rv;
+ },
+
+ /**
+ * Read a string.
+ * TODO: implement UTF8.
+ */
+ parseString: function(bytes, pos, end) {
+ var codes = [];
+ while (pos < end) {
+ codes.push(bytes[pos++]);
+ }
+ return String.fromCharCode.apply(null, codes);
+ },
+
+ base64Alphabet: ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
+ 'abcdefghijklmnopqrstuvwxyz' +
+ '0123456789+/').split(''),
+
+ parseBase64: function(bytes, pos, end) {
+ var rv = [];
+ var chars = [];
+ var padding = 0;
+
+ while (pos < end) {
+ var bits = bytes[pos++] << 16;
+
+ if (pos < end) {
+ bits |= bytes[pos++] << 8;
+
+ if (pos < end) {
+ bits |= bytes[pos++];
+ } else {
+ padding = 1;
+ }
+ } else {
+ padding = 2;
+ }
+
+ chars[3] = parseUtil.base64Alphabet[bits & 63];
+ chars[2] = parseUtil.base64Alphabet[(bits >> 6) & 63];
+ chars[1] = parseUtil.base64Alphabet[(bits >> 12) & 63];
+ chars[0] = parseUtil.base64Alphabet[(bits >> 18) & 63];
+
+ rv.push.apply(rv, chars);
+ }
+
+ if (padding > 0)
+ chars[chars.length - 1] = '=';
+ if (padding > 1)
+ chars[chars.length - 2] = '=';
+
+ return rv.join('');
+ },
+
+ createImageDataUrl: function(bytes, pos, end) {
+ var format;
+ if (parseUtil.parseString(bytes, pos, 4) == '\x89PNG') {
+ format = 'png';
+ } else if (parseUtil.parseBig(bytes, pos, 2) == 0xFFD8) {
+ format = 'jpeg';
+ } else {
+ format = 'unknown';
+ }
+ var b64 = parseUtil.parseBase64(bytes, pos, end);
+ return 'data:image/' + format + ';base64,' + b64;
+ }
+} \ No newline at end of file
diff --git a/chrome/browser/resources/file_manager/js/mpeg_reader.js b/chrome/browser/resources/file_manager/js/mpeg_reader.js
new file mode 100644
index 0000000..ed2aec1
--- /dev/null
+++ b/chrome/browser/resources/file_manager/js/mpeg_reader.js
@@ -0,0 +1,251 @@
+// 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.
+
+var mpeg = {
+ urlFilter: /\.(mp4|m4v|m4a|mpe?g4?)$/i,
+
+ parse: function (file, callback, onError, logger) {
+ const HEADER_SIZE = 8;
+
+ var metadata = {};
+
+ // MPEG-4 format parsing primitives.
+
+ function checkBuffer(bytes, pos, end) {
+ if (!end) end = bytes.length;
+ if (pos > end)
+ throw "Reading past the buffer end: pos=" + pos + ", end=" + end;
+ }
+
+ function parseFourCC(bytes, pos, end) {
+ checkBuffer(bytes, pos + 4, end);
+ return parseUtil.parseString(bytes, pos, pos + 4);
+ }
+
+ function parseUint16(bytes, pos, end) {
+ checkBuffer(bytes, pos + 2, end)
+ return parseUtil.parseBig(bytes, pos, 2);
+ }
+
+ function parseUint32(bytes, pos, end) {
+ checkBuffer(bytes, pos + 4, end)
+ return parseUtil.parseBig(bytes, pos, 4);
+ }
+
+ function parseAtomSize(bytes, pos, end) {
+ var size = parseUint32(bytes, pos, end);
+
+ if (size < HEADER_SIZE)
+ throw "atom too short (" + size + ") @" + pos;
+
+ if (end && (end < pos + size))
+ throw "atom too long (" + size + ") @" + pos;
+
+ return size;
+ }
+
+ function parseAtomName(bytes, pos, end) {
+ return parseFourCC(bytes, pos, end).toLowerCase();
+ }
+
+ function findParentAtom(atom, name) {
+ for (;;) {
+ atom = atom.parent;
+ if (!atom) return null;
+ if (atom.name == name) return atom;
+ }
+ }
+
+ // Specific atom parsers.
+ function parseFtyp(bytes, atom) {
+ metadata.brand = parseFourCC(bytes, atom.start, atom.end);
+ }
+
+ function parseMvhd(bytes, atom) {
+ var version = parseUint32(bytes, atom.start, atom.end);
+ var offset = (version == 0) ? 12 : 20;
+ var timescale = parseUint32(bytes, atom.start + offset, atom.end)
+ var duration = parseUint32(bytes, atom.start + offset + 4, atom.end)
+ metadata.duration = duration / timescale;
+ }
+
+ function parseHdlr(bytes, atom) {
+ findParentAtom(atom, 'trak').trackType =
+ parseFourCC(bytes, atom.start + 8, atom.end);
+ }
+
+ function parseStsd(bytes, atom) {
+ var track = findParentAtom(atom, 'trak');
+ if (track && track.trackType == 'vide') {
+ metadata.width = parseUint16(bytes, atom.start + 40, atom.end);
+ metadata.height = parseUint16(bytes, atom.start + 42, atom.end);
+ }
+ }
+
+ function parseDataString(name, bytes, atom) {
+ metadata[name] = parseUtil.parseString(bytes, atom.start + 8, atom.end);
+ }
+
+ function parseCovr(bytes, atom) {
+ metadata.thumbnailURL =
+ parseUtil.createImageDataUrl(bytes, atom.start + 8, atom.end);
+ }
+
+ // 'meta' atom can occur at one of the several places in the file structure.
+ var parseMeta = {
+ ilst: {
+ "©nam": { data : parseDataString.bind(null, "title") },
+ "©alb": { data : parseDataString.bind(null, "album") },
+ "©art": { data : parseDataString.bind(null, "artist") },
+ "covr": { data : parseCovr }
+ },
+ versioned: true
+ };
+
+ // main parser for the entire file structure.
+ var parseRoot = {
+ ftyp: parseFtyp,
+ moov: {
+ mvhd : parseMvhd,
+ trak: {
+ mdia: {
+ hdlr: parseHdlr,
+ minf: {
+ stbl: {
+ stsd: parseStsd
+ }
+ }
+ },
+ meta: parseMeta
+ },
+ udta: {
+ meta: parseMeta
+ },
+ meta: parseMeta
+ },
+ meta: parseMeta
+ };
+
+ function applyParser(parser, bytes, atom, file_pos) {
+ if (logger.verbose) {
+ var path = atom.name;
+ for (var p = atom.parent; p && p.name; p = p.parent) {
+ path = p.name + '.' + path;
+ }
+ var action;
+ if (!parser) {
+ action = 'skipping ';
+ } else if (parser instanceof Function) {
+ action = 'parsing ';
+ } else {
+ action = 'recursing';
+ }
+ var start = atom.start - HEADER_SIZE;
+ logger.vlog(path,
+ '@' + (file_pos + start) + ':' + (atom.end - start),
+ action);
+ }
+
+ if (parser) {
+ if (parser instanceof Function) {
+ parser(bytes, atom);
+ } else {
+ if (parser.versioned) {
+ atom.start += 4;
+ }
+ parseMpegAtomsInRange(parser, bytes, atom, file_pos);
+ }
+ }
+ }
+
+ function parseMpegAtomsInRange(parser, bytes, parentAtom, file_pos) {
+ var count = 0;
+ for (var offset = parentAtom.start; offset != parentAtom.end;) {
+ if (count++ > 100) // Most likely we are looping through a corrupt file.
+ throw "too many child atoms in " + parentAtom.name + " @" + offset;
+
+ var size = parseAtomSize(bytes, offset, parentAtom.end);
+ var name = parseAtomName(bytes, offset + 4, parentAtom.end);
+
+ applyParser(
+ parser[name],
+ bytes,
+ { start: offset + HEADER_SIZE,
+ end: offset + size,
+ name: name,
+ parent: parentAtom
+ },
+ file_pos
+ );
+
+ offset += size;
+ }
+ }
+
+ function requestRead(file, file_pos, size, name) {
+ var reader = new FileReader();
+ reader.onerror = onError;
+ reader.onload = function(event) {
+ processTopLevelAtom(file, reader.result, file_pos, size, name);
+ };
+ logger.vlog("reading @" + file_pos + ":" + size);
+ reader.readAsArrayBuffer(file.webkitSlice(file_pos, file_pos + size));
+ }
+
+ function processTopLevelAtom(file, buf, file_pos, size, name) {
+ try {
+ var bytes = new Uint8Array(buf);
+
+ var atom_end = size - HEADER_SIZE; // the header has already been read.
+
+ // Check the available data size. It should be either exactly
+ // what we requested or HEADER_SIZE bytes less (for the last atom).
+ if (bytes.length != atom_end && bytes.length != size) {
+ throw "Read failure @" + file_pos + ", " +
+ "requested " + size + ", read " + bytes.length;
+ }
+
+ // Process the top level atom.
+ if (name) { // name is undefined only the first time.
+ applyParser(
+ parseRoot[name],
+ bytes,
+ {start: 0, end: atom_end, name: name},
+ file_pos
+ );
+ }
+
+ file_pos += bytes.length;
+ if (bytes.length == size) {
+ // The previous read returned everything we asked for, including
+ // the next atom header at the end of the buffer.
+ // Parse this header and schedule the next read.
+ var next_size = parseAtomSize(bytes, bytes.length - HEADER_SIZE);
+ var next_name = parseAtomName(bytes, bytes.length - HEADER_SIZE + 4);
+
+ // If we do not have a parser for the next atom, skip the content and
+ // read only the header (the one after the next).
+ if (!parseRoot[next_name]) {
+ file_pos += next_size - HEADER_SIZE;
+ next_size = HEADER_SIZE;
+ }
+
+ requestRead(file, file_pos, next_size, next_name);
+ } else {
+ // The previous read did not return the next atom header, EOF reached.
+ logger.vlog("EOF @" + file_pos);
+ metadata.fileSize = file_pos;
+ callback(metadata);
+ }
+ } catch(e) {
+ return onError(e);
+ }
+ }
+
+ // Kick off the processing by reading the first atom's header.
+ requestRead(file, 0, HEADER_SIZE);
+ }
+};
+
+if (metadataReader) metadataReader.registerParser(mpeg); \ No newline at end of file