Upload videos (up to 1 GB, e.g. MP4, WebM, MOV) and watch them by ID. The frontend provides an upload page and a watch page; the backend stores raw uploads in S3 and serves presigned URLs; a separate worker transcodes videos into multiple qualities (360p, 720p, 1080p) and writes them to a second S3 bucket.
Upload — drag & drop, progress, then get a watch link.
Watch — play by ID; when transcoding is done, pick a quality (360p / 720p / 1080p).
ADMIN_TOKEN. Playback uses short-lived presigned URLs; CORS is configurable via CORS_ORIGINS. ┌─────────────────────────────────────────────────────────┐
│ Frontend │
│ (Svelte 5 + Vite) Upload page · Watch page (by ID) │
└───────────────────────────┬─────────────────────────────┘
│ /api
▼
┌─────────────────────────────────────────────────────────┐
│ Backend │
│ (Rust / Axum) Upload · Presigned URLs · Status │
└───────┬─────────────────────────────────┬───────────────┘
│ │
S3 multipart │ uploads/<id>.orig │ GET status, GET
upload + │ queue/<id> │ presigned URLs
write queue │ │
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ S3 raw bucket │ │ S3 video bucket │
│ uploads/, queue/ │ │ videos/<id>/ │
└────────┬──────────┘ │ status.json │
│ │ <id>/360p.mp4 … │
│ poll queue/ └────────▲───────────┘
│ claim by delete │
│ presigned GET │ put status, put
│ (no download) │ renditions
▼ │
┌─────────────────────────────────────────┴───────────┐
│ Worker │
│ (Rust) Poll S3 · FFmpeg via presigned URL · Upload │
└─────────────────────────────────────────────────────┘
VITE_API_URL). It never touches S3 directly.queue/ prefix, claims a job by deleting the marker, transcodes using a presigned URL (no local copy of the raw file), uploads renditions to the video bucket, deletes the raw object, and writes final status.json (e.g. done + qualities or error).Run the backend on port 3000 and the frontend dev server (with /api proxied to the backend) for local use. Run the worker where it can reach the same S3 buckets (same AWS credentials and bucket names as the backend).