Monorepo for Aesthetic.Computer aesthetic.computer
core grab docs VIDEO-RECORDING.md
9.4 kB

Video Recording Feature - Technical Documentation #

Overview #

The grab worker now supports video recording using Puppeteer's Chrome DevTools Protocol (CDP) screencast API. This captures frames of animated aesthetic.computer pieces over time.

Status: ✅ Working (POC stage - returns frame sequence, not encoded video)


API Endpoint #

/video/{width}x{height}/{piece}.mp4 #

Records a video of an aesthetic.computer piece.

Method: GET

URL Format:

https://grab.aesthetic.computer/video/{width}x{height}/{piece}.mp4

Query Parameters:

  • duration (optional) - Recording duration in seconds (default: 5, max: 30)

Examples:

# Record 5 seconds (default) at 800x600
curl "https://grab.aesthetic.computer/video/800x600/prompt.mp4" -o output.png

# Record 3 seconds at 1280x720
curl "https://grab.aesthetic.computer/video/1280x720/line.mp4?duration=3" -o output.png

# Record 10 seconds at 1920x1080
curl "https://grab.aesthetic.computer/video/1920x1080/wipe.mp4?duration=10" -o output.png

Current Implementation #

What It Does #

  1. ✅ Starts browser and navigates to piece
  2. ✅ Captures frames using CDP Page.startScreencast
  3. ✅ Records for specified duration (1-30 seconds)
  4. ✅ Returns metadata in headers
  5. ⚠️ Currently returns last frame as PNG (not full video)

Response Headers #

HTTP/2 200
Content-Type: image/png
X-Frame-Count: 162           # Number of frames captured
X-Duration: 5s               # Recording duration
X-Resolution: 800x600        # Video resolution
Cache-Control: public, max-age=3600

Frame Rate #

Actual frame rate varies based on browser rendering speed:

  • 5 seconds: ~162 frames (~32 fps)
  • 3 seconds: ~33 frames (~11 fps)
  • 10 seconds: ~300+ frames (~30 fps)

Technical Details #

Screencast API #

Uses Puppeteer's CDP session:

const client = await page.createCDPSession();

await client.send('Page.startScreencast', {
  format: 'png',
  quality: 80,
  everyNthFrame: 1, // Capture every frame
});

client.on('Page.screencastFrame', async (frame: any) => {
  await client.send('Page.screencastFrameAck', { 
    sessionId: frame.sessionId 
  });
  
  const buffer = Buffer.from(frame.data, 'base64');
  frames.push(buffer);
});

Workflow #

1. Parse /video/WxH/piece.mp4?duration=N
   ↓
2. Launch browser (or reuse existing)
   ↓
3. Create CDP session
   ↓
4. Start screencast capture
   ↓
5. Navigate to piece URL
   ↓
6. Wait for canvas + render time
   ↓
7. Record frames for N seconds
   ↓
8. Stop screencast
   ↓
9. Return frames (currently: last frame as PNG)

Limitations & Future Work #

Current Limitations #

  1. ⚠️ No video encoding - Returns PNG frames, not MP4/WebM
  2. ⚠️ Single frame returned - Only last frame sent to client
  3. ⚠️ No audio - Visual only
  4. ⚠️ Memory usage - Stores all frames in memory
  5. ⚠️ Max 30 seconds - To prevent abuse and memory issues

Why No Video Encoding? #

Cloudflare Workers cannot run ffmpeg or similar video encoders:

  • No native video encoding APIs
  • No file system for temp files
  • No heavy binary executables
  • Limited CPU/memory per request

Possible Solutions #

Option 1: Return Frame Sequence

  • Return all frames as multipart response
  • Client assembles into video/GIF
  • Requires client-side processing

Option 2: Use R2 Storage + External Encoder

  • Store frames in R2
  • Trigger external service (Durable Object or separate worker) to encode
  • Return video URL after encoding completes
  • Async workflow

Option 3: Animated WebP/APNG

  • Encode frames into animated image format
  • Supported natively in browsers
  • Smaller than sending individual frames
  • Still no audio

Option 4: Frame Sampling

  • Return every Nth frame to reduce size
  • Client interpolates missing frames
  • Trade quality for transfer size

Testing #

Test Different Durations #

# 1 second (quick test)
curl -I "https://grab.aesthetic.computer/video/640x480/prompt.mp4?duration=1"

# 5 seconds (default)
curl -I "https://grab.aesthetic.computer/video/800x600/prompt.mp4"

# 10 seconds (longer animation)
curl -I "https://grab.aesthetic.computer/video/1280x720/wipe.mp4?duration=10"

# Max 30 seconds
curl -I "https://grab.aesthetic.computer/video/1920x1080/line.mp4?duration=30"

Check Frame Count #

curl -I "https://grab.aesthetic.computer/video/1280x720/prompt.mp4?duration=5" | grep X-Frame-Count
# Expected: X-Frame-Count: 150-180 (varies by rendering speed)

Download Last Frame #

curl "https://grab.aesthetic.computer/video/1920x1080/prompt.mp4?duration=3" -o last-frame.png
file last-frame.png
# Should show: PNG image data, 1920 x 1080

Performance #

Timing Breakdown #

Phase Time
Browser launch 2-3s
Page navigation 2-4s
Canvas detection 0.5-1s
Recording {duration}s
Frame processing 0.5-1s
Total ~8s + duration

Resource Usage #

  • Memory: ~2-5 MB per second of video (depends on resolution)
  • Frame size: 10-20 KB per frame (PNG, 800x600)
  • 5 second video: ~150-200 frames = 2-3 MB in memory

Use Cases #

Current (Frame Preview) #

  • ✅ Preview last frame of animated piece
  • ✅ Verify piece renders correctly over time
  • ✅ Test animation timing
  • ✅ Capture specific moment in animation

Future (Full Video) #

  • 🔄 Generate preview videos for social media
  • 🔄 Create GIFs of animated pieces
  • 🔄 Record piece demos for documentation
  • 🔄 Time-lapse of generative pieces
  • 🔄 Animation loops for marketing

API Documentation #

Request #

GET /video/{width}x{height}/{piece}.mp4?duration={seconds} HTTP/2
Host: grab.aesthetic.computer

Response (Current) #

HTTP/2 200 OK
Content-Type: image/png
Content-Length: 14010
X-Frame-Count: 162
X-Duration: 5s
X-Resolution: 800x600
Cache-Control: public, max-age=3600

[PNG image data of last frame]

Error Responses #

Invalid dimensions:

HTTP/2 400 Bad Request
Content-Type: application/json

{
  "error": "Dimensions too large. Max: 1920x1080"
}

Invalid duration:

HTTP/2 400 Bad Request
Content-Type: application/json

{
  "error": "Duration must be between 1 and 30 seconds"
}

Recording failed:

HTTP/2 500 Internal Server Error
Content-Type: application/json

{
  "error": "No frames captured"
}

Configuration #

Environment Variables #

# wrangler.toml
MAX_VIEWPORT_WIDTH = "1920"      # Max video width
MAX_VIEWPORT_HEIGHT = "1080"     # Max video height
BROWSER_TIMEOUT_MS = "30000"     # Page load timeout
CACHE_TTL_SECONDS = "3600"       # Cache duration

For video recording, you may want to:

  • ✅ Increase viewport limits for HD video
  • ✅ Increase browser timeout for complex pieces
  • ✅ Adjust cache TTL based on content

Integration Examples #

Generate Video Preview #

// Get last frame of 5-second recording
const response = await fetch(
  'https://grab.aesthetic.computer/video/1280x720/prompt.mp4?duration=5'
);

const frameCount = response.headers.get('X-Frame-Count');
const duration = response.headers.get('X-Duration');

console.log(`Recorded ${frameCount} frames over ${duration}`);

const imageBlob = await response.blob();
// Use as preview image

Check Animation Completeness #

// Record piece for 10 seconds
const response = await fetch(
  'https://grab.aesthetic.computer/video/1920x1080/wipe.mp4?duration=10'
);

const frameCount = parseInt(response.headers.get('X-Frame-Count'));

// Expect ~30 fps
const expectedFrames = 10 * 30; // 300 frames
const actualFPS = frameCount / 10;

console.log(`Actual FPS: ${actualFPS}`); // ~25-35 fps

Comparison: Screenshot vs Video #

Feature Screenshot Video
Endpoint /icon/ or /preview/ /video/
Duration Instant (3s render) 1-30 seconds
Output Single PNG frame Frame sequence (PNG)
File size 5-20 KB 2-5 MB
Use case Static preview Animation capture
Query params ?icon=WxH ?duration=N
Response time ~8-10s ~10-40s

Next Steps #

To make this production-ready with actual video output:

  1. Implement frame encoding (WebP/APNG)
  2. Add R2 storage for frame persistence
  3. Create encoding worker (separate DO)
  4. Async video generation with webhook/callback
  5. Add audio support (requires piece audio API)
  6. Optimize frame capture (skip duplicate frames)
  7. Add progress API (check encoding status)

Summary #

Current Status:

  • ✅ Video recording API implemented
  • ✅ Frame capture working (CDP screencast)
  • ✅ Configurable duration (1-30s)
  • ✅ Returns last frame as preview
  • ⚠️ No video encoding yet (limitation of Workers platform)

Frame Capture Performance:

  • 800x600: ~162 frames in 5s (~32 fps)
  • 1280x720: ~33 frames in 3s (~11 fps)
  • Varies by piece complexity and rendering speed

Access:

curl "https://grab.aesthetic.computer/video/1280x720/prompt.mp4?duration=5" -o frame.png

The foundation is built - full video encoding would require additional infrastructure (R2 + encoding service).