]> 105106.c2e0p.group Git - movie_parser.git/blobdiff - Atom.js
Added tkhd, mdhd atoms. Added getAtomByType and getAtomsByType.
[movie_parser.git] / Atom.js
diff --git a/Atom.js b/Atom.js
index 50ba17f9fc80e39b7d3087a8279ca30f0b4bb7c1..c7507db2f734e9917fd041d47d459b83d76ed917 100644 (file)
--- a/Atom.js
+++ b/Atom.js
@@ -1,10 +1,63 @@
 var Atom = function(buffer, offset) {
+    this.setDefaults();
+    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':
+        case 'trak':
+        case 'mdia':
+        case 'minf':
+        case 'stbl':
+            return new ContainerAtom(buffer, offset);
+        case 'mvhd':
+            return new MvhdAtom(buffer, offset);
+        case 'tkhd':
+            return new TkhdAtom(buffer, offset);
+        case 'mdhd':
+            return new MdhdAtom(buffer, offset);
+        default:
+            return new Atom(buffer, offset);
+    }
+}
+
+Atom.prototype.setDefaults = function()
+{
+    Object.defineProperty(this, "is64bit", { 
+            value: false,
+            writable: true,
+            enumerable: false,
+            configurable: true,
+        });
+    Object.defineProperty(this, "minimumSize", { 
+            value: 8,
+            writable: true,
+            enumerable: false,
+            configurable: true,
+        });
+    Object.defineProperty(this, "parent", { 
+            value: null,
+            writable: true,
+            enumerable: false,
+            configurable: true,
+        });
     this.size = 0;
     this.type = '';
-    this.childAtoms = [];
-    
-    return this.parse(buffer, offset)
-};
+}
 
 Atom.prototype.parse = function(buffer, offset)
 {
@@ -14,13 +67,403 @@ 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.getAtomByType = function(type) {
+    if (typeof(this.childAtoms) == 'undefined')
+        return null;
+
+    // Bredth first
+    for (index in this.childAtoms) {
+        if (this.childAtoms[index].type == type)
+            return this.childAtoms[index];
+    }
+
+    var result = null;
+    for (index in this.childAtoms) {
+        if (result = this.childAtoms[index].getAtomsByType(type))
+            break;
+    }
+    return result;
+};
+
+Atom.prototype.getAtomsByType = function(type) {
+    if (typeof(this.childAtoms) == 'undefined')
+        return [];
+
+    var result = [];
+
+    // Bredth first
+    for (index in this.childAtoms) {
+        if (this.childAtoms[index].type == type)
+            result.push(this.childAtoms[index]);
+    }
+
+    for (index in this.childAtoms)
+        result = result.concat(this.childAtoms[index].getAtomsByType(type));
+
+    return result;
+};
+
+var FtypAtom = function(buffer, offset) {
+    return Atom.prototype.constructor.call(this, buffer, offset);
+}
+
+FtypAtom.prototype = Object.create(Atom.prototype);
+
+FtypAtom.prototype.setDefaults = function() {
+    Atom.prototype.setDefaults.call(this);
+    this.minimumSize = 16;
+    this.brand = "";
+    this.version = 0;
+    this.compatible_brands = [];
+}
+
+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) {
+    return Atom.prototype.constructor.call(this, buffer, offset);
+}
+
+ContainerAtom.prototype = Object.create(Atom.prototype);
+
+ContainerAtom.prototype.setDefaults = function()
+{
+    Atom.prototype.setDefaults.call(this);
+    this.childAtoms = [];
+}
+
+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);
+        childAtom.parent = this;
+    }
+}
+
+var MvhdAtom = function(buffer, offset) {
+    return Atom.prototype.constructor.call(this, buffer, offset);
+}
+
+MvhdAtom.prototype = Object.create(Atom.prototype);
+
+MvhdAtom.prototype.setDefaults = function() {
+    Atom.prototype.setDefaults.call(this);
+    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;
+}
+
+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 = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
+    headerOffset += 4;
+
+    this.modificationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
+    headerOffset += 4;
+
+    this.timeScale = view.getUint32(headerOffset);
+    headerOffset += 4;
+    
+    this.duration  = view.getUint32(headerOffset);
+    headerOffset += 4;
+    
+    this.preferredRate = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+
+    this.preferredVolume = view.getUint16(headerOffset) / (1 << 8);
+    headerOffset += 2;
+
+    // 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][0] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.movieMatrix[0][1] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.movieMatrix[0][2] = view.getUint32(headerOffset) / (1 << 30);
+    headerOffset += 4;
+
+    // c, d, v:
+    this.movieMatrix[1] = new Array(3);
+    this.movieMatrix[1][0] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.movieMatrix[1][1] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.movieMatrix[1][2] = view.getUint32(headerOffset) / (1 << 30);
+    headerOffset += 4;
+
+    // x, y, w:
+    this.movieMatrix[2] = new Array(3);
+    this.movieMatrix[2][0] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.movieMatrix[2][1] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.movieMatrix[2][2] = 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;
+}
+
+var TkhdAtom = function(buffer, offset) {
+    return Atom.prototype.constructor.call(this, buffer, offset);
+}
+
+TkhdAtom.prototype = Object.create(Atom.prototype);
+
+TkhdAtom.prototype.setDefaults = function() {
+    Atom.prototype.setDefaults.call(this);
+    
+    this.version = 0;
+    this.flags = 0;
+    this.creationTime = 0;
+    this.modificationTime = 0;
+    this.trackID = 0;
+    this.duration = 0;
+    this.layer = 0;
+    this.alternateGroup = 0;
+    this.volume = 0.0;
+    this.trackMatrix = [];
+    this.width = 0;
+    this.height = 0;
+}
+
+TkhdAtom.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 += 1;
+    
+    // 'flags' is a 3-byte field, so retrieve from one extra byte and concatenate
+    this.flags = (view.getUint8(headerOffset) << 8) + view.getUint16(headerOffset + 1);
+    headerOffset += 3;
+    
+    this.creationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
+    headerOffset += 4;
+
+    this.modificationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
+    headerOffset += 4;
+
+    this.trackID = view.getUint32(headerOffset);
+    headerOffset += 4;
+    
+    // Reserved
+    // A 32-bit integer that is reserved for use by Apple. Set this field to 0.
+    headerOffset += 4;
+
+    this.duration = view.getUint32(headerOffset);
+    headerOffset += 4;
+    
+    // Reserved
+    // An 8-byte value that is reserved for use by Apple. Set this field to 0.
+    headerOffset += 8;
+    
+    this.layer = view.getUint16(headerOffset);
+    headerOffset += 2;
+    
+    this.alternateGroup = view.getUint16(headerOffset);
+    headerOffset += 2;
+
+    this.volume = view.getUint16(headerOffset) / (1 << 8);
+    headerOffset += 2;
+    
+    // Reserved
+    // A 16-bit integer that is reserved for use by Apple. Set this field to 0.
+    headerOffset += 2;
+    
+    this.trackMatrix = new Array(3);
+    // a, b, u:
+    this.trackMatrix[0] = new Array(3);
+    this.trackMatrix[0][0] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.trackMatrix[0][1] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.trackMatrix[0][2] = view.getUint32(headerOffset) / (1 << 30);
+    headerOffset += 4;
+
+    // c, d, v:
+    this.trackMatrix[1] = new Array(3);
+    this.trackMatrix[1][0] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.trackMatrix[1][1] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.trackMatrix[1][2] = view.getUint32(headerOffset) / (1 << 30);
+    headerOffset += 4;
+
+    // x, y, w:
+    this.trackMatrix[2] = new Array(3);
+    this.trackMatrix[2][0] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.trackMatrix[2][1] = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    this.trackMatrix[2][2] = view.getUint32(headerOffset) / (1 << 30);
+    headerOffset += 4;
+    
+    this.width = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+    
+    this.height = view.getUint32(headerOffset) / (1 << 16);
+    headerOffset += 4;
+}
+
+var MdhdAtom = function(buffer, offset) {
+    return Atom.prototype.constructor.call(this, buffer, offset);
+}
+
+MdhdAtom.prototype = Object.create(Atom.prototype);
+
+MdhdAtom.prototype.setDefaults = function() {
+    Atom.prototype.setDefaults.call(this);
+    
+    this.version = 0;
+    this.flags = 0;
+    this.creationTime = 0;
+    this.modificationTime = 0;
+    this.timeScale = 0;
+    this.duration = 0;
+    this.language = 0;
+    this.quality = 0;
+}
+
+MdhdAtom.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 += 1;
+    
+    // 'flags' is a 3-byte field, so retrieve from one extra byte and concatenate
+    this.flags = (view.getUint8(headerOffset) << 8) + view.getUint16(headerOffset + 1);
+    headerOffset += 3;
+    
+    this.creationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
+    headerOffset += 4;
+
+    this.modificationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
+    headerOffset += 4;
+
+    this.timeScale = view.getUint32(headerOffset);
+    headerOffset += 4;
+    
+    this.duration = view.getUint32(headerOffset);
+    headerOffset += 4;
+        
+    this.language = view.getUint16(headerOffset);
+    headerOffset += 2;
     
-    return this;
-};
\ No newline at end of file
+    this.quality = view.getUint16(headerOffset);
+    headerOffset += 2;
+}
\ No newline at end of file