A self-hosted video transcoding service with a built-in web console. Submit a video, pick a template, get transcoded outputs back in object storage — powered by FFmpeg, managed by Go, fronted by Astro.
You have videos. You need them in the right format, the right resolution, with watermarks — at scale. video-encoder handles the pipeline: upload → queue → transcode → store → notify, with a web UI for self-service and a REST API for automation.
- Multi-codec transcoding — H.264 / H.265 / AV1 / VP9, MP4 / WebM containers, CRF or bitrate mode, per-rendition tuning.
- ABR ladders — one template, multiple renditions (720p / 480p / …), each independently configured.
- Watermarks — text and image overlays, 9-anchor positioning, opacity, time ranges, auto-scaling across renditions.
- Thumbnail sprites — generate sprite sheets + WebVTT for player scrubber previews, with or without transcoding.
- Templates — system presets + tenant custom templates, validated JSONB spec, reusable across jobs.
- Web console — upload, submit, monitor, manage templates and watermarks, all from the browser.
- Bilingual UI — English (default) and Chinese, switchable from the sidebar.
- Webhooks — HMAC-signed callbacks on job completion, per-job or per-tenant.
- Multi-tenant — API-key auth, isolated tenants, self-service sign-up.
docker compose up -d --build
docker compose run --rm api seed # load system preset templates
docker compose run --rm api tenant acme # create a tenant → prints your API keyThat's it. Three services come up:
| Service | URL | What |
|---|---|---|
| Web console | http://localhost:4321 |
Sign up, upload, transcode, manage |
| API | http://localhost:8080 |
REST API for automation |
| MinIO | http://localhost:9001 |
Object storage console (minioadmin/minioadmin) |
Open the console, sign up, upload a video, pick a template, hit transcode. Done.
Prefer running from source? See Development Setup below.
curl -X POST http://localhost:8080/v1/jobs \
-H "Authorization: Bearer vek_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"template_id": "tpl_mp4_720p",
"input": {"storage": "s3://videos/in/src.mp4"},
"output": {"prefix": "s3://videos/out/job1"}
}'
# → {"job_id":"...","status":"queued"}
curl http://localhost:8080/v1/jobs/<job_id> \
-H "Authorization: Bearer vek_xxxxx"Client ──POST /v1/jobs──▶ API ──enqueue──▶ Worker
│ │ download → FFmpeg → upload
▼ │
Postgres Object Storage
Client ◀──GET /v1/jobs── API ◀── webhook ──┘
Single Go binary (videoenc) runs as api or worker. Postgres stores state, Redis queues jobs, S3/MinIO holds I/O. The web console is a separate Astro SSR app talking to the API.
Valid combos: MP4 → h264/h265/av1 · WebM → vp9. See the API docs in the console for the full spec.
# Prerequisites: Go 1.25+, Docker, FFmpeg (for local transcode tests)
# Start dependencies
docker compose up -d
# Migrate + seed + create tenant
go run ./cmd/videoenc migrate
go run ./cmd/videoenc seed
go run ./cmd/videoenc tenant acme
# Run api + worker (two terminals)
go run ./cmd/videoenc api
go run ./cmd/videoenc worker
# Frontend (another terminal)
cd web && npm install && npm run dev
# Tests
make test # Go
cd web && npm test # FrontendKey env vars are in .env.example. The Docker image bundles FFmpeg (with drawtext + all encoders) and CJK fonts — no host-side FFmpeg needed.
- GPU hardware encoding (NVENC / VAAPI)
- MPEG-DASH output
- Commercial DRM (Widevine / FairPlay)
MIT
{ "output": { "container": "mp4" }, "renditions": [ { "name": "720p", "fit": "letterbox", "encoding": { "codec": "h264", "width": 1280, "height": 720, "crf": 23, "preset": "veryfast", "audio_bitrate": 128000, "channels": 2 } } ], "watermarks": [ { "type": "text", "text": "© 2026", "anchor": "bottom_right", "font": "NotoSansSC", "font_size_pct": 4, "color": "white" } ], "thumbnails": { "columns": 4, "rows": 10 } // optional sprite + WebVTT }