Install
openclaw skills install @ofoxai/explainerRequires OFOX_API_KEY — create one at https://app.ofox.ai. Turn an article, doc or release note into a short explainer clip — one person to camera, or a voiceover over illustrative footage. The user supplies the source text and the model generates the speech; audio cannot be uploaded, measured. A 30-second clip holds about eighty spoken words in eight sentences — measured, and well under a tenth of a 1,200-word post — so this skill does not summarise an article, it picks the single idea worth saying and helps choose which one. Use when a user asks to turn writing into a short spoken video, e.g. "make a 30-second explainer from this blog post", "explain this feature in a short video", "turn our changelog into a clip", "a quick video explaining what this paper found". Do not use for a scene between people (see seedance-short-drama), a brand or product ad (see seedance-ad-creative), a handheld creator clip (see ugc-ads), or when the user already has both a portrait and the finished words (see talking-head). Budget sentences as well as words — each sentence boundary costs about 0.7 seconds of silence, so a script with more sentences runs longer at the same word count.
openclaw skills install @ofoxai/explainerTakes something written — an article, a README, a release note, a paper — and produces a short clip of it being said out loud. The user supplies the source; this skill picks the one idea that fits, writes it as spoken words, and sends it to be generated.
This skill is a thin, scenario-specific layer over
ofox-video-core. It owns the idea selection,
the explainer prompt craft, the brief, the defaults and the pre-generation
cost estimate; ofox-video-core owns talking to the Ofox API correctly and
safely (the OFOX_API_KEY handling, the no-resubmit rule, error-code mapping,
download/verification, and reporting the downloaded file's absolute
VIDEO_PATH). Read that skill's safety contract before using this one —
it is not restated here.
Shared prose is linked rather than copied: the prompt formula, camera and
delivery vocabulary, the word-rate tiers and the negative-list items are in
../ofox-video-core/references/prompt-structure.md;
the pre-prompt question rules in
../ofox-video-core/references/creative-brief.md;
the spend rule in
../ofox-video-core/references/approval-gate.md.
People ask for "a 30-second video of this article". The arithmetic says no, and it says no by a wide enough margin that the honest move is to say it in the first reply rather than after the bill.
🚨 This section used to be headed "a thirty-second clip holds about ninety spoken words". A 30-second run disproved it. The old number came from multiplying a 20-second clip's rate by 30, and the thing that multiplication misses is that every sentence boundary costs about 0.7 seconds of silence. A script with more sentences takes longer at the same word count.
The two word-rate tiers and the gallery cases behind them live in the shared
file —
prompt-structure.md →
"Dialogue and sound" → "Density — two tiers, not one" — and are not restated
here; what this skill has measured on this API is below. The tier that
applies to an explainer is monologue / talking head — one person speaking
continuously. The other tier, dialogue drama at 0.4–1.7 words/s, measures a
different shape: two people, with silence between their lines. It is a floor
here rather than a budget.
The budget formula, from the 30-second run:
speech span (seconds) ≈ words / 3.56 + 0.7 x (sentences − 1)
3.56 words a second is the measured rate while actually speaking; the second term is the pauses between sentences. Checked against its own run: 80 words in 8 sentences predicts 27.4s, and the delivered speech span was 28.13s. Then leave room at both ends — that clip started speaking at 0.567s and finished 1.39s before the last frame.
| Clip | A safe script | Which is | Share of a 1,200-word article |
|---|---|---|---|
| 10s | ~25 words, 2–3 sentences | one thought | 2% |
| 15s | ~40 words, 3–4 sentences | a short paragraph | 3.3% |
| 20s | ~55 words, 5 sentences | a paragraph | 4.6% |
| 30s | ~80 words, 8 sentences — measured, fits with 1.39s to spare | a claim, its reason, and what to do about it | 6.7% |
Only the 30-second row is measured. The others apply the same formula with the same headroom and have not been run.
Ninety words in thirty seconds does not fit. By the formula, 90 words
across 8 sentences needs 90/3.56 + 4.9 = 30.2s of speech span — longer than
the clip, before any opening or closing beat. The failure mode is the
expensive one: overrunning comes back rushed, garbled or cut off, which costs
the whole clip, while under-filling costs a pause at the end. Round down.
Count the sentences, not just the words. Two 80-word scripts can differ by several seconds — short punchy sentences are slower on this model than the same words in fewer, longer ones. That is the opposite of how most people estimate, and it is the practical half of this section: a script that fails the budget can often be fixed by joining sentences rather than cutting words.
What kind of evidence this is. Both tiers started as counts of gallery prompt text against clip length: real prompts and published clips, but unrecorded platforms and parameters, and no measurement of what this API delivers. That is still what the 3.5 / 5 ceiling is, and the CJK figures are still exactly that and nothing more. The English budget is now this model's own, measured twice:
| Run | Script | Speech span | Overall rate | Rate while speaking |
|---|---|---|---|---|
edef379e (20s) | 59 words | 19.33s | 3.05 w/s | not separated |
42d8b5c3 (30s) | 80 words, 8 sentences | 28.13s | 2.84 w/s | 3.56 w/s |
The two overall rates differ, and the second run explains why. Splitting speech from silence showed 22.49 seconds of actual speaking and 5.64 seconds of internal silence — 7 pauses, matching the script's 7 sentence boundaries exactly, about 0.7s each. So the overall rate is not a property of the model; it falls out of how many sentences the script has. A denser-sentence script scores a lower "words per second" while speaking at the same speed.
That is why the budget above is a formula rather than a rate, and why the old 90-word row could not survive: it multiplied a 20-second observation by 1.5 and carried no term for the pauses.
Until that run this was an extrapolation across tiers, and that is a mistake
this repo has already made once. Every word rate measured here before today
came from two-person dialogue, and handing the dialogue band to a continuous
speaker is a category error rather than a conservative estimate — the dialogue
tier's low floor is not somebody talking slowly, it is words spread across a
clip where most seconds have nobody speaking at all. talking-head shipped
that error and had to correct it. The monologue tier now has measurements of
its own instead.
A third observation, on another model, and that is still not a law.
talking-head's first paid run measured 3.16 words a second overall on
alibaba/wan-3.0-prime (2026-09-16). It is close to this skill's two, which
is the reassuring direction — but its speech and silence were never separated,
so it cannot be compared against the 3.56 figure, and the sentence-boundary
term has been measured on one run only. Nothing has been measured in
another language or at another resolution. Two data points on one model,
agreeing about why they differ, is exactly as much as this file claims.
⚠️ Thirty seconds is now measured; the rest of the table is not. The 30-second row comes from a real 30-second clip. The 10, 15 and 20-second rows apply the same formula with the same headroom and have not been run at those lengths — the closest evidence is the 20-second clip, whose script was written against the older, looser budget and still fitted.
Eighty words is not a summary of a 1,200-word article — it is well under a tenth of it. It is a claim, its consequence, and one concrete detail. That is not a degraded explainer — it is what a short explainer has always been — but it has to be said out loud before a user pays for something they thought was a summary:
A 30-second clip holds about eighty spoken words — roughly one paragraph and under a tenth of your article, and that is measured rather than estimated. It will not summarise the piece. What it can do well is land one idea from it — so the useful question is which idea, and the next section is how to pick.
Never quietly compress the article into eighty words instead. A summary squeezed to that length becomes a string of abstractions that means nothing to someone who has not read the source — the worst of both products. One concrete idea, fully said, beats five ideas gestured at.
If the user genuinely needs the whole article covered, the honest answers are: a series of clips (priced below, and it has a real continuity problem), a narrated slide deck, or written text. Say which you think fits before quoting.
This is the work. Everything after it is mechanics.
Read the source and list its candidate ideas — usually three to five. A candidate is a claim, not a section heading: "the migration is automatic" is a candidate; "Migration" is not.
Write each one as the sentence that would actually be spoken. Not a label, the words. This is the step that kills most candidates: an idea that cannot be said in one or two plain sentences will not survive the clip either.
Score each against four tests, all four of which have to pass:
| Test | Fails when |
|---|---|
| Stands alone — does it make sense to someone who has not read the article? | It depends on a definition, a number, or a previous paragraph |
| Concrete — is there a specific thing, number or action in it? | It is a category ("improved performance", "better developer experience") |
| Fits the budget — does the spoken form come in under the clip's word count? | It needs a subordinate clause to be true |
| Worth 30 seconds of someone's attention — is it the thing the reader should remember or do? | It is true and nobody's decision changes because of it |
Offer the two or three survivors to the user, as the actual sentences, and let them choose. This is a must-ask axis — it is their article, and no model can tell which idea they meant. If they say "you pick", pick the most concrete one, name it in the recap, and let the approval gate be the check.
Not in the introduction. In practice the sentence worth saying is:
A title is usually a bad candidate: it was written to be clicked, so it is vague on purpose, and vague is what one paragraph of speech cannot afford.
Source: a post announcing a database client release. Candidates, each written as it would be spoken, with the verdict:
| Candidate | Spoken form | Verdict |
|---|---|---|
| The release is out | "Version 4 of the client is out today, with a lot of improvements." | Fails concrete and worth-it. Nobody's decision changes |
| Connection pooling was rewritten | "We rewrote connection pooling on top of a new scheduler with backpressure." | Fails stands-alone. Means nothing without the article |
| Reconnects no longer drop queries | "Version 4 stops dropping queries when the connection blips. If you wrapped every query in a retry, you can delete that now." — 22 words | Passes all four. Concrete, a reader acts on it |
| Old versions stop getting fixes in March | "Version 2 stops getting security fixes in March, so upgrading is now a date rather than a preference." — 18 words | Passes all four. A number, a decision |
Two survivors go to the user. The recap names which one they picked and says the clip carries that one and not the post.
This skill has two paid runs of its own, and the second one overturned the
number the whole file used to be built on. Both bytedance/seedance-2.5 on
byteplus, 480p, text-to-video with nothing attached, --generate-audio left
at the server default so the speech came back on the track, and both read
afterwards — audio measured and transcribed, frames extracted — rather than
called done at STATUS completed.
| Run | Shape | Cost |
|---|---|---|
edef379e-9ac0-41c9-ab59-6378d030239e (2026-09-16), seed 623333235 | 20s, presenter-to-camera, 59 words | $2.20 |
42d8b5c3-31f4-41d2-90eb-b07a3181b465 (2026-09-17), seed 1021654967 | 30s, 16:9, the Voiceover template, 80 words in 8 sentences | $3.30 |
| Question | Measured | How |
|---|---|---|
| Speech rate | 3.05 words a second overall | 59 words across a 19.33s speech span. silencedetect at -30dB puts the first word at 0.399s and the last at 19.729s, with 7 internal pauses |
| Truncation | None | A whisper tiny.en transcription returns all 59 words, in order, with nothing added or dropped |
| The closing beat | It fitted | 0.335s of silence between the last word and the last frame. The template asks for that beat and the clip had room for it — under-filling by a hair is what bought it |
| Whether a 30-second clip behaves the same | ❌ It does not | This row used to say "not measured" and carried the 90-word extrapolation. See the second run |
⚠️ One measurement here was nearly reported backwards, which is worth
knowing before repeating it. A first pass with silencedetect at a 0.35s
minimum found no trailing silence, and the tempting reading was "a script at 3
words a second squeezes the closing beat out". Wrong: the beat is plainly
there in the frames — mouth closed, expression held — and the pause is 0.335s,
sitting just under the threshold that was looking for it. A tool not reporting
something is not the thing not happening. Check that the threshold can see the
size of the thing you are asking about before concluding from its silence.
What that run does not establish: one run, one script, English, 480p, 20 seconds, presenter-to-camera. A faster or slower script, another language and another resolution are all still unmeasured here.
Job 42d8b5c3, 30 seconds, the Voiceover template (no visible
speaker), 80 words in 8 sentences, $3.30. It closed this skill's two largest
gaps at once: the duration it is named for, and the shape it had never
generated.
| Question | Measured | How |
|---|---|---|
| 🚨 Does "about ninety words" hold at 30 seconds? | ❌ No | Speech span 0.567s → 28.695s = 28.13s for 80 words. Split into speech and silence: 22.49s speaking, 5.64s of internal silence in 7 pauses — exactly the script's 7 sentence boundaries, about 0.7s each. Speaking rate 3.56 w/s; overall 2.84 w/s. By that model 90 words in 8 sentences needs 30.2s of span, which does not fit in a 30s clip |
| The budget formula | span ≈ words/3.56 + 0.7 x (sentences − 1) | Predicts 27.4s for this script; delivered 28.13s |
| Truncation | None | 1.39s of tail left over. The script fitted with room |
| The voiceover shape itself | ✅ It works | No person anywhere in 30 seconds; a slow continuous push with no cut; no on-screen text at all; and the ending holds — the frames at 22s and 29.5s are nearly identical, on a recessed button, exactly as the template asks |
| Whether the 20s and 30s rates conflict | They don't | 3.05 and 2.84 are the overall rates of scripts with different sentence densities. The underlying speaking rate is the thing that is stable, and only the 30s run separated it |
What this run does not establish: one run, one script, English, 480p, 30 seconds, voiceover. The 0.7s sentence-boundary cost is measured once, on one script's 7 boundaries — it is the most load-bearing number in this file and the least replicated. A faster or slower script, another language, another resolution, and any duration past 30 seconds remain unmeasured.
The rest of what this file rests on:
| Claim | Strength |
|---|---|
| The English budget formula — 3.56 words a second while speaking, plus ~0.7s per sentence boundary | Measured on this model, once, at 30 seconds (42d8b5c3). The 20-second run agrees on the overall figure it can supply (3.05 w/s across a script with 7 pauses of its own) but never separated speech from silence, so it corroborates rather than replicates. talking-head's 3.16 w/s on alibaba/wan-3.0-prime is a third overall rate on a different model, also unseparated |
| A supplied audio track does not become the clip's audio | Measured, job d8561509-dcc6-4f2c-8864-a193cd239b14, 55 cents, 2026-09-15 — the input was near-continuous speech, the delivered track was sparse, correlation 0.41. The model generates its own voice |
| The model renders a photoreal person speaking, from text alone | Measured, six bytedance/seedance-2.5 text-to-video jobs of 20–30 seconds built entirely around photoreal people, all completed, five of them carrying spoken lines. On five of the six only the picture and the timing were checked; the sixth is this file's own run above, where the audio was transcribed against the script |
A photoreal person in an attached image is refused at submission on bytedance/seedance-2.5 | Measured 2026-08-30, input_moderation_failed, nothing billed. It decides the continuity limits below |
| Asking this model for music can fail output moderation on copyright | Measured once, unbilled |
| Text rendered from a description comes back invented or garbled; text approved on a still and attached as a frame is preserved | Measured, several jobs. It decides the on-screen-text section below |
| The ceiling the budget sits under — the monologue tier at 3.5 words/s English and 5 characters/s Chinese — and the CJK budget of 4 characters/s | Gallery practice — prompt text counted against clip length across a public corpus, not Ofox runs. It is a Seedance 2.5 collection and this skill's default is Seedance 2.5, so the model at least matches; but for the community entries the platform that produced the clip is unrecorded. Nothing here has been run in Chinese or Japanese, so the character-per-second rows are exactly as strong as they were |
| A voiceover with no visible speaker | Measured, job 42d8b5c3 (2026-09-17, 30s, $3.30): no person in any frame, a slow continuous push with no cut, no on-screen text, and the ending held on its final subject. One run, English, 480p, 16:9. It was "gallery practice only, never run here" until then |
Practical consequence: both shapes this skill offers have now been run, and both at the durations the file leads with. What is risky about a first clip now is the script, not the format — specifically its sentence count, which the old budget ignored entirely and which costs about 0.7 seconds a boundary. A non-English script and any duration past 30 seconds are still unmeasured. Draft short and cheap, read the frames, listen once, then price the deliverable.
Resolve once, before the first call:
for d in ../ofox-video-core \
../ofoxai-skills-ofox-video-core \
~/.agents/skills/ofox-video-core \
~/.agents/skills/ofoxai-skills-ofox-video-core \
~/.claude/skills/ofox-video-core; do
[ -f "$d/references/ofox-video.sh" ] && echo "$d" && break
done
Examples below are written as ../ofox-video-core/... (the skills.sh /
ClawHub / npx ofox-skills layout, where a skill's directory is named after
the skill). If the probe found a different directory — LobeHub unpacks each
skill as ofoxai-skills-<name>, so the sibling there is
ofoxai-skills-ofox-video-core — substitute it, in the ofox-video.sh
commands and in the references/*.md links alike.
That ../ is relative to this skill's own directory, which is also where
the probe has to run. From anywhere else nothing resolves — use the absolute
path the probe printed (candidates 3–5 are absolute already), or, in a clone
of this repo, skills/ofox-video-core/references/ofox-video.sh from the repo
root.
Nothing found → the core skill isn't installed; see "If the script isn't found".
Run this once per session (not on every request):
bash ../ofox-video-core/references/ofox-video.sh check
If it fails, follow ofox-video-core's guidance (install curl/jq, or get
an OFOX_API_KEY at https://app.ofox.ai) — don't dead-end the conversation,
and don't re-run this check on every subsequent request once it has passed.
check reports whether the key is present, not whether it is valid, and
makes no network call. A failing check is not a stop sign: the idea
selection, the script and the price can all be settled without a key — see
"Pricing a job with no API key".
| Presenter to camera (recommended) | Voiceover over footage | |
|---|---|---|
| What is on screen | one person, chest-up, speaking | the thing being explained; the speaker is never seen |
| Evidence | six completed Ofox jobs built around photoreal people, text-to-video — one of them this skill's own 20s run, whose speech was transcribed against the script | one paid run, job 42d8b5c3 (2026-09-17, 30s, $3.30): no person in any frame, a slow continuous push with no cut, no invented lettering, and the ending held on its final subject |
| Best for | an opinion, an announcement, anything whose credibility comes from a person saying it | a product, an interface, a process, a physical object |
| Watch out for | the presenter is generated fresh every job and cannot be reused | a voice with nothing on screen to anchor it reads as stock footage with narration. Whether the model produces a disembodied narrator is no longer the open question — it did, for 30 seconds, once. Whether it does so reliably is |
| A real presenter's photo | not this skill — that is talking-head, and it needs a different model | — |
Default to presenter-to-camera unless the subject is visual. When the subject is an interface or a physical object, say what the voiceover shape costs in certainty before choosing it.
The shared rules — the three tiers, one round of at most four questions, the
shape of a question, the "Let the AI decide" discipline, the order with the
approval gate, the fallback without AskUserQuestion — are in
../ofox-video-core/references/creative-brief.md.
This section adds only this scenario's question set.
| Tier | Explainer axes |
|---|---|
| must-ask | which idea from the source the clip carries; whether the source is really there (a link nobody can open is not a source) |
| ask-if-open | presenter or voiceover, aspect ratio, register |
| never-ask | resolution, model, provider, audio on/off, the spoken language (it follows the source), duration once the word count has fixed it |
| # | Tier | header | Question | Options — first is recommended; "Let the AI decide" comes last where it appears, and never on a must-ask row | Ask when |
|---|---|---|---|---|---|
| 1 | must-ask | Idea | A clip this long holds about <N> spoken words in about <S> sentences, so it carries one idea rather than the article. Which one? (get <N> and <S> from the budget formula, not from memory — sentences cost about 0.7s each) | The two or three survivors of "Picking the idea", each shown as the sentence that would be spoken, with its word count; recommended = the most concrete. No "Let the AI decide" — it is their article. If they answer "you pick" in free text, take the most concrete, name it in the recap, and let the gate be the check | Always, unless the user already gave one sentence and asked for exactly that |
| 2 | ask-if-open | Shape | Who is on screen? | A presenter, to camera (recommended) — the shape this repo has actually run / Voiceover over footage of the thing — no speaker visible; better for an interface or an object, and untested here / Let the AI decide | The subject could go either way and the request doesn't say |
| 3 | ask-if-open | Aspect | Where will it be watched? | 9:16 vertical (recommended) — feeds / 16:9 landscape — docs, a site, YouTube / 1:1 / Let the AI decide | No platform word and no ratio in the input |
| 4 | ask-if-open | Register | How should it sound? | Plain and direct (recommended) — a colleague telling you something useful / Warm and enthusiastic — a launch / Careful and precise — research, security, anything where overclaiming is the failure / Let the AI decide | The source's own register is ambiguous and the user gave no direction |
If more than four are open, ask in this order: Idea, Shape, Aspect,
Register. Duration is not a question — it falls out of the chosen idea's word
count; it is a line in the recap and a row in the cost table.
On top of the generic rows in creative-brief.md:
| Input says… | Axis | Value |
|---|---|---|
| the language the source is written in | spoken language | that language, never asked — unless the user asks for a translation, which is then their words to approve |
| the user quotes one sentence and says "this, as a video" | Idea | settled; do not re-open it |
| "explain the new export feature" on a doc covering six features | Idea | narrow to that feature, then still pick one idea within it |
| "for LinkedIn", "for the docs site", "for the README" | Aspect | 16:9 |
| "for TikTok", "Reels", "Shorts" | Aspect | 9:16 |
| "show the app while I explain" | Shape | voiceover over footage — measured 2026-09-17 at 30s (job 42d8b5c3): no person, no cut, no invented lettering, and the ending held. One run, so still price a first one as a draft |
| "use my photo", "have me say it" | — | that is talking-head |
| "put the bullet points on screen" | — | not available from a description; see "On-screen text" |
| Answer | Where it goes |
|---|---|
| Idea | the quoted LINE, and the word count that sets --duration |
| Shape | whether the prompt has a SPEAKER block or a SUBJECT block, and whether the voice is on camera or over |
| Aspect | --aspect-ratio |
| Register | the DELIVERY note and the micro-beats |
Brief
- Source: the v4 release post you pasted (1,100 words)
- Idea: "Version 4 stops dropping queries when the connection blips. If you wrapped
every query in a retry, you can delete that now." — 22 words (you chose it from two)
- What this clip is NOT: a summary of the post. 9 seconds holds roughly 22 words
in 2 sentences, so it carries this one idea and nothing else
- Shape: a presenter to camera (AI's pick)
- Duration: 9s — 22 words in 2 sentences is 22/3.56 + 0.7 = about 6.9s of speech,
leaving room to open and to close
- Register: plain and direct (AI's pick)
- 9:16, 480p draft, audio on
- Note: the budget is measured on this model at 30 seconds (job `42d8b5c3`) — each
sentence boundary costs about 0.7s, so a script with more sentences runs longer
at the same word count. The delivery is still a roll, so treat the draft as the
take you listen to
Then the full prompt, then the cost table approval-gate.md specifies, all in
one message.
An explainer wants a title, three bullets and a logo. This API does not deliver them, and the reason is measured rather than stylistic: text rendered from a description comes back invented or garbled, repeatedly, across several jobs in this repo — while text approved on a still image and then attached as a frame is preserved, which is a different task.
So:
AVOID
(subtitles, captions, on-screen text, watermarks, logos) and keep lettered
surfaces out of the set — the shared file's "Unwanted text is designed out
of the set, not forbidden in the list" has the measurement, including that a
negative list alone fails on a set full of signage.--frame-first-image. The clip then opens on exactly that card. Attaching a
frame makes the output follow the image's shape (the script prints a
NOTE: about adaptive), so crop the card to the delivery ratio first —
crop, never pad. ofox-image-core's --target-aspect W:H does that and
measures the real file.If the user wants a three-part explainer with the same presenter, say this first: a photoreal presenter does not survive between jobs.
generate is stateless; a person described in text is generated fresh
every time.bytedance/seedance-2.5 when that frame holds
a photoreal person (input_moderation_failed, measured, nothing billed).What that leaves:
| Want | Available |
|---|---|
| Three clips, same real person | not this skill. A portrait re-attached to each job is talking-head's route, and its own continuity is unmeasured |
| Three clips, same illustrated presenter | seedance-anime-drama — a non-photoreal character frame is accepted, which is exactly why that skill works |
| Three clips, no presenter | voiceover shape, with a consistent SCENE and STYLE block repeated word for word. Palette and setting carry; nothing else is guaranteed |
| Three clips, three different presenters | fine, and often the right answer — three ideas, three people, cut together |
Each part is a separately billed job, and the cost table gets a row per part
plus a total, per approval-gate.md → "Batches get an itemised table, not one
total". Splitting is not a discount.
Vocabulary is not repeated here — delivery notes, camera and focus terms and
the negative-list rows are in
../ofox-video-core/references/prompt-structure.md.
An explainer is a single shot with performance beats inside it, so the shared
file's "Short prompts (10 seconds or less)" shape applies even above 10
seconds: no shot manifest, no cut list.
Slots in <angle brackets>; optional lines in [square brackets].
One continuous shot, <T> seconds, fixed camera, no cuts.
SPEAKER: <age range, build, hair, top with colour and material>, <one bearing word — relaxed, precise>. <No name; this person exists for one clip.>
SCENE: <a plain setting that does not compete: a home study, a quiet office, a plain wall>. <One light source and its direction.> <Nothing in the background carrying letters.>
FRAMING: chest-up medium close-up, <fixed camera | a faint breathing handheld>, background softly out of focus; real mirrorless texture, slight sensor noise, real skin texture, no smoothing.
<Speaker> faces the camera and says, <delivery: plain and direct, unhurried | warm | careful and precise>: "<the chosen idea, verbatim, in the language to be spoken>"
0–<a>s: <eye contact, one natural blink>. <a>–<b>s: on "<the key word>", <one gesture>. <b>–<c>s: <stillness>. <c>–<T>s: after "<last word>", <the ending: lips close, half-second pause, the smallest nod>.
SOUND: <room tone>, no music. Speech in <language>, mouth shape matched to it.
CONSISTENCY: face, hair, <clothing items>, background and light direction identical from the first frame to the last.
AVOID: subtitles, captions, on-screen text, watermarks, logos, diagrams, charts; a second person, a cutaway, an interview setup; music, score, soundtrack, instrumental, humming, singing; presenter cadence, theatrical over-acting, wild gesturing; skin smoothing, beauty filter, plastic skin, CGI look; camera movement, zooms, cuts.
One continuous shot, <T> seconds, <slow push | slow drift>, no cuts. No person on screen at any point.
SUBJECT: <what is being explained, described the way a camera sees it: a laptop on a desk with a dashboard open, a hand-held device on a bench, a machine in a workshop>.
SCENE: <where it sits, one light source and its direction>. <Nothing in frame carrying letters.>
A single voice, off-screen, says, <delivery>: "<the chosen idea, verbatim, in the language to be spoken>" — no speaker is ever visible.
0–<a>s: <what the camera is looking at>. <a>–<b>s: <what changes, tied to the words being said>. <b>–<T>s: <the close>.
SOUND: <room tone>, <one or two sounds tied to what is visible>, no music. Narration in <language>.
AVOID: subtitles, captions, on-screen text, watermarks, logos; a presenter, a face, hands entering frame, an interview setup; music, score, soundtrack, instrumental, humming, singing; cuts, zooms, whip pans; CGI look, plastic surfaces.
Five notes on both shapes:
output_moderation_failed on audio copyright in this repo,
unbilled. Name the music words in AVOID as well as writing no music —
that is the pair of blocks the two clean runs used. A track goes on in an
editor.--generate-audio stays at the server default (true). The speech is
the deliverable.This has not been generated as written. It is the template filled in, carrying the release-post idea chosen above.
One continuous shot, 9 seconds, fixed camera, no cuts.
SPEAKER: a man in his 30s, average build, short dark hair, a plain charcoal crew-neck sweater; relaxed, precise.
SCENE: a quiet home office; a plain pale wall behind him with nothing on it. The only light is a window to the front-left, soft and slightly cool, falling off across the far side of his face.
FRAMING: chest-up medium close-up, fixed camera, background softly out of focus; real mirrorless texture, slight sensor noise, real skin texture, no smoothing.
He faces the camera and says, plain and direct, unhurried: "Version 4 stops dropping queries when the connection blips. If you wrapped every query in a retry, you can delete that now."
0-2s: he looks straight into the lens, one natural blink. 2-5s: on "dropping queries" a single small open-palm beat, low in frame, and the hand leaves again. 5-8s: stillness; the eyebrows lift slightly on "retry". 8-9s: after "now" the lips close, one slow blink, the smallest nod, and the shot holds.
SOUND: quiet room tone, a little distant traffic. No music. Speech in English, mouth shape matched to it.
CONSISTENCY: face, short dark hair, charcoal crew-neck sweater, the pale wall and the light direction identical from the first frame to the last.
AVOID: subtitles, captions, on-screen text, watermarks, logos, diagrams, charts; a second person, a cutaway, an interview setup; music, score, soundtrack, instrumental, humming, singing; presenter cadence, theatrical over-acting, wild gesturing; skin smoothing, beauty filter, plastic skin, CGI look; camera movement, zooms, cuts.
The duration was derived from the script rather than the other way round: 22 words at 3.56 a second is 6.2 seconds of speaking, plus one sentence boundary at 0.7s, giving a 6.9-second span — then room to open and to close, rounded up to 9. Note that the sentence count is in that arithmetic. Break the same 22 words into four short sentences and the span grows by about 1.4 seconds, which is most of the headroom. If a script is running long, joining sentences buys back time that cutting words does not.
It is also why a 22-word idea gets a 9-second clip and not a 15-second one with six seconds of nothing in it: on a per-second bill, the script decides what you pay for.
| Parameter | Default | Why |
|---|---|---|
--model | the script's own default, bytedance/seedance-2.5 — unless the user named one, which always wins | no portrait is attached here, so nothing forces a different model, and this repo's spoken-dialogue runs are all on it. See "Choosing a model" |
--duration | derived from the chosen idea with words/3.56 + 0.7 x (sentences − 1), plus room to open and close, then clamped to the model's range — which ofox-video.sh models prints. For Chinese or Japanese the only figure available is still 4 characters a second | the script decides the length; a default that ignores it produces rushed speech. The English formula is measured on this model at 30 seconds (job 42d8b5c3): 3.56 w/s while speaking and ~0.7s at each of the script's 7 sentence boundaries, predicting 27.4s against a delivered 28.13s. ⚠️ Count sentences as well as words — that term is what the old "3 words a second" flat rate left out, and it is why "about ninety words in thirty seconds" did not fit. The CJK figure is still gallery-derived and untested |
--resolution | draft at the model's cheapest tier, deliver one tier up | a face at the cheapest tier is where lip and eye detail goes first, so read the draft's frames rather than shipping it |
--aspect-ratio | 9:16 unless the brief or the platform said otherwise; not passed if a title card is attached as a frame | short explainers are watched in feeds |
--generate-audio | leave at the server default (true) | the speech is the deliverable |
--seed | let the script roll one and keep it | printed as SEED and written to the .json sidecar. It does not reproduce a take — measured, an identical request on a fixed seed came back a visibly different clip — so a re-render is another roll aimed at the same shot. Say that before the user pays for one |
--frame-first-image | unset, unless a prepared title card or screenshot is the opening frame | the only route to correct lettering; it also fixes the clip's shape to the image's |
--real-person | leave unset | nothing in this skill needs it — the presenter is written in text, and text-generated people are not what the refusal is about. true is Ofox's privacy-preserving preprocessing path for authorised real-person reference images, measured lifting seedance-2.5's refusal on 2026-09-16; it is an authorisation route, never a way past the check, and a skill whose presenter comes from a photo is talking-head, not this one. See api-params.md → "--real-person true lifts that refusal on 2.5" |
The model stays never-ask — the agent doesn't raise it. But never-ask is not "never listen": if the user names a model id or a shorthand, use it.
Model ids, prices, resolutions, durations and aspect ratios are deliberately not tabulated in this file. They are catalog facts, they change, and this repo has recorded defects that trace to a hardcoded copy of somebody else's value table. Read them live, free, with no API key:
bash ../ofox-video-core/references/ofox-video.sh models # every model, its tiers and ranges
bash ../ofox-video-core/references/ofox-video.sh providers MODEL # that model's per-resolution rates
providers with no model argument prints the flagship's matrix, not the
catalog — pass the id you actually mean. The rate models shows is the one at
each model's own default resolution, which differs between models, so it
ranks rather than quotes. The number you put in front of a user comes from
generate --dry-run at the parameters you are about to send.
Two things worth saying to a user who is choosing: a cheaper model is a different look, not just a smaller bill — and it is faces that lose most. And moderation policy is per-model, so a prompt refused on one model can be accepted on another.
Never submit a paid job until the user has seen a cost table and said yes.
The rule, the required columns and where the numbers must come from are
written down once for every Ofox skill in this repo:
../ofox-video-core/references/approval-gate.md.
Get the numbers from --dry-run, which validates everything and prints the
estimate without sending a request:
bash ../ofox-video-core/references/ofox-video.sh generate --dry-run \
--prompt "..." --duration 9 --resolution 480p --aspect-ratio 9:16 \
--out-dir /absolute/path/to/out
Relay the Estimated cost: line it prints — never a number of your own — then
wait for a yes, then re-run the identical command with --dry-run removed.
The estimate a real run prints comes microseconds before the request goes
out, too late to relay. Pass the same --out-dir to both.
Three things belong in that message beyond the table:
This skill has exactly one cost anchor, and it anchors one point rather
than a curve: 20 seconds at 480p on bytedance/seedance-2.5, text-to-video,
billed 2 dollars 20 (job edef379e). Another duration or resolution is a
different number, so the --dry-run figure at the parameters you are about to
send is still the only one to put in front of anyone.
Afterwards the actual bill is VIDEO_COST from the finished job. Report
it as money, not as the raw ten-decimal string.
Delivery is a roll, and this genre is judged on whether the person sounds like
they mean it. batch prices the whole set up front, waits concurrently, and
tiles a contact sheet:
bash ../ofox-video-core/references/ofox-video.sh batch --dry-run \
--prompt "..." --takes 3 --duration 9 --resolution 480p \
--aspect-ratio 9:16 --out-dir /absolute/path/to/out
Quote BATCH_COST_TOTAL, not BATCH_COST_PER_TAKE, and give the takes a row
each. Hand over the CONTACT_SHEET path on its own line, then the take paths
beneath it. A contact sheet cannot tell you how a take sounds — it is
frames. Listen to one before promoting it.
If the user wants several cheap vertical drafts as the deliverable rather than
one clip, shorts-reels owns that ladder. The two
compose: pick the idea and write the prompt here, run the set there.
models, providers and generate --dry-run all work with OFOX_API_KEY
unset. So when a user hasn't signed up yet, quote the job first and let them
decide whether it's worth registering — don't open by sending them to a
signup form.
bash: ../ofox-video-core/references/ofox-video.sh: No such file or directory
Nothing is broken — this skill delegates all execution to ofox-video-core
and reaches it by relative path, and that path just missed. Two different
situations wear this message, so run the probe in "Where the core skill lives"
before deciding which:
ofoxai-skills-ofox-video-core). Re-run against what the probe printed.ofox-video-core really is absent, and
installing it is the user's call to make, not yours: an install writes
outside this working directory, so hand over the command and let them run
it rather than running it for them. Which command depends on the installer
they already have — skills.sh is
npx skills add ofoxai/skills --skill ofox-video-core, which asks for that
one skill and answers none of the agent, scope or confirmation questions on
the user's behalf; this repo's own wrapper is
npx ofox-skills ofox-video-core, the same install with all three answered
in advance (every agent, user-level, no prompts); on LobeHub or ClawHub,
install ofox-video-core from the same publisher. Ask for the one skill
that is missing rather than the whole repo, and give all three routes —
pointing a LobeHub user at the skills.sh line alone reads as "abandon your
installer", which isn't the advice.Either way, name the missing skill and where it was expected rather than relaying the raw path error, which names neither.
A broken link to a shared reference has the same two causes. This skill degrades gracefully: the selection procedure, the question set, the templates, the word budget and the defaults are all written out here. What is out of reach is the detail behind them — the full delivery and camera vocabulary, the gallery cases behind the word rates, and the exact wording of the spend gate, which stays mandatory either way.
Full table in ../ofox-video-core/SKILL.md.
The ones that come up:
| Code | Meaning | What to do |
|---|---|---|
1 | Parameter rejected locally, no network call, nothing billed | Fix the flag and retry freely |
2 | Environment problem — curl/jq missing, or no OFOX_API_KEY | Ask the user to fix it; check reports the same |
3 | API rejected it, or the job ended failed/cancelled/expired | Read the mapped message; a rejected create was not billed |
4 | Timed out waiting — the job is still running and billable | poll JOB_ID, never re-run generate |
5 | Ambiguous network failure on create | Do not retry blindly; check https://app.ofox.ai first |
6 | --out-dir unusable | Fix the path; if it happened after a create, poll JOB_ID |
generate blocks while it polls, up to --max-wait (default 540s). A short
low-resolution clip is usually one to three minutes. Say so before starting,
so the wait isn't silent.
If your tool call can't stay open that long, use create (submits and returns
a job id in seconds) followed by poll. That way a timeout can never strand a
job whose id you never saw. For batch, the worst case is takes x max-wait.
Always pass --out-dir, and make it an absolute path. Without it the
script writes to the current working directory — which, given that the
examples here run from this skill's own directory, would drop the user's video
inside an installed skill. Relay the absolute VIDEO_PATH the script
prints, on its own line.
Pass --name too, named after the idea rather than left for the script to
guess from the prompt's opening words, which here describe a shot length. The
clip lands as <name>-<short job id>.mp4 with a .json sidecar holding the
full job id, the prompt, the seed and the real cost.
bash ../ofox-video-core/references/ofox-video.sh generate \
--prompt "<the explainer prompt built above>" \
--name "<short idea name, e.g. v4 retry removal>" \
--duration 9 \
--resolution 480p \
--aspect-ratio 9:16 \
--out-dir /absolute/path/to/out
Drop --aspect-ratio if a title card or screenshot is attached as the first
frame — the clip then follows the image's shape.
This one call validates the parameters, submits the job, polls to completion,
downloads the mp4, and prints STATUS, JOB_ID, VIDEO_PATH,
VIDEO_SECONDS, SEED and VIDEO_COST. Report the actual values from
that output — never the estimate, and never a path or cost you didn't see the
script print. Do not re-implement any of the request, poll or download logic
here.
Three things, before calling it done. None of them is STATUS completed.
Report what you checked, not just that it finished.
| Symptom | Cause | Fix |
|---|---|---|
| The user expected the article summarised and got one idea | The expectation was never corrected | Correct it before the cost table, in the recap's "What this clip is NOT" line. After the fact, the only remedy is another job |
| The speech is rushed, garbled, or the last words are missing | More script than the clip holds — usually a summary squeezed to fit. ⚠️ Check the sentence count as well as the word count: each boundary costs about 0.7s, so a script that passed a words-only check can still overrun | Fewer words, or the same words in fewer sentences, or a longer clip inside the model's range. Never compress the delivery. New prompt, new cost table |
| The clip ends mid-sentence | Same cause, plus no written ending beat | Re-budget with words/3.56 + 0.7 x (sentences − 1) and write the final beat explicitly. If the word count already looked safe, count the sentences — each boundary costs about 0.7s, and joining two short sentences buys that back without losing content |
| The voice speaks the wrong language | The line was translated on the way into the prompt | Put the source's own words in, untouched. The line's language decides the voice's language |
| Invented captions, a garbled title, a logo nobody asked for | Text rendered from a description is the classic failure, and a negative list alone does not hold on a lettered set | Keep the text items in AVOID and compose lettered surfaces out of the frame. Real captions go on in an editor; a title card has to be a prepared image attached as the first frame |
| A music bed nobody asked for | --generate-audio true and a prompt that didn't exclude music | Keep no music in SOUND and the music words in AVOID |
Exit 3, output_moderation_failed mentioning audio copyright | The prompt asked for music | Rewrite SOUND as room tone only and re-run — a new request, safe immediately, nothing was billed |
Exit 3, input_moderation_failed on create | An attached frame contains a photoreal person — refused at submission on this model, nothing billed | Attach a card, a screenshot or an object instead and write the person in text; or move to talking-head, which runs a different model for exactly this reason |
| Part 2's presenter is a different person from part 1's | Every job generates the presenter fresh, and nothing in this skill's route carries a face between jobs — the words route brings back staging, not a face | See "Continuity across clips". Choose one of the four available shapes rather than re-rolling |
| The presenter gestures like a newsreader | No delivery note, or too many gestures written | One gesture per beat at most, and a plain register in the DELIVERY note. New prompt, new cost table |
| The voiceover clip has a person in it anyway | A subject description that implies a user | No person on screen at any point in the first sentence, and a face and hands in AVOID. Untested shape — draft it cheaply |
Exit 4, timed out waiting | Still running upstream, not failed | poll JOB_ID with the id printed before the timeout; never re-run generate |
Exit 5, ambiguous network failure on create | No HTTP response at all — can't tell whether a job exists | Don't guess or retry; tell the user to check https://app.ofox.ai |
talking-head. The split
is about where the labour is: there the words already exist and a specific
face has to say them; here the words have to be found in a source and no
particular face is required. That skill also runs a different model, because
bytedance/seedance-2.5 refuses a real person's photo at submission.seedance-short-drama. The boundary is not
"is there speech", it is who is being addressed: characters talking to
each other is that skill, one voice addressing the viewer is this one. Short
drama also owns shot lists, cuts and stage direction, none of which belongs
in an explainer.seedance-ad-creative. An explainer that exists
to sell is an ad, and that skill has the beat structure for it.ugc-ads, even when the creator is
explaining something. That skill inverts the polish this one defaults to.shorts-reels owns
that ladder. Write the prompt here, run the set there.