Express + TypeScript API for YouTube extract (yt-dlp). Health, info, jobs (live progress), cache, Range resume, download, and audio-only extract are implemented.
The UI at https://github.com/Ax108/ax-clipforge-frontend uses YouTube oEmbed for Load/preview (not POST /info). In-app Download uses POST /api/v1/jobs (video) or POST /api/v1/audio/jobs (audio) + SSE, then the browser saves GET /api/v1/jobs/:id/file. GET /api/v1/download and GET /api/v1/audio are the copyable/curl paths. /info remains available for curl/tools. Extract routes are IP rate-limited (RATE_LIMIT_* in .env.example); /health and /robots.txt are not. GET /robots.txt is Disallow: / with X-Robots-Tag on all responses.
Read docs/AGENTS.md first, then:
- Local API testing — curl full + clipped downloads and audio;
KEEP_TMP - Architecture — HTTP contracts, tmp lifecycle, how the browser saves files
- Deployment — Docker Desktop vs local Bun + yt-dlp + FFmpeg
- Maintenance — keep yt-dlp/FFmpeg current; cookies/proxy
- Now: this API on
127.0.0.1:5000(LISTEN_HOSTdefaults to loopback). Extract works viabun run docker:up(yt-dlp + FFmpeg inside the image) orbun run dev(Bun + latest yt-dlp + latest FFmpeg on PATH or viaYTDLP_BIN; Redis optional). - Not now: Vercel/Netlify/Render or a public image.
| Run | Extracts |
|---|---|
bun run docker:up |
Stored in container /tmp/clipforge/cache, reused for the same video+format+quality+range. KEEP_TMP=false only evicts by TTL/size. Host git repo is not written. |
bun run dev |
Kept in gitignored ./tmp/cache (KEEP_TMP=true by default). Same key = no second YouTube pull. |
| How they run the API | Install on the machine |
|---|---|
Recommended: bun run docker:up |
Docker Desktop (Windows/macOS) or Docker Engine (Linux). Not Bun, Node, Deno, yt-dlp, FFmpeg, or Python on the host. |
Local process: bun run dev |
Bun ≥ 1.0, Node.js ≥ 24, Deno ≥ 2.3, yt-dlp (latest), FFmpeg (latest stable). |
| Browser only | Nothing. The file is saved by the browser after /download or /audio streams Content-Disposition: attachment. |
Always use the latest yt-dlp release (yt-dlp -U or the GitHub latest binary). Use a current FFmpeg build (not a years-old distro pin if you can avoid it). YouTube extractors break often; stale binaries fail first.
bun install --frozen-lockfile
bun run allow-scripts
bun run devPrefer --frozen-lockfile so install matches bun.lock (same as CI). Use plain bun install or bun add only when you intend to change dependencies.
bunfig.toml sets ignoreScripts = true and a 3-day minimumReleaseAge. Follow install with bun run allow-scripts so any LavaMoat-allowlisted native install scripts run.
Local bun run dev: the process must find yt-dlp and FFmpeg. If GET /api/v1/health shows "ytdlp": false, extracts return 503 even though the API is up. Put both binaries on PATH for that shell, or set YTDLP_BIN in .env to the full path of yt-dlp / yt-dlp.exe (common on Windows after WinGet when the terminal was opened before PATH updated). Details: docs/DEPLOYMENT.md.
Docker: bun run docker:up — yt-dlp, FFmpeg, and Deno are inside the image; the API process is Bun. No host PATH / YTDLP_BIN needed. Compose caps CPU, memory, and process count, and rotates container logs (10MB × 3 files per service). Recreate the containers for those limits to apply.
Health: http://localhost:5000/api/v1/health
Optional env: copy .env.example. Defaults assume local Vite http://localhost:5173.
Docker (no host yt-dlp/FFmpeg/Deno). Jest and src/tests are not copied into the image (see .dockerignore); the container only runs the API:
bun run docker:up| Command | Purpose |
|---|---|
bun run dev |
Watch the API on port 5000 |
bun start |
Run without watch |
bun verify |
Lint, format check, typecheck, tests, Fallow |
bun run lint |
oxlint |
bun run format |
oxfmt |
bun run test |
Jest |
bun run allow-scripts |
LavaMoat-allowlisted install scripts |
bun run docker:up |
docker compose up --build (local image) |
Husky pre-push runs bun verify. CI on main / develop runs verify plus an audit job that fails only on high or critical advisories (bun audit --audit-level=high). Low/moderate findings are reported, not blocking.
Proprietary. Internal use only. Not for sale or distribution.
See LICENSE.