UNPKG

@svta/common-media-library

Version:
286 lines 9.93 kB
import { CONTAINERS } from './CONTAINERS.js'; import { DATA } from './fields/DATA.js'; import { INT } from './fields/INT.js'; import { STRING } from './fields/STRING.js'; import { TEMPLATE } from './fields/TEMPLATE.js'; import { UINT } from './fields/UINT.js'; import { UTF8 } from './fields/UTF8.js'; import { readData } from './readers/readData.js'; import { readInt } from './readers/readInt.js'; import { readString } from './readers/readString.js'; import { readTemplate } from './readers/readTemplate.js'; import { readTerminatedString } from './readers/readTerminatedString.js'; import { readUint } from './readers/readUint.js'; import { readUtf8String } from './readers/readUtf8String.js'; import { readUtf8TerminatedString } from './readers/readUtf8TerminatedString.js'; /** * ISO BMFF data view. Similar to DataView, but with additional methods for reading ISO BMFF data. * It implements the iterator protocol, so it can be used in a for...of loop. * * @group ISOBMFF * * @beta */ export class IsoView { /** * Creates a new IsoView instance. Similar to DataView, but with additional * methods for reading ISO BMFF data. It implements the iterator protocol, * so it can be used in a for...of loop. * * @param raw - The raw data to view. * @param config - The configuration for the IsoView. */ constructor(raw, config) { this.truncated = false; /** * Creates a new IsoView instance with a slice of the current data view. * * @param size - The size of the slice. * @returns A new IsoView instance. */ this.slice = (size) => { const dataView = new DataView(this.dataView.buffer, this.offset, size); this.offset += size; return new IsoView(dataView, this.config); }; this.read = (type, size = 0) => { // TODO: Change all sizes from bits to bytes const { dataView, offset } = this; let result; let cursor = size; switch (type) { case UINT: result = readUint(dataView, offset, size); break; case INT: result = readInt(dataView, offset, size); break; case TEMPLATE: result = readTemplate(dataView, offset, size); break; case STRING: if (size === -1) { result = readTerminatedString(dataView, offset); cursor = result.length + 1; } else { result = readString(dataView, offset, size); } break; case DATA: result = readData(dataView, offset, size); cursor = result.length; break; case UTF8: if (size === -1) { result = readUtf8TerminatedString(dataView, offset); cursor = result.length + 1; } else { result = readUtf8String(dataView, offset); } break; default: result = -1; } this.offset += cursor; return result; }; /** * Reads a unsigned integer from the data view. * * @param size - The size of the integer in bytes. * @returns The unsigned integer. */ this.readUint = (size) => { return this.read(UINT, size); }; /** * Reads a signed integer from the data view. * * @param size - The size of the integer in bytes. * @returns The signed integer. */ this.readInt = (size) => { return this.read(INT, size); }; /** * Reads a string from the data view. * * @param size - The size of the string in bytes. * @returns The string. */ this.readString = (size) => { return this.read(STRING, size); }; /** * Reads a template from the data view. * * @param size - The size of the template in bytes. * @returns The template. */ this.readTemplate = (size) => { return this.read(TEMPLATE, size); }; /** * Reads a byte array from the data view. * * @param size - The size of the data in bytes. * @returns The data. */ this.readData = (size) => { return this.read(DATA, size); }; /** * Reads a UTF-8 string from the data view. * * @param size - The size of the string in bytes. * @returns The UTF-8 string. */ this.readUtf8 = (size) => { return this.read(UTF8, size); }; /** * Reads a full box from the data view. * * @returns The full box. */ this.readFullBox = () => { return { version: this.readUint(1), flags: this.readUint(3), }; }; /** * Reads an array of values from the data view. * * @param type - The type of the values. * @param size - The size of the values in bytes. * @param length - The number of values to read. * @returns The array of values. */ this.readArray = (type, size, length) => { const value = []; for (let i = 0; i < length; i++) { value.push(this.read(type, size)); } return value; }; /** * Reads a raw box from the data view. * * @returns The box. */ this.readBox = () => { const { dataView, offset } = this; // read box size and type without advancing the cursor in case the box is truncated let cursor = 0; const box = { size: readUint(dataView, offset, 4), type: readString(dataView, offset + 4, 4), }; cursor += 8; if (box.size === 1) { box.largesize = readUint(dataView, offset + cursor, 8); cursor += 8; } const actualSize = box.largesize || box.size; if (this.cursor + actualSize > dataView.byteLength) { this.truncated = true; throw new Error('Truncated box'); } this.offset += cursor; if (box.type === 'uuid') { box.usertype = this.readArray('uint', 1, 16); } const viewSize = box.size === 0 ? this.bytesRemaining : actualSize - cursor; box.data = this.slice(viewSize); return box; }; /** * Reads a number of boxes from the data view. * * @param length - The number of boxes to read. * @returns The boxes. */ this.readBoxes = (length) => { const result = []; for (const box of this) { result.push(box); if (length > 0 && result.length >= length) { break; } } return result; }; /** * Reads a number of entries from the data view. * * @param length - The number of entries to read. * @param map - The function to map the entries. * @returns The entries. */ this.readEntries = (length, map) => { const result = []; for (let i = 0; i < length; i++) { result.push(map()); } return result; }; this.dataView = (raw instanceof ArrayBuffer) ? new DataView(raw) : (raw instanceof Uint8Array) ? new DataView(raw.buffer, raw.byteOffset, raw.byteLength) : raw; this.offset = this.dataView.byteOffset; this.config = config || { recursive: false, parsers: {} }; } /** * The current byteoffset in the data view. */ get cursor() { return this.offset - this.dataView.byteOffset; } /** * Whether the end of the data view has been reached. */ get done() { return this.cursor >= this.dataView.byteLength || this.truncated; } /** * The number of bytes remaining in the data view. */ get bytesRemaining() { return this.dataView.byteLength - this.cursor; } /** * Iterates over the boxes in the data view. * * @returns A generator of boxes. */ *[Symbol.iterator]() { const { parsers = {}, recursive = false } = this.config; while (!this.done) { try { const { type, data, ...rest } = this.readBox(); const box = { type, ...rest }; const parser = parsers[type] || parsers[type.trim()]; // url and urn boxes have a trailing space in their type field if (parser) { Object.assign(box, parser(data, this.config)); } box.view = data; if (CONTAINERS.includes(type)) { const boxes = []; for (const child of data) { if (recursive) { yield child; } boxes.push(child); } box.boxes = boxes; } yield box; } catch (error) { break; } } } } //# sourceMappingURL=IsoView.js.map