]> id.pley.net Git - movie_parser.git/commitdiff
Add new atom types: SampleSize, TrackExtends, OriginalFormat, SchemeType, and SchemeInfo.
authorJer Noble <jer.noble@apple.com>
Thu, 27 Mar 2014 18:46:45 +0000 (11:46 -0700)
committerJer Noble <jer.noble@apple.com>
Thu, 27 Mar 2014 18:46:45 +0000 (11:46 -0700)
Atom.js
AtomTester.html

diff --git a/Atom.js b/Atom.js
index 2fc62ef76f9cb86fb6b7fab005807e935e8c5379..8a73803fd4bc2d5a0f21c0c50958f80f1f1f7370 100644 (file)
--- a/Atom.js
+++ b/Atom.js
@@ -1,26 +1,34 @@
-var Atom = function(buffer, offset) {
+var Atom = function (buffer, offset) {
     this.setDefaults();
     return this.parse(buffer, offset) ? this : null;
 };
 
-Atom.create = function(buffer, offset) {
+Atom.create = function (buffer, offset) {
     // 'offset' is optional.
-    if (arguments.length < 2)
+    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);
+    var atom;
 
     switch (type) {
         case 'ftyp':
             return new FileTypeAtom(buffer, offset);
         case 'moov':
+            return new ContainerAtom(buffer, offset, 'Movie Atom');
         case 'trak':
+            return new ContainerAtom(buffer, offset, 'Track Atom');
         case 'mdia':
+            return new ContainerAtom(buffer, offset, 'Media Atom');
         case 'minf':
+            return new ContainerAtom(buffer, offset, 'Media Info Atom');
+        case 'mvex':
+            return new ContainerAtom(buffer, offset, 'Movie Extends Atom');
         case 'stbl':
             return new ContainerAtom(buffer, offset);
         case 'mvhd':
@@ -33,16 +41,30 @@ Atom.create = function(buffer, offset) {
             return new SyncSampleAtom(buffer, offset);
         case 'stts':
             return new TimeToSampleAtom(buffer, offset);
+        case 'stsz':
+            return new SampleSizeAtom(buffer, offset);
+        case 'trex':
+            return new TrackExtendsAtom(buffer, offset);
+        case 'sinf':
+            return new ContainerAtom(buffer, offset, 'Protection Scheme Info Atom');
+        case 'ipro':
+            return new ContainerAtom(buffer, offset, 'Item Protection Atom');
+        case 'frma':
+            return new OriginalFormatBox(buffer, offset);
+        case 'schm':
+            return new SchemeTypeBox(buffer, offset);
+        case 'schi':
+            return new SchemeInfoBox(Buffer, offset);
         default:
             return new Atom(buffer, offset);
     }
-}
+};
 
-Atom.prototype.super = function(object) {
+Atom.prototype.super = function (object) {
     return Object.getPrototypeOf(object.prototype);
-}
+};
 
-Atom.prototype.setDefaults = function() {
+Atom.prototype.setDefaults = function () {
     Object.defineProperty(this, "is64bit", {
             value: false,
             writable: true,
@@ -61,20 +83,29 @@ Atom.prototype.setDefaults = function() {
             enumerable: false,
             configurable: true,
         });
+    Object.defineProperty(this, "description", {
+            value: "Undifferentiated Atom",
+            writable: true,
+            enumerable: false,
+            configurable: true,
+        });
+    this.offset = 0;
     this.size = 0;
     this.type = '';
-}
+};
 
-Atom.prototype.parse = function(buffer, offset) {
+Atom.prototype.parse = function (buffer, offset) {
     // 'offset' is optional.
     if (typeof(offset) == 'undefined')
         offset = 0;
 
+    this.offset = offset;
+
     if (buffer.byteLength - offset < this.minimumSize)
-        return 0;
+        throw 'Buffer not long enough';
 
     var view = new DataView(buffer, offset, 4);
-    headerOffset = 0;
+    var headerOffset = 0;
 
     this.size = view.getUint32(0);
     headerOffset += 4;
@@ -86,7 +117,7 @@ Atom.prototype.parse = function(buffer, offset) {
     if (this.size == 1) {
         this.is64bit = true;
         if (buffer.byteLength - offset < 8)
-            return false;
+            throw 'Malformed extended size field';
 
         // NOTE: JavaScript can only represent up to 2^53 as precise integer.
         // This calculation may result in incorrect values.
@@ -100,61 +131,59 @@ Atom.prototype.parse = function(buffer, offset) {
     return headerOffset;
 };
 
-Atom.prototype.getAtomByType = function(type) {
+Atom.prototype.getAtomByType = function (type) {
     if (typeof(this.childAtoms) == 'undefined')
         return null;
 
     // Bredth first
-    for (index in this.childAtoms) {
+    for (var 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))
+    for (var index in this.childAtoms) {
+        if (result = this.childAtoms[index].getAtomByType(type))
             break;
     }
     return result;
 };
 
-Atom.prototype.getAtomsByType = function(type) {
+Atom.prototype.getAtomsByType = function (type) {
     if (typeof(this.childAtoms) == 'undefined')
         return [];
 
     var result = [];
 
     // Bredth first
-    for (index in this.childAtoms) {
+    for (var index in this.childAtoms) {
         if (this.childAtoms[index].type == type)
             result.push(this.childAtoms[index]);
     }
 
-    for (index in this.childAtoms)
+    for (var index in this.childAtoms)
         result = result.concat(this.childAtoms[index].getAtomsByType(type));
 
     return result;
 };
 
-var FileTypeAtom = function(buffer, offset) {
+var FileTypeAtom = function (buffer, offset) {
     this.super(FileTypeAtom).constructor.call(this, buffer, offset);
 }
 
 FileTypeAtom.prototype = Object.create(Atom.prototype);
 
-FileTypeAtom.prototype.setDefaults = function() {
+FileTypeAtom.prototype.setDefaults = function () {
     this.super(FileTypeAtom).setDefaults.call(this);
+    this.description = "File Type Atom";
     this.minimumSize = 16;
     this.brand = "";
     this.version = 0;
     this.compatible_brands = [];
 }
 
-FileTypeAtom.prototype.parse = function(buffer, offset) {
+FileTypeAtom.prototype.parse = function (buffer, offset) {
     var headerOffset = this.super(FileTypeAtom).parse.call(this, buffer, offset);
-    if (!headerOffset)
-        return 0;
-
     var view = new DataView(buffer, offset, this.size);
 
     var brandArrayView = new Uint8Array(buffer, offset + headerOffset, 4);
@@ -173,22 +202,21 @@ FileTypeAtom.prototype.parse = function(buffer, offset) {
     return true;
 }
 
-var ContainerAtom = function(buffer, offset) {
-    this.super(ContainerAtom).constructor.call(this, buffer, offset);
+var ContainerAtom = function (buffer, offset, description) {
+    var atom = this.super(ContainerAtom).constructor.call(this, buffer, offset);
+    atom.description = description;
+    return;
 }
 
 ContainerAtom.prototype = Object.create(Atom.prototype);
 
-ContainerAtom.prototype.setDefaults = function() {
+ContainerAtom.prototype.setDefaults = function () {
     this.super(ContainerAtom).setDefaults.call(this);
     this.childAtoms = [];
 }
 
-ContainerAtom.prototype.parse = function(buffer, offset) {
+ContainerAtom.prototype.parse = function (buffer, offset) {
     var headerOffset = this.super(ContainerAtom).parse.call(this, buffer, offset);
-    if (!headerOffset)
-        return 0;
-
     while (headerOffset < this.size) {
         var childAtom = Atom.create(buffer, offset + headerOffset);
         if (!childAtom)
@@ -197,25 +225,23 @@ ContainerAtom.prototype.parse = function(buffer, offset) {
         this.childAtoms.push(childAtom);
         childAtom.parent = this;
     }
+       return headerOffset;
 }
 
-var VersionFlagsAtom = function(buffer, offset) {
+var VersionFlagsAtom = function (buffer, offset) {
     this.super(VersionFlagsAtom).constructor.call(this, buffer, offset);
 }
 
 VersionFlagsAtom.prototype = Object.create(Atom.prototype);
 
-VersionFlagsAtom.prototype.setDefaults = function() {
+VersionFlagsAtom.prototype.setDefaults = function () {
     this.super(VersionFlagsAtom).setDefaults.call(this);
     this.version = 0;
     this.flags = 0;
 }
 
-VersionFlagsAtom.prototype.parse = function(buffer, offset) {
+VersionFlagsAtom.prototype.parse = function (buffer, offset) {
     var headerOffset = this.super(VersionFlagsAtom).parse.call(this, buffer, offset);
-    if (!headerOffset)
-        return 0;
-
     var view = new DataView(buffer, offset);
 
     this.version = view.getUint8(headerOffset);
@@ -228,14 +254,15 @@ VersionFlagsAtom.prototype.parse = function(buffer, offset) {
     return headerOffset;
 }
 
-var MovieHeaderAtom = function(buffer, offset) {
+var MovieHeaderAtom = function (buffer, offset) {
     return this.super(MovieHeaderAtom).constructor.call(this, buffer, offset);
 }
 
 MovieHeaderAtom.prototype = Object.create(VersionFlagsAtom.prototype);
 
-MovieHeaderAtom.prototype.setDefaults = function() {
+MovieHeaderAtom.prototype.setDefaults = function () {
     this.super(MovieHeaderAtom).setDefaults.call(this);
+    this.description = "Movie Header Atom";
     this.creationTime = 0;
     this.modificationTime = 0;
     this.timeScale = 0;
@@ -250,11 +277,8 @@ MovieHeaderAtom.prototype.setDefaults = function() {
     this.nextTrackID = 0;
 }
 
-MovieHeaderAtom.prototype.parse = function(buffer, offset) {
+MovieHeaderAtom.prototype.parse = function (buffer, offset) {
     var headerOffset = this.super(MovieHeaderAtom).parse.call(this, buffer, offset);
-    if (!headerOffset)
-        return 0;
-
     var view = new DataView(buffer, offset);
 
     this.creationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
@@ -328,15 +352,16 @@ MovieHeaderAtom.prototype.parse = function(buffer, offset) {
     return true;
 }
 
-var TrackHeaderAtom = function(buffer, offset) {
+var TrackHeaderAtom = function (buffer, offset) {
     this.super(TrackHeaderAtom).constructor.call(this, buffer, offset);
 }
 
 TrackHeaderAtom.prototype = Object.create(VersionFlagsAtom.prototype);
 
-TrackHeaderAtom.prototype.setDefaults = function() {
+TrackHeaderAtom.prototype.setDefaults = function () {
     this.super(TrackHeaderAtom).setDefaults.call(this);
 
+    this.description = "Track Header Atom";
     this.creationTime = 0;
     this.modificationTime = 0;
     this.trackID = 0;
@@ -349,11 +374,8 @@ TrackHeaderAtom.prototype.setDefaults = function() {
     this.height = 0;
 }
 
-TrackHeaderAtom.prototype.parse = function(buffer, offset) {
+TrackHeaderAtom.prototype.parse = function (buffer, offset) {
     var headerOffset = this.super(TrackHeaderAtom).parse.call(this, buffer, offset);
-    if (!headerOffset)
-        return 0;
-
     var view = new DataView(buffer, offset);
 
     this.creationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
@@ -424,15 +446,16 @@ TrackHeaderAtom.prototype.parse = function(buffer, offset) {
     headerOffset += 4;
 }
 
-var MediaHeaderAtom = function(buffer, offset) {
+var MediaHeaderAtom = function (buffer, offset) {
     this.super(MediaHeaderAtom).constructor.call(this, buffer, offset);
 }
 
 MediaHeaderAtom.prototype = Object.create(VersionFlagsAtom.prototype);
 
-MediaHeaderAtom.prototype.setDefaults = function() {
+MediaHeaderAtom.prototype.setDefaults = function () {
     this.super(MediaHeaderAtom).setDefaults.call(this);
 
+    this.description = "Media Header Atom";
     this.creationTime = 0;
     this.modificationTime = 0;
     this.timeScale = 0;
@@ -441,11 +464,8 @@ MediaHeaderAtom.prototype.setDefaults = function() {
     this.quality = 0;
 }
 
-MediaHeaderAtom.prototype.parse = function(buffer, offset) {
+MediaHeaderAtom.prototype.parse = function (buffer, offset) {
     var headerOffset = this.super(MediaHeaderAtom).parse.call(this, buffer, offset);
-    if (!headerOffset)
-        return 0;
-
     var view = new DataView(buffer, offset);
 
     this.creationTime = new Date(view.getUint32(headerOffset)*1000 + Date.UTC(1904, 0, 1));
@@ -465,28 +485,26 @@ MediaHeaderAtom.prototype.parse = function(buffer, offset) {
 
     this.quality = view.getUint16(headerOffset);
     headerOffset += 2;
-}
+};
 
-var SyncSampleAtom = function(buffer, offset) {
+var SyncSampleAtom = function (buffer, offset) {
     this.super(SyncSampleAtom).constructor.call(this, buffer, offset);
-}
+};
 
 SyncSampleAtom.prototype = Object.create(Atom.prototype);
 
-SyncSampleAtom.prototype.setDefaults = function() {
+SyncSampleAtom.prototype.setDefaults = function () {
     this.super(SyncSampleAtom).setDefaults.call(this);
 
+    this.description = "Sync Sample Atom";
     this.version = 0;
     this.flags = 0;
     this.entries = 0;
     this.syncSamples = [];
-}
+};
 
-SyncSampleAtom.prototype.parse = function(buffer, offset) {
+SyncSampleAtom.prototype.parse = function (buffer, offset) {
     var headerOffset = this.super(SyncSampleAtom).parse.call(this, buffer, offset);
-    if (!headerOffset)
-        return 0;
-
     var view = new DataView(buffer, offset);
 
     this.version = view.getUint8(headerOffset);
@@ -499,41 +517,45 @@ SyncSampleAtom.prototype.parse = function(buffer, offset) {
     this.entries = view.getUint32(headerOffset);
     headerOffset += 4;
 
+    this.syncSamples = new Uint32Array(this.entries);
+    var i = 0;
     while (headerOffset < this.size) {
         var sampleNumber = view.getUint32(headerOffset);
         headerOffset += 4;
-        this.syncSamples.push(sampleNumber);
+        this.syncSamples[i] = sampleNumber;
+        ++i;
     }
-}
+};
 
-var TimeToSampleAtom = function(buffer, offset) {
+var TimeToSampleAtom = function (buffer, offset) {
     this.super(TimeToSampleAtom).constructor.call(this, buffer, offset);
-}
+};
 
 TimeToSampleAtom.prototype = Object.create(VersionFlagsAtom.prototype);
 
-TimeToSampleAtom.prototype.setDefaults = function() {
+TimeToSampleAtom.prototype.setDefaults = function () {
     this.super(TimeToSampleAtom).setDefaults.call(this);
 
+    this.description = "Time-to-Sample Atom";
     this.entries = 0;
     
     Object.defineProperty(this, "timeToSamples", {
-        value: [],
+        value: null,
         writable: true,
         enumerable: false,
         configurable: true,
     });
 }
 
-TimeToSampleAtom.prototype.parse = function(buffer, offset) {
+TimeToSampleAtom.prototype.parse = function (buffer, offset) {
     var headerOffset = this.super(TimeToSampleAtom).parse.call(this, buffer, offset);
-    if (!headerOffset)
-        return 0;
-
     var view = new DataView(buffer, offset);
 
     this.entries = view.getUint32(headerOffset);
-        headerOffset += 4;
+    headerOffset += 4;
+
+    this.timeToSamples = new Array(this.entries);
+    var i = 0;
 
     while (headerOffset < this.size) {
         var sampleCount = view.getUint32(headerOffset);
@@ -542,6 +564,155 @@ TimeToSampleAtom.prototype.parse = function(buffer, offset) {
         var sampleDuration = view.getUint32(headerOffset);
         headerOffset += 4;
 
-        this.timeToSamples.push([sampleCount, sampleDuration]);
+        this.timeToSamples[i] = [sampleCount, sampleDuration];
+        ++i;
+    }
+}
+
+var SampleSizeAtom = function (buffer, offset) {
+    this.super(SampleSizeAtom).constructor.call(this, buffer, offset);
+}
+
+SampleSizeAtom.prototype = Object.create(VersionFlagsAtom.prototype);
+
+SampleSizeAtom.prototype.setDefaults = function () {
+    this.super(SampleSizeAtom).setDefaults.call(this);
+
+    this.description = "Sample Size Atom";
+    this.sampleSize = 0;
+    this.entries = 0;
+    
+    Object.defineProperty(this, "sampleSizes", {
+        value: null,
+        writable: true,
+        enumerable: false,
+        configurable: true,
+    });
+}
+
+SampleSizeAtom.prototype.parse = function (buffer, offset) {
+    var headerOffset = this.super(SampleSizeAtom).parse.call(this, buffer, offset);
+    var view = new DataView(buffer, offset);
+
+    this.sampleSize = view.getUint32(headerOffset);
+    headerOffset += 4;
+
+    this.entries = view.getUint32(headerOffset);
+    headerOffset += 4;
+
+    this.sampleSizes = new Uint32Array(this.entries);
+    var i = 0;
+
+    while (headerOffset < this.size) {
+        this.sampleSizes[i] = view.getUint32(headerOffset);
+        headerOffset += 4;
+        ++i;
+    }
+}
+
+var TrackExtendsAtom = function (buffer, offset) {
+    this.super(TrackExtendsAtom).constructor.call(this, buffer, offset);
+}
+
+TrackExtendsAtom.prototype = Object.create(VersionFlagsAtom.prototype);
+
+TrackExtendsAtom.prototype.setDefaults = function () {
+    this.super(TrackExtendsAtom).setDefaults.call(this);
+
+    this.description = "Track Extends Atom";
+    this.trackID = 0;
+    this.default_sample_description_index = 0;
+    this.default_sample_duration = 0;
+    this.default_sample_size = 0;
+    this.default_sample_flags = 0;
+}
+
+TrackExtendsAtom.prototype.parse = function (buffer, offset) {
+    var headerOffset = this.super(TrackExtendsAtom).parse.call(this, buffer, offset);
+    var view = new DataView(buffer, offset);
+
+    this.trackID = view.getUint32(headerOffset);
+    headerOffset += 4;
+
+    this.default_sample_description_index = view.getUint32(headerOffset);
+    headerOffset += 4;
+
+    this.default_sample_duration = view.getUint32(headerOffset);
+    headerOffset += 4;
+
+    this.default_sample_size = view.getUint32(headerOffset);
+    headerOffset += 4;
+
+    this.default_sample_flags = view.getUint32(headerOffset);
+    headerOffset += 4;
+}
+
+var OriginalFormatBox = function (buffer, offset) {
+    this.super(OriginalFormatBox).constructor.call(this, buffer, offset);
+}
+
+OriginalFormatBox.prototype = Object.create(Atom.prototype);
+
+OriginalFormatBox.prototype.setDefaults = function () {
+    this.super(OriginalFormatBox).setDefaults.call(this);
+
+    this.description = "Original Format Box";
+    this.dataFormat = 0;
+}
+
+OriginalFormatBox.prototype.parse = function (buffer, offset) {
+    var headerOffset = this.super(OriginalFormatBox).parse.call(this, buffer, offset);
+    var view = new DataView(buffer, offset);
+
+    this.dataFormat = view.getUint32(headerOffset);
+    headerOffset += 4;
+}
+
+var SchemeTypeBox = function (buffer, offset) {
+    this.super(SchemeTypeBox).constructor.call(this, buffer, offset);
+}
+
+SchemeTypeBox.prototype = Object.create(VersionFlagsAtom.prototype);
+
+SchemeTypeBox.prototype.setDefaults = function () {
+    this.super(SchemeTypeBox).setDefaults.call(this);
+
+    this.description = "Scheme Type Box";
+    this.schemeType = 0;
+    this.schemeVersion = 0;
+    this.schemeURL = 0;
+}
+
+SchemeTypeBox.prototype.parse = function (buffer, offset) {
+    var headerOffset = this.super(SchemeTypeBox).parse.call(this, buffer, offset);
+    var view = new DataView(buffer, offset);
+
+    this.schemeType = view.getUint32(headerOffset);
+    headerOffset += 4;
+    this.schemeVersion = view.getUint32(headerOffset);
+    headerOffset += 4;
+    if (this.flags & 0x1) {
+        var array = new Uint8Array(buffer, headerOffset, this.size - headerOffset);
+        this.schemeURL = String.fromCharCode.apply(null, array);
     }
-}
\ No newline at end of file
+}
+
+var SchemeInfoBox = function (buffer, offset) {
+    this.super(SchemeInfoBox).constructor.call(this, buffer, offset);
+}
+
+SchemeInfoBox.prototype = Object.create(VersionFlagsAtom.prototype);
+
+SchemeInfoBox.prototype.setDefaults = function () {
+    this.super(SchemeInfoBox).setDefaults.call(this);
+
+    this.description = "Scheme Information Box";
+    this.schemeSpecificData = 0;
+}
+
+SchemeInfoBox.prototype.parse = function (buffer, offset) {
+    var headerOffset = this.super(SchemeInfoBox).parse.call(this, buffer, offset);
+    this.schemeSpecificData = buffer.slice(headerOffset, this.size - headerOffset);
+}
+
+
index 26172643932f28a827bb6ebe664a8f5b0f1da7ee..78587ca5ae7d9660dd4cde017e421e910ddadee1 100644 (file)
             var reader = new FileReader();
             output.innerHTML = '';
             
-            function checkForMoovAtom(offset) {
+            function checkForAtom(offset) {
                 reader.onload = function(e) {
-                    var result = e.target.result;
-                    var basicAtom = new Atom(result);
-                    if (!basicAtom)
-                        return;
-                    else if (basicAtom.type == 'moov') 
-                        readMoovAtom(offset, basicAtom.size);
-                    else
-                        checkForMoovAtom(offset + basicAtom.size);
+                    var atom = new Atom(e.target.result);
+                    if (atom)
+                        readAtom(offset, atom.size);
                 };
                 var subset = file.slice(offset, offset + 16);
                 reader.readAsArrayBuffer(subset);
-            };
-            
-            function readMoovAtom(offset, length) {
-                reader.onload = function(e) { 
-                    moovAtom = Atom.create(e.target.result);
+            }
+
+            function readAtom(offset, length) {
+                reader.onload = function(e) {
+                    atom = Atom.create(e.target.result);
+                    if (!atom)
+                        return;
                     var output = document.getElementById('output');
-                    output.appendChild(toDOMNode(moovAtom));
+                    output.appendChild(toDOMNode(atom));
+                    if (file.size > offset + length)
+                        checkForAtom(offset + length);
                 };
                 var subset = file.slice(offset, offset + length);
                 reader.readAsArrayBuffer(subset);
-            };
+            }
             
-            checkForMoovAtom(0);
+            checkForAtom(0);
         }
         
         function toDOMRepresentation(object)
         {
             if (object instanceof Atom)
                 return toDOMNode(object);
-            else if (object instanceof Array) {
+            else if (object instanceof Array || object instanceof Uint32Array)  {
                 var span = document.createElement('span');
                 span.appendChild(document.createTextNode('['));
                 for (var i = 0; i < object.length; ++i) {
         function toDOMNode(object)
         {
             var output = document.createElement('dl');
+            
+            if (typeof(object.description) == 'string') {
+                var div = document.createElement('div');
+                div.classList.add('description');
+                div.appendChild(document.createTextNode(object.description));
+                output.appendChild(div);
+            }
 
             for (property in object) {
                 var value = object[property];
 
         dt, dd { display: inline-block; min-width: 8em; }
         dd { margin-left: 1em; }
+        .description { text-align: center; font-weight: bold; }
         
         /* make the output display as a table */
         /*