Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / file_manager / foreground / js / metadata / mpeg_parser.js
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.
4
5 'use strict';
6
7 /**
8  * @param {MetadataDispatcher} parent Parent object.
9  * @constructor
10  */
11 function MpegParser(parent) {
12   MetadataParser.call(this, parent, 'mpeg', /\.(mp4|m4v|m4a|mpe?g4?)$/i);
13   this.mimeType = 'video/mpeg';
14 }
15
16 MpegParser.prototype = {__proto__: MetadataParser.prototype};
17
18 /**
19  * Size of the atom header.
20  */
21 MpegParser.HEADER_SIZE = 8;
22
23 /**
24  * @param {ByteReader} br ByteReader instance.
25  * @param {number=} opt_end End of atom position.
26  * @return {number} Atom size.
27  */
28 MpegParser.readAtomSize = function(br, opt_end) {
29   var pos = br.tell();
30
31   if (opt_end) {
32     // Assert that opt_end <= buffer end.
33     // When supplied, opt_end is the end of the enclosing atom and is used to
34     // check the correct nesting.
35     br.validateRead(opt_end - pos);
36   }
37
38   var size = br.readScalar(4, false, opt_end);
39
40   if (size < MpegParser.HEADER_SIZE)
41     throw 'atom too short (' + size + ') @' + pos;
42
43   if (opt_end && pos + size > opt_end)
44     throw 'atom too long (' + size + '>' + (opt_end - pos) + ') @' + pos;
45
46   return size;
47 };
48
49 /**
50  * @param {ByteReader} br ByteReader instance.
51  * @param {number=} opt_end End of atom position.
52  * @return {string} Atom name.
53  */
54 MpegParser.readAtomName = function(br, opt_end) {
55   return br.readString(4, opt_end).toLowerCase();
56 };
57
58 /**
59  * @param {Object} metadata Metadata object.
60  * @return {Object} Root of the parser tree.
61  */
62 MpegParser.createRootParser = function(metadata) {
63   function findParentAtom(atom, name) {
64     for (;;) {
65       atom = atom.parent;
66       if (!atom) return null;
67       if (atom.name == name) return atom;
68     }
69   }
70
71   function parseFtyp(br, atom) {
72     metadata.brand = br.readString(4, atom.end);
73   }
74
75   function parseMvhd(br, atom) {
76     var version = br.readScalar(4, false, atom.end);
77     var offset = (version == 0) ? 8 : 16;
78     br.seek(offset, ByteReader.SEEK_CUR);
79     var timescale = br.readScalar(4, false, atom.end);
80     var duration = br.readScalar(4, false, atom.end);
81     metadata.duration = duration / timescale;
82   }
83
84   function parseHdlr(br, atom) {
85     br.seek(8, ByteReader.SEEK_CUR);
86     findParentAtom(atom, 'trak').trackType = br.readString(4, atom.end);
87   }
88
89   function parseStsd(br, atom) {
90     var track = findParentAtom(atom, 'trak');
91     if (track && track.trackType == 'vide') {
92       br.seek(40, ByteReader.SEEK_CUR);
93       metadata.width = br.readScalar(2, false, atom.end);
94       metadata.height = br.readScalar(2, false, atom.end);
95     }
96   }
97
98   function parseDataString(name, br, atom) {
99     br.seek(8, ByteReader.SEEK_CUR);
100     metadata[name] = br.readString(atom.end - br.tell(), atom.end);
101   }
102
103   function parseCovr(br, atom) {
104     br.seek(8, ByteReader.SEEK_CUR);
105     metadata.thumbnailURL = br.readImage(atom.end - br.tell(), atom.end);
106   }
107
108   // 'meta' atom can occur at one of the several places in the file structure.
109   var parseMeta = {
110     ilst: {
111       '©nam': { data: parseDataString.bind(null, 'title') },
112       '©alb': { data: parseDataString.bind(null, 'album') },
113       '©art': { data: parseDataString.bind(null, 'artist') },
114       'covr': { data: parseCovr }
115     },
116     versioned: true
117   };
118
119   // main parser for the entire file structure.
120   return {
121     ftyp: parseFtyp,
122     moov: {
123       mvhd: parseMvhd,
124       trak: {
125         mdia: {
126           hdlr: parseHdlr,
127           minf: {
128             stbl: {
129               stsd: parseStsd
130             }
131           }
132         },
133         meta: parseMeta
134       },
135       udta: {
136         meta: parseMeta
137       },
138       meta: parseMeta
139     },
140     meta: parseMeta
141   };
142 };
143
144 /**
145  *
146  * @param {File} file File.
147  * @param {Object} metadata Metadata.
148  * @param {function(Object)} callback Success callback.
149  * @param {function} onError Error callback.
150  */
151 MpegParser.prototype.parse = function(file, metadata, callback, onError) {
152   this.rootParser_ = MpegParser.createRootParser(metadata);
153
154   // Kick off the processing by reading the first atom's header.
155   this.requestRead(file, 0, MpegParser.HEADER_SIZE, null,
156       onError, callback.bind(null, metadata));
157 };
158
159 /**
160  * @param {function(ByteReader, Object)|Object} parser Parser tree node.
161  * @param {ByteReader} br ByteReader instance.
162  * @param {Object} atom Atom descriptor.
163  * @param {number} filePos File position of the atom start.
164  */
165 MpegParser.prototype.applyParser = function(parser, br, atom, filePos) {
166   if (this.verbose) {
167     var path = atom.name;
168     for (var p = atom.parent; p && p.name; p = p.parent) {
169       path = p.name + '.' + path;
170     }
171
172     var action;
173     if (!parser) {
174       action = 'skipping ';
175     } else if (parser instanceof Function) {
176       action = 'parsing  ';
177     } else {
178       action = 'recursing';
179     }
180
181     var start = atom.start - MpegParser.HEADER_SIZE;
182     this.vlog(path + ': ' +
183               '@' + (filePos + start) + ':' + (atom.end - start),
184               action);
185   }
186
187   if (parser) {
188     if (parser instanceof Function) {
189       br.pushSeek(atom.start);
190       parser(br, atom);
191       br.popSeek();
192     } else {
193       if (parser.versioned) {
194         atom.start += 4;
195       }
196       this.parseMpegAtomsInRange(parser, br, atom, filePos);
197     }
198   }
199 };
200
201 /**
202  * @param {function(ByteReader, Object)|Object} parser Parser tree node.
203  * @param {ByteReader} br ByteReader instance.
204  * @param {Object} parentAtom Parent atom descriptor.
205  * @param {number} filePos File position of the atom start.
206  */
207 MpegParser.prototype.parseMpegAtomsInRange = function(
208     parser, br, parentAtom, filePos) {
209   var count = 0;
210   for (var offset = parentAtom.start; offset != parentAtom.end;) {
211     if (count++ > 100) // Most likely we are looping through a corrupt file.
212       throw 'too many child atoms in ' + parentAtom.name + ' @' + offset;
213
214     br.seek(offset);
215     var size = MpegParser.readAtomSize(br, parentAtom.end);
216     var name = MpegParser.readAtomName(br, parentAtom.end);
217
218     this.applyParser(
219         parser[name],
220         br,
221         { start: offset + MpegParser.HEADER_SIZE,
222           end: offset + size,
223           name: name,
224           parent: parentAtom
225         },
226         filePos
227     );
228
229     offset += size;
230   }
231 };
232
233 /**
234  * @param {File} file File.
235  * @param {number} filePos Start position in the file.
236  * @param {number} size Atom size.
237  * @param {string} name Atom name.
238  * @param {function} onError Error callback.
239  * @param {function} onSuccess Success callback.
240  */
241 MpegParser.prototype.requestRead = function(
242     file, filePos, size, name, onError, onSuccess) {
243   var self = this;
244   var reader = new FileReader();
245   reader.onerror = onError;
246   reader.onload = function(event) {
247     self.processTopLevelAtom(
248         reader.result, file, filePos, size, name, onError, onSuccess);
249   };
250   this.vlog('reading @' + filePos + ':' + size);
251   reader.readAsArrayBuffer(file.slice(filePos, filePos + size));
252 };
253
254 /**
255  * @param {ArrayBuffer} buf Data buffer.
256  * @param {File} file File.
257  * @param {number} filePos Start position in the file.
258  * @param {number} size Atom size.
259  * @param {string} name Atom name.
260  * @param {function} onError Error callback.
261  * @param {function} onSuccess Success callback.
262  */
263 MpegParser.prototype.processTopLevelAtom = function(
264     buf, file, filePos, size, name, onError, onSuccess) {
265   try {
266     var br = new ByteReader(buf);
267
268     // the header has already been read.
269     var atomEnd = size - MpegParser.HEADER_SIZE;
270
271     var bufLength = buf.byteLength;
272
273     // Check the available data size. It should be either exactly
274     // what we requested or HEADER_SIZE bytes less (for the last atom).
275     if (bufLength != atomEnd && bufLength != size) {
276       throw 'Read failure @' + filePos + ', ' +
277           'requested ' + size + ', read ' + bufLength;
278     }
279
280     // Process the top level atom.
281     if (name) { // name is null only the first time.
282       this.applyParser(
283           this.rootParser_[name],
284           br,
285           {start: 0, end: atomEnd, name: name},
286           filePos
287       );
288     }
289
290     filePos += bufLength;
291     if (bufLength == size) {
292       // The previous read returned everything we asked for, including
293       // the next atom header at the end of the buffer.
294       // Parse this header and schedule the next read.
295       br.seek(-MpegParser.HEADER_SIZE, ByteReader.SEEK_END);
296       var nextSize = MpegParser.readAtomSize(br);
297       var nextName = MpegParser.readAtomName(br);
298
299       // If we do not have a parser for the next atom, skip the content and
300       // read only the header (the one after the next).
301       if (!this.rootParser_[nextName]) {
302         filePos += nextSize - MpegParser.HEADER_SIZE;
303         nextSize = MpegParser.HEADER_SIZE;
304       }
305
306       this.requestRead(file, filePos, nextSize, nextName, onError, onSuccess);
307     } else {
308       // The previous read did not return the next atom header, EOF reached.
309       this.vlog('EOF @' + filePos);
310       onSuccess();
311     }
312   } catch (e) {
313     onError(e.toString());
314   }
315 };
316
317 MetadataDispatcher.registerParserClass(MpegParser);