Files
twotalesanimation 81ad7e4ea9 Initial commit
2026-06-11 10:46:09 +02:00

4.6 KiB
Raw Permalink Blame History

Remote Transcoder Worker

Runs on Windows Docker Desktop (or any machine with Docker).
Pulls video jobs from the Hetzner CMS over HTTPS, transcodes them locally using all available CPU cores, and pushes the finished HLS package back — no SSH, no shared drives, no VPS CPU load.


How it works

Docker Desktop (local)                  Hetzner VPS (CMS)
──────────────────────                  ─────────────────
1. POST /api/transcoder/claim     →     Lock next uploaded video
   ← { videoId, downloadUrl }
2. GET  /api/transcoder/download/:id →  Stream source MP4
3. ffmpeg (local CPU)
   Generate HLS segments + playlists
4. Create ZIP of HLS output
5. POST /api/transcoder/upload/:id  →  Extract ZIP, validate, rename
                                        Update status → "transcoded"
6. Repeat until queue empty, exit 0

Prerequisites

Requirement Notes
Docker Desktop Windows, Mac, or Linux
CMS environment variable TRANSCODER_SECRET Add to CMS .env and redeploy
CMS redeployed with new API routes See CMS changes section

Setup

1 — Add TRANSCODER_SECRET to the CMS

In your Hetzner CMS .env (or however you manage secrets):

TRANSCODER_SECRET=replace-with-a-strong-random-secret

Generate a secret with:

openssl rand -hex 32

Redeploy the CMS so the new API routes are live.

2 — Configure the worker

cd transcoder-remote
cp .env.example .env
# Edit .env and fill in CMS_URL and TRANSCODER_SECRET

.env example:

CMS_URL=https://cms.yourdomain.com
TRANSCODER_SECRET=replace-with-a-strong-random-secret

Running

Build and run (one command)

docker compose up --build

The container will:

  1. Build the TypeScript worker
  2. Start processing all queued videos
  3. Exit automatically when the queue is empty

Run again later (no rebuild)

docker compose up

Run without docker-compose

docker build -t transcoder-remote .
docker run --rm --env-file .env transcoder-remote

Log output

[Worker] Remote Transcoder starting
[Worker] CMS     : https://cms.yourdomain.com
[Worker] Work dir: /work

[Job] cm1a2b3c4d5e6f7g8h9i0j

[Download] Starting...
[Download] 312 MB received...
[Download] 624 MB in 38s

[Probe] Resolution: 1920x1080
[HLS] Creating 1080p
[HLS] Creating 720p
[HLS] Creating 480p
[Transcode] 1080p, 720p, 480p in 8m 14s

[Package] Creating zip...
[Package] 1.1 GB in 22s

[Upload] Starting...
[Upload] Done in 31s

[Complete] cm1a2b3c4d5e6f7g8h9i0j in 9m 45s

[Worker] Queue empty. Jobs processed this run: 1
[Worker] Exiting.

CMS changes (already applied)

Four new API routes were added to the CMS Next.js app:

Route Purpose
POST /api/transcoder/claim Atomically claim next job
GET /api/transcoder/download/:videoId Stream source MP4
POST /api/transcoder/upload/:videoId Receive HLS zip
POST /api/transcoder/fail/:videoId Mark job failed

All routes require Authorization: Bearer <TRANSCODER_SECRET>.

The claim endpoint uses SELECT … FOR UPDATE SKIP LOCKED so multiple workers can run concurrently without racing on the same job.


Disk space requirements

Each job requires roughly:

Step Space
Downloaded MP4 Up to ~2 GB
HLS output (all variants) ~14× source size
ZIP archive ~same as HLS output
Total per job ~48 GB

Ensure Docker Desktop's virtual disk limit is large enough (Settings → Resources → Disk image size). 80 GB+ is recommended for large educational videos.

Temp files are deleted automatically after each job.


Transcoding settings

Identical to the VPS transcoder — output is fully compatible with the existing HLS player:

Variant Resolution Video bitrate Audio
1080p 1920×1080 3500k (max 4000k) AAC 128k
720p 1280×720 1800k (max 2000k) AAC 128k
480p 854×480 900k (max 1000k) AAC 128k

Variants are skipped if the source resolution is smaller than the preset.


Environment variables

Variable Required Description
CMS_URL Public HTTPS base URL of the CMS
TRANSCODER_SECRET Shared secret matching TRANSCODER_SECRET on CMS
WORK_DIR optional Temp directory inside container (default: /work)

Exit codes

Code Meaning
0 Success (all jobs processed, or no jobs found)
1 Fatal error (missing env vars, DB unreachable, etc.)