KeiSeiKit-1.0/skills/video-gen/SKILL.md
Parfii-bot 0be354a920 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

367 lines
9.8 KiB
Markdown

---
name: video-gen
description: Use when generating video from frame sequences — FFmpeg extraction, WebP/AVIF conversion, sprite sheets, scroll-synced playback, video encoding/transcoding. Covers the full pipeline from video source to web-ready frame sequence or optimized video.
arguments:
- name: source
description: "Source: video file path, image sequence directory, or AI-generated frames"
required: true
- name: target
description: "Target: frame-sequence, sprite-sheet, optimized-video, gif (default: frame-sequence)"
required: false
---
# Video-Gen Skill — Frame Sequence Pipeline
## Pipeline Overview
```
Source Video (MP4/MOV/ProRes)
├─→ [Frame Extraction] FFmpeg → PNG sequence
│ │
│ ├─→ [Optimize] cwebp → WebP sequence (primary)
│ ├─→ [Optimize] avifenc → AVIF sequence (smaller, slower encode)
│ └─→ [Sprite Sheet] ImageMagick montage → single image
├─→ [Video Scrub] FFmpeg re-encode → optimized MP4 for scroll scrub
└─→ [Web Playback] Canvas + ScrollTrigger / video.currentTime
```
---
## 1. Frame Extraction [E1]
### Basic Extraction
```bash
# Extract all frames at source FPS
ffmpeg -i source.mp4 -qscale:v 2 frames/frame_%04d.png
# Extract at specific FPS (30fps → 150 frames for 5s video)
ffmpeg -i source.mp4 -vf "fps=30" frames/frame_%04d.png
# Extract with resolution scaling
ffmpeg -i source.mp4 -vf "fps=30,scale=1920:1080" frames/frame_%04d.png
# Extract specific time range (2s to 7s)
ffmpeg -i source.mp4 -ss 2 -t 5 -vf "fps=30" frames/frame_%04d.png
```
### Frame Count Guidelines
| Duration | Desktop (30fps) | Mobile (15fps) | Notes |
|----------|-----------------|----------------|-------|
| 3 seconds | 90 frames | 45 frames | Short reveal |
| 5 seconds | 150 frames | 75 frames | Product showcase |
| 10 seconds | 300 frames | 150 frames | Full story section |
| 15 seconds | 450 frames | 225 frames | Max recommended |
**Rule:** More frames = smoother but heavier. 120-180 is the sweet spot for most scroll animations.
---
## 2. Format Conversion [E1]
### PNG to WebP (Recommended)
```bash
# Single file
cwebp -q 80 frame_0001.png -o frame_0001.webp
# Batch convert all PNGs
for f in frames/*.png; do
cwebp -q 80 "$f" -o "${f%.png}.webp"
done
# Parallel batch (faster)
find frames/ -name "*.png" | xargs -P 8 -I {} sh -c '
cwebp -q 80 "{}" -o "$(echo {} | sed s/.png/.webp/)"
'
```
### PNG to AVIF (Smaller, Slower Encode)
```bash
# Requires avifenc (brew install libavif)
avifenc --min 20 --max 30 -s 6 frame_0001.png frame_0001.avif
# Batch
for f in frames/*.png; do
avifenc --min 20 --max 30 -s 6 "$f" "${f%.png}.avif"
done
```
### Format Comparison
| Format | Quality at q80 | Size vs PNG | Encode Speed | Browser Support |
|--------|----------------|-------------|--------------|-----------------|
| WebP | Excellent | -85-90% | Fast | All modern [E1] |
| AVIF | Excellent | -90-95% | Slow (10x) | Chrome, Firefox, Safari 16+ [E1] |
| JPEG | Good | -80-85% | Fastest | Universal [E1] |
| PNG | Lossless | Baseline | Fast | Universal [E1] |
**Decision:** WebP for production (best balance). AVIF if encode time is not an issue and you need minimum size.
---
## 3. Size Budgets [E2]
### Per-Frame Targets
| Resolution | WebP q80 | AVIF q30 | Target per frame |
|------------|----------|----------|------------------|
| 960x540 (mobile) | 12-18 KB | 8-12 KB | <15 KB |
| 1280x720 (tablet) | 18-28 KB | 12-20 KB | <25 KB |
| 1920x1080 (desktop) | 25-45 KB | 18-30 KB | <35 KB |
### Total Budget
| Frames | Desktop total | Mobile total | Acceptable? |
|--------|---------------|--------------|-------------|
| 60 | ~1.5 MB | ~0.7 MB | Great |
| 120 | ~3.0 MB | ~1.4 MB | Good |
| 180 | ~4.5 MB | ~2.1 MB | Acceptable |
| 300 | ~7.5 MB | ~3.5 MB | Heavy, needs lazy load |
**Hard limit:** <5MB total for initial load. Lazy load anything beyond.
---
## 4. Responsive Frame Sets [E2]
### Directory Structure
```
/public/frames/
/desktop/ # 1920x1080, 150 frames
/tablet/ # 1280x720, 120 frames
/mobile/ # 960x540, 75 frames
```
### FFmpeg Multi-Resolution Script
```bash
#!/bin/bash
SOURCE="source.mp4"
# Desktop: 1920x1080, 30fps
mkdir -p frames/desktop
ffmpeg -i "$SOURCE" -vf "fps=30,scale=1920:1080" frames/desktop/frame_%04d.png
# Tablet: 1280x720, 24fps
mkdir -p frames/tablet
ffmpeg -i "$SOURCE" -vf "fps=24,scale=1280:720" frames/tablet/frame_%04d.png
# Mobile: 960x540, 15fps
mkdir -p frames/mobile
ffmpeg -i "$SOURCE" -vf "fps=15,scale=960:540" frames/mobile/frame_%04d.png
# Convert all to WebP
for dir in frames/desktop frames/tablet frames/mobile; do
for f in "$dir"/*.png; do
cwebp -q 80 "$f" -o "${f%.png}.webp"
rm "$f" # remove PNG after conversion
done
done
```
### Responsive Loading (JS)
```js
function getBreakpoint() {
const w = window.innerWidth;
if (w >= 1280) return { dir: "desktop", count: 150 };
if (w >= 768) return { dir: "tablet", count: 120 };
return { dir: "mobile", count: 75 };
}
const { dir, count } = getBreakpoint();
const basePath = `/frames/${dir}/frame_`;
```
---
## 5. Sprite Sheet (Alternative) [E2]
For fewer frames (<60), a single sprite sheet can be faster than individual files:
```bash
# Create sprite sheet with ImageMagick
montage frames/frame_*.webp -tile 10x6 -geometry 480x270+0+0 spritesheet.webp
# 60 frames, 10 columns x 6 rows, each 480x270
```
### CSS Sprite Animation
```css
.sprite-player {
width: 480px;
height: 270px;
background: url("spritesheet.webp");
background-size: 4800px 1620px; /* 10 cols x 6 rows */
}
```
### JS Sprite + Scroll
```js
const sprite = document.querySelector(".sprite-player");
const cols = 10, rows = 6, total = 60;
const frameW = 480, frameH = 270;
gsap.to({ frame: 0 }, {
frame: total - 1,
snap: "frame",
ease: "none",
scrollTrigger: {
trigger: "#sprite-section",
start: "top top",
end: "+=2000",
pin: true,
scrub: 0.5,
},
onUpdate: function() {
const i = Math.round(this.targets()[0].frame);
const col = i % cols;
const row = Math.floor(i / cols);
sprite.style.backgroundPosition = `-${col * frameW}px -${row * frameH}px`;
}
});
```
---
## 6. Video Scrub (Alternative to Frame Sequence) [E2]
Apple's modern approach: single compressed video + scroll-driven playback.
### Optimize Video for Scrub
```bash
# Encode for web scrub: low bitrate, many keyframes
ffmpeg -i source.mp4 \
-c:v libx264 \
-preset slow \
-crf 23 \
-g 1 \ # keyframe every frame (critical for scrub)
-an \ # no audio
-movflags +faststart \
-vf "scale=1920:1080" \
output-scrub.mp4
```
**Key:** `-g 1` makes every frame a keyframe, enabling instant seeking. File will be larger than normal video but smaller than frame sequence.
### Playback
```js
const video = document.getElementById("scrub-video");
// Ensure video is loaded
video.preload = "auto";
gsap.to(video, {
currentTime: video.duration || 5, // fallback if metadata not loaded
ease: "none",
scrollTrigger: {
trigger: "#video-section",
start: "top top",
end: "+=4000",
pin: true,
scrub: true,
}
});
// Alternative: manual scroll control
video.addEventListener("loadedmetadata", () => {
const section = document.getElementById("video-section");
window.addEventListener("scroll", () => {
const rect = section.getBoundingClientRect();
const progress = Math.max(0, Math.min(1,
-rect.top / (rect.height - window.innerHeight)
));
video.currentTime = progress * video.duration;
});
});
```
### Frame Sequence vs Video Scrub
| Factor | Frame Sequence | Video Scrub |
|--------|---------------|-------------|
| Smoothness | Best (instant) | Good (may drop on mobile) |
| File size | 2-5 MB (150 frames) | 1-3 MB (one file) |
| HTTP requests | 150 requests | 1 request |
| Memory usage | High (all frames in RAM) | Low (decoded on demand) |
| Mobile perf | Good | Variable |
| Complexity | More code | Simpler |
**Decision:** Frame sequence for hero sections where smoothness is critical. Video scrub for secondary content or when bandwidth is limited.
---
## 7. Preloading Strategy [E1]
### Priority Loading
```js
async function loadFrames(basePath, count) {
const frames = [];
// Phase 1: Load first 10 frames immediately (show something fast)
const priority = Array.from({ length: 10 }, (_, i) =>
loadImage(`${basePath}${String(i + 1).padStart(4, "0")}.webp`)
);
const first10 = await Promise.all(priority);
frames.push(...first10);
// Phase 2: Load remaining frames in background
for (let i = 10; i < count; i++) {
const img = new Image();
img.src = `${basePath}${String(i + 1).padStart(4, "0")}.webp`;
frames.push(img);
}
return frames;
}
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
```
### IntersectionObserver Trigger
```js
// Only start loading when section is near viewport
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
loadFrames("/frames/desktop/frame_", 150);
observer.disconnect();
}
},
{ rootMargin: "200px" } // start 200px before visible
);
observer.observe(document.getElementById("sequence-section"));
```
---
## Workflow
1. **Source video** get MP4/MOV at highest quality available
2. **Plan frame count** duration * fps, aim for 120-180 sweet spot
3. **Extract frames** FFmpeg with target resolution and fps
4. **Convert to WebP** cwebp q80, check total size budget
5. **Create responsive sets** desktop/tablet/mobile with different counts
6. **Implement playback** Canvas + GSAP ScrollTrigger (or video scrub)
7. **Add preloading** priority first 10, lazy rest, IntersectionObserver
8. **Test on mobile** check memory usage, reduce frames if needed
9. **Add fallback** static image for prefers-reduced-motion