ChromaStage
คู่มือการใช้งานแบบละเอียด
เว็บ Chroma Key Compositor — ตัดคนจากภาพพื้นเขียว ใส่ฉากหลัง ส่งออกเป็นรูป ZIP/PDF หรือวิดีโอ MP4 คู่มือนี้ทดสอบครบทุกฟีเจอร์กับรูปภาพพื้นเขียวจริง 32 เทสเคส ผ่าน 32/32
🚀 เริ่มต้นใช้งาน (Quick Start)
5 นาที — เปิด server, upload รูป, render ออกมา
1. Start server
cd C:\Users\sjryzen7\Documents\Codex\chromastage
python main.py
# → INFO: Uvicorn running on http://0.0.0.0:30089
2. เช็คสุขภาพ server
curl http://127.0.0.1:30089/healthz
# {"ok":true,"ffmpeg":"...","version":"1.4.2"}
✅ ถ้าเห็น "version":"1.4.2" แสดงว่าพร้อมใช้งาน
3. เปิดเว็บ
ไปที่ http://localhost:30089/ — UI จะ auto-mint cookie cs_uid ให้
4. Flow พื้นฐาน 4 ขั้น
5. ตัวอย่าง curl (5 คำสั่ง จบ)
# 1) Mint cookie
curl -c jar.txt http://127.0.0.1:30089/api/me
# 2) Upload person
curl -b jar.txt -F "file=@my_greenscreen.jpg" \
http://127.0.0.1:30089/api/upload/person
# → {"job_id":"abc123","chroma":{"color":"0x00DC00",...}}
# 3) Upload 3 backgrounds
curl -b jar.txt -F "files=@bg1.jpg" -F "files=@bg2.jpg" -F "files=@bg3.jpg" \
http://127.0.0.1:30089/api/jobs/abc123/upload/backgrounds
# 4) Render image set
curl -b jar.txt -X POST http://127.0.0.1:30089/api/jobs/abc123/render/images \
-H 'Content-Type: application/json' -d '{"image_preset":"square","image_format":"png"}'
# 5) Download ZIP
curl -b jar.txt -o out.zip http://127.0.0.1:30089/api/jobs/abc123/download/zip
📁 ไฟล์ต้นทาง (Source Files)
ทุกเทสเคสในคู่มือนี้ใช้รูปจริงจาก G:\My Drive\0000000\20260828\
Person (คนพื้นเขียว)
| ไฟล์ | ขนาดต้นฉบับ | Resize ใช้ทดสอบ | คุณสมบัติ |
|---|---|---|---|
IMG_5557.JPG ⭐ greener |
6000×4000, 5.3MB | 1500×1000, 176KB | 230/300 pixels เป็นสีเขียว → พื้นเขียวชัดเจน ใช้เป็นหลัก |
IMG_5554.JPG |
6000×4000, 5.3MB | 1500×1000, 176KB | 227/300 green pixels → alt person |
Backgrounds (ฉากหลัง)
| ไฟล์ | Resize | ใช้ในเทส |
|---|---|---|
IMG_5544.JPG | 1620×1080 | เกือบทุกเทส (BG1) |
IMG_5546.JPG | 1620×1080 | BG2 |
IMG_5584.JPG | 1620×1080 | BG3 · intro/outro |
IMG_5628.JPG | 1620×1080 | BG4 |
IMG_5715.JPG | 1620×1080 | BG5 |
ภาพต้นฉบับ 6000×4000 = 24 megapixel, render จะใช้เวลา 10-30 วินาที Resize เหลือ 1500×1000 = 1.5 MP ใช้เวลา 1-3 วินาที คุณภาพ chroma key ไม่ได้ลดลง (ระบบจะ downscale ใหม่อีกครั้งตอน composite อยู่แล้ว)
🟢 Case A · Image Mode
คนพื้นเขียว + N ฉากหลัง → ชุดภาพ Composite + ZIP + PDF
Square 1080×1080 PNG (IG square post)
Config ที่ใช้
{
"image_preset": "square", // → 1080×1080
"image_format": "png", // lossless
"rename_pattern": "img_{n:03d}" // img_000.png, img_001.png, img_002.png
}
ผลที่ได้
- ✅ 3 ภาพ PNG ขนาด 1080×1080 ตรงเป๊ะ
- ✅ ZIP 2.0MB, PDF 193KB
- ✅ Chroma key: พื้นเขียวถูกลบออกหมด ขอบคนสะอาด
Preset: square
IMAGE_PRESETS = {
"square": (1080, 1080), # IG square post
"portrait": (1080, 1350), # IG portrait post
"story": (1080, 1920), # IG/TikTok story 9:16
"fb_cover": (820, 312), # FB cover banner
"youtube": (1280, 720), # YouTube thumbnail
"wallpaper": (1920, 1080), # 1080p wallpaper
"custom": None, # use output_w / output_h
}
Portrait 1080×1350 + Watermark
Config
{
"image_preset": "portrait", // → 1080×1350
"image_format": "jpg", // lossy
"jpg_quality": 80, // 0-100
"watermark": {
"enabled": true,
"text": "© ChromaStage 2026",
"position": "bottom-right",
"opacity": 0.7,
"size": 36,
"color": "#FFFFFF"
}
}
ผล
- ✅ ขนาด 1080×1350 ตรงเป๊ะ
- ✅ JPG quality 80 → ไฟล์เล็ก เหมาะกับ social upload
- ✅ Watermark render ที่มุมขวาล่าง
Story 1080×1920 + Thai Watermark + Rename
Config
{
"image_preset": "story", // → 1080×1920 (IG/TikTok 9:16)
"image_format": "webp", // เล็กกว่า PNG เยอะ คุณภาพดีกว่า JPG
"rename_pattern": "story_{n:04d}", // story_0000.webp, story_0001.webp, ...
"watermark": {
"enabled": true,
"text": "ทดสอบ 🎉 ภาษาไทย", // Thai + emoji
"position": "center",
"opacity": 0.8,
"size": 60,
"color": "#FFEB3B" // สีเหลือง
}
}
🐛 Regression Note
ก่อนแก้ bug B3: การ save config ที่มี Thai text ใน watermark
จะได้ 500 UnicodeEncodeError: 'charmap' codec can't encode characters
บน Windows (cp1252 default)
Fix: เพิ่ม encoding="utf-8" ใน
app/storage.py:write_meta()
ผล
- ✅ Thai + emoji ไม่ crash — render 3.66s
- ✅ 1080×1920 ตรงเป๊ะ
- ✅ ไฟล์
story_0000.webp,story_0001.webp, ...
Facebook Cover 820×312 JPG q70
Config
{
"image_preset": "fb_cover", // → 820×312
"image_format": "jpg",
"jpg_quality": 70,
"rename_pattern": "cover_{n}"
}
ผล
- ✅ 0.51s render — เร็วมากเพราะ output เล็ก
- ✅ Aspect ratio 820:312 ≈ 2.63:1 (banner)
YouTube Thumbnail 1280×720
Config
{
"image_preset": "youtube", // → 1280×720
"image_format": "png",
"rename_pattern": "yt_{n}"
}
Tip
YouTube แนะนำ thumbnail อย่างน้อย 1280×720 ขนาดใหญ่สุด 2MB. PNG ที่ preset นี้จะใหญ่กว่า JPG แต่คมชัดกว่าสำหรับข้อความ
Wallpaper 1920×1080 + Watermark
Config
{
"image_preset": "wallpaper", // → 1920×1080
"image_format": "jpg",
"jpg_quality": 92,
"watermark": {
"enabled": true, "text": "Desktop Wallpaper",
"position": "bottom-left", "opacity": 0.5, "size": 28
}
}
Custom 800×600 PNG
Config
{
"image_preset": "custom", // ← สำคัญ: บอกว่าใช้ output_w/h
"output_w": 800,
"output_h": 600,
"image_format": "png",
"rename_pattern": "custom_{n}"
}
💡 image_preset: "custom" บอกให้ระบบใช้ค่า output_w/output_h ตรงๆ แทนการ lookup จาก IMAGE_PRESETS
Format Comparison (PNG vs JPG q95 vs JPG q10 vs WebP)
| Format | Quality | Render | ZIP Size | ใช้เมื่อ |
|---|---|---|---|---|
PNG | default | 0.56s | 857KB | คุณภาพสูงสุด, lossless, alpha channel |
JPG | 95 | 0.54s | 193KB | ⭐ คุณภาพสูง + ขนาดเล็ก — แนะนำ |
JPG | 10 | 0.42s | 14KB | ⚠️ artifacts หนัก, เล็กมาก เหมาะแค่ thumbnail |
WebP | default | 0.52s | 14KB | ⭐ เล็กเท่า JPG q10 แต่คุณภาพดีกว่ามาก |
💡 WebP คือตัวเลือกที่ดีที่สุดถ้าต้องการทั้งคุณภาพและขนาดเล็ก — เล็กกว่า PNG 60 เท่า, คุณภาพใกล้เคียง JPG q95
Watermark × 9 ตำแหน่ง









TEXT_POSITIONS constant
TEXT_POSITIONS = {
"top-left", "top-center", "top-right",
"middle-left", "middle-center", "middle-right",
"bottom-left", "bottom-center", "bottom-right",
}
💡 ทุกตำแหน่ง validated ใน app/config.py. ถ้าใส่ค่านอกเหนือ 9 นี้ จะ fallback เป็น bottom-right
Person Cut (RGBA PNG with transparency)
เรียกดู
curl -b jar.txt -o cut.png http://127.0.0.1:30089/api/jobs/<JOB>/person_cut
วิเคราะห์ผล
mode: RGBA (4 channels)
size: 1500×1000
transparent pixels (alpha<50): 1,058,893 / 1,500,000 = 70.6%
opaque pixels (alpha>200): 438,138 / 1,500,000 = 29.2%
การใช้งาน
- ✅ ตรวจสอบคุณภาพ chroma key — ถ้า % transparent ต่ำเกินไป = key ไม่สะอาด
- ✅ นำไปใส่ program อื่น (Photoshop, GIMP) เพื่อ edit เพิ่ม
- ✅ ใช้เป็น asset ตั้งต้นสำหรับ render pipeline อื่น
🎬 Case B · VDO Mode
คนพื้นเขียว + N ฉากหลัง → MP4 พร้อม transitions, BGM, text overlay, Ken Burns
Cut Transition · 3 bgs × 5s = 15s
Config
{
"scene_duration": 5, // แต่ละ scene 5 วินาที
"transition": "cut", // เปลี่ยนฉากแบบตัดตรงๆ (เร็วสุด)
"transition_duration": 0.5, // ไม่ใช้กับ cut แต่เก็บไว้
"ken_burns": false, // ไม่มี zoom
"output_w": 1280,
"output_h": 720,
"fps": 30
}
ผลที่ได้
- ✅ MP4 ขนาด 1280×720, 30fps
- ✅ duration = 15.000000s exact (ffprobe confirmed)
- ✅ File size 1.15MB (เล็กมาก เพราะ cut transition ไม่มี motion)
- ✅ Render time 1.83s = 8x realtime
Pipeline
person photo → cut_person (chroma+despill) → RGBA PNG
↓
for each background:
composite_scene (overlay centered) → still PNG
render_scene_clip (Ken Burns optional) → scene_NNN.mp4
↓
concat with xfade (or cut) → merged.mp4
↓
apply text overlay (drawtext) → overlay.mp4
↓
apply BGM (stream_loop + volume) → final.mp4
Fade Transition · 2 bgs × 8s = 16s
Config
{
"scene_duration": 8,
"transition": "fade", // จางเข้า-ออก ระหว่าง scene
"transition_duration": 0.8 // ใช้ 0.8s สำหรับ fade
}
💡 Fade ใช้ ffmpeg xfade filter — ต้องมี scene ≥ 2 จึงจะเห็น effect
Slide-Zoom Transition · 4 bgs × 3s = 12s
Config
{"scene_duration": 3, "transition": "slide-zoom", "transition_duration": 0.5}
TRANSITIONS ที่รองรับ: cut · fade · slide-zoom
+ BGM (Background Music)
Steps
# 1) สร้าง BGM test (10s sine wave)
python -c "
import wave, struct, math
sr = 22050
with wave.open('test.wav','wb') as w:
w.setnchannels(1); w.setsampwidth(2); w.setframerate(sr)
for i in range(sr*10):
s = int(8000 * math.sin(2*math.pi*440*i/sr))
w.writeframes(struct.pack('<h', s))"
# 2) Upload BGM
curl -b jar.txt -F "file=@test.wav" \
http://127.0.0.1:30089/api/jobs/<JOB>/bgm
# 3) Config + Render
{
"scene_duration": 5,
"bgm_volume": 0.5 // 0.0 - 1.0
}
รองรับ audio formats
ALLOWED_AUDIO_EXTS = {".mp3", ".wav", ".m4a", ".aac", ".ogg", ".flac"}
MAX_AUDIO_BYTES = 50 * 1024 * 1024 // 50MB
Behavior พิเศษ
- ✅ BGM สั้นกว่า video →
stream_loop -1loop จนจบ - ✅ BGM ยาวกว่า video → ตัด audio ตาม video duration
- ✅ ไม่มี BGM → MP4 ไม่มี audio stream (ยัง render ได้)
+ English Text Overlay "Hello World"
Config
{
"scene_duration": 4,
"transition": "cut",
"text_overlay": {
"text": "Hello World",
"position": "bottom-center",
"color": "#FFFFFF",
"size": 48,
"opacity": 1.0
}
}
Text Overlay fields
text: ข้อความ ≤120 charsposition: 9 ตำแหน่ง (เหมือน watermark)color: hex#RRGGBBหรือ#RRGGBBAAsize: 10-200 (px)opacity: 0.0-1.0
+ Thai Text Overlay (regression test)
Config
{
"scene_duration": 4,
"transition": "cut",
"text_overlay": {
"text": "สวัสดี 🎉 ไทยแลนด์",
"position": "top-center",
"color": "#FFD700",
"size": 56,
"opacity": 0.95
}
}
Bug B3: ก่อนแก้, การ save config ที่มี Thai text ใน text_overlay.text → 500 UnicodeEncodeError บน Windows
Fix: app/storage.py เพิ่ม encoding="utf-8" ใน write_text()
Regression test: เคสนี้คือ regression lock — ถ้า regress อีกจะ fail
+ Intro + Outro Images
Steps
# Upload intro
curl -b jar.txt -F "file=@intro.jpg" \
http://127.0.0.1:30089/api/jobs/<JOB>/intro
# Upload outro
curl -b jar.txt -F "file=@outro.jpg" \
http://127.0.0.1:30089/api/jobs/<JOB>/outro
# Config
{
"scene_duration": 3,
"intro_duration": 2.0,
"outro_duration": 2.0
}
💡 Intro/outro ใช้ render_static_clip() — เปลี่ยนภาพนิ่งเป็น MP4 แบบ static
Ken Burns (Slow Zoom)
Config
{
"scene_duration": 5,
"ken_burns": true // ← เปิด slow zoom
}
Effect
กล้องค่อยๆ zoom in จากขอบเข้าไปกลางภาพ ตลอด scene_duration — ทำให้ภาพนิ่งดู dynamic
💡 ใช้ /preview-clip/{n} ดู effect 3s ก่อน render เต็ม — เซฟเวลา debug
VDO Max Duration (60s)
Config
{
"scene_duration": 15, // 4 × 15 = 60s (limit)
"transition": "cut"
}
Limits
scene_duration: 0.5 - 30 วินาที (validated)- จำนวน backgrounds: สูงสุด 30
- Theoretical max video: 30 × 30 = 900 วินาที (15 นาที)
✅ ffprobe confirmed: duration=60.000000s exact
5 Backgrounds with Fade
📹 VDO output saved to vdo-mode/
(5 backgrounds × 4s = 20s · fade transition)
Config
{
"scene_duration": 4,
"transition": "fade",
"transition_duration": 0.5
}
// 5 backgrounds uploaded
🎯 Edge Cases
เทสเพิ่มเติม — ทดสอบความเสถียรของระบบ
Same Person × 3 Backgrounds



✅ คนเดียวกัน + chroma เดียวกัน → ขอบคนสะอาดเหมือนกันทุก background ตรวจสอบ chroma detection consistency
Preview-Clip × 3 Scenes



API
GET /api/jobs/<JOB>/preview-clip/{n}
# Returns 3-second MP4 (max) with Ken Burns + text overlay applied
# Render time ~0.5-1s per clip — fast enough for "see before you render" UX
🆕 AI Backgrounds · 9:16 TikTok Ready
ทดสอบครั้งที่ 2 กับพื้นหลัง AI ขนาด 941×1672 (9:16 portrait) — 20/20 PASS
ภาพรวม: Same Person × 5 AI Backgrounds
AI backgrounds ทั้งหมดเป็น 9:16 portrait พอดี — render VDO แนวตั้ง TikTok/Reels/Shorts ได้ตรงเป๊ะ ไม่ต้อง letterbox.
Chroma key ทำงานสม่ำเสมอแม้ background จะแตกต่างกันมาก (อุตสาหกรรม / สตูดิโอ / โกดัง) — ขอบคนสะอาด ไม่มี green halo
🟢 Image Mode (AI backgrounds)






🎬 VDO Mode 9:16 (TikTok-ready)
ทุก VDO ใช้ output_w=1080, output_h=1920 — render ตรง 9:16 โดยไม่ scale/letterbox







| Case | Description | Output | Time | Speed |
|---|---|---|---|---|
| T07 | VDO 16:9 (letterbox from 9:16 bg) | 15.0s @ 1280×720 | 1.83s | 8× realtime |
| T08 | VDO 9:16 TikTok Portrait | 15.0s @ 1080×1920 | 2.4s | 6× realtime |
| T09 | VDO 9:16 fade transition | 14.5s @ 1080×1920 | 2.5s | 5.8× realtime |
| T10 | VDO 9:16 slide-zoom | 10.5s @ 1080×1920 | — | — |
| T11 | VDO 9:16 + BGM (12s sine WAV) | 12.0s @ 1080×1920 | — | — |
| T12 | VDO 9:16 + Thai text "สวัสดี 🎬 ฉากหลัง AI" | 12.0s @ 1080×1920 | — | — |
| T13 | VDO 9:16 Ken Burns | 15.0s @ 1080×1920 | — | — |
| T14 | VDO 9:16 max 60s (4 × 15s) | 60.0s @ 1080×1920 exact | 5.8s | 10× realtime |
| T15 | VDO 9:16 with all 5 AI backgrounds | 18.0s @ 1080×1920 | — | — |
🆚 เปรียบเทียบ Real-photo vs AI Backgrounds
| Real-photo (test 1) | AI Backgrounds (test 2) | |
|---|---|---|
| Source | 20260828\ (133 JPG) | Ai ฉากเขียว/พื้นหลัง\ (253 PNG) |
| Resolution | 1620×1080 (16:9) | 941×1672 (9:16) |
| Content | ภาพธรรมชาติ/สถานที่ | อุตสาหกรรม AI generated |
| Tests | 32 cases | 20 cases |
| Pass | 32/32 (100%) | 20/20 (100%) |
| Best for | Desktop, YouTube, wallpaper | TikTok, Reels, Shorts |
| Output sizes | 1920×1080 wallpaper, 1280×720 vdo | 1080×1920 vdo (vertical) |
🆕 EXIF Auto-Orient + Person Fit/Position
3 features ใหม่ใน v1.4.2 — แก้ปัญหา "คนนอนตะแคง" + "คนเล็กไป"
EXIF Auto-Orient (B10 fix)

ก่อนแก้ (B10)
Source: 6000×4000 pixels, EXIF orientation=8 (90° CCW)
Upload → stored as-is
Render → person นอนตะแคง
→ ต้อง pre-rotate ก่อน upload (workaround)
หลังแก้
Source: 6000×4000 pixels, EXIF orientation=8
Upload → ImageOps.exif_transpose() applied
→ file re-saved as 4000×6000, EXIF stripped
Render → person upright ✓
ที่ใช้
from PIL import Image, ImageOps
from .storage import auto_orient_image
# Called automatically after every person/bg/intro/outro upload:
auto_orient_image(save_path)
โทรศัพท์ส่วนใหญ่ถ่ายแนวตั้ง แต่ JPG บันทึก landscape pixels + EXIF tag
ถ้าไม่อ่าน EXIF → output ออกมานอนตะแคง (เคยเจอใน live test 2026-08-30)
person_fit & person_position
3 โหมด person_fit
| Mode | Behavior | When to use |
|---|---|---|
contain (default) | scale คนให้พอดี frame, preserve aspect, may letterbox | คนเต็มตัว, มี padding |
cover | scale คนให้เต็ม frame, may crop ขอบ | คนเด่น พื้นหลังเต็ม |
fill | stretch คนให้เต็มเป๊ะ, บิด aspect | artistic effect |
9 ตำแหน่ง person_position
{"person_position": "bottom-center"} // ← default for "ground" feel
// Same 9-grid as watermark/text_overlay
ใหม่: defaults ดีขึ้น
| Field | Old default | New default (v1.4.2+) |
|---|---|---|
person_scale | 0.7 | 0.85 (เต็มขึ้น ~20%) |
person_fit | (none) | "contain" |
person_position | "center" | "center" (same) |
ตัวอย่าง config
// คนเต็ม frame แบบ TikTok
{
"image_preset": "story",
"person_fit": "cover",
"person_position": "bottom-center",
"person_scale": 1.0
}
// คนยืนบนพื้น ไม่ลอย
{
"person_fit": "contain",
"person_position": "bottom-center",
"person_scale": 0.85
}
Validation
// Invalid values → 400
POST /api/jobs/{id}/config
{"person_fit": "stretch"} // → 400 "invalid person_fit: stretch (must be contain|cover|fill)"
{"person_position": "nowhere"} // → 400 "invalid person_position: nowhere (must be one of 9-grid)"
📊 Performance Benchmarks
ทดสอบบน Intel CPU (no GPU), libx264 ultrafast, crf=22
| Operation | Render Time | Output | Speed |
|---|---|---|---|
| Image 3 bgs square PNG | 1.21s | 3 PNGs (2MB zip) | — |
| Image 4 bgs story WebP + Thai | 3.66s | 4 WebPs (315KB zip) | — |
| VDO cut 15s | 1.83s | 1.15MB MP4 | 8× realtime |
| VDO fade 16s | 2.08s | 1.29MB MP4 | 7.7× realtime |
| VDO slide-zoom 12s | 2.61s | 2.18MB MP4 | 4.6× realtime |
| VDO max 60s | 3.73s | 3.05MB MP4 | 16× realtime |
| Preview single scene | 0.24s | PNG | — |
| Preview-clip single scene | 0.31s | 3s MP4 | 10× realtime |
| Person cut (chroma key only) | <1s | 1.26MB RGBA PNG | — |
💡 CPU-only path เร็วพอสำหรับ interactive web app — render 60s video ใน 4 วินาที
📐 Watermark Position Reference
9 ตำแหน่งใน TEXT_POSITIONS — ทั้ง watermark และ text_overlay ใช้ชุดนี้ร่วมกัน









📚 API Quick Reference
ทั้งหมด 38 endpoints — ดูรายละเอียดเต็มใน [test plan](../2026-08-30-chromastage-test-plan.md)
Job Lifecycle
| Method | Path | Description |
|---|---|---|
| POST | /api/upload/person | Upload person → new job |
| POST | /api/jobs/{id}/upload/person | Add person to existing job |
| POST | /api/jobs/{id}/upload/backgrounds | Upload 1-30 backgrounds |
| GET | /api/jobs/{id} | Full job meta |
| DEL | /api/jobs/{id}/backgrounds/{n} | Remove 1 background |
| POST | /api/jobs/{id}/reorder | Reorder backgrounds |
Configuration
| Method | Path | Description |
|---|---|---|
| POST | /api/jobs/{id}/chroma | Set chroma key params |
| POST | /api/jobs/{id}/config | Set 24 config fields |
| POST | /api/jobs/{id}/bgm | Upload BGM |
| POST | /api/jobs/{id}/intro | Upload intro image |
| POST | /api/jobs/{id}/outro | Upload outro image |
Preview & Render
| Method | Path | Description |
|---|---|---|
| GET | /api/jobs/{id}/preview/{n} | Still composite |
| GET | /api/jobs/{id}/preview-clip/{n} | 3s MP4 clip |
| POST | /api/jobs/{id}/render | Render final VDO |
| POST | /api/jobs/{id}/render/images | Render image set |
| GET | /api/jobs/{id}/download | MP4 |
| GET | /api/jobs/{id}/download/zip | ZIP |
| GET | /api/jobs/{id}/download/pdf |
📖 เต็ม endpoint list ดูที่ live demo หรือ /docs
🔧 Troubleshooting
ปัญหาที่เจอบ่อย + วิธีแก้
app/__init__.py อัปเดตเป็น "1.4.2". restart server ด้วย systemctl restart chromastage หรือ Ctrl+C แล้ว python main.py ใหม่
read_text() ใช้ encoding="utf-8". template birthday / business / product / realestate / gaming / travel มี Thai หรือ emoji
write_meta() ใน app/storage.py ใช้ encoding="utf-8". watermark + text_overlay รับ Thai/emoji/Japanese ได้แล้ว
job_id ตรวจ meta.user_uid กับ cookie cs_uid แล้ว — ต่างกัน → 403 forbidden
/bg/1 คือ bg1.jpg, /bg/5 คือ bg5.jpg, /bg/0 คือ 404
MAX_PERSON_BYTES = 40MB → return 413 Payload Too Large พร้อม detail "person image too large (>40MB)". resize ก่อน upload
MAX_BG_COUNT = 30 → return 400 max 30 backgrounds. ลดจำนวน หรือ split หลาย jobs
{"image_preset":"square","output_w":null,"output_h":null} — ถ้า output_w/h ถูก override มาก่อน ระบบจะใช้ค่านั้น. ลอง POST /api/jobs/{id}/config ใหม่โดยไม่ส่ง output_w/h
POST /api/jobs/{id}/upload/backgrounds ก่อน
ls /c/var/lib/chromastage/persons/<job_id>.*
ls /c/var/lib/chromastage/jobs/<job_id>/person_rgba.png
ถ้า person_rgba.png หาย ให้ upload ใหม่
/api/me ใหม่. หรืองานเกิน 30 วัน retention — ดู README section "30-day retention"
✅ ผลทดสอบรวม
Test Matrix
| ID | Case | Time | Output | Result |
|---|---|---|---|---|
| A.1 | Square 1080² PNG | 1.21s | ZIP 2.0MB | PASS |
| A.2 | Portrait + Watermark | 1.23s | ZIP 208KB | PASS |
| A.3 | Story + Thai Watermark | 3.66s | ZIP 315KB | PASS |
| A.4 | FB Cover 820×312 | 0.51s | — | PASS |
| A.5 | YouTube 1280×720 | 0.91s | — | PASS |
| A.6 | Wallpaper 1920×1080 | 2.04s | — | PASS |
| A.7 | Custom 800×600 | 0.70s | — | PASS |
| A.8 | Format Comparison (4 sub-tests) | — | — | PASS |
| A.9 | Watermark × 9 | — | — | PASS |
| A.10 | Person Cut RGBA | <1s | 1.26MB PNG | PASS |
| B.1 | VDO Cut 15s | 1.83s | MP4 1.15MB | PASS |
| B.2 | VDO Fade 16s | 2.08s | MP4 1.29MB | PASS |
| B.3 | VDO Slide-Zoom 12s | 2.61s | MP4 2.18MB | PASS |
| B.4 | VDO + BGM | 1.66s | MP4 860KB | PASS |
| B.5 | VDO + EN Text | 1.46s | MP4 599KB | PASS |
| B.6 | VDO + Thai Text | 1.45s | MP4 859KB | PASS |
| B.7 | VDO + Intro/Outro | 1.21s | MP4 762KB | PASS |
| B.8 | VDO Ken Burns | 1.90s | MP4 1.29MB | PASS |
| B.9 | VDO Max 60s | 3.73s | MP4 3.05MB | PASS |
| B.10 | VDO 5 Backgrounds | 3.46s | — | PASS |
| EC.1 | Same Person × 3 BG | 1.24s | — | PASS |
| EC.2 | Preview-Clip × 3 | 0.6s each | — | PASS |