UNPKG

isobmff-inspector

Version:

Simple ISOBMFF parser, compatible with JavaScript and Node.JS

1,412 lines (1,403 loc) 157 kB
(function (global, factory) { typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, global.inspectISOBMFF = factory()); })(this, function () { "use strict"; "use strict"; var __inspectISOBMFFBundle = (() => { var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); var __typeError = (msg) => { throw TypeError(msg); }; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); 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); var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method); var __await = function(promise, isYieldStar) { this[0] = promise; this[1] = isYieldStar; }; var __asyncGenerator = (__this, __arguments, generator) => { var resume = (k, v, yes, no) => { try { var x = generator[k](v), isAwait = (v = x.value) instanceof __await, done = x.done; Promise.resolve(isAwait ? v[0] : v).then((y) => isAwait ? resume(k === "return" ? k : "next", v[1] ? { done: y.done, value: y.value } : y, yes, no) : yes({ value: y, done })).catch((e) => resume("throw", e, yes, no)); } catch (e) { no(e); } }, method = (k, call, wait, clear) => it[k] = (x) => (call = new Promise((yes, no, run) => (run = () => resume(k, x, yes, no), q ? q.then(run) : run())), clear = () => q === wait && (q = 0), q = wait = call.then(clear, clear), call), q, it = {}; return generator = generator.apply(__this, __arguments), it[__knownSymbol("asyncIterator")] = () => it, method("next"), method("throw"), method("return"), it; }; var __yieldStar = (value) => { var obj = value[__knownSymbol("asyncIterator")], isAwait = false, method, it = {}; if (obj == null) { obj = value[__knownSymbol("iterator")](); method = (k) => it[k] = (x) => obj[k](x); } else { obj = obj.call(value); method = (k) => it[k] = (v) => { if (isAwait) { isAwait = false; if (k === "throw") throw v; return v; } isAwait = true; return { done: false, value: new __await(new Promise((resolve) => { var x = obj[k](v); if (!(x instanceof Object)) __typeError("Object expected"); resolve(x); }), 1) }; }; } return it[__knownSymbol("iterator")] = () => it, method("next"), "throw" in obj ? method("throw") : it.throw = (x) => { throw x; }, "return" in obj && method("return"), it; }; var __forAwait = (obj, it, method) => (it = obj[__knownSymbol("asyncIterator")]) ? it.call(obj) : (obj = obj[__knownSymbol("iterator")](), it = {}, method = (key, fn) => (fn = obj[key]) && (it[key] = (arg) => new Promise((yes, no, done) => (arg = fn.call(obj, arg), done = arg.done, Promise.resolve(arg.value).then((value) => yes({ value, done }), no)))), method("next"), method("return"), it); // src/main.js var main_exports = {}; __export(main_exports, { default: () => main_default, parse: () => parse, parseBuffer: () => parseBuffer, parseEvents: () => parseEvents }); // src/utils/bytes.js var textDecoder = new TextDecoder(); function parseBoxType(bytes, offset) { return String.fromCharCode( bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3] ); } function be2toi(bytes, off) { return (bytes[0 + off] << 8) + bytes[1 + off]; } function be3toi(bytes, off) { return bytes[0 + off] * 65536 + bytes[1 + off] * 256 + bytes[2 + off]; } function be4toi(bytes, off) { return bytes[0 + off] * 16777216 + bytes[1 + off] * 65536 + bytes[2 + off] * 256 + bytes[3 + off]; } function be5toi(bytes, off) { return bytes[0 + off] * 4294967296 + bytes[1 + off] * 16777216 + bytes[2 + off] * 65536 + bytes[3 + off] * 256 + bytes[4 + off]; } function be8toi(bytes, off) { return (bytes[0 + off] * 16777216 + bytes[1 + off] * 65536 + bytes[2 + off] * 256 + bytes[3 + off]) * 4294967296 + bytes[4 + off] * 16777216 + bytes[5 + off] * 65536 + bytes[6 + off] * 256 + bytes[7 + off]; } function bytesToHex(uint8arr, off, nbBytes) { if (!uint8arr) { return ""; } const arr = uint8arr.slice(off, nbBytes + off); let hexStr = ""; for (let i = 0; i < arr.length; i++) { let hex = (arr[i] & 255).toString(16); hex = hex.length === 1 ? `0${hex}` : hex; hexStr += hex; } return hexStr.toUpperCase(); } function utf8ToStr(uint8arr, off = 0, nbBytes) { if (!uint8arr) { return ""; } if (nbBytes === void 0) { if (off === 0) { return textDecoder.decode(uint8arr); } return textDecoder.decode(uint8arr.slice(off)); } const arr = uint8arr.slice(off, nbBytes + off); return textDecoder.decode(arr); } function viewToUint8Array(view) { return new Uint8Array(view.buffer, view.byteOffset, view.byteLength); } function isBufferSource(value) { return value instanceof ArrayBuffer || ArrayBuffer.isView(value); } function bufferSourceToUint8Array(arr) { if (arr instanceof Uint8Array) { return arr; } if (arr instanceof ArrayBuffer) { return new Uint8Array(arr); } return viewToUint8Array(arr); } function byteChunkToUint8Array(chunk) { if (chunk instanceof Uint8Array) { return chunk; } if (chunk instanceof ArrayBuffer) { return new Uint8Array(chunk); } if (ArrayBuffer.isView(chunk)) { return viewToUint8Array(chunk); } throw new Error( "Progressive ISOBMFF inputs must yield ArrayBuffer or TypedArray chunks." ); } function asyncByteIterable(iterable) { return { [Symbol.asyncIterator]() { return __asyncGenerator(this, null, function* () { try { for (var iter = __forAwait(iterable), more, temp, error; more = !(temp = yield new __await(iter.next())).done; more = false) { const chunk = temp.value; yield byteChunkToUint8Array(chunk); } } catch (temp) { error = [temp]; } finally { try { more && (temp = iter.return) && (yield new __await(temp.call(iter))); } finally { if (error) throw error[0]; } } }); } }; } function getProgressiveSource(input) { if (input === null || input === void 0) { return void 0; } if (typeof input === "object" && "body" in input) { const body = ( /** @type {{ body?: unknown }} */ input.body ); if (body !== null && body !== void 0) { return getProgressiveSource(body); } } if (typeof input === "object" && "getReader" in input && typeof input.getReader === "function") { return { [Symbol.asyncIterator]() { return __asyncGenerator(this, null, function* () { const reader = ( /** @type {ReadableStream} */ input.getReader() ); try { while (true) { const { done, value } = yield new __await(reader.read()); if (done) { break; } yield byteChunkToUint8Array(value); } } finally { reader.releaseLock(); } }); } }; } if (typeof input === "object" && Symbol.asyncIterator in input && typeof input[Symbol.asyncIterator] === "function") { return asyncByteIterable( /** @type {AsyncIterable<unknown>} */ input ); } if (typeof input === "object" && Symbol.iterator in input && typeof input[Symbol.iterator] === "function") { return asyncByteIterable( /** @type {Iterable<unknown>} */ input ); } if (typeof input === "object" && "stream" in input && typeof input.stream === "function") { return getProgressiveSource(input.stream()); } if (typeof input === "object" && "arrayBuffer" in input && typeof input.arrayBuffer === "function") { const arrayBuffer = ( /** @type {{ arrayBuffer: () => Promise<ArrayBuffer> }} */ input.arrayBuffer ); return asyncByteIterable({ [Symbol.asyncIterator]() { return __asyncGenerator(this, null, function* () { yield yield new __await(arrayBuffer.call(input)); }); } }); } return void 0; } // src/utils/ProgressiveByteReader.js var ProgressiveByteReader = class { /** * @param {AsyncIterator<Uint8Array>} iterator */ constructor(iterator) { this._iterator = iterator; this._buffers = []; this._bufferedLength = 0; this._done = false; } /** * @param {number} nbBytes * @returns {Promise<void>} */ async ensure(nbBytes) { while (!this._done && this._bufferedLength < nbBytes) { const next = await this._iterator.next(); if (next.done) { this._done = true; break; } if (next.value.length > 0) { this._buffers.push(next.value); this._bufferedLength += next.value.length; } } } /** * @returns {number} */ getBufferedLength() { return this._bufferedLength; } /** * @returns {boolean} */ isDone() { return this._done && this._bufferedLength === 0; } /** * @param {number} nbBytes * @returns {Uint8Array} */ takeAvailable(nbBytes) { const size = Math.min(nbBytes, this._bufferedLength); const result = new Uint8Array(size); let resultOffset = 0; while (resultOffset < size) { const buffer = this._buffers[0]; const copiedLength = Math.min(buffer.length, size - resultOffset); result.set(buffer.subarray(0, copiedLength), resultOffset); resultOffset += copiedLength; if (copiedLength === buffer.length) { this._buffers.shift(); } else { this._buffers[0] = buffer.subarray(copiedLength); } this._bufferedLength -= copiedLength; } return result; } /** * @param {number} nbBytes * @returns {Promise<Uint8Array>} */ async read(nbBytes) { await this.ensure(nbBytes); return this.takeAvailable(nbBytes); } /** * @param {number} nbBytes * @param {(chunk: Uint8Array) => void | Promise<void>} onChunk * @returns {Promise<Uint8Array>} */ async readWithCallback(nbBytes, onChunk) { return this._readConsumed(nbBytes, onChunk); } /** * @param {number} nbBytes * @returns {Promise<number>} */ async skip(nbBytes) { return this._skipConsumed(nbBytes); } /** * @param {number} nbBytes * @param {(chunk: Uint8Array) => void | Promise<void>} onChunk * @returns {Promise<number>} */ async skipWithCallback(nbBytes, onChunk) { return this._skipConsumed(nbBytes, onChunk); } /** * @returns {Promise<number>} */ async skipUntilEnd() { return this._skipConsumed(void 0); } /** * @param {(chunk: Uint8Array) => void | Promise<void>} onChunk * @returns {Promise<number>} */ async skipUntilEndWithCallback(onChunk) { return this._skipConsumed(void 0, onChunk); } /** * @returns {Promise<Uint8Array>} */ async readUntilEnd() { return this._readConsumed(void 0); } /** * @param {(chunk: Uint8Array) => void | Promise<void>} onChunk * @returns {Promise<Uint8Array>} */ async readUntilEndWithCallback(onChunk) { return this._readConsumed(void 0, onChunk); } /** * @param {number | undefined} nbBytes * @param {((chunk: Uint8Array) => void | Promise<void>)=} onChunk * @returns {Promise<number>} */ async _skipConsumed(nbBytes, onChunk) { return this._consume(nbBytes, onChunk, false); } /** * @param {number | undefined} nbBytes * @param {((chunk: Uint8Array) => void | Promise<void>)=} onChunk * @returns {Promise<Uint8Array>} */ async _readConsumed(nbBytes, onChunk) { return this._consume(nbBytes, onChunk, true); } /** * @param {number | undefined} nbBytes * @param {((chunk: Uint8Array) => void | Promise<void>) | undefined} onChunk * @param {boolean} collect * @returns {Promise<any>} */ async _consume(nbBytes, onChunk, collect) { let remaining = nbBytes; let consumed = 0; const chunks = []; while (remaining === void 0 || remaining > 0) { await this.ensure(1); if (this._bufferedLength === 0) { break; } const chunkLength = remaining === void 0 ? this._bufferedLength : Math.min(remaining, this._bufferedLength); const chunk = this.takeAvailable(chunkLength); consumed += chunk.length; if (remaining !== void 0) { remaining -= chunk.length; } if (collect) { chunks.push(chunk); } await (onChunk == null ? void 0 : onChunk(chunk)); } if (!collect) { return consumed; } const result = new Uint8Array(consumed); let offset = 0; for (const chunk of chunks) { result.set(chunk, offset); offset += chunk.length; } return result; } }; // src/fields.js var MAC_EPOCH_TO_UNIX_EPOCH_SECONDS = 2082844800; function decodeFixedPoint(value, fractionalBits) { return value / 2 ** fractionalBits; } function toSignedInt(value, bits) { const maxUnsigned = 2 ** bits; const signedBoundary = 2 ** (bits - 1); return value >= signedBoundary ? value - maxUnsigned : value; } function decodeSignedFixedPoint(value, bits, fractionalBits) { return decodeFixedPoint(toSignedInt(value, bits), fractionalBits); } function bytesField(value, offset, nbBytes) { return { kind: "bytes", value: bytesToHex(value, offset, nbBytes) }; } function fixedPointField(raw, bits, fractionalBits, format) { return { kind: "fixed-point", value: decodeFixedPoint(raw, fractionalBits), raw, format, signed: false, bits }; } function signedFixedPointField(raw, bits, fractionalBits, format) { return { kind: "fixed-point", value: decodeSignedFixedPoint(raw, bits, fractionalBits), raw, format, signed: true, bits }; } function bitsField(raw, totalBits, parts) { var _a, _b; let remainingBits = totalBits; const fields = parts.map((part) => { remainingBits -= part.bits; const value = Math.floor(raw / 2 ** remainingBits) & 2 ** part.bits - 1; return { key: part.key, value, bits: part.bits, shift: remainingBits, mask: (2 ** part.bits - 1) * 2 ** remainingBits }; }); return { kind: "bits", value: (_b = (_a = fields.find((field) => field.key === "value")) == null ? void 0 : _a.value) != null ? _b : raw, raw, bits: totalBits, fields }; } function flagsField(raw, totalBits, flags) { return { kind: "flags", value: raw, raw, bits: totalBits, flags: Object.entries(flags).map(([key, mask]) => ({ key, value: (raw & mask) !== 0, mask })) }; } function unixSecondsToIsoString(unixSeconds) { if (typeof unixSeconds === "bigint") { if (unixSeconds < BigInt(Number.MIN_SAFE_INTEGER) || unixSeconds > BigInt(Number.MAX_SAFE_INTEGER)) { return null; } return unixSecondsToIsoString(Number(unixSeconds)); } const unixMilliseconds = unixSeconds * 1e3; if (!Number.isFinite(unixMilliseconds)) { return null; } const date = new Date(unixMilliseconds); return Number.isNaN(date.getTime()) ? null : date.toISOString(); } function macDateField(value) { const unixSeconds = typeof value === "bigint" ? value - BigInt(MAC_EPOCH_TO_UNIX_EPOCH_SECONDS) : value - MAC_EPOCH_TO_UNIX_EPOCH_SECONDS; return { kind: "date", value, date: unixSecondsToIsoString(unixSeconds), epoch: "1904-01-01T00:00:00.000Z", unit: "seconds" }; } function parsedBoxValue(key, value, meta) { const metadata = typeof meta === "string" ? { description: meta } : meta != null ? meta : {}; const ret = __spreadValues({ key }, normalizeField(value)); if (metadata.offset !== void 0) { ret.offset = metadata.offset; } if (metadata.byteLength !== void 0) { ret.byteLength = metadata.byteLength; } if (metadata.description !== void 0) { ret.description = metadata.description; } return ret; } function structField(fields, layout) { const ret = { kind: "struct", fields }; if (layout !== void 0) { ret.layout = layout; } return ret; } function isParsedField(value) { return typeof value === "object" && value !== null && "kind" in value && typeof value.kind === "string"; } function normalizeField(value) { if (isParsedField(value)) { return value; } if (typeof value === "number") { return { kind: "number", value }; } if (typeof value === "bigint") { return { kind: "bigint", value }; } if (typeof value === "string") { return { kind: "string", value }; } if (typeof value === "boolean") { return { kind: "boolean", value }; } if (Array.isArray(value)) { return { kind: "array", items: value.map((item) => normalizeField(item)) }; } if (value && typeof value === "object") { if (value instanceof Uint8Array) { return { kind: "bytes", value: bytesToHex(value, 0, value.byteLength) }; } return structField( Object.entries(value).map( ([key, fieldValue]) => parsedBoxValue(key, fieldValue) ) ); } if (value === null) { return { kind: "null", value: null }; } throw new TypeError(`Unsupported parsed field value: ${typeof value}`); } // src/BoxReader.js var _buffer, _baseOffset, _values, _issues, _currentOffset, _BoxReader_instances, ensureAvailable_fn, withSpan_fn, pushField_fn, bytesToInt_fn, bytesToUint64BigInt_fn, bytesToInt64BigInt_fn, readAsUtf8_fn, readFourCc_fn, parseNullTerminatedAscii_fn, parseNullTerminatedUtf8_fn; var BoxReader = class { /** * @param {Uint8Array} buffer * @param {number=} baseOffset */ constructor(buffer, baseOffset = 0) { __privateAdd(this, _BoxReader_instances); /** @type {Uint8Array} */ __privateAdd(this, _buffer); /** @type {number} */ __privateAdd(this, _baseOffset); /** @type {import("./types.js").ParsedBoxValue[]} */ __privateAdd(this, _values, []); /** @type {import("./types.js").ParsedBoxIssue[]} */ __privateAdd(this, _issues, []); /** * Current byte position in #buffer, starting at 0 and ending at * `#buffer.length`. * * Each read operation will advance this cursor in #buffer. */ __privateAdd(this, _currentOffset, 0); __privateSet(this, _buffer, buffer); __privateSet(this, _baseOffset, baseOffset); } /** * Get the number of bytes that are not yet read. * @returns {number} */ getRemainingLength() { return Math.max(0, __privateGet(this, _buffer).length - __privateGet(this, _currentOffset)); } /** * If `true`, the current box is already fully parsed. * @returns {boolean} */ isFinished() { return __privateGet(this, _buffer).length <= __privateGet(this, _currentOffset); } /** * Returns the total length of the current box in bytes. * @returns {number} */ getTotalLength() { return __privateGet(this, _buffer).length; } /** * Returns the current byte position in the box payload. * @returns {number} */ getCurrentOffset() { return __privateGet(this, _currentOffset); } /** * Read the next `nbBytes` bytes, convert it into the corresponding * unsigned integer and store it as a field named `key` for the current box. * * Throws if less that `nbBytes` bytes remain in the current box. * * Throws if 8 bytes or more is read. If you need to read 8 bytes, use * `fieldUint64` (which creates a bigint). * * @template {NumberKeys<T>} K * @param {K} key * @param {number} nbBytes * @param {string|ParsedBoxFieldMetadata} [meta] * @returns {number} */ fieldUint(key, nbBytes, meta) { const baseOffset = __privateGet(this, _currentOffset); return __privateMethod(this, _BoxReader_instances, pushField_fn).call(this, key, __privateMethod(this, _BoxReader_instances, bytesToInt_fn).call(this, nbBytes), __spreadValues({}, __privateMethod(this, _BoxReader_instances, withSpan_fn).call(this, baseOffset, meta))); } /** * Read the next 8 bytes, convert it into the corresponding * unsigned bigint and store it as a field named `key` for the current box. * * Throws if less that 8 bytes remain in the current box. * * @template {BigIntKeys<T>} K * @param {K} key * @param {string|ParsedBoxFieldMetadata} [meta] * @returns {bigint} */ fieldUint64(key, meta) { const baseOffset = __privateGet(this, _currentOffset); return __privateMethod(this, _BoxReader_instances, pushField_fn).call(this, key, __privateMethod(this, _BoxReader_instances, bytesToUint64BigInt_fn).call(this), __spreadValues({}, __privateMethod(this, _BoxReader_instances, withSpan_fn).call(this, baseOffset, meta))); } /** * Read the next 8 bytes, convert it into the corresponding * **signed** bigint and store it as a field named `key` for the current box. * * Throws if less that 8 bytes remain in the current box. * * @template {BigIntKeys<T>} K * @param {K} key * @param {string|ParsedBoxFieldMetadata} [meta] * @returns {bigint} */ fieldInt64(key, meta) { const baseOffset = __privateGet(this, _currentOffset); return __privateMethod(this, _BoxReader_instances, pushField_fn).call(this, key, __privateMethod(this, _BoxReader_instances, bytesToInt64BigInt_fn).call(this), __spreadValues({}, __privateMethod(this, _BoxReader_instances, withSpan_fn).call(this, baseOffset, meta))); } /** * @template {NumberKeys<T>} K * @param {K} key * @param {number} nbBytes * @param {string|ParsedBoxFieldMetadata} [meta] * @returns {number} */ fieldSignedInt(key, nbBytes, meta) { const baseOffset = __privateGet(this, _currentOffset); return __privateMethod(this, _BoxReader_instances, pushField_fn).call(this, key, toSignedInt(__privateMethod(this, _BoxReader_instances, bytesToInt_fn).call(this, nbBytes), nbBytes * 8), __spreadValues({}, __privateMethod(this, _BoxReader_instances, withSpan_fn).call(this, baseOffset, meta))); } /** * @template {BytesKeys<T>} K * @param {K} key * @param {number} nbBytes * @param {string|ParsedBoxFieldMetadata} [meta] * @returns {Uint8Array} */ fieldBytes(key, nbBytes, meta) { __privateMethod(this, _BoxReader_instances, ensureAvailable_fn).call(this, nbBytes); const baseOffset = __privateGet(this, _currentOffset); const value = __privateGet(this, _buffer).slice(baseOffset, baseOffset + nbBytes); __privateSet(this, _currentOffset, __privateGet(this, _currentOffset) + nbBytes); __privateGet(this, _values).push( parsedBoxValue( key, bytesField(__privateGet(this, _buffer), baseOffset, nbBytes), __privateMethod(this, _BoxReader_instances, withSpan_fn).call(this, baseOffset, meta) ) ); return value; } /** * @template {StringKeys<T>} K * @param {K} key * @param {string|ParsedBoxFieldMetadata} [meta] * @returns {string} */ fieldNullTerminatedAscii(key, meta) { const baseOffset = __privateGet(this, _currentOffset); return __privateMethod(this, _BoxReader_instances, pushField_fn).call(this, key, __privateMethod(this, _BoxReader_instances, parseNullTerminatedAscii_fn).call(this), __spreadValues({}, __privateMethod(this, _BoxReader_instances, withSpan_fn).call(this, baseOffset, meta))); } /** * @template {StringKeys<T>} K * @param {K} key * @param {string|ParsedBoxFieldMetadata} [meta] * @returns {string} */ fieldNullTerminatedUtf8(key, meta) { const baseOffset = __privateGet(this, _currentOffset); return __privateMethod(this, _BoxReader_instances, pushField_fn).call(this, key, __privateMethod(this, _BoxReader_instances, parseNullTerminatedUtf8_fn).call(this), __spreadValues({}, __privateMethod(this, _BoxReader_instances, withSpan_fn).call(this, baseOffset, meta))); } /** * Decode the next 4 bytes into a string if printable ASCII, or the * corresponding 32 bit integer if not, and set it as a field named * `key` on the current box. * * Throws if less than 4 are remaining in the buffer. * * @template {StringKeys<T>} K * @param {K} key * @param {string|ParsedBoxFieldMetadata} [meta] * @returns {string|number} */ fieldFourCc(key, meta) { const baseOffset = __privateGet(this, _currentOffset); return __privateMethod(this, _BoxReader_instances, pushField_fn).call(this, key, __privateMethod(this, _BoxReader_instances, readFourCc_fn).call(this), __spreadValues({}, __privateMethod(this, _BoxReader_instances, withSpan_fn).call(this, baseOffset, meta))); } /** * @template {FixedPointKeys<T>} K * @param {K} key * @param {number} nbBytes * @param {number} fractionalBits * @param {string} format * @param {string|ParsedBoxFieldMetadata} [meta] * @returns {import("./types.js").ParsedFixedPointField} */ fieldFixedPoint(key, nbBytes, fractionalBits, format, meta) { const baseOffset = __privateGet(this, _currentOffset); const value = fixedPointField( __privateMethod(this, _BoxReader_instances, bytesToInt_fn).call(this, nbBytes), nbBytes * 8, fractionalBits, format ); __privateMethod(this, _BoxReader_instances, pushField_fn).call(this, key, value, __spreadValues({}, __privateMethod(this, _BoxReader_instances, withSpan_fn).call(this, baseOffset, meta))); return value; } /** * @template {FixedPointKeys<T>} K * @param {K} key * @param {number} nbBytes * @param {number} bits * @param {number} fractionalBits * @param {string} format * @param {string|ParsedBoxFieldMetadata} [meta] * @returns {import("./types.js").ParsedFixedPointField} */ fieldSignedFixedPoint(key, nbBytes, bits, fractionalBits, format, meta) { const baseOffset = __privateGet(this, _currentOffset); const value = signedFixedPointField( __privateMethod(this, _BoxReader_instances, bytesToInt_fn).call(this, nbBytes), bits, fractionalBits, format ); __privateMethod(this, _BoxReader_instances, pushField_fn).call(this, key, value, __spreadValues({}, __privateMethod(this, _BoxReader_instances, withSpan_fn).call(this, baseOffset, meta))); return value; } /** * @template {DateKeys<T>} K * @param {K} key * @param {number} nbBytes * @param {string|ParsedBoxFieldMetadata} [meta] * @returns {import("./types.js").ParsedDateField} */ fieldMacDate(key, nbBytes, meta) { const baseOffset = __privateGet(this, _currentOffset); const raw = nbBytes === 8 ? __privateMethod(this, _BoxReader_instances, bytesToUint64BigInt_fn).call(this) : __privateMethod(this, _BoxReader_instances, bytesToInt_fn).call(this, nbBytes); const value = macDateField(raw); __privateMethod(this, _BoxReader_instances, pushField_fn).call(this, key, value, __spreadValues({}, __privateMethod(this, _BoxReader_instances, withSpan_fn).call(this, baseOffset, meta))); return value; } /** * @template {BitsKeys<T>} K * @param {K} key * @param {number} nbBytes * @param {import("./types.js").ParsedBitsFieldPartDefinition[]} parts * @param {string|ParsedBoxFieldMetadata} [meta] * @returns {number} */ fieldBits(key, nbBytes, parts, meta) { const baseOffset = __privateGet(this, _currentOffset); const value = bitsField(__privateMethod(this, _BoxReader_instances, bytesToInt_fn).call(this, nbBytes), nbBytes * 8, parts); __privateMethod(this, _BoxReader_instances, pushField_fn).call(this, key, value, __spreadValues({}, __privateMethod(this, _BoxReader_instances, withSpan_fn).call(this, baseOffset, meta))); return value.value; } /** * @template {FlagsKeys<T>} K * @param {K} key * @param {number} nbBytes * @param {Record<string, number>} flags * @param {string|ParsedBoxFieldMetadata} [meta] * @returns {number} */ fieldFlags(key, nbBytes, flags, meta) { const baseOffset = __privateGet(this, _currentOffset); const value = flagsField(__privateMethod(this, _BoxReader_instances, bytesToInt_fn).call(this, nbBytes), nbBytes * 8, flags); __privateMethod(this, _BoxReader_instances, pushField_fn).call(this, key, value, __spreadValues({}, __privateMethod(this, _BoxReader_instances, withSpan_fn).call(this, baseOffset, meta))); return value.value; } /** * @template V * @template {KeysForValue<T, V>} K * @param {K} key * @param {V} value * @param {string | ParsedBoxFieldMetadata=} [meta] * @returns {V} */ addField(key, value, meta) { return __privateMethod(this, _BoxReader_instances, pushField_fn).call(this, key, value, typeof meta === "string" || (meta == null ? void 0 : meta.offset) === void 0 ? meta : __spreadProps(__spreadValues({}, meta), { offset: __privateGet(this, _baseOffset) + meta.offset })); } /** * @param {"warning" | "error"} severity * @param {string} message * @returns {void} */ addIssue(severity, message) { __privateGet(this, _issues).push({ severity, message }); } /** * Read the next `nbBytes` bytes and returns the corresponding * unsigned integer. * * Throws if less that `nbBytes` bytes remain in the current box. * * Throws if 8 bytes or more is read. If you need to read 8 bytes, use * `fieldUint64` (which creates a bigint). * @param {number} nbBytes * @returns {number} */ readUint(nbBytes) { return __privateMethod(this, _BoxReader_instances, bytesToInt_fn).call(this, nbBytes); } /** * Read the next 8 bytes and returns the corresponding bigint. * * Throws if less that 8 bytes remain in the current box. * * @returns {bigint} */ readUint64() { return __privateMethod(this, _BoxReader_instances, bytesToUint64BigInt_fn).call(this); } /** * Parse the next 4 bytes as a **signed** (two's complement) 64-bit integer * into a bigint. * * Throws if less than 8 bytes are remaining in the buffer. * * @returns {bigint} */ readInt64() { return __privateMethod(this, _BoxReader_instances, bytesToInt64BigInt_fn).call(this); } /** * Read the next bytes and return it as an Uint8Array. * * Throws if less than `nbBytes` bytes are remaining in the buffer. * * @param {number} nbBytes * @returns {Uint8Array} */ readBytes(nbBytes) { __privateMethod(this, _BoxReader_instances, ensureAvailable_fn).call(this, nbBytes); const res = __privateGet(this, _buffer).slice( __privateGet(this, _currentOffset), __privateGet(this, _currentOffset) + nbBytes ); __privateSet(this, _currentOffset, __privateGet(this, _currentOffset) + nbBytes); return res; } /** * Decode the next `nbBytes` as UTF-8 text. * * Throws if less than `nbBytes` bytes are remaining in the buffer. * * @param {number} nbBytes * @returns {string} */ readAsUtf8(nbBytes) { return __privateMethod(this, _BoxReader_instances, readAsUtf8_fn).call(this, nbBytes); } /** * Decode the next 4 bytes into a string if printable ASCII, or the * corresponding 32 bit integer if not. * * Throws if less than 4 are remaining in the buffer. * * @returns {string|number} */ readFourCc() { return __privateMethod(this, _BoxReader_instances, readFourCc_fn).call(this); } /** @returns {import("./types.js").ParsedBoxValue[]} */ getValues() { return __privateGet(this, _values).slice(); } /** @returns {import("./types.js").ParsedBoxIssue[]} */ getIssues() { return __privateGet(this, _issues).slice(); } }; _buffer = new WeakMap(); _baseOffset = new WeakMap(); _values = new WeakMap(); _issues = new WeakMap(); _currentOffset = new WeakMap(); _BoxReader_instances = new WeakSet(); // Now, private methods implementing the logic /** * @param {number} nbBytes * @returns {void} */ ensureAvailable_fn = function(nbBytes) { if (!Number.isInteger(nbBytes) || nbBytes < 0) { throw new Error(`Cannot read an invalid byte length: ${nbBytes}.`); } const remaining = __privateGet(this, _buffer).length - __privateGet(this, _currentOffset); if (remaining < nbBytes) { throw new Error( `Cannot read ${nbBytes} byte(s) at offset ${__privateGet(this, _currentOffset)}: only ${Math.max(0, remaining)} byte(s) remaining.` ); } }; /** * @param {number} baseOffset * @param {string | ParsedBoxFieldMetadata | undefined} meta * @returns {ParsedBoxFieldMetadata} */ withSpan_fn = function(baseOffset, meta) { var _a; const normalized = typeof meta === "string" ? { description: meta } : meta != null ? meta : {}; return __spreadProps(__spreadValues({}, normalized), { offset: normalized.offset === void 0 ? __privateGet(this, _baseOffset) + baseOffset : __privateGet(this, _baseOffset) + normalized.offset, byteLength: (_a = normalized.byteLength) != null ? _a : __privateGet(this, _currentOffset) - baseOffset }); }; /** * @template V * @template {KeysForValue<T, V>} K * @param {K} key * @param {V} value * @param {string | ParsedBoxFieldMetadata=} [meta] * @returns {V} */ pushField_fn = function(key, value, meta) { __privateGet(this, _values).push(parsedBoxValue(key, value, meta)); return value; }; /** * Returns the N next bytes, as a single number. * * /!\ only work for now for 1, 2, 3, 4, 5 or 8 bytes. * * /!\ Depending on the size of the number, it may be larger than JS' * limit. * * @param {number} nbBytes * @returns {number} */ bytesToInt_fn = function(nbBytes) { __privateMethod(this, _BoxReader_instances, ensureAvailable_fn).call(this, nbBytes); let res; switch (nbBytes) { case 1: res = __privateGet(this, _buffer)[__privateGet(this, _currentOffset)]; break; case 2: res = be2toi(__privateGet(this, _buffer), __privateGet(this, _currentOffset)); break; case 3: res = be3toi(__privateGet(this, _buffer), __privateGet(this, _currentOffset)); break; case 4: res = be4toi(__privateGet(this, _buffer), __privateGet(this, _currentOffset)); break; case 5: res = be5toi(__privateGet(this, _buffer), __privateGet(this, _currentOffset)); break; default: throw new Error("not implemented yet."); } __privateSet(this, _currentOffset, __privateGet(this, _currentOffset) + nbBytes); return res; }; /** * Returns the next 8 bytes as an exact unsigned 64-bit bigint. * @returns {bigint} */ bytesToUint64BigInt_fn = function() { __privateMethod(this, _BoxReader_instances, ensureAvailable_fn).call(this, 8); const hex = bytesToHex(__privateGet(this, _buffer), __privateGet(this, _currentOffset), 8); const toBigint = hexToBigInt(hex); __privateSet(this, _currentOffset, __privateGet(this, _currentOffset) + 8); return toBigint; }; /** * Returns the next 8 bytes as an exact signed 64-bit bigint. * @returns {bigint} */ bytesToInt64BigInt_fn = function() { __privateMethod(this, _BoxReader_instances, ensureAvailable_fn).call(this, 8); const hex = bytesToHex(__privateGet(this, _buffer), __privateGet(this, _currentOffset), 8); const toBigInt = BigInt.asIntN(64, hexToBigInt(hex)); __privateSet(this, _currentOffset, __privateGet(this, _currentOffset) + 8); return toBigInt; }; /** * Returns the N next bytes into a string. * @param {number} nbBytes * @returns {string} */ readAsUtf8_fn = function(nbBytes) { __privateMethod(this, _BoxReader_instances, ensureAvailable_fn).call(this, nbBytes); const res = utf8ToStr(__privateGet(this, _buffer), __privateGet(this, _currentOffset), nbBytes); __privateSet(this, _currentOffset, __privateGet(this, _currentOffset) + nbBytes); return res; }; readFourCc_fn = function() { __privateMethod(this, _BoxReader_instances, ensureAvailable_fn).call(this, 4); let isPrintable = true; for (let i = __privateGet(this, _currentOffset); i < __privateGet(this, _currentOffset) + 4; i++) { const b = __privateGet(this, _buffer)[i]; if (b < 32 || b > 126) { isPrintable = false; break; } } const res = isPrintable ? ( // Convert to string, same codes as UTF-16's lower byte String.fromCharCode( __privateGet(this, _buffer)[__privateGet(this, _currentOffset)], __privateGet(this, _buffer)[__privateGet(this, _currentOffset) + 1], __privateGet(this, _buffer)[__privateGet(this, _currentOffset) + 2], __privateGet(this, _buffer)[__privateGet(this, _currentOffset) + 3] ) ) : ( // Fallback: return unsigned 32-bit number (big-endian) be4toi(__privateGet(this, _buffer), __privateGet(this, _currentOffset)) ); __privateSet(this, _currentOffset, __privateGet(this, _currentOffset) + 4); return res; }; /** * @returns {string} */ parseNullTerminatedAscii_fn = function() { const bytes = []; while (!this.isFinished()) { const value = this.readUint(1); if (value === 0) { break; } bytes.push(value); } return String.fromCharCode.apply(null, bytes); }; /** * @returns {string} */ parseNullTerminatedUtf8_fn = function() { const bytes = []; while (!this.isFinished()) { const value = this.readUint(1); if (value === 0) { break; } bytes.push(value); } return utf8ToStr(new Uint8Array(bytes)); }; function hexToBigInt(hex) { return BigInt(`0x${hex}`); } // src/boxes/helpers.js function createTrackReferenceTypeBox(name, description) { return { name, description, parser(reader) { const track_IDs = []; const trackIdsOffset = reader.getCurrentOffset(); while (reader.getRemainingLength() >= 4) { track_IDs.push(reader.readUint(4)); } reader.addField("track_IDs", track_IDs, { offset: trackIdsOffset, byteLength: reader.getCurrentOffset() - trackIdsOffset }); if (!reader.isFinished()) { reader.fieldBytes("trailing_bytes", reader.getRemainingLength()); } } }; } function parseTransformationMatrix(r) { return structField( [ parsedBoxValue( "a", signedFixedPointField(r.readUint(4), 32, 16, "16.16") ), parsedBoxValue( "b", signedFixedPointField(r.readUint(4), 32, 16, "16.16") ), parsedBoxValue("u", signedFixedPointField(r.readUint(4), 32, 30, "2.30")), parsedBoxValue( "c", signedFixedPointField(r.readUint(4), 32, 16, "16.16") ), parsedBoxValue( "d", signedFixedPointField(r.readUint(4), 32, 16, "16.16") ), parsedBoxValue("v", signedFixedPointField(r.readUint(4), 32, 30, "2.30")), parsedBoxValue( "x", signedFixedPointField(r.readUint(4), 32, 16, "16.16") ), parsedBoxValue( "y", signedFixedPointField(r.readUint(4), 32, 16, "16.16") ), parsedBoxValue("w", signedFixedPointField(r.readUint(4), 32, 30, "2.30")) ], "matrix-3x3" ); } function parsePascalAsciiString(r, length) { const stringLength = Math.min(r.readUint(1), length - 1); let value = ""; for (let i = 0; i < stringLength; i++) { const byte = r.readUint(1); if (byte < 32 || byte > 126) { throw new Error( `Non-printable ASCII character found: 0x${byte.toString(16).toUpperCase()}` ); } value += String.fromCharCode(byte); } const paddingLength = length - 1 - stringLength; if (paddingLength > 0) { r.readBytes(paddingLength); } return value; } function readVisualSampleEntry(reader) { const reserved = []; const reservedOffset = reader.getCurrentOffset(); for (let i = 0; i < 6; i++) { reserved.push(reader.readUint(1)); } reader.addField("reserved", reserved, { offset: reservedOffset, byteLength: reader.getCurrentOffset() - reservedOffset }); reader.fieldUint("data_reference_index", 2); reader.fieldUint("pre_defined", 2); reader.fieldUint("reserved_1", 2); const preDefined1Offset = reader.getCurrentOffset(); const preDefined1 = [ reader.readUint(4), reader.readUint(4), reader.readUint(4) ]; reader.addField("pre_defined_1", preDefined1, { offset: preDefined1Offset, byteLength: reader.getCurrentOffset() - preDefined1Offset }); reader.fieldUint("width", 2); reader.fieldUint("height", 2); reader.fieldFixedPoint("horizresolution", 4, 16, "16.16"); reader.fieldFixedPoint("vertresolution", 4, 16, "16.16"); reader.fieldUint("reserved_2", 4); reader.fieldUint("frame_count", 2); const compressorNameOffset = reader.getCurrentOffset(); reader.addField("compressorname", parsePascalAsciiString(reader, 32), { offset: compressorNameOffset, byteLength: reader.getCurrentOffset() - compressorNameOffset }); reader.fieldUint("depth", 2); reader.fieldUint("pre_defined", 2); } function parseAudioSampleEntry(r) { const reserved = []; const reservedOffset = r.getCurrentOffset(); for (let i = 0; i < 6; i++) { reserved.push(r.readUint(1)); } r.addField("reserved", reserved, { offset: reservedOffset, byteLength: r.getCurrentOffset() - reservedOffset }); r.fieldUint("data_reference_index", 2); const version = r.fieldUint("version", 2); r.fieldUint("revision_level", 2); r.fieldUint("vendor", 4); r.fieldUint("channelcount", 2); r.fieldUint("samplesize", 2); r.fieldUint("compression_id", 2); r.fieldUint("packet_size", 2); r.fieldFixedPoint("samplerate", 4, 16, "16.16"); if (version === 1) { r.fieldUint("samples_per_packet", 4); r.fieldUint("bytes_per_packet", 4); r.fieldUint("bytes_per_frame", 4); r.fieldUint("bytes_per_sample", 4); } else if (version === 2) { r.fieldUint("struct_size", 4); r.fieldFixedPoint("sample_rate", 4, 16, "16.16"); r.fieldUint("channel_count", 4); r.fieldUint("reserved_1", 4); r.fieldUint("bits_per_channel", 4); r.fieldUint("format_specific_flags", 4); r.fieldUint("bytes_per_audio_packet", 4); r.fieldUint("LPCM_frames_per_audio_packet", 4); } } function parseDescriptorLength(r) { let length = 0; let size = 0; while (size < 4) { const currentByte = r.readUint(1); size += 1; length = length << 7 | currentByte & 127; if ((currentByte & 128) === 0) { return { length, size }; } } throw new Error("invalid descriptor length"); } function parseNestedDescriptors(r, size) { const descriptors = []; let remaining = size; while (remaining > 0) { const before = r.getRemainingLength(); const descriptor = parseDescriptor(r); const consumed = before - r.getRemainingLength(); remaining -= consumed; descriptors.push(descriptor); } if (remaining !== 0) { throw new Error("descriptor size mismatch"); } return descriptors; } function parseDescriptorPayload(r, tag, size) { if (tag === 3) { const es_id = r.readUint(2); const flags = r.readUint(1); const ret = { es_id, stream_dependence_flag: !!(flags & 128), URL_flag: !!(flags & 64), OCRstream_flag: !!(flags & 32), stream_priority: flags & 31 }; let consumed = 3; if (ret.stream_dependence_flag) { ret.depends_on_es_id = r.readUint(2); consumed += 2; } if (ret.URL_flag) { const urlLength = r.readUint(1); ret.URL_length = urlLength; ret.URL_string = urlLength > 0 ? r.readAsUtf8(urlLength) : ""; consumed += 1 + urlLength; } if (ret.OCRstream_flag) { ret.ocr_es_id = r.readUint(2); consumed += 2; } if (size > consumed) { ret.descriptors = parseNestedDescriptors(r, size - consumed); } return ret; } if (tag === 4) { const objectTypeIndication = r.readUint(1); const streamByte = r.readUint(1); const ret = { object_type_indication: objectTypeIndication, stream_type: streamByte >> 2 & 63, up_stream: !!(streamByte >> 1 & 1), reserved: streamByte & 1, buffer_size_db: r.readUint(3), max_bitrate: r.readUint(4), avg_bitrate: r.readUint(4) }; if (size > 13) { ret.descriptors = parseNestedDescriptors(r, size - 13); } return ret; } if (tag === 5) { return { decoder_specific_info: size > 0 ? r.readBytes(size) : "" }; } if (tag === 6) { return { predefined: r.readUint(1), remaining_payload: size > 1 ? r.readBytes(size - 1) : "" }; } return { data: size > 0 ? r.readBytes(size) : "" }; } function parseDescriptor(r) { const tag = r.readUint(1); const { length, size } = parseDescriptorLength(r); return { tag, size: length, header_size: size + 1, payload: parseDescriptorPayload(r, tag, length) }; } // src/boxes/ac-3.js var ac_3_default = { name: "AC-3 Audio Sample Entry", description: "Describes AC-3 audio samples and their dac3 decoder-specific box.", container: true, parser(reader) { parseAudioSampleEntry(reader); } }; // src/boxes/av01.js var av01_default = { name: "AV1 Sample Entry", description: "Describes AV1 video samples and carries child decoder configuration boxes such as av1C.", container: true, parser(reader) { readVisualSampleEntry(read