mirror of
https://github.com/DeNNiiInc/Web-Page-Performance-Test.git
synced 2026-04-17 20:05:58 +00:00
Fix video generation crash: patch Whammy.js logic and safeguard empty frames
This commit is contained in:
30
main.js
30
main.js
@@ -649,8 +649,13 @@ async function downloadVideo() {
|
|||||||
|
|
||||||
// We will generate a frame for every 1/30th of a second
|
// We will generate a frame for every 1/30th of a second
|
||||||
const frameInterval = 1000 / fps; // ~33.33ms
|
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
|
// Pre-load all images
|
||||||
const loadedImages = await Promise.all(videoFrames.map(async frame => {
|
const loadedImages = await Promise.all(videoFrames.map(async frame => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
@@ -664,6 +669,10 @@ async function downloadVideo() {
|
|||||||
|
|
||||||
const validImages = loadedImages.filter(i => i !== null);
|
const validImages = loadedImages.filter(i => i !== null);
|
||||||
|
|
||||||
|
if (validImages.length === 0) {
|
||||||
|
throw new Error("Failed to load any source images");
|
||||||
|
}
|
||||||
|
|
||||||
// Generate video frames
|
// Generate video frames
|
||||||
for (let i = 0; i < totalOutputFrames; i++) {
|
for (let i = 0; i < totalOutputFrames; i++) {
|
||||||
const currentTime = i * frameInterval;
|
const currentTime = i * frameInterval;
|
||||||
@@ -685,14 +694,16 @@ async function downloadVideo() {
|
|||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
// Center and scale image (Contain)
|
// Center and scale image (Contain)
|
||||||
const scale = Math.min(canvas.width / currentImage.img.width, canvas.height / currentImage.img.height);
|
if (currentImage && currentImage.img) {
|
||||||
const x = (canvas.width - currentImage.img.width * scale) / 2;
|
const scale = Math.min(canvas.width / currentImage.img.width, canvas.height / currentImage.img.height);
|
||||||
const y = (canvas.height - currentImage.img.height * scale) / 2;
|
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;
|
// Use high quality image smoothing
|
||||||
ctx.imageSmoothingQuality = 'high';
|
ctx.imageSmoothingEnabled = true;
|
||||||
ctx.drawImage(currentImage.img, x, y, currentImage.img.width * scale, currentImage.img.height * scale);
|
ctx.imageSmoothingQuality = 'high';
|
||||||
|
ctx.drawImage(currentImage.img, x, y, currentImage.img.width * scale, currentImage.img.height * scale);
|
||||||
|
}
|
||||||
|
|
||||||
// Add timestamp overlay (crisp text)
|
// Add timestamp overlay (crisp text)
|
||||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
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);
|
ctx.fillText(`${(currentTime / 1000).toFixed(1)}s`, 40, canvas.height - 35);
|
||||||
|
|
||||||
// Add frame to encoder
|
// Add frame to encoder
|
||||||
|
// Note: add() might parse the webp immediately, so this must happen
|
||||||
encoder.add(canvas);
|
encoder.add(canvas);
|
||||||
|
|
||||||
// Yield to UI thread occasionally to prevent freezing
|
// Yield to UI thread occasionally to prevent freezing
|
||||||
|
|||||||
22
whammy.js
22
whammy.js
@@ -208,16 +208,24 @@
|
|||||||
var height = tmp & 0x3FFF;
|
var height = tmp & 0x3FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
// now we want to put the VP8 chunk into the RIFF
|
// Fix: Assign dimensions to the frame object so checkFrames can access them
|
||||||
// we use valid RIFF structures: RIFF -> WEBP -> VP8
|
if (typeof width !== 'undefined') {
|
||||||
// but we only care about the VP8 chunk
|
frames[i].width = width;
|
||||||
// so we just throw it in there
|
frames[i].height = height;
|
||||||
//riff.RIFF[0].WEBP.push(vp8); // this won't work because it's not a valid RIFF stucture
|
} else {
|
||||||
|
// Copy from first frame if not the first iteration
|
||||||
// we need to keep track of the duration of each frame
|
frames[i].width = frames[0].width;
|
||||||
|
frames[i].height = frames[0].height;
|
||||||
|
}
|
||||||
|
|
||||||
frames[i].data = vp8;
|
frames[i].data = vp8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Safety check for empty frames
|
||||||
|
if (!frames || frames.length === 0) {
|
||||||
|
throw "No frames to compile";
|
||||||
|
}
|
||||||
|
|
||||||
var info = checkFrames(frames);
|
var info = checkFrames(frames);
|
||||||
|
|
||||||
var CLUSTER_MAX_DURATION = 30000;
|
var CLUSTER_MAX_DURATION = 30000;
|
||||||
|
|||||||
Reference in New Issue
Block a user