📘
เอกสารเทคนิคฉบับสมบูรณ์
SPVDO AI Video System — สร้างวิดีโอโฆษณาสินค้า Shopee ด้วย Google Veo
โดเมน https://spvdo.sj88ai.com · อัปเดตล่าสุด 8 ก.ย. 2026 · เวอร์ชัน 1.0
1ภาพรวมระบบ
ระบบสร้างวิดีโอโฆษณา AI ติดตั้งแยกบริการ (service) ของตัวเอง ทำงานคู่กับเว็บหลัก spvdo (docker) — ผู้ใช้กดปุ่มบนเว็บหลักแล้วเด้งมาระบบนี้ผ่าน path /v1/
🛒 เว็บหลัก spvdo (docker)→
ปุ่ม 🎬 ทำ VDO (spvdo-button.js)→
/v1/freestyle.html หรือ /v1/catalog.html→
Service :8100 (uvicorn)→
genaipro.io (Veo)→
📥 MP4 4 คลิป + ลิงก์แชร์ + ลิงก์ Shopee
| คำถาม | คำตอบสั้น |
| ผู้ใช้ต้องใส่คีย์ไหม | ไม่ต้อง — ใช้คีย์กลางอัตโนมัติ (ใครมีคีย์ตัวเองก็ใช้ของตัวเองได้) |
| ต้องสมัครไหม | ไม่ต้อง (Guest จำ 1 ปี) — สมัครอีเมลเพื่อย้ายประวัติข้ามเครื่อง |
| ได้กี่คลิป | 4 คลิป/สินค้า/รอบ · Auto Retry สูงสุด 10 · จำกัด 3 งานพร้อมกัน/คน, 2 งานทั้งระบบ |
| สเปกคลิป | MP4 · H264 · 720×1280 (9:16) · 24fps · 8 วินาที (เลือก 16:9 / 1:1 ได้) |
2หน้าเว็บทั้งหมด (URL Map)
| URL | หน้าที่ | ที่มา |
/ | หน้าหลักเว็บ spvdo + แบนเนอร์ VDO + ปุ่มทำ VDO ทุกการ์ด | เว็บหลัก |
/v1/freestyle.html?pid=&sku=&source=catalog | หน้าสร้างเดี่ยว (replica ต้นฉบับ ttvip V1) — สินค้า+รูป+prompt โหลดอัตโนมัติ | ไฟล์จริงจาก V1 |
/v1/catalog.html | แค็ตตาล็อกของเรา — ค้นหาสินค้าจริง 1M+ / เลือกหลายตัว / รันทั้งชุด | สร้างเอง |
/v1/history.html | ประวัติเต็มรูปแบบ — สถิติ/ฟิลเตอร์/ค้นหา/ดู 4 คลิป/ลบ/ล้าง + ลิงก์แชร์ + ลิงก์ Shopee | สร้างเอง |
/v1/login.html | ล็อกอิน/สมัคร VDO (เมลใหม่สมัครให้เอง + ย้ายประวัติ Guest) | สร้างเอง |
/v1/keys.html | จัดการคีย์ส่วนตัว (BYOK) — เพิ่ม/สลับ/ลบ เข้ารหัส Fernet | สร้างเอง |
/v1/manual.html | คู่มือผู้ใช้ (รูป screenshot จริง + คลิปจริงจาก Veo) | สร้างเอง |
/v1/docs.html | เอกสารเทคนิคฉบับนี้ | สร้างเอง |
/v1/share/{token}/{ไฟล์}.mp4 | ลิงก์วิดีโอสาธารณะ (เปิดได้ทุกที่ ไม่ต้อง login — ปิดได้ทันที) | backend |
3สถาปัตยกรรม + โครงสร้างไฟล์
เครื่องนี้ (spvdo.sj88ai.com)
├── nginx (host) — /etc/nginx/sites-available/spvdo.sj88ai.com
│ ├── location ^~ /v1/api/auth/ → 127.0.0.1:8100 (rate 10r/s)
│ ├── location ^~ /v1/api/freestyle/submit → :8100 (burst 5)
│ ├── location ^~ /v1/ → 127.0.0.1:8100 (VDO ทั้งหมด)
│ └── location / (เว็บหลัก) → /opt/spvdo/www-cache (HTML no-cache)
│ └── block *.bak · block wp-admin/.env/.git
│
├── docker (เว็บหลักเดิม — ไม่เกี่ยวกับ VDO โดยตรง)
│ ├── spvdo-api-1 → 127.0.0.1:18765 (/api/research/list, /api/auth/* ของเว็บหลัก)
│ ├── spvdo-ssr-1 → 127.0.0.1:18766
│ └── volume data → /var/lib/docker/volumes/spvdo_shopee_data/…/shopee/shopee.db (10GB)
│
└── ระบบ VDO (ของเรา)
├── systemd: veo-freestyle.service → uvicorn :8100 · Restart=always · enabled
└── /root/veo-freestyle/
├── .env ← 🔑 คีย์กลาง + ค่าตั้ง (chmod 600)
├── app/
│ ├── main.py FastAPI ทุก route + middleware guest + SSRF guard
│ ├── keys.py BYOK เข้ารหัส Fernet + resolve user→central
│ ├── veo.py ตัวคุยกับ genaipro.io (สร้าง/poll/โหลด)
│ ├── jobs.py worker pool (2) + retry state machine
│ ├── store.py jobs JSON-per-file + delete/clear + recovery
│ ├── identity.py guest cookie 1 ปี + PBKDF2 + session 365 วัน
│ ├── prompts.py template V1 (STRICT TIMELINE + negative)
│ ├── images.py Pillow composite รวมรูปทุกช่อง
│ └── config.py อ่าน .env + hard-fail ถ้าไม่มี SESSION_SECRET
├── static/v1/ หน้าเว็บทั้งหมด (freestyle=ไฟล์จริง V1, ที่เหลือสร้างเอง)
├── data/ products.json · users.json · user_keys.json · mock
└── storage/ jobs/ · outputs/ · uploads/ · work/ (GC 30 วัน)
4API Reference (mount ทั้ง /api และ /v1/api)
Identity / Auth
| Method + Path | รายละเอียด |
GET /v1/api/auth/me | ตัวตนปัจจุบัน — request แรกไม่มี cookie จะได้ guest ref + Set-Cookie HttpOnly/Secure/SameSite=lax อายุ 1 ปี ทันที · มี field videoMode: mock|veo |
POST /v1/api/auth/email | {email, password≥8, name?} — เมลใหม่=registered / เมลเดิม=logged_in (ผิด=401) · claim งาน Guest เข้าบัญชีเฉพาะตอนยังเป็น guest → คืน claimedJobs |
POST /v1/api/auth/logout | ล้าง session cookie → กลับ guest |
BYOK Keys
GET /v1/api/keys/me | รายการคีย์ (prefix 12 ตัวเท่านั้น) + centralAvailable + mode |
POST /v1/api/keys/add | {name, key} — ต้องขึ้นต้น sk_gap_ · เก็บ เข้ารหัส Fernet (PBKDF2 จาก SESSION_SECRET) |
POST /v1/api/keys/{id}/active | สลับคีย์ที่ใช้ |
DELETE /v1/api/keys/{id} | ลบคีย์ (ไม่เหลือ = กลับใช้คีย์กลาง) |
Products
GET /v1/api/products/ensure?itemid=&title=&image=&price=&cat=&brand=&score= | ลงทะเบียนสินค้าจากหน้าเว็บ — เสริมข้อมูลจาก DB จริงอัตโนมัติ: shopee_link, shop_name, cat 2 ชั้น, sold, rating (route นี้ต้องมาก่อน {pid}) |
GET /v1/api/products/{pid} | ข้อมูลสินค้า (freestyle ใช้โหลด banner) |
AI
POST /v1/api/ai/refine | {prompt, mode, camera?, lighting?, thai_speech?} → แปลงเป็นบทกำกับ V1 + negative_prompt (แยก 2 โหมด) |
POST /v1/api/ai/storyboard | บท 3 องก์ (Hook→Demo→Social Proof) — engine: heuristic |
Jobs (หัวใจ)
POST /v1/api/freestyle/submit | multipart: file1-3 + file2_url + prompt/mode/aspectRatio/camera/lighting/thaiSpeech/maxAttempts/productId → dedup (prompt+productId เดิมที่ยังรัน = คืน job เดิม) · cap 3 งาน/คน (429) · composite รวมรูปทุกช่อง |
POST /v1/api/freestyle/queue-product | {product_id, mode, prompt?} — ดึงรูปสินค้าเอง + prompt อัตโนมัติ (drawer ของ V1 ใช้) |
POST /v1/api/freestyle/queue-batch | {products:[{itemid,title,image,…}], mode} หรือ {product_ids:[…]} — สูงสุด 20/ชุด |
GET /v1/api/freestyle/jobs?limit= | รายการของตัวเอง (≤100) — แนบ product ล่าสุดเสมอ (มี shopee_link) |
GET /v1/api/freestyle/jobs/{id} | สถานะ/log/videos/retry — frontend poll ทุก 3.5 วิ |
DELETE /v1/api/freestyle/jobs/{id} | ลบ + ลบไฟล์ mp4 ด้วย · งานกำลังรัน = 409 |
POST /v1/api/freestyle/jobs/clear | {status:"finished"} ล้างเฉพาะงานจบ + ลบไฟล์ตาม |
POST /v1/api/freestyle/jobs/{id}/share | {enabled:true/false} → shareToken 32 hex · token คงเดิมตลอด (ปิด→เปิด ลิงก์เดิมกลับมา) |
GET /v1/share/{token}/{file}.mp4 | ลิงก์สาธารณะ ไม่ต้อง login · Cache-Control public 1 ชม. · ปิดแชร์แล้ว 404 ทันที |
ตัวอย่างเร็ว
# ล็อกอิน
curl -c c.txt -X POST https://spvdo.sj88ai.com/v1/api/auth/email \
-H "Content-Type: application/json" \
-d '{"email":"sj@sj.com","password":"•••"}'
# ส่งงานจากสินค้า
curl -b c.txt -X POST https://spvdo.sj88ai.com/v1/api/freestyle/queue-product \
-H "Content-Type: application/json" -d '{"product_id":"26311859091"}'
# poll จน completed → โหลดคลิป
curl -b c.txt https://spvdo.sj88ai.com/v1/api/freestyle/jobs/{jobId}
curl -b c.txt -O https://spvdo.sj88ai.com/v1/api/freestyle/output/{jobId}.mp4
5ระบบคีย์ (เหมือน V1)
สร้างงาน→
มีคีย์ผู้ใช้ (active)?→ ใช่:🔑 คีย์ของผู้ใช้
→ ไม่:🏢 คีย์กลาง (.env)
- คีย์กลาง:
GENAIPRO_API_KEY ใน /root/veo-freestyle/.env (chmod 600) — ใส่ครั้งเดียว ผู้ใช้ไม่เห็น
- คีย์ผู้ใช้: เพิ่มผ่าน
/v1/keys.html · เก็บใน data/user_keys.json แบบเข้ารหัส Fernet (กุญแจได้จาก PBKDF2 ของ SESSION_SECRET) · โชว์เฉพาะ prefix 12 ตัว
- คีย์ plaintext วิ่งในหน่วยความจำระหว่าง render เท่านั้น — job บันทึกแค่
keySource: user|central
- ไม่มีคีย์ทั้งคู่ = 503 พร้อมข้อความแนะนำ
6Provider: genaipro.io
| ขั้น | เรียก |
| สร้างงาน | POST /v2/veo/ingredients-to-video multipart · 202 = สำเร็จ → histories[0].id · timeout 60 วิ (แบบ V1) |
| poll | GET /v2/veo/tasks/{id} ทุก 5 วิ จน completed → file_urls · คุยละะ attempt 600 วิ (10 นาที ตาม V1) · network error/429/5xx ระหว่างรอ = ทน สู้ต่อ ไม่ fail · หมดเวลา → reconcile poll อีกครั้งเดียว แล้วยังไม่จบ = STATE_UNKNOWN หยุด ไม่ยิงซ้ำ |
| โหลด | GET file_url (retry 3 ครั้งต่อไฟล์) → {jobId}.mp4, _v2.._v4 |
| กฎ retry (ตาม V1) | retry เฉพาะ: submit 408/425/429/5xx · ConnectTimeout · task failed แบบ transient (เช่น AUDIO_FILTERED) · ห้าม retry: ReadTimeout/หลุดกลางทาง (STATE_UNKNOWN — task อาจถูกเรียกเก็บเงินแล้ว) · 401/403/400 · UNAUTHORIZED/INVALID_API_KEY/INSUFFICIENT_CREDIT/INVALID_ARGUMENT · หน่วงรอบ exponential 5→10→20→40→cap60 วิ |
⚠️ บทเรียนสำคัญ provider ตอบ 202 Accepted = งานถูกสร้างแล้ว (ไม่ใช่ error) — ต้องรับ 200/201/202 ทั้งหมด มิฉะนั้นจะ retry ทิ้งซ้ำๆ เปล่า และถ้า timeout กลางทาง = "สถานะไม่ชัด" → หยุด ไม่ยิงซ้ำ (กันเผาเครดิต)
7แหล่งข้อมูล
DB ของ spvdo (อ่านอย่างเดียว — mode=ro)
เส้นทาง: /var/lib/docker/volumes/spvdo_shopee_data/_data/shopee/shopee.db (10GB)
| ตาราง | แถว | คอลัมน์สำคัญ |
| products | 1,849,493 | itemid · shopid · shop_name · product_link (ลิงก์ Shopee ตรง) · title · brand · cat1/cat2 · price/sale_price/discount · historical_sold/sold · likes · rating_star · score · is_hot · stock · image_url/image_link · description · seller_name · shop_rating · is_official/is_preferred/is_verified … |
| price_log | 26,700,857 | ประวัติราคารายวัน: snapshot_date · price_change_pct · change_type · stock/sold ย้อนหลัง |
ทุกครั้งที่สินค้าเข้าระบบ VDO (ensure/queue) backend จะเปิด DB แบบอ่านอย่างเดียวเสริม: ลิงก์ Shopee · ชื่อร้าน · หมวด 2 ชั้น · ยอดขาย · เรตติ้ง — งานเก่าก็ถูกแนบข้อมูลล่าสุดตอนแสดงผลเสมอ
ไฟล์ JSON ของระบบ
data/products.json | แคตตาล็อกที่เคยใช้ + ข้อมูล enrich จาก DB |
data/users.json | บัญชีอีเมล — PBKDF2 120k + salt/คน (ไม่มี plaintext) |
data/user_keys.json | คีย์ BYOK เข้ารหัส Fernet ต่อ identity |
storage/jobs/*.json | 1 งาน = 1 ไฟล์ (สถานะ/log/videos/shareToken) |
storage/outputs/*.mp4 | คลิปทั้งหมด — GC เกิน 30 วัน (CLEANUP_DAYS) |
8Prompt Engineering
คำสั่งไทยของผู้ใช้ถูกแปลงเป็นบทกำกับมาตรฐานก่อนส่ง Veo เสมอ (นี่คือเหตุผลหลักที่คลิปเนียน):
TIKTOK UGC ECOMMERCE VIDEO (…):
[ATTACHED_FILE_1_CHARACTER] = … / HANDS-ONLY (โหมด Faceless)
SETTING / Thai dialogue: "บทพูดผู้ใช้"
STRICT TIMELINE: 0-2s ชูสินค้าเฟรมแรก / 2-4s สาธิต / 4-6s close-up / 6-8s ปิดการขาย
STRICT RULES: สินค้าตรงรูปต้นฉบับเป๊ะ · ห้ามมือบังโลโก้ · ห้ามหน้าตาการ์ตูน/พลาสติก AI
negative_prompt: (แยกต่างหาก — Faceless เพิ่มชุด "ห้ามเห็นหน้าคน")
ตัดที่ ~1,600 ตัวอักษร
9ความปลอดภัยที่ implement
| จุด | กลไก |
| SSRF ผ่าน file2_url | รับเฉพาะ http/https + บล็อก IP private/loopback/link-local + ตรวจ content-type เป็นรูป + cap 10MB |
| ไฟล์ output | fail-closed (ไม่มี job = 404) · ตอบ 404 เหมือนกันทั้งเจ้าของผิด/ไม่มีไฟล์ (กัน enumeration) · ลบงาน = ลบไฟล์จริง |
| ลิงก์แชร์ | token 32 hex เดาไม่ได้ ผูกงานเดียว · ปิด = 404 ทันที · อ่านอย่างเดียว |
| รหัสผ่าน | PBKDF2 120k + salt/คน + hmac.compare_digest · เผาเวลาเท่ากันกัน timing oracle |
| Session | เซ็น HMAC ฝังวันออก 365 วัน · HttpOnly/Secure/SameSite · ไม่มี SESSION_SECRET จริง = ไม่สตาร์ท |
| คีย์ | Fernet at rest · ไม่บันทึกลง job/log |
| Rate limit (nginx) | auth 10r/s · submit burst 5 |
| ต้นทุน | cap 3 งาน/คน · worker ทั้งระบบ 2 · dedup งานซ้ำ · "สถานะไม่ชัด = หยุด ไม่ยิงซ้ำ" |
| อัปโหลด | 10MB/ไฟล์ + ตรวจนามสกุล + validation ทุก input |
| การกู้คืน | restart = งานค้างกลายเป็น failed อัตโนมัติ · GC 30 วัน · work dir ลบทุกกรณี |
| ไฟล์ประจำเครื่อง | .env chmod 600 · nginx block *.bak/.env/.git |
10Ops Runbook
# สถานะ / log
systemctl status veo-freestyle
journalctl -u veo-freestyle -f
# แก้ค่า (คีย์กลาง/โหมด/จำกัดงาน)
nano /root/veo-freestyle/.env # GENAIPRO_API_KEY / MOCK_VIDEO / MAX_ACTIVE_JOBS_PER_IDENTITY / CLEANUP_DAYS
systemctl restart veo-freestyle
# สำรอง (แนะนำสัปดาห์ละครั้ง)
tar czf spvdo-vdo-backup-$(date +%F).tar.gz \
/root/veo-freestyle/storage /root/veo-freestyle/data /root/veo-freestyle/.env
# กู้ nginx config
cp /opt/spvdo/backups/veo-inject/… /etc/nginx/sites-available/spvdo.sj88ai.com && nginx -s reload
⚠️ กฎเหล็ก 2 ข้อ (ระเบิดเวลา)
1) หาก re-run /opt/spvdo/scripts/install_nginx_block.sh — template ต้องมี /v1/ ก่อน (ตอนนี้ sync ไว้แล้วที่ /opt/spvdo/nginx/spvdo.host.conf — อย่าเขียนทับด้วยของเก่า)
2) หาก deploy เว็บหลักทับ www-cache — ต้องแทรก <script src="/v1/spvdo-button.js"> ก่อน </body> ของ index/research/signals/dashboard ใหม่ (สำรองเดิมอยู่ /opt/spvdo/backups/veo-inject/)
11Troubleshooting
| อาการ | สาเหตุ/วิธีแก้ |
| กด "ทำ VDO" เปิด modal เก่าของ spvdo | แคชเบราว์เซอร์ → F5 / Ctrl+Shift+R · ถ้าทั้งเว็ย = สคริปต์ถูก deploy ทับ → แทรกใหม่ (ดูกฎเหล็ก 2) |
| /v1/* ทั้งหมด 404 | service ตาย (systemctl restart veo-freestyle) หรือ nginx โดนทับ template (กฎเหล็ก 1) |
| งาน fail "provider ตอบ 401/403" | คีย์หมดโควต้า/ไม่ถูก → เช็คคีย์กลางใน .env หรือคีย์ผู้ใช้ที่ /v1/keys.html |
| งาน fail "หยุดเพื่อป้องกันงานซ้ำ" | เน็ตหลุดกลางทาง (งานอาจสำเร็จที่ provider) — กดสร้างใหม่ได้ ระบบไม่ยิงซ้ำเองเพื่อกันเครดิตซ้อน |
| คลิปเพี้ยน/ไม่ตรงสินค้า | ธรรมชาติ Veo — เลือกดีที่สุดจาก 4 เวอร์ชัน หรือเจนใหม่ · ใส่รูปสินค้าชัด/มุมเสริมช่วยได้มาก |
| 429 งานเกิน | cap 3 งาน/คน — รองานเสร็จ (ปรับได้ที่ MAX_ACTIVE_JOBS_PER_IDENTITY) |
| ลิงก์แชร์เปิดไม่ได้ | ถูกปิดแชร์หรืองานถูกลบ → กดเปิดแชร์ใหม่ที่หน้าประวัติ (ลิงก์เดิมกลับมา) |
| ล็อกอินไม่ได้หลังแตะ .env | เปลี่ยน SESSION_SECRET = session เดิมตายหมด + คีย์ BYOK ถอดไม่ออก — อย่าเปลี่ยนถ้าไม่จำเป็น |
12Changelog
| วันที่ | งาน |
| 7 ก.ย. 66 | ศึกษา ttvip V1 (read-only) → พอร์ตระบบ: prompt template V1 / guest 1 ปี / claim / retry / 4 คลิป · ติดตั้ง service+nginx+ปุ่มบนเว็บหลัก · E2E ผ่าน |
| 7 ก.ย. 66 | คู่มือผู้ใช้ + screenshot จริง + วิดีโอตัวอย่าง |
| 7 ก.ย. 66 | Audit 3 ซับเอเจนต์ (โค้ด/การเชื่อม/production 34 เช็ค) → แก้ 12 จุด: SSRF, fail-open, ระเบิดเวลา nginx template, popup blocker, startup recovery, rate limit, ฯลฯ |
| 7 ก.ย. 66 | เพิ่ม queue-product/queue-batch + หน้า Catalog ของเรา + cap งาน + composite รวมรูปนายแบบ + GC |
| 7-8 ก.ย. 66 | ต่อ provider genaipro.io จริง (คีย์กลางใน .env + BYOK Fernet + แก้ bug 202=สำเร็จ) → เจนจริงผ่าน: Diller Thermos 4 คลิป H264 9:16 8 วิ · ซับเอเจนต์ QA เฟรม 7.5/10 · คู่มือเปลี่ยนเป็นคลิปจริง |
| 8 ก.ย. 66 | ปรับ UX ให้เหมือน V1 (Score log-scale, หมวดไม่ซ้ำ) · หน้าประวัติเต็มรูปแบบ · ลิงก์แชร์วิดีโอ + คัดลอก (token คงเดิมตลอด) · ลิงก์ Shopee + คัดลอก (enrich จาก DB จริง) · หน้า login · แบนเนอร์หน้าหลัก · ทดสอบบัญชี sj@sj.com ผูก/ข้ามเครื่อง ผ่าน |
SPVDO AI Video System · เอกสารเทคนิค v1.0 · 8 กันยายน 2026 · ดูคู่มือผู้ใช้ที่
/v1/manual.html