Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

Animated GIF Feature Status#

✅ Working Features#

  • GIF Encoding: Successfully encoding frames to animated GIF using gifenc library
  • PNG Decoding: Decoding PNG frames to RGBA using fast-png library
  • Video Endpoint: /video/{width}x{height}/{piece}.gif?duration={seconds}
  • Deployment: Working in Cloudflare Workers production environment
  • Output: Valid animated GIF files with proper headers

⚠️ Current Limitations#

Low Frame Rate (~0.4 FPS, 2 frames per 5 seconds)#

Issue: CDP Page.startScreencast only captures 1-2 frames during a 5-second recording period, regardless of parameters.

Investigation Results:

  • ✅ Event listener order: Confirmed correct (listener before startScreencast)
  • ✅ Frame acknowledgment: Working properly
  • ✅ Navigation timing: Tested before and after navigation
  • ✅ Content rendering: Confirmed 2-second wait for render
  • ✅ Parameters tested:
    • format: 'png' and 'jpeg' (both same result)
    • quality: 60-80 (no effect)
    • maxWidth/maxHeight: Tested various sizes
    • everyNthFrame: 1 (capture every frame)

Comparison:

  • Previous POC (returning PNG frames): 162 frames in 5s (~32 FPS)
  • Current (with GIF encoding): 2 frames in 5s (~0.4 FPS)

Hypothesis: The CDP screencast API in Cloudflare Workers' Browser Rendering environment appears to be heavily throttled or behaves differently than regular Chrome. The frame encoding process happens AFTER capture, so it shouldn't affect capture rate.

🔧 Tested Parameters#

await client.send('Page.startScreencast', {
  format: 'png',          // Tried: 'png', 'jpeg'
  quality: 80,            // Tried: 60, 80
  maxWidth: params.width, // Tried: various resolutions
  maxHeight: params.height,
  everyNthFrame: 1,       // Tried: with and without
});

📊 Performance Metrics#

  • Request Duration: ~20-24 seconds for 5-second video
    • ~2s: Navigation + content load
    • ~5s: Recording duration
    • ~13-17s: Frame processing + GIF encoding
  • Output Size: ~3-4KB for 2 frames at 600x400
  • Memory: Frames stored in memory as Buffer array

🎯 Potential Solutions#

1. Alternative Capture Method#

Instead of CDP screencast, use a loop with page.screenshot():

for (let i = 0; i < targetFrames; i++) {
  const screenshot = await page.screenshot();
  frames.push(screenshot);
  await new Promise(resolve => setTimeout(resolve, frameDelay));
}

Trade-off: More control over frame rate, but higher CPU usage and potentially slower.

2. Client-Side Capture#

Have the browser capture frames client-side and send them back:

await page.evaluate(() => {
  const canvas = document.querySelector('canvas');
  // Capture frames using canvas.toDataURL() or similar
});

Trade-off: Requires injecting code, but might bypass CDP throttling.

3. Accept Lower Frame Rate#

The current 0.4 FPS captures "before" and "after" snapshots, which could be useful for certain use cases (e.g., showing initial vs. final state).

4. Increase Duration#

With longer durations, we might get more frames:

  • 10s: Maybe 3-4 frames (~0.3-0.4 FPS)
  • 30s: Maybe 10-12 frames (~0.3-0.4 FPS)

📝 Files Modified#

  • /workspaces/aesthetic-computer/grab/src/index.ts:

    • Added recordVideo() method
    • Integrated gifenc for GIF encoding
    • Integrated fast-png for PNG decoding
    • Video endpoint routing
  • /workspaces/aesthetic-computer/grab/package.json:

    • Added gifenc@1.0.3 (MIT, 0 dependencies)
    • Added fast-png@7.0.1 (MIT, 3 dependencies)

🚀 Usage#

# Generate 5-second animated GIF
curl "https://aesthetic-grab.aesthetic-computer.workers.dev/video/800x600/starfield.gif?duration=5" -o starfield.gif

# Generate 3-second animated GIF
curl "https://aesthetic-grab.aesthetic-computer.workers.dev/video/400x300/prompt.gif?duration=3" -o prompt.gif

# Check metadata
curl -I "https://aesthetic-grab.aesthetic-computer.workers.dev/video/600x400/starfield.gif?duration=5"
# Returns:
# X-Frame-Count: 2
# X-FPS: 0.40
# X-Duration: 5s
# X-Resolution: 600x400
# Content-Type: image/gif

🎨 Libraries Used#

gifenc#

  • Version: 1.0.3
  • License: MIT
  • Dependencies: 0
  • Size: 172.5 KB unpacked
  • Purpose: Fast JavaScript GIF encoder
  • Features: Quantization, palette mapping, streaming encoder

fast-png#

  • Version: 7.0.1
  • License: MIT
  • Dependencies: 3 (@types/pako, iobuffer, pako)
  • Size: 160.1 KB unpacked
  • Purpose: PNG decoder/encoder in pure JavaScript
  • Performance: Fast decoding of PNG to RGBA data

📚 References#

🔮 Next Steps#

  1. Test Alternative Capture: Implement loop-based screenshot capture
  2. Investigate CDP Throttling: Research Cloudflare Workers Browser Rendering limitations
  3. Optimize for 0.4 FPS: Adjust UI/expectations for current frame rate
  4. Add JPEG Support: Consider JPEG format for smaller file sizes
  5. R2 Storage: For longer videos, store intermediate frames in R2