diff --git a/main.js b/main.js index 76189de..ead4438 100644 --- a/main.js +++ b/main.js @@ -649,8 +649,13 @@ 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 => { const img = new Image(); @@ -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; - - // 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); + 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); + } // 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 diff --git a/whammy.js b/whammy.js index 6a81648..3f90768 100644 --- a/whammy.js +++ b/whammy.js @@ -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 - - // we need to keep track of the duration of each frame + // 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; + } + 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;