Use when someone wants a narrated, painted or hand-drawn animated short (TikTok, Reels, YouTube Shorts) from a script or topic and a look described in words. Every frame is drawn in code and GPU-painted, voiced locally, captioned and scored into an MP4; no image models or stock.
Installs into .claude/skills of the current project.
Sponsors
SSkills DirectorySkills Directory
Know which skills are safe — weekly.
SSkills DirectorySkills Directory
Is that skill safe to install?
Are you the author of Shorts Animator?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/oleg-koval-shorts-animator-agent-skills)
---
name: shorts-animator
description: Use when someone wants a narrated, painted or hand-drawn animated short (TikTok, Reels, YouTube Shorts) from a script or topic and a look described in words. Every frame is drawn in code and GPU-painted, voiced locally, captioned and scored into an MP4; no image models or stock.
license: MIT
allowed-tools: Bash, Read, Write, Edit
compatibility: Codex, Claude Code, Hermes Agent, Cursor, GitHub Copilot, Windsurf, Kiro, and other Agent Skills compatible tools. Needs Node 22.18+, ffmpeg and Chrome; the narrator installs Python and its voice model on first use.
metadata:
targets: [_source-only]
author: Oleg Koval
tags:
- video
- animation
- shorts
- narration
- canvas
---
# Shorts Animator
A script and a look in, an animated short out. You write the lines and draw every shot as canvas code; the
tool paints it on the GPU on twos, narrates it with a local Kokoro voice, captions it, scores it and
encodes the MP4. Nothing is filmed, generated by an image model, or licensed.
`SA` = `node <this skill's folder>/scripts/sa.ts`. Write the full command each time. One folder per short:
`WORK=./<slug>-short`. First run on a machine: `SA doctor` (checks Node, ffmpeg, Chrome, sets up the voice,
renders a 3-second test).
## 1. Decide, do not interrogate
Decide the lines, shots, look, voice and music yourself. Ask one question only if the subject is unclear.
A pasted script is used word for word. A topic: write it per `references/writing.md`.
| they say | look |
|---|---|
| oil, painterly, Van Gogh, swirly, "like the faith shorts" | `oil` |
| watercolour, soft, dreamy, children's book | `watercolor` |
| crayon, pastel, chalk, for kids | `pastel` |
| ink, comic, graphic novel, line art | `ink` |
| flat, storybook, gouache, poster, calm | `gouache` |
Custom look: `"look": { "base": "oil", "brush": 16, "palette": {...} }`. Read `references/looks.md` only
when overriding numbers. Voice `bm_george` (default, scripture and quotes), `am_michael`, `bf_emma`
(stories), `af_heart` (kids); `speed` 0.85 to 0.95 reads calm. Their recording: `"voice": { "file":
"assets/voice.m4a" }` with `say` lines matching it in order. `"voice": "none"` for music only. Music
`mood`: `tender`, `hopeful`, `night`, `wonder`, `playful`, `none`, or a track path. `SA looks` lists all.
## 2. Write the plan and the drawings
```bash
SA new "$WORK" --look oil # short.json + scenes.js + assets/
```
`short.json` lines: `{ "say": "...", "scene": "meadow" | ["a", "b"], "move": "push", "hold": 0.8 }`.
- A list of scenes cuts on the caption chunks. Cut often: a new picture every 1.5 to 3 s.
- `move`: push, pull, pan-left, pan-right, rise, fall, drift, still. `transition`: cut, fade, dip (rarely).
- `caption` shows different words; `/` forces a break. `captions.style`: box, plain, none.
- `format`: 9:16 (default), 16:9, 1:1, 4:5. `twos`: 2 (hand-drawn), 1 (smooth).
`scenes.js`: `window.SCENES = { "meadow": (k) => { k.sky({...}); k.person({...}); } }`. Read
`references/kit.md` before writing scenes. Draw like a painter:
- Big simple shapes, strong value contrast, one subject. The paint pass adds texture; draw nothing fussy.
- Keep faces out of the caption band (about 77% down).
- Every shot different: place, scale, day and night, warm and cool. Match the words.
- Small motion: `pose: "walk"`, `dx`, rain, glints. `k.t` seconds, `k.p` progress 0 to 1.
- No helper for it (a boat, an animal)? Write a small function with `k.g`, flat shapes in the palette.
## 3. Check, then look (budgeted)
```bash
SA check "$WORK" # no browser, about a second: missing scenes, throwing scenes, unknown kit calls,
# figures under the captions, line and total length
SA frames "$WORK" # narrates (cached), one painted frame per shot -> frames/sheet.jpg
SA still "$WORK" 7 # one shot full size, only when the sheet shows a problem you cannot place
```
Fix every `ERROR` from `check` before spending a browser run. Then read `frames/sheet.jpg` once. Fix muddy
or empty shots, a figure lost in its background (contrast, `rim`), look-alike shots, a red "no scene"
frame, and every `⚠ the page said` line. **Budget: at most two frames rounds before the first render.**
Images are the expensive part of this loop; do not reopen a sheet you have already judged.
## 4. Render and deliver
```bash
SA render "$WORK" # about a minute for 40 s -> <title>.mp4, renders/vN.mp4 (+ its short.json), qa/sheet.jpg
```
Read `qa/sheet.jpg` only if the render printed a warning or they report a problem. Tell them in two
lines what it is (look, length, shots) and: "Tell me what to change, one sentence is enough."
## 5. One-sentence changes
Smallest change, `SA check`, render, show. `SA set "$WORK" voice.speed=0.85 music.mood=night` edits
short.json (JSON values; plain text otherwise) and refuses an invalid result.
| they say | change |
|---|---|
| more painterly, softer | `look` or its numbers (brush, swirl, wash) |
| slower, faster | `voice.speed`, `pause` |
| a woman's voice, American | `voice.id` |
| no music, more emotional | `music.mood` |
| bigger captions, no box | `captions.size` 52, `captions.style` plain |
| for YouTube | `format` 16:9, redraw shots whose layout breaks |
| change shot 4 | that scene function only |
## When something goes wrong
| symptom | fix |
|---|---|
| no Chrome | install Chrome, or `SA doctor` fetches a headless one |
| "painting on the CPU" | works, about 10x slower |
| narrator setup fails | needs Python 3.10+ or `uv`; or use `"voice": "none"` or their recording |
| voice timing off with their recording | `say` lines must match its words, in order |