Faceless YouTube Channel Automation: $0.30 per Short, Start to Finish
One key, four steps: script model, three 5-second clips, ffmpeg concat. 15 seconds of vertical video for $0.3009 in 260 seconds, and 3 parts that break.
A 15-second vertical short, script to stitched file, cost $0.3009 and took 260 seconds. Four steps, one API key, about sixty lines of Python. The interesting part is not that it works; it is which three parts of it break when you try to run it daily.
What you get: 15s vertical 9:16 MP4, 496x864, H.264 + AAC
Steps: 1 chat call -> 3 video jobs (parallel) -> ffmpeg concat
Time: 5.7s script + 254.7s clips (parallel) + 0.05s stitch = 260.4s
Cost: $0.30 video + $0.0009 script = $0.3009
Per 60s short: about $1.20 of generation at the same settings
Not included: voiceover (no TTS on this endpoint), music, captions, QC
Measured: 2026-08-24, one end-to-end run
Last updated 2026-08-24. Prices are the ofox model page rates on that date, and Seedance 2.0 Mini was running a 50% discount, so check the page before you extrapolate a monthly budget from these numbers.
What Does a Faceless Video Pipeline Actually Cost?
Thirty cents for fifteen seconds, and the script model is a rounding error.
| Step | Model / tool | Time | Billed |
|---|---|---|---|
| Shot list | deepseek/deepseek-v4-flash-0731 | 5.7s | ~$0.0009 |
| 3 clips, 5s each, 480p 9:16 | bytedance/seedance-2.0-mini | 254.7s wall | $0.30 |
| Stitch | ffmpeg concat, stream copy | 0.05s | $0 |
| Total | 260.4s | $0.3009 |
The script call billed 317 input and 572 output tokens, of which 349 were reasoning tokens, at $0.44 and $1.32 per million. Every text-side decision in this pipeline is free in practice. The video is the entire bill, and it scales with seconds of footage, so the only lever that matters is how many seconds you generate and at what resolution.
At Seedance 2.0 Mini rates, $0.02 per second at 480p and $0.04 at 720p, a daily 60-second short is about $1.20 a day at 480p and $2.40 at 720p. A daily channel for a year is $438 of generation at 480p and $876 at 720p. That is the real number to argue with, not the per-clip price.
Step 1: How Do You Turn a Niche Into a Shot List?
One chat call in JSON mode, and the schema in the system prompt does the work.
import json, requests
BASE = "https://api.ofox.io/v1"
H = {"Authorization": "Bearer YOUR_OFOX_API_KEY"}
SYS = (
"You write shot lists for faceless vertical short-form video. Reply with JSON only: "
'{"title": str, "hook": str, "shots": [{"n": int, "narration": str, "video_prompt": str}]}. '
"Exactly 3 shots. Each narration is one sentence a narrator reads in about 5 seconds. "
"Each video_prompt describes a single continuous 5-second shot with camera movement, "
"no on-screen text, no people, no watermark, vertical framing."
)
r = requests.post(f"{BASE}/chat/completions", headers=H, json={
"model": "deepseek/deepseek-v4-flash-0731",
"messages": [{"role": "system", "content": SYS},
{"role": "user", "content": "Niche: strange facts about deep-sea creatures. "
"Audience: TikTok, 15 seconds total."}],
"response_format": {"type": "json_object"},
"temperature": 0.7,
})
script = json.loads(r.json()["choices"][0]["message"]["content"])
What came back, verbatim, in 5.7 seconds:
{"title": "Deep Sea Oddities",
"hook": "You won't believe what lives in the deep sea.",
"shots": [
{"n": 1, "narration": "The anglerfish lures prey with a glowing light attached to its head.",
"video_prompt": "Slow push-in on a bioluminescent anglerfish in the dark deep sea, its glowing lure bobbing gently, marine snow drifting through the beam, vertical framing."},
{"n": 2, "narration": "The barreleye fish has a transparent head to see through its own skull.",
"video_prompt": "Side tracking shot of a barreleye fish with a transparent head, its green eyes visible inside, floating in a dark blue abyss, subtle light from above, vertical framing."}]}
Two details worth copying. Asking for n on each shot means you can name files shot1.mp4 without depending on array order surviving a parallel map. And spelling out the negatives in the system prompt, no on-screen text, no people, no watermark, keeps them out of all three video prompts without repeating yourself; the DeepSeek JSON mode guide covers the response format itself.
The narration lines are the part with no home yet. Hold that thought.
Step 2: How Do You Generate the Clips in Parallel?
Submit all three, then wait once. The video endpoint is asynchronous, so a thread pool over submit-and-poll is the whole trick.
from concurrent.futures import ThreadPoolExecutor
import time
TERMINAL = {"completed", "failed", "cancelled", "expired"}
def make_clip(shot):
job = requests.post(f"{BASE}/videos", headers=H, json={
"model": "bytedance/seedance-2.0-mini",
"prompt": shot["video_prompt"] + " Vertical 9:16 framing. No text, no watermark.",
"duration": 5, "resolution": "480p", "aspect_ratio": "9:16",
}).json() # 202 + id + polling_url
while True:
s = requests.get(job["polling_url"], headers=H).json()
if s["status"] in TERMINAL:
break
time.sleep(3)
open(f"shot{shot['n']}.mp4", "wb").write(requests.get(s["unsigned_urls"][0]).content)
return s["usage"] # {'video_seconds': 5, 'video_cost': '0.1000000000'}
with ThreadPoolExecutor(max_workers=3) as ex:
usages = list(ex.map(make_clip, script["shots"]))

The three clips finished at 85.1, 126.7 and 253.2 seconds. Same model, same duration, same resolution, submitted in the same second. Parallel wall clock was 254.7 seconds against 465.0 seconds if they had run one after another, so the pool saved about 45% of the time and the slowest clip still set the pace.
Download the file inside the same worker that saw completed. The unsigned_urls address is signed for 24 hours, and there is no second copy to fall back on for this model. That plus the rest of the async traps are in our video polling walkthrough.
Step 3: How Do You Stitch the Clips Together?
ffmpeg concat with stream copy, if and only if the clips match.
printf "file 'shot1.mp4'\nfile 'shot2.mp4'\nfile 'shot3.mp4'\n" > list.txt
ffmpeg -y -f concat -safe 0 -i list.txt -c copy short.mp4
That took 0.05 seconds and produced a 15.296-second file, 496x864, 4.4 MB. Stream copy works here because all three clips came back identical in format: H.264 at 24 fps, 32 kHz stereo AAC, same dimensions. The ffmpeg concat demuxer requires that.

The concat and the level check on the three clips this run produced.
It stops working the moment your batch is mixed. A 1080p Seedance 2.5 clip comes back as HEVC rather than H.264, and concatenating that with H.264 clips under -c copy will not do what you want. Either pin one model and one resolution for a whole video, or re-encode:
ffmpeg -y -f concat -safe 0 -i list.txt -c:v libx264 -c:a aac -r 24 short.mp4
Also note the arithmetic: three clips of 5.041 seconds do not make 15.000 seconds. If a platform or an editor expects exact durations, trim after concat rather than assuming.
Why Is There No Voiceover Step?
Because there is no text-to-speech model on this endpoint, and the clips’ own audio is not narration. This is the honest gap in every faceless-channel pipeline built on a video API, and it is usually papered over.
What you do get is a model-generated ambient track. What you do not get is a voice reading the narration lines the script model wrote. And the ambient track has its own problem:
| Clip | Mean volume | Peak |
|---|---|---|
| Shot 1 | -35.0 dBFS | -19.2 dBFS |
| Shot 2 | -27.0 dBFS | -12.8 dBFS |
| Shot 3 | -24.3 dBFS | -7.4 dBFS |
Nearly 11 dB of spread inside one batch. Those are RMS figures from volumedetect, not LUFS, so do not compare them straight to a platform loudness target; the spread between clips is the part that matters, because a raw concat has an audible level jump at every cut. One loudnorm pass evens it out:
ffmpeg -i short.mp4 -af loudnorm=I=-14:TP=-1.5:LRA=11 -c:v copy short_normalised.mp4
For the voice itself there are three routes, and none of them is this API: send the narration to a dedicated TTS provider and mix it in, record it yourself, or drop the voice and burn the narration in as captions. Captions are not the weak option they sound like, given how much short-form video is watched muted.
What Breaks When You Scale This to a Daily Channel?
Three things, in the order they will hurt.
Timing variance breaks schedulers. A 3x spread on identical requests means a cron job that renders at 08:00 and publishes at 08:05 will eventually publish nothing. Render ahead, queue the output, publish from the queue.
Nobody is fact-checking the script. Shot 2 asked for a barreleye fish with a transparent head. What came back is a fish with large eyes and no transparent dome, which is wrong in exactly the way a viewer who searched the topic will notice. The script model wrote a true sentence and the video model illustrated it loosely, and there is no step in this pipeline that catches the gap. At one video a day you can eyeball it. At ten you will not.
Platform policy is not a rendering problem. YouTube’s channel monetisation policies require original and authentic content and single out mass-produced and repetitious material; the Partner Program eligibility rules sit on top of that, and TikTok’s content sharing guidelines cover the API-posting side. Generation is a production tool. The editorial judgement, the specificity and the reason anyone should watch are still the parts you have to supply, and they are the parts an API does not sell.
Used with that in mind, the pipeline earns its place: it turns “make a 15-second illustrated short” from an afternoon into four minutes and thirty cents, which changes what is worth trying. Our guide to choosing a video generation API by use case covers picking the model that goes in step 2, and the Seedance tier comparison has the quality-per-dollar argument for staying on Mini.
How Do You Keep One Key Across the Script Model and the Video Model?
The pipeline above touches two completely different kinds of model: a text model that has to return strict JSON, and a video model that runs asynchronously and bills by the second. Done natively that is two providers, two SDKs, two dashboards and two invoices, plus the discovery that your text vendor has no video model and your video vendor’s text model is not good at JSON.
Both calls in this article went to the same base URL with the same key: /v1/chat/completions for the shot list, /v1/videos for the clips. That is the only reason the script is sixty lines. We run on ofox because it exposes both shapes, and any gateway that does the same will work; the thing to verify before you commit is that the text side supports response_format properly, because a pipeline that gets prose back where it expected JSON fails at step one every time. Test that with one call before you build the other three steps.
References
Frequently Asked Questions
- How much does it cost to generate a faceless short with an API?
- Our 15-second vertical short cost $0.3009: $0.30 for three 5-second 480p clips on Seedance 2.0 Mini at $0.02 per second, and about $0.0009 for the script call on DeepSeek V4 Flash. That scales linearly with length, so a 60-second short is roughly $1.20 in generation cost. It does not include voiceover, music licensing or your own time.
- How long does the pipeline take end to end?
- 260 seconds of wall clock for a 15-second short. The script took 5.7 seconds, the three clips took 254.7 seconds running in parallel, and the ffmpeg concat took 0.05 seconds. Run serially the clips would have taken 465 seconds, so submitting them together saved about 45% of the wall time.
- Can one API key cover both the script and the video?
- Yes, if the endpoint is a gateway that serves both. Our script call went to /v1/chat/completions and the clips to /v1/videos with the same base URL and the same key. That is the part that makes the pipeline a 60-line script rather than a project with two vendor SDKs and two billing relationships.
- Do the generated clips come with sound?
- They come with a model-generated AAC track, and it is quiet and inconsistent. Across three clips from one batch ffmpeg volumedetect reported mean levels of -35.0, -27.0 and -24.3 dBFS, a spread of nearly 11 dB. Those are RMS figures rather than LUFS, so do not read them against a platform loudness target, but the spread between clips alone means you need a loudness normalisation pass before publishing.
- Is there a text-to-speech step in this pipeline?
- Not on this gateway, and pretending otherwise would be the most common lie in faceless-channel tutorials. The narration lines come out of the script model as text, and you either send them to a separate TTS provider or render them as on-screen captions. The video endpoint does not read your narration.
- Can I stitch the clips with ffmpeg stream copy?
- Only when every clip shares codec, resolution and audio parameters. All three of our 480p clips were H.264 at 24 fps with 32 kHz AAC, so concat with -c copy took 0.05 seconds. Mix in a 1080p Seedance 2.5 clip, which comes back as HEVC, and stream copy stops working.
- How consistent are generation times across identical clips?
- Not very. The three clips in this run were submitted in the same second with the same model, duration and resolution, and finished at 85.1, 126.7 and 253.2 seconds. Any scheduler you build has to assume the slowest clip decides when the batch is done.
- Will YouTube monetise videos made this way?
- Not on their own. YouTube's monetisation policies require original and authentic content, and mass-produced or repetitious material is called out explicitly. Generated footage is a production tool, not an exemption, so the edit, the writing and the point of view still have to be yours.


