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_statusmovespending → processing → done(orfailed).- The web client polls
GET /versions/{id}/reviewableand shows a processing state until the proxy/thumbnail are ready, surfacingfailedwith the error. - Failures are retried (default 2 attempts) before the job is marked
failedwithtranscode_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.