摘要:接这套视频翻译接口时,最容易踩的是默认打到海外站、建任务丢了幂等键、以及原片硬字幕没先擦。下面按官方示例仓库把国内站流程和几个坑记下来,方便对照改自己的脚本。
前阵子要把一批口播成片出英语版,配音和字幕都要。网页端单条试没问题,批量就想走 API。官方在 GitHub 放了翻译接入示例,Python 和 cURL 都有,仓库里没有模型,处理都在服务端完成:
https://github.com/DayBreak-u/unmarkai-video-translation-api
下面按国内站写。字段和路径跟海外站相同,差在接口地址、密钥和计费币种。英文 README 对着海外站,中文 README 才是国内说明。
整体流程
一个视频异步走四步:
POST /v1/uploads申请上传票据,再把文件 PUT 到返回的签名地址;视频已在自己 CDN 上时,可改成POST /v1/url-uploads传source_url,再轮询GET /v1/uploads/{upload_id}直到状态变成uploaded;POST /v1/jobs,type填video_translation,带上目标语言等信息;- 轮询
GET /v1/jobs/{job_id},直到succeeded(失败或取消是终态); - 从结果字段下载能拿到的文件,一般是译制视频、源字幕、译文字幕,有时还有音频。
国内 REST 基址是https://unmarkai.cn/api/v1。控制台用手机号登录,在里面创建uma_live_开头的密钥。手机号登录会话不能拿来调接口。翻译目前只能走 REST——国内站的 MCP 只有去字幕相关工具,没有翻译。一个任务只对应一种目标语言。
URL 导入只支持 HTTPS 直链,视频网站播放页不行。仓库没有附演示素材,先拿一段有权处理的短视频试。
环境变量先设对
示例里的client.py/quickstart.sh读UNMARKAI_API_BASE,不设就默认海外地址。国内站和海外站的密钥、钱包是分开的,互相不通用。我第一次踩坑就是:国内控制台建了 key,脚本却打到海外默认 BASE,报错信息还不直观,查了半天才对上。
gitclone https://github.com/DayBreak-u/unmarkai-video-translation-api.gitcdunmarkai-video-translation-api python3-mvenv .venv&&source.venv/bin/activate python-mpipinstall-rrequirements.txtexportUNMARKAI_API_BASE='https://unmarkai.cn/api/v1'exportUNMARKAI_API_KEY='uma_live_xxx'python examples/translate_video.py--file./source.mp4--targetenHTTPS 直链版本:
python examples/translate_video.py--url'https://your-cdn.example/owned-video.mp4'--targeten继续查已有任务、不会重新创建:
python examples/translate_video.py --job-id job_REPLACE已有 upload、只补建任务时,把幂等键自己带上:
python examples/translate_video.py --upload-id upl_REPLACE--targeten --idempotency-key your-stable-request-id常用参数:--target新任务必填,例如en/ja/es;--source默认auto;--advanced走高级翻译计价;--no-subtitles不烧录字幕,只保留配音。OpenAPI 里还有音色、目标地区、背景音、字幕样式等字段,示例脚本没全部做成命令行,需要时按契约加到请求体。可用音色先调GET /v1/voices。结果默认落在output/。.env.example只是模板,脚本不会自动读,密钥放服务端环境变量里。
建任务请求体
上传完成后,建任务大致如下:
importos,uuid,requests BASE=os.environ.get("UNMARKAI_API_BASE","https://unmarkai.cn/api/v1").rstrip("/")AUTH={"Authorization":f"Bearer{os.environ['UNMARKAI_API_KEY']}"}key=str(uuid.uuid4())r=requests.post(f"{BASE}/jobs",headers={**AUTH,"Idempotency-Key":key,"Content-Type":"application/json"},timeout=60,json={"type":"video_translation","input":{"upload_id":"upl_xxx"},"source_language":"auto","target_language":"en","advanced_quality":False,"subtitle_enabled":True,},)r.raise_for_status()job=r.json()print(job["id"],key)# 两个都要记下来curl 想先跑通上传和建任务,可以用仓库脚本(需要 jq):
exportUNMARKAI_API_BASE='https://unmarkai.cn/api/v1'exportUNMARKAI_API_KEY='uma_live_xxx'TARGET_LANGUAGE=enbashexamples/quickstart.sh ./source.mp4它只负责上传、建任务、查一次状态。后续有界轮询和下载,接 Python 示例的--job-id。
幂等键、签名上传、轮询超时
建任务的Idempotency-Key要自己持久化。网络抖了、不确定创建响应有没有落到自己手里时,正确做法是用同一个键和完全相同的请求体重发,而不是重新--file跑整条流水线。重新上传会产生新的upload_id,即便幂等键相同,请求体已经变了,等于又开了一个任务。URL 导入那条路径对幂等键要求更严,漏了会直接失败。
签名 PUT 只能用一次。票据被消费后再 PUT,可能返回 412。上传失败不要无脑重试 PUT,先查这个 upload 的状态,再决定要不要重新申请票据。PUT 必须把票据返回的 headers 原样带上,而且不能带 API key。下载结果的签名链接同样不需要 Authorization。示例对瞬态网络错误和 429/502/503/504 会有限次退避重试,但不会盲目重试已消费的签名 PUT,也不会对没带幂等键的创建请求瞎重放。
本地轮询超时不等于服务端取消任务。脚本退出了,任务可能还在跑,用--job-id接着查即可。结果下载链接会过期;还在保留期内的话,重新查询任务可以拿到新链接,拿到后尽快落盘。
原片有硬字幕:翻译不会帮你擦
这点要单独说。video_translation不会擦掉画面里已经烧进去的硬字幕。旧字还在,新字幕一烧,就是两层字。正确顺序是:先走去字幕接口拿到干净母版,再重新上传、建翻译任务。这是两个任务,分别计费。去字幕示例在:
https://github.com/DayBreak-u/unmarkai-video-text-removal-api
网页端如果混用「画面字幕 OCR」当识别源,也只是读字,不会改像素。API 侧同理,别指望翻译任务顺手把硬字幕清掉。
批量脚本里建议把 upload ID、job ID、幂等键打进同一行日志,方便对账。任务失败时看响应里的error.code和error.request_id,比只看 HTTP 状态码好排查。音色、字幕布局这类细字段以当前 OpenAPI 为准,示例仓库会跟着契约改,接入前最好再拉一次 README.zh-CN.md。
限制(以仓库中文说明为准)
按秒向上取整,最低 15 秒。单价和余额以GET /v1/balance返回为准。单个视频最多 2 GiB、60 分钟,格式支持 MP4、MOV、AVI、MKV、WebM、MPEG,以服务端校验为准。任务可轮询,也可在控制台配回调,示例用的是轮询。
短视频先跑通再上批量。术语表和角色称呼最好在脚本外维护一份,和网页端抽检用同一套。
只处理自己拥有、已获授权或明确允许编辑的视频。仓库里python -m unittest discover -s tests -v跑的是模拟响应,不会上传,也不会扣费。示例代码 MIT,托管服务按服务方条款使用。有问题去仓库提 issue。