From: Jer Noble Date: Thu, 6 Dec 2012 23:58:44 +0000 (-0800) Subject: Support ftyp, moov, and mhdr atoms. X-Git-Url: http://id.pley.net/movie_parser.git/commitdiff_plain/a65052e7db9fec0f0098eec2d42175bf39ce27c6?hp=86cb3dcb7102f7c715c323bbd1c62f4f136ddfab Support ftyp, moov, and mhdr atoms. --- diff --git a/Atom.js b/Atom.js index 50ba17f..52ee673 100644 --- 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 diff --git a/AtomTester.html b/AtomTester.html index 789e246..82d9be2 100644 --- a/AtomTester.html +++ b/AtomTester.html @@ -10,11 +10,14 @@ 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; } }; @@ -26,7 +29,20 @@ document.getElementById('file').addEventListener('change', onFileSelect, false); } + +
\ No newline at end of file