Skip to content

Commit

Permalink
implement playback fps for gif capture
Browse files Browse the repository at this point in the history
  • Loading branch information
louisholley committed Dec 27, 2024
1 parent cbd6a48 commit 8bfa054
Showing 1 changed file with 59 additions and 20 deletions.
79 changes: 59 additions & 20 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ const DELAY_MAX = 300000;
// GIF specific constants
const GIF_DEFAULTS = {
FRAME_COUNT: 30,
FRAME_DELAY: 100, // milliseconds between frames
CAPTURE_INTERVAL: 100, // milliseconds between capturing frames
PLAYBACK_FPS: 10, // default playback speed in frames per second
QUALITY: 10,
MIN_FRAMES: 2,
MAX_FRAMES: 100,
MIN_DELAY: 20,
MAX_DELAY: 1000,
MIN_CAPTURE_INTERVAL: 20,
MAX_CAPTURE_INTERVAL: 15000,
MIN_FPS: 1,
MAX_FPS: 50,
};

// the different capture modes
Expand Down Expand Up @@ -52,24 +55,34 @@ const sleep = (time) =>
setTimeout(resolve, time);
});

function validateGifParams(frameCount, frameDelay) {
function validateGifParams(frameCount, captureInterval, playbackFps) {
if (
frameCount < GIF_DEFAULTS.MIN_FRAMES ||
frameCount > GIF_DEFAULTS.MAX_FRAMES
) {
return false;
}
if (
frameDelay < GIF_DEFAULTS.MIN_DELAY ||
frameDelay > GIF_DEFAULTS.MAX_DELAY
captureInterval < GIF_DEFAULTS.MIN_CAPTURE_INTERVAL ||
captureInterval > GIF_DEFAULTS.MAX_CAPTURE_INTERVAL
) {
return false;
}
if (
playbackFps < GIF_DEFAULTS.MIN_FPS ||
playbackFps > GIF_DEFAULTS.MAX_FPS
) {
return false;
}
return true;
}

async function captureFramesToGif(frames, width, height, frameDelay) {
async function captureFramesToGif(frames, width, height, playbackFps) {
const gif = GIFEncoder();
const playbackDelay = Math.round(1000 / playbackFps);
console.log(
`Creating GIF with playback delay: ${playbackDelay}ms (${playbackFps} FPS)`
);

for (const frame of frames) {
let pngData;
Expand Down Expand Up @@ -100,7 +113,7 @@ async function captureFramesToGif(frames, width, height, frameDelay) {

gif.writeFrame(index, width, height, {
palette,
delay: frameDelay,
delay: playbackDelay,
});
}

Expand Down Expand Up @@ -133,7 +146,13 @@ const waitPreview = (triggerMode, page, delay) =>
}
});

async function captureViewport(page, isGif, frameCount, frameDelay) {
async function captureViewport(
page,
isGif,
frameCount,
captureInterval,
playbackFps
) {
if (!isGif) {
return await page.screenshot();
}
Expand All @@ -144,19 +163,26 @@ async function captureViewport(page, isGif, frameCount, frameDelay) {
encoding: "binary",
});
frames.push(frameBuffer);
await sleep(frameDelay);
await sleep(captureInterval);
}

const viewport = page.viewport();
return await captureFramesToGif(
frames,
viewport.width,
viewport.height,
frameDelay
playbackFps
);
}

async function captureCanvas(page, selector, isGif, frameCount, frameDelay) {
async function captureCanvas(
page,
selector,
isGif,
frameCount,
captureInterval,
playbackFps
) {
if (!isGif) {
console.log("converting canvas to PNG with selector:", selector);
const base64 = await page.$eval(selector, (el) => {
Expand All @@ -176,7 +202,7 @@ async function captureCanvas(page, selector, isGif, frameCount, frameDelay) {
});
if (!base64) throw null;
frames.push(base64);
await sleep(frameDelay);
await sleep(captureInterval);
}

const dimensions = await page.$eval(selector, (el) => ({
Expand All @@ -188,7 +214,7 @@ async function captureCanvas(page, selector, isGif, frameCount, frameDelay) {
frames,
dimensions.width,
dimensions.height,
frameDelay
playbackFps
);
}

Expand Down Expand Up @@ -268,7 +294,11 @@ program
)
.option("--gif", "Create an animated GIF instead of a static image")
.option("--frameCount <frameCount>", "Number of frames for GIF")
.option("--frameDelay <frameDelay>", "Delay between frames in milliseconds");
.option(
"--captureInterval <captureInterval>",
"Interval between frames for GIF"
)
.option("--playbackFps <playbackFps>", "Playback speed for GIF");

program.parse(process.argv);

Expand All @@ -289,7 +319,8 @@ const main = async () => {
selector,
gif = false,
frameCount = GIF_DEFAULTS.FRAME_COUNT,
frameDelay = GIF_DEFAULTS.FRAME_DELAY,
captureInterval = GIF_DEFAULTS.CAPTURE_INTERVAL,
playbackFps = GIF_DEFAULTS.PLAYBACK_FPS,
} = program.opts();

console.log("running capture with params:", {
Expand All @@ -302,7 +333,8 @@ const main = async () => {
selector,
gif,
frameCount,
frameDelay,
captureInterval,
playbackFps,
});

// default parameter for triggerMode
Expand All @@ -323,7 +355,7 @@ const main = async () => {
}

// validate GIF parameters if GIF mode is enabled
if (gif && !validateGifParams(frameCount, frameDelay)) {
if (gif && !validateGifParams(frameCount, captureInterval, playbackFps)) {
throw ERRORS.INVALID_GIF_PARAMETERS;
}

Expand Down Expand Up @@ -415,14 +447,21 @@ const main = async () => {

// based on the capture mode use different capture strategies
if (mode === "VIEWPORT") {
capture = await captureViewport(page, gif, frameCount, frameDelay);
capture = await captureViewport(
page,
gif,
frameCount,
captureInterval,
playbackFps
);
} else if (mode === "CANVAS") {
capture = await captureCanvas(
page,
selector,
gif,
frameCount,
frameDelay
captureInterval,
playbackFps
);
}
} catch (err) {
Expand Down

0 comments on commit 8bfa054

Please sign in to comment.