本文へ移動
cccskills
無料GitHub で公開

keyframe-animation

Requires OFOX_API_KEY — create one at https://app.ofox.ai, plus two images you already have, a start frame and an end frame. Animates the motion between them as one video — both frames go into a single Ofox video job, the clip opens on A, closes on B, and the model fills the middle. Use when a user has two stills and wants the in-between animated, e.g. "here is the before and the after, animate the transition", "make a video that starts on this image and ends on that one", "tween these two frames", or "move the object from where it sits in the first picture to where it sits in the second". Do not use when only one image exists (animating a single frame is seedance-ad-creative or seedance-product-video), or when the pair is two states of a user interface (see product-demo).

インストール方法を見る

含まれるファイル(2)

  • SKILL.md47.8 KB
  • CHANGELOG.md13.6 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

keyframe-animation: start on image A, end on image B, fill in the middle

Two stills you already have become one clip: image A is attached as the first frame, image B as the last frame, in the same job, and the model generates the motion between them.

This skill is a thin, scenario-specific layer over ofox-video-core. It owns what makes a usable A/B pair, the prompt for the middle, the duration advice, 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.

The shared prompt craft — the vendor's formula, timestamp rules, camera vocabulary, consistency locks, negative lists, and what a frame lock has been measured to hold — lives in ../ofox-video-core/references/prompt-structure.md. The rules for the questions that come before a prompt exists are in ../ofox-video-core/references/creative-brief.md, and the spend rule is in ../ofox-video-core/references/approval-gate.md. This file carries only what is specific to a two-ended interpolation.

Where the core skill lives

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: every example below assumes the working directory is the one this SKILL.md sits in. 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".

This skill needs ofox-video-core 2.0.0 or newer. From that version the billable subcommands refuse to run without --approved, and every real-run command below passes it. An older core does not know the flag and stops with unknown option '--approved' before any request — nothing is submitted and nothing is billed, so the fix is to update the core, never to drop the flag.

Before generating: the availability check

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; it makes no network call. The key is an ordinary environment variable in the shell that runs the script — the script reads nothing from a .env file on its own, so if a project keeps it in one it has to be sourced into the same shell before the command runs.

A failing check is not a stop sign for this conversation. It exits 2 when OFOX_API_KEY is unset, but models, providers and generate --dry-run all run without a key — so a keyless user can still get the pair of frames read, the prompt written and the job priced, and only needs the key at the moment they say yes to the cost table. Do that work first rather than opening with a signup link; see "Pricing a job with no API key".

What this skill rests on: three measured jobs on two pairs

Everything below traces to real runs, or it says it doesn't. All three ran bytedance/seedance-2.5 at 480p with a first frame and a last frame attached in one job, 44 to 55 cents each.

The first two ran 4 seconds on the same inputs: a red square on a flat background with no text, drawn at x=120–240 in the first frame and at x=614–734 in the last.

JobWhat it tested
259c3ce2the mechanism — can both ends be locked in one job at all, and how does the middle behave
2514540e (2026-09-15)the prompt template in this file, filled in by an agent working from this skill and nothing else. Same pair, so it widens nothing about the inputs; what it adds is that the template — not a hand-written prompt — produces the same result, and that the result replicated
1b7a3fab (2026-09-17, 5s, 55 cents)the inputs — the first pair that is not the red square. A real photograph of a product carrying a readable wordmark and a brushed-metal texture, left third to right third, cropped for free out of an image this repo already had. It overturned this file's duration claim; see "Duration: the easing curve is not a property of the tool"

The third run is the one that changes how to read this file. Two of the three clips share a pair, so until it there was one input class behind every claim here, and the file said so. What 1b7a3fab adds:

  • A real subject with lettering survives the interpolation. The wordmark is intact and legible at every sampled point — 0, 1, 2, 3 and 4.5 seconds — not just at the two supplied ends. That is the question a pair of plain red squares could not ask, and the answer is positive. It is also the measured backing for the AVOID-list advice below, which until now was reasoning.
  • Both ends honoured again, on a pair the earlier runs say nothing about: first frame matches A, last matches B, the camera does not move, the background does not change, no second copy of the subject appears.
  • ❌ And the duration claim this file carried did not survive it. The detail is in the duration section; the short version is that the two clips' easing curves differ so much that neither can be quoted as the model's behaviour.

The measurements below are from 259c3ce2 unless noted. The endpoint result is the one that has now happened three times:

Endpoint replication, job 2514540e. Scanning the delivered video's own first and last frames for red gives x=120–239 and x=614–733 — the same figures, to the pixel, as the first run. And again on a different pair (1b7a3fab, a photographed product): first frame matches A, last matches B. Three runs across two input classes is not a guarantee, but "both ends are honoured" is by some distance the best-supported claim in this file — and note that it is the only one that survived the third run unchanged.

What was measuredThe measurement
Both ends are honoured, to the pixelscanning the delivered video's own frames for red: its first frame has the square at x=120–239, its last frame at x=614–733. Not "the job reported completed" — the artifact was read
This clip's motion was front-loaded ⚠️the same scan at t=1/2/3s gives x≈384, 564, 614. Against a total travel of 494 px that is roughly 53% of the distance in the first quarter, 90% by halfway, and arrival at 3 seconds of 4. ⚠️ Do not carry this over to your clip — 1b7a3fab measured a near-linear curve on the same mechanism. See the duration section
This clip's last beat was a hold, not travel ⚠️having arrived at ~3s, it stays; the final second delivers no movement. Same caveat — 1b7a3fab's hold was about half a second of five

What those jobs do not establish, and this skill does not claim:

  • any easing curve at all. This used to read "whether the front-loaded easing scales with duration is untested", which assumed the front-loading and questioned only its scaling. The second duration was measured and came back a different shape, so what is untested is the curve itself, on any duration;
  • anything wider about the inputs than two pairs. The first two runs share one pair; 1b7a3fab adds a real photographic subject with lettering. A UI, a person, a scene change, or ends that differ in more than one bounded way are all still untested here;
  • whether first+last in one job works on any model other than bytedance/seedance-2.5. The catalog lists i2v for the current video models, but i2v says nothing about locking both ends at once — that granularity is not in the catalog and has not been tested here;
  • whether a mismatched pair (two images of different pixel dimensions) works. The measured pair were two renderings of one canvas;
  • anything about how a pair that differs wildly is interpolated. See "When the two frames differ too much", which is reasoning, and labelled as such.

Before writing the prompt: the brief

The shared rules — the three tiers, one round of at most four questions, the "Let the AI decide" discipline, the order with the approval gate, the fallback for a runtime without AskUserQuestion — are in ../ofox-video-core/references/creative-brief.md. This section adds only this scenario's question set.

Most of the axes other Seedance scenarios ask about are settled here by the two images themselves: the subject, the setting, the framing, the lighting and the output's shape are all in the pair. Zero questions is the common case when both files arrive with names that say which is which.

TierKeyframe axes
must-askdoes the second image exist? and, when the input leaves it ambiguous, which of the two is the start. Neither can be guessed: a frame that does not exist cannot be invented, and getting the order backwards produces a clip that runs the wrong way and costs the same
ask-if-openthe path — what happens between the two frames, when the pair leaves more than one reading
never-askduration, resolution, model, provider, audio (off by default here). Aspect ratio is not a question at all: with frames attached the clip follows the frames. Two durations or two resolutions are two rows in the cost table, which tells the user more than a question with two labels and no prices. Never-ask means the agent doesn't raise it — a model the user names by id or by shorthand is used instead of the default

The question set

#TierheaderQuestionOptions (1 = recommended)Ask when
1must-askFramesI need both ends of this: the picture the clip starts on and the picture it finishes on. Do you have the second one?Yes — I'll give a local path (recommended): both frames go into one job and the clip lands on both exactly. / No, only one: then this is not a two-ended job — animating a single frame is seedance-ad-creative or seedance-product-video, and I can hand it over. No AI option — a frame that does not exist cannot be invented.Only one image is attached and the user did not name a second.
2must-askOrderWhich of the two is where the clip starts?the two filenames as the two options. No AI option.Two images arrived and nothing in the names, the paths or the message says which comes first. Skip it whenever the names themselves state an order — before/after, start/end, first/last, a/b, 1/2, 01/02, open/closed, old/new, a timestamp or a frame number — or the message says so. The list is illustrative: any pair that reads as a sequence to you will read that way to the user.
3ask-if-openPathHow should the subject get from the first picture to the second?Straight there (recommended): the plain reading of the pair, and the only one with a measured run behind it. / Along an arc: name the arc in the prompt (over, under, around the object between them). / Something happens on the way: describe it and it goes in the prompt as a beat — untested here, and the middle is the part the model invents. / Let the AI decide.The two frames leave more than one plausible route between them. Skip it when the pair is a plain displacement.

Not asked: duration and resolution (rows in the cost table), the model, the provider, the audio flag, the aspect ratio.

Skip rows specific to this scenario

On top of the generic rows in creative-brief.md:

Input says…AxisValue
filenames that state an order — before/after, start/end, first/last, a/b, 1/2, 01/02, open/closed, old/new, timestamps, frame numbersOrderas named; don't ask. Treat these as examples of the pattern, not as the whole list
"from this one to that one", in the order the files were attachedOrderattachment order
"it should slide / travel / move across"Pathstraight
"it should arc / swing / hop over"Patharc, described in the prompt
"a quick transition", "short", "for a GIF"durationthe model's minimum — ofox-video.sh models prints each model's range

From answers to prompt — traceability

AnswerLands in
Frames: two paths--frame-first-image A and --frame-last-image B
Orderwhich path goes on which of those two flags
Paththe MOTION line of the prompt
Duration--duration, and a row in the cost table

The recap for this scenario

Brief:
- Start frame: /Users/me/shots/ball-left.png (given)
- End frame:   /Users/me/shots/ball-right.png (given)
- Path: straight across the table (inferred from "slide it over")
- Duration / resolution / audio: 4s / 480p / off — one row below; 720p as a second row
- Frame shape: 1024x576 on both files, so the clip comes out in that shape

Then the full prompt, then the cost table, all in one message — the order and the rule for a change made at the gate are in creative-brief.md's "Order, with the approval gate".

What makes a good A/B pair

The two frames are the only thing the job is anchored to, so the pair carries more weight here than a reference photo does anywhere else in this repo.

  • Two states of one scene, not two pictures. Same subject, same set, same lighting, same camera position and the same distance from it. The measured pair differed in exactly one thing — where the square sat — and the model had one job to do. The further the pair drifts from that, the more of the clip is the model's invention rather than your material.
  • One change, or a small bounded set of them. Every difference between A and B has to be resolved somewhere in the middle, and you are paying for all of them at once.
  • Same pixel dimensions on both files. The measured pair were two renderings of one canvas. A mismatched pair has not been run here; matching them first costs nothing and removes the question. hal-image or any ImageMagick/sips call will do it, and reading the real pixels (sips -g pixelWidth -g pixelHeight <file>, identify <file>) rather than trusting a filename or an export dialog is this repo's standing habit.
  • Crop to the shape you want the clip in, before generating. With frames attached the output follows the frames — see "The frame decides the shape" below — so the aspect ratio is a crop you make first, not a flag you pass afterwards.
  • A local file beats a remote URL when one is available. ofox-video-core base64-encodes a local readable file into the request; a publicly reachable URL has been rejected upstream at least once in this repo (likely host-side hotlink protection). A URL works, it is just the less-reliable of the two.
  • A photoreal person in either frame stops the job by default — and the one exception is an authorisation, not a trick. bytedance/seedance-2.5 refuses a photoreal-person reference at submission (input_moderation_failed, nothing billed) — a documented ofox-video-core behaviour, and the refusal happens before any money moves. --real-person true routes such a reference through Ofox's privacy-preserving preprocessing for real-person material the user is authorised to use, and as of 2026-09-16 it is measured lifting that refusal on 2.5. It is an authorisation route, never a way past the check: reach for it only when the user holds the right to use that likeness and has said so, and never as a fix for a rejection. What was measured is a synthetic portrait on a single first frame at one tier — a two-ended pair with the flag set is outside it, and so is a real photograph of a real person. Evidence and limits: ../ofox-video-core/references/api-params.md → "--real-person true lifts that refusal on 2.5".

Duration: the easing curve is not a property of the tool

This section used to be headed "the motion arrives early and then holds" and that was overturned by the second measurement. It is kept as a heading rather than quietly deleted because the old version told callers to budget for a hold, and anyone who read it should know why not to.

Two clips, each read the same way — the subject's horizontal position sampled per second and expressed as a fraction of the total travel:

Fraction of the clip elapsed259c3ce2 (4s, red square)1b7a3fab (5s, real product)
0.25~53% of the distance—
0.20—20%
0.40—51%
0.50~90%—
0.60—80%
0.75arrived—
0.90—arrived

The first is strongly front-loaded with a quarter of the clip left over as a hold. The second is close to linear with a slight ease-out, and its hold is the last half second — about 10% of the clip, not 25%.

So there is no easing curve to plan against. The two runs differ in duration (4s vs 5s), subject, travel distance and seed, and one measurement each cannot attribute the difference to any of them. What used to be written here as a property of the model is a property of those two clips.

What to do with that:

  • Do not promise a hold, and do not promise arrival on the final frame. The old text said flatly that an arrival on the last frame "won't" happen. One of two clips arrives at 90% of the way through, which is close enough to the end that the claim cannot stand. Tell the user the clip lands on frame B — that part is measured, twice, to the pixel — and that when it gets there is not something this skill can predict.
  • If the timing of the arrival matters, it is a draft question. Generate the cheapest tier first and look, rather than writing a clause. There is no known clause.
  • Short is still the honest default, for a different reason than before: not "a longer clip buys more hold" (unsupported) but simply that it bills per second and the arrival is unpredictable either way. Start at the model's minimum duration (ofox-video.sh models prints each model's range) and show a longer option as a second row in the cost table.
  • Don't write "at an even speed" into the prompt. Both clips eased to some degree regardless, and an instruction the tool measurably does not follow is a liability rather than a rule — this repo has the scar to prove it. If even pacing genuinely matters, that is a question for a real run, not a clause.
  • Two points are not a curve. Do not read the table above as "longer clips are more linear". It is two observations that disagree, which is exactly enough to retire the old claim and not nearly enough to replace it.

When the two frames differ too much

This section is reasoning, not measurement. The one run behind this skill used a pair that differed in one thing. Nothing here has been paid for.

The middle of the clip is the part nobody supplied, and the further apart the endpoints, the more of it the model is inventing. Signals that a pair is asking for too much: a different set, a different camera position, a different subject, a different time of day, or several unrelated changes at once.

Three routes, in the order worth trying:

  1. Make the pair closer. Re-shoot, re-render or re-crop B so it differs from A in one bounded way. Cheapest by a wide margin — it costs no API call at all.
  2. Split it into two jobs and let the middle be a cut. Two shorter A→B clips joined in an editor beat one clip asked to invent a transition it was never shown. Two jobs are two rows in the cost table.
  3. Use chain instead when what you actually have is a sequence rather than two ends: ofox-video.sh chain carries the last frame of one job into the first frame of the next and joins the clips. It is a different tool — you give it prompts, not a destination frame — but for "keep going from here" it is the one that fits.

If the user wants the wide pair anyway, price it, say plainly that the middle is unmeasured territory, and suggest the cheapest resolution for the first attempt.

The prompt

With both ends supplied, the prompt has a smaller job than in any other Seedance scenario: it is not describing a shot, it is describing what happens between two pictures the model can already see. Keep it to the subject, the single change, and what holds still.

Vocabulary and the general formula are not repeated here — see ../ofox-video-core/references/prompt-structure.md. Two things from that file bear directly on this template: negative clauses are honoured more reliably than positive ones, and a camera verb on its own tends not to produce a camera move. The second is why this template asks the camera to stay put rather than asking it to travel.

SUBJECT: <the subject, in physical words — material, colour, shape, size relative to the frame>. It keeps the same shape, colour, proportions and markings from the first frame to the last.
SCENE: <the setting, described once — it is the same setting in both frames>. The background, the lighting and the framing stay as they are; the camera does not move.
MOTION: the <subject> travels from <where it is in the first frame — "the left third of the frame, on the table"> to <where it is in the last frame — "the right third, on the same table">, <along a straight line | in a shallow arc over the <object>>, staying <in contact with the surface | at the same height> the whole way. Nothing else in the frame moves.
ENDING: it comes to rest in the position of the last frame and stays there.
AVOID: a cut, a fade to black, a dissolve, a wipe; camera shake, zoom, pan or reframing; the <subject> changing shape, colour or size; a second <subject> appearing; motion blur smearing the <subject>; subtitles, captions, on-screen text, watermarks.

About that last AVOID item. It is here because the first measured pair carried no lettering, so any text in the middle would be text the model invented — which is the failure mode every AVOID: on-screen text line in this repo exists for. If your own two frames contain lettering — a label, a sign, a readable screen — the premise flips: the model is then interpolating between two supplied renderings rather than inventing glyphs, and forbidding text fights your own material. Drop the item.

That flip is now measured here rather than only reasoned. Job 1b7a3fab (2026-09-17) ran this template on a pair whose subject carries a printed wordmark, with the no-text item removed for exactly this reason, and the wordmark is intact and legible at every sampled point through the clip, not only at the two supplied ends. One pair, one wordmark, 5 seconds — but it is the first direct evidence for an instruction this file had been giving on inference. product-demo is still the skill to read when the pair is two states of an interface.

Worked example

Two renderings of one 1024x576 canvas: a red ceramic mug on a light wooden table, at the left in A and at the right in B, nothing else different.

SUBJECT: a red ceramic mug with a rounded handle, matte glaze, about one sixth of the frame's width. It keeps the same shape, colour, proportions and handle angle from the first frame to the last.
SCENE: a light wooden table under even, soft, overhead light, a plain pale wall behind it. The background, the lighting and the framing stay as they are; the camera does not move.
MOTION: the mug travels from the left third of the frame to the right third, along a straight line across the table, its base staying in contact with the tabletop the whole way and the handle keeping the same angle. Nothing else in the frame moves.
ENDING: the mug comes to rest in the position of the last frame and stays there.
AVOID: a cut, a fade to black, a dissolve, a wipe; camera shake, zoom, pan or reframing; the mug changing shape, colour or size; a second mug appearing; motion blur smearing the mug; subtitles, captions, on-screen text, watermarks.

Check the draft the same way the evidence jobs were checked: read the delivered clip's own first and last frames against the two inputs, and sample the middle — never take STATUS completed as proof the interpolation is the one you asked for. This template has been run twice, on two different subjects: job 2514540e filled this shape in for a red square, and job 1b7a3fab for a real photographed product with a readable wordmark. Both came back with the ends honoured, which is what the check above is looking for. What the two disagreed on is when the subject arrives — see the duration section — so check the ends, and look at the middle rather than predicting it.

The frame decides the shape

With a frame attached, the output's aspect ratio follows the frame, and how it gets there depends on the model:

  • on bytedance/seedance-2.5, ofox-video-core forces aspect_ratio=adaptive and prints a NOTE: saying so, because the API requires it for image-to-video on that model. Anything you passed is overridden;
  • on a model whose catalog entry lists adaptive, the script defaults to it when you pass no --aspect-ratio, and keeps your value if you pass one;
  • on a model that does not offer adaptive, the script sends no ratio and says so — the frame's shape may not survive.

So: don't pass --aspect-ratio on this route, on any model. Crop both frames to the shape you want before generating; that crop is what actually decides the output. Relay the NOTE: the script prints — it names which of the three branches applied.

This needs ofox-video-core 1.22.0 or newer. On an older core, a non-Seedance model with frames attached sent no ratio at all, with nothing printed to say so.

Recommended defaults

ParameterDefaultWhy
--frame-first-image / --frame-last-imageboth, in one generate callthe whole point of this skill; both ends were honoured to the pixel on both measured jobs
--modelnot passed — the script's own default applies, unless the user named a model, which always winsboth measured jobs ran on bytedance/seedance-2.5; whether both ends can be locked on another model is untested here, so say so before switching rather than after. models lists the models but does not mark which one the script defaults to — the MODEL line a generate --dry-run prints is what names the id that will really be sent, which is also the id the cost table has to carry
--durationthe model's minimum for a plain A→B movethe motion arrives at about three quarters of the clip and holds; extra seconds buy hold and bill per second. ofox-video.sh models prints each model's range — don't quote a range from memory
--resolutionthe cheapest tier for the draft, the deliverable's tier for the final. When the user named neither — the common case — draft at the model's cheapest tier and put the next tier up as a second row of the same cost table, so the upgrade is priced rather than asked aboutboth measured jobs ran at 480p, the cheapest tier their model lists. Which tiers a model has is a catalog fact; models prints them, and dry-running both rows costs nothing
--aspect-rationot passedthe attached frames decide it — see "The frame decides the shape"
--generate-audiofalsea tween of two stills has nothing to sync to, and the server's default is true. Omit the flag only when the clip genuinely wants a track
--seedlet the script roll one, and keep itit prints SEED and writes it to the clip's .json sidecar, which is what lets "that take, at a higher resolution" be re-submitted at all. It does not reproduce it: measured, an identical request on a fixed seed came back a visibly different clip. A byte-identical prompt is necessary and not sufficient — tell the user a re-render is another roll aimed at the same shot before they pay for it
--real-personleave unset — unless the user holds the right to use the likeness in the pair and has said soa photoreal person in a frame is refused at submission on bytedance/seedance-2.5, nothing billed. true is Ofox's privacy-preserving preprocessing path for authorised real-person references, and it was measured lifting that refusal on 2.5 (2026-09-16) — an authorisation route, never a way past the check. Measured on a synthetic portrait as a single first frame; a two-ended pair with the flag, and a real photograph of a real person, are both outside it. See api-params.md → "--real-person true lifts that refusal on 2.5"

Choosing a model

The model stays never-ask — the agent doesn't raise it. But never-ask is not "never listen": if the user names a model, use it.

Model ids, prices, resolutions, durations and aspect ratios are not written down in this file on purpose. They are catalog facts, they change, and this repo has five recorded defects that trace to a hardcoded copy of somebody else's value table — the most recent created by refreshing the data next to it. Read them live instead, free and with no API key:

bash ../ofox-video-core/references/ofox-video.sh models      # models, tiers, ranges
bash ../ofox-video-core/references/ofox-video.sh providers   # the price matrix

One thing worth telling the user before they switch: the first+last-frame combination has only been run here on bytedance/seedance-2.5. The catalog says the current models do i2v, but i2v does not distinguish "one end locked" from "both", so another model is an untested route, not a known-good one.

Before you spend: the approval gate

Never submit a paid job until the user has seen a cost table and said yes. The rule, the table's required columns, where the numbers must come from, and what to do when no estimate is possible are written down once for every Ofox skill in this repo: ../ofox-video-core/references/approval-gate.md. Follow it rather than improvising.

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 "..." \
  --frame-first-image /path/to/A.png \
  --frame-last-image  /path/to/B.png \
  --duration 4 --resolution 480p --generate-audio false \
  --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 swapped for --approved. The estimate a real run prints comes microseconds before the request goes out, too late to relay; that is what --dry-run is for.

--approved is where that yes gets typed out. Since ofox-video-core 2.0.0 the four billable subcommands — generate, create, batch, chain — refuse to run without it, while --dry-run never needs it, so the quote above is still free and still works with no API key. Be exact about what the flag does: it records a stance, it cannot prove one. Nothing in a shell script can observe the conversation you had, and it can be typed without showing anyone a price. What it changes is that spending without quoting is no longer the default — it has to be written into the command, where a transcript shows it. The rule above is still the rule, and it is still yours to follow.

The brief recap goes in the same message as the prompt and the table, above them.

The cost anchor this skill has, with the parameters it was measured at attached, because a figure without them is not a measurement: bytedance/seedance-2.5, 4 seconds, 480p, first+last frames, 44 cents billed — twice, on jobs 259c3ce2 and 2514540e, at the same figure. It is not a quote for any other combination — an image-to-video job bills at the text-to-video tier, so a longer or higher clip scales from the dry run, never from this number.

Afterwards the actual bill is VIDEO_COST from the finished job. Report it as money, not as the raw ten-decimal string. An estimate is never a bill.

Confirm both frames are attached, before the money moves

The dry run's own output names the model, the duration and the resolution and says nothing about the frames — so a mistyped flag, or a path that quietly matched only one file, reads as a perfectly healthy quote and shows up in the delivered clip instead. --print-payload closes that: it dumps the request body on stderr before the dry run stops (the API key travels in a header, never in the body), and the two frame_type entries are the part to read:

bash ../ofox-video-core/references/ofox-video.sh generate --dry-run --print-payload \
  --prompt "..." \
  --frame-first-image /path/to/A.png \
  --frame-last-image  /path/to/B.png \
  --duration 4 --resolution 480p --generate-audio false \
  --out-dir /absolute/path/to/out \
  2>&1 >/dev/null | grep -o '"frame_type":"[a-z_]*"'

Measured output on a two-frame job:

"frame_type":"first_frame"
"frame_type":"last_frame"

Two lines, in that order, is the confirmation. One line means one of the two flags didn't take; a different order means --frame-first-image and --frame-last-image are carrying the wrong paths, which is the same defect as a backwards clip and is free to fix at this point. Grep the payload rather than printing it whole: each local frame is base64-inlined into that JSON, so the body runs to megabytes.

--out-dir is worth passing to the dry run for the same reason — the script creates and enters it here, and a path it can't use fails as exit 6 with nothing submitted. Pass the same directory to the real run; a different one afterwards re-opens the question the dry run just settled (an exit 6 after a create costs the job's price, and is recovered with poll JOB_ID --out-dir <writable dir>, never with a second generate).

Several takes to choose from

The middle is generated, so it is a roll. When the user wants options rather than one clip, batch prices the whole set up front, waits on the takes concurrently, and builds a contact sheet:

bash ../ofox-video-core/references/ofox-video.sh batch --dry-run \
  --prompt "..." --takes 3 \
  --frame-first-image /path/to/A.png --frame-last-image /path/to/B.png \
  --duration 4 --resolution 480p --out-dir /absolute/path/to/out

Quote BATCH_COST_TOTAL, not BATCH_COST_PER_TAKE, and give the takes a row each — the gate wants the itemised shape. If one take in three is usable, that clip cost the whole total. At --dry-run the total is the Estimated cost: ~$… for N takes line; the two BATCH_COST_* keys are what the real run prints afterwards.

Hand the user the CONTACT_SHEET path on its own line; in this flow it is the artifact they look at first. Each TAKE line carries seed=N, which is how a take is named and re-submitted at a higher resolution — an aim at that take, not a promise of it (see the --seed row above).

Submission is sequential and only the waiting runs in parallel, so a submission failure stops the remaining takes — but the whole total is committed within seconds of the yes. That is a real reduction in recoverability compared with running takes one at a time, and the cost table is the only place a batch can be stopped.

Pricing a job with no API key

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.

If the script isn't found

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:

  • The probe printed a directory — the core is installed and only the directory name was wrong, which is the normal LobeHub case (ofoxai-skills-ofox-video-core). Re-run the command against what the probe printed. Nothing needs installing.
  • The probe printed nothing — 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, say which skill is missing 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 packages only its SKILL.md and CHANGELOG.md, so prompt-structure.md, creative-brief.md, approval-gate.md and api-params.md are out of reach whenever the script is. The skill still works without them — the template, the question set and the defaults are written out here; what is unavailable is the depth behind the rules they reference.

Exit codes worth knowing

Full table in ../ofox-video-core/SKILL.md. The ones that come up:

CodeMeaningWhat to do
1Parameter rejected locally, no network call, nothing billedFix the flag and retry freely
2Environment problem — curl/jq missing, or no OFOX_API_KEYAsk the user to fix it; check reports the same
3API rejected it, or the job ended failed/cancelled/expiredRead the mapped message; a rejected create was not billed
4Timed out waiting — the job is still running and billablepoll JOB_ID, never re-run generate
5Ambiguous network failure on createDo not retry blindly; check https://app.ofox.ai first
6--out-dir unusableFix the path; if it happened after a create, poll JOB_ID

How long to tell the user it will take

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, instead of generate. That way a timeout can never strand a job whose id you never saw.

Where the file lands

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.

The dry run is where a bad directory gets caught: generate --dry-run (and batch --dry-run) creates and enters --out-dir before it quotes, and exits 6 without submitting anything if it can't. That only covers the directory you actually passed it, so give the real run the same one — swapping it afterwards puts you back to discovering the problem after the job is paid for.

Pass --name too — you know what the clip is, so name the file after it rather than leaving the script to guess from the prompt's opening words. 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.

Generating

bash ../ofox-video-core/references/ofox-video.sh generate --approved \
  --prompt "<the A-to-B prompt built above>" \
  --name "<short description, e.g. mug slides right>" \
  --frame-first-image "/absolute/path/to/A.png" \
  --frame-last-image  "/absolute/path/to/B.png" \
  --duration 4 \
  --resolution 480p \
  --generate-audio false \
  --out-dir /absolute/path/to/out

No --aspect-ratio on purpose, on any model — see "The frame decides the shape".

--approved is not decoration: without it the script refuses, submits nothing, and prints the quote-first steps instead. Add it only once the cost table has actually gone in front of the user and come back with a yes — the flag cannot check that for you.

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.

Common failure modes and fixes

SymptomCauseFix
Exit 1, no network call madeA flag the model doesn't accept — a duration outside its range, a resolution it doesn't haveRead the error, which names the model's real values, and re-run. Nothing was submitted, so retrying is free
Exit 1, references_conflict--frame-first-image/--frame-last-image together with an input_references array in --extra-jsonPick one meaning — locked frames, or identity references — and drop the other. They cannot be combined in one job
Exit 3, input_moderation_failedOne of the two frames contains a photoreal person. Refused at submission on bytedance/seedance-2.5; nothing billedUse frames without a photoreal person, or crop them out. If the user is authorised to use that likeness, --real-person true is the route Ofox provides for it and is measured lifting this refusal on 2.5 — ask first, price it as an experiment (it was measured on a single first frame, not on a two-ended pair), and do not offer it as a way past the check
Exit 3, insufficient_creditsOfox balance too lowNo charge was made; add credits at https://app.ofox.ai and retry
Exit 3, job ends failed with output_moderation_failedThe generated output failed a post-generation check — after the job ran, not at submission. Not billedRetry as a brand-new generate with a different prompt or frames. A new request, not a resubmission
The clip runs backwardsThe two paths were passed on the wrong flagsSwap --frame-first-image and --frame-last-image. New job, new cost table — which is why Order is a must-ask when the input is ambiguous
The subject reaches its destination early and then sits therePossible, not expected. Job 259c3ce2 did exactly that (~53% of the distance in the first quarter, arrival at 3 of 4 seconds); job 1b7a3fab did not (20% at 1s of 5, arrival at 4.5s). Two clips, two different curvesNothing in the prompt is known to change it, and nothing predicts which you will get — so treat the arrival time as a draft question rather than a plannable parameter. Don't write "constant speed"; both clips eased to some degree anyway
The output is the frames' shape, not the shape you wantedExpected: with frames attached the clip follows the framesCrop both frames to the target shape and generate again — a new job, billed again, which is why the crop happens before the first submission
The middle invents something that is in neither frameThe pair was too far apart, so the middle was the model's to fillBring the pair closer, or split it into two shorter jobs. See "When the two frames differ too much" — reasoning, not measurement
Readable lettering appears mid-clip that is in neither frameThe AVOID list's text items were dropped, or the pair has lettering in only one endKeep the text items when your frames are text-free. When both frames genuinely carry the same lettering, product-demo is the skill with a measured run behind that case
Exit 4, timed out waitingThe job is still running upstream, not failedDo not re-run generate; run poll JOB_ID with the id printed before the timeout
Exit 5, ambiguous network failure on createNo HTTP response at all — can't tell whether a job was createdDon't guess or retry; tell the user to check https://app.ofox.ai for a job that may already be running
Exit 6, --out-dir unusableLocal filesystem problem, not an API problemThe job is unaffected — fix the path and poll JOB_ID --out-dir <writable dir> rather than regenerating

When NOT to use

  • Only one image exists. Animating a single still is seedance-ad-creative (cinematic) or seedance-product-video (catalog-literal). This skill's whole premise is that the destination is supplied.
  • The pair is two states of a user interface — a settings panel before and after a toggle, a dashboard at two values. Use product-demo: it is the same mechanism, but its measured behaviour and its prompt advice differ, and it deliberately does not carry the anti-text line above.
  • The two pictures are unrelated scenes. Two ends with nothing in common is a cut, not an interpolation, and this skill has nothing measured to say about it. Two jobs and an editor are cheaper and more predictable.
  • What you have is a sequence, not two ends — "and then this happens, and then this". That is ofox-video-core's chain, which carries each clip's last frame into the next job's first frame.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Publish a static site (a folder of HTML/CSS/JS/images/fonts) to Cloudflare and get back a live, shareable URL in seconds. Use when you have a finished static page or site and need to hand someone a link they can open on any device — reports, mockups, one-off landing pages, AI-generated HTML, "give me a link I can share". Runs one packaged command (references/deploy.mjs) built on the Wrangler CLI, which is what Cloudflare's own agent guidance tells agents to use. Deploys permanently to your Cloudflare account when credentials are present, or as a 60-minute claimable preview when they are not — and says plainly which one you got. Bakes an expiry countdown into previews (matched to the real 60-minute limit; --ttl to shorten it), and supports optional server-side six-digit access-code protection with -otp for permanent links only. Unprotected delivery may fall back to a file; protected delivery fails closed.

日本語の概要は準備中です。原文の説明を表示しています。

ofoxai/skills22026年9月22日 更新

explainer

無料

Requires 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.

日本語の概要は準備中です。原文の説明を表示しています。

ofoxai/skills22026年9月22日 更新

hal-image

無料

Handle images with ImageMagick (magick) — read metadata, resize, crop, rotate, convert format, combine images (side-by-side or grids with montage), overlay logos/watermarks, and losslessly compress with oxipng before sending. Use when the user sends an image to process, when you produce an image to deliver, or before attaching any image to a message or upload — compressing first keeps transfers fast and tokens low. Core discipline - always compress before delivering, never expose local file paths to the user, and fail open (if a tool is missing or a step errors, pass the original through unchanged; never block the task).

日本語の概要は準備中です。原文の説明を表示しています。

ofoxai/skills22026年9月22日 更新

hal-vault

無料

Securely store, search, and use secrets (API keys, tokens, passwords, SSH keys) with hal-vault, an SSH-key encrypted local secret store. Use when the user shares a credential that should be saved, asks what secrets are stored or where a key is, or when a command/workflow needs a secret injected. Core discipline - never print raw secret values into chat, logs, or files; reference secrets only by their masked form, and use --reveal only to feed another process, never into a command argument (where it lands in the machine's process table) or into text you write down.

日本語の概要は準備中です。原文の説明を表示しています。

ofoxai/skills22026年9月22日 更新

Requires OFOX_API_KEY — create one at https://app.ofox.ai. Change one thing in an image you already have and leave the rest of the picture alone — swap the background, recolour a part, remove or add an object, clean up a photo — from a local jpeg/png/webp file. Delegates to ofox-image-core's `edit` subcommand (POST /v1/images/edits, one synchronous request), prices the job with --dry-run before spending, and reports the real token cost including the uploaded picture, which is billed. Use when a user hands over an image and asks for a change to it, e.g. "change the background of this photo to a beach and keep the person unchanged", "make this button green", "remove the car in the background", or "put this product on a plain white background". Do not use to draw a new image from a text description with no input picture (that is ofox-image-core's `generate`), to produce a set of several images to choose between (see product-image), or to turn a photo into video (see seedance-product-video or seedance-ad-creative). Editing the content of an existing video — "change the background of my clip, keep the product" — has no route here and none in the video API either (measured; the mode field an edit would use is accepted and silently ignored, so nothing errors); this skill edits a single still, and routing such a request to a video skill generates brand-new footage instead of changing theirs.

日本語の概要は準備中です。原文の説明を表示しています。

ofoxai/skills22026年9月22日 更新

Requires OFOX_API_KEY — create one at https://app.ofox.ai, plus the music file the finished video must carry. Your audio never reaches the API (measured) — the visuals are written to the track's tempo, mood and sections, then your own file is laid on locally at zero cost. One job caps at 30 seconds, so a three-minute song is six jobs minimum and the cost table says that before anything is spent. Use when a user has a specific piece of music and wants visuals for it, e.g. "make a music video for this track", "visuals for my song", "an MV for this instrumental", "generate footage cut to this beat". Do not use for cheap vertical social drafts (shorts-reels), a brand film that happens to have a music bed (seedance-ad-creative), or when there is no particular audio file the finished video has to carry.

日本語の概要は準備中です。原文の説明を表示しています。

ofoxai/skills22026年9月22日 更新

ofoxai のスキルをすべて見る

このスキルの問題を報告する