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
+1 -1
View File
@@ -176,7 +176,7 @@ Negative sequence numbers mean the packet's ordering is undefined. When creating
### Cloning packets ### Cloning packets
Use the `clone` method to create a new packet from an existing packet. While doing so, you can change its timestamp and duration. Use the `clone` method to create a new packet from an existing packet. While doing so, you can partially change its data.
```ts ```ts
// Creates a clone identical to the original: // Creates a clone identical to the original:
packet.clone(); packet.clone();
@@ -75,7 +75,7 @@ const compressFile = async (resource: File | string) => {
let progress = 0; let progress = 0;
currentConversion.onProgress = newProgress => progress = newProgress; currentConversion.onProgress = newProgress => progress = newProgress;
const fileDuration = await input.computeDuration(); const fileDuration = (await input.computeDuration()) - (await input.getFirstTimestamp());
const startTime = performance.now(); const startTime = performance.now();
const updateProgress = () => { const updateProgress = () => {
+36 -23
View File
@@ -84,7 +84,7 @@ export type ConversionOptions = {
trim?: { trim?: {
/** /**
* The time in the input file in seconds at which the output file should start. Must be less than `end`. * 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; start?: number;
/** /**
@@ -462,9 +462,9 @@ export class Conversion {
/** @internal */ /** @internal */
_options: ConversionOptions; _options: ConversionOptions;
/** @internal */ /** @internal */
_startTimestamp: number; _startTimestamp!: number;
/** @internal */ /** @internal */
_endTimestamp: number; _endTimestamp!: number;
/** @internal */ /** @internal */
_addedCounts: Record<TrackType, number> = { _addedCounts: Record<TrackType, number> = {
@@ -592,9 +592,6 @@ export class Conversion {
this.input = options.input; this.input = options.input;
this.output = options.output; this.output = options.output;
this._startTimestamp = options.trim?.start ?? 0;
this._endTimestamp = options.trim?.end ?? Infinity;
const { promise: started, resolve: start } = promiseWithResolvers(); const { promise: started, resolve: start } = promiseWithResolvers();
this._started = started; this._started = started;
this._start = start; this._start = start;
@@ -602,6 +599,14 @@ export class Conversion {
/** @internal */ /** @internal */
async _init() { 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 inputTracks = await this.input.getTracks();
const outputTrackCounts = this.output.format.getSupportedTrackCounts(); const outputTrackCounts = this.output.format.getSupportedTrackCounts();
@@ -900,8 +905,7 @@ export class Conversion {
const firstTimestamp = await track.getFirstTimestamp(); const firstTimestamp = await track.getFirstTimestamp();
const needsTranscode = !!trackOptions.forceTranscode const needsTranscode = !!trackOptions.forceTranscode
|| this._startTimestamp > 0 || firstTimestamp < this._startTimestamp
|| firstTimestamp < 0
|| !!trackOptions.frameRate || !!trackOptions.frameRate
|| trackOptions.keyFrameInterval !== undefined || trackOptions.keyFrameInterval !== undefined
|| trackOptions.process !== undefined; || trackOptions.process !== undefined;
@@ -943,17 +947,19 @@ export class Conversion {
return; return;
} }
if (alpha === 'discard') { const modifiedPacket = packet.clone({
// Feels hacky given that the rest of the packet is readonly. But, works for now. timestamp: packet.timestamp - this._startTimestamp,
delete packet.sideData.alpha; sideData: alpha === 'discard'
delete packet.sideData.alphaByteLength; ? {} // Remove alpha side data
} : packet.sideData,
});
assert(modifiedPacket.timestamp >= 0);
this._reportProgress(track.id, packet.timestamp); this._reportProgress(track.id, modifiedPacket.timestamp);
await source.add(packet, meta); await source.add(modifiedPacket, meta);
if (this._synchronizer.shouldWait(track.id, packet.timestamp)) { if (this._synchronizer.shouldWait(track.id, modifiedPacket.timestamp)) {
await this._synchronizer.wait(packet.timestamp); await this._synchronizer.wait(modifiedPacket.timestamp);
} }
} }
@@ -1314,8 +1320,7 @@ export class Conversion {
let sampleRate = trackOptions.sampleRate ?? originalSampleRate; let sampleRate = trackOptions.sampleRate ?? originalSampleRate;
let needsResample = numberOfChannels !== originalNumberOfChannels let needsResample = numberOfChannels !== originalNumberOfChannels
|| sampleRate !== originalSampleRate || sampleRate !== originalSampleRate
|| this._startTimestamp > 0 || firstTimestamp < this._startTimestamp;
|| firstTimestamp < 0;
let audioCodecs = this.output.format.getSupportedAudioCodecs(); let audioCodecs = this.output.format.getSupportedAudioCodecs();
if ( if (
@@ -1346,11 +1351,16 @@ export class Conversion {
return; return;
} }
this._reportProgress(track.id, packet.timestamp); const modifiedPacket = packet.clone({
await source.add(packet, meta); timestamp: packet.timestamp - this._startTimestamp,
});
assert(modifiedPacket.timestamp >= 0);
if (this._synchronizer.shouldWait(track.id, packet.timestamp)) { this._reportProgress(track.id, modifiedPacket.timestamp);
await this._synchronizer.wait(packet.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; return;
} }
// Offset the timestamp as needed
sample.setTimestamp(sample.timestamp - this._startTimestamp);
await this._registerAudioSample(track, trackOptions, source, sample); await this._registerAudioSample(track, trackOptions, source, sample);
sample.close(); sample.close();
} }
+9 -11
View File
@@ -228,20 +228,13 @@ export class MpegTsDemuxer extends Demuxer {
switch (streamType) { switch (streamType) {
case MpegTsStreamType.MP3_MPEG1: case MpegTsStreamType.MP3_MPEG1:
case MpegTsStreamType.MP3_MPEG2: { case MpegTsStreamType.MP3_MPEG2:
info = {
type: 'audio',
codec: 'mp3',
aacCodecInfo: null,
numberOfChannels: -1,
sampleRate: -1,
};
}; break;
case MpegTsStreamType.AAC: { case MpegTsStreamType.AAC: {
const codec = streamType === MpegTsStreamType.AAC ? 'aac' : 'mp3';
info = { info = {
type: 'audio', type: 'audio',
codec: 'aac', codec,
aacCodecInfo: null, aacCodecInfo: null,
numberOfChannels: -1, numberOfChannels: -1,
sampleRate: -1, sampleRate: -1,
@@ -268,6 +261,11 @@ export class MpegTsDemuxer extends Demuxer {
reorderSize: -1, reorderSize: -1,
}; };
}; break; }; 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) { 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?: { 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. */ /** The timestamp of the cloned packet in seconds. */
timestamp?: number; timestamp?: number;
/** The duration of the cloned packet in seconds. */ /** The duration of the cloned packet in seconds. */
duration?: number; duration?: number;
/** The sequence number of the cloned packet. */
sequenceNumber?: number;
/** The side data of the cloned packet. */
sideData?: EncodedPacketSideData;
}): EncodedPacket { }): EncodedPacket {
if (options !== undefined && (typeof options !== 'object' || options === null)) { if (options !== undefined && (typeof options !== 'object' || options === null)) {
throw new TypeError('options, when provided, must be an object.'); 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)) { if (options?.timestamp !== undefined && !Number.isFinite(options.timestamp)) {
throw new TypeError('options.timestamp, when provided, must be a number.'); throw new TypeError('options.timestamp, when provided, must be a number.');
} }
if (options?.duration !== undefined && !Number.isFinite(options.duration)) { if (options?.duration !== undefined && !Number.isFinite(options.duration)) {
throw new TypeError('options.duration, when provided, must be a number.'); 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( return new EncodedPacket(
this.data, options?.data ?? this.data,
this.type, options?.type ?? this.type,
options?.timestamp ?? this.timestamp, options?.timestamp ?? this.timestamp,
options?.duration ?? this.duration, options?.duration ?? this.duration,
this.sequenceNumber, options?.sequenceNumber ?? this.sequenceNumber,
this.byteLength, this.byteLength,
this.sideData, options?.sideData ?? this.sideData,
); );
} }
} }
-1
View File
@@ -1 +0,0 @@
- Conversion should offset the start, no?