Fixed doubly stacked rotation correction when converting rotated video with a custom process function (fixes #218)

This commit is contained in:
Vanilagy
2025-11-11 11:05:15 +01:00
parent 6022781e8c
commit 3db4aa8b91
3 changed files with 19 additions and 9 deletions
+9 -6
View File
@@ -48,6 +48,7 @@
target target
}); });
let ctx = null;
const conversion = await Mediabunny.Conversion.init({ const conversion = await Mediabunny.Conversion.init({
input: new Mediabunny.Input({ input: new Mediabunny.Input({
formats: Mediabunny.ALL_FORMATS, formats: Mediabunny.ALL_FORMATS,
@@ -56,6 +57,7 @@
output, output,
audio: (_, n) => ({ audio: (_, n) => ({
discard: n > 1, discard: n > 1,
//codec: 'opus',
/* /*
process: (sample) => { process: (sample) => {
return sample; return sample;
@@ -97,7 +99,7 @@
}, },
*/ */
video: () => ({ video: () => ({
/* //discard: true,
process: (sample) => { process: (sample) => {
if (!ctx) { if (!ctx) {
// Create a canvas for image compositing // Create a canvas for image compositing
@@ -108,13 +110,14 @@
ctx = canvas.getContext('2d'); ctx = canvas.getContext('2d');
} }
console.log(ctx.canvas.width, ctx.canvas.height);
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
sample.draw(ctx, 0, 0); sample.drawWithFit(ctx, { fit: 'fill' });
ctx.drawImage(watermark, 32, 32); //ctx.drawImage(watermark, 32, 32);
return ctx.canvas; return ctx.canvas;
}, },
*/
//width: 300, //width: 300,
//alpha: 'keep', //alpha: 'keep',
//width: 320, //width: 320,
@@ -170,8 +173,8 @@
} }
}, },
trim: { trim: {
start: 0, //start: 0,
end: 10 //end: 10
}, },
}); });
console.log(conversion); console.log(conversion);
+6 -2
View File
@@ -182,7 +182,8 @@ export type ConversionVideoOptions = {
* corrections. * corrections.
* *
* Must return a {@link VideoSample} or a `CanvasImageSource`, an array of them, or `null` for dropping the frame. * Must return a {@link VideoSample} or a `CanvasImageSource`, an array of them, or `null` for dropping the frame.
* When non-timestamped data is returned, the timestamp and duration from the source sample will be used. * When non-timestamped data is returned, the timestamp and duration from the source sample will be used. Rotation
* metadata of the returned sample will be ignored.
* *
* This function can also be used to manually resize frames. When doing so, you should signal the post-process * This function can also be used to manually resize frames. When doing so, you should signal the post-process
* dimensions using the `processedWidth` and `processedHeight` fields, which enables the encoder to better know what * dimensions using the `processedWidth` and `processedHeight` fields, which enables the encoder to better know what
@@ -873,7 +874,10 @@ export class Conversion {
|| trackOptions.process !== undefined; || trackOptions.process !== undefined;
let needsRerender = width !== originalWidth let needsRerender = width !== originalWidth
|| height !== originalHeight || height !== originalHeight
|| (totalRotation !== 0 && !outputSupportsRotation) // TODO This is suboptimal: Forcing a rerender when both rotation and process are set is not
// performance-optimal, but right now there's no other way because we can't change the track rotation
// metadata after the output has already started. Should be possible with API changes in v2, though!
|| (totalRotation !== 0 && (!outputSupportsRotation || trackOptions.process !== undefined))
|| !!crop; || !!crop;
const alpha = trackOptions.alpha ?? 'discard'; const alpha = trackOptions.alpha ?? 'discard';
+4 -1
View File
@@ -552,7 +552,6 @@ export class VideoSample implements Disposable {
dHeight, dHeight,
); );
// Restore the previous transformation state
context.restore(); context.restore();
} }
@@ -644,6 +643,8 @@ export class VideoSample implements Disposable {
dy = (canvasHeight - newHeight) / 2; dy = (canvasHeight - newHeight) / 2;
} }
context.save();
const aspectRatioChange = rotation % 180 === 0 ? 1 : newWidth / newHeight; const aspectRatioChange = rotation % 180 === 0 ? 1 : newWidth / newHeight;
context.translate(canvasWidth / 2, canvasHeight / 2); context.translate(canvasWidth / 2, canvasHeight / 2);
context.rotate(rotation * Math.PI / 180); context.rotate(rotation * Math.PI / 180);
@@ -655,6 +656,8 @@ export class VideoSample implements Disposable {
// Important that we don't use .draw() here since that would take rotation into account, but we wanna handle it // Important that we don't use .draw() here since that would take rotation into account, but we wanna handle it
// ourselves here // ourselves here
context.drawImage(this.toCanvasImageSource(), sx, sy, sWidth, sHeight, dx, dy, newWidth, newHeight); context.drawImage(this.toCanvasImageSource(), sx, sy, sWidth, sHeight, dx, dy, newWidth, newHeight);
context.restore();
} }
/** @internal */ /** @internal */