Fix video generation crash: patch Whammy.js logic and safeguard empty frames

This commit is contained in:
2025-12-28 11:55:27 +11:00
parent 83cec85258
commit 7cfe4f74a5
2 changed files with 36 additions and 16 deletions

28
main.js
View File

@@ -649,7 +649,12 @@ async function downloadVideo() {
// We will generate a frame for every 1/30th of a second
const frameInterval = 1000 / fps; // ~33.33ms
const totalOutputFrames = Math.ceil(totalDuration / frameInterval);
let totalOutputFrames = Math.ceil(totalDuration / frameInterval);
// Ensure at least one frame if duration is 0 or very small
if (totalOutputFrames <= 0) totalOutputFrames = 1;
console.log(`Generating ${totalOutputFrames} output frames`);
// Pre-load all images
const loadedImages = await Promise.all(videoFrames.map(async frame => {
@@ -664,6 +669,10 @@ async function downloadVideo() {
const validImages = loadedImages.filter(i => i !== null);
if (validImages.length === 0) {
throw new Error("Failed to load any source images");
}
// Generate video frames
for (let i = 0; i < totalOutputFrames; i++) {
const currentTime = i * frameInterval;
@@ -685,14 +694,16 @@ async function downloadVideo() {
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Center and scale image (Contain)
const scale = Math.min(canvas.width / currentImage.img.width, canvas.height / currentImage.img.height);
const x = (canvas.width - currentImage.img.width * scale) / 2;
const y = (canvas.height - currentImage.img.height * scale) / 2;
if (currentImage && currentImage.img) {
const scale = Math.min(canvas.width / currentImage.img.width, canvas.height / currentImage.img.height);
const x = (canvas.width - currentImage.img.width * scale) / 2;
const y = (canvas.height - currentImage.img.height * scale) / 2;
// Use high quality image smoothing
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(currentImage.img, x, y, currentImage.img.width * scale, currentImage.img.height * scale);
// Use high quality image smoothing
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(currentImage.img, x, y, currentImage.img.width * scale, currentImage.img.height * scale);
}
// Add timestamp overlay (crisp text)
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
@@ -702,6 +713,7 @@ async function downloadVideo() {
ctx.fillText(`${(currentTime / 1000).toFixed(1)}s`, 40, canvas.height - 35);
// Add frame to encoder
// Note: add() might parse the webp immediately, so this must happen
encoder.add(canvas);
// Yield to UI thread occasionally to prevent freezing

View File

@@ -208,16 +208,24 @@
var height = tmp & 0x3FFF;
}
// now we want to put the VP8 chunk into the RIFF
// we use valid RIFF structures: RIFF -> WEBP -> VP8
// but we only care about the VP8 chunk
// so we just throw it in there
//riff.RIFF[0].WEBP.push(vp8); // this won't work because it's not a valid RIFF stucture
// Fix: Assign dimensions to the frame object so checkFrames can access them
if (typeof width !== 'undefined') {
frames[i].width = width;
frames[i].height = height;
} else {
// Copy from first frame if not the first iteration
frames[i].width = frames[0].width;
frames[i].height = frames[0].height;
}
// we need to keep track of the duration of each frame
frames[i].data = vp8;
}
// Safety check for empty frames
if (!frames || frames.length === 0) {
throw "No frames to compile";
}
var info = checkFrames(frames);
var CLUSTER_MAX_DURATION = 30000;