/** * Copyright 2015 CANAL+ Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import log from "../../../log"; import assert from "../../../utils/assert"; import { be4toi, be8toi } from "../../../utils/byte_parsing"; /** * From a given buffer representing ISOBMFF data, browses inner boxes in * `childNames`, each element being a child box of the one before it. * Returns `null` if one of the child (or if the parent) is not found. * @param {Uint8Array} buf * @param {number[]} childNames * @returns {Uint8Array|null} */ function getChildBox( buf: Uint8Array, childNames: number[], ): Uint8Array | null { let currBox = buf; for (const childName of childNames) { const box = getBoxContent(currBox, childName); if (box === null) { return null; } currBox = box; } return currBox; } /** * Returns the content of a box based on its name. * `null` if not found. * @param {Uint8Array} buf - the isobmff data * @param {Number} boxName - the 4-letter 'name' of the box as a 4 byte integer * generated from encoding the corresponding ASCII in big endian. * @returns {UInt8Array|null} */ function getBoxContent( buf: Uint8Array, boxName: number, ): Uint8Array | null { const offsets = getBoxOffsets(buf, boxName); return offsets !== null ? buf.subarray(offsets[1], offsets[2]) : null; } /** * Reads the whole ISOBMFF and returns the content of all boxes with the given * name, in order. * @param {Uint8Array} buf - the isobmff data * @param {Number} boxName - the 4-letter 'name' of the box as a 4 byte integer * generated from encoding the corresponding ASCII in big endian. * @returns {Array.} */ function getBoxesContent( buf: Uint8Array, boxName: number, ): Array> { const ret = []; let currentBuf = buf; while (true) { const offsets = getBoxOffsets(currentBuf, boxName); if (offsets === null) { return ret; } // Guard against a (very highly improbable) infinite loop assert(offsets[2] !== 0 && currentBuf.length !== 0); ret.push(currentBuf.subarray(offsets[1], offsets[2])); currentBuf = currentBuf.subarray(offsets[2]); } } /** * Returns an ISOBMFF box - size and name included - based on its name. * `null` if not found. * @param {Uint8Array} buf - the isobmff data * @param {Number} boxName - the 4-letter 'name' of the box as a 4 byte integer * generated from encoding the corresponding ASCII in big endian. * @returns {UInt8Array|null} */ function getBox( buf: Uint8Array, boxName: number, ): Uint8Array | null { const offsets = getBoxOffsets(buf, boxName); return offsets !== null ? buf.subarray(offsets[0], offsets[2]) : null; } /** * Returns byte offsets for the start of the box, the start of its content and * the end of the box (not inclusive). * * `null` if not found. * * If found, the tuple returned has three elements, all numbers: * 1. The starting byte corresponding to the start of the box (from its size) * 2. The beginning of the box content - meaning the first byte after the * size and the name of the box. * 3. The first byte after the end of the box, might be equal to `buf`'s * length if we're considering the last box. * @param {Uint8Array} buf - the isobmff data * @param {Number} boxName - the 4-letter 'name' of the box as a 4 byte integer * generated from encoding the corresponding ASCII in big endian. * @returns {Array.|null} */ function getBoxOffsets( buf: Uint8Array, boxName: number, ): | [ number /* start byte */, number /* First byte after the size and name (where the content begins)*/, number /* end byte, not included. */, ] | null { const len = buf.length; let boxBaseOffset = 0; let name: number; let lastBoxSize: number = 0; let lastOffset; while (boxBaseOffset + 8 <= len) { lastOffset = boxBaseOffset; lastBoxSize = be4toi(buf, lastOffset); lastOffset += 4; name = be4toi(buf, lastOffset); lastOffset += 4; if (lastBoxSize === 0) { lastBoxSize = len - boxBaseOffset; } else if (lastBoxSize === 1) { if (lastOffset + 8 > len) { return null; } lastBoxSize = be8toi(buf, lastOffset); lastOffset += 8; } if (lastBoxSize < 0) { throw new Error("ISOBMFF: Size out of range"); } if (name === boxName) { if (boxName === 0x75756964 /* === "uuid" */) { lastOffset += 16; // Skip uuid name } return [boxBaseOffset, lastOffset, boxBaseOffset + lastBoxSize]; } else { boxBaseOffset += lastBoxSize; } } return null; } /** * Gives the content of a specific UUID box. * `undefined` if that box is not found. * * If found, the returned Uint8Array contains just the box's content: the box * without its name and size. * @param {Uint8Array} buf * @param {Number} id1 * @param {Number} id2 * @param {Number} id3 * @param {Number} id4 * @returns {Uint8Array|undefined} */ function getUuidContent( buf: Uint8Array, id1: number, id2: number, id3: number, id4: number, ): Uint8Array | undefined { const len = buf.length; let boxSize: number; for (let boxBaseOffset = 0; boxBaseOffset < len; boxBaseOffset += boxSize) { let currentOffset = boxBaseOffset; boxSize = be4toi(buf, currentOffset); currentOffset += 4; const boxName = be4toi(buf, currentOffset); currentOffset += 4; if (boxSize === 0) { boxSize = len - boxBaseOffset; } else if (boxSize === 1) { if (currentOffset + 8 > len) { return undefined; } boxSize = be8toi(buf, currentOffset); currentOffset += 8; } if ( boxName === 0x75756964 /* === "uuid" */ && currentOffset + 16 <= len && be4toi(buf, currentOffset) === id1 && be4toi(buf, currentOffset + 4) === id2 && be4toi(buf, currentOffset + 8) === id3 && be4toi(buf, currentOffset + 12) === id4 ) { currentOffset += 16; return buf.subarray(currentOffset, boxBaseOffset + boxSize); } } } /** * For the next encountered box, return byte offsets corresponding to: * 1. the starting byte offset for the next box (should always be equal to * `0`). * 2. The beginning of the box content - meaning the first byte after the * size and the name of the box. * 3. The first byte after the end of the box, might be equal to `buf`'s * length if we're considering the last box. * * `null` if no box is found. * @param {Uint8Array} buf - the isobmff data * @returns {Array.|null} */ function getNextBoxOffsets( buf: Uint8Array, ): | [ 0 /* start byte */, number /* First byte after the size and name (where the content begins)*/, number /* end byte, not included. */, ] | null { const len = buf.length; if (len < 8) { log.warn("isobmff", "box inferior to 8 bytes, cannot find offsets"); return null; } let lastOffset = 0; let boxSize = be4toi(buf, lastOffset); lastOffset += 4; const name = be4toi(buf, lastOffset); lastOffset += 4; if (boxSize === 0) { boxSize = len; } else if (boxSize === 1) { if (lastOffset + 8 > len) { log.warn("isobmff", "box too short, cannot find offsets"); return null; } boxSize = be8toi(buf, lastOffset); lastOffset += 8; } if (boxSize < 0) { throw new Error("ISOBMFF: Size out of range"); } if (name === 0x75756964 /* === "uuid" */) { lastOffset += 16; // Skip uuid name } return [0, lastOffset, boxSize]; } export { getBox, getBoxContent, getBoxesContent, getBoxOffsets, getChildBox, getNextBoxOffsets, getUuidContent, };