Merge pull request #45 from yonathan06/main

Custom fps convertion WIP
This commit is contained in:
David P.
2025-08-08 17:20:04 +02:00
committed by GitHub
+40 -8
View File
@@ -83,6 +83,8 @@ export type ConversionOptions = {
bitrate?: VideoEncodingConfig['bitrate']; bitrate?: VideoEncodingConfig['bitrate'];
/** When true, video will always be re-encoded instead of directly copying over the encoded samples. */ /** When true, video will always be re-encoded instead of directly copying over the encoded samples. */
forceTranscode?: boolean; forceTranscode?: boolean;
/** The desired fps of the output video, if not specified will use the fps of the input video. */
fps?: number;
}; };
/** Audio-specific options. */ /** Audio-specific options. */
@@ -261,6 +263,12 @@ export class Conversion {
if (options.video?.rotate !== undefined && ![0, 90, 180, 270].includes(options.video.rotate)) { if (options.video?.rotate !== undefined && ![0, 90, 180, 270].includes(options.video.rotate)) {
throw new TypeError('options.video.rotate, when provided, must be 0, 90, 180 or 270.'); throw new TypeError('options.video.rotate, when provided, must be 0, 90, 180 or 270.');
} }
if (
options.video?.fps !== undefined
&& (typeof options.video.fps !== 'number' || isNaN(options.video.fps) || options.video.fps <= 0)
) {
throw new TypeError('options.video.fps, when provided, must be a positive number.');
}
if (options.audio !== undefined && (!options.audio || typeof options.audio !== 'object')) { if (options.audio !== undefined && (!options.audio || typeof options.audio !== 'object')) {
throw new TypeError('options.video, when provided, must be an object.'); throw new TypeError('options.video, when provided, must be an object.');
} }
@@ -469,11 +477,19 @@ export class Conversion {
height = ceilToMultipleOfTwo(this._options.video.height); height = ceilToMultipleOfTwo(this._options.video.height);
} }
const packetStats = await track.computePacketStats();
const originalFps = packetStats.averagePacketRate;
let fps = originalFps;
if (this._options.video?.fps !== undefined) {
fps = this._options.video.fps;
}
const firstTimestamp = await track.getFirstTimestamp(); const firstTimestamp = await track.getFirstTimestamp();
const needsTranscode = !!this._options.video?.forceTranscode || this._startTimestamp > 0 || firstTimestamp < 0; const needsTranscode = !!this._options.video?.forceTranscode || this._startTimestamp > 0 || firstTimestamp < 0;
const needsRerender = width !== originalWidth const needsRerender = width !== originalWidth
|| height !== originalHeight || height !== originalHeight
|| (totalRotation !== 0 && !outputSupportsRotation); || (totalRotation !== 0 && !outputSupportsRotation)
|| fps !== originalFps;
let videoCodecs = this.output.format.getSupportedVideoCodecs(); let videoCodecs = this.output.format.getSupportedVideoCodecs();
if ( if (
@@ -563,6 +579,10 @@ export class Conversion {
}); });
const iterator = sink.canvases(this._startTimestamp, this._endTimestamp); const iterator = sink.canvases(this._startTimestamp, this._endTimestamp);
const frameDuration = 1 / fps;
let outputTimestamp = 0;
for await (const { canvas, timestamp, duration } of iterator) { for await (const { canvas, timestamp, duration } of iterator) {
if (this._synchronizer.shouldWait(track.id, timestamp)) { if (this._synchronizer.shouldWait(track.id, timestamp)) {
await this._synchronizer.wait(timestamp); await this._synchronizer.wait(timestamp);
@@ -572,13 +592,25 @@ export class Conversion {
return; return;
} }
const sample = new VideoSample(canvas, { const relativeToStartTimestamp = Math.max(timestamp - this._startTimestamp, 0);
timestamp: Math.max(timestamp - this._startTimestamp, 0), if (originalFps === fps) {
duration, const sample = new VideoSample(canvas, {
}); timestamp: relativeToStartTimestamp,
duration,
await source.add(sample); });
sample.close(); await source.add(sample);
sample.close();
} else {
while (relativeToStartTimestamp >= outputTimestamp) {
const sample = new VideoSample(canvas, {
timestamp: outputTimestamp,
duration: frameDuration,
});
await source.add(sample);
sample.close();
outputTimestamp += frameDuration;
}
}
} }
})()); })());
} else { } else {