Skip to content

Library & Media

The platform persists two kinds of things: media assets (images, audio, video, narration, renders) and saved documents (projects). Both are file-backed under a single STORAGE_ROOT and served through Next.js API routes.

Storage map: web/src/lib/storage.ts Routes: /api/media/[...path], /api/assets, /api/assets/upload, /api/director/docs

Storage layout

STORAGE_ROOT is storage/ in dev and /app/storage in the container (an Azure file share in production). Logical categories map to subfolders:

Category Folder Written by
Saved documents projects/ /api/director/docs (and Director HANDOFF)
Narration voiceover/ /api/director/voiceover
Renders renders/ /api/director/export
Uploads uploads/ /api/assets/upload
Music / audio audio/ seeded library + uploads
Images images/ seeded + uploads
Footage footage/ seeded video clips

Serving media — /api/media/[...path]

Streams a logical path (e.g. voiceover/s1.mp3) to the resolved physical file:

  • Range-aware — audio/video scrubbing works
  • Traversal-guarded — paths can't escape STORAGE_ROOT
  • Not auth-gated — the headless renderer's Chromium fetches assets without a session cookie
  • MIME types for audio (mp3/wav/m4a/aac/ogg), video (mp4/webm/mov), image (png/jpg/svg/webp/avif)
  • Optional ?download=1 sets Content-Disposition: attachment

Browsing assets — /api/assets

GET /api/assets?kind=audio|image|video walks the relevant media folders and returns logical paths, used by the inspector's media pickers (MediaInput, VoicePicker). This is how "pick an image / audio / clip" dialogs are populated.

Uploading — /api/assets/upload

POST multipart FormData → files land in uploads/. Filenames are sanitized and returned as logical paths (uploads/<timestamp>-<checksum>-<safe-name>), ready to drop into a scene's media slot, or to use as a logo/brand asset.

Saved documents (projects) — /api/director/docs

The project store (storage/projects/, file-backed; survives restarts):

Method Path Purpose
GET /api/director/docs List saved documents — { id, title, savedAt, scenes, seconds, aspect }
POST /api/director/docs Save / update a document (the full ResolvedIR + title) → docId
GET /api/director/docs/:id Fetch one document
DELETE /api/director/docs/:id Delete a document

The editor lists these to reopen prior work, and the Director writes finished documents here at HANDOFF.

Provenance & cleanup

Assets aren't tracked in a database — they're discovered by scanning the storage folders. Renders accumulate under renders/; in production you'll want to prune old renders/voiceover on a schedule. Saved documents are lightweight JSON and safe to keep.

Legacy /api/assets (Express)

The retired v1 had a different /api/assets on the Express server backed by per-run runs/ directories with categories like footage, voiceover, render, stock-footage. That endpoint is part of the unused v1 surface; the live Library is the Next.js /api/assets + /api/media + /api/director/docs described above.