UNPKG

mp4box

Version:

JavaScript version of GPAC's MP4Box tool

383 lines (348 loc) 12 kB
/* * Copyright (c) 2012-2013. Telecom ParisTech/TSI/MM/GPAC Cyril Concolato * License: BSD-3-Clause (see LICENSE file) */ var BoxParser = { ERR_NOT_ENOUGH_DATA : 0, OK : 1, boxCodes : [ "mdat", "avcC", "hvcC", "ftyp", "styp", "payl", "vttC", "vmhd", "smhd", "hmhd", "dref", "elst" // full boxes not yet parsed ], fullBoxCodes : [ "mvhd", "tkhd", "mdhd", "hdlr", "smhd", "hmhd", "nhmd", "url ", "urn ", "ctts", "cslg", "stco", "co64", "stsc", "stss", "stsz", "stz2", "stts", "stsh", "mehd", "trex", "mfhd", "tfhd", "trun", "tfdt", "esds", "subs", "txtC", "sidx" /* missing "stsd": special case full box and container */ ], containerBoxCodes : [ [ "moov", [ "trak" ] ], [ "trak" ], [ "edts" ], [ "mdia" ], [ "minf" ], [ "dinf" ], [ "stbl" ], [ "mvex", [ "trex" ] ], [ "moof", [ "traf" ] ], [ "traf", [ "trun" ] ], [ "vttc" ], [ "tref" ] ], sampleEntryCodes : [ /* 4CC as registered on http://mp4ra.org/codecs.html */ { prefix: "Visual", types: [ "mp4v", "avc1", "avc2", "avc3", "avc4", "avcp", "drac", "encv", "mjp2", "mvc1", "mvc2", "resv", "s263", "svc1", "vc-1", "hvc1", "hev1" ] }, { prefix: "Audio", types: [ "mp4a", "ac-3", "alac", "dra1", "dtsc", "dtse", ,"dtsh", "dtsl", "ec-3", "enca", "g719", "g726", "m4ae", "mlpa", "raw ", "samr", "sawb", "sawp", "sevc", "sqcp", "ssmv", "twos" ] }, { prefix: "Hint", types: [ "fdp ", "m2ts", "pm2t", "prtp", "rm2t", "rrtp", "rsrp", "rtp ", "sm2t", "srtp" ] }, { prefix: "Metadata", types: [ "metx", "mett", "urim" ] }, { prefix: "Subtitle", types: [ "stpp", "wvtt", "sbtt", "tx3g", "stxt" ] } ], trackReferenceTypes: [ "scal" ], initialize: function() { var i, j; var length; BoxParser.FullBox.prototype = new BoxParser.Box(); BoxParser.ContainerBox.prototype = new BoxParser.Box(); BoxParser.stsdBox.prototype = new BoxParser.FullBox(); BoxParser.SampleEntry.prototype = new BoxParser.FullBox(); BoxParser.TrackReferenceTypeBox.prototype = new BoxParser.Box(); /* creating constructors for simple boxes */ length = BoxParser.boxCodes.length; for (i=0; i<length; i++) { BoxParser[BoxParser.boxCodes[i]+"Box"] = (function (j) { /* creating a closure around the iterating value of i */ return function(size) { BoxParser.Box.call(this, BoxParser.boxCodes[j], size); } })(i); BoxParser[BoxParser.boxCodes[i]+"Box"].prototype = new BoxParser.Box(); } /* creating constructors for full boxes */ length = BoxParser.fullBoxCodes.length; for (i=0; i<length; i++) { BoxParser[BoxParser.fullBoxCodes[i]+"Box"] = (function (j) { return function(size) { BoxParser.FullBox.call(this, BoxParser.fullBoxCodes[j], size); } })(i); BoxParser[BoxParser.fullBoxCodes[i]+"Box"].prototype = new BoxParser.FullBox(); } /* creating constructors for container boxes */ length = BoxParser.containerBoxCodes.length; for (i=0; i<length; i++) { BoxParser[BoxParser.containerBoxCodes[i][0]+"Box"] = (function (j, subBoxNames) { return function(size) { BoxParser.ContainerBox.call(this, BoxParser.containerBoxCodes[j][0], size); if (subBoxNames) { this.subBoxNames = subBoxNames; var nbSubBoxes = subBoxNames.length; for (var k = 0; k<nbSubBoxes; k++) { this[subBoxNames[k]+"s"] = []; } } } })(i, BoxParser.containerBoxCodes[i][1]); BoxParser[BoxParser.containerBoxCodes[i][0]+"Box"].prototype = new BoxParser.ContainerBox(); } /* creating constructors for stsd entries */ length = BoxParser.sampleEntryCodes.length; for (j = 0; j < length; j++) { var prefix = BoxParser.sampleEntryCodes[j].prefix; var types = BoxParser.sampleEntryCodes[j].types; var nb_types = types.length; BoxParser[prefix+"SampleEntry"] = function(type, size) { BoxParser.SampleEntry.call(this, type, size); }; BoxParser[prefix+"SampleEntry"].prototype = new BoxParser.SampleEntry(); for (i=0; i<nb_types; i++) { BoxParser[types[i]+"Box"] = (function (k, l) { return function(size) { BoxParser[BoxParser.sampleEntryCodes[k].prefix+"SampleEntry"].call(this, BoxParser.sampleEntryCodes[k].types[l], size); } })(j, i); BoxParser[types[i]+"Box"].prototype = new BoxParser[prefix+"SampleEntry"](); } } /* creating constructors for track reference type boxes */ length = BoxParser.trackReferenceTypes.length; for (i=0; i<length; i++) { BoxParser[BoxParser.trackReferenceTypes[i]+"Box"] = (function (j) { return function(size) { BoxParser.TrackReferenceTypeBox.call(this, BoxParser.trackReferenceTypes[j], size); } })(i); BoxParser[BoxParser.trackReferenceTypes[i]+"Box"].prototype = new BoxParser.Box(); } }, Box: function(_type, _size) { this.type = _type; this.size = _size; }, FullBox: function(type, size) { BoxParser.Box.call(this, type, size); this.flags = 0; this.version = 0; }, ContainerBox: function(type, size) { BoxParser.Box.call(this, type, size); this.boxes = []; }, SampleEntry: function(type, size) { BoxParser.Box.call(this, type, size); this.boxes = []; }, TrackReferenceTypeBox: function(type, size) { BoxParser.Box.call(this, type, size); this.track_ids = []; }, stsdBox: function(size) { BoxParser.FullBox.call(this, "stsd", size); this.entries = []; }, parseOneBox: function(stream, isSampleEntry) { var box; var start = stream.position; var hdr_size = 0; if (stream.byteLength - stream.position < 8) { Log.d("BoxParser", "Not enough data in stream to parse the type and size of the box"); return { code: BoxParser.ERR_NOT_ENOUGH_DATA }; } var size = stream.readUint32(); var type = stream.readString(4); Log.d("BoxParser", "Found box of type "+type+" and size "+size+" at position "+start+" in the current buffer ("+(stream.buffer.fileStart+start)+" in the file)"); hdr_size = 8; if (type == "uuid") { uuid = stream.readString(16); hdr_size += 16; } if (size == 1) { if (stream.byteLength - stream.position < 8) { stream.seek(start); Log.w("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 */ throw "Unlimited box size not supported"; } if (start + size > stream.byteLength ) { stream.seek(start); Log.w("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 }; } if (BoxParser[type+"Box"]) { box = new BoxParser[type+"Box"](size - hdr_size); } else { if (isSampleEntry) { box = new BoxParser.SampleEntry(type, size - hdr_size); } else { box = new BoxParser.Box(type, size - hdr_size); } } /* recording the position of the box in the input stream */ box.hdr_size = hdr_size; box.start = start; box.fileStart = start + stream.buffer.fileStart; box.parse(stream); return { code: BoxParser.OK, box: box, size: size }; }, } BoxParser.initialize(); BoxParser.TKHD_FLAG_ENABLED = 0x000001; BoxParser.TKHD_FLAG_IN_MOVIE = 0x000002; BoxParser.TKHD_FLAG_IN_PREVIEW = 0x000004; BoxParser.TFHD_FLAG_BASE_DATA_OFFSET = 0x01; BoxParser.TFHD_FLAG_SAMPLE_DESC = 0x02; BoxParser.TFHD_FLAG_SAMPLE_DUR = 0x08; BoxParser.TFHD_FLAG_SAMPLE_SIZE = 0x10; BoxParser.TFHD_FLAG_SAMPLE_FLAGS = 0x20; BoxParser.TFHD_FLAG_DUR_EMPTY = 0x10000; BoxParser.TFHD_FLAG_DEFAULT_BASE_IS_MOOF= 0x20000; BoxParser.TRUN_FLAGS_DATA_OFFSET= 0x01; BoxParser.TRUN_FLAGS_FIRST_FLAG = 0x04; BoxParser.TRUN_FLAGS_DURATION = 0x100; BoxParser.TRUN_FLAGS_SIZE = 0x200; BoxParser.TRUN_FLAGS_FLAGS = 0x400; BoxParser.TRUN_FLAGS_CTS_OFFSET = 0x800; BoxParser.SampleEntry.prototype.isVideo = function() { return false; } BoxParser.SampleEntry.prototype.isAudio = function() { return false; } BoxParser.SampleEntry.prototype.isSubtitle = function() { return false; } BoxParser.SampleEntry.prototype.isMetadata = function() { return false; } BoxParser.SampleEntry.prototype.isHint = function() { return false; } BoxParser.SampleEntry.prototype.getCodec = function() { return this.type; } BoxParser.SampleEntry.prototype.getWidth = function() { return ""; } BoxParser.SampleEntry.prototype.getHeight = function() { return ""; } BoxParser.SampleEntry.prototype.getChannelCount = function() { return ""; } BoxParser.SampleEntry.prototype.getSampleRate = function() { return ""; } BoxParser.SampleEntry.prototype.getSampleSize = function() { return ""; } BoxParser.VisualSampleEntry.prototype.isVideo = function() { return true; } BoxParser.VisualSampleEntry.prototype.getWidth = function() { return this.width; } BoxParser.VisualSampleEntry.prototype.getHeight = function() { return this.height; } BoxParser.AudioSampleEntry.prototype.isAudio = function() { return true; } BoxParser.AudioSampleEntry.prototype.getChannelCount = function() { return this.channel_count; } BoxParser.AudioSampleEntry.prototype.getSampleRate = function() { return this.samplerate; } BoxParser.AudioSampleEntry.prototype.getSampleSize = function() { return this.samplesize; } BoxParser.SubtitleSampleEntry.prototype.isSubtitle = function() { return true; } BoxParser.MetadataSampleEntry.prototype.isMetadata = function() { return true; } BoxParser.decimalToHex = function(d, padding) { var hex = Number(d).toString(16); padding = typeof (padding) === "undefined" || padding === null ? padding = 2 : padding; while (hex.length < padding) { hex = "0" + hex; } return hex; } BoxParser.avc1Box.prototype.getCodec = function() { var baseCodec = BoxParser.SampleEntry.prototype.getCodec.call(this); if (this.avcC) { return baseCodec+"."+BoxParser.decimalToHex(this.avcC.AVCProfileIndication)+ ""+BoxParser.decimalToHex(this.avcC.profile_compatibility)+ ""+BoxParser.decimalToHex(this.avcC.AVCLevelIndication); } else { return baseCodec; } } BoxParser.hvc1Box.prototype.getCodec = function() { var i; var baseCodec = BoxParser.SampleEntry.prototype.getCodec.call(this); if (this.hvcC) { baseCodec += '.'; switch (this.hvcC.general_profile_space) { case 0: baseCodec += ''; break; case 1: baseCodec += 'A'; break; case 2: baseCodec += 'B'; break; case 3: baseCodec += 'C'; break; } baseCodec += this.hvcC.general_profile_idc; baseCodec += '.'; var val = this.hvcC.general_profile_compatibility; var reversed = 0; for (i=0; i<32; i++) { reversed |= val & 1; if (i==31) break; reversed <<= 1; val >>=1; } baseCodec += BoxParser.decimalToHex(reversed, 0); baseCodec += '.'; if (this.hvcC.general_tier_flag === 0) { baseCodec += 'L'; } else { baseCodec += 'H'; } baseCodec += this.hvcC.general_level_idc; var hasByte = false; var constraint_string = ""; for (i = 5; i >= 0; i--) { if (this.hvcC.general_constraint_indicator[i] || hasByte) { constraint_string = "."+BoxParser.decimalToHex(this.hvcC.general_constraint_indicator[i], 0)+constraint_string; hasByte = true; } } baseCodec += constraint_string; } return baseCodec; } BoxParser.mp4aBox.prototype.getCodec = function() { var baseCodec = BoxParser.SampleEntry.prototype.getCodec.call(this); if (this.esds && this.esds.esd) { var oti = this.esds.esd.getOTI(); var dsi = this.esds.esd.getAudioConfig(); return baseCodec+"."+BoxParser.decimalToHex(oti)+(dsi ? "."+dsi: ""); } else { return baseCodec; } }