# CapCut Web — ตัดต่อวิดีโอออนไลน์

เครื่องมือตัดต่อวิดีโอแบบ CapCut ที่ทำงาน **ทั้งหมดในเบราว์เซอร์** — ไม่มีการอัปโหลดไฟล์ไปเซิร์ฟเวอร์ ไม่ต้องใช้ API key

## โครงสร้างโปรเจกต์ (Refactored — ทุกไฟล์ ≤ 217 บรรทัด)

```
src/
├── app/                          # Next.js App Router
├── components/
│   ├── Editor.tsx                # top-level layout
│   ├── MediaLibrary.tsx          # asset upload + drag source
│   ├── Preview.tsx               # <video> + transport
│   ├── Timeline.tsx              # timeline orchestrator (217 lines)
│   ├── PropertiesPanel.tsx       # inspector orchestrator (39 lines)
│   ├── TopBar.tsx                # undo/redo + export
│   ├── KeyboardShortcuts.tsx     # global hotkeys
│   ├── Waveform.tsx              # audio waveform canvas
│   ├── Toast.tsx                 # toast overlay
│   │
│   ├── timeline/                 # Timeline internals
│   │   ├── constants.ts         # dimensions + snap distance
│   │   ├── snap.ts               # snap-to-edges
│   │   ├── detectSilence.ts      # silence detection driver
│   │   ├── TimelineToolbar.tsx   # zoom/detect/split toolbar
│   │   ├── Ruler.tsx             # ruler + hover timecode
│   │   ├── TrackLabel.tsx        # left-rail track header
│   │   ├── ClipBlock.tsx         # draggable clip
│   │   ├── SilenceBlock.tsx      # red overlay on silence
│   │   └── SubtitleMarkers.tsx   # yellow markers under ruler
│   │
│   ├── properties/               # PropertiesPanel internals
│   │   ├── FormFields.tsx        # Section/Row/Slider/NumberField/SRT time
│   │   ├── ClipProperties.tsx
│   │   ├── TrackProperties.tsx
│   │   ├── SubtitleProperties.tsx
│   │   └── ExportProperties.tsx
│   │
│   └── preview/                  # Preview internals
│       ├── findActiveVideo.ts    # topmost-clip-at-playhead helper
│       └── usePlayback.ts        # rAF loop + <video> sync + ended
│
├── store/
│   ├── editor.ts                 # main Zustand store (73 lines)
│   └── editor/
│       ├── types.ts              # StateSlice + actions interface
│       ├── selectors.ts          # memoised selectors
│       ├── clips.ts              # assets/tracks/clips/split actions
│       ├── silence.ts            # silence detection + removal
│       └── history.ts            # push/undo/redo
│
├── lib/
│   ├── audio.ts                  # RMS-based silence detection
│   ├── media.ts                  # SRT parser + timecode utils
│   └── ffmpeg.ts                 # public export API (95 lines)
│       └── ffmpeg/
│           ├── loader.ts         # lazy wasm singleton
│           └── filter-graph.ts   # pure graph + argv builder
│
└── types/index.ts
```

หลักการ refactor:
- ทุกไฟล์ ≤ 217 บรรทัด (เป้า 500)
- แยก concerns: orchestrator (main) ↔ sub-components ↔ helpers ↔ types
- Folder-per-feature ทำให้หาไฟล์ง่าย
- Pure functions แยกออกจาก side-effects (เช่น filter-graph.ts pure, ffmpeg.ts orchestrate)


## เริ่มต้นใช้งาน

```bash
# 1. ติดตั้ง dependencies (ครั้งเดียว)
npm install

# 2. รัน dev server
npm run dev
# → เปิด http://localhost:3000

# 3. สร้าง production build
npm run build
```

## Deploy บน Vercel

```bash
# ติดตั้ง Vercel CLI (ครั้งเดียว)
npm i -g vercel

# Login
vercel login

# Deploy
vercel --prod --yes
```

โปรเจกต์ใช้ Next.js 14 App Router แบบ static + client-side FFmpeg.wasm
ทุกอย่างทำงานในเบราว์เซอร์ — Vercel จะ serve static files และ headers (COOP/COEP)
ที่จำเป็นสำหรับ SharedArrayBuffer จะถูกตั้งใน `next.config.js` แล้ว

### Deploy ผ่าน Vercel Dashboard (ถ้าไม่มี CLI)
1. Push โปรเจกต์ขึ้น GitHub
2. ไปที่ https://vercel.com/new
3. Import repository
4. กด "Deploy" — ใช้ค่า default ทั้งหมด

## Features (ครบทั้ง 28 ข้อ — V3)

### Core (1-13)
| # | ฟีเจอร์ | สถานะ | วิธีใช้ |
|---|---------|--------|---------|
| 1 | **อัปโหลดไฟล์** — drag & drop video/audio หลายไฟล์ | ✅ | ลากไฟล์มาวางที่แผง "ไลบรารีสื่อ" หรือกดปุ่ม "เลือกไฟล์" |
| 2 | **Multi-track timeline** — แยก video/audio, เพิ่ม track, ลาก clip, trim ขอบ | ✅ | กดปุ่ม "+ วิดีโอ" หรือ "+ เสียง" เพื่อเพิ่มแทร็ก · ลากคลิปย้าย · ลากขอบซ้าย/ขวาเพื่อ trim |
| 3 | **Waveform visualization** — วาดจริงจาก AudioBuffer, zoom ได้, คลิก ruler เพื่อ seek | ✅ | เพิ่มคลิปเสียงลงแทร็ก → waveform สีส้มจะปรากฏ · ใช้ปุ่ม 🔍+/🔍- ปรับ zoom |
| 4 | **Video preview** — `<video>` sync กับ playhead, play/pause/seek | ✅ | ปุ่ม ▶ ตรงกลางด้านล่าง preview · คลิกที่ ruler เพื่อ seek |
| 5 | **Trim / Cut / Split** — ตั้ง in/out (I/O), split (S), ลบ (Delete) | ✅ | เลือกคลิป → กด `I` (in-point) / `O` (out-point) / `S` (split ที่ playhead) / `Delete` (ลบ) |
| 6 | **Dead air detection** — ตรวจจับช่วงเงียบ ปรับ threshold (dB) + min duration (ms) | ✅ | กดปุ่ม "ตรวจจับช่วงเงียบ" → ช่วงเงียบจะแสดงเป็นแถบสีแดง · ดับเบิลคลิกเพื่อลบทีละช่วง หรือกด "ลบทั้งหมด" |
| 7 | **Volume control** — slider ต่อ track + ปุ่ม mute | ✅ | ที่ label ของแทร็กเสียง มี slider ระดับเสียง 0–150% + ปุ่มปิดเสียง |
| 8 | **พื้นหลัง video (สี/เบลอ)** — letterbox color picker + blur toggle | ✅ | เลือกแทร็กวิดีโอ → แผงขวามี color picker + checkbox "เบลอพื้นหลัง" |
| 9 | **คำบรรยาย (SRT import)** — นำเข้า .srt, แสดงบน preview + marker บน timeline | ✅ | แผงขวา → "คำบรรยาย" → กดปุ่ม "นำเข้า .srt" · marker สีเหลืองจะปรากฏบน ruler |
| 10 | **Undo / Redo** — Ctrl+Z / Ctrl+Shift+Z, history stack | ✅ | ปุ่ม ↶/↷ ที่ top bar หรือใช้คีย์ลัด · เก็บ history สูงสุด 50 ขั้น |
| 11 | **คีย์ลัดครบ** — Space (play/pause), J/K/L (±1s), I/O (in/out), S (split) | ✅ | ทำงานเมื่อไม่ได้ focus ที่ input/textarea |
| 12 | **Export mp4** — FFmpeg.wasm, libx264 + aac, ตัด dead air ได้ | ✅ | กดปุ่ม "ส่งออก mp4" ที่ top bar · รอ progress แล้วไฟล์จะดาวน์โหลดอัตโนมัติ |
| 13 | **Dark theme + UI ภาษาไทย** | ✅ | ทุกปุ่ม/label/tooltip/error เป็นภาษาไทย · สีดำ/ฟ้า/ส้ม ตามสไตล์ CapCut |

### v1.x (14-18)
| # | ฟีเจอร์ | สถานะ | วิธีใช้ |
|---|---------|--------|---------|
| 14 | **Noise reduction** — FFmpeg `afftdn` per audio track | ✅ | เลือก audio track → Properties → "ตัดเสียงรบกวน" + slider ความแรง |
| 15 | **Export presets** — 6 ขนาด: TikTok/Reels/YouTube/YT Shorts/IG Square/720p + FPS 24-60 | ✅ | แผงขวา → "ตั้งค่าส่งออก" → เลือก preset |
| 16 | **Transitions** — cut/crossfade/fadeIn/fadeOut/dipToBlack | ✅ | คลิกคลิป → "เอฟเฟกต์เข้าคลิป" → เลือกแบบ + ระยะเวลา (ms) |
| 17 | **Auto-captions (Whisper-tiny)** — ใช้ @xenova/transformers, รองรับไทย+อังกฤษ | ✅ | แผงขวา → "สร้างคำบรรยายอัตโนมัติ" → เลือกภาษา + model |
| 18 | **AI Editor (auto highlight)** — scene detection + audio peaks → greedy top-K clips | ✅ | แผงขวา → "AI Editor" → ตั้งค่า → "สร้างวิดีโอเด่น" |

### v2.0 (19-23)
| # | ฟีเจอร์ | สถานะ | วิธีใช้ |
|---|---------|--------|---------|
| 19 | **Auto-save (IndexedDB)** — บันทึกอัตโนมัติทุก 3 วินาที + Restore Banner | ✅ | ทำงานอัตโนมัติ · ตอนเปิดแอปจะถามว่าต้องการ restore หรือไม่ |
| 20 | **Video Audio Extract** — ดึง audio track ออกจาก video ผ่าน FFmpeg.wasm | ✅ | ใช้ภายใน Whisper auto-captions เพื่อ extract audio จากวิดีโอ |
| 21 | **Subtitle styling** — ตำแหน่ง (บน/กลาง/ล่าง), font size, สีตัวอักษร/ขอบ/พื้น, ตัวหนา | ✅ | คลิก subtitle ใน Properties → ปรับ style + ASS export |
| 22 | **Project naming + multi-project** — ตั้งชื่อโปรเจกต์ + สลับระหว่างหลายโปรเจกต์ (IndexedDB) | ✅ | คลิกชื่อโปรเจกต์ที่ topbar → rename · ปุ่ม "📁 N โปรเจกต์" → สลับ/ลบ |
| 23 | **Share Link** — gzip+base64url ใน URL hash `#p=...` ไม่ผ่าน server | ✅ | คลิกปุ่ม "แชร์" ใน topbar → copy URL → ผู้รับเปิดแล้ว popup ให้นำเข้า |

### v2.1 (24-28) — Waveform Upgrade + 5 New Features
| # | ฟีเจอร์ | สถานะ | วิธีใช้ |
|---|---------|--------|---------|
| 24 | **Track waveform peaks** — TrackLabel mini-waveform + ClipBlock keyframes curve | ✅ | อัตโนมัติ — peaks แสดงใต้ชื่อ track และ curve ของ volume keyframes บน clip |
| 25 | **Empty row ghost waveform** — deterministic peaks สำหรับ track row ที่ว่าง | ✅ | อัตโนมัติ — เห็น peaks ลากยาวแม้ไม่มี clip |
| 26 | **🎨 Color Grading (NEW)** — 8 presets (อุ่น/เย็น/ขาว-ดำ/วินเทจ/สด/ภาพยนตร์/นัวร์/ดั้งเดิม) + sliders | ✅ | คลิกคลิปวิดีโอ → "ฟิลเตอร์สี" → เลือก preset หรือปรับ brightness/contrast/saturation |
| 27 | **🔀 Speed Ramp (NEW)** — ปรับความเร็วต่อคลิป 0.25× – 4× (สโลว์โมชั่น/เร่งความเร็ว) | ✅ | คลิกคลิปวิดีโอ → "ความเร็ว" → slider หรือปุ่ม ¼× ½× 1× 2× |
| 28 | **🖼️ Image Overlay (NEW)** — ลาก PNG/JPG/WebP ลง video track (default 5s) | ✅ | ลากไฟล์ภาพลง MediaLibrary → ลง video track ได้เหมือนวิดีโอ |
| **🎞️ Text Overlay (NEW)** | ข้อความลอย (title, lower-third) — font, สี, animation, ตำแหน่ง | ✅ | คลิกปุ่ม **T** (สีส้ม) ใน topbar → ปรับ Properties panel |
| **📦 Template Library (NEW)** | 5 preset: Blank / YouTube Intro / Beat Drop / YouTube Outro / PIP Layout | ✅ | คลิกไอคอน Template ใน topbar → เลือก template → ใช้ |

## คีย์ลัดทั้งหมด

| คีย์ | การทำงาน |
|------|----------|
| `Space` | เล่น / หยุด |
| `J` / `Shift+J` | ย้อนกลับ 1s / 5s |
| `K` | หยุดเล่น |
| `L` / `Shift+L` | ไปข้างหน้า 1s / 5s |
| `I` | ตั้ง in-point ของคลิปที่เลือก |
| `O` | ตั้ง out-point ของคลิปที่เลือก |
| `S` | แยกคลิปที่ตำแหน่ง playhead |
| `Delete` / `Backspace` | ลบคลิปที่เลือก |
| `←` / `→` | เลื่อน playhead ±1 เฟรม (1/30s) |
| `Ctrl+Z` | ย้อนกลับ |
| `Ctrl+Shift+Z` / `Ctrl+Y` | ทำซ้ำ |

## โครงสร้างโปรเจกต์

```
capcut-web/
├── src/
│   ├── app/
│   │   ├── layout.tsx       # Root layout
│   │   ├── page.tsx         # Main page (dynamic-import editor)
│   │   └── globals.css      # Tailwind + custom styles
│   ├── components/
│   │   ├── Editor.tsx          # Main shell layout
│   │   ├── TopBar.tsx          # Title, undo/redo, export button
│   │   ├── MediaLibrary.tsx    # Drag-drop file upload + asset list
│   │   ├── Preview.tsx         # Video player + subtitle overlay
│   │   ├── Timeline.tsx        # Multi-track timeline + ruler
│   │   ├── Waveform.tsx        # Canvas waveform renderer
│   │   ├── PropertiesPanel.tsx # Right-side panel (clip/track/sub/export)
│   │   ├── KeyboardShortcuts.tsx # Global keydown handler
│   │   └── Toast.tsx           # Bottom notification toast
│   ├── lib/
│   │   ├── audio.ts       # AudioBuffer decode, peak compute, silence detect
│   │   ├── ffmpeg.ts      # FFmpeg.wasm export pipeline
│   │   ├── media.ts       # SRT parser, time formatters
│   │   └── utils.ts       # cls() helper
│   ├── store/
│   │   └── editor.ts      # Zustand store (clips, tracks, undo/redo, export settings)
│   └── types/
│       └── index.ts       # Type definitions
├── test-media/            # Sample test files
│   ├── test-audio-with-silence.wav   # 35s audio w/ 4 silence gaps (5-7, 11-13, 18-20, 25-30s)
│   ├── test-video.mp4                # 15s sample video
│   └── test-subtitles.srt            # 3 Thai subtitle cues
├── next.config.js         # COOP/COEP headers for SharedArrayBuffer
├── tailwind.config.js     # CapCut color palette
├── postcss.config.js
├── tsconfig.json
├── package.json
└── README.md
```

## หมายเหตุทางเทคนิค

- **FFmpeg.wasm** โหลด core จาก unpkg CDN (`@ffmpeg/core@0.12.6`) — ทำงาน offline ได้ถ้าแคชครั้งแรกสำเร็จ
- **SharedArrayBuffer** ต้องการ COOP/COEP headers (ตั้งใน `next.config.js` แล้ว)
- **AudioContext** จะถูกสร้างตอนผู้ใช้โต้ตอบครั้งแรก (autoplay policy ของเบราว์เซอร์)
- **Tone mapping ของวิดีโอ**: ใช้ `pad` filter แทน `color + overlay` (เสถียรกว่า)
- **Waveform**: ใช้ `AudioBuffer.getChannelData()` แล้ววาดเองด้วย Canvas 2D + ResizeObserver
- **Dead air detection**: RMS ทุก 10ms, threshold แปลงจาก dB → linear, รวมช่วงที่ยาวเกิน min duration

## ทดสอบ

ไฟล์ทดสอบอยู่ใน `test-media/`:

```bash
# 1. รัน dev server
npm run dev

# 2. ลาก test-media/test-video.mp4 ลงแทร็กวิดีโอ
# 3. ลาก test-media/test-audio-with-silence.wav ลงแทร็กเสียง
# 4. กดปุ่ม "ตรวจจับช่วงเงียบ" → ควรเห็น 4 ช่วงสีแดง (5-7s, 11-13s, 18-20s, 25-30s)
# 5. ลาก test-media/test-subtitles.srt เข้าแผง "คำบรรยาย"
# 6. กดปุ่ม "ส่งออก mp4" → รอ progress 100% → ไฟล์ .mp4 จะดาวน์โหลด
```

## License

MIT


## 📦 Version History

| Version | What | Files | Features |
|---------|------|-------|----------|
| **v1.0** | MVP · 13 features พื้นฐาน | 18 | 13 |
| **v1.1** | Refactor 18 → 40 ไฟล์ ≤500 บรรทัด | 40 | 13 |
| **v1.2** | Noise reduction (afftdn) | 40 | 14 |
| **v1.3** | Demo button + 404 fix | 40 | 15 |
| **v2.0** | 5 features ใหม่ (Auto-save/Export/Transitions/Captions/AI Editor) | ~50 | 20 |
| **v2.1** | Video audio extract + IndexedDB cache | ~52 | 20 |
| **v6.0** | Export fix (ffmpeg-core local bundle) | 52 | 20 |
| **v7.0** | Subtitle styling + Project naming + Volume keyframes + Mobile + Share | ~60 | 23 |
| **v8.0** | Track waveform (mini + ghost + keyframes curve) | 64 | 23 |
| **v9.0** | 3 video tracks + Picture-in-Picture (PIP) | 64 | 23 |
| **v10.0 (V3)** | 🎨 Color Grading · 🔀 Speed Ramp · 🖼️ Image Overlay · 🎞️ Text Overlay · 📦 Templates | 66 | **28** |

## 📜 User × AI — คำสั่งจริงทั้งหมด

ทุก version เกิดจาก prompt ของ user จริง — ดู tutorial เต็มได้ที่ http://62.84.178.0:3001/

| # | User พูดว่า... | AI ทำ |
|---|----------------|------|
| 1 | "ขอ CapCut-style web editor — มี audio trim + dead air detection · UI ภาษาไทย · รัน client-only · FFmpeg.wasm · deploy ที่ VPS 62.84.178.0" | v1.0 (13 features MVP) |
| 2 | "ไฟล์ใหญ่เกินไป ขอแบ่งให้เล็กลง — ไม่เกิน 500 บรรทัด" | v1.1 (Refactor 18 → 40) |
| 3 | "อยากให้ลดเสียงรบกวนได้ — เปิด/ปิด + ปรับ strength · ใช้ FFmpeg filter" | v1.2 (afftdn) |
| 4 | "กด detect silence แล้วขึ้น 'ไม่มีคลิปเสียง' — ทำให้ง่ายขึ้น · มีวิดีโอตัวอย่าง" | v1.3 (Demo + 404 Fix) |
| 5 | "ทำให้หมดเลย — Auto-save · Export Presets · Transitions · Auto-Captions · AI Editor" | v2.0 (5 features) |
| 6 | "ถ้า VDO มีเสียงมันควรจะทำได้เลย ไม่ใช่บอกต้องมีเสียงแยก" | v2.1 (extractAudioFromVideo + Whisper cache + hallucination filter) |
| 7 | "ทดลองจริงเต็มระบบ ตอนนี้ส่งออกไม่ได้ — Loading chunk 589 failed" | v6.0 (bundle ffmpeg-core + CORP headers) |
| 8 | "ศึกษาอย่างละเอียดควรเพิ่มอะไร" → AI เสนอ 5 → "ทำให้หมดเลย" | v7.0 (Subtitle + Project + Volume + Mobile + Share) |
| 9 | "เส้นเสียงทำให้มันเป็น wave form ไม่ได้หรอ" → เลือก "D ทำทั้ง 3 อย่าง" | v8.0 (Mini + Ghost + Keyframes) |
| 10 | "เอา vdo เพิ่ม 2 track ได้ไหม" | v9.0 (3 video + PIP) |
| 11 | "🎞️ Text overlay · 🎨 Color grading · 🔀 Speed ramp · 🖼️ Image overlay · 📦 Template" | v10.0 / V3 (5 features) |
| 12 | "อัพเดทเอกสาร และ เอา code ตัวใหม่ไปแจก แบบที่เคยทำ commit push" | v10.1 (tutorial + README + ZIP + GitHub push) |

## v12 — Multi-User Cloud Storage

**Live URLs:**
- Editor: http://62.84.178.0:3000
- Auth API: http://62.84.178.0:3019

**New in v12:**
- 👤 User system (register/login/JWT auth)
- ☁️ Per-user cloud storage on VPS
- 🔄 Auto-sync local ↔ server based on auth state
- 🎛️ Storage mode picker (auto/local/server)

**Backend setup** (Node.js + Express + SQLite):

```bash
cd server
npm install
node server.js   # listens on :3019

# As a systemd service
sudo cp capcut-auth.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now capcut-auth
```

**Data layout:**
- `/opt/capcut-auth/data.db` — SQLite users + bcrypt hashes
- `/opt/capcut-auth/users/{userId}/projects.json` — project metadata
- `/opt/capcut-auth/users/{userId}/projects/{projectId}.json` — full project state
- `/opt/capcut-auth/users/{userId}/assets/{projectId}/{assetId}.{ext}` — binary blobs (videos, audio, images)

**Endpoints:**
- `POST /api/auth/register` `{username, password}` → `{token, user}`
- `POST /api/auth/login`    `{username, password}` → `{token, user}`
- `GET  /api/auth/me`                              → `{user}`
- `POST /api/auth/logout`                          → `{ok}`
- `GET  /api/projects`                             → `{projects: [...]}`
- `POST /api/projects` `{name, project}`           → `{project}`
- `GET  /api/projects/:id`                         → `{project, assetsMeta}`
- `PUT  /api/projects/:id` `{project}`             → `{ok}`
- `DELETE /api/projects/:id`                       → `{ok}`
- `PUT  /api/projects/:id/assets/:assetId` (multipart) → `{ok, size}`
- `GET  /api/projects/:id/assets/:assetId`         → binary
- `DELETE /api/projects/:id/assets/:assetId`       → `{ok}`

**Frontend behavior:**
- When NOT logged in → storage falls back to local IndexedDB (existing v11 behavior)
- When logged in (default "auto" mode) → all saves go to server, mirrored to local for offline
- User can switch to "local" mode to keep projects on-device only
