mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-29 03:43:50 +02:00
+40
-8
@@ -83,6 +83,8 @@ export type ConversionOptions = {
|
||||
bitrate?: VideoEncodingConfig['bitrate'];
|
||||
/** When true, video will always be re-encoded instead of directly copying over the encoded samples. */
|
||||
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. */
|
||||
@@ -261,6 +263,12 @@ export class Conversion {
|
||||
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.');
|
||||
}
|
||||
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')) {
|
||||
throw new TypeError('options.video, when provided, must be an object.');
|
||||
}
|
||||
@@ -469,11 +477,19 @@ export class Conversion {
|
||||
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 needsTranscode = !!this._options.video?.forceTranscode || this._startTimestamp > 0 || firstTimestamp < 0;
|
||||
const needsRerender = width !== originalWidth
|
||||
|| height !== originalHeight
|
||||
|| (totalRotation !== 0 && !outputSupportsRotation);
|
||||
|| (totalRotation !== 0 && !outputSupportsRotation)
|
||||
|| fps !== originalFps;
|
||||
|
||||
let videoCodecs = this.output.format.getSupportedVideoCodecs();
|
||||
if (
|
||||
@@ -563,6 +579,10 @@ export class Conversion {
|
||||
});
|
||||
const iterator = sink.canvases(this._startTimestamp, this._endTimestamp);
|
||||
|
||||
const frameDuration = 1 / fps;
|
||||
|
||||
let outputTimestamp = 0;
|
||||
|
||||
for await (const { canvas, timestamp, duration } of iterator) {
|
||||
if (this._synchronizer.shouldWait(track.id, timestamp)) {
|
||||
await this._synchronizer.wait(timestamp);
|
||||
@@ -572,13 +592,25 @@ export class Conversion {
|
||||
return;
|
||||
}
|
||||
|
||||
const sample = new VideoSample(canvas, {
|
||||
timestamp: Math.max(timestamp - this._startTimestamp, 0),
|
||||
duration,
|
||||
});
|
||||
|
||||
await source.add(sample);
|
||||
sample.close();
|
||||
const relativeToStartTimestamp = Math.max(timestamp - this._startTimestamp, 0);
|
||||
if (originalFps === fps) {
|
||||
const sample = new VideoSample(canvas, {
|
||||
timestamp: relativeToStartTimestamp,
|
||||
duration,
|
||||
});
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user