Move PacketReader elsewhere

This commit is contained in:
Vanilagy
2025-12-30 12:31:33 +01:00
parent ba26367529
commit 51b9de1c6f
17 changed files with 244 additions and 297 deletions
+1 -2
View File
@@ -19,11 +19,10 @@ import {
ResultValue,
UNDETERMINED_LANGUAGE,
} from '../misc';
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
import { EncodedPacket, PacketRetrievalOptions, PLACEHOLDER_DATA } from '../packet';
import { readBytes, Reader } from '../reader';
import { DEFAULT_TRACK_DISPOSITION } from '../metadata';
import { FrameHeader, MAX_FRAME_HEADER_SIZE, MIN_FRAME_HEADER_SIZE, readFrameHeader } from './adts-reader';
import { PacketRetrievalOptions } from '../cursors';
const SAMPLES_PER_AAC_FRAME = 1024;
+7 -245
View File
@@ -8,7 +8,6 @@
import { PCM_AUDIO_CODECS } from './codec';
import { AudioDecoderWrapper, DecoderWrapper, PcmAudioDecoderWrapper, VideoDecoderWrapper } from './decode';
import { InputDisposedError } from './input';
import { InputAudioTrack, InputTrack, InputVideoTrack } from './input-track';
import {
assert,
@@ -24,230 +23,19 @@ import {
ResultValue,
Rotation,
AsyncGate,
isNumber,
MaybeRelevantPromise,
} from './misc';
import { EncodedPacket } from './packet';
import {
EncodedPacket,
PacketReader,
PacketRetrievalOptions,
validatePacketRetrievalOptions,
validateTimestamp,
} from './packet';
import { AudioSample, clampCropRectangle, CropRectangle, validateCropRectangle, VideoSample } from './sample';
polyfillSymbolDispose();
/**
* Additional options for controlling packet retrieval.
* @group Media sinks
* @public
*/
export type PacketRetrievalOptions = {
/**
* When set to `true`, only packet metadata (like timestamp) will be retrieved - the actual packet data will not
* be loaded.
*/
metadataOnly?: boolean;
/**
* When set to true, key packets will be verified upon retrieval by looking into the packet's bitstream.
* If not enabled, the packet types will be determined solely by what's stored in the containing file and may be
* incorrect, potentially leading to decoder errors. Since determining a packet's actual type requires looking into
* its data, this option cannot be enabled together with `metadataOnly`.
*/
verifyKeyPackets?: boolean;
};
export const validatePacketRetrievalOptions = (options: PacketRetrievalOptions) => {
if (!options || typeof options !== 'object') {
throw new TypeError('options must be an object.');
}
if (options.metadataOnly !== undefined && typeof options.metadataOnly !== 'boolean') {
throw new TypeError('options.metadataOnly, when defined, must be a boolean.');
}
if (options.verifyKeyPackets !== undefined && typeof options.verifyKeyPackets !== 'boolean') {
throw new TypeError('options.verifyKeyPackets, when defined, must be a boolean.');
}
if (options.verifyKeyPackets && options.metadataOnly) {
throw new TypeError('options.verifyKeyPackets and options.metadataOnly cannot be enabled together.');
}
};
export const validateTimestamp = (timestamp: number) => {
if (!isNumber(timestamp)) {
throw new TypeError('timestamp must be a number.'); // It can be non-finite, that's fine
}
};
export class PacketReader<T extends InputTrack = InputTrack> {
track: T;
constructor(track: T) {
if (!(track instanceof InputTrack)) {
throw new TypeError('track must be an InputTrack.');
}
this.track = track;
}
private _maybeVerifyPacketType(
packet: EncodedPacket | null,
options: PacketRetrievalOptions,
): MaybePromise<EncodedPacket | null> {
if (!options.verifyKeyPackets || !packet || packet.type === 'delta') {
return packet;
}
return this.track.determinePacketType(packet).then((determinedType) => {
if (determinedType) {
// @ts-expect-error Technically readonly
packet.type = determinedType;
}
return packet;
});
}
getFirst(options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
validatePacketRetrievalOptions(options);
if (this.track.input._disposed) {
throw new InputDisposedError();
}
const result = new ResultValue<EncodedPacket | null>();
const promise = this.track._backing.getFirstPacket(result, options);
if (result.pending) {
return promise.then(() => this._maybeVerifyPacketType(result.value, options));
} else {
return this._maybeVerifyPacketType(result.value, options);
}
}
getAt(timestamp: number, options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
validateTimestamp(timestamp);
validatePacketRetrievalOptions(options);
if (this.track.input._disposed) {
throw new InputDisposedError();
}
const result = new ResultValue<EncodedPacket | null>();
const promise = this.track._backing.getPacket(result, timestamp, options);
if (result.pending) {
return promise.then(() => this._maybeVerifyPacketType(result.value, options));
} else {
return this._maybeVerifyPacketType(result.value, options);
}
}
getKeyAt(timestamp: number, options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
validateTimestamp(timestamp);
validatePacketRetrievalOptions(options);
if (this.track.input._disposed) {
throw new InputDisposedError();
}
if (options.verifyKeyPackets) {
return this._readKeyAtVerified(timestamp, options);
}
const result = new ResultValue<EncodedPacket | null>();
const promise = this.track._backing.getKeyPacket(result, timestamp, options);
if (result.pending) {
return promise.then(() => result.value);
} else {
return result.value;
}
}
private async _readKeyAtVerified(
timestamp: number,
options: PacketRetrievalOptions,
): Promise<EncodedPacket | null> {
const result = new ResultValue<EncodedPacket | null>();
const promise = this.track._backing.getKeyPacket(result, timestamp, options);
if (result.pending) await promise;
const packet = result.value;
if (!packet) {
return null;
}
const determinedType = await this.track.determinePacketType(packet);
if (determinedType === 'delta') {
// Try returning the previous key packet (in hopes that it's actually a key packet)
return this._readKeyAtVerified(packet.timestamp - 1 / this.track.timeResolution, options);
}
return packet;
}
getNext(from: EncodedPacket, options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
if (!(from instanceof EncodedPacket)) {
throw new TypeError('from must be an EncodedPacket.');
}
validatePacketRetrievalOptions(options);
if (this.track.input._disposed) {
throw new InputDisposedError();
}
const result = new ResultValue<EncodedPacket | null>();
const promise = this.track._backing.getNextPacket(result, from, options);
if (result.pending) {
return promise.then(() => this._maybeVerifyPacketType(result.value, options));
} else {
return this._maybeVerifyPacketType(result.value, options);
}
}
getNextKey(from: EncodedPacket, options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
if (!(from instanceof EncodedPacket)) {
throw new TypeError('from must be an EncodedPacket.');
}
validatePacketRetrievalOptions(options);
if (this.track.input._disposed) {
throw new InputDisposedError();
}
if (options.verifyKeyPackets) {
return this._getNextKeyVerified(from, options);
}
const result = new ResultValue<EncodedPacket | null>();
const promise = this.track._backing.getNextKeyPacket(result, from, options);
if (result.pending) {
return promise.then(() => result.value);
} else {
return result.value;
}
}
private async _getNextKeyVerified(
from: EncodedPacket,
options: PacketRetrievalOptions,
): Promise<EncodedPacket | null> {
const result = new ResultValue<EncodedPacket | null>();
const promise = this.track._backing.getNextKeyPacket(result, from, options);
if (result.pending) await promise;
const nextPacket = result.value;
if (!nextPacket) {
return null;
}
const determinedType = await this.track.determinePacketType(nextPacket);
if (determinedType === 'delta') {
// Try returning the next key packet (in hopes that it's actually a key packet)
return this._getNextKeyVerified(nextPacket, options);
}
return nextPacket;
}
}
export class PacketCursor<T extends InputTrack = InputTrack> {
track: T;
current: EncodedPacket | null = null;
@@ -1640,29 +1428,3 @@ export const canvasTransformer = (
return new WrappedCanvas(canvas, sample.timestamp, sample.duration);
};
};
/**
* An AudioBuffer with additional timing information (timestamp & duration).
* @public
*/
export type WrappedAudioBuffer = {
/** An AudioBuffer. */
buffer: AudioBuffer;
/** The timestamp of the corresponding audio sample, in seconds. */
timestamp: number;
/** The duration of the corresponding audio sample, in seconds. */
duration: number;
};
export const audioBufferTransformer = (): SampleTransformer<AudioSample, WrappedAudioBuffer> => {
return (sample) => {
const result: WrappedAudioBuffer = {
buffer: sample.toAudioBuffer(),
timestamp: sample.timestamp,
duration: sample.duration,
};
sample.close();
return result;
};
};
+1 -2
View File
@@ -20,7 +20,7 @@ import {
textDecoder,
UNDETERMINED_LANGUAGE,
} from '../misc';
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
import { EncodedPacket, PacketRetrievalOptions, PLACEHOLDER_DATA } from '../packet';
import {
FileSlice,
readBytes,
@@ -38,7 +38,6 @@ import {
readSampleRate,
getSampleRateOrUncommon,
} from './flac-misc';
import { PacketRetrievalOptions } from '../cursors';
type FlacAudioInfo = {
numberOfChannels: number;
+1 -1
View File
@@ -161,6 +161,7 @@ export {
EncodedPacket,
EncodedPacketSideData,
PacketType,
PacketReader,
} from './packet';
export {
AudioSample,
@@ -174,7 +175,6 @@ export {
VIDEO_SAMPLE_PIXEL_FORMATS,
} from './sample';
export {
PacketReader,
PacketCursor,
SampleCursor,
VideoSampleCursor,
+2 -2
View File
@@ -12,9 +12,9 @@ import { customAudioDecoders, customVideoDecoders } from './custom-coder';
import { Input } from './input';
import { assert, MaybeRelevantPromise, ResultValue, Rotation } from './misc';
import { TrackType } from './output';
import { EncodedPacket, PacketType } from './packet';
import { EncodedPacket, PacketRetrievalOptions, PacketType } from './packet';
import { TrackDisposition } from './metadata';
import { PacketCursor, PacketRetrievalOptions } from './cursors';
import { PacketCursor } from './cursors';
/**
* Contains aggregate statistics about the encoded packets of a track.
+1 -2
View File
@@ -60,7 +60,7 @@ import {
ResultValue,
MaybeRelevantPromise,
} from '../misc';
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
import { EncodedPacket, PacketRetrievalOptions, PLACEHOLDER_DATA } from '../packet';
import { buildIsobmffMimeType } from './isobmff-misc';
import {
MAX_BOX_HEADER_SIZE,
@@ -88,7 +88,6 @@ import {
readAscii,
} from '../reader';
import { DEFAULT_TRACK_DISPOSITION, MetadataTags, RichImageData, TrackDisposition } from '../metadata';
import { PacketRetrievalOptions } from '../cursors';
type InternalTrack = {
id: number;
+1 -2
View File
@@ -48,7 +48,7 @@ import {
TRANSFER_CHARACTERISTICS_MAP_INVERSE,
UNDETERMINED_LANGUAGE,
} from '../misc';
import { EncodedPacket, EncodedPacketSideData, PLACEHOLDER_DATA } from '../packet';
import { EncodedPacket, EncodedPacketSideData, PacketRetrievalOptions, PLACEHOLDER_DATA } from '../packet';
import {
assertDefinedSize,
CODEC_STRING_MAP,
@@ -70,7 +70,6 @@ import {
} from './ebml';
import { buildMatroskaMimeType } from './matroska-misc';
import { FileSlice, readBytes, Reader, readI16Be, readU8 } from '../reader';
import { PacketRetrievalOptions } from '../cursors';
type Segment = {
seekHeadSeen: boolean;
+1 -2
View File
@@ -19,7 +19,7 @@ import {
ResultValue,
UNDETERMINED_LANGUAGE,
} from '../misc';
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
import { EncodedPacket, PacketRetrievalOptions, PLACEHOLDER_DATA } from '../packet';
import { FrameHeader, getXingOffset, INFO, XING } from '../../shared/mp3-misc';
import {
ID3_V1_TAG_SIZE,
@@ -30,7 +30,6 @@ import {
} from '../id3';
import { readNextFrameHeader } from './mp3-reader';
import { readAscii, readBytes, Reader, readU32Be } from '../reader';
import { PacketRetrievalOptions } from '../cursors';
type Sample = {
timestamp: number;
+1 -2
View File
@@ -8,7 +8,6 @@
import { OPUS_SAMPLE_RATE } from '../codec';
import { parseModesFromVorbisSetupPacket, parseOpusIdentificationHeader, readVorbisComments } from '../codec-data';
import { PacketRetrievalOptions } from '../cursors';
import { Demuxer } from '../demuxer';
import { Input } from '../input';
import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
@@ -25,7 +24,7 @@ import {
toDataView,
UNDETERMINED_LANGUAGE,
} from '../misc';
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
import { EncodedPacket, PacketRetrievalOptions, PLACEHOLDER_DATA } from '../packet';
import { readBytes, Reader } from '../reader';
import { buildOggMimeType, computeOggPageCrc, extractSampleMetadata, OggCodecInfo } from './ogg-misc';
import {
+219 -1
View File
@@ -6,7 +6,9 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { SECOND_TO_MICROSECOND_FACTOR } from './misc';
import { InputDisposedError } from './input';
import { InputTrack } from './input-track';
import { isNumber, MaybePromise, ResultValue, SECOND_TO_MICROSECOND_FACTOR } from './misc';
export const PLACEHOLDER_DATA = /* #__PURE__ */ new Uint8Array(0);
@@ -267,3 +269,219 @@ export class EncodedPacket {
);
}
}
/**
* Additional options for controlling packet retrieval.
* @group Media sinks
* @public
*/
export type PacketRetrievalOptions = {
/**
* When set to `true`, only packet metadata (like timestamp) will be retrieved - the actual packet data will not
* be loaded.
*/
metadataOnly?: boolean;
/**
* When set to true, key packets will be verified upon retrieval by looking into the packet's bitstream.
* If not enabled, the packet types will be determined solely by what's stored in the containing file and may be
* incorrect, potentially leading to decoder errors. Since determining a packet's actual type requires looking into
* its data, this option cannot be enabled together with `metadataOnly`.
*/
verifyKeyPackets?: boolean;
};
export const validatePacketRetrievalOptions = (options: PacketRetrievalOptions) => {
if (!options || typeof options !== 'object') {
throw new TypeError('options must be an object.');
}
if (options.metadataOnly !== undefined && typeof options.metadataOnly !== 'boolean') {
throw new TypeError('options.metadataOnly, when defined, must be a boolean.');
}
if (options.verifyKeyPackets !== undefined && typeof options.verifyKeyPackets !== 'boolean') {
throw new TypeError('options.verifyKeyPackets, when defined, must be a boolean.');
}
if (options.verifyKeyPackets && options.metadataOnly) {
throw new TypeError('options.verifyKeyPackets and options.metadataOnly cannot be enabled together.');
}
};
export const validateTimestamp = (timestamp: number) => {
if (!isNumber(timestamp)) {
throw new TypeError('timestamp must be a number.'); // It can be non-finite, that's fine
}
};
export class PacketReader<T extends InputTrack = InputTrack> {
track: T;
constructor(track: T) {
if (!(track instanceof InputTrack)) {
throw new TypeError('track must be an InputTrack.');
}
this.track = track;
}
private _maybeVerifyPacketType(
packet: EncodedPacket | null,
options: PacketRetrievalOptions,
): MaybePromise<EncodedPacket | null> {
if (!options.verifyKeyPackets || !packet || packet.type === 'delta') {
return packet;
}
return this.track.determinePacketType(packet).then((determinedType) => {
if (determinedType) {
// @ts-expect-error Technically readonly
packet.type = determinedType;
}
return packet;
});
}
getFirst(options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
validatePacketRetrievalOptions(options);
if (this.track.input._disposed) {
throw new InputDisposedError();
}
const result = new ResultValue<EncodedPacket | null>();
const promise = this.track._backing.getFirstPacket(result, options);
if (result.pending) {
return promise.then(() => this._maybeVerifyPacketType(result.value, options));
} else {
return this._maybeVerifyPacketType(result.value, options);
}
}
getAt(timestamp: number, options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
validateTimestamp(timestamp);
validatePacketRetrievalOptions(options);
if (this.track.input._disposed) {
throw new InputDisposedError();
}
const result = new ResultValue<EncodedPacket | null>();
const promise = this.track._backing.getPacket(result, timestamp, options);
if (result.pending) {
return promise.then(() => this._maybeVerifyPacketType(result.value, options));
} else {
return this._maybeVerifyPacketType(result.value, options);
}
}
getKeyAt(timestamp: number, options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
validateTimestamp(timestamp);
validatePacketRetrievalOptions(options);
if (this.track.input._disposed) {
throw new InputDisposedError();
}
if (options.verifyKeyPackets) {
return this._readKeyAtVerified(timestamp, options);
}
const result = new ResultValue<EncodedPacket | null>();
const promise = this.track._backing.getKeyPacket(result, timestamp, options);
if (result.pending) {
return promise.then(() => result.value);
} else {
return result.value;
}
}
private async _readKeyAtVerified(
timestamp: number,
options: PacketRetrievalOptions,
): Promise<EncodedPacket | null> {
const result = new ResultValue<EncodedPacket | null>();
const promise = this.track._backing.getKeyPacket(result, timestamp, options);
if (result.pending) await promise;
const packet = result.value;
if (!packet) {
return null;
}
const determinedType = await this.track.determinePacketType(packet);
if (determinedType === 'delta') {
// Try returning the previous key packet (in hopes that it's actually a key packet)
return this._readKeyAtVerified(packet.timestamp - 1 / this.track.timeResolution, options);
}
return packet;
}
getNext(from: EncodedPacket, options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
if (!(from instanceof EncodedPacket)) {
throw new TypeError('from must be an EncodedPacket.');
}
validatePacketRetrievalOptions(options);
if (this.track.input._disposed) {
throw new InputDisposedError();
}
const result = new ResultValue<EncodedPacket | null>();
const promise = this.track._backing.getNextPacket(result, from, options);
if (result.pending) {
return promise.then(() => this._maybeVerifyPacketType(result.value, options));
} else {
return this._maybeVerifyPacketType(result.value, options);
}
}
getNextKey(from: EncodedPacket, options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
if (!(from instanceof EncodedPacket)) {
throw new TypeError('from must be an EncodedPacket.');
}
validatePacketRetrievalOptions(options);
if (this.track.input._disposed) {
throw new InputDisposedError();
}
if (options.verifyKeyPackets) {
return this._getNextKeyVerified(from, options);
}
const result = new ResultValue<EncodedPacket | null>();
const promise = this.track._backing.getNextKeyPacket(result, from, options);
if (result.pending) {
return promise.then(() => result.value);
} else {
return result.value;
}
}
private async _getNextKeyVerified(
from: EncodedPacket,
options: PacketRetrievalOptions,
): Promise<EncodedPacket | null> {
const result = new ResultValue<EncodedPacket | null>();
const promise = this.track._backing.getNextKeyPacket(result, from, options);
if (result.pending) await promise;
const nextPacket = result.value;
if (!nextPacket) {
return null;
}
const determinedType = await this.track.determinePacketType(nextPacket);
if (determinedType === 'delta') {
// Try returning the next key packet (in hopes that it's actually a key packet)
return this._getNextKeyVerified(nextPacket, options);
}
return nextPacket;
}
}
+1 -2
View File
@@ -12,10 +12,9 @@ import { Input } from '../input';
import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
import { DEFAULT_TRACK_DISPOSITION, MetadataTags } from '../metadata';
import { assert, MaybeRelevantPromise, ResultValue, UNDETERMINED_LANGUAGE } from '../misc';
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
import { EncodedPacket, PacketRetrievalOptions, PLACEHOLDER_DATA } from '../packet';
import { readAscii, readBytes, Reader, readU16, readU32, readU64 } from '../reader';
import { parseId3V2Tag, readId3V2Header } from '../id3';
import { PacketRetrievalOptions } from '../cursors';
export enum WaveFormat {
PCM = 0x0001,
-28
View File
@@ -3,7 +3,6 @@ import { Input } from '../../src/input.js';
import { BufferSource, UrlSource } from '../../src/source.js';
import { ALL_FORMATS } from '../../src/input-format.js';
import {
audioBufferTransformer,
AudioSampleCursor,
canvasTransformer,
VideoSampleCursor,
@@ -1115,33 +1114,6 @@ test('Canvas transformer', async () => {
await cursor2.close();
});
test('AudioBuffer transformer', async () => {
using input = new Input({
source: new UrlSource('/trim-buck-bunny.mov'),
formats: ALL_FORMATS,
});
const audioTrack = (await input.getPrimaryAudioTrack())!;
const cursor = new AudioSampleCursor(audioTrack, {
transform: audioBufferTransformer(),
});
const firstSample = (await cursor.seekToFirst())!;
expect(firstSample.buffer).toBeInstanceOf(AudioBuffer);
expect(firstSample.timestamp).toBe(0);
expect(firstSample.duration).toBeGreaterThan(0);
expect(firstSample.buffer.duration).toBe(firstSample.duration);
const nextSample = (await cursor.next())!;
expect(nextSample.buffer).toBeInstanceOf(AudioBuffer);
expect(nextSample.timestamp).toBeGreaterThan(firstSample.timestamp);
expect(nextSample.buffer).not.toBe(firstSample.buffer);
expect(nextSample.buffer.duration).toBe(nextSample.duration);
await cursor.close();
});
test('Unthrottled decoder', async () => {
// While the decoder is throttled in practice, these tests simulate what happens when the decoder naturally reaches
// the end of the encodable packets without having been stopped; in this case, we still expect the cursor to keep
+2 -1
View File
@@ -9,7 +9,8 @@ import { CanvasSource, VideoSampleSource } from '../../src/media-source.js';
import { canEncodeVideo, QUALITY_HIGH } from '../../src/encode.js';
import { VideoSample } from '../../src/sample.js';
import { Conversion } from '../../src/conversion.js';
import { canvasTransformer, PacketCursor, PacketReader, VideoSampleCursor } from '../../src/cursors.js';
import { canvasTransformer, PacketCursor, VideoSampleCursor } from '../../src/cursors.js';
import { PacketReader } from '../../src/packet.js';
test('Can decode transparent video', async () => {
using input = new Input({
+1 -1
View File
@@ -8,7 +8,7 @@ import { Mp4OutputFormat } from '../../src/output-format.js';
import { BufferTarget } from '../../src/target.js';
import { Conversion } from '../../src/conversion.js';
import { extractAvcNalUnits } from '../../src/codec-data.js';
import { PacketReader } from '../../src/cursors.js';
import { PacketReader } from '../../src/packet.js';
const __dirname = new URL('.', import.meta.url).pathname;
+1 -2
View File
@@ -4,10 +4,9 @@ import { BufferSource, FilePathSource } from '../../src/source.js';
import path from 'node:path';
import fs from 'node:fs';
import { ADTS, ALL_FORMATS, FLAC, MP3, MP4, OGG, QTFF, WAVE, WEBM } from '../../src/input-format.js';
import { PacketReader } from '../../src/cursors.js';
import { InputAudioTrack, InputTrack } from '../../src/input-track.js';
import { assert } from '../../src/misc.js';
import { EncodedPacket } from '../../src/packet.js';
import { EncodedPacket, PacketReader } from '../../src/packet.js';
const __dirname = new URL('.', import.meta.url).pathname;
+2 -1
View File
@@ -8,7 +8,8 @@ import { Output } from '../../src/output.js';
import { BufferTarget } from '../../src/target.js';
import { FlacOutputFormat } from '../../src/output-format.js';
import { Conversion } from '../../src/conversion.js';
import { PacketCursor, PacketReader } from '../../src/cursors.js';
import { PacketCursor } from '../../src/cursors.js';
import { PacketReader } from '../../src/packet.js';
const __dirname = new URL('.', import.meta.url).pathname;
+2 -1
View File
@@ -4,8 +4,9 @@ import { BufferSource, FilePathSource } from '../../src/source.js';
import path from 'node:path';
import fs from 'node:fs';
import { ALL_FORMATS } from '../../src/input-format.js';
import { PacketCursor, PacketReader } from '../../src/cursors.js';
import { PacketCursor } from '../../src/cursors.js';
import { promiseAllEnsureOrder } from '../../src/misc.js';
import { PacketReader } from '../../src/packet.js';
const __dirname = new URL('.', import.meta.url).pathname;