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 * @param {MetadataDispatcher} parent Parent object.
9 function MpegParser(parent) {
10 MetadataParser.call(this, parent, 'mpeg', /\.(mp4|m4v|m4a|mpe?g4?)$/i);
11 this.mimeType = 'video/mpeg';
14 MpegParser.prototype = {__proto__: MetadataParser.prototype};
17 * Size of the atom header.
19 MpegParser.HEADER_SIZE = 8;
22 * @param {ByteReader} br ByteReader instance.
23 * @param {number=} opt_end End of atom position.
24 * @return {number} Atom size.
26 MpegParser.readAtomSize = function(br, opt_end) {
30 // Assert that opt_end <= buffer end.
31 // When supplied, opt_end is the end of the enclosing atom and is used to
32 // check the correct nesting.
33 br.validateRead(opt_end - pos);
36 var size = br.readScalar(4, false, opt_end);
38 if (size < MpegParser.HEADER_SIZE)
39 throw 'atom too short (' + size + ') @' + pos;
41 if (opt_end && pos + size > opt_end)
42 throw 'atom too long (' + size + '>' + (opt_end - pos) + ') @' + pos;
48 * @param {ByteReader} br ByteReader instance.
49 * @param {number=} opt_end End of atom position.
50 * @return {string} Atom name.
52 MpegParser.readAtomName = function(br, opt_end) {
53 return br.readString(4, opt_end).toLowerCase();
57 * @param {Object} metadata Metadata object.
58 * @return {Object} Root of the parser tree.
60 MpegParser.createRootParser = function(metadata) {
61 function findParentAtom(atom, name) {
64 if (!atom) return null;
65 if (atom.name == name) return atom;
69 function parseFtyp(br, atom) {
70 metadata.brand = br.readString(4, atom.end);
73 function parseMvhd(br, atom) {
74 var version = br.readScalar(4, false, atom.end);
75 var offset = (version == 0) ? 8 : 16;
76 br.seek(offset, ByteReader.SEEK_CUR);
77 var timescale = br.readScalar(4, false, atom.end);
78 var duration = br.readScalar(4, false, atom.end);
79 metadata.duration = duration / timescale;
82 function parseHdlr(br, atom) {
83 br.seek(8, ByteReader.SEEK_CUR);
84 findParentAtom(atom, 'trak').trackType = br.readString(4, atom.end);
87 function parseStsd(br, atom) {
88 var track = findParentAtom(atom, 'trak');
89 if (track && track.trackType == 'vide') {
90 br.seek(40, ByteReader.SEEK_CUR);
91 metadata.width = br.readScalar(2, false, atom.end);
92 metadata.height = br.readScalar(2, false, atom.end);
96 function parseDataString(name, br, atom) {
97 br.seek(8, ByteReader.SEEK_CUR);
98 metadata[name] = br.readString(atom.end - br.tell(), atom.end);
101 function parseCovr(br, atom) {
102 br.seek(8, ByteReader.SEEK_CUR);
103 metadata.thumbnailURL = br.readImage(atom.end - br.tell(), atom.end);
106 // 'meta' atom can occur at one of the several places in the file structure.
109 '©nam': { data: parseDataString.bind(null, 'title') },
110 '©alb': { data: parseDataString.bind(null, 'album') },
111 '©art': { data: parseDataString.bind(null, 'artist') },
112 'covr': { data: parseCovr }
117 // main parser for the entire file structure.
144 * @param {File} file File.
145 * @param {Object} metadata Metadata.
146 * @param {function(Object)} callback Success callback.
147 * @param {function} onError Error callback.
149 MpegParser.prototype.parse = function(file, metadata, callback, onError) {
150 var rootParser = MpegParser.createRootParser(metadata);
152 // Kick off the processing by reading the first atom's header.
153 this.requestRead(rootParser, file, 0, MpegParser.HEADER_SIZE, null,
154 onError, callback.bind(null, metadata));
158 * @param {function(ByteReader, Object)|Object} parser Parser tree node.
159 * @param {ByteReader} br ByteReader instance.
160 * @param {Object} atom Atom descriptor.
161 * @param {number} filePos File position of the atom start.
163 MpegParser.prototype.applyParser = function(parser, br, atom, filePos) {
165 var path = atom.name;
166 for (var p = atom.parent; p && p.name; p = p.parent) {
167 path = p.name + '.' + path;
172 action = 'skipping ';
173 } else if (parser instanceof Function) {
176 action = 'recursing';
179 var start = atom.start - MpegParser.HEADER_SIZE;
180 this.vlog(path + ': ' +
181 '@' + (filePos + start) + ':' + (atom.end - start),
186 if (parser instanceof Function) {
187 br.pushSeek(atom.start);
191 if (parser.versioned) {
194 this.parseMpegAtomsInRange(parser, br, atom, filePos);
200 * @param {function(ByteReader, Object)|Object} parser Parser tree node.
201 * @param {ByteReader} br ByteReader instance.
202 * @param {Object} parentAtom Parent atom descriptor.
203 * @param {number} filePos File position of the atom start.
205 MpegParser.prototype.parseMpegAtomsInRange = function(
206 parser, br, parentAtom, filePos) {
208 for (var offset = parentAtom.start; offset != parentAtom.end;) {
209 if (count++ > 100) // Most likely we are looping through a corrupt file.
210 throw 'too many child atoms in ' + parentAtom.name + ' @' + offset;
213 var size = MpegParser.readAtomSize(br, parentAtom.end);
214 var name = MpegParser.readAtomName(br, parentAtom.end);
219 { start: offset + MpegParser.HEADER_SIZE,
232 * @param {Object} rootParser Parser definition.
233 * @param {File} file File.
234 * @param {number} filePos Start position in the file.
235 * @param {number} size Atom size.
236 * @param {string} name Atom name.
237 * @param {function} onError Error callback.
238 * @param {function} onSuccess Success callback.
240 MpegParser.prototype.requestRead = function(
241 rootParser, file, filePos, size, name, onError, onSuccess) {
243 var reader = new FileReader();
244 reader.onerror = onError;
245 reader.onload = function(event) {
246 self.processTopLevelAtom(
247 reader.result, rootParser, file, filePos, size, name,
250 this.vlog('reading @' + filePos + ':' + size);
251 reader.readAsArrayBuffer(file.slice(filePos, filePos + size));
255 * @param {ArrayBuffer} buf Data buffer.
256 * @param {Object} rootParser Parser definition.
257 * @param {File} file File.
258 * @param {number} filePos Start position in the file.
259 * @param {number} size Atom size.
260 * @param {string} name Atom name.
261 * @param {function} onError Error callback.
262 * @param {function} onSuccess Success callback.
264 MpegParser.prototype.processTopLevelAtom = function(
265 buf, rootParser, file, filePos, size, name, onError, onSuccess) {
267 var br = new ByteReader(buf);
269 // the header has already been read.
270 var atomEnd = size - MpegParser.HEADER_SIZE;
272 var bufLength = buf.byteLength;
274 // Check the available data size. It should be either exactly
275 // what we requested or HEADER_SIZE bytes less (for the last atom).
276 if (bufLength != atomEnd && bufLength != size) {
277 throw 'Read failure @' + filePos + ', ' +
278 'requested ' + size + ', read ' + bufLength;
281 // Process the top level atom.
282 if (name) { // name is null only the first time.
286 {start: 0, end: atomEnd, name: name},
291 filePos += bufLength;
292 if (bufLength == size) {
293 // The previous read returned everything we asked for, including
294 // the next atom header at the end of the buffer.
295 // Parse this header and schedule the next read.
296 br.seek(-MpegParser.HEADER_SIZE, ByteReader.SEEK_END);
297 var nextSize = MpegParser.readAtomSize(br);
298 var nextName = MpegParser.readAtomName(br);
300 // If we do not have a parser for the next atom, skip the content and
301 // read only the header (the one after the next).
302 if (!rootParser[nextName]) {
303 filePos += nextSize - MpegParser.HEADER_SIZE;
304 nextSize = MpegParser.HEADER_SIZE;
307 this.requestRead(rootParser, file, filePos, nextSize, nextName,
310 // The previous read did not return the next atom header, EOF reached.
311 this.vlog('EOF @' + filePos);
315 onError(e.toString());
319 MetadataDispatcher.registerParserClass(MpegParser);