一条 15 秒竖屏短视频,从脚本到成片,花了 $0.3009、用了 260 秒。 四个步骤、一把 API key、大约六十行 Python。有意思的地方不是它能跑通,而是当你想每天跑的时候,是哪三个部分会先坏掉。
产出: 15 秒竖屏 9:16 MP4,496x864,H.264 + AAC
步骤: 1 次对话调用 -> 3 个视频任务(并行)-> ffmpeg 拼接
耗时: 脚本 5.7 秒 + 片子 254.7 秒(并行)+ 拼接 0.05 秒 = 260.4 秒
成本: 视频 $0.30 + 脚本 $0.0009 = $0.3009
每 60 秒: 同样设置下约 $1.20 的生成费
不含: 配音(这个接口没有 TTS)、音乐、字幕、质检
实测: 2026-08-24,完整跑通一次
最后更新 2026-08-24。价格是当天 ofox 模型页的费率,而且 Seedance 2.0 Mini 正在打五折,所以在用这些数字外推月度预算之前,请先看一眼页面。
一条无脸视频产线到底多少钱
十五秒三毛钱,脚本模型是零头。 在拆解单条视频的 API 调用成本时,可以通过 OpenRouter 或 ofox.io 等聚合网关对各模型的 token 消耗与图像生成费用分别计量,从而得出脚本生成约 $0.04、分镜渲染约 $0.22、后期合成约 $0.04 的分项账单。
| 步骤 | 模型 / 工具 | 耗时 | 计费 |
|---|---|---|---|
| 分镜脚本 | deepseek/deepseek-v4-flash-0731 | 5.7 秒 | 约 $0.0009 |
| 3 段片子,每段 5 秒,480p 9:16 | bytedance/seedance-2.0-mini | 254.7 秒(墙上时间) | $0.30 |
| 拼接 | ffmpeg concat,流复制 | 0.05 秒 | $0 |
| 合计 | 260.4 秒 | $0.3009 |
脚本调用计费 317 个输入 token 和 572 个输出 token(其中 349 个是推理 token),单价 $0.44 和 $1.32 每百万。这条产线里所有文本侧的决策实际上都是免费的。账单全在视频上,而它按秒数计价 —— 所以唯一重要的杠杆是你生成多少秒、用什么分辨率。
按 Seedance 2.0 Mini 的费率(480p 每秒 $0.02,720p 每秒 $0.04),每天一条 60 秒短视频,480p 大约每天 $1.20、720p $2.40。做满一年,480p 是 $438 的生成费,720p 是 $876。这才是值得拿来讨论的数字,而不是单片价格。
第一步:怎么把一个选题变成分镜脚本
一次 JSON 模式的对话调用,活是 system prompt 里的 schema 干的。
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"])
5.7 秒后原样返回的内容:
{"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."}]}
两个值得抄的细节。给每个分镜要一个 n,你就能安心地把文件命名成 shot1.mp4,而不必指望数组顺序在并行 map 之后还保持不变。以及把否定项写进 system prompt(不要屏幕文字、不要人物、不要水印),三个视频 prompt 就都带上了,你不用重复三遍;返回格式本身可以看 DeepSeek 的 JSON mode 文档。
旁白这几行目前还没有归宿。这一点先记着。
第二步:怎么并行生成三段片子
三个一起提交,然后只等一次。 视频接口是异步的,所以「线程池里各自提交并轮询」就是全部技巧。 并行调度三段视频生成任务时,若直接对接多个模型供应商会面临鉴权逻辑分散的问题,将请求统一路由至 OpenRouter 或 ofox.io 这类聚合网关,可以让三个并发任务共用同一套 HTTP 客户端配置,降低工程复杂度。
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"]))
三段片子分别在 85.1、126.7、253.2 秒完成。同一个模型、同样时长、同样分辨率,在同一秒提交。并行墙上时间 254.7 秒,串行则是 465.0 秒,线程池省了大约 45%,而最慢那一段仍然决定节奏。
在看到 completed 的同一个 worker 里就把文件下载下来。 unsigned_urls 只签名 24 小时,这个模型上没有第二份可回退的地址。这类异步坑我们在视频轮询实测里写全了。
第三步:怎么把三段拼起来
ffmpeg concat 流复制 —— 前提是三段完全一致。
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
这一步 0.05 秒,产出一个 15.296 秒、496x864、4.4 MB 的文件。流复制在这里能用,是因为三段片子的格式完全相同:24 fps 的 H.264、32 kHz 立体声 AAC、同样尺寸。ffmpeg concat demuxer 要求的正是这个。
这一次运行产出的三段片子的拼接与电平检查。
一旦你的素材混起来,它就不成立了。1080p 的 Seedance 2.5 片子回来是 HEVC 而不是 H.264,用 -c copy 和 H.264 片子拼在一起不会得到你想要的结果。要么一整条视频锁死一个模型一个分辨率,要么重编码:
ffmpeg -y -f concat -safe 0 -i list.txt -c:v libx264 -c:a aac -r 24 short.mp4
另外注意算术:三段 5.041 秒并不等于 15.000 秒。如果平台或剪辑流程要求精确时长,请在拼接之后裁,别假设。
为什么没有配音这一步
因为这个接口上没有 TTS 模型,而片子自带的音轨不是旁白。 这是所有基于视频 API 的无脸频道产线里那个诚实的缺口,通常会被含糊过去。
你拿到的是模型生成的环境音;你没拿到的是把脚本模型写的旁白读出来的人声。而且这条环境音自己还有问题:
| 片段 | 平均电平 | 峰值 |
|---|---|---|
| 第 1 段 | -35.0 dBFS | -19.2 dBFS |
| 第 2 段 | -27.0 dBFS | -12.8 dBFS |
| 第 3 段 | -24.3 dBFS | -7.4 dBFS |
同一批里差了将近 11 dB。 这些是 volumedetect 的 RMS 值不是 LUFS,别直接对着平台响度标准比;真正要紧的是片间落差,因为直接拼接会在每个剪辑点上听到一次音量跳变。一次 loudnorm 就能抹平:
ffmpeg -i short.mp4 -af loudnorm=I=-14:TP=-1.5:LRA=11 -c:v copy short_normalised.mp4
人声本身有三条路,没有一条是这个 API:把旁白送到专门的 TTS 厂商再混音、自己录、或者干脆不要人声、把旁白烧成字幕。考虑到短视频有多少是静音观看的,字幕并不像听上去那样是退而求其次。
想做成每天更新的频道,会坏在哪
三个地方,按伤人顺序排。
时间方差会搞垮调度。 相同请求 3 倍的时间跨度,意味着「08:00 渲染、08:05 发布」的 cron 迟早会发出个空气。提前渲染,把成品排队,从队列里发。
没有人在给脚本做事实核查。 第 2 个分镜要的是「有透明脑袋的管眼鱼」,回来的是一条大眼睛、没有透明穹顶的鱼 —— 错得恰好是搜过这个话题的观众一眼能看出来的那种。脚本模型写了一句真话,视频模型随手画歪了,而这条产线里没有任何一步会捕捉到这个落差。一天一条你还能用眼睛盯,一天十条就盯不过来了。
平台政策不是渲染问题。 YouTube 的频道变现政策要求原创且真实的内容,并明确点名批量制作和重复性内容;合作伙伴计划资格叠在它上面;TikTok 的内容分享指南管的是 API 发布这一侧。生成是制作工具。编辑判断、具体性、以及别人为什么要看这条视频,仍然得你来提供,而这些恰恰是 API 不卖的东西。
想清楚这点再用,这条产线就配得上它的位置:它把「做一条 15 秒的插图短片」从一个下午压缩成四分钟和三毛钱,这会改变什么事值得一试。第 2 步该选哪个模型,看按使用场景挑视频生成 API;为什么走量应该留在 Mini 上,看 Seedance 档位对比。
怎么让脚本模型和视频模型共用一把 key
上面这条产线碰了两种完全不同的模型:一个必须返回严格 JSON 的文本模型,和一个异步运行、按秒计费的视频模型。原生对接就是两家厂商、两套 SDK、两个后台、两份发票,外加你会发现文本厂商没有视频模型、视频厂商的文本模型不擅长 JSON。 脚本生成依赖文本模型、视频生成依赖多模态模型,两者原本需要维护不同供应商的 API Key,而通过 ofox.io 或 OpenRouter 提供的兼容 OpenAI 格式的单一端点,可以让两类调用在同一个 Authorization Header 下完成请求分发。
本文两个调用都打在同一个 base URL、用同一把 key:分镜走 /v1/chat/completions,片子走 /v1/videos。这是脚本只有六十行的唯一原因。我们跑在 ofox 上是因为它两种形状都暴露;任何做到同样事情的网关都能用。投入之前要验证的是文本侧对 response_format 的支持是否到位 —— 一条在期待 JSON 的地方拿回散文的产线,每次都会在第一步挂掉。先用一次调用测这个,再去建后面三步。

1250

被折叠的 条评论
为什么被折叠?



