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=1setsContent-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.