Expand packet.clone(), make conversion start timestamp default to the input's start timestamp

This commit is contained in:
Vanilagy
2026-01-19 14:04:20 +01:00
parent d23b0369f3
commit dbaaea255c
6 changed files with 72 additions and 42 deletions
+36 -23
View File
@@ -84,7 +84,7 @@ export type ConversionOptions = {
trim?: {
/**
* The time in the input file in seconds at which the output file should start. Must be less than `end`.
* Defaults to 0 when omitted.
* When omitted, defaults to the start timestamp of the input or to 0, whichever is higher.
*/
start?: number;
/**
@@ -462,9 +462,9 @@ export class Conversion {
/** @internal */
_options: ConversionOptions;
/** @internal */
_startTimestamp: number;
_startTimestamp!: number;
/** @internal */
_endTimestamp: number;
_endTimestamp!: number;
/** @internal */
_addedCounts: Record<TrackType, number> = {
@@ -592,9 +592,6 @@ export class Conversion {
this.input = options.input;
this.output = options.output;
this._startTimestamp = options.trim?.start ?? 0;
this._endTimestamp = options.trim?.end ?? Infinity;
const { promise: started, resolve: start } = promiseWithResolvers();
this._started = started;
this._start = start;
@@ -602,6 +599,14 @@ export class Conversion {
/** @internal */
async _init() {
this._startTimestamp = this._options.trim?.start ?? Math.max(
await this.input.getFirstTimestamp(),
// Samples can also have negative timestamps, but the meaning typically is "don't present me", so let's cut
// those out by default.
0,
);
this._endTimestamp = this._options.trim?.end ?? Infinity;
const inputTracks = await this.input.getTracks();
const outputTrackCounts = this.output.format.getSupportedTrackCounts();
@@ -900,8 +905,7 @@ export class Conversion {
const firstTimestamp = await track.getFirstTimestamp();
const needsTranscode = !!trackOptions.forceTranscode
|| this._startTimestamp > 0
|| firstTimestamp < 0
|| firstTimestamp < this._startTimestamp
|| !!trackOptions.frameRate
|| trackOptions.keyFrameInterval !== undefined
|| trackOptions.process !== undefined;
@@ -943,17 +947,19 @@ export class Conversion {
return;
}
if (alpha === 'discard') {
// Feels hacky given that the rest of the packet is readonly. But, works for now.
delete packet.sideData.alpha;
delete packet.sideData.alphaByteLength;
}
const modifiedPacket = packet.clone({
timestamp: packet.timestamp - this._startTimestamp,
sideData: alpha === 'discard'
? {} // Remove alpha side data
: packet.sideData,
});
assert(modifiedPacket.timestamp >= 0);
this._reportProgress(track.id, packet.timestamp);
await source.add(packet, meta);
this._reportProgress(track.id, modifiedPacket.timestamp);
await source.add(modifiedPacket, meta);
if (this._synchronizer.shouldWait(track.id, packet.timestamp)) {
await this._synchronizer.wait(packet.timestamp);
if (this._synchronizer.shouldWait(track.id, modifiedPacket.timestamp)) {
await this._synchronizer.wait(modifiedPacket.timestamp);
}
}
@@ -1314,8 +1320,7 @@ export class Conversion {
let sampleRate = trackOptions.sampleRate ?? originalSampleRate;
let needsResample = numberOfChannels !== originalNumberOfChannels
|| sampleRate !== originalSampleRate
|| this._startTimestamp > 0
|| firstTimestamp < 0;
|| firstTimestamp < this._startTimestamp;
let audioCodecs = this.output.format.getSupportedAudioCodecs();
if (
@@ -1346,11 +1351,16 @@ export class Conversion {
return;
}
this._reportProgress(track.id, packet.timestamp);
await source.add(packet, meta);
const modifiedPacket = packet.clone({
timestamp: packet.timestamp - this._startTimestamp,
});
assert(modifiedPacket.timestamp >= 0);
if (this._synchronizer.shouldWait(track.id, packet.timestamp)) {
await this._synchronizer.wait(packet.timestamp);
this._reportProgress(track.id, modifiedPacket.timestamp);
await source.add(modifiedPacket, meta);
if (this._synchronizer.shouldWait(track.id, modifiedPacket.timestamp)) {
await this._synchronizer.wait(modifiedPacket.timestamp);
}
}
@@ -1449,6 +1459,9 @@ export class Conversion {
return;
}
// Offset the timestamp as needed
sample.setTimestamp(sample.timestamp - this._startTimestamp);
await this._registerAudioSample(track, trackOptions, source, sample);
sample.close();
}
+9 -11
View File
@@ -228,20 +228,13 @@ export class MpegTsDemuxer extends Demuxer {
switch (streamType) {
case MpegTsStreamType.MP3_MPEG1:
case MpegTsStreamType.MP3_MPEG2: {
info = {
type: 'audio',
codec: 'mp3',
aacCodecInfo: null,
numberOfChannels: -1,
sampleRate: -1,
};
}; break;
case MpegTsStreamType.MP3_MPEG2:
case MpegTsStreamType.AAC: {
const codec = streamType === MpegTsStreamType.AAC ? 'aac' : 'mp3';
info = {
type: 'audio',
codec: 'aac',
codec,
aacCodecInfo: null,
numberOfChannels: -1,
sampleRate: -1,
@@ -268,6 +261,11 @@ export class MpegTsDemuxer extends Demuxer {
reorderSize: -1,
};
}; break;
default: {
// If we don't recognize the codec, we don't surface the track at all. This is because
// we can't determine its metadata and also have no idea how to packetize its data.
}
}
if (info) {
+25 -5
View File
@@ -234,31 +234,51 @@ export class EncodedPacket {
);
}
/** Clones this packet while optionally updating timing information. */
/** Clones this packet while optionally modifying the new packet's data. */
clone(options?: {
/** The data of the cloned packet. */
data?: Uint8Array;
/** The type of the cloned packet. */
type?: PacketType;
/** The timestamp of the cloned packet in seconds. */
timestamp?: number;
/** The duration of the cloned packet in seconds. */
duration?: number;
/** The sequence number of the cloned packet. */
sequenceNumber?: number;
/** The side data of the cloned packet. */
sideData?: EncodedPacketSideData;
}): EncodedPacket {
if (options !== undefined && (typeof options !== 'object' || options === null)) {
throw new TypeError('options, when provided, must be an object.');
}
if (options?.data !== undefined && !(options.data instanceof Uint8Array)) {
throw new TypeError('options.data, when provided, must be a Uint8Array.');
}
if (options?.type !== undefined && options.type !== 'key' && options.type !== 'delta') {
throw new TypeError('options.type, when provided, must be either "key" or "delta".');
}
if (options?.timestamp !== undefined && !Number.isFinite(options.timestamp)) {
throw new TypeError('options.timestamp, when provided, must be a number.');
}
if (options?.duration !== undefined && !Number.isFinite(options.duration)) {
throw new TypeError('options.duration, when provided, must be a number.');
}
if (options?.sequenceNumber !== undefined && !Number.isFinite(options.sequenceNumber)) {
throw new TypeError('options.sequenceNumber, when provided, must be a number.');
}
if (options?.sideData !== undefined && (typeof options.sideData !== 'object' || options.sideData === null)) {
throw new TypeError('options.sideData, when provided, must be an object.');
}
return new EncodedPacket(
this.data,
this.type,
options?.data ?? this.data,
options?.type ?? this.type,
options?.timestamp ?? this.timestamp,
options?.duration ?? this.duration,
this.sequenceNumber,
options?.sequenceNumber ?? this.sequenceNumber,
this.byteLength,
this.sideData,
options?.sideData ?? this.sideData,
);
}
}