mp4box
Version:
JavaScript version of GPAC's MP4Box tool
196 lines (185 loc) • 7.01 kB
JavaScript
/*
* Copyright (c) Telecom ParisTech/TSI/MM/GPAC Cyril Concolato
* License: BSD-3-Clause (see LICENSE file)
*/
BoxParser.parseUUID = function(stream) {
return BoxParser.parseHex16(stream);
}
BoxParser.parseHex16 = function(stream) {
var hex16 = ""
for (var i = 0; i <16; i++) {
var hex = stream.readUint8().toString(16);
hex16 += (hex.length === 1 ? "0"+hex : hex);
}
return hex16;
}
BoxParser.parseOneBox = function(stream, headerOnly, parentSize) {
var box;
var start = stream.getPosition();
var hdr_size = 0;
var diff;
var uuid;
if (stream.getEndPosition() - start < 8) {
Log.debug("BoxParser", "Not enough data in stream to parse the type and size of the box");
return { code: BoxParser.ERR_NOT_ENOUGH_DATA };
}
if (parentSize && parentSize < 8) {
Log.debug("BoxParser", "Not enough bytes left in the parent box to parse a new box");
return { code: BoxParser.ERR_NOT_ENOUGH_DATA };
}
var size = stream.readUint32();
var type = stream.readString(4);
var box_type = type;
Log.debug("BoxParser", "Found box of type '"+type+"' and size "+size+" at position "+start);
hdr_size = 8;
if (type == "uuid") {
if ((stream.getEndPosition() - stream.getPosition() < 16) || (parentSize -hdr_size < 16)) {
stream.seek(start);
Log.debug("BoxParser", "Not enough bytes left in the parent box to parse a UUID box");
return { code: BoxParser.ERR_NOT_ENOUGH_DATA };
}
uuid = BoxParser.parseUUID(stream);
hdr_size += 16;
box_type = uuid;
}
if (size == 1) {
if ((stream.getEndPosition() - stream.getPosition() < 8) || (parentSize && (parentSize - hdr_size) < 8)) {
stream.seek(start);
Log.warn("BoxParser", "Not enough data in stream to parse the extended size of the \""+type+"\" box");
return { code: BoxParser.ERR_NOT_ENOUGH_DATA };
}
size = stream.readUint64();
hdr_size += 8;
} else if (size === 0) {
/* box extends till the end of file or invalid file */
if (parentSize) {
size = parentSize;
} else {
/* box extends till the end of file */
if (type !== "mdat") {
Log.error("BoxParser", "Unlimited box size not supported for type: '"+type+"'");
box = new BoxParser.Box(type, size);
return { code: BoxParser.OK, box: box, size: box.size };
}
}
}
if (size !== 0 && size < hdr_size) {
Log.error("BoxParser", "Box of type "+type+" has an invalid size "+size+" (too small to be a box)");
return { code: BoxParser.ERR_NOT_ENOUGH_DATA, type: type, size: size, hdr_size: hdr_size, start: start };
}
if (size !== 0 && parentSize && size > parentSize) {
Log.error("BoxParser", "Box of type '"+type+"' has a size "+size+" greater than its container size "+parentSize);
return { code: BoxParser.ERR_NOT_ENOUGH_DATA, type: type, size: size, hdr_size: hdr_size, start: start };
}
if (size !== 0 && start + size > stream.getEndPosition()) {
stream.seek(start);
Log.info("BoxParser", "Not enough data in stream to parse the entire '"+type+"' box");
return { code: BoxParser.ERR_NOT_ENOUGH_DATA, type: type, size: size, hdr_size: hdr_size, start: start };
}
if (headerOnly) {
return { code: BoxParser.OK, type: type, size: size, hdr_size: hdr_size, start: start };
} else {
if (BoxParser[type+"Box"]) {
box = new BoxParser[type+"Box"](size);
} else {
if (type !== "uuid") {
Log.warn("BoxParser", "Unknown box type: '"+type+"'");
box = new BoxParser.Box(type, size);
box.has_unparsed_data = true;
} else {
if (BoxParser.UUIDBoxes[uuid]) {
box = new BoxParser.UUIDBoxes[uuid](size);
} else {
Log.warn("BoxParser", "Unknown uuid type: '"+uuid+"'");
box = new BoxParser.Box(type, size);
box.uuid = uuid;
box.has_unparsed_data = true;
}
}
}
}
box.hdr_size = hdr_size;
/* recording the position of the box in the input stream */
box.start = start;
if (box.write === BoxParser.Box.prototype.write && box.type !== "mdat") {
Log.info("BoxParser", "'"+box_type+"' box writing not yet implemented, keeping unparsed data in memory for later write");
box.parseDataAndRewind(stream);
}
box.parse(stream);
diff = stream.getPosition() - (box.start+box.size);
if (diff < 0) {
Log.warn("BoxParser", "Parsing of box '"+box_type+"' did not read the entire indicated box data size (missing "+(-diff)+" bytes), seeking forward");
stream.seek(box.start+box.size);
} else if (diff > 0) {
Log.error("BoxParser", "Parsing of box '"+box_type+"' read "+diff+" more bytes than the indicated box data size, seeking backwards");
if (box.size !== 0) stream.seek(box.start+box.size);
}
return { code: BoxParser.OK, box: box, size: box.size };
}
BoxParser.Box.prototype.parse = function(stream) {
if (this.type != "mdat") {
this.data = stream.readUint8Array(this.size-this.hdr_size);
} else {
if (this.size === 0) {
stream.seek(stream.getEndPosition());
} else {
stream.seek(this.start+this.size);
}
}
}
/* Used to parse a box without consuming its data, to allow detailled parsing
Useful for boxes for which a write method is not yet implemented */
BoxParser.Box.prototype.parseDataAndRewind = function(stream) {
this.data = stream.readUint8Array(this.size-this.hdr_size);
// rewinding
stream.position -= this.size-this.hdr_size;
}
BoxParser.FullBox.prototype.parseDataAndRewind = function(stream) {
this.parseFullHeader(stream);
this.data = stream.readUint8Array(this.size-this.hdr_size);
// restore the header size as if the full header had not been parsed
this.hdr_size -= 4;
// rewinding
stream.position -= this.size-this.hdr_size;
}
BoxParser.FullBox.prototype.parseFullHeader = function (stream) {
this.version = stream.readUint8();
this.flags = stream.readUint24();
this.hdr_size += 4;
}
BoxParser.FullBox.prototype.parse = function (stream) {
this.parseFullHeader(stream);
this.data = stream.readUint8Array(this.size-this.hdr_size);
}
BoxParser.ContainerBox.prototype.parse = function(stream) {
var ret;
var box;
while (stream.getPosition() < this.start+this.size) {
ret = BoxParser.parseOneBox(stream, false, this.size - (stream.getPosition() - this.start));
if (ret.code === BoxParser.OK) {
box = ret.box;
/* store the box in the 'boxes' array to preserve box order (for offset) but also store box in a property for more direct access */
this.boxes.push(box);
if (this.subBoxNames && this.subBoxNames.indexOf(box.type) != -1) {
this[this.subBoxNames[this.subBoxNames.indexOf(box.type)]+"s"].push(box);
} else {
var box_type = box.type !== "uuid" ? box.type : box.uuid;
if (this[box_type]) {
Log.warn("Box of type "+box_type+" already stored in field of this type");
} else {
this[box_type] = box;
}
}
} else {
return;
}
}
}
BoxParser.Box.prototype.parseLanguage = function(stream) {
this.language = stream.readUint16();
var chars = [];
chars[0] = (this.language>>10)&0x1F;
chars[1] = (this.language>>5)&0x1F;
chars[2] = (this.language)&0x1F;
this.languageString = String.fromCharCode(chars[0]+0x60, chars[1]+0x60, chars[2]+0x60);
}