diff --git a/package.json b/package.json index 1b2aa13..8c16d5a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "konoebml", - "version": "0.1.0-rc.2", + "version": "0.1.0-rc.3", "description": "A modern JavaScript implementation of EBML RFC8794", "main": "./dist/index.cjs", "module": "./dist/index.js", diff --git a/src/decode-utils.ts b/src/decode-utils.ts index 94a2479..268ded5 100644 --- a/src/decode-utils.ts +++ b/src/decode-utils.ts @@ -1,5 +1,5 @@ import { type EbmlTagIdType, isEbmlMasterTagId } from './models/enums'; -import type { EbmlTagTrait } from './models/tag-trait'; +import type { DecodeContentOptions, EbmlTagTrait } from './models/tag-trait'; import type { FileDataViewController } from './adapters'; import { checkVintSafeSize, @@ -71,8 +71,9 @@ export async function decodeEbmlTagHeader( } export async function* decodeEbmlContent( - controller: FileDataViewController + options: DecodeContentOptions ): AsyncGenerator { + const controller = options.dataViewController; while (true) { const offset = controller.getOffset(); @@ -112,7 +113,7 @@ export async function* decodeEbmlContent( parent: undefined, }); - for await (const item of tag.decodeContent(controller)) { + for await (const item of tag.decodeContent(options)) { yield item; } diff --git a/src/decoder.ts b/src/decoder.ts index 17bb779..71a1bc9 100644 --- a/src/decoder.ts +++ b/src/decoder.ts @@ -1,6 +1,9 @@ import { Queue } from 'mnemonist'; import type { FileDataViewController } from './adapters'; -import type { EbmlTagTrait } from './models/tag-trait'; +import type { + DecodeContentCollectChildPredicate, + EbmlTagTrait, +} from './models/tag-trait'; import { decodeEbmlContent } from './decode-utils'; import { StreamFlushReason, UnreachableOrLogicError } from './errors'; import { dataViewSlice } from './tools'; @@ -10,6 +13,11 @@ export type EbmlStreamDecoderChunkType = | ArrayBuffer | ArrayBufferLike; +export interface EbmlDecodeStreamTransformerOptions { + collectChild?: DecodeContentCollectChildPredicate; + streamStartOffset?: number; +} + export class EbmlDecodeStreamTransformer implements Transformer, @@ -23,6 +31,11 @@ export class EbmlDecodeStreamTransformer private _tickIdleCallback: VoidFunction | undefined; private _currentTask: Promise | undefined; private _writeBuffer = new Queue(); + public readonly options: EbmlDecodeStreamTransformerOptions; + + constructor(options: EbmlDecodeStreamTransformerOptions = {}) { + this.options = options; + } public getBuffer(): Uint8Array { return this._buffer; @@ -171,7 +184,10 @@ export class EbmlDecodeStreamTransformer if (!this._currentTask && !isFlush) { const decode = async () => { try { - for await (const tag of decodeEbmlContent(this)) { + for await (const tag of decodeEbmlContent({ + collectChild: this.options.collectChild, + dataViewController: this, + })) { this.tryEnqueueToBuffer(tag); } this._currentTask = undefined; @@ -189,7 +205,7 @@ export class EbmlDecodeStreamTransformer } async start(ctrl: TransformStreamDefaultController) { - this._offset = 0; + this._offset = this.options.streamStartOffset ?? 0; this._buffer = new Uint8Array(0); this._requests.clear(); this._tickIdleCallback = undefined; @@ -223,14 +239,17 @@ export class EbmlDecodeStreamTransformer } } +export interface EbmlStreamDecoderOptions + extends EbmlDecodeStreamTransformerOptions {} + export class EbmlStreamDecoder extends TransformStream< EbmlStreamDecoderChunkType, EbmlTagTrait > { public readonly transformer: EbmlDecodeStreamTransformer; - constructor() { - const transformer = new EbmlDecodeStreamTransformer(); + constructor(options: EbmlStreamDecoderOptions = {}) { + const transformer = new EbmlDecodeStreamTransformer(options); super(transformer); this.transformer = transformer; } diff --git a/src/index.ts b/src/index.ts index f816ad8..59fb156 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,22 @@ -export { EbmlBlockTag } from './models/tag-block'; -export { EbmlDataTag } from './models/tag-data'; -export { EbmlMasterTag } from './models/tag-master'; -export { EbmlSimpleBlockTag } from './models/tag-simple-block'; -export { EbmlTagTrait } from './models/tag-trait'; +export { + EbmlBlockTag, + type CreateEbmlBlockTagOptions, +} from './models/tag-block'; +export { EbmlDataTag, type CreateEbmlDataTagOptions } from './models/tag-data'; +export { + EbmlMasterTag, + type CreateEbmlMasterTagOptions, +} from './models/tag-master'; +export { + EbmlSimpleBlockTag, + type CreateEbmlSimpleBlockTagOptions, +} from './models/tag-simple-block'; +export { + EbmlTagTrait, + type DecodeContentCollectChildPredicate, + type DecodeContentOptions, + type CreateEbmlTagOptions, +} from './models/tag-trait'; export { createEbmlTag, createEbmlTagForManuallyBuild, @@ -15,6 +29,8 @@ export { EbmlStreamDecoder, EbmlDecodeStreamTransformer, type EbmlStreamDecoderChunkType, + type EbmlStreamDecoderOptions, + type EbmlDecodeStreamTransformerOptions, } from './decoder'; export { EbmlStreamEncoder, diff --git a/src/models/tag-block.ts b/src/models/tag-block.ts index 57f5bd7..496f18d 100644 --- a/src/models/tag-block.ts +++ b/src/models/tag-block.ts @@ -14,7 +14,7 @@ import { EbmlTagIdEnum, } from './enums'; import { EbmlElementType } from './enums'; -import type { FileDataViewController } from '../adapters'; +import type { DecodeContentOptions } from './tag-trait'; export interface CreateEbmlBlockTagOptions extends Omit { @@ -77,7 +77,8 @@ export class EbmlBlockTag extends EbmlDataTag { } // biome-ignore lint/correctness/useYield: - async *decodeContentImpl(controller: FileDataViewController) { + async *decodeContentImpl(options: DecodeContentOptions) { + const controller = options.dataViewController; const offset = controller.getOffset(); const view = await controller.read(offset, this.contentLength, true); const track = readVint(view)!; diff --git a/src/models/tag-data.ts b/src/models/tag-data.ts index d540091..06c3b24 100644 --- a/src/models/tag-data.ts +++ b/src/models/tag-data.ts @@ -1,7 +1,10 @@ -import { type CreateEbmlTagOptions, EbmlTagTrait } from './tag-trait'; +import { + type CreateEbmlTagOptions, + type DecodeContentOptions, + EbmlTagTrait, +} from './tag-trait'; import { EbmlElementType } from './enums'; import { - dataViewSlice, dataViewSliceToBuf, readAscii, readFloat, @@ -15,7 +18,6 @@ import { writeUtf8, } from '../tools'; import { EbmlTagPosition } from './enums'; -import type { FileDataViewController } from 'src/adapters'; export type CreateEbmlDataTagOptions = Omit; @@ -30,7 +32,8 @@ export class EbmlDataTag extends EbmlTagTrait { } // biome-ignore lint/correctness/useYield: - override async *decodeContentImpl(controller: FileDataViewController) { + override async *decodeContentImpl(options: DecodeContentOptions) { + const controller = options.dataViewController; const offset = controller.getOffset(); const view = await controller.read(offset, this.contentLength, true); switch (this.type) { diff --git a/src/models/tag-master.ts b/src/models/tag-master.ts index a27b0da..27cd4ee 100644 --- a/src/models/tag-master.ts +++ b/src/models/tag-master.ts @@ -1,9 +1,12 @@ -import { type CreateEbmlTagOptions, EbmlTagTrait } from './tag-trait'; +import { + type CreateEbmlTagOptions, + type DecodeContentOptions, + EbmlTagTrait, +} from './tag-trait'; import { EbmlElementType, EbmlTagPosition, isEbmlMasterTagId } from './enums'; import { decodeEbmlTagHeader } from '../decode-utils'; import { createEbmlTag } from 'src/factory'; import type { EbmlMasterTagIdType } from './enums'; -import type { FileDataViewController } from '../adapters'; export interface CreateEbmlMasterTagOptions extends Omit { @@ -35,7 +38,9 @@ export class EbmlMasterTag extends EbmlTagTrait { } } - async *decodeContentImpl(controller: FileDataViewController) { + async *decodeContentImpl(options: DecodeContentOptions) { + const controller = options.dataViewController; + const collectChild = options.collectChild; while (true) { const offset = controller.getOffset(); @@ -79,13 +84,22 @@ export class EbmlMasterTag extends EbmlTagTrait { parent: this, }); - for await (const item of tag.decodeContent(controller)) { + for await (const item of tag.decodeContent(options)) { yield item; } tag.endOffset = controller.getOffset(); - this._children.push(tag); + let shouldCollectChild: boolean; + if (typeof collectChild === 'function') { + shouldCollectChild = !!collectChild(tag, this); + } else { + shouldCollectChild = !!collectChild; + } + + if (shouldCollectChild) { + this._children.push(tag); + } yield tag; } diff --git a/src/models/tag-simple-block.ts b/src/models/tag-simple-block.ts index df9c887..67da4e8 100644 --- a/src/models/tag-simple-block.ts +++ b/src/models/tag-simple-block.ts @@ -1,7 +1,7 @@ import { readVint } from '../tools'; import { type CreateEbmlBlockTagOptions, EbmlBlockTag } from './tag-block'; import type { EbmlSimpleBlockTagIdType } from './enums'; -import type { FileDataViewController } from '../adapters'; +import type { DecodeContentOptions } from './tag-trait'; export interface CreateEbmlSimpleBlockTagOptions extends Omit { @@ -33,12 +33,13 @@ export class EbmlSimpleBlockTag extends EbmlBlockTag { yield this.payload; } - async *decodeContentImpl(controller: FileDataViewController) { + async *decodeContentImpl(options: DecodeContentOptions) { + const controller = options.dataViewController; const offset = controller.getOffset(); const view = await controller.read(offset, this.contentLength, true); - for await (const item of super.decodeContentImpl(controller)) { + for await (const item of super.decodeContentImpl(options)) { yield item; } diff --git a/src/models/tag-trait.ts b/src/models/tag-trait.ts index 3888820..025cd8b 100644 --- a/src/models/tag-trait.ts +++ b/src/models/tag-trait.ts @@ -4,6 +4,7 @@ import type { EbmlElementType } from './enums'; import { hexStringToBuf, UNKNOWN_SIZE_VINT_BUF, writeVint } from '../tools'; import type { FileDataViewController } from '../adapters'; import { InconsistentOffsetOnDecodingContentError } from '../errors'; +import type { EbmlMasterTag } from './tag-master'; export interface CreateEbmlTagOptions { id: EbmlTagIdType; @@ -16,6 +17,15 @@ export interface CreateEbmlTagOptions { parent?: EbmlTagTrait; } +export type DecodeContentCollectChildPredicate = + | boolean + | ((child: EbmlTagTrait, parent: EbmlMasterTag) => boolean); + +export interface DecodeContentOptions { + collectChild?: DecodeContentCollectChildPredicate; + dataViewController: FileDataViewController; +} + export abstract class EbmlTagTrait { /** * The id of the EBML tag. @@ -117,7 +127,7 @@ export abstract class EbmlTagTrait { * @param controller DataView controller, simulate async filesystem file */ protected abstract decodeContentImpl( - controller: FileDataViewController + options: DecodeContentOptions ): AsyncGenerator; /** @@ -126,13 +136,14 @@ export abstract class EbmlTagTrait { * @returns Deep traversal async iterators of all descendants */ public async *decodeContent( - controller: FileDataViewController + options: DecodeContentOptions ): AsyncGenerator { + const controller = options.dataViewController; if (this.contentLength === 0 || this.position === EbmlTagPosition.Start) { return; } const startOffset = controller.getOffset(); - for await (const tag of this.decodeContentImpl(controller)) { + for await (const tag of this.decodeContentImpl(options)) { yield tag; } const endOffset = controller.getOffset();