]> id.pley.net Git - movie_parser.git/commitdiff
Support ftyp, moov, and mhdr atoms.
authorJer Noble <jer.noble@apple.com>
Thu, 6 Dec 2012 23:58:44 +0000 (15:58 -0800)
committerJer Noble <jer.noble@apple.com>
Thu, 6 Dec 2012 23:58:44 +0000 (15:58 -0800)
Atom.js
AtomTester.html

diff --git a/Atom.js b/Atom.js
index 50ba17f9fc80e39b7d3087a8279ca30f0b4bb7c1..52ee673e5d14ff65b9fd1d3755aa7a1619f7e6b9 100644 (file)
--- a/Atom.js
+++ b/Atom.js
@@ -1,11 +1,36 @@
 var Atom = function(buffer, offset) {
+    this.is64bit = false;
     this.size = 0;
+    this.minimumSize;
     this.type = '';
-    this.childAtoms = [];
     
-    return this.parse(buffer, offset)
+    return this.parse(buffer, offset) ? this : null;
 };
 
+Atom.create = function(buffer, offset)
+{
+    // 'offset' is optional.
+    if (arguments.length < 2)
+        offset = 0;
+
+    if (buffer.byteLength - offset < this.minimumSize)
+        return null;
+
+    var typeArrayView = new Uint8Array(buffer, offset + 4, 4);
+    var type = String.fromCharCode.apply(null, typeArrayView);
+    
+    switch (type) {
+        case 'ftyp':
+            return new FtypAtom(buffer, offset);
+        case 'moov':
+            return new ContainerAtom(buffer, offset);
+        case 'mvhd':
+            return new MvhdAtom(buffer, offset);
+        default:
+            return new Atom(buffer, offset);
+    }
+}
+
 Atom.prototype.parse = function(buffer, offset)
 {
     // 'offset' is optional.
@@ -14,13 +39,233 @@ Atom.prototype.parse = function(buffer, offset)
 
     // Atoms are 8 bytes minimum.
     if (buffer.byteLength - offset < 8)
-        return null;
+        return false;
 
     var view = new DataView(buffer, offset, 4);
+    offset += 4;
     this.size = view.getUint32(0);
 
-    var typeArrayView = new Uint8Array(buffer, offset + 4, 4);
+    var typeArrayView = new Uint8Array(buffer, offset, 4);
+    offset += 4;
     this.type = String.fromCharCode.apply(null, typeArrayView);
+
+    if (this.size == 1) {
+        this.is64bit = true;
+        if (buffer.byteLength - offset < 8)
+            return false;
+
+        // NOTE: JavaScript can only represent up to 2^53 as precise integer. 
+        // This calculation may result in incorrect values.
+        var view = new DataView(buffer, offset, 8);
+        offset += 8;
+        var upper = view.getUint32(0);
+        var lower = view.getUint32(4);
+        this.size = (upper << 32)  + lower;
+    }
+    
+    return true;
+};
+
+Atom.prototype.toDOMNode = function()
+{
+    var output = document.createElement('dl');
+    
+    for (property in this) {
+        var value = this[property];
+        if (typeof(value) == 'function')
+            continue;
+        var div = document.createElement('div');
+        var dt = document.createElement('dt');
+        dt.innerText = property;
+        
+        var dd = document.createElement('dd');
+        if (value instanceof Atom)
+            dd.appendChild(value.toDOMNode());
+        else if (value instanceof Array) {
+            var ol = document.createElement('ol');
+            for (index in value) {
+                var li = document.createElement('li');
+                li.value = index;
+                if (value[index] instanceof Atom)
+                    li.appendChild(value[index].toDOMNode())
+                else
+                    li.innerText = value[index];
+                ol.appendChild(li);
+            }
+            dd.appendChild(ol);
+        }
+        else if (typeof(value) == "string")
+            dd.innerText = '"' + value + '"';
+        else
+            dd.innerText = value;
+        
+        div.appendChild(dt);
+        div.appendChild(dd);
+        output.appendChild(div);
+    }
+    return output;
+}
+
+var FtypAtom = function(buffer, offset) {
+    this.minimumSize = 16;
+    this.brand = "";
+    this.version = 0;
+    this.compatible_brands = [];
+    
+    return Object.getPrototypeOf(FtypAtom.prototype).constructor.call(this, buffer, offset)
+}
+
+FtypAtom.prototype = Object.create(Atom.prototype);
+
+FtypAtom.prototype.parse = function(buffer, offset) {
+    if (!Atom.prototype.parse.call(this, buffer, offset))
+        return false;
+
+    var begin = offset;
+    var end = begin + this.size;
+    offset += this.is64bit ? 16 : 8;
+
+    var brandArrayView = new Uint8Array(buffer, offset, 4);
+    offset += 4;
+    this.brand = String.fromCharCode.apply(null, brandArrayView);
+
+    var view = new DataView(buffer, offset, 4);
+    offset += 4;
+    this.version = view.getUint32(0);
+
+    while (offset <= end - 4) {
+        var brandArrayView = new Uint8Array(buffer, offset, 4);
+        offset += 4;
+        this.compatible_brands.push(String.fromCharCode.apply(null, brandArrayView));
+    }
+    
+    return true;
+}
+
+var ContainerAtom = function(buffer, offset) {
+    this.childAtoms = [];
+
+    return Object.getPrototypeOf(ContainerAtom.prototype).constructor.call(this, buffer, offset);
+}
+
+ContainerAtom.prototype = Object.create(Atom.prototype);
+
+ContainerAtom.prototype.parse = function(buffer, offset) {
+    if (!Atom.prototype.parse.call(this, buffer, offset))
+        return false;
+
+    var begin = offset;
+    var end = begin + this.size;
+    offset += this.is64bit ? 16 : 8;
+
+    while (offset < end) {
+        var childAtom = Atom.create(buffer, offset);
+        if (!childAtom)
+            break;
+        offset += childAtom.size;
+        this.childAtoms.push(childAtom);
+    }
+}
+
+var MvhdAtom = function(buffer, offset) {
+    this.version = 0;
+    this.creationTime = 0;
+    this.modificationTime = 0;
+    this.timeScale = 0;
+    this.duration = 0;
+    this.preferredRate = 0.0;
+    this.preferredVolume = 0.0;
+    this.movieMatrix = [[]];
+    this.previewTime = 0;
+    this.posterTime = 0;
+    this.selectionTime = 0;
+    this.selectionDuration = 0;
+    this.nextTrackID = 0;
+    
+    return Object.getPrototypeOf(MvhdAtom.prototype).constructor.call(this, buffer, offset);
+}
+
+MvhdAtom.prototype = Object.create(Atom.prototype);
+
+MvhdAtom.prototype.parse = function(buffer, offset) {
+    if (!Atom.prototype.parse.call(this, buffer, offset))
+        return false;
+
+    offset += this.is64bit ? 16 : 8;
+
+    var headerOffset = 0;
+    var view = new DataView(buffer, offset);
+
+    this.version = view.getUint8(headerOffset);
+    headerOffset += 4;
+
+    this.creationTime = view.getUint32(headerOffset);
+    headerOffset += 4;
+
+    this.modificationTime = view.getUint32(headerOffset);
+    headerOffset += 4;
+
+    this.timeScale = view.getUint32(headerOffset);
+    headerOffset += 4;
     
-    return this;
-};
\ No newline at end of file
+    this.duration  = view.getUint32(headerOffset);
+    headerOffset += 4;
+    
+    this.preferredRate = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+
+    this.preferredVolume = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+
+    // Reserved
+    // Ten bytes reserved for use by Apple. Set to 0.
+    headerOffset += 10;
+
+    this.movieMatrix = new Array(3);
+    // a, b, u:
+    this.movieMatrix[0] = new Array(3);
+    this.movieMatrix[0][1] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.movieMatrix[0][2] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.movieMatrix[0][3] = view.getUint32(headerOffset) / (1 << 30);
+    headerOffset += 4;
+
+    // c, d, v:
+    this.movieMatrix[1] = new Array(3);
+    this.movieMatrix[1][1] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.movieMatrix[1][2] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.movieMatrix[1][3] = view.getUint32(headerOffset) / (1 << 30);
+    headerOffset += 4;
+
+    // x, y, w:
+    this.movieMatrix[2] = new Array(3);
+    this.movieMatrix[2][1] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.movieMatrix[2][2] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.movieMatrix[2][3] = view.getUint32(headerOffset) / (1 << 30);
+    headerOffset += 4;
+
+    this.previewTime = view.getUint32(headerOffset);
+    headerOffset += 4;
+    
+    this.previewDuration = view.getUint32(headerOffset);
+    headerOffset += 4;
+    
+    this.posterTime = view.getUint32(headerOffset);
+    headerOffset += 4;
+    
+    this.selectionTime = view.getUint32(headerOffset);
+    headerOffset += 4;
+    
+    this.selectionDuration = view.getUint32(headerOffset);
+    headerOffset += 4;
+    
+    this.nextTrackID = view.getUint32(headerOffset);
+    headerOffset += 4;
+
+    return true;
+}
\ No newline at end of file
index 789e24633336267030f319cbd398e8787206729f..82d9be2727d03348c6799746eb4012bf8201a5c8 100644 (file)
             reader.onload = (function(file) {
                 return function(e) {
                     var offset = 0;
+                    var output = document.getElementById('output');
+                    output.innerHTML = '';
                     while (offset < e.target.result.byteLength) {
-                        var atom = new Atom(e.target.result, offset);
+                        var atom = Atom.create(e.target.result, offset);
                         if (!atom)
                             break;
                         atoms.push(atom);
+                        output.appendChild(atom.toDOMNode());
                         offset += atom.size;
                     }
                 };
             document.getElementById('file').addEventListener('change', onFileSelect, false);
         }
     </script>
+    <style>
+        dl { border: 1px solid black; }
+        dt, dd { display: inline; }
+        dt:after { content:": " }
+        dd:after { content:"\A"; white-space:pre; }
+        dd ol { display: inline-block; margin: 0; vertical-align: top }
+        
+        /* make the output display as a table */
+        dl { display: table; }
+        dl div { display: table-row; }
+        dt, dd { display: table-cell; }
+    </style>
 </head>
 <body id="atomtester" onload="setup()">
     <input type="file" id="file" name="file" />
+    <div id=output>
 </body>
\ No newline at end of file