Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / ui / file_manager / 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 /**
6  * @param {MetadataDispatcher} parent Parent object.
7  * @constructor
8  */
9 function MpegParser(parent) {
10   MetadataParser.call(this, parent, 'mpeg', /\.(mp4|m4v|m4a|mpe?g4?)$/i);
11   this.mimeType = 'video/mpeg';
12 }
13
14 MpegParser.prototype = {__proto__: MetadataParser.prototype};
15
16 /**
17  * Size of the atom header.
18  */
19 MpegParser.HEADER_SIZE = 8;
20
21 /**
22  * @param {ByteReader} br ByteReader instance.
23  * @param {number=} opt_end End of atom position.
24  * @return {number} Atom size.
25  */
26 MpegParser.readAtomSize = function(br, opt_end) {
27   var pos = br.tell();
28
29   if (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);
34   }
35
36   var size = br.readScalar(4, false, opt_end);
37
38   if (size < MpegParser.HEADER_SIZE)
39     throw 'atom too short (' + size + ') @' + pos;
40
41   if (opt_end && pos + size > opt_end)
42     throw 'atom too long (' + size + '>' + (opt_end - pos) + ') @' + pos;
43
44   return size;
45 };
46
47 /**
48  * @param {ByteReader} br ByteReader instance.
49  * @param {number=} opt_end End of atom position.
50  * @return {string} Atom name.
51  */
52 MpegParser.readAtomName = function(br, opt_end) {
53   return br.readString(4, opt_end).toLowerCase();
54 };
55
56 /**
57  * @param {Object} metadata Metadata object.
58  * @return {Object} Root of the parser tree.
59  */
60 MpegParser.createRootParser = function(metadata) {
61   function findParentAtom(atom, name) {
62     for (;;) {
63       atom = atom.parent;
64       if (!atom) return null;
65       if (atom.name == name) return atom;
66     }
67   }
68
69   function parseFtyp(br, atom) {
70     metadata.brand = br.readString(4, atom.end);
71   }
72
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;
80   }
81
82   function parseHdlr(br, atom) {
83     br.seek(8, ByteReader.SEEK_CUR);
84     findParentAtom(atom, 'trak').trackType = br.readString(4, atom.end);
85   }
86
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);
93     }
94   }
95
96   function parseDataString(name, br, atom) {
97     br.seek(8, ByteReader.SEEK_CUR);
98     metadata[name] = br.readString(atom.end - br.tell(), atom.end);
99   }
100
101   function parseCovr(br, atom) {
102     br.seek(8, ByteReader.SEEK_CUR);
103     metadata.thumbnailURL = br.readImage(atom.end - br.tell(), atom.end);
104   }
105
106   // 'meta' atom can occur at one of the several places in the file structure.
107   var parseMeta = {
108     ilst: {
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 }
113     },
114     versioned: true
115   };
116
117   // main parser for the entire file structure.
118   return {
119     ftyp: parseFtyp,
120     moov: {
121       mvhd: parseMvhd,
122       trak: {
123         mdia: {
124           hdlr: parseHdlr,
125           minf: {
126             stbl: {
127               stsd: parseStsd
128             }
129           }
130         },
131         meta: parseMeta
132       },
133       udta: {
134         meta: parseMeta
135       },
136       meta: parseMeta
137     },
138     meta: parseMeta
139   };
140 };
141
142 /**
143  *
144  * @param {File} file File.
145  * @param {Object} metadata Metadata.
146  * @param {function(Object)} callback Success callback.
147  * @param {function} onError Error callback.
148  */
149 MpegParser.prototype.parse = function(file, metadata, callback, onError) {
150   var rootParser = MpegParser.createRootParser(metadata);
151
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));
155 };
156
157 /**
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.
162  */
163 MpegParser.prototype.applyParser = function(parser, br, atom, filePos) {
164   if (this.verbose) {
165     var path = atom.name;
166     for (var p = atom.parent; p && p.name; p = p.parent) {
167       path = p.name + '.' + path;
168     }
169
170     var action;
171     if (!parser) {
172       action = 'skipping ';
173     } else if (parser instanceof Function) {
174       action = 'parsing  ';
175     } else {
176       action = 'recursing';
177     }
178
179     var start = atom.start - MpegParser.HEADER_SIZE;
180     this.vlog(path + ': ' +
181               '@' + (filePos + start) + ':' + (atom.end - start),
182               action);
183   }
184
185   if (parser) {
186     if (parser instanceof Function) {
187       br.pushSeek(atom.start);
188       parser(br, atom);
189       br.popSeek();
190     } else {
191       if (parser.versioned) {
192         atom.start += 4;
193       }
194       this.parseMpegAtomsInRange(parser, br, atom, filePos);
195     }
196   }
197 };
198
199 /**
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.
204  */
205 MpegParser.prototype.parseMpegAtomsInRange = function(
206     parser, br, parentAtom, filePos) {
207   var count = 0;
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;
211
212     br.seek(offset);
213     var size = MpegParser.readAtomSize(br, parentAtom.end);
214     var name = MpegParser.readAtomName(br, parentAtom.end);
215
216     this.applyParser(
217         parser[name],
218         br,
219         { start: offset + MpegParser.HEADER_SIZE,
220           end: offset + size,
221           name: name,
222           parent: parentAtom
223         },
224         filePos
225     );
226
227     offset += size;
228   }
229 };
230
231 /**
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.
239  */
240 MpegParser.prototype.requestRead = function(
241     rootParser, file, filePos, size, name, onError, onSuccess) {
242   var self = this;
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,
248         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 {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.
263  */
264 MpegParser.prototype.processTopLevelAtom = function(
265     buf, rootParser, file, filePos, size, name, onError, onSuccess) {
266   try {
267     var br = new ByteReader(buf);
268
269     // the header has already been read.
270     var atomEnd = size - MpegParser.HEADER_SIZE;
271
272     var bufLength = buf.byteLength;
273
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;
279     }
280
281     // Process the top level atom.
282     if (name) { // name is null only the first time.
283       this.applyParser(
284           rootParser[name],
285           br,
286           {start: 0, end: atomEnd, name: name},
287           filePos
288       );
289     }
290
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);
299
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;
305       }
306
307       this.requestRead(rootParser, file, filePos, nextSize, nextName,
308                        onError, onSuccess);
309     } else {
310       // The previous read did not return the next atom header, EOF reached.
311       this.vlog('EOF @' + filePos);
312       onSuccess();
313     }
314   } catch (e) {
315     onError(e.toString());
316   }
317 };
318
319 MetadataDispatcher.registerParserClass(MpegParser);