Skip to content

Media Domain

Manages media assets stored in SeaweedFS β€” video, image, audio, document. Full lifecycle: upload token β†’ TUS upload β†’ background processing (normalize/transcode/optimize/rasterize) β†’ asset serving.

See also: Architecture (serving, storage layout, upload flow, status lifecycle, NATS events) and Transcoding (data model, pipelines, passthrough, seeded profiles).

Handles:

  • TUS resumable upload (prepare β†’ upload β†’ post-finish webhook)
  • Video normalize (AV trim, mdat defrag, EBU R128 loudnorm)
  • Video transcode (CMAF ladder), image optimize (webp), document rasterize (per-page JPEG + slide metadata) β€” one shared, category-discriminated TranscodingProfile
  • Passthrough per category (skip pipeline, serve as-is)
  • Thumbnail generation (video/image/pdf/office)
  • CRUD, soft delete with S3 purge (default on), storage audit
  • NATS events on processing milestones
  • Batch upload
  • Cache purge on rename/delete/reprocess (cproxy purge not wired to media mutations)
  • Real CDN/GSLB hostname (currently a bucket-qualified local URL)

Domain Structure

domain/media/
β”œβ”€β”€ config.go                          env config
β”œβ”€β”€ helper.go                          response shaping, media-type inference, ffprobe wrappers
β”œβ”€β”€ models.go                          GORM models β€” Media, TranscodingProfile(+Version), MediaSlide, MediaPlaylist(+Item), UploadToken
β”œβ”€β”€ validation.go                      ValidatePrepare
β”‚
β”œβ”€β”€ handlers/
β”‚   β”œβ”€β”€ handler_media.go               CRUD, asset serving/presign, delete+purge
β”‚   β”œβ”€β”€ handler_upload.go              prepare token, tusd post-finish webhook
β”‚   β”œβ”€β”€ handler_process.go             pipeline orchestration, thumbnails, dedup
β”‚   β”œβ”€β”€ handler_normalize.go           video normalize (remux + loudnorm)
β”‚   β”œβ”€β”€ handler_image.go               image optimize (webp)
β”‚   β”œβ”€β”€ handler_document.go            document rasterize + slides
β”‚   β”œβ”€β”€ handler_transcode.go           video CMAF ladder
β”‚   β”œβ”€β”€ handler_transcoding_profiles.go  profile/version CRUD
β”‚   β”œβ”€β”€ handler_playlist.go            media playlists (SMIL/CMAF)
β”‚   β”œβ”€β”€ handler_query.go               folders/tenants/tags/stats
β”‚   β”œβ”€β”€ handler_nats.go                NATS subscribers
β”‚   β”œβ”€β”€ handler_storage_audit.go       orphan/missing audit
β”‚   └── handler_pdf.go                 legacy PDF path β€” see To-do, not part of the real pipeline
β”‚
β”œβ”€β”€ repositories/media_repository.go   MediaRepository
└── routes/                            wire.go (Setup) + routes.go

Mount point: media's own routes (/media/...) sit under /api/v1; /assets/* and /hooks/tusd are mounted on the app root instead (see Architecture).


Testing

An in-page upload form isn't practical here β€” this is a static docs site, and the upload path is TUS (a resumable-upload protocol), not a plain HTTP form Swagger's "try it" can drive.

  • The embedded spec below can exercise every JSON endpoint directly against a running server (/media/prepare, /media/transcoding-profiles CRUD, /media/:id, etc. β€” see the servers: list in the spec).
  • The file upload step itself still needs a real TUS client β€” either drive it through PlayoutAdmin's own "Add content" UI, or see guide.md for a curl-based walkthrough per pipeline (profile lookup β†’ prepare β†’ verify).

To-do / known gaps

  • Auth: media routes aren't gated by the RS256 JWT middleware at registration (unlike identity/tenancy) β€” confirm whether that's intentional before wiring tenant/org derivation from the token.
  • Tenant/folder still hardcoded on the frontend β€” see the Media Upload capability app for the concrete to-do (derive from AuthAPI's orgs[] claim).
  • /hooks/tusd has no signature/secret check β€” only the opaque uploadToken metadata value gates it. Anyone who can reach CoreAPI can POST a fabricated post-finish payload.
  • handler_pdf.go (GET /media/convert-pdf/:key) is a legacy, parallel PDF-rasterization path with hardcoded staging hostnames (stg.filer.castis.io, stg.api.castis.io) and its own local disk cache β€” unrelated to and duplicating handler_document.go's real rasterizeDocument pipeline. Candidate for removal.
  • Dead code: validation.go's ValidateDirectUpload, handler_playlist_helpers.go's two error constructors, handler_playlist.go's concatAndNormalize β€” none have any caller.
  • /assets/* is bypassed in the local/Quickstart topology β€” nginx routes it straight to cproxyβ†’SeaweedFS, not to CoreAPI's own ServeAsset/PresignAsset. Don't assume a browser client ever hits CoreAPI directly for asset bytes; see Architecture.
  • Batch upload, cache invalidation on mutation, real CDN hostname β€” as above.