voiceover-video-skill
AGENTS.md29 starsChanged 33 days agoMITView on GitHub
Instructions for AI coding agents (Claude Code, Codex, Cursor, and others) working on this repository. If you are running the skill to make a video, follow skills/shenghua/SKILL.md instead. One skill, shenghua, packaged as a plugin for Claude Code and Kimi Code CLI. It is model-agnostic and turns a voice recording into an animated vertical video. With no recording (script mode), speak.py voices a written script with one offline voice per character, and cartoon hosts in the template act it out.…
- Pipes a download into a shell
1 other project uses a version of this.
Adopted by
Most recently taken up by
- Dancan254/shenghuaword for word, first seen Oct 13, 2026
Other versions1 project uses a version of this.
Word for word · 1
- Dancan254/shenghua★ 29100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,439 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repositoryOne other file it gives its agents.
- shenghuaSkill
The file
We split this AGENTS.md into the rules it gives an agent
6 rules. About 0 are common, 2 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 78
Keep it that way:
- Very specific: line 131
Scripts fail loud: Every script prints a one-line result and a Next: line, and on failure names the missing input and the fix.
- Distinctive: line 159
Run the unit-test suite first (numpy only, no setup needed):
- Very specific: line 191
Do not pipe a step through tail/head when checking it — the pipe hides the exit code.
- Very specific: line 198
Keep SKILL.md imperative and short enough for an agent to follow in one pass.
- Very specific: line 199
Do not add per-user or per-brand content to the repo.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AGENTS.md
Instructions for AI coding agents (Claude Code, Codex, Cursor, and others) working on this
repository. If you are running the skill to make a video, follow
skills/shenghua/SKILL.md instead.
What this repo is
One skill, shenghua, packaged as a plugin for Claude Code and Kimi Code CLI. It is
model-agnostic and turns a voice recording into an animated vertical video. With no recording (script
mode), speak.py voices a written script with one offline voice per character, and cartoon hosts in the
template act it out. Everything lives in skills/shenghua/.
skills/shenghua/
├── SKILL.md the core workflow the agent follows at run time (every mode)
├── brand.example.json fallback brand kit (version 2) when the user has none
├── examples/kits/ two fictional kits (dark Northwind, light Lumen) for testing and copying
├── references/
│ ├── scene-blocks.md scene catalogue, pacing rules, sound cues, safe zones
│ ├── presenter.md presenter mode: keying, layouts, speaker position, pacing
│ ├── long-form.md long form: chapter projects, holds, captions off, landscape zones, assembly
│ ├── face-bookends.md bookends mode: face shots, [FACE]/[VOICE] sections, extract_face.sh
│ ├── brand-kits.md creating and managing kits, the kits folder, the brand board, glyphs
│ └── explainers.md script mode: explainer shape, analogy, cast, script format, voices
├── templates/ visual theme engine + themes
│ ├── kinetic.html the shared HTML/JS engine + demo shots
│ ├── board.html the brand board: one kit shown in one theme, for approval
│ ├── sounds.json CC0 sound packs (audio.pack) and the music library (audio.music): archive URLs, sha256, files
│ ├── templates.json theme catalogue: canvas (dark/light), mood, extra fonts, motion profile
│ └── themes/ shared.css (kit tokens, backgrounds, people/footage blocks) + one CSS per theme
├── tests/ unittest suite (numpy only), run from the repo root
└── scripts/
├── setup.sh dependency check, playwright-core + Chromium, GSAP, fonts; --voices, --vision, --enhance
├── doctor.sh install health check: required + optional pieces, kit fonts; names the fix, exit 1 on gaps
├── make.sh fast path: prepare (doctor+transcribe+trim+theme suggestions) / finish (render+deliver+verify)
├── suggest_theme.py ranks themes for a mode/topic from templates.json, deterministic
├── speak.py script mode: script → voice.wav + speech.json/js + aligned words.json
├── transcribe.py faster-whisper, word-level timestamps; config.json for model/device defaults
├── trim_take.py cut dead air: lossless voice.wav, word times shifted to 0, take.json (+ probe block for video); --remove-fillers (audio-only)
├── enhance_voice.py optional DeepFilterNet cleanup: voice.wav → voice-clean.wav (setup.sh --enhance)
├── episodes.py split a script/transcript into epN folders at sentence boundaries; status shows series progress
├── key_greenscreen.py green-screen take → presenter/fNNNNN.webp with alpha, resumable; 30 fps edit frames
├── retime.py re-time an edit onto a retake of the same script
├── build_captions.py applies fixes.json → words.js + captions.srt/vtt; --from-srt re-imports externally edited captions
├── brand_kit.py kit validation, colour derivation, contrast/glyph report, kits folder, per-kit install
├── init_kit.py answers or client files → a kit in the kits folder; --from converts a version-1 brand.json
├── fill_template.py kit + theme + geometry + duration → work/index.html (or board.html); keeps authored shots; --energy, --captions, --platform
├── extract_face.sh to-camera video → face/fNNNNN.jpg, numbered by edit frame
├── face_zones.py optional YuNet face boxes from face/ frames → facezones.js for overlap checks
├── find_media.py photo/video search (Commons, Openverse, Archive, Pexels, web, YouTube) + credits.json
├── excerpt.js article/PDF passage → excerpts/<name>.png + highlight box in excerpts.js + credits.json
├── extract_clip.sh fetched clip → clips/<name>/fNNNNN.jpg (+ clips/<name>.wav)
├── render.js stills | frames | stream | cues | check | board, driven by window.renderAt(t)
├── render-frames.sh parallel frame rendering, resumable; --resolution 4k, --blur, --force
├── render-chunks.sh default for finals: streams frames into the encoder in chunks, resumable, no frames directory
├── preview.py serve a work dir with a browser shell: play/scrub/click-to-seek around renderAt; --check self-tests headless
├── contact-sheet.sh stills → one review image
├── synth_audio.py cues.json + the kit's audio block → sfx.wav + music.wav + mix.json (+ duck envelope from words.js)
├── mix-audio.sh the soundtrack both encoders share: voice + ducked clip audio + ducked music + SFX → mix.wav
├── mix-encode.sh frames + mix-audio.sh → mp4; --10bit, --embed; bitrate cap scales with the canvas
├── check_quality.py VMAF gate: encoded output vs source video, skips cleanly without libvmaf
├── deliver.sh master + stereo web copy + captions + cover + credits.txt → delivery/; quality gate vs the source (--source, --skip-quality)
└── assemble.py long form: chapters.json → one master, one loudness pass, merged captions, YouTube chapters.claude-plugin/marketplace.json registers the skill for Claude Code's /plugin install, and
.kimi-plugin/plugin.json registers it for Kimi Code CLI's /plugins install. Both are additive: other
hosts ignore them and read SKILL.md directly. install.sh at the root is the install path for
everyone else: it copies the skill into an agent's skills directory (auto-detect, or --dir), updates
in place without losing setup.sh's downloads, checks for updates (--check, comparing each install's
.installed-from commit against the remote main), and uninstalls. Keep it dependency-free, working on
macOS's bash 3.2, and working when piped through curl | bash.
What an agent needs to run this
The skill is model-agnostic by construction — no script calls a model, and SKILL.md names no vendor.
Keep it that way:
- Required: a shell, file writing, reading text output. Every script prints a one-line result and a
Next:line, so an agent can follow the workflow from stdout alone. - Optional: vision. Step 5 (judging a downloaded image), Step 7 (reading the contact sheet), and
automatic face measurements benefit. The visual checks have a documented text path: list images for
the user to confirm, and
render.js check, which measures every shot and reports defects as text. - Never assumed: audio. Nothing can hear the mix, so the workflow always asks the user to listen.
A change that makes any step impossible without vision, or that ties the workflow to one agent's tool names, breaks this.
Invariants — do not break these
- Rendering is deterministic. The composition exposes
window.renderAt(t)and every frame is a seek tof / 30. NoMath.random(), noDate.now(), norequestAnimationFrame-driven state, no GSAPrepeat: -1oryoyoon the main timeline. Grain uses the seeded PRNG in the template. Playback with arequestAnimationFrameloop lives only in_preview.html(preview.py's shell); the composition itself never auto-plays. - Every sound comes from a cue. Helpers that make noise push into
window.SFX;synth_audio.pymust handle every cuetypethe template orscene-blocks.mddocuments. Adding a cue type means changing both, plusbrand_kit.CUE_TYPES(a unit test holds the two in step). - Frame ranges are two arguments.
render.js frames <html> <out> <from> <to>. A single quoted"from to"renders zero frames;render.jsrejects it — keep that check. - The mix pads to the full duration.
mix-audio.sh(shared bymix-encode.shandrender-chunks.sh) usesapad+atrim; without itloudnormtrims the tail and the video comes out short. - The encoder caps bitrate. Film grain defeats CRF alone; removing
-maxrateproduces 800 MB files. The encode also converts RGB frames with the BT.709 matrix and tags the stream BT.709; without both, phones shift the brand colours. - Final frames are supersampled PNG.
render.jsrenders at 2x device pixels and saves 1x PNG, so edges stay crisp and red text has no JPEG chroma bleed. JPEG frames are forVV_QUALITY=draftonly, andmix-encode.shrefuses a frames directory that mixes the two.render.js streamwrites those same frame bytes to stdout only (image2pipe); every diagnostic goes to stderr. - Nothing third-party is committed. GSAP, fonts,
node_modules, and Chromium are downloaded bysetup.sh. Never add them to git. - Placeholders are
{{dotted.names}}filled byfill_template.py. A new placeholder needs a value there and, if it comes from the kit, a fieldbrand_kit.pyvalidates andbrand.example.jsonshows. - Every fetched file is credited.
find_media.pyrecords each download incredits.jsonwith its licence, and marks web and YouTube files as unlicensed so the report can name them. A new source must do the same, and must be listed inLICENSED_SOURCESonly if its results genuinely carry a licence. - Every theme styles every block, in the kit's colours. A theme CSS defines every class
kinetic.cssdoes plus the--panel,--panel-fg,--frame,--radius,--displaytokensshared.cssreads. Every colour is a--brand-*token or acolor-mix()of one; the only literals are pure black and white and the--fx-*(effect) and--host-*(cartoon host) declarations inshared.css, and CI fails on any other. A theme declares its canvas (scheme: dark or light) intemplates.json; the kit's display font always wins, and a theme's own typeface (templates.json→fonts) is only its fallback. - Hosts are pure functions of
t.renderHosts(t)derives every mouth, blink and bob fromtandspeech.js; a host never keeps state between frames.speech.js,faces.js,facezones.jsandexcerpts.jsalways exist (fill_template.pywrites an empty one of each), so a recording without speakers, face measurements or captured excerpts renders hosts idle, photos unshifted and no document cards rather than failing. - Characters are original. The shipped hosts are original designs. Never add a host that imitates an existing cartoon, film or game character.
- Scripts fail loud. Every script prints a one-line result and a
Next:line, and on failure names the missing input and the fix. Match that shape. - Presenter frames are numbered by edit frame and keying resumes.
key_greenscreen.pyappliestake.json's cut when it keys, so frame N of the edit ispresenter/fNNNNN.webpwith no runtime offset. A re-run keys only frames whose WebP is incomplete, and workers stop when their parent dies, so a killed run never leaves a process writing frames a resumed run is also writing. Keep both properties: agents run this step for most of an hour, often past their shell's time limit. - No render on a fallback font.
render.jsloads every kit family by name beforestills,frames,checkandboard, and exits 1 if one has no face: a lost font otherwise renders silently in a lookalike with different spacing, which no one notices until the captions shift.checkalso flags single characters no kit face covers, unless the kit'sfonts.fallbackmarks them deliberate.
Conventions
- Bash:
set -euo pipefail, quote every variable,SCRIPTS_DIRresolved fromBASH_SOURCE. Scripts are called from zsh too — never rely on word splitting. - Python: 3.10+, standard library plus
numpyandfaster-whisperonly (find_media.pyusesurllib;speak.pyalone also needskokoro-onnx, installed bysetup.sh --voices;key_greenscreen.py,face_zones.pyandfind_media.pyuseopencvonly whensetup.sh --visioninstalled it;enhance_voice.pyneedsdeepfilternet, installed bysetup.sh --enhance).argparse, amain()returning an exit code, errors to stderr. - JavaScript:
render.jsandexcerpt.jsdepend onplaywright-coreonly; the template on GSAP only.excerpt.js --pdfalso needspdftoppm(poppler) on PATH, an external binary like ffmpeg. - Comments explain why, never what. One line.
- No new dependency without a reason in the PR description and an entry in
THIRD_PARTY.md.
How to verify a change
Run the unit-test suite first (numpy only, no setup needed):
python3 -m unittest discover -s skills/shenghua/tests -t skills/shenghuaThe pipeline itself is verified end to end on a clean copy, because a cached setup hides broken installs:
C=$(mktemp -d) && cp -r skills/shenghua "$C/skill" && S="$C/skill/scripts" && W="$C/work"
K="$C/skill/examples/kits/northwind" # repeat the run with examples/kits/lumen
bash "$S/setup.sh" "$K" # must print "ready"
python3 "$S/brand_kit.py" check "$K" # contrast report, exit 0
python3 "$S/fill_template.py" "$W" 6 --format landscape --brand "$K" --template aurora --board
node "$S/render.js" board "$W/board.html" "$W/board.png" # look at it
python3 "$S/transcribe.py" <any short speech clip> --outdir "$W" --model base
python3 "$S/build_captions.py" "$W"
python3 "$S/fill_template.py" "$W" 9.5 --brand "$K"
node "$S/render.js" stills "$W/index.html" "$W/stills" 1.0,2.4,5.0,7.8
bash "$S/contact-sheet.sh" "$W/stills" "$W/contact.jpg" # look at it
node "$S/render.js" cues "$W/index.html" "$W/cues.json"
python3 "$S/synth_audio.py" "$W/cues.json" 9.5 "$W"
bash "$S/render-frames.sh" "$W/index.html" "$W/frames" 9.5 8
bash "$S/mix-encode.sh" "$W" <the same clip> 9.5 "$W/out.mp4"
ffmpeg -v error -y -ss 5 -i "$W/out.mp4" -frames:v 1 "$W/verify.jpg" # look at itA change is verified when: setup.sh prints ready, every step exits 0, contact.jpg and
verify.jpg look right, and mix-encode.sh reports the expected duration and roughly −14 LUFS.
Look at the images. Renders that exit 0 can still be blank, clipped, or in a fallback font.
Do not pipe a step through tail/head when checking it — the pipe hides the exit code.
Scope
- Changes to the workflow belong in
SKILL.md; mode detail lives inreferences/(presenter.md,face-bookends.md,brand-kits.md,explainers.md); changes to visual vocabulary belong inreferences/scene-blocks.mdand the template helpers. - Keep
SKILL.mdimperative and short enough for an agent to follow in one pass. - Do not add per-user or per-brand content to the repo; it lives in the user's own kit folder. The only
kits in the repo are the fictional ones in
examples/kits/; never add a real company's brand there.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

