教程
通过 API 为视频添加文字:字幕与叠加层工作流
为什么视频上的文字是一个 API 设计问题,而不仅仅是一个提示词
给生成的视频添加文字看起来就像改一行提示词:描述字幕,发送请求,拿到一段带字的片段。但在实践中,一旦你需要字幕可读、有时序、可本地化,或能跨场景复用,提示词就不再是有意思的部分了。架构才是。
有两条可行的路径,它们通向不同的代码库:
- 生成时原生文字。 你让视频模型把文字作为场景的一部分渲染出来。一次请求,一个素材,无需后处理。
- 干净素材加叠加层。 你先生成不带文字的素材(或要求无文字的构图),然后在第二个渲染步骤中合成字幕和叠加层。
为什么这不仅仅是美观问题?因为下游的一切都取决于这个选择。如果文字存在于生成的像素中,你就无法轻易地重新设计样式、翻译、按语音对齐时序,或在不重新生成片段的情况下修正错别字。如果文字存在于单独的叠加层中,你就掌握了布局、字体和时序,但你现在要自己拥有一个渲染器,并在流水线中多一个请求。
本指南的其余部分把视频上的文字当作它在生产环境中通常变成的东西来处理:一个数据问题(什么文字、在哪里、何时)加上一个渲染问题(谁来绘制它)。一旦你把这两者分开,API 调用就变得直截了当,而且你可以在不重写字幕逻辑的情况下在生成后端之间切换。
一个有用的心智模型是始终把三件事区分开来:内容(文字本身)、时序(它们何时出现)和呈现(字体、位置、样式)。原生生成把这三者都压缩成一个不透明的步骤。叠加层让这三者都保持显式且可编辑。两者都不是普遍更好的;目标是在你编写集成之前知道你要承诺的是哪一个。
路径一:在生成时通过提示词实现原生屏幕文字
原生文字意味着生成模型把字符绘制进视频帧中。你得到一个文字已烘焙进去的单一输出素材,流水线保持简短:构建提示词、提交、轮询、交付。
提示词中要放什么
模型渲染的字体排版比合成文字更不可预测,所以把提示词当作狭窄的指令,而不是描述整个设计系统的地方。实用建议:
- 保持文字简短。 每个镜头几个词。长句子容易导致拼写漂移和布局拥挤。
- 用引号引用字面字符串。 如果文字必须精确出现,就把它们放在引号里并要求那个确切的文本。模糊的描述产生模糊的结果。
- 明确说明位置和样式。 “居中的大号白色文字放在下三分之一处”是可执行的。“好看的标题”不是。
- 每个镜头一个文字元素。 在同一个片段中要求标题、副标题和水印会成倍增加失败模式。
- 说明语言。 如果文案不是英文,要说明,因为字形覆盖范围各不相同。
预期返回什么
把输出当作草稿,而不是规格。即使好的结果也可能出现字形变形、间距不一致或被运动部分遮挡。在交付前始终检查渲染出的帧;一个廉价的程序化检查加上对首尾含文字帧的人工一瞥能抓住大多数问题。
这条路径何时胜出
当文字是附带的、氛围性的时候,原生文字是正确的默认选择:背景街道上的招牌、产品上的风格化标志、你愿意在出错时重新生成的短标题卡。当你确实负担不起第二个渲染步骤时它也胜出,例如在紧迫的周转流程中,每个片段一次请求就是预算。
代价是控制力。措辞、样式或时序的任何改变都意味着新的生成请求,而且结果在重试之间不是确定性的。设计你的提示词和存储,使重新生成的片段能够干净地替换旧的。
路径二:生成干净素材并自行合成文字
叠加层路径把工作一分为二。首先你生成刻意不带文字的素材。然后你在上面合成字幕和叠加层,使用渲染服务或你自己的媒体流水线。
先生成干净素材
要求不带文字的构图:描述场景、主体、摄像机运动和光线,并明确避免要求屏幕上的文字。如果模型倾向于编造招牌或 UI 文字,就提到画面应包含无可读文字。这里的输出是一个基础素材——一个稳定的输入,你可以跨许多文字变体复用它。
后合成
叠加层步骤消耗两个输入:基础素材和结构化的字幕/叠加层载荷。因为你控制渲染器,你得到提示词无法保证的属性:
- 确定的字体和大小。 相同的载荷每次渲染都完全一致,这对品牌一致性很重要。
- 精确的时序。 字幕可以精确到帧地对齐语音、音乐节拍或场景切换。
- 安全区域和布局规则。 你可以强制边距、避开人脸和关键主体,并把文字保持在平台特定的安全区域内。
- 廉价的迭代。 修正错别字或翻译字幕是重新渲染叠加层,而不是重新生成视频。
- 可组合性。 水印、下三分之一条和字幕可以独立分层,并按输出切换。
权衡取舍
叠加层路径用模型依赖换取流水线复杂性。你增加第二个请求,你需要某个地方存储中间素材,而且你要拥有渲染配置。作为交换,文字变成了你可以版本化、测试和替换的数据。对于任何有真实字幕需求的东西——无障碍字幕、多语言变体、模板化社交格式——这个交易几乎总是值得的。
一个实用的模式是非破坏性地渲染叠加层:把基础素材和字幕载荷作为唯一事实来源,把合成后的视频当作一个你总能重新生成的派生产物。
设计一个能在两条路径下存活的字幕载荷
如果你把字幕数据设计好,原生生成和叠加层之间的选择就变成一个渲染细节,而不是重写。目标是单一载荷,它既能序列化成提示词用于原生文字,也能喂给渲染器用于叠加层。
核心字段
把每个文字元素建模为一个对象,内容、时序和呈现保持分离:
{
"track": "captions",
"items": [
{
"text": "Everything you need to know",
"start": 0.0,
"end": 2.4,
"style": { "role": "title", "align": "center", "position": "lower_third" },
"lang": "en"
}
]
}
关键设计决策:
- 时间以秒为单位,浮点数。 整个项目使用单一时间基准,仅在渲染时转换为帧。
- 按角色而非绝对值设定样式。 命名像
title、caption和lower_third这样的角色,并在渲染器中把它们解析为字体和大小。这使载荷可跨格式移植。 - 轨道名称。 分开
captions、titles和watermark轨道,以便按输出启用或禁用轨道。 - 每项的语言标签。 字体选择和后续翻译通道都需要它。
- 文本字段中不烘焙呈现信息。 永远不要把样式提示放进文字里;那正是样式对象的用途。
从一个 schema 供给两条路径
对于原生生成,写一个小适配器,挑选最重要的项并将其转换为提示词指令:字面文本、它的位置和一个样式描述符。有意让这个适配器有损——原生渲染无论如何都无法遵循完整的字幕轨道。
对于叠加层,把载荷传给渲染器,渲染器解析角色和位置,并在每个项的时间窗口内绘制它。
回报是:你的应用代码构建一个字幕对象,从原生切换到叠加层(或为特定片段切回)是配置更改,不是数据迁移。
一个可用的叠加层请求,逐步说明
以下是叠加层路径的序列,以 API 形式的步骤表达。确切的端点名称和参数取决于提供商,但形状是稳定的,一个薄抽象层能让它跨后端保持稳定。
步骤 1:提交干净素材生成
POST /video/generations
{
"prompt": "A slow dolly shot through a modern office, no on-screen text, natural light",
"duration_seconds": 6,
"aspect_ratio": "16:9"
}
保持提示词无文字,并存储返回的任务标识符。
步骤 2:轮询直到素材就绪
GET /video/generations/{job_id}
用退避而非紧凑循环进行轮询。把 queued、running、succeeded 和 failed 等状态当作代码中的一等状态,并持久化结果素材 URL 或 ID,这样崩溃不会丢失生成结果。
步骤 3:提交叠加层渲染
POST /video/overlays
{
"source_asset": "asset_abc123",
"captions": {
"track": "captions",
"items": [
{ "text": "Shipping today", "start": 0.4, "end": 2.0, "style": { "role": "title", "position": "lower_third", "align": "center" }, "lang": "en" },
{ "text": "No rebuild required", "start": 2.2, "end": 4.0, "style": { "role": "title", "position": "lower_third", "align": "center" }, "lang": "en" }
]
}
}
注意,叠加层请求只携带源素材和文字数据。它不重新生成素材,这正是迭代廉价的原因。
步骤 4:取回合成结果
用轮询生成任务的同样方式轮询叠加层任务,然后获取最终素材。把基础素材、字幕载荷和输出一起存储,使合成可复现。
在代码中,把步骤 1–4 包装成一个函数,它接受一个提示词和一个字幕载荷并返回最终素材。那个函数是你真正的接口;各个端点是可以替换的实现细节。
发布前的可靠性检查
视频文字流水线以安静的方式失败:被裁切的字幕、翻译错误的行、从未被清理的重新生成。在用户看到它们之前添加检查。
验证渲染的文字
- 原生路径: 在每个文字窗口的起点和终点采样帧并检查它们。尽可能自动化基本的可读性检查,但对面向客户的文案保留人工审核步骤,因为模型字体排版可能不可预测地退化。
- 叠加层路径: 在渲染前验证载荷——每个项的时序不重叠、在片段时长内、使用受支持的语言和角色。渲染后,在项边界抽查帧以确认没有任何内容被裁掉。
让重试安全
- 提交请求上的幂等键,这样重试的网络调用不会创建重复的生成任务。
- 每个任务的状态机:queued → running → succeeded/failed,持久化,这样重启会恢复而不是重新开始。
- 有界轮询,带退避和超时,呈现清晰的失败而不是永久循环。
- 存储中间素材。 如果叠加层步骤失败,你不应该花钱重新生成你已经有的素材。
控制整条链路的成本
叠加层路径有两个计费步骤——生成和渲染——所以两者都要监测。跟踪你重新生成素材与仅重新渲染叠加层的频率;如果素材重新生成占主导,你的原生/叠加层划分对该工作负载可能不对。按 (基础素材, 字幕载荷) 缓存最终素材,这样对同一输出的重复请求是免费的。
可观测性
把提示词、字幕载荷哈希、任务 ID 和最终素材 ID 一起记录。当用户报告字幕错误时,你希望能在几秒内确定问题出在生成、数据还是渲染。把字幕载荷作为版本化的唯一事实来源,这样你总能解释渲染器被要求绘制什么。
选择你的默认工作流
把这作为起始规则,并随着产品成长重新审视它。
选择原生文字当文字是装饰性或附带的、措辞简短、你能接受重新生成作为修正错误的方式,而且添加第二个渲染步骤对该片段不值得工程时间。
选择叠加层路径当文字必须精确、有时序、被翻译、重新设计样式或跨多个输出复用。字幕、副标题、下三分之一条和模板化营销格式几乎总是落在这里。额外的请求是确定性的代价。
混合是合理的。 许多团队默认生成干净素材,对任何重要的东西依赖叠加层,把原生文字保留给一次性氛围镜头。因为两条路径消耗相同的字幕载荷,你可以对每个片段做这个决定而无需特殊处理你的数据。
无论你选哪个,把集成保持在薄抽象之后:一个函数,接受提示词和字幕载荷并返回最终素材。那个边界正是让你能切换生成后端、添加渲染选项,或在工作流之间移动单个片段而不触碰花了功夫才弄对字幕逻辑的东西。