New: Earn 20% as an AgentAlley affiliate. Share your link and earn on every sale you refer. Join now New: Earn 20% as an affiliate. Join now

Takes an existing screen recording or demo video

Video Polish Skill

by gooseworks-ai · MIT license · ★ 1,220 on GitHub · updated Sep 21

Video Polish Skill cover

Takes an existing screen recording or demo video and adds professional zoom/pan effects synchronized to the narration. Uses transcript-driven zoom targeting and Remotion for rendering.

---
name: video-polish
description: Takes an existing screen recording or demo video and adds professional zoom/pan effects synchronized to the narration. Uses transcript-driven zoom targeting and Remotion for rendering. Optionally replaces audio with a soundtrack.
user-invocable: true
allowed-tools: Bash, Read, Write, Edit, Grep, Glob, WebSearch
argument-hint: [video-file-path]
---

# Video Polish Skill

You take an existing video (screen recording, demo, walkthrough, Loom) and add professional zoom/pan effects that follow the narration. The output looks like a professionally edited video where the camera zooms into whatever the speaker is discussing.

---

## What This Skill Does

**Input:** A raw video file (screen recording, Loom, product demo) + optionally a soundtrack
**Output:** The same video with smooth zoom/pan effects synchronized to the narration

Overview

You take an existing video (screen recording, demo, walkthrough, Loom) and add professional zoom/pan effects that follow the narration. The output looks like a professionally edited video where the camera zooms into whatever the speaker is discussing.

What you get

  • Zoom-in effects on UI elements, metrics, text, code when the narrator mentions t
  • Smooth pan/slide effects across sections (e.g., sliding across table columns)
  • Transitions with ease-in-out easing (no jarring jumps)
  • Optional audio replacement (background music instead of or mixed with original n
Manual install (npx, ZIP)
Claude Codenpx skills add gooseworks-ai/goose-skills --skill video-polish -g -a claude-code
Claude Code (manual)mkdir -p ~/.claude/skills && curl -sL https://codeload.github.com/gooseworks-ai/goose-skills/tar.gz/615c685f34f8 \ | tar -xz -C ~/.claude/skills --strip-components=5 goose-skills-615c685f34f8/skills/design/packs/video-production/video-polish
Windows (PowerShell)New-Item -ItemType Directory -Force "$HOME\.claude\skills" | Out-Null; Invoke-WebRequest "https://m.agentalley.io/zip/5c44a239f0bcc025945e/video-polish.zip" -OutFile "$env:TEMP\video-polish.zip"; Expand-Archive "$env:TEMP\video-polish.zip" -DestinationPath "$HOME\.claude\skills" -Force
Claude.ai / desktopDownload the ZIP and upload it under Settings → Capabilities → Skills.
Codex, Cursor, other agentsPoint them at SKILL.md. The other files are plain text.

Connects with Agent Alley MCP · agentalley.io/mcp

Files included

  • SKILL.md13.8 KB
  • skill.meta.json286 B
Read the files here
gooseworks-ai/615c6852 files
SKILL.md
Show the full text365 lines

Video Polish Skill

You take an existing video (screen recording, demo, walkthrough, Loom) and add professional zoom/pan effects that follow the narration. The output looks like a professionally edited video where the camera zooms into whatever the speaker is discussing.


What This Skill Does

Input: A raw video file (screen recording, Loom, product demo) + optionally a soundtrack Output: The same video with smooth zoom/pan effects synchronized to the narration

What it adds:

  • Zoom-in effects on UI elements, metrics, text, code when the narrator mentions them
  • Smooth pan/slide effects across sections (e.g., sliding across table columns)
  • Transitions with ease-in-out easing (no jarring jumps)
  • Optional audio replacement (background music instead of or mixed with original narration)

What it does NOT do:

  • Generate new video content
  • Add avatars or talking heads
  • Edit or cut the video (no trimming, no removing sections)
  • Add text overlays or annotations

Prerequisites

  • Node.js (v18+) and npm — required for Remotion
  • Remotion — Video rendering framework. If not already set up, create a project:
    npx create-video@latest --yes --blank --no-tailwind video-polish
    cd video-polish && npm i
    
  • whisper-cpp — For audio transcription. Install via brew install whisper-cpp on macOS
  • Whisper model — Download the base English model:
    curl -L -o /tmp/ggml-base.en.bin "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin"
    
  • Python 3 + Pillow — For frame extraction and coordinate grid overlays (pip install Pillow)

Before starting: Verify that Node.js, whisper-cpp, and Python 3 with Pillow are installed. If any are missing, instruct the user to install them before proceeding.


How This Skill Works

Step 1: Analyze the Source Video

Get video metadata:

npx remotion ffprobe -v quiet -print_format json -show_format -show_streams <video_path>

Record: duration, resolution (width x height), fps, whether audio exists.

Step 2: Transcribe the Audio

Extract audio and transcribe with word-level timestamps.

Extract audio:

npx remotion ffmpeg -y -i <video_path> -vn -acodec pcm_s16le -ar 16000 -ac 1 /tmp/audio.wav

Transcribe:

whisper-cli -m /tmp/ggml-base.en.bin -f /tmp/audio.wav --output-json --output-file /tmp/transcript

Read the transcript and identify moments where the narrator emphasizes or references specific on-screen elements:

  • "look at this", "you can see", "notice", "here", "this is where"
  • Naming specific UI elements: "the latency column", "pass rate", "this button"
  • Describing what's on screen: "each row represents...", "on the top you can see..."

For each emphasis moment, record:

  • Timestamp (when they start talking about it)
  • What they're referring to (which UI element, metric, section)
Step 3: Extract Source Frames at Key Timestamps

For each emphasis moment from the transcript, extract the source frame at that timestamp:

npx remotion ffmpeg -y -i <video_path> -vf "select=eq(n\,<frame_number>)" -vframes 1 -update 1 /tmp/frame-<timestamp>.png

Where frame_number = timestamp_seconds * fps.

Important: The video content may scroll or change over time. Always extract frames at the ACTUAL timestamp, not from a single reference frame. UI element positions change as the user scrolls.

Step 4: Measure Zoom Target Coordinates

For each extracted frame, draw a coordinate grid overlay to precisely identify element positions:

from PIL import Image, ImageDraw
img = Image.open('/tmp/frame-<timestamp>.png')
w, h = img.size
draw = ImageDraw.Draw(img)
for pct in range(0, 100, 5):
    y = int(h * pct / 100)
    x = int(w * pct / 100)
    color = 'red' if pct % 10 == 0 else 'yellow'
    draw.line([(0, y), (w, y)], fill=color, width=1)
    draw.text((5, y+2), f'y{pct}', fill=color)
    draw.line([(x, 0), (x, h)], fill=color, width=1)
    draw.text((x+2, 12), f'x{pct}', fill=color)
img.save('/tmp/frame-<timestamp>-grid.png')

Look at the grid overlay and measure the center point (focusX, focusY) of the element the narrator is referring to. Express as normalized 0-1 values (e.g., x=0.47 means 47% from the left edge).

Step 5: Build the Keyframe Timeline

Create a list of keyframes. Each keyframe defines a target zoom state at a specific time. The system smoothly interpolates between consecutive keyframes using ease-in-out.

Keyframe design rules:

  1. Anticipate the narration by 0.5 seconds. If the narrator says "pass rate" at 0:37, start zooming at 0:36.5 so the zoom arrives just as they say it.

  2. Between two zoom-in targets, don't zoom all the way out. If going from metric A to metric B, reduce zoom to 1.5x briefly while shifting focus, then zoom back in. This is faster and smoother than full-out-then-full-in.

  3. For sliding across adjacent elements (e.g., table columns), keep the same zoom level and just change focusX. This creates a smooth horizontal pan.

  4. Fast transitions between distant targets (e.g., jumping from the query column to the latency column) should take 1-1.5 seconds max. Slow slides across distant areas feel boring.

  5. Slow slides across adjacent elements (e.g., panning from latency → tokens → cost) should take 3-5 seconds. This lets the viewer read each element.

  6. Hold the zoom for at least 2-3 seconds after arriving at a target. Quick zoom-in → immediate zoom-out is disorienting.

  7. Start and end the video at full view (zoom=1.0). Don't start zoomed in — let the viewer orient first.

Zoom level guide:

What you're showing Zoom level
Full dashboard/page overview 1.0 (no zoom)
A section (metrics row + charts) 1.1-1.3
A specific area (one chart, a few table columns) 1.8-2.5
A single metric, cell, or button 2.8-3.5

Example keyframe timeline:

const KEYFRAMES = [
  { timeSec: 0, zoom: 1.0, focusX: 0.5, focusY: 0.5 },      // Full view
  { timeSec: 23, zoom: 1.0, focusX: 0.5, focusY: 0.5 },      // Still full, about to zoom
  { timeSec: 25, zoom: 1.2, focusX: 0.45, focusY: 0.20 },    // Gentle zoom on metrics area
  { timeSec: 29, zoom: 1.2, focusX: 0.45, focusY: 0.20 },    // Hold
  { timeSec: 30.5, zoom: 3.0, focusX: 0.56, focusY: 0.15 },  // Zoom on specific metric
  { timeSec: 34, zoom: 3.0, focusX: 0.56, focusY: 0.15 },    // Hold
  { timeSec: 36, zoom: 3.0, focusX: 0.06, focusY: 0.15 },    // Slide to different metric
  // ... etc
];
Step 6: Verify with Still Frames (CRITICAL)

Before doing a full render, verify every zoom target with still frames. This is the most important step — it catches coordinate errors that would waste a full render cycle.

For each keyframe where the zoom or focus changes, render a single still frame:

npx remotion still <CompositionId> --frame=<frame_number> --output=/tmp/verify-<timestamp>.png

Self-review each still frame:

  1. Look at the rendered frame
  2. Ask: "The narrator says [X] at this moment. Is [X] visible and prominent in this frame?"
  3. If the wrong element is centered, adjust the focusX/focusY coordinates
  4. Re-render the still frame and check again
  5. Only proceed to full render when ALL still frames show the correct targets

Common coordinate mistakes to check for:

  • Zooming into the wrong column (neighboring columns look similar)
  • focusY landing on chart labels instead of the actual data
  • Coordinates measured from one timestamp applied to a different timestamp where the page has scrolled
  • Transform origin clipping — at high zoom levels, the focus point might be too close to an edge, causing black bars
Step 7: Build the Remotion Composition

Create the Remotion project files. The composition structure:

Root.tsx:

import { Composition } from "remotion";
import { MyComposition } from "./Composition";

export const RemotionRoot: React.FC = () => {
  return (
    <Composition
      id="VideoPolish"
      component={MyComposition}
      durationInFrames={DURATION_SECONDS * FPS}
      fps={FPS}
      width={VIDEO_WIDTH}
      height={VIDEO_HEIGHT}
    />
  );
};

Composition.tsx:

import {
  AbsoluteFill, Audio, OffthreadVideo, staticFile,
  useCurrentFrame, useVideoConfig,
} from "remotion";

type Keyframe = {
  timeSec: number;
  zoom: number;
  focusX: number;
  focusY: number;
};

const KEYFRAMES: Keyframe[] = [
  // ... keyframes from Step 5
];

// Smooth ease-in-out interpolation
function smoothstep(t: number): number {
  const c = Math.max(0, Math.min(1, t));
  return c * c * (3 - 2 * c);
}

function getStateAtTime(timeSec: number) {
  if (timeSec <= KEYFRAMES[0].timeSec) return KEYFRAMES[0];
  if (timeSec >= KEYFRAMES[KEYFRAMES.length - 1].timeSec)
    return KEYFRAMES[KEYFRAMES.length - 1];

  for (let i = 0; i < KEYFRAMES.length - 1; i++) {
    const kf0 = KEYFRAMES[i];
    const kf1 = KEYFRAMES[i + 1];
    if (timeSec >= kf0.timeSec && timeSec <= kf1.timeSec) {
      const t = (timeSec - kf0.timeSec) / (kf1.timeSec - kf0.timeSec);
      const e = smoothstep(t);
      return {
        zoom: kf0.zoom + (kf1.zoom - kf0.zoom) * e,
        focusX: kf0.focusX + (kf1.focusX - kf0.focusX) * e,
        focusY: kf0.focusY + (kf1.focusY - kf0.focusY) * e,
      };
    }
  }
  return KEYFRAMES[KEYFRAMES.length - 1];
}

export const MyComposition: React.FC = () => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();
  const { zoom, focusX, focusY } = getStateAtTime(frame / fps);

  return (
    <AbsoluteFill style={{ backgroundColor: "black" }}>
      <AbsoluteFill
        style={{
          transform: `scale(${zoom})`,
          transformOrigin: `${focusX * 100}% ${focusY * 100}%`,
        }}
      >
        <OffthreadVideo
          src={staticFile("source.mp4")}
          style={{ width: "100%", height: "100%", objectFit: "cover" }}
        />
      </AbsoluteFill>
      {/* Optional: background music */}
      <Audio
        src={staticFile("music.mp3")}
        volume={(f) => {
          const total = DURATION * fps;
          if (f < fps) return f / fps;           // 1s fade in
          if (f > total - 3 * fps)
            return (total - f) / (3 * fps);      // 3s fade out
          return 1;
        }}
      />
    </AbsoluteFill>
  );
};

Copy the source video (and optional music) into the Remotion project's public/ folder:

cp <source_video> <remotion_project>/public/source.mp4
cp <music_file> <remotion_project>/public/music.mp3  # optional
Step 8: Render
npx remotion render <CompositionId> --output=<output_path>.mp4

Rendering takes approximately 1-3 minutes for a 60-90 second video at 720p/1080p.

Step 9: Present the Output
Video polished!
- Duration: [X] seconds
- Resolution: [W]x[H]
- Zoom effects: [N] zoom points
- Audio: [original / replaced with soundtrack / mixed]
- File: [local path]

Want me to adjust any zoom points and re-render?

Supported Inputs

Input Required Formats Notes
Source video Yes MP4, MOV, WebM Screen recording, Loom, product demo
Soundtrack No MP3, WAV, AAC Replaces or mixes with original audio
Zoom instructions No Natural language "Zoom in when he talks about metrics." If not provided, the skill auto-detects from transcript.
Specific timestamps No "Zoom at 0:15, 0:42" Overrides auto-detection for specific moments

Audio Options

Option What happens
Keep original (default if no soundtrack provided) Original narration plays, zoom effects are visual only
Replace with soundtrack Original audio removed, soundtrack plays with fade-in/fade-out
Mix Soundtrack plays at ~20% volume underneath original narration

Output Specifications

Property Value
Format MP4 (H.264)
Resolution Same as source video
Frame rate Same as source video
Easing Smoothstep (cubic ease-in-out) on all transitions
Render engine Remotion (CSS transform-based, sub-pixel precision)

Limitations and Gotchas

  1. Coordinates change when the page scrolls. Always extract the source frame at the EXACT timestamp you're setting a keyframe for. Don't reuse coordinates from a different timestamp.

  2. Still frame verification is mandatory. Never do a full render without verifying zoom targets via still frames first. A full render takes 1-3 minutes — a still frame takes 2 seconds. Always verify.

  3. High zoom levels (3x+) on low-res source video will look pixelated. The skill is scaling up the video, not enhancing resolution. For 720p source, 2.5x is about the max before it looks bad. For 1080p source, 3.5x is the limit.

  4. No dynamic zoom tracking. The zoom targets are set based on static frame analysis. If the UI is animating (dropdown opening, modal appearing), the zoom point is based on where the element is at the keyframe timestamp.

  5. Remotion project setup required. The first run requires creating a Remotion project (npx create-video). Subsequent runs reuse the same project — just swap the source video and update keyframes.

  6. Whisper transcription quality depends on audio clarity. Background noise, multiple speakers, or heavy accents may produce inaccurate timestamps. Always verify transcript timestamps against the actual video.

  7. Transform origin at edges. At high zoom with focusX near 0 or 1, part of the zoomed view may show black bars (beyond the video edge). Keep focusX between 0.05-0.95 at zoom levels above 2.5x.

1---
2name: video-polish
3description: Takes an existing screen recording or demo video and adds professional zoom/pan effects synchronized to the narration. Uses transcript-driven zoom targeting and Remotion for rendering. Optionally replaces audio with a soundtrack.
4user-invocable: true
5allowed-tools: Bash, Read, Write, Edit, Grep, Glob, WebSearch
6argument-hint: [video-file-path]
7---
8 
9# Video Polish Skill
10 
11You take an existing video (screen recording, demo, walkthrough, Loom) and add professional zoom/pan effects that follow the narration. The output looks like a professionally edited video where the camera zooms into whatever the speaker is discussing.
12 
13---
14 
15## What This Skill Does
16 
17**Input:** A raw video file (screen recording, Loom, product demo) + optionally a soundtrack
18**Output:** The same video with smooth zoom/pan effects synchronized to the narration
19 
20**What it adds:**
21- Zoom-in effects on UI elements, metrics, text, code when the narrator mentions them
22- Smooth pan/slide effects across sections (e.g., sliding across table columns)
23- Transitions with ease-in-out easing (no jarring jumps)
24- Optional audio replacement (background music instead of or mixed with original narration)
25 
26**What it does NOT do:**
27- Generate new video content
28- Add avatars or talking heads
29- Edit or cut the video (no trimming, no removing sections)
30- Add text overlays or annotations
31 
32---
33 
34## Prerequisites
35 
36- **Node.js** (v18+) and **npm** — required for Remotion
37- **Remotion** — Video rendering framework. If not already set up, create a project:
38 ```bash
39 npx create-video@latest --yes --blank --no-tailwind video-polish
40 cd video-polish && npm i
41 ```
42- **whisper-cpp** — For audio transcription. Install via `brew install whisper-cpp` on macOS
43- **Whisper model** — Download the base English model:
44 ```bash
45 curl -L -o /tmp/ggml-base.en.bin "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin"
46 ```
47- **Python 3 + Pillow** — For frame extraction and coordinate grid overlays (`pip install Pillow`)
48 
49**Before starting:** Verify that Node.js, whisper-cpp, and Python 3 with Pillow are installed. If any are missing, instruct the user to install them before proceeding.
50 
51---
52 
53## How This Skill Works
54 
55### Step 1: Analyze the Source Video
56 
57Get video metadata:
58```bash
59npx remotion ffprobe -v quiet -print_format json -show_format -show_streams <video_path>
60```
61 
62Record: duration, resolution (width x height), fps, whether audio exists.
63 
64### Step 2: Transcribe the Audio
65 
66Extract audio and transcribe with word-level timestamps.
67 
68**Extract audio:**
69```bash
70npx remotion ffmpeg -y -i <video_path> -vn -acodec pcm_s16le -ar 16000 -ac 1 /tmp/audio.wav
71```
72 
73**Transcribe:**
74```bash
75whisper-cli -m /tmp/ggml-base.en.bin -f /tmp/audio.wav --output-json --output-file /tmp/transcript
76```
77 
78**Read the transcript** and identify moments where the narrator emphasizes or references specific on-screen elements:
79- "look at this", "you can see", "notice", "here", "this is where"
80- Naming specific UI elements: "the latency column", "pass rate", "this button"
81- Describing what's on screen: "each row represents...", "on the top you can see..."
82 
83For each emphasis moment, record:
84- Timestamp (when they start talking about it)
85- What they're referring to (which UI element, metric, section)
86 
87### Step 3: Extract Source Frames at Key Timestamps
88 
89For each emphasis moment from the transcript, extract the source frame at that timestamp:
90 
91```bash
92npx remotion ffmpeg -y -i <video_path> -vf "select=eq(n\,<frame_number>)" -vframes 1 -update 1 /tmp/frame-<timestamp>.png
93```
94 
95Where `frame_number = timestamp_seconds * fps`.
96 
97**Important:** The video content may scroll or change over time. Always extract frames at the ACTUAL timestamp, not from a single reference frame. UI element positions change as the user scrolls.
98 
99### Step 4: Measure Zoom Target Coordinates
100 
101For each extracted frame, draw a coordinate grid overlay to precisely identify element positions:
102 
103```python
104from PIL import Image, ImageDraw
105img = Image.open('/tmp/frame-<timestamp>.png')
106w, h = img.size
107draw = ImageDraw.Draw(img)
108for pct in range(0, 100, 5):
109 y = int(h * pct / 100)
110 x = int(w * pct / 100)
111 color = 'red' if pct % 10 == 0 else 'yellow'
112 draw.line([(0, y), (w, y)], fill=color, width=1)
113 draw.text((5, y+2), f'y{pct}', fill=color)
114 draw.line([(x, 0), (x, h)], fill=color, width=1)
115 draw.text((x+2, 12), f'x{pct}', fill=color)
116img.save('/tmp/frame-<timestamp>-grid.png')
117```
118 
119Look at the grid overlay and measure the **center point** (focusX, focusY) of the element the narrator is referring to. Express as normalized 0-1 values (e.g., x=0.47 means 47% from the left edge).
120 
121### Step 5: Build the Keyframe Timeline
122 
123Create a list of keyframes. Each keyframe defines a target zoom state at a specific time. The system smoothly interpolates between consecutive keyframes using ease-in-out.
124 
125**Keyframe design rules:**
126 
1271. **Anticipate the narration by 0.5 seconds.** If the narrator says "pass rate" at 0:37, start zooming at 0:36.5 so the zoom arrives just as they say it.
128 
1292. **Between two zoom-in targets, don't zoom all the way out.** If going from metric A to metric B, reduce zoom to 1.5x briefly while shifting focus, then zoom back in. This is faster and smoother than full-out-then-full-in.
130 
1313. **For sliding across adjacent elements** (e.g., table columns), keep the same zoom level and just change focusX. This creates a smooth horizontal pan.
132 
1334. **Fast transitions between distant targets** (e.g., jumping from the query column to the latency column) should take 1-1.5 seconds max. Slow slides across distant areas feel boring.
134 
1355. **Slow slides across adjacent elements** (e.g., panning from latency → tokens → cost) should take 3-5 seconds. This lets the viewer read each element.
136 
1376. **Hold the zoom for at least 2-3 seconds** after arriving at a target. Quick zoom-in → immediate zoom-out is disorienting.
138 
1397. **Start and end the video at full view (zoom=1.0).** Don't start zoomed in — let the viewer orient first.
140 
141**Zoom level guide:**
142| What you're showing | Zoom level |
143|---|---|
144| Full dashboard/page overview | 1.0 (no zoom) |
145| A section (metrics row + charts) | 1.1-1.3 |
146| A specific area (one chart, a few table columns) | 1.8-2.5 |
147| A single metric, cell, or button | 2.8-3.5 |
148 
149**Example keyframe timeline:**
150```typescript
151const KEYFRAMES = [
152 { timeSec: 0, zoom: 1.0, focusX: 0.5, focusY: 0.5 }, // Full view
153 { timeSec: 23, zoom: 1.0, focusX: 0.5, focusY: 0.5 }, // Still full, about to zoom
154 { timeSec: 25, zoom: 1.2, focusX: 0.45, focusY: 0.20 }, // Gentle zoom on metrics area
155 { timeSec: 29, zoom: 1.2, focusX: 0.45, focusY: 0.20 }, // Hold
156 { timeSec: 30.5, zoom: 3.0, focusX: 0.56, focusY: 0.15 }, // Zoom on specific metric
157 { timeSec: 34, zoom: 3.0, focusX: 0.56, focusY: 0.15 }, // Hold
158 { timeSec: 36, zoom: 3.0, focusX: 0.06, focusY: 0.15 }, // Slide to different metric
159 // ... etc
160];
161```
162 
163### Step 6: Verify with Still Frames (CRITICAL)
164 
165**Before doing a full render, verify every zoom target with still frames.** This is the most important step — it catches coordinate errors that would waste a full render cycle.
166 
167For each keyframe where the zoom or focus changes, render a single still frame:
168 
169```bash
170npx remotion still <CompositionId> --frame=<frame_number> --output=/tmp/verify-<timestamp>.png
171```
172 
173**Self-review each still frame:**
1741. Look at the rendered frame
1752. Ask: "The narrator says [X] at this moment. Is [X] visible and prominent in this frame?"
1763. If the wrong element is centered, adjust the focusX/focusY coordinates
1774. Re-render the still frame and check again
1785. Only proceed to full render when ALL still frames show the correct targets
179 
180**Common coordinate mistakes to check for:**
181- Zooming into the wrong column (neighboring columns look similar)
182- focusY landing on chart labels instead of the actual data
183- Coordinates measured from one timestamp applied to a different timestamp where the page has scrolled
184- Transform origin clipping — at high zoom levels, the focus point might be too close to an edge, causing black bars
185 
186### Step 7: Build the Remotion Composition
187 
188Create the Remotion project files. The composition structure:
189 
190**Root.tsx:**
191```tsx
192import { Composition } from "remotion";
193import { MyComposition } from "./Composition";
194 
195export const RemotionRoot: React.FC = () => {
196 return (
197 <Composition
198 id="VideoPolish"
199 component={MyComposition}
200 durationInFrames={DURATION_SECONDS * FPS}
201 fps={FPS}
202 width={VIDEO_WIDTH}
203 height={VIDEO_HEIGHT}
204 />
205 );
206};
207```
208 
209**Composition.tsx:**
210```tsx
211import {
212 AbsoluteFill, Audio, OffthreadVideo, staticFile,
213 useCurrentFrame, useVideoConfig,
214} from "remotion";
215 
216type Keyframe = {
217 timeSec: number;
218 zoom: number;
219 focusX: number;
220 focusY: number;
221};
222 
223const KEYFRAMES: Keyframe[] = [
224 // ... keyframes from Step 5
225];
226 
227// Smooth ease-in-out interpolation
228function smoothstep(t: number): number {
229 const c = Math.max(0, Math.min(1, t));
230 return c * c * (3 - 2 * c);
231}
232 
233function getStateAtTime(timeSec: number) {
234 if (timeSec <= KEYFRAMES[0].timeSec) return KEYFRAMES[0];
235 if (timeSec >= KEYFRAMES[KEYFRAMES.length - 1].timeSec)
236 return KEYFRAMES[KEYFRAMES.length - 1];
237 
238 for (let i = 0; i < KEYFRAMES.length - 1; i++) {
239 const kf0 = KEYFRAMES[i];
240 const kf1 = KEYFRAMES[i + 1];
241 if (timeSec >= kf0.timeSec && timeSec <= kf1.timeSec) {
242 const t = (timeSec - kf0.timeSec) / (kf1.timeSec - kf0.timeSec);
243 const e = smoothstep(t);
244 return {
245 zoom: kf0.zoom + (kf1.zoom - kf0.zoom) * e,
246 focusX: kf0.focusX + (kf1.focusX - kf0.focusX) * e,
247 focusY: kf0.focusY + (kf1.focusY - kf0.focusY) * e,
248 };
249 }
250 }
251 return KEYFRAMES[KEYFRAMES.length - 1];
252}
253 
254export const MyComposition: React.FC = () => {
255 const frame = useCurrentFrame();
256 const { fps } = useVideoConfig();
257 const { zoom, focusX, focusY } = getStateAtTime(frame / fps);
258 
259 return (
260 <AbsoluteFill style={{ backgroundColor: "black" }}>
261 <AbsoluteFill
262 style={{
263 transform: `scale(${zoom})`,
264 transformOrigin: `${focusX * 100}% ${focusY * 100}%`,
265 }}
266 >
267 <OffthreadVideo
268 src={staticFile("source.mp4")}
269 style={{ width: "100%", height: "100%", objectFit: "cover" }}
270 />
271 </AbsoluteFill>
272 {/* Optional: background music */}
273 <Audio
274 src={staticFile("music.mp3")}
275 volume={(f) => {
276 const total = DURATION * fps;
277 if (f < fps) return f / fps; // 1s fade in
278 if (f > total - 3 * fps)
279 return (total - f) / (3 * fps); // 3s fade out
280 return 1;
281 }}
282 />
283 </AbsoluteFill>
284 );
285};
286```
287 
288Copy the source video (and optional music) into the Remotion project's `public/` folder:
289```bash
290cp <source_video> <remotion_project>/public/source.mp4
291cp <music_file> <remotion_project>/public/music.mp3 # optional
292```
293 
294### Step 8: Render
295 
296```bash
297npx remotion render <CompositionId> --output=<output_path>.mp4
298```
299 
300Rendering takes approximately 1-3 minutes for a 60-90 second video at 720p/1080p.
301 
302### Step 9: Present the Output
303 
304```
305Video polished!
306- Duration: [X] seconds
307- Resolution: [W]x[H]
308- Zoom effects: [N] zoom points
309- Audio: [original / replaced with soundtrack / mixed]
310- File: [local path]
311 
312Want me to adjust any zoom points and re-render?
313```
314 
315---
316 
317## Supported Inputs
318 
319| Input | Required | Formats | Notes |
320|---|---|---|---|
321| **Source video** | Yes | MP4, MOV, WebM | Screen recording, Loom, product demo |
322| **Soundtrack** | No | MP3, WAV, AAC | Replaces or mixes with original audio |
323| **Zoom instructions** | No | Natural language | "Zoom in when he talks about metrics." If not provided, the skill auto-detects from transcript. |
324| **Specific timestamps** | No | "Zoom at 0:15, 0:42" | Overrides auto-detection for specific moments |
325 
326---
327 
328## Audio Options
329 
330| Option | What happens |
331|---|---|
332| **Keep original** (default if no soundtrack provided) | Original narration plays, zoom effects are visual only |
333| **Replace with soundtrack** | Original audio removed, soundtrack plays with fade-in/fade-out |
334| **Mix** | Soundtrack plays at ~20% volume underneath original narration |
335 
336---
337 
338## Output Specifications
339 
340| Property | Value |
341|---|---|
342| **Format** | MP4 (H.264) |
343| **Resolution** | Same as source video |
344| **Frame rate** | Same as source video |
345| **Easing** | Smoothstep (cubic ease-in-out) on all transitions |
346| **Render engine** | Remotion (CSS transform-based, sub-pixel precision) |
347 
348---
349 
350## Limitations and Gotchas
351 
3521. **Coordinates change when the page scrolls.** Always extract the source frame at the EXACT timestamp you're setting a keyframe for. Don't reuse coordinates from a different timestamp.
353 
3542. **Still frame verification is mandatory.** Never do a full render without verifying zoom targets via still frames first. A full render takes 1-3 minutes — a still frame takes 2 seconds. Always verify.
355 
3563. **High zoom levels (3x+) on low-res source video** will look pixelated. The skill is scaling up the video, not enhancing resolution. For 720p source, 2.5x is about the max before it looks bad. For 1080p source, 3.5x is the limit.
357 
3584. **No dynamic zoom tracking.** The zoom targets are set based on static frame analysis. If the UI is animating (dropdown opening, modal appearing), the zoom point is based on where the element is at the keyframe timestamp.
359 
3605. **Remotion project setup required.** The first run requires creating a Remotion project (`npx create-video`). Subsequent runs reuse the same project — just swap the source video and update keyframes.
361 
3626. **Whisper transcription quality** depends on audio clarity. Background noise, multiple speakers, or heavy accents may produce inaccurate timestamps. Always verify transcript timestamps against the actual video.
363 
3647. **Transform origin at edges.** At high zoom with focusX near 0 or 1, part of the zoomed view may show black bars (beyond the video edge). Keep focusX between 0.05-0.95 at zoom levels above 2.5x.
365 
View all files on GitHub →

Details

Updated
Sep 21, 2026
License
MIT
Stars on the repo
★ 1,220
Checked
Source
gooseworks-ai/goose-skills ↗
Creator
gooseworks-ai · 206 listings
View creator

Made with video-polish

Video Polish Skill — CleanShot 2026-07-13 at 20 16 54@2x (from the gooseworks-ai/goose-skills README)
Video Polish Skill — CleanShot 2026-07-13 at 20 16 54@2x (from the gooseworks-ai/goose-skills README)

by gooseworks-ai

Similar skills

See all →
SkillMake a polished video of an app or a featureMake a demo video with OpenScreengetopenscreen/openscreen · by getopenscreenMake a polished video of an app or a feature with OpenScreen's MCP tools, from storyboard to recording, editing and export, then either attach it to a pull request or draft a post on X. Use when the user asks for a demo, a demo video, a screen recording, a product video, a launch clip, a video for X/Twitter, LinkedIn or a README, or to show a change in a pull request ("add a video to the PR", "show the feature to reviewers"). Also use to set OpenScreen up for an agent, and to continue an OpenScreen demo in a new session.★ 4.5k GitHub starsSkillVideo and audio processing with FFmpegFFmpeg for Video Productiondigitalsamba/claude-code-video-toolkit · by digitalsambaVideo and audio processing with FFmpeg. Use for format conversion, resizing, compression, audio extraction, and preparing assets for Remotion. Triggers include converting GIF to MP4, resizing video, extracting audio, compressing files, or any media transformation task.★ 2.1k GitHub starsSkillTurn static SCREENSHOTS into a simulated screen recordingfake-screencast — screenshots → simulated screen recordinghassancs91/claude-youtube-editor · by hassancs91Turn static SCREENSHOTS into a simulated screen recording (TSX) — a fake screencast with an animated cursor that eases to targets and clicks, a browser URL bar that updates per page, hard-cut navigations, in-page filter crossfades, smooth scroll, and a ken-burns zoom onto the payoff. Use when a beat needs to show a walkthrough of a web app / dashboard / settings page and you'd rather build it from screenshots than get (or ask for) a real screen recording — "fake a screen recording", "turn these screenshots into a screencast", "simulate clicking through the dashboard", "animate this UI walkthrough", "cursor moving through the app". Built on remotion/src/lib/screencast.tsx. This is a technique WITHIN step 2 (make-tsx): defer timeline/render/bake orchestration to make-tsx and raw crash-free TSX rules to vidtsx-2d-generator. Not for a single static page clone (use WebBrowserFrame directly) and not when the point is genuine proof of real speed/output (use a real recording).★ 330 GitHub starsSkillA Khan Academy style explainer videoKhan explainerhaiderfarooq3/khan-explainer · by haiderfarooq3Use when the user wants a Khan Academy style explainer video, a blackboard or whiteboard lesson, a hand-drawn chalk-talk animation, a short narrated video that teaches a concept ("explain X in a video", "explainer video", "teach X in 30 seconds"), or a tutorial that draws hand-written arrows, circles and captions over product screenshots ("draw over the UI", "annotated walkthrough"), or clips cut from a YouTube video or podcast where the speaker's real audio plays under a blackboard drawn to it ("khanify this video", "clip this with the Khan visual"), especially when it must be free, local, and use no paid APIs or keys. macOS only.★ 113 GitHub starsSkillPick the output format that makes an explanation easiestUnderstanding ladderChangWenC/understanding-ladder · by ChangWenCPick the output format that makes an explanation easiest to understand, following Karpathy's understanding ladder — (1) plain controlled text, (2) diagram, (3) interactive HTML page, (4) 3Blue1Brown-style explainer video. Classify the content (definition/steps, process/causality, parameter→outcome, unfolding derivation), recommend the lowest rung that fully answers, then produce it. Works in English and Chinese. Use when the user says "explain this clearly", "help me understand", "draw a diagram", "make it interactive", "make an explainer video", "what's the best way to explain X", "understanding ladder", "Karpathy ladder", or 「讲清楚」「帮我理解」「画个图」「做个交互页」「做个讲解视频」「用哪种方式讲最好懂」「理解阶梯」; also when explaining a non-trivial concept, mechanism, algorithm, or an AI agent's output.★ 108 GitHub starsSkillAnimate first frames into A-roll talking head clipsAnimate (A-Roll)TheMattBerman/scrollclaw · by TheMattBermanAnimate first frames into A-roll talking head clips. Uses Sora 2 via fal.ai (primary) with Kling 3 auto-fallback when Sora is unavailable or sunset.★ 71 GitHub stars

More from gooseworks-ai

SkillMine the highest-converting ad angles from customer reviewsAd angle minergooseworks-ai/goose-skills · by gooseworks-aiMine the highest-converting ad angles from customer reviews, Reddit complaints, support tickets, and competitor ads. Extracts actual pain language, competitor weaknesses, and outcome phrases that real buyers use. Outputs a ranked angle bank with proof quotes and recommended ad formats per angle.★ 1.2k GitHub starsSkillAnalyze ad campaign performance data to identify what'sAd campaign analyzergooseworks-ai/goose-skills · by gooseworks-aiAnalyze ad campaign performance data (Google, Meta, LinkedIn) to identify what's working, what's wasting budget, and specific cut/scale/test recommendations. Runs statistical analysis, funnel diagnostics, and multi-channel budget reallocation with specific dollar-amount shift recommendations and scenario modeling.★ 1.2k GitHub starsSkillFor paid lead-gen and participant-recruitment adsAd lead quality analyzergooseworks-ai/goose-skills · by gooseworks-aiFor paid lead-gen and participant-recruitment ads, replaces vanity CPA with true CAC per qualified lead by joining ad-platform data with downstream funnel events, surfaces tracking gaps, and classifies every creative into Scale / Keep / Investigate / Cut.★ 1.2k GitHub starsSkillAnalyze the message match between your ads and landing pagesAd to landing page auditorgooseworks-ai/goose-skills · by gooseworks-aiAnalyze the message match between your ads and landing pages. Checks if the promise in the ad copy carries through to the landing page headline, body, and CTA. Flags disconnects that kill conversion rates. Works with Google, Meta, and LinkedIn ads.★ 1.2k GitHub starsSkillCheck and improve your brand's visibility across AI searchAeogooseworks-ai/goose-skills · by gooseworks-aiCheck and improve your brand's visibility across AI search engines (ChatGPT, Perplexity, Gemini, Grok, Claude, DeepSeek). Set up tracking, run visibility analyses, audit your website for AI readability, and get actionable recommendations. Uses the npx goose-aeo@latest CLI.★ 1.2k GitHub starsSkillSearch Amazon products - find items, compare pricesAmazon Product Searchgooseworks-ai/goose-skills · by gooseworks-aiSearch Amazon products - find items, compare prices, read reviews★ 1.2k GitHub stars