X-Git-Url: http://105106.c2e0p.group/movie_parser.git/blobdiff_plain/86cb3dcb7102f7c715c323bbd1c62f4f136ddfab..48b43d25b544737831d2252f6af2a64c0861180b:/Atom.js diff --git a/Atom.js b/Atom.js index 50ba17f..c7507db 100644 --- 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