Skip to content

Media pipeline

Uploading a reviewable (a render or movie attached to a Version) kicks off a thumbnail + proxy transcode. That work is asynchronous so the upload returns immediately and large files don't block the request.

Lifecycle

POST /versions/{id}/reviewable
   │  store original, create Representation (transcode_status=pending)
return 202-style { representation, status: "pending" }   ← upload is done
   │  (background) process_reviewable:
   ▼  ffmpeg → thumbnail (+ H.264 proxy for video) → status=done
  • transcode_status moves pending → processing → done (or failed).
  • The web client polls GET /versions/{id}/reviewable and shows a processing state until the proxy/thumbnail are ready, surfacing failed with the error.
  • Failures are retried (default 2 attempts) before the job is marked failed with transcode_error — a bad upload never returns a 500.

Status API

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/versions/<id>/reviewable
# -> {"status": "done", "review_kind": "video", "review_media_key": "...", ...}

Scaling out

By default the transcode runs in a FastAPI background task in the API process — zero extra infrastructure. The job contract is just a representation_id, so for heavier workloads move process_reviewable to an external worker (Arq or Celery over the existing Redis) and have the upload enqueue the id instead of scheduling a background task. The worker reaches the same database and media storage; nothing else changes.

Storage

Media is read and written through a storage interface (LocalStorage today, S3/MinIO settings are present for a future backend). Keys are resolved against the configured root and rejected if they escape it, so the public GET /media/{key} route can never serve a file outside the store.