Story Telling Video Skill
Environment
- Node 20+, npm. No bun and no ffmpeg anywhere in the pipeline
- Remotion 4.0.512, every
@remotion/* on one version, mediabunny 1.50.8
- Narration: Sarvam AI
bulbul:v3, returns WAV, played directly with no conversion
- Images: FLUX 2 on fal.ai,
fal-ai/flux-2-pro for text to image and
fal-ai/flux-2-pro/edit to restyle a reference photo the user owns
- Drafting: OpenAI, optional, one call, only used by
npm run draft
- Durations:
@remotion/media-parser with nodeReader, never ffprobe
- Keys and every model id come from
.env, loaded by cli/env.ts from the
project root and from the parent folder. SARVAM_KEY aliases SARVAM_API_KEY
- Fonts:
@remotion/google-fonts, loaded at module level with explicit weights
- Output: 1920x1080 landscape or 1080x1920 vertical, 30fps, H.264
- Stories live in
public/<slug>/, renders in out/
- Never use icons or emoji in code, prompts, logs, narration or commit messages
Critical Rules
- story.json is the single source of truth. Script, image prompts, voice,
fonts, colours, stat cards and measured durations all live in
public/<slug>/story.json. The composition reads it through
calculateMetadata. Never hardcode a duration, size or asset path in a component.
- Audio drives length.
npm run generate measures every voice file and
writes durationInFrames back. durationInFrames and
audioDurationInSeconds are outputs, never hand edited. To lengthen a video,
change the words or raise outro.durationInFrames, never stretch a scene.
- Show the narration before generating. Voices and images cost money and
take minutes. A wrong figure caught in review is free.
- Facts come only from the given context. Nothing inferred, rounded or
assumed. When the source contradicts itself, prefer body text over headline
and tell the user which reading was used.
- Spell numbers as words in narration, digits only on stat cards. "thirty
four thousand crore" is spoken,
Rs 34,387 cr is shown.
- Never name a real person in an image prompt. Use a reference photo the
user owns through the edit model, or describe the person generically.
In an edit prompt the likeness clause comes first, scene direction second.
- One art style for the whole story.
style.artPrompt is prepended to
every image prompt. That single string is what makes the frames feel like
one film rather than a stock photo set.
- Animate with
useCurrentFrame() and interpolate(), inline in the
style prop, always clamped. CSS transitions, CSS animations and Tailwind
animation classes render as still frames.
- Use
scale, translate, rotate CSS properties, not transform
strings, with output: "perceptual-scale" on scale animations.
- Respect the video safe area. 80px from the sides, 100px from top and
bottom at 1080 wide, scaled by composition width. Headlines at least 84px,
supporting text at least 44px on the same scale. Take sizes from
getLayout(), never typed pixel constants.
- Image ids are global across the story, not per scene. A reused id
silently overwrites a frame, and zod will not catch it.
- Skip work that already exists. The generator skips any voice or image
already on disk. Delete exactly what should change and rerun;
--force
should be rare.
- Never commit
.env, a key, or generated media. story.json regenerates
everything else, and keys must never reach a settings file or a skill file.
Modular Rule Files
| Rule File |
Topic |
| story-json |
The schema field by field, full example, cheap edit recipes |
| narration |
Script shape, word budget per duration, TTS-safe phrasing, stat cards |
| voices |
Complete bulbul voice catalog, 11 languages, pace and temperature, font pairing |
| images |
FLUX 2 models, prompt recipe, reference photos, likeness, real failure modes |
| models |
Every model id, where it is configured, how to choose, cheap iteration loop |
| story-formats |
Format catalog with scene skeletons and stat placement |
| composition |
Remotion best practices the components follow |
| workflow |
Commands, key verification, cost and timing, troubleshooting |
| pitfalls |
The fourteen common mistakes, plus both checklists |
Story Templates (in rules/assets/)
Schema-validated starting points. Copy to public/<slug>/story.json, change
slug and compositionId, rewrite the narration and prompts, then generate.
| Format |
Path |
Length |
Scenes |
Images |
| Biography |
assets/biography/story.json |
60s |
6 |
12, uses a reference photo |
| Concept explainer |
assets/explainer/story.json |
60s |
6 |
9, metaphor led |
| Vertical short |
assets/reel/story.json |
30s |
3 |
3, 1080x1920 |
| Numbered list |
assets/listicle/story.json |
60s |
6 |
7, numbered stat cards |
| News or event |
assets/news/story.json |
45s |
5 |
7, dated claims |
| Myth versus fact |
assets/myth-buster/story.json |
45s |
5 |
5, alternating |
Companion Skills
| Skill |
Use for |
story-setup |
First run: Node check, install, .env, key verification, version alignment |
story-new |
Context to finished video, with a script review step |
story-render |
Change one line, one image, the voice or the orientation, then re-render |
Pipeline
context (article, notes, a paragraph, a page already read)
|
| npm run draft OpenAI, optional. Or write story.json yourself.
v
public/<slug>/story.json script, image prompts, voice, style, stats
|
| npm run generate Sarvam voices, FLUX 2 images, measured durations
v
public/<slug>/ voice-0.wav ... image-1.jpg ... story.json updated
|
| calculateMetadata reads story.json, sets size, fps, durationInFrames
v
npx remotion render <CompositionId> out/<slug>.mp4 --codec h264
Instructions
Check the pipeline exists before anything else. These skills drive a
Remotion project. If the skills were installed on their own, cli/ and
src/ are not present yet and must be fetched once:
test -f cli/generate.ts && test -f src/Root.tsx && echo present || echo missing
If missing, run the story-setup skill, which fetches the project with
npx --yes degit marketcalls/story-telling story-telling, installs
dependencies, creates .env and verifies the keys. Everything below runs
from the project root.
Read the rules you need from the table above. story-formats first if the
shape is not decided, narration and images before writing anything.
Confirm the environment: .env with SARVAM_API_KEY and FAL_KEY,
npm install done. If unsure, run the story-setup skill.
Settle three things with the user if not already clear: orientation, length
in seconds, and whether reference photos are involved.
Write public/<slug>/story.json from a template, or draft it with
npm run draft and then fix the facts by hand.
Show the narration and wait for approval.
npm run generate -- --slug <slug>.
Preview with npm run studio, then render.
Run the shipping checklist in rules/pitfalls.md.
Report measured numbers: scene count, duration, image count, output path and
size, plus any assumption made about an ambiguous fact.
Commands
npm install # once
npm run story -- --slug big-bull --context ./article.md --seconds 60 [--render]
npm run draft -- --slug big-bull --context ./article.md --orientation vertical
npm run generate -- --slug big-bull [--only voice|images] [--force] [--logs]
npm run studio
npm run typecheck
npx remotion render BigBull out/big-bull.mp4 --codec h264
--context accepts a file path or the text itself. Any folder under public/
containing a story.json becomes a composition automatically, named after the
slug in PascalCase, so nothing is registered by hand.
Project Layout
.env keys and every model id
cli/
env.ts loads .env from here and from the parent folder
service.ts Sarvam, FLUX 2 text to image, FLUX 2 edit, OpenAI
draft.ts context -> story.json
generate.ts story.json -> assets -> measured durations
story.ts draft, generate and optionally render in one go
src/
Root.tsx story discovery, calculateMetadata
StoryVideo.tsx scene timeline, audio, outro
components/ ImageLayer, Subtitle, StatCard, TitleCard, OutroCard
lib/schema.ts zod schema shared by the CLI and the composition
lib/layout.ts safe areas and type scale, both orientations
lib/fonts.ts module level font loading
public/<slug>/ story.json, voice-N.wav, image-N.jpg
out/ rendered mp4
Worked Example
public/jhunjhunwala/story.json is a complete 6 scene, 58.7 second landscape
story: cartoon art, two reference photos restyled through flux-2-pro/edit,
stat cards on four scenes, and a silent outro. Read it before writing a new one.
Its media files are gitignored, so regenerate them with
npm run generate -- --slug jhunjhunwala.
Example Usage
/story-telling turn this article into a sixty second explainer
/story-new big-bull ./article.md 60 landscape
/story-render big-bull images
1---2name: story-telling3description: Story video expert. Use when the user asks to make an explainer video, a story video, a reel or a short, a narrated video from an article, a cartoon explainer, or asks to add a scene, change a voice, redo an image, switch to vertical, or re-render an existing story. Also triggers for Sarvam AI voiceover, bulbul TTS, FLUX 2 image generation, flux-2-pro/edit reference photos, and Remotion narration timing.4---56# Story Telling Video Skill78## Environment910- Node 20+, npm. No bun and no ffmpeg anywhere in the pipeline11- Remotion 4.0.512, every `@remotion/*` on one version, `mediabunny` 1.50.812- Narration: Sarvam AI `bulbul:v3`, returns WAV, played directly with no conversion13- Images: FLUX 2 on fal.ai, `fal-ai/flux-2-pro` for text to image and14 `fal-ai/flux-2-pro/edit` to restyle a reference photo the user owns15- Drafting: OpenAI, optional, one call, only used by `npm run draft`16- Durations: `@remotion/media-parser` with `nodeReader`, never ffprobe17- Keys and every model id come from `.env`, loaded by `cli/env.ts` from the18 project root and from the parent folder. `SARVAM_KEY` aliases `SARVAM_API_KEY`19- Fonts: `@remotion/google-fonts`, loaded at module level with explicit weights20- Output: 1920x1080 landscape or 1080x1920 vertical, 30fps, H.26421- Stories live in `public/<slug>/`, renders in `out/`22- Never use icons or emoji in code, prompts, logs, narration or commit messages2324## Critical Rules25261. **story.json is the single source of truth.** Script, image prompts, voice,27 fonts, colours, stat cards and measured durations all live in28 `public/<slug>/story.json`. The composition reads it through29 `calculateMetadata`. Never hardcode a duration, size or asset path in a component.302. **Audio drives length.** `npm run generate` measures every voice file and31 writes `durationInFrames` back. `durationInFrames` and32 `audioDurationInSeconds` are outputs, never hand edited. To lengthen a video,33 change the words or raise `outro.durationInFrames`, never stretch a scene.343. **Show the narration before generating.** Voices and images cost money and35 take minutes. A wrong figure caught in review is free.364. **Facts come only from the given context.** Nothing inferred, rounded or37 assumed. When the source contradicts itself, prefer body text over headline38 and tell the user which reading was used.395. **Spell numbers as words in narration, digits only on stat cards.** "thirty40 four thousand crore" is spoken, `Rs 34,387 cr` is shown.416. **Never name a real person in an image prompt.** Use a reference photo the42 user owns through the edit model, or describe the person generically.43 In an edit prompt the likeness clause comes first, scene direction second.447. **One art style for the whole story.** `style.artPrompt` is prepended to45 every image prompt. That single string is what makes the frames feel like46 one film rather than a stock photo set.478. **Animate with `useCurrentFrame()` and `interpolate()`,** inline in the48 `style` prop, always clamped. CSS transitions, CSS animations and Tailwind49 animation classes render as still frames.509. **Use `scale`, `translate`, `rotate` CSS properties,** not `transform`51 strings, with `output: "perceptual-scale"` on scale animations.5210. **Respect the video safe area.** 80px from the sides, 100px from top and53 bottom at 1080 wide, scaled by composition width. Headlines at least 84px,54 supporting text at least 44px on the same scale. Take sizes from55 `getLayout()`, never typed pixel constants.5611. **Image ids are global across the story,** not per scene. A reused id57 silently overwrites a frame, and zod will not catch it.5812. **Skip work that already exists.** The generator skips any voice or image59 already on disk. Delete exactly what should change and rerun; `--force`60 should be rare.6113. **Never commit `.env`, a key, or generated media.** `story.json` regenerates62 everything else, and keys must never reach a settings file or a skill file.6364## Modular Rule Files6566| Rule File | Topic |67|---|---|68| [story-json](rules/story-json.md) | The schema field by field, full example, cheap edit recipes |69| [narration](rules/narration.md) | Script shape, word budget per duration, TTS-safe phrasing, stat cards |70| [voices](rules/voices.md) | Complete bulbul voice catalog, 11 languages, pace and temperature, font pairing |71| [images](rules/images.md) | FLUX 2 models, prompt recipe, reference photos, likeness, real failure modes |72| [models](rules/models.md) | Every model id, where it is configured, how to choose, cheap iteration loop |73| [story-formats](rules/story-formats.md) | Format catalog with scene skeletons and stat placement |74| [composition](rules/composition.md) | Remotion best practices the components follow |75| [workflow](rules/workflow.md) | Commands, key verification, cost and timing, troubleshooting |76| [pitfalls](rules/pitfalls.md) | The fourteen common mistakes, plus both checklists |7778## Story Templates (in rules/assets/)7980Schema-validated starting points. Copy to `public/<slug>/story.json`, change81`slug` and `compositionId`, rewrite the narration and prompts, then generate.8283| Format | Path | Length | Scenes | Images |84|---|---|---|---|---|85| Biography | `assets/biography/story.json` | 60s | 6 | 12, uses a reference photo |86| Concept explainer | `assets/explainer/story.json` | 60s | 6 | 9, metaphor led |87| Vertical short | `assets/reel/story.json` | 30s | 3 | 3, 1080x1920 |88| Numbered list | `assets/listicle/story.json` | 60s | 6 | 7, numbered stat cards |89| News or event | `assets/news/story.json` | 45s | 5 | 7, dated claims |90| Myth versus fact | `assets/myth-buster/story.json` | 45s | 5 | 5, alternating |9192## Companion Skills9394| Skill | Use for |95|---|---|96| `story-setup` | First run: Node check, install, `.env`, key verification, version alignment |97| `story-new` | Context to finished video, with a script review step |98| `story-render` | Change one line, one image, the voice or the orientation, then re-render |99100## Pipeline101102```103context (article, notes, a paragraph, a page already read)104 |105 | npm run draft OpenAI, optional. Or write story.json yourself.106 v107public/<slug>/story.json script, image prompts, voice, style, stats108 |109 | npm run generate Sarvam voices, FLUX 2 images, measured durations110 v111public/<slug>/ voice-0.wav ... image-1.jpg ... story.json updated112 |113 | calculateMetadata reads story.json, sets size, fps, durationInFrames114 v115npx remotion render <CompositionId> out/<slug>.mp4 --codec h264116```117118## Instructions1191200. **Check the pipeline exists before anything else.** These skills drive a121 Remotion project. If the skills were installed on their own, `cli/` and122 `src/` are not present yet and must be fetched once:123124 ```bash125 test -f cli/generate.ts && test -f src/Root.tsx && echo present || echo missing126 ```127128 If missing, run the `story-setup` skill, which fetches the project with129 `npx --yes degit marketcalls/story-telling story-telling`, installs130 dependencies, creates `.env` and verifies the keys. Everything below runs131 from the project root.1321. Read the rules you need from the table above. `story-formats` first if the133 shape is not decided, `narration` and `images` before writing anything.1342. Confirm the environment: `.env` with `SARVAM_API_KEY` and `FAL_KEY`,135 `npm install` done. If unsure, run the `story-setup` skill.1363. Settle three things with the user if not already clear: orientation, length137 in seconds, and whether reference photos are involved.1384. Write `public/<slug>/story.json` from a template, or draft it with139 `npm run draft` and then fix the facts by hand.1405. Show the narration and wait for approval.1416. `npm run generate -- --slug <slug>`.1427. Preview with `npm run studio`, then render.1438. Run the shipping checklist in `rules/pitfalls.md`.1449. Report measured numbers: scene count, duration, image count, output path and145 size, plus any assumption made about an ambiguous fact.146147## Commands148149```bash150npm install # once151npm run story -- --slug big-bull --context ./article.md --seconds 60 [--render]152npm run draft -- --slug big-bull --context ./article.md --orientation vertical153npm run generate -- --slug big-bull [--only voice|images] [--force] [--logs]154npm run studio155npm run typecheck156npx remotion render BigBull out/big-bull.mp4 --codec h264157```158159`--context` accepts a file path or the text itself. Any folder under `public/`160containing a `story.json` becomes a composition automatically, named after the161slug in PascalCase, so nothing is registered by hand.162163## Project Layout164165```166.env keys and every model id167cli/168 env.ts loads .env from here and from the parent folder169 service.ts Sarvam, FLUX 2 text to image, FLUX 2 edit, OpenAI170 draft.ts context -> story.json171 generate.ts story.json -> assets -> measured durations172 story.ts draft, generate and optionally render in one go173src/174 Root.tsx story discovery, calculateMetadata175 StoryVideo.tsx scene timeline, audio, outro176 components/ ImageLayer, Subtitle, StatCard, TitleCard, OutroCard177 lib/schema.ts zod schema shared by the CLI and the composition178 lib/layout.ts safe areas and type scale, both orientations179 lib/fonts.ts module level font loading180public/<slug>/ story.json, voice-N.wav, image-N.jpg181out/ rendered mp4182```183184## Worked Example185186`public/jhunjhunwala/story.json` is a complete 6 scene, 58.7 second landscape187story: cartoon art, two reference photos restyled through `flux-2-pro/edit`,188stat cards on four scenes, and a silent outro. Read it before writing a new one.189Its media files are gitignored, so regenerate them with190`npm run generate -- --slug jhunjhunwala`.191192## Example Usage193194`/story-telling turn this article into a sixty second explainer`195`/story-new big-bull ./article.md 60 landscape`196`/story-render big-bull images`