X-Git-Url: http://id.pley.net/movie_parser.git/blobdiff_plain/48b43d25b544737831d2252f6af2a64c0861180b..9114b5c6a1b71b9c3007b44d69acf3b886861a4d:/Atom.js diff --git a/Atom.js b/Atom.js index c7507db..8a73803 100644 --- a/Atom.js +++ b/Atom.js @@ -1,213 +1,268 @@ -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 FtypAtom(buffer, offset); + 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': - return new MvhdAtom(buffer, offset); + return new MovieHeaderAtom(buffer, offset); case 'tkhd': - return new TkhdAtom(buffer, offset); + return new TrackHeaderAtom(buffer, offset); case 'mdhd': - return new MdhdAtom(buffer, offset); + return new MediaHeaderAtom(buffer, offset); + case 'stss': + 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) { + return Object.getPrototypeOf(object.prototype); +}; -Atom.prototype.setDefaults = function() -{ - Object.defineProperty(this, "is64bit", { +Atom.prototype.setDefaults = function () { + Object.defineProperty(this, "is64bit", { value: false, writable: true, enumerable: false, configurable: true, }); - Object.defineProperty(this, "minimumSize", { + Object.defineProperty(this, "minimumSize", { value: 8, writable: true, enumerable: false, configurable: true, }); - Object.defineProperty(this, "parent", { + Object.defineProperty(this, "parent", { value: null, writable: true, 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 (arguments.length < 2) + if (typeof(offset) == 'undefined') offset = 0; - // Atoms are 8 bytes minimum. - if (buffer.byteLength - offset < 8) - return false; + this.offset = offset; + + if (buffer.byteLength - offset < this.minimumSize) + throw 'Buffer not long enough'; var view = new DataView(buffer, offset, 4); - offset += 4; + var headerOffset = 0; + this.size = view.getUint32(0); + headerOffset += 4; - var typeArrayView = new Uint8Array(buffer, offset, 4); - offset += 4; + var typeArrayView = new Uint8Array(buffer, offset + headerOffset, 4); this.type = String.fromCharCode.apply(null, typeArrayView); + headerOffset += 4; 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. + // 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 view = new DataView(buffer, offset + headerOffset, 8); var upper = view.getUint32(0); var lower = view.getUint32(4); this.size = (upper << 32) + lower; + headerOffset += 8; } - - return true; + + 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 FtypAtom = function(buffer, offset) { - return Atom.prototype.constructor.call(this, buffer, offset); +var FileTypeAtom = function (buffer, offset) { + this.super(FileTypeAtom).constructor.call(this, buffer, offset); } -FtypAtom.prototype = Object.create(Atom.prototype); +FileTypeAtom.prototype = Object.create(Atom.prototype); -FtypAtom.prototype.setDefaults = function() { - Atom.prototype.setDefaults.call(this); +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 = []; } -FtypAtom.prototype.parse = function(buffer, offset) { - if (!Atom.prototype.parse.call(this, buffer, offset)) - return false; +FileTypeAtom.prototype.parse = function (buffer, offset) { + var headerOffset = this.super(FileTypeAtom).parse.call(this, buffer, offset); + var view = new DataView(buffer, offset, this.size); - var begin = offset; - var end = begin + this.size; - offset += this.is64bit ? 16 : 8; - - var brandArrayView = new Uint8Array(buffer, offset, 4); - offset += 4; + var brandArrayView = new Uint8Array(buffer, offset + headerOffset, 4); this.brand = String.fromCharCode.apply(null, brandArrayView); + headerOffset += 4; - var view = new DataView(buffer, offset, 4); - offset += 4; - this.version = view.getUint32(0); + this.version = view.getUint32(headerOffset); + headerOffset += 4; - while (offset <= end - 4) { - var brandArrayView = new Uint8Array(buffer, offset, 4); - offset += 4; + while (headerOffset < this.size - 4) { + var brandArrayView = new Uint8Array(buffer, offset + headerOffset, 4); this.compatible_brands.push(String.fromCharCode.apply(null, brandArrayView)); + headerOffset += 4; } - + return true; } -var ContainerAtom = function(buffer, offset) { - return Atom.prototype.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() -{ - Atom.prototype.setDefaults.call(this); +ContainerAtom.prototype.setDefaults = function () { + this.super(ContainerAtom).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); +ContainerAtom.prototype.parse = function (buffer, offset) { + var headerOffset = this.super(ContainerAtom).parse.call(this, buffer, offset); + while (headerOffset < this.size) { + var childAtom = Atom.create(buffer, offset + headerOffset); if (!childAtom) break; - offset += childAtom.size; + headerOffset += childAtom.size; this.childAtoms.push(childAtom); childAtom.parent = this; } + return headerOffset; } -var MvhdAtom = function(buffer, offset) { - return Atom.prototype.constructor.call(this, buffer, offset); +var VersionFlagsAtom = function (buffer, offset) { + this.super(VersionFlagsAtom).constructor.call(this, buffer, offset); } -MvhdAtom.prototype = Object.create(Atom.prototype); +VersionFlagsAtom.prototype = Object.create(Atom.prototype); -MvhdAtom.prototype.setDefaults = function() { - Atom.prototype.setDefaults.call(this); +VersionFlagsAtom.prototype.setDefaults = function () { + this.super(VersionFlagsAtom).setDefaults.call(this); this.version = 0; + this.flags = 0; +} + +VersionFlagsAtom.prototype.parse = function (buffer, offset) { + var headerOffset = this.super(VersionFlagsAtom).parse.call(this, buffer, offset); + 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; + + return headerOffset; +} + +var MovieHeaderAtom = function (buffer, offset) { + return this.super(MovieHeaderAtom).constructor.call(this, buffer, offset); +} + +MovieHeaderAtom.prototype = Object.create(VersionFlagsAtom.prototype); + +MovieHeaderAtom.prototype.setDefaults = function () { + this.super(MovieHeaderAtom).setDefaults.call(this); + this.description = "Movie Header Atom"; this.creationTime = 0; this.modificationTime = 0; this.timeScale = 0; @@ -222,18 +277,10 @@ MvhdAtom.prototype.setDefaults = function() { 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; +MovieHeaderAtom.prototype.parse = function (buffer, offset) { + var headerOffset = this.super(MovieHeaderAtom).parse.call(this, buffer, offset); 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; @@ -242,10 +289,10 @@ MvhdAtom.prototype.parse = function(buffer, offset) { this.timeScale = view.getUint32(headerOffset); headerOffset += 4; - + this.duration = view.getUint32(headerOffset); headerOffset += 4; - + this.preferredRate = view.getUint32(headerOffset) / (1 << 16); headerOffset += 4; @@ -286,36 +333,35 @@ MvhdAtom.prototype.parse = function(buffer, offset) { 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); +var TrackHeaderAtom = function (buffer, offset) { + this.super(TrackHeaderAtom).constructor.call(this, buffer, offset); } -TkhdAtom.prototype = Object.create(Atom.prototype); +TrackHeaderAtom.prototype = Object.create(VersionFlagsAtom.prototype); -TkhdAtom.prototype.setDefaults = function() { - Atom.prototype.setDefaults.call(this); - - this.version = 0; - this.flags = 0; +TrackHeaderAtom.prototype.setDefaults = function () { + this.super(TrackHeaderAtom).setDefaults.call(this); + + this.description = "Track Header Atom"; this.creationTime = 0; this.modificationTime = 0; this.trackID = 0; @@ -328,23 +374,10 @@ TkhdAtom.prototype.setDefaults = function() { 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; +TrackHeaderAtom.prototype.parse = function (buffer, offset) { + var headerOffset = this.super(TrackHeaderAtom).parse.call(this, buffer, offset); 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; @@ -353,31 +386,31 @@ TkhdAtom.prototype.parse = function(buffer, offset) 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); @@ -405,25 +438,24 @@ TkhdAtom.prototype.parse = function(buffer, offset) 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); +var MediaHeaderAtom = function (buffer, offset) { + this.super(MediaHeaderAtom).constructor.call(this, buffer, offset); } -MdhdAtom.prototype = Object.create(Atom.prototype); +MediaHeaderAtom.prototype = Object.create(VersionFlagsAtom.prototype); -MdhdAtom.prototype.setDefaults = function() { - Atom.prototype.setDefaults.call(this); - - this.version = 0; - this.flags = 0; +MediaHeaderAtom.prototype.setDefaults = function () { + this.super(MediaHeaderAtom).setDefaults.call(this); + + this.description = "Media Header Atom"; this.creationTime = 0; this.modificationTime = 0; this.timeScale = 0; @@ -432,23 +464,10 @@ MdhdAtom.prototype.setDefaults = function() { 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; +MediaHeaderAtom.prototype.parse = function (buffer, offset) { + var headerOffset = this.super(MediaHeaderAtom).parse.call(this, buffer, offset); 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; @@ -457,13 +476,243 @@ MdhdAtom.prototype.parse = function(buffer, offset) this.timeScale = view.getUint32(headerOffset); headerOffset += 4; - + this.duration = view.getUint32(headerOffset); headerOffset += 4; - + this.language = view.getUint16(headerOffset); headerOffset += 2; - + this.quality = view.getUint16(headerOffset); headerOffset += 2; -} \ No newline at end of file +}; + +var SyncSampleAtom = function (buffer, offset) { + this.super(SyncSampleAtom).constructor.call(this, buffer, offset); +}; + +SyncSampleAtom.prototype = Object.create(Atom.prototype); + +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) { + var headerOffset = this.super(SyncSampleAtom).parse.call(this, buffer, offset); + 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.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[i] = sampleNumber; + ++i; + } +}; + +var TimeToSampleAtom = function (buffer, offset) { + this.super(TimeToSampleAtom).constructor.call(this, buffer, offset); +}; + +TimeToSampleAtom.prototype = Object.create(VersionFlagsAtom.prototype); + +TimeToSampleAtom.prototype.setDefaults = function () { + this.super(TimeToSampleAtom).setDefaults.call(this); + + this.description = "Time-to-Sample Atom"; + this.entries = 0; + + Object.defineProperty(this, "timeToSamples", { + value: null, + writable: true, + enumerable: false, + configurable: true, + }); +} + +TimeToSampleAtom.prototype.parse = function (buffer, offset) { + var headerOffset = this.super(TimeToSampleAtom).parse.call(this, buffer, offset); + var view = new DataView(buffer, offset); + + this.entries = view.getUint32(headerOffset); + headerOffset += 4; + + this.timeToSamples = new Array(this.entries); + var i = 0; + + while (headerOffset < this.size) { + var sampleCount = view.getUint32(headerOffset); + headerOffset += 4; + + var sampleDuration = view.getUint32(headerOffset); + headerOffset += 4; + + 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); + } +} + +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); +} + +