agentleFS
AvyanshKatiyarAvyanshKatiyar/narrated-video/SKILL.md

narrated-video

Skill0 starsChanged 4 months agoMITView on GitHub

Turn a long document nobody will read — a reading, report, analysis, research writeup, dossier — into a narrated 1080p MP4 with burned-in subtitles and visuals computed from the document's own data. Fully local and free (Kokoro TTS, cairosvg, ffmpeg); no GPU, no API, no video-generation model. Use this whenever the user wants a video, voiceover, narration, subtitles, or an explainer out of existing written material, or says people don't read the long version, wants something for social/demo/YouTube, wants to "make this watchable", or asks to convert a PDF/report/reading into video. Also use it when they ask what free TTS or video models exist for narrating their own content — the answer is usually this pipeline, not a video model.

5 other projects use a version of this.

Adopted by

Most recently taken up by

  • Other versions5 projects use a version of this.

    Word for word · 0

    None found.

    Edited · 0

    None found.

    Wrote their own version · 5

    1. every-app/video-skills★ 14same name
    2. AI-Business-Tools/claude-code★ 5same name
    3. lumisum/article-expert★ 2same name
    4. StellarLucas/article-expert★ 2same name
    5. Carthavate/narrated-video-template★ 0same name

    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.

The file

We split this skill into the rules it gives an agent

12 rules. 0 are common, 6 are distinctive, and 6 are very specific to this project.

  • 0

    Common

    Most of the 3 edited versions we read keep these.

  • 6

    Distinctive

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Run references/setup.md once per machine — Python 3.12 venv, kokoro-onnx, cairosvg, espeak-ng, and the Kokoro model weights.

    line 42
  2. Very specific:

    Create a module with a SEGMENTS list.

    line 49
  3. Distinctive:

    Strip the jargon, or the audience stops listening: If the source document uses a specialist vocabulary, translate it.

    line 71
  4. Distinctive:

    Keep the document's best lines verbatim: If the source says *"you do the sovereign's work and then introduce yourself as the assistant"*, …

    line 84
  5. Distinctive:

    Write what the material earns and check the estimate before synthesising.

    line 91
  6. Very specific:

    Import the primitives from scripts/video_kit.py and write one function per visual, each taking (p, data) where p is 0→1 progress through …

    line 112
  7. Distinctive:

    Show the comparison the sentence is making: If the narration says a value is unusual, put the other values next to it.

    line 138
  8. Very specific:

    Use render_frames() from the kit — it walks the manifest, picks the active segment and the active subtitle for each frame, and rasterises:

    line 150
  9. Very specific:

    Check: subtitles readable and on the audio.

    line 203
  10. Very specific:

    If a frame looks sparse, check p before assuming it's broken — a frame sampled at the start of a segment is *supposed* to be mid-fade-in.

    line 207
  11. Distinctive:

    If the user wants generative footage — a dreamlike sequence, b-roll, an imagined scene — this is the wrong tool.

    line 223
  12. Distinctive:

    If the deliverable is a talking head, this pipeline still produces the narration and subtitles.

    line 227

“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Narrated video from a document

Long documents don't get read. The fix is not a shorter document — it's the same content as something a person will sit through. This pipeline turns written material into a narrated MP4 with subtitles and visuals, entirely on the local machine.

The thing that makes it work: the visuals are computed from the document's own numbers. A generative video model cannot draw your data — it will produce handsome, unrelated footage, which is worse than nothing when the document's value is that it's specific and checkable. Everything here is SVG drawn from JSON, so if a bar moves on screen, a number moved in the source.

The shape

document + its underlying data
        │
        ├─► narration script  (you write this — the only judgment call)
        │        │
        │        └─► tts_narrate.py ──► per-sentence WAV + manifest + subtitles.srt
        │                                        │  measured durations
        │                                        ▼
        └─► visual functions ──────────► render loop ──► f000000.png … (1080p)
                                                 │
                                                 ▼
                                          assemble.sh ──► narrated.mp4

One model call writes the narration. Everything after it is deterministic code. Cost is roughly $0.15–0.40 per video at API rates plus ~18 minutes of CPU for a 10-minute runtime.

Before you start

Run references/setup.md once per machine — Python 3.12 venv, kokoro-onnx, cairosvg, espeak-ng, and the Kokoro model weights. It has the two failure modes that will otherwise cost you twenty minutes (espeak-ng phoneme data path, and system Python 3.9 being too old).

Step 1 — Write the narration script

Create a module with a SEGMENTS list. Each segment is one visual and the sentences spoken over it:

SEGMENTS = [
    {
        "id": "open", "visual": "gravities",
        "sentences": [
            "Almost everything in your chart drains toward a single point.",
            "It is the strongest thing you carry.",
        ],
    },
]

Store sentences individually, not as a paragraph. The TTS step synthesises one sentence at a time and records each one's measured start and end. That is what lets subtitles sit exactly on the audio — an estimate drifts badly over ten minutes, and the drift is the first thing a viewer notices.

Three things decide whether the narration lands:

Strip the jargon, or the audience stops listening. If the source document uses a specialist vocabulary, translate it. Nobody outside the field knows what an antardasha or a p-value adjustment for family-wise error is, and a narrator saying it aloud sounds like a robot reading a foreign menu. Keep a banned-term list in the module docstring and assert against it — a mechanical check catches what your ear won't:

BANNED = ["dasha", "lagna", "ashtakavarga", "yogakaraka"]
text = " ".join(s for seg in SEGMENTS for s in seg["sentences"]).lower()
assert not [b for b in BANNED if b in text]

Keep the document's best lines verbatim. If the source says "you do the sovereign's work and then introduce yourself as the assistant", that sentence is why the document works. Paraphrasing it into "you tend to under-credit yourself" throws away the only part a viewer will repeat afterwards.

Length follows from the material, not a target. Roughly 150 words per minute at a measured pace. A five-minute video is ~750 words; ten minutes is ~1,500. Write what the material earns and check the estimate before synthesising.

Step 2 — Synthesise the narration

./ttsenv/bin/python scripts/tts_narrate.py \
    --script my_video_script.py --out audio --voice bm_george --speed 0.88

Emits per-sentence WAVs, a joined narration.wav, manifest.json (measured start/end for every sentence and segment), and a standards-compliant subtitles.srt.

Voices worth knowing: bm_george and bm_lewis (British male, measured — good for anything serious), am_michael (US male), af_bella and af_nicole (US female). --speed around 0.85–0.90 reads as considered; 1.0 is noticeably brisk for reflective material. Kokoro runs faster than you expect — synthesise first and read the total, rather than tuning speed blind.

Step 3 — Write the visuals

Import the primitives from scripts/video_kit.py and write one function per visual, each taking (p, data) where p is 0→1 progress through that segment:

from video_kit import frame, txt, bars, ring, fade, ease, W, H, BLUE

def v_scores(p, d):
    o = fade(p)                       # opacity envelope: fade in, hold, fade out
    return frame(
        txt(W/2, 180, "how it scores", size=44, op=o) +
        bars([(k, v) for k, v in d["scores"].items()], reveal=ease(p * 1.6))
    )

VISUALS = {"scores": v_scores}

The kit provides frame, txt, bars, ring, timeline, orb, wave, fade, ease, stars, and a palette. Read scripts/video_kit.py — it is short and the function signatures are the documentation.

Two habits separate visuals that add value from ones that fill space:

Pass real values in. bars(d["scores"]), not bars([("a", 80), ("b", 60)]). The moment a number is typed by hand it can drift from the document, and nobody will catch it.

Show the comparison the sentence is making. If the narration says a value is unusual, put the other values next to it. A lone number means nothing; the same number against six smaller ones is an argument. The strongest frame in the reference build was a plain bar chart — one planet at 15, everything else at 5 or below — because it made a claim visible rather than decorating one.

Animate with ease() rather than linear interpolation, and wrap each segment in fade() so cuts land soft. There is no crossfade filter in the assembly step because the opacity envelope already does it.

Step 4 — Render frames

Use render_frames() from the kit — it walks the manifest, picks the active segment and the active subtitle for each frame, and rasterises:

from video_kit import render_frames
render_frames(VISUALS, data, "audio/manifest.json", "frames", fps=12)

12 fps is the right default. These are typographic visuals, not motion footage; 12 fps looks identical to 24 and halves the render. cairosvg costs ~50 ms/frame for simple compositions and ~100 ms for busy ones at 1080p, so ten minutes of video is ~7,900 frames and roughly 7–13 minutes of single-threaded CPU. That is the pipeline's real bottleneck — if you ever need volume, parallelise this step, not anything else.

Preview every visual before committing to the full render. One frame per visual at p=0.6 takes under a second and catches the composition problems that would otherwise cost you a full render:

for name, fn in VISUALS.items():
    svg = fn(0.6, data).replace("</svg>", subtitle("a sample line") + "</svg>")
    cairosvg.svg2png(bytestring=svg.encode(), write_to=f"preview/{name}.png",
                     output_width=W, output_height=H)

Then actually look at them. In the reference build this caught a flow diagram whose inflow arrows didn't reach the reservoir they were supposed to fill, and leak lines too thin to see at 1080p — both invisible in code, both obvious in the image.

Step 5 — Assemble

scripts/assemble.sh . out.mp4 12 frames audio

Takes the working directory, output path, fps, frame directory, and audio directory — the last two are parameters rather than defaults on purpose. When a project has more than one video in flight, hardcoded frames/ will silently mux the wrong project's frames onto the right project's narration, and the result looks plausible enough to ship.

Step 6 — QA the encoded file, not the source

Pull frames back out of the MP4 and look at them:

for t in 12 90 240 400; do
  ffmpeg -v error -ss $t -i out.mp4 -frames:v 1 -y qa/t$t.png
done

Check: subtitles readable and on the audio; every visual reaches the state the narration describes; no text overlapping a graphic; the last segment isn't cut off. ffprobe the duration and confirm it matches the manifest's total.

If a frame looks sparse, check p before assuming it's broken — a frame sampled at the start of a segment is supposed to be mid-fade-in.

Where this goes wrong

SymptomCause
Subtitles drift late over a long videoTimings estimated from word count instead of measured per sentence
moov atom not found from ffprobeEncode still running; +faststart writes the index last
Chart renders as an empty boxSVG uses CSS custom properties the page never defines — declare them on :root or pass literal colours
Wrong footage muxed to right audioFrame/audio directories defaulted instead of passed
espeak phoneme error on first TTS runESPEAK_DATA_PATH unset — see references/setup.md
Narration much shorter than plannedKokoro is faster than 150 wpm; lower --speed, or add material

When something else is the better answer

If the user wants generative footage — a dreamlike sequence, b-roll, an imagined scene — this is the wrong tool; that needs a video model (Wan, LTX, Hunyuan, or a hosted service), and on a Mac it needs rented GPU.

If the deliverable is a talking head, this pipeline still produces the narration and subtitles; only the face needs an external service.

And if the document has no data behind it — pure prose with nothing to plot — say so before building. You can still make a handsome typographic video, but be honest that the visuals are illustration rather than evidence, because the whole argument for this approach is that they usually aren't.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.