UNPKG

mp4box

Version:

JavaScript version of GPAC's MP4Box tool

1,607 lines (1,603 loc) 263 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // entries/simple.ts var simple_exports = {}; __export(simple_exports, { Box: () => Box, BoxParser: () => BoxParser, FullBox: () => FullBox, ISOFile: () => ISOFile, Log: () => Log, MultiBufferStream: () => MultiBufferStream, SampleGroupEntry: () => SampleGroupEntry, SampleGroupInfo: () => SampleGroupInfo, SingleItemTypeReferenceBox: () => SingleItemTypeReferenceBox, SingleItemTypeReferenceBoxLarge: () => SingleItemTypeReferenceBoxLarge, TrackGroupTypeBox: () => TrackGroupTypeBox, TrackReferenceTypeBox: () => TrackReferenceTypeBox, createFile: () => createFile }); module.exports = __toCommonJS(simple_exports); // src/constants.ts var MAX_SIZE = Math.pow(2, 32); var MAX_UINT32 = Math.pow(2, 32) - 1; var TKHD_FLAG_ENABLED = 1; var TKHD_FLAG_IN_MOVIE = 2; var TKHD_FLAG_IN_PREVIEW = 4; var TFHD_FLAG_BASE_DATA_OFFSET = 1; var TFHD_FLAG_SAMPLE_DESC = 2; var TFHD_FLAG_SAMPLE_DUR = 8; var TFHD_FLAG_SAMPLE_SIZE = 16; var TFHD_FLAG_SAMPLE_FLAGS = 32; var TFHD_FLAG_DEFAULT_BASE_IS_MOOF = 131072; var TRUN_FLAGS_DATA_OFFSET = 1; var TRUN_FLAGS_FIRST_FLAG = 4; var TRUN_FLAGS_DURATION = 256; var TRUN_FLAGS_SIZE = 512; var TRUN_FLAGS_FLAGS = 1024; var TRUN_FLAGS_CTS_OFFSET = 2048; var ERR_INVALID_DATA = -1; var ERR_NOT_ENOUGH_DATA = 0; var OK = 1; // src/mp4boxbuffer.ts var MP4BoxBuffer = class _MP4BoxBuffer extends ArrayBuffer { constructor(byteLength) { super(byteLength); this.fileStart = 0; this.usedBytes = 0; } static fromArrayBuffer(buffer, fileStart) { const mp4BoxBuffer = new _MP4BoxBuffer(buffer.byteLength); const view = new Uint8Array(mp4BoxBuffer); view.set(new Uint8Array(buffer)); mp4BoxBuffer.fileStart = fileStart; return mp4BoxBuffer; } }; // src/DataStream.ts var DataStream = class _DataStream { /** * DataStream reads scalars, arrays and structs of data from an ArrayBuffer. * It's like a file-like DataView on steroids. * * @param arrayBuffer ArrayBuffer to read from. * @param byteOffset Offset from arrayBuffer beginning for the DataStream. * @param endianness Endianness of the DataStream (default: BIG_ENDIAN). */ constructor(arrayBuffer, byteOffset, endianness) { /** * Virtual byte length of the DataStream backing buffer. * Updated to be max of original buffer size and last written size. * If dynamicSize is false is set to buffer size. */ this._byteLength = 0; /** * Seek position where DataStream#readStruct ran into a problem. * Useful for debugging struct parsing. * * @type {number} */ this.failurePosition = 0; /** * Whether to extend DataStream buffer when trying to write beyond its size. * If set, the buffer is reallocated to twice its current size until the * requested write fits the buffer. * * @type {boolean} * @bundle DataStream-write.js */ this._dynamicSize = 1; this._byteOffset = byteOffset || 0; if (arrayBuffer instanceof ArrayBuffer) { this.buffer = MP4BoxBuffer.fromArrayBuffer(arrayBuffer, 0); } else if (arrayBuffer instanceof DataView) { this.dataView = arrayBuffer; if (byteOffset) this._byteOffset += byteOffset; } else { this.buffer = new MP4BoxBuffer(arrayBuffer || 0); } this.position = 0; this.endianness = endianness ? endianness : 1 /* BIG_ENDIAN */; } static { this.ENDIANNESS = new Int8Array(new Int16Array([1]).buffer)[0] > 0 ? 2 /* LITTLE_ENDIAN */ : 1 /* BIG_ENDIAN */; } getPosition() { return this.position; } /** * Internal function to resize the DataStream buffer when required. * @param extra Number of bytes to add to the buffer allocation. */ _realloc(extra) { if (!this._dynamicSize) { return; } const req = this._byteOffset + this.position + extra; let blen = this._buffer.byteLength; if (req <= blen) { if (req > this._byteLength) { this._byteLength = req; } return; } if (blen < 1) { blen = 1; } while (req > blen) { blen *= 2; } const buf = new MP4BoxBuffer(blen); const src = new Uint8Array(this._buffer); const dst = new Uint8Array(buf, 0, src.length); dst.set(src); this.buffer = buf; this._byteLength = req; } /** * Internal function to trim the DataStream buffer when required. * Used for stripping out the extra bytes from the backing buffer when * the virtual byteLength is smaller than the buffer byteLength (happens after * growing the buffer with writes and not filling the extra space completely). */ _trimAlloc() { if (this._byteLength === this._buffer.byteLength) { return; } const buf = new MP4BoxBuffer(this._byteLength); const dst = new Uint8Array(buf); const src = new Uint8Array(this._buffer, 0, dst.length); dst.set(src); this.buffer = buf; } /** * Returns the byte length of the DataStream object. * @type {number} */ get byteLength() { return this._byteLength - this._byteOffset; } /** * Set/get the backing ArrayBuffer of the DataStream object. * The setter updates the DataView to point to the new buffer. * @type {Object} */ get buffer() { this._trimAlloc(); return this._buffer; } set buffer(value) { this._buffer = value; this._dataView = new DataView(value, this._byteOffset); this._byteLength = value.byteLength; } /** * Set/get the byteOffset of the DataStream object. * The setter updates the DataView to point to the new byteOffset. * @type {number} */ get byteOffset() { return this._byteOffset; } set byteOffset(value) { this._byteOffset = value; this._dataView = new DataView(this._buffer, this._byteOffset); this._byteLength = this._buffer.byteLength; } /** * Set/get the byteOffset of the DataStream object. * The setter updates the DataView to point to the new byteOffset. * @type {number} */ get dataView() { return this._dataView; } set dataView(value) { this._byteOffset = value.byteOffset; this._buffer = MP4BoxBuffer.fromArrayBuffer(value.buffer, 0); this._dataView = new DataView(this._buffer, this._byteOffset); this._byteLength = this._byteOffset + value.byteLength; } /** * Sets the DataStream read/write position to given position. * Clamps between 0 and DataStream length. * * @param pos Position to seek to. * @return */ seek(pos) { const npos = Math.max(0, Math.min(this.byteLength, pos)); this.position = isNaN(npos) || !isFinite(npos) ? 0 : npos; } /** * Returns true if the DataStream seek pointer is at the end of buffer and * there's no more data to read. * * @return True if the seek pointer is at the end of the buffer. */ isEof() { return this.position >= this._byteLength; } #isTupleType(type) { return Array.isArray(type) && type.length === 3 && type[0] === "[]"; } /** * Maps a Uint8Array into the DataStream buffer. * * Nice for quickly reading in data. * * @param length Number of elements to map. * @param e Endianness of the data to read. * @return Uint8Array to the DataStream backing buffer. */ mapUint8Array(length) { this._realloc(length * 1); const arr = new Uint8Array(this._buffer, this.byteOffset + this.position, length); this.position += length * 1; return arr; } /** * Reads an Int32Array of desired length and endianness from the DataStream. * * @param length Number of elements to map. * @param endianness Endianness of the data to read. * @return The read Int32Array. */ readInt32Array(length, endianness) { length = length === void 0 ? this.byteLength - this.position / 4 : length; const arr = new Int32Array(length); _DataStream.memcpy( arr.buffer, 0, this.buffer, this.byteOffset + this.position, length * arr.BYTES_PER_ELEMENT ); _DataStream.arrayToNative(arr, endianness ?? this.endianness); this.position += arr.byteLength; return arr; } /** * Reads an Int16Array of desired length and endianness from the DataStream. * * @param length Number of elements to map. * @param endianness Endianness of the data to read. * @return The read Int16Array. */ readInt16Array(length, endianness) { length = length === void 0 ? this.byteLength - this.position / 2 : length; const arr = new Int16Array(length); _DataStream.memcpy( arr.buffer, 0, this.buffer, this.byteOffset + this.position, length * arr.BYTES_PER_ELEMENT ); _DataStream.arrayToNative(arr, endianness ?? this.endianness); this.position += arr.byteLength; return arr; } /** * Reads an Int8Array of desired length from the DataStream. * * @param length Number of elements to map. * @param e Endianness of the data to read. * @return The read Int8Array. */ readInt8Array(length) { length = length === void 0 ? this.byteLength - this.position : length; const arr = new Int8Array(length); _DataStream.memcpy( arr.buffer, 0, this.buffer, this.byteOffset + this.position, length * arr.BYTES_PER_ELEMENT ); this.position += arr.byteLength; return arr; } /** * Reads a Uint32Array of desired length and endianness from the DataStream. * * @param length Number of elements to map. * @param endianness Endianness of the data to read. * @return The read Uint32Array. */ readUint32Array(length, endianness) { length = length === void 0 ? this.byteLength - this.position / 4 : length; const arr = new Uint32Array(length); _DataStream.memcpy( arr.buffer, 0, this.buffer, this.byteOffset + this.position, length * arr.BYTES_PER_ELEMENT ); _DataStream.arrayToNative(arr, endianness ?? this.endianness); this.position += arr.byteLength; return arr; } /** * Reads a Uint16Array of desired length and endianness from the DataStream. * * @param length Number of elements to map. * @param endianness Endianness of the data to read. * @return The read Uint16Array. */ readUint16Array(length, endianness) { length = length === void 0 ? this.byteLength - this.position / 2 : length; const arr = new Uint16Array(length); _DataStream.memcpy( arr.buffer, 0, this.buffer, this.byteOffset + this.position, length * arr.BYTES_PER_ELEMENT ); _DataStream.arrayToNative(arr, endianness ?? this.endianness); this.position += arr.byteLength; return arr; } /** * Reads a Uint8Array of desired length from the DataStream. * * @param length Number of elements to map. * @param e Endianness of the data to read. * @return The read Uint8Array. */ readUint8Array(length) { length = length === void 0 ? this.byteLength - this.position : length; const arr = new Uint8Array(length); _DataStream.memcpy( arr.buffer, 0, this.buffer, this.byteOffset + this.position, length * arr.BYTES_PER_ELEMENT ); this.position += arr.byteLength; return arr; } /** * Reads a Float64Array of desired length and endianness from the DataStream. * * @param length Number of elements to map. * @param endianness Endianness of the data to read. * @return The read Float64Array. */ readFloat64Array(length, endianness) { length = length === void 0 ? this.byteLength - this.position / 8 : length; const arr = new Float64Array(length); _DataStream.memcpy( arr.buffer, 0, this.buffer, this.byteOffset + this.position, length * arr.BYTES_PER_ELEMENT ); _DataStream.arrayToNative(arr, endianness ?? this.endianness); this.position += arr.byteLength; return arr; } /** * Reads a Float32Array of desired length and endianness from the DataStream. * * @param length Number of elements to map. * @param endianness Endianness of the data to read. * @return The read Float32Array. */ readFloat32Array(length, endianness) { length = length === void 0 ? this.byteLength - this.position / 4 : length; const arr = new Float32Array(length); _DataStream.memcpy( arr.buffer, 0, this.buffer, this.byteOffset + this.position, length * arr.BYTES_PER_ELEMENT ); _DataStream.arrayToNative(arr, endianness ?? this.endianness); this.position += arr.byteLength; return arr; } /** * Reads a 32-bit int from the DataStream with the desired endianness. * * @param endianness Endianness of the number. * @return The read number. */ readInt32(endianness) { const v = this._dataView.getInt32( this.position, (endianness ?? this.endianness) === 2 /* LITTLE_ENDIAN */ ); this.position += 4; return v; } /** * Reads a 16-bit int from the DataStream with the desired endianness. * * @param endianness Endianness of the number. * @return The read number. */ readInt16(endianness) { const v = this._dataView.getInt16( this.position, (endianness ?? this.endianness) === 2 /* LITTLE_ENDIAN */ ); this.position += 2; return v; } /** * Reads an 8-bit int from the DataStream. * * @return The read number. */ readInt8() { const v = this._dataView.getInt8(this.position); this.position += 1; return v; } /** * Reads a 32-bit unsigned int from the DataStream with the desired endianness. * * @param endianness Endianness of the number. * @return The read number. */ readUint32(endianness) { const v = this._dataView.getUint32( this.position, (endianness ?? this.endianness) === 2 /* LITTLE_ENDIAN */ ); this.position += 4; return v; } /** * Reads a 16-bit unsigned int from the DataStream with the desired endianness. * * @param endianness Endianness of the number. * @return The read number. */ readUint16(endianness) { const v = this._dataView.getUint16( this.position, (endianness ?? this.endianness) === 2 /* LITTLE_ENDIAN */ ); this.position += 2; return v; } /** * Reads an 8-bit unsigned int from the DataStream. * * @return The read number. */ readUint8() { const v = this._dataView.getUint8(this.position); this.position += 1; return v; } /** * Reads a 32-bit float from the DataStream with the desired endianness. * * @param endianness Endianness of the number. * @return The read number. */ readFloat32(endianness) { const value = this._dataView.getFloat32( this.position, (endianness ?? this.endianness) === 2 /* LITTLE_ENDIAN */ ); this.position += 4; return value; } /** * Reads a 64-bit float from the DataStream with the desired endianness. * * @param endianness Endianness of the number. * @return The read number. */ readFloat64(endianness) { const value = this._dataView.getFloat64( this.position, (endianness ?? this.endianness) === 2 /* LITTLE_ENDIAN */ ); this.position += 8; return value; } /** * Copies byteLength bytes from the src buffer at srcOffset to the * dst buffer at dstOffset. * * @param dst Destination ArrayBuffer to write to. * @param dstOffset Offset to the destination ArrayBuffer. * @param src Source ArrayBuffer to read from. * @param srcOffset Offset to the source ArrayBuffer. * @param byteLength Number of bytes to copy. */ static memcpy(dst, dstOffset, src, srcOffset, byteLength) { const dstU8 = new Uint8Array(dst, dstOffset, byteLength); const srcU8 = new Uint8Array(src, srcOffset, byteLength); dstU8.set(srcU8); } /** * Converts array to native endianness in-place. * * @param typedArray Typed array to convert. * @param endianness True if the data in the array is * little-endian. Set false for big-endian. * @return The converted typed array. */ static arrayToNative(typedArray, endianness) { if (endianness === _DataStream.ENDIANNESS) { return typedArray; } else { return this.flipArrayEndianness(typedArray); } } /** * Converts native endianness array to desired endianness in-place. * * @param typedArray Typed array to convert. * @param littleEndian True if the converted array should be * little-endian. Set false for big-endian. * @return The converted typed array. */ static nativeToEndian(typedArray, littleEndian) { if (littleEndian && _DataStream.ENDIANNESS === 2 /* LITTLE_ENDIAN */) { return typedArray; } else { return this.flipArrayEndianness(typedArray); } } /** * Flips typed array endianness in-place. * * @param typedArray Typed array to flip. * @return The converted typed array. */ static flipArrayEndianness(typedArray) { const u8 = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength); for (let i = 0; i < typedArray.byteLength; i += typedArray.BYTES_PER_ELEMENT) { for (let j = i + typedArray.BYTES_PER_ELEMENT - 1, k = i; j > k; j--, k++) { const tmp = u8[k]; u8[k] = u8[j]; u8[j] = tmp; } } return typedArray; } /** * Read a string of desired length and encoding from the DataStream. * * @param length The length of the string to read in bytes. * @param encoding The encoding of the string data in the DataStream. * Defaults to ASCII. * @return The read string. */ readString(length, encoding) { if (encoding === void 0 || encoding === "ASCII") { return fromCharCodeUint8( this.mapUint8Array(length === void 0 ? this.byteLength - this.position : length) ); } else { return new TextDecoder(encoding).decode(this.mapUint8Array(length)); } } /** * Read null-terminated string of desired length from the DataStream. Truncates * the returned string so that the null byte is not a part of it. * * @param length The length of the string to read. * @return The read string. */ readCString(length) { let i = 0; const blen = this.byteLength - this.position; const u8 = new Uint8Array(this._buffer, this._byteOffset + this.position); const len = length !== void 0 ? Math.min(length, blen) : blen; for (; i < len && u8[i] !== 0; i++) ; const s = fromCharCodeUint8(this.mapUint8Array(i)); if (length !== void 0) { this.position += len - i; } else if (i !== blen) { this.position += 1; } return s; } readInt64() { return this.readInt32() * MAX_SIZE + this.readUint32(); } readUint64() { return this.readUint32() * MAX_SIZE + this.readUint32(); } readUint24() { return (this.readUint8() << 16) + (this.readUint8() << 8) + this.readUint8(); } /** * Saves the DataStream contents to the given filename. * Uses Chrome's anchor download property to initiate download. * * @param filename Filename to save as. * @return * @bundle DataStream-write.js */ save(filename) { const blob = new Blob([this.buffer]); if (typeof window !== "undefined" && typeof document !== "undefined") { if (window.URL && URL.createObjectURL) { const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); document.body.appendChild(a); a.setAttribute("href", url); a.setAttribute("download", filename); a.setAttribute("target", "_self"); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); } else { throw new Error("DataStream.save: Can't create object URL."); } } return blob; } /** @bundle DataStream-write.js */ get dynamicSize() { return this._dynamicSize; } /** @bundle DataStream-write.js */ set dynamicSize(v) { if (!v) { this._trimAlloc(); } this._dynamicSize = v; } /** * Internal function to trim the DataStream buffer when required. * Used for stripping out the first bytes when not needed anymore. * * @return * @bundle DataStream-write.js */ shift(offset) { const buf = new MP4BoxBuffer(this._byteLength - offset); const dst = new Uint8Array(buf); const src = new Uint8Array(this._buffer, offset, dst.length); dst.set(src); this.buffer = buf; this.position -= offset; } /** * Writes an Int32Array of specified endianness to the DataStream. * * @param array The array to write. * @param endianness Endianness of the data to write. * @bundle DataStream-write.js */ writeInt32Array(array, endianness) { this._realloc(array.length * 4); if (array instanceof Int32Array && this.byteOffset + this.position % array.BYTES_PER_ELEMENT === 0) { _DataStream.memcpy( this._buffer, this.byteOffset + this.position, array.buffer, 0, array.byteLength ); this.mapInt32Array(array.length, endianness); } else { for (let i = 0; i < array.length; i++) { this.writeInt32(array[i], endianness); } } } /** * Writes an Int16Array of specified endianness to the DataStream. * * @param array The array to write. * @param endianness Endianness of the data to write. * @bundle DataStream-write.js */ writeInt16Array(array, endianness) { this._realloc(array.length * 2); if (array instanceof Int16Array && this.byteOffset + this.position % array.BYTES_PER_ELEMENT === 0) { _DataStream.memcpy( this._buffer, this.byteOffset + this.position, array.buffer, 0, array.byteLength ); this.mapInt16Array(array.length, endianness); } else { for (let i = 0; i < array.length; i++) { this.writeInt16(array[i], endianness); } } } /** * Writes an Int8Array to the DataStream. * * @param array The array to write. * @bundle DataStream-write.js */ writeInt8Array(array) { this._realloc(array.length * 1); if (array instanceof Int8Array && this.byteOffset + this.position % array.BYTES_PER_ELEMENT === 0) { _DataStream.memcpy( this._buffer, this.byteOffset + this.position, array.buffer, 0, array.byteLength ); this.mapInt8Array(array.length); } else { for (let i = 0; i < array.length; i++) { this.writeInt8(array[i]); } } } /** * Writes a Uint32Array of specified endianness to the DataStream. * * @param array The array to write. * @param endianness Endianness of the data to write. * @bundle DataStream-write.js */ writeUint32Array(array, endianness) { this._realloc(array.length * 4); if (array instanceof Uint32Array && this.byteOffset + this.position % array.BYTES_PER_ELEMENT === 0) { _DataStream.memcpy( this._buffer, this.byteOffset + this.position, array.buffer, 0, array.byteLength ); this.mapUint32Array(array.length, endianness); } else { for (let i = 0; i < array.length; i++) { this.writeUint32(array[i], endianness); } } } /** * Writes a Uint16Array of specified endianness to the DataStream. * * @param array The array to write. * @param endianness Endianness of the data to write. * @bundle DataStream-write.js */ writeUint16Array(array, endianness) { this._realloc(array.length * 2); if (array instanceof Uint16Array && this.byteOffset + this.position % array.BYTES_PER_ELEMENT === 0) { _DataStream.memcpy( this._buffer, this.byteOffset + this.position, array.buffer, 0, array.byteLength ); this.mapUint16Array(array.length, endianness); } else { for (let i = 0; i < array.length; i++) { this.writeUint16(array[i], endianness); } } } /** * Writes a Uint8Array to the DataStream. * * @param array The array to write. * @bundle DataStream-write.js */ writeUint8Array(array) { this._realloc(array.length * 1); if (array instanceof Uint8Array && this.byteOffset + this.position % array.BYTES_PER_ELEMENT === 0) { _DataStream.memcpy( this._buffer, this.byteOffset + this.position, array.buffer, 0, array.byteLength ); this.mapUint8Array(array.length); } else { for (let i = 0; i < array.length; i++) { this.writeUint8(array[i]); } } } /** * Writes a Float64Array of specified endianness to the DataStream. * * @param array The array to write. * @param endianness Endianness of the data to write. * @bundle DataStream-write.js */ writeFloat64Array(array, endianness) { this._realloc(array.length * 8); if (array instanceof Float64Array && this.byteOffset + this.position % array.BYTES_PER_ELEMENT === 0) { _DataStream.memcpy( this._buffer, this.byteOffset + this.position, array.buffer, 0, array.byteLength ); this.mapFloat64Array(array.length, endianness); } else { for (let i = 0; i < array.length; i++) { this.writeFloat64(array[i], endianness); } } } /** * Writes a Float32Array of specified endianness to the DataStream. * * @param array The array to write. * @param endianness Endianness of the data to write. * @bundle DataStream-write.js */ writeFloat32Array(array, endianness) { this._realloc(array.length * 4); if (array instanceof Float32Array && this.byteOffset + this.position % array.BYTES_PER_ELEMENT === 0) { _DataStream.memcpy( this._buffer, this.byteOffset + this.position, array.buffer, 0, array.byteLength ); this.mapFloat32Array(array.length, endianness); } else { for (let i = 0; i < array.length; i++) { this.writeFloat32(array[i], endianness); } } } /** * Writes a 64-bit int to the DataStream with the desired endianness. * * @param value Number to write. * @param endianness Endianness of the number. * @bundle DataStream-write.js */ writeInt64(value, endianness) { this._realloc(8); this._dataView.setBigInt64( this.position, BigInt(value), (endianness ?? this.endianness) === 2 /* LITTLE_ENDIAN */ ); this.position += 8; } /** * Writes a 32-bit int to the DataStream with the desired endianness. * * @param value Number to write. * @param endianness Endianness of the number. * @bundle DataStream-write.js */ writeInt32(value, endianness) { this._realloc(4); this._dataView.setInt32( this.position, value, (endianness ?? this.endianness) === 2 /* LITTLE_ENDIAN */ ); this.position += 4; } /** * Writes a 16-bit int to the DataStream with the desired endianness. * * @param value Number to write. * @param endianness Endianness of the number. * @bundle DataStream-write.js */ writeInt16(value, endianness) { this._realloc(2); this._dataView.setInt16( this.position, value, (endianness ?? this.endianness) === 2 /* LITTLE_ENDIAN */ ); this.position += 2; } /** * Writes an 8-bit int to the DataStream. * * @param value Number to write. * @bundle DataStream-write.js */ writeInt8(value) { this._realloc(1); this._dataView.setInt8(this.position, value); this.position += 1; } /** * Writes a 32-bit unsigned int to the DataStream with the desired endianness. * * @param value Number to write. * @param endianness Endianness of the number. * @bundle DataStream-write.js */ writeUint32(value, endianness) { this._realloc(4); this._dataView.setUint32( this.position, value, (endianness ?? this.endianness) === 2 /* LITTLE_ENDIAN */ ); this.position += 4; } /** * Writes a 16-bit unsigned int to the DataStream with the desired endianness. * * @param value Number to write. * @param endianness Endianness of the number. * @bundle DataStream-write.js */ writeUint16(value, endianness) { this._realloc(2); this._dataView.setUint16( this.position, value, (endianness ?? this.endianness) === 2 /* LITTLE_ENDIAN */ ); this.position += 2; } /** * Writes an 8-bit unsigned int to the DataStream. * * @param value Number to write. * @bundle DataStream-write.js */ writeUint8(value) { this._realloc(1); this._dataView.setUint8(this.position, value); this.position += 1; } /** * Writes a 32-bit float to the DataStream with the desired endianness. * * @param value Number to write. * @param endianness Endianness of the number. * @bundle DataStream-write.js */ writeFloat32(value, endianness) { this._realloc(4); this._dataView.setFloat32( this.position, value, (endianness ?? this.endianness) === 2 /* LITTLE_ENDIAN */ ); this.position += 4; } /** * Writes a 64-bit float to the DataStream with the desired endianness. * * @param value Number to write. * @param endianness Endianness of the number. * @bundle DataStream-write.js */ writeFloat64(value, endianness) { this._realloc(8); this._dataView.setFloat64( this.position, value, (endianness ?? this.endianness) === 2 /* LITTLE_ENDIAN */ ); this.position += 8; } /** * Write a UCS-2 string of desired endianness to the DataStream. The * lengthOverride argument lets you define the number of characters to write. * If the string is shorter than lengthOverride, the extra space is padded with * zeroes. * * @param value The string to write. * @param endianness The endianness to use for the written string data. * @param lengthOverride The number of characters to write. * @bundle DataStream-write.js */ writeUCS2String(value, endianness, lengthOverride) { if (lengthOverride === void 0) { lengthOverride = value.length; } let i; for (i = 0; i < value.length && i < lengthOverride; i++) { this.writeUint16(value.charCodeAt(i), endianness); } for (; i < lengthOverride; i++) { this.writeUint16(0); } } /** * Writes a string of desired length and encoding to the DataStream. * * @param value The string to write. * @param encoding The encoding for the written string data. * Defaults to ASCII. * @param length The number of characters to write. * @bundle DataStream-write.js */ writeString(value, encoding, length) { let i = 0; if (encoding === void 0 || encoding === "ASCII") { if (length !== void 0) { const len = Math.min(value.length, length); for (i = 0; i < len; i++) { this.writeUint8(value.charCodeAt(i)); } for (; i < length; i++) { this.writeUint8(0); } } else { for (i = 0; i < value.length; i++) { this.writeUint8(value.charCodeAt(i)); } } } else { this.writeUint8Array(new TextEncoder(encoding).encode(value.substring(0, length))); } } /** * Writes a null-terminated string to DataStream and zero-pads it to length * bytes. If length is not given, writes the string followed by a zero. * If string is longer than length, the written part of the string does not have * a trailing zero. * * @param value The string to write. * @param length The number of characters to write. * @bundle DataStream-write.js */ writeCString(value, length) { let i = 0; if (length !== void 0) { const len = Math.min(value.length, length); for (i = 0; i < len; i++) { this.writeUint8(value.charCodeAt(i)); } for (; i < length; i++) { this.writeUint8(0); } } else { for (i = 0; i < value.length; i++) { this.writeUint8(value.charCodeAt(i)); } this.writeUint8(0); } } /** * Writes a struct to the DataStream. Takes a structDefinition that gives the * types and a struct object that gives the values. Refer to readStruct for the * structure of structDefinition. * * @param structDefinition Type definition of the struct. * @param struct The struct data object. * @bundle DataStream-write.js */ writeStruct(structDefinition, struct) { for (let i = 0; i < structDefinition.length; i++) { const [structName, structType] = structDefinition[i]; const structValue = struct[structName]; this.writeType(structType, structValue, struct); } } /** * Writes object v of type t to the DataStream. * * @param type Type of data to write. * @param value Value of data to write. * @param struct Struct to pass to write callback functions. * @bundle DataStream-write.js */ writeType(type, value, struct) { if (typeof type === "function") { return type(this, value); } else if (typeof type === "object" && !(type instanceof Array)) { return type.set(this, value, struct); } let lengthOverride; let charset = "ASCII"; const pos = this.position; let parsedType = type; if (typeof type === "string" && /:/.test(type)) { const tp = type.split(":"); parsedType = tp[0]; lengthOverride = parseInt(tp[1]); } if (typeof parsedType === "string" && /,/.test(parsedType)) { const tp = parsedType.split(","); parsedType = tp[0]; charset = tp[1]; } switch (parsedType) { case "uint8": this.writeUint8(value); break; case "int8": this.writeInt8(value); break; case "uint16": this.writeUint16(value, this.endianness); break; case "int16": this.writeInt16(value, this.endianness); break; case "uint32": this.writeUint32(value, this.endianness); break; case "int32": this.writeInt32(value, this.endianness); break; case "float32": this.writeFloat32(value, this.endianness); break; case "float64": this.writeFloat64(value, this.endianness); break; case "uint16be": this.writeUint16(value, 1 /* BIG_ENDIAN */); break; case "int16be": this.writeInt16(value, 1 /* BIG_ENDIAN */); break; case "uint32be": this.writeUint32(value, 1 /* BIG_ENDIAN */); break; case "int32be": this.writeInt32(value, 1 /* BIG_ENDIAN */); break; case "float32be": this.writeFloat32(value, 1 /* BIG_ENDIAN */); break; case "float64be": this.writeFloat64(value, 1 /* BIG_ENDIAN */); break; case "uint16le": this.writeUint16(value, 2 /* LITTLE_ENDIAN */); break; case "int16le": this.writeInt16(value, 2 /* LITTLE_ENDIAN */); break; case "uint32le": this.writeUint32(value, 2 /* LITTLE_ENDIAN */); break; case "int32le": this.writeInt32(value, 2 /* LITTLE_ENDIAN */); break; case "float32le": this.writeFloat32(value, 2 /* LITTLE_ENDIAN */); break; case "float64le": this.writeFloat64(value, 2 /* LITTLE_ENDIAN */); break; case "cstring": this.writeCString(value, lengthOverride); break; case "string": this.writeString(value, charset, lengthOverride); break; case "u16string": this.writeUCS2String(value, this.endianness, lengthOverride); break; case "u16stringle": this.writeUCS2String(value, 2 /* LITTLE_ENDIAN */, lengthOverride); break; case "u16stringbe": this.writeUCS2String(value, 1 /* BIG_ENDIAN */, lengthOverride); break; default: if (this.#isTupleType(parsedType)) { const [, ta] = parsedType; for (let i = 0; i < value.length; i++) { this.writeType(ta, value[i]); } break; } else { this.writeStruct(parsedType, value); break; } } if (lengthOverride) { this.position = pos; this._realloc(lengthOverride); this.position = pos + lengthOverride; } } /** @bundle DataStream-write.js */ writeUint64(value) { const h = Math.floor(value / MAX_SIZE); this.writeUint32(h); this.writeUint32(value & 4294967295); } /** @bundle DataStream-write.js */ writeUint24(value) { this.writeUint8((value & 16711680) >> 16); this.writeUint8((value & 65280) >> 8); this.writeUint8(value & 255); } /** @bundle DataStream-write.js */ adjustUint32(position, value) { const pos = this.position; this.seek(position); this.writeUint32(value); this.seek(pos); } /** * Reads a struct of data from the DataStream. The struct is defined as * an array of [name, type]-pairs. See the example below: * * ```ts * ds.readStruct([ * ['headerTag', 'uint32'], // Uint32 in DataStream endianness. * ['headerTag2', 'uint32be'], // Big-endian Uint32. * ['headerTag3', 'uint32le'], // Little-endian Uint32. * ['array', ['[]', 'uint32', 16]], // Uint32Array of length 16. * ['array2', ['[]', 'uint32', 'array2Length']] // Uint32Array of length array2Length * ]); * ``` * * The possible values for the type are as follows: * * ## Number types * * Unsuffixed number types use DataStream endianness. * To explicitly specify endianness, suffix the type with * 'le' for little-endian or 'be' for big-endian, * e.g. 'int32be' for big-endian int32. * * - `uint8` -- 8-bit unsigned int * - `uint16` -- 16-bit unsigned int * - `uint32` -- 32-bit unsigned int * - `int8` -- 8-bit int * - `int16` -- 16-bit int * - `int32` -- 32-bit int * - `float32` -- 32-bit float * - `float64` -- 64-bit float * * ## String types * * - `cstring` -- ASCII string terminated by a zero byte. * - `string:N` -- ASCII string of length N. * - `string,CHARSET:N` -- String of byteLength N encoded with given CHARSET. * - `u16string:N` -- UCS-2 string of length N in DataStream endianness. * - `u16stringle:N` -- UCS-2 string of length N in little-endian. * - `u16stringbe:N` -- UCS-2 string of length N in big-endian. * * ## Complex types * * ### Struct * ```ts * [[name, type], [name_2, type_2], ..., [name_N, type_N]] * ``` * * ### Callback function to read and return data * ```ts * function(dataStream, struct) {} * ``` * * ### Getter/setter functions * to read and return data, handy for using the same struct definition * for reading and writing structs. * ```ts * { * get: function(dataStream, struct) {}, * set: function(dataStream, struct) {} * } * ``` * * ### Array * Array of given type and length. The length can be either * - a number * - a string that references a previously-read field * - `*` * - a callback: `function(struct, dataStream, type){}` * * If length is `*`, reads in as many elements as it can. * ```ts * ['[]', type, length] * ``` * * @param structDefinition Struct definition object. * @return The read struct. Null if failed to read struct. * @bundle DataStream-read-struct.js */ readStruct(structDefinition) { const struct = {}; const p = this.position; for (let i = 0; i < structDefinition.length; i += 1) { const t = structDefinition[i][1]; const v = this.readType(t, struct); if (!v) { if (this.failurePosition === 0) { this.failurePosition = this.position; } this.position = p; return; } struct[structDefinition[i][0]] = v; } return struct; } /** * Read UCS-2 string of desired length and endianness from the DataStream. * * @param length The length of the string to read. * @param endianness The endianness of the string data in the DataStream. * @return The read string. * @bundle DataStream-read-struct.js */ readUCS2String(length, endianness) { return String.fromCharCode.apply(void 0, this.readUint16Array(length, endianness)); } /** * Reads an object of type t from the DataStream, passing struct as the thus-far * read struct to possible callbacks that refer to it. Used by readStruct for * reading in the values, so the type is one of the readStruct types. * * @param type Type of the object to read. * @param struct Struct to refer to when resolving length references * and for calling callbacks. * @return Returns the object on successful read, null on unsuccessful. * @bundle DataStream-read-struct.js */ readType(type, struct) { if (typeof type === "function") { return type(this, struct); } if (typeof type === "object" && !(type instanceof Array)) { return type.get(this, struct); } if (type instanceof Array && type.length !== 3) { return this.readStruct(type); } let value; let lengthOverride; let charset = "ASCII"; const pos = this.position; let parsedType = type; if (typeof parsedType === "string" && /:/.test(parsedType)) { const tp = parsedType.split(":"); parsedType = tp[0]; lengthOverride = parseInt(tp[1]); } if (typeof parsedType === "string" && /,/.test(parsedType)) { const tp = parsedType.split(","); parsedType = tp[0]; charset = tp[1]; } switch (parsedType) { case "uint8": value = this.readUint8(); break; case "int8": value = this.readInt8(); break; case "uint16": value = this.readUint16(this.endianness); break; case "int16": value = this.readInt16(this.endianness); break; case "uint32": value = this.readUint32(this.endianness); break; case "int32": value = this.readInt32(this.endianness); break; case "float32": value = this.readFloat32(this.endianness); break; case "float64": value = this.readFloat64(this.endianness); break; case "uint16be": value = this.readUint16(1 /* BIG_ENDIAN */); break; case "int16be": value = this.readInt16(1 /* BIG_ENDIAN */); break; case "uint32be": value = this.readUint32(1 /* BIG_ENDIAN */); break; case "int32be": value = this.readInt32(1 /* BIG_ENDIAN */); break; case "float32be": value = this.readFloat32(1 /* BIG_ENDIAN */); break; case "float64be": value = this.readFloat64(1 /* BIG_ENDIAN */); break; case "uint16le": value = this.readUint16(2 /* LITTLE_ENDIAN */); break; case "int16le": value = this.readInt16(2 /* LITTLE_ENDIAN */); break; case "uint32le": value = this.readUint32(2 /* LITTLE_ENDIAN */); break; case "int32le": value = this.readInt32(2 /* LITTLE_ENDIAN */); break; case "float32le": value = this.readFloat32(2 /* LITTLE_ENDIAN */); break; case "float64le": value = this.readFloat64(2 /* LITTLE_ENDIAN */); break; case "cstring": value = this.readCString(lengthOverride); break; case "string": value = this.readString(lengthOverride, charset); break; case "u16string": value = this.readUCS2String(lengthOverride, this.endianness); break; case "u16stringle": value = this.readUCS2String(lengthOverride, 2 /* LITTLE_ENDIAN */); break; case "u16stringbe": value = this.readUCS2String(lengthOverride, 1 /* BIG_ENDIAN */); break; default: if (this.#isTupleType(parsedType)) { const [, ta, len] = parsedType; const length = typeof len === "function" ? len(struct, this, parsedType) : typeof len === "string" && struct[len] !== void 0 ? ( // @ts-expect-error FIXME: Struct[string] is currently of type Type parseInt(struct[len]) ) : typeof len === "number" ? len : len === "*" ? void 0 : parseInt(len); if (typeof ta === "string") { const tap = ta.replace(/(le|be)$/, ""); let endianness; if (/le$/.test(ta)) { endianness = 2 /* LITTLE_ENDIAN */; } else if (/be$/.test(ta)) { endianness = 1 /* BIG_ENDIAN */; } switch (tap) { case "uint8": value = this.readUint8Array(length); break; case "uint16": value = this.readUint16Array(length, endianness); break; case "uint32": value = this.readUint32Array(length, endianness); break; case "int8": value = this.readInt8Array(length); break; case "int16": value = this.readInt16Array(length, endianness); break; case "int32": value = this.readInt32Array(length, endianness); break; case "float32": value = this.readFloat32Array(length, endianness); break; case "float64": value = this.readFloat64Array(length, endianness); break; case "cstring": case "utf16string": case "string": if (!length) { value = []; while (!this.isEof()) { const u = this.readType(ta, struct); if (!u) break; value.push(u); } } else { value = new Array(length); for (let i = 0; i < length; i++) { value[i] = this.readType(ta, struct); } } break; } } else { if (!length) { value = []; while (true) { const pos2 = this.position; try { const type2 = this.readType(ta, struct); if (!type2) { this.position = pos2; break; } value.push(type2); } catch { this.position = pos2; break; } } } else { value = new Array(length); for (let i = 0; i < length; i++) { const type2 = this.readType(ta, struct); if (!type2) return; value[i] = type2; } } } break; } } if (lengthOverride) { this.position = pos + lengthOverride; } return value; } /** * Maps an Int32Array into the DataStream buffer, swizzling it to native * endianness in-place. The current offset from the start of the buffer needs to * be a multiple of element size, just like with typed array views. * * Nice for quickly reading in data. Warning: potentially modifies the buffer * contents. * * @param length Number of elements to map. * @param endianness Endianness of the data to read. * @return Int32Array to the DataStream backing buffer. * @bundle DataStream-map.js */ mapInt32Array(length, endianness) { this._realloc(length * 4); const arr = new Int32Array(this._buffer, this.byteOffset + this.position, length); _DataStream.arrayToNative(arr, endianness ?? this.endianness); this.position += length * 4; return arr; } /** * Maps an Int16Array into the DataStream buffer, swizzling it to native * endianness in-place. The current offset from the start of the buffer needs to * be a multiple of element size, just like with typed array views. * * Nice for quickly reading in data. Warning: potentially modifies the buffer * contents. * * @param length Number of elements to map. * @param endianness Endianness of the data to read. * @return Int16Array to the DataStream backing buffer. * @bundle DataStream-map.js */ mapInt16Array(length, endianness) { this._realloc(length * 2); const arr = new Int16Array(this._buffer, this.byteOffset + this.position, length); _DataStream.arrayToNative(arr, endianness ?? this.endianness); this.position += length * 2; return arr; } /** * Maps an Int8Array into the DataStream buffer. * * Nice for quickly reading in data. * * @param length Number of elements to map. * @param endiannes