Video API Proxy
Proxy nội bộ cho các app AI video. App chỉ gọi 1 URL kèm token — key vendor (DashScope, WaveSpeed) nằm an toàn trên Cloudflare Worker, không lộ trong binary.
https://aivideo-proxy.manhpd-football.workers.dev
Tổng quan
Một endpoint duy nhất cho toàn bộ chức năng sinh video.
Hệ thống gồm 6 chức năng gen video qua 3 vendor. DashScope & WaveSpeed đi qua Worker proxy (prefix /ds, /ws); Ufoto app gọi thẳng (tự ký sign).
| Chức năng | Vendor | Route |
|---|---|---|
| Text → Video | DashScope | /ds |
| Image → Video (+lip-sync) | DashScope | /ds |
| Face Swap (video) | WaveSpeed | /ws |
| Template Face Fusion | Ufoto | trực tiếp |
| AI Effect (beatImage2Video) | Ufoto | trực tiếp |
| AI Live Photo / Multi Face | Ufoto | trực tiếp |
Luồng chung mọi chức năng: submit job → nhận task_id/URL → poll mỗi 3s tới khi có video hoặc lỗi (tối đa ~10 phút) → tải video về.
Dùng ngay
3 bước là chạy, không cần Firebase/SDK.
1) Health check (không cần token)
curl https://aivideo-proxy.manhpd-football.workers.dev/health # → ok
2) Submit 1 job Text→Video
curl -X POST \ https://aivideo-proxy.manhpd-football.workers.dev/ds/api/v1/services/aigc/video-generation/video-synthesis \ -H "X-App-Token:" \ -H "Content-Type: application/json" \ -H "X-DashScope-Async: enable" \ -d '{"model":"wan2.6-t2v","input":{"prompt":"a cat on the beach"},"parameters":{"size":"1280*720","duration":5}}'
3) Poll tới khi xong
curl https://aivideo-proxy.manhpd-football.workers.dev/ds/api/v1/tasks/{task_id} \ -H "X-App-Token:" # output.task_status: PENDING → RUNNING → SUCCEEDED (kèm output.video_url)
Kiến trúc
App → Worker → Vendor. Key thật chỉ nằm ở Worker.
| Prefix | Upstream thật | Auth Worker gắn |
|---|---|---|
/ds/* | dashscope-intl.aliyuncs.com | Bearer ALI_KEY |
/ws/* | api.wavespeed.ai | Bearer WSK |
Ufoto không qua Worker — app gọi thẳng cpi.ufotosoft.com / face-api.ufotosoft.com và tự ký sign (xem mục Ký request).
Xác thực
Mọi request tới proxy phải mang header token.
| Header | Giá trị | |
|---|---|---|
X-App-Token | | bắt buộc |
Thiếu hoặc sai token → 401 Unauthorized. Riêng /health không cần token.
wrangler secret put APP_PROXY_TOKEN rồi cập nhật lại app (xem mục Tự dựng Worker).🧪 Test thật
Gõ prompt → tạo video thật qua proxy → xem kết quả ngay tại đây.
Text → Video
Sinh video từ prompt bằng model wan2.6-t2v.
Header bắt buộc: X-App-Token, Content-Type: application/json, X-DashScope-Async: enable.
Body
| Field | Kiểu | Mô tả |
|---|---|---|
model | string | wan2.6-t2v |
input.prompt | string | Mô tả video bắt buộc |
parameters.size | string | vd 1280*720 |
parameters.duration | int | số giây, vd 5 |
{
"model": "wan2.6-t2v",
"input": { "prompt": "a cat walking on the beach at sunset" },
"parameters": { "size": "1280*720", "duration": 5 }
}{
"output": { "task_id": "06ad4c22-...", "task_status": "PENDING" }
}Lấy output.task_id rồi chuyển sang Poll task.
Image → Video
Sinh video từ ảnh (kèm prompt & audio lip-sync tùy chọn) bằng wan2.6-i2v-flash.
Ảnh/audio phải là URL công khai. Nếu là file local → upload trước qua WaveSpeed Upload để lấy CDN URL.
| Field | Kiểu | Mô tả |
|---|---|---|
model | string | wan2.6-i2v-flash |
input.img_url | string | URL ảnh bắt buộc |
input.prompt | string | tùy chọn |
input.audio_url | string | lip-sync tùy chọn |
parameters.resolution | string | vd 720P |
parameters.duration | int | vd 5 |
{
"model": "wan2.6-i2v-flash",
"input": {
"img_url": "https://cdn.../face.jpg",
"prompt": "gentle smile, cinematic",
"audio_url": "https://cdn.../voice.mp3"
},
"parameters": { "resolution": "720P", "duration": 5 }
}Poll task (DashScope)
Dùng chung cho Text→Video và Image→Video.
Chỉ cần header X-App-Token. Gọi lại mỗi 3 giây.
| task_status | Ý nghĩa |
|---|---|
| PENDING RUNNING | đang xử lý → poll tiếp |
| SUCCEEDED | xong → output.video_url |
| FAILED | lỗi → output.code / message |
{
"output": {
"task_status": "SUCCEEDED",
"video_url": "https://.../result.mp4"
}
}Upload file (WaveSpeed)
Đưa file local lên CDN để lấy URL dùng cho i2v / face swap.
Gửi raw bytes trong body. ?ext= giúp server nhận đúng định dạng (vd jpg, mp4, mp3). Header Content-Type theo loại file.
{ "data": { "download_url": "https://cdn.wavespeed.ai/..." } }Face Swap (WaveSpeed)
Ghép mặt vào video.
Body JSON gồm URL video + URL ảnh mặt (đã upload). Trả về URL để poll.
{ "video": "https://cdn.../src.mp4", "face_image": "https://cdn.../face.jpg" }{ "data": { "urls": { "get": "https://aivideo-proxy.../ws/..." } } }Poll
GET vào URL data.urls.get (kèm X-App-Token) mỗi 3s:
| data.status | Ý nghĩa |
|---|---|
| created processing | poll tiếp |
| completed | xong → data.outputs[0] |
| failed | lỗi → data.error |
Ufoto — Ký request
Các chức năng Template/Effect/Live gọi thẳng Ufoto, không qua proxy.
Mỗi request gắn bộ header sau:
| Header | Giá trị |
|---|---|
cp | ins.story.unfold |
version | 3.35.876 (fusion) · 876 (effect/live) |
platform | 1 |
timeStamp | epoch giây, vd 1754900000 |
sign | md5("IZCU5z" + timeStamp) — hex thường |
userId | 16 ký tự hex ngẫu nhiên (giữ cố định/thiết bị) |
cpi.ufotosoft.com (upload/effect/live) · face-api.ufotosoft.com (fusion). Server thực tế không verify sign/userId nhưng vẫn nên gửi đúng.Ufoto — Upload face
Multipart field files (số nhiều). Filename phải unique (UUID) — nếu trùng, CDN dedup trả về ảnh cũ → lỗi "no face".
{ "c": 200, "d": [ "https://.../face.jpeg" ] }Ufoto — Face Fusion (template & multi-face)
Query: cp, platform, projectId, modelId, templateId, imageUrls, level=0. Multi-face: lặp nhiều imageUrls theo thứ tự slot.
{ "c": 200, "d": { "jobId": "..." } }Poll — POST /style/v1/queryVideoFaceFusion?cp&platform&jobId
{ "c": 200, "d": { "videoFaceFusionOutput": { "videoUrl": "https://.../out.mp4" } } }c==200 mà chưa có videoUrl → còn render, poll tiếp. c==1022 → jobId chưa đăng ký, poll tiếp.
Ufoto — AI Effect (beatImage2Video)
Chạy trên version=876. Body JSON:
{ "imageUrls": ["https://.../face.jpg"], "level": 0, "effectType": 12 }Trả d.taskId. Poll: POST .../beatImage2Video/queryTask?ifWise=true body {"taskId":"..."} → d.taskStatus == "success" + d.videoUrl.
Ufoto — AI Live Photo (livePortrait)
{ "imageUrls": ["https://.../face.jpg"], "modelId": "<song id>", "ifFace": true, "level": 0 }Trả d.taskId. Poll: POST .../livePortraitQuery?ifWise=true body {"taskId":"..."} → d.taskStatus == "success" + d.videoUrl.
Code mẫu
Gọi Text→Video + poll.
Swift (async/await)
let base = "https://aivideo-proxy.manhpd-football.workers.dev" let token = "" func submitT2V(prompt: String) async throws -> String { var req = URLRequest(url: URL(string: base + "/ds/api/v1/services/aigc/video-generation/video-synthesis")!) req.httpMethod = "POST" req.setValue(token, forHTTPHeaderField: "X-App-Token") req.setValue("application/json", forHTTPHeaderField: "Content-Type") req.setValue("enable", forHTTPHeaderField: "X-DashScope-Async") req.httpBody = try JSONSerialization.data(withJSONObject: [ "model": "wan2.6-t2v", "input": ["prompt": prompt], "parameters": ["size": "1280*720", "duration": 5] ]) let (data, _) = try await URLSession.shared.data(for: req) let json = try JSONSerialization.jsonObject(with: data) as! [String:Any] return ((json["output"] as! [String:Any])["task_id"] as! String) }
cURL
curl -X POST "$BASE/ds/api/v1/services/aigc/video-generation/video-synthesis" \ -H "X-App-Token: $TOKEN" -H "Content-Type: application/json" \ -H "X-DashScope-Async: enable" \ -d '{"model":"wan2.6-t2v","input":{"prompt":"..."},"parameters":{"size":"1280*720","duration":5}}'
Polling
Chiến lược chờ kết quả.
| Tham số | Giá trị |
|---|---|
| Khoảng cách | 3 giây |
| Số lần tối đa | 200 (~10 phút) |
| Lần poll đầu | chờ 3s trước khi poll (để job kịp đăng ký) |
Chỉ dừng khi đạt trạng thái terminal (thành công/thất bại) hoặc chạm giới hạn thời gian.
Mã lỗi
Proxy / DashScope
| Mã | Ý nghĩa |
|---|---|
| 401 | Sai/thiếu X-App-Token |
| 404 | Prefix không phải /ds hay /ws |
InvalidApiKey / Arrearage | Key vendor hỏng/hết tiền → xoay key trên Worker |
Ufoto (field c)
| c | Ý nghĩa |
|---|---|
| 1022 | jobId chưa đăng ký — poll tiếp |
| 1011 | Không phát hiện khuôn mặt |
| 2002 | Không đủ credit |
| 5000 | Ảnh bị từ chối |
Tự dựng Worker
Khi cần proxy riêng (đổi tài khoản, xoay key).
# trong thư mục proxy-worker/ npx wrangler login npx wrangler secret put ALI_KEY # key DashScope npx wrangler secret put WSK # key WaveSpeed npx wrangler secret put APP_PROXY_TOKEN # token tự đặt npx wrangler deploy # in ra URL Worker
Sau đó cập nhật BASE URL + X-App-Token trong app. Xoay key: chạy lại secret put + deploy, không cần build lại app (trừ khi đổi token).