Video API Proxy
🔒 Nội bộ · Dùng ngay · Không cần setup

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.

BASE URL 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ăngVendorRoute
Text → VideoDashScope/ds
Image → Video (+lip-sync)DashScope/ds
Face Swap (video)WaveSpeed/ws
Template Face FusionUfototrực tiếp
AI Effect (beatImage2Video)Ufototrực tiếp
AI Live Photo / Multi FaceUfototrự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)

bash
curl https://aivideo-proxy.manhpd-football.workers.dev/health
# → ok

2) Submit 1 job Text→Video

bash
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

bash
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.

📱 App──(X-App-Token)──▶ ☁️ Cloudflare Worker──(Bearer key)──▶ 🎬 DashScope / WaveSpeed
PrefixUpstream thậtAuth Worker gắn
/ds/*dashscope-intl.aliyuncs.comBearer ALI_KEY
/ws/*api.wavespeed.aiBearer WSK
Worker tự strip header nhận diện (authorization, cf-*, x-forwarded-*) trước khi chuyển tiếp, và viết lại URL poll của WaveSpeed để nó vẫn đi qua proxy.

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.

HeaderGiá trị
X-App-Tokenbắt buộc

Thiếu hoặc sai token → 401 Unauthorized. Riêng /health không cần token.

Token này chỉ mở đúng Worker nội bộ này. Có thể xoay bằng 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.

Ảnh/video của bạn được upload lên CDN vendor để xử lý. Mỗi lần chạy tiêu tốn quota thật; video có thể mất 1–5 phút. Trang tự poll mỗi 3s.

🧭 Dùng cái nào? — hướng dẫn chọn

Mỗi nhu cầu → chọn đúng chức năng + đầu vào cần chuẩn bị.

Bạn muốnChọn chế độCần đưa vàoVendorThời gian
Ghép mặt vào clip có sẵn (giống app)Ghép mặt vào mẫuChọn 1 mẫu + ảnh mặtUfoto~10s
Tạo ảnh từ mô tảText → ẢnhPrompt + kích thướcDezgo~5–15s
Sửa ảnh giữ mặt (chế siêu anh hùng…)Ảnh + Prompt → Ảnh (giữ mặt)Ảnh mặt rõ + prompt "giữ mặt, đổi…"WaveSpeed (Kontext/Gemini)~6s / ~1ph
Tạo video từ chữText → VideoPrompt (+ tick ⚡ Nhanh)DashScope~40s–2ph
Ảnh tĩnh → video chuyển độngImage → VideoẢnh + prompt (tùy chọn)DashScope~40s–2ph
Ghép mặt vào video của riêng bạnFace SwapVideo của bạn + ảnh mặtWaveSpeed~60–70s

Mẹo chạy nhanh & ít lỗi

  • ⚡ Nhanh (Text/Image→Video): tick để render 480p · 3s — nhanh hơn nhiều, hợp lúc test. Bỏ tick khi cần chất lượng cao.
  • Ảnh mặt: chọn ảnh rõ mặt, chính diện, đủ sáng. Trang tự nén ảnh (≤1280px, JPEG) nên HEIC/ảnh to vẫn OK.
  • strength (Ảnh→Ảnh): 0.3–0.5 giữ gần ảnh gốc; 0.7–0.95 biến đổi mạnh theo prompt.
  • Lỗi mạng/máy chủ tạm thời: trang tự thử lại (poll & upload) — cứ để chạy.
  • Video mất 1–5 phút là bình thường (phía vendor), không phải treo. Có đồng hồ đếm giây + log tiến trình.

Lỗi hay gặp

Báo lỗiNguyên nhân / cách xử lý
Không phát hiện khuôn mặtẢnh mặt mờ/nghiêng/tối → chọn ảnh rõ, chính diện.
Không đủ creditQuota vendor hết — đổi key riêng của bạn.
Ảnh bị từ chốiNội dung ảnh không hợp lệ → thử ảnh khác.
Hết thời gian chờVendor quá tải → chạy lại, hoặc tick ⚡ Nhanh.

Text → Video

Sinh video từ prompt bằng model wan2.6-t2v.

POST/ds/api/v1/services/aigc/video-generation/video-synthesis

Header bắt buộc: X-App-Token, Content-Type: application/json, X-DashScope-Async: enable.

Body

FieldKiểuMô tả
modelstringwan2.6-t2v
input.promptstringMô tả video bắt buộc
parameters.sizestringvd 1280*720
parameters.durationintsố giây, vd 5
json · request
{
  "model": "wan2.6-t2v",
  "input": { "prompt": "a cat walking on the beach at sunset" },
  "parameters": { "size": "1280*720", "duration": 5 }
}
json · response
{
  "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.

POST/ds/api/v1/services/aigc/video-generation/video-synthesis

Ảnh/audio phải là URL công khai. Nếu là file local → upload trước qua WaveSpeed Upload để lấy CDN URL.

FieldKiểuMô tả
modelstringwan2.6-i2v-flash
input.img_urlstringURL ảnh bắt buộc
input.promptstringtùy chọn
input.audio_urlstringlip-sync tùy chọn
parameters.resolutionstringvd 720P
parameters.durationintvd 5
json · request
{
  "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.

GET/ds/api/v1/tasks/{task_id}

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
SUCCEEDEDxong → output.video_url
FAILEDlỗi → output.code / message
json · response (SUCCEEDED)
{
  "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.

POST/ws/api/v3/media/upload/binary?ext={ext}

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.

json · response
{ "data": { "download_url": "https://cdn.wavespeed.ai/..." } }

Face Swap (WaveSpeed)

Ghép mặt vào video.

POST/ws/api/v3/wavespeed-ai/video-face-swap

Body JSON gồm URL video + URL ảnh mặt (đã upload). Trả về URL để poll.

json · request
{ "video": "https://cdn.../src.mp4", "face_image": "https://cdn.../face.jpg" }
json · response
{ "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 processingpoll tiếp
completedxong → data.outputs[0]
failedlỗ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:

HeaderGiá trị
cpins.story.unfold
version3.35.876 (fusion) · 876 (effect/live)
platform1
timeStampepoch giây, vd 1754900000
signmd5(<signKey> + timeStamp) — ký server-side, hex thường
userId16 ký tự hex ngẫu nhiên (giữ cố định/thiết bị)
Hosts: 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

POSTface-api.ufotosoft.com/file/style/multipleUpload?cp&platform

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".

json · response
{ "c": 200, "d": [ "https://.../face.jpeg" ] }

Ufoto — Face Fusion (template & multi-face)

POSTface-api.ufotosoft.com/style/v1/videoFaceFusion

Query: cp, platform, projectId, modelId, templateId, imageUrls, level=0. Multi-face: lặp nhiều imageUrls theo thứ tự slot.

json · response
{ "c": 200, "d": { "jobId": "..." } }

Poll — POST /style/v1/queryVideoFaceFusion?cp&platform&jobId

json · response (xong)
{ "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)

POSTcpi.ufotosoft.com/algo/v1/faceFusion/aigc/beatImage2Video/createNormalTask?ifWise=true

Chạy trên version=876. Body JSON:

json · request
{ "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)

POSTcpi.ufotosoft.com/algo/v1/faceFusion/aigc/livePortraitCreate?ifWise=true
json · request
{ "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.

Text → Ảnh (Dezgo)

Sinh ảnh từ mô tả. Gọi qua proxy, key Dezgo chèn server-side.

POST/proxy/dezgo/text2image

Body application/x-www-form-urlencoded. Trả về ảnh binary (PNG/JPEG) trực tiếp.

FieldKiểuMô tả
promptstringMô tả ảnh bắt buộc
negative_promptstringThứ cần tránh tùy chọn
modelstringvd dreamshaper_8, absolute_reality_1_8_1 tùy chọn
stepsint20–50 (mặc định ~25)
width / heightint512 / 768 / 1024
bash
curl -X POST https://video-api-docs.pages.dev/proxy/dezgo/text2image \
  --data-urlencode "prompt=a knight in shining armor, epic" \
  --data-urlencode "model=dreamshaper_8" \
  --data-urlencode "negative_prompt=blurry, deformed" \
  --data-urlencode "steps=25" --data-urlencode "width=768" --data-urlencode "height=768" \
  --output out.png

Model dòng SD1 (dùng cho cả text2image & image2image): dreamshaper_8 absolute_reality_1_8_1 realistic_vision_5_1 cyberrealistic_3_3 epic_diffusion_1_1 stablediffusion_1_5

🦸 Sửa ảnh giữ mặt (Ảnh + Prompt → Ảnh)

Gửi ảnh + mô tả → đổi trang phục/bối cảnh nhưng giữ nguyên khuôn mặt. Đây là cách đúng cho "chế thành siêu anh hùng". Chạy qua WaveSpeed (đã proxy sẵn).

ModelGiữ mặtTốc độEndpoint
Flux Kontext Pro (mặc định)tốt~6swavespeed-ai/flux-kontext-pro
Nano-Banana / Geminitốt nhất~1 phútgoogle/nano-banana/edit

Luồng (2 bước)

  1. Upload ảnh gốc → CDN: POST /proxy/ws/api/v3/media/upload/binary?ext=jpgdata.download_url
  2. Gọi model + prompt → poll tới khi xong:
POST/proxy/ws/api/v3/wavespeed-ai/flux-kontext-pro
json · request
{ "prompt": "keep the exact same face, only change clothing into a superhero costume",
  "image": "https://cdn.../src.jpg" }
// Nano-Banana dùng "images": [url] (số nhiều) thay cho "image"

Trả data.urls.get → poll GET → data.status == "completed"data.outputs[0] (URL ảnh).

Bí quyết giữ mặt: prompt nói rõ "keep the exact same face, only change…"; tránh mask; ảnh input rõ mặt, chính diện. Muốn mặt đúng 100% của bạn → dùng Face Swap (tạo cảnh rồi ghép mặt thật).

Code mẫu

Gọi Text→Video + poll.

Swift (async/await)

swift
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

bash
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ách3 giây
Số lần tối đa200 (~10 phút)
Lần poll đầuchờ 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

Ý nghĩa
401Sai/thiếu X-App-Token
404Prefix không phải /ds hay /ws
InvalidApiKey / ArrearageKey vendor hỏng/hết tiền → xoay key trên Worker

Ufoto (field c)

cÝ nghĩa
1022jobId chưa đăng ký — poll tiếp
1011Không phát hiện khuôn mặt
2002Khô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).

bash
# 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).

Key vendor hiện tại là của bên thứ ba lấy từ Remote Config — có thể bị thu hồi bất cứ lúc nào. Sản phẩm chính thức nên dùng tài khoản DashScope + WaveSpeed riêng.
🎬 Video API Proxy · Tài liệu nội bộ · Cập nhật giá trị token/URL khi xoay key.