JaduCutV2 Module¶
Backend for the JaduCutV2 timeline editor. Persists the slim JaduCutV2StoryVideo
overlay (timeline elements + settings + exports) per story, handles media asset
upload/list/delete, and orchestrates export jobs dispatched to the Python backend.
Story content (scenes, shots, dialogues, timing) remains the single source of
truth in storyvideos and is not duplicated here.
Design contract¶
storyvideos= SOT for story content (scenes, shots, dialogues, timing, media).jaduCutV2StoryVideos= editor-only overlay: timeline elements, settings, export refs.projectIdon the overlay is a one-time mirror ofstoryvideos.projectId— set at create time and structurally immutable.PUT /jaduCutV2/storyVideo/:storyIddoes not acceptprojectIdin the body.
Folder layout¶
src/jaduCutV2/
├── jaduCutV2.router.ts Express router — mounted at /jaduCutV2 in src/router.ts
├── jaduCutV2.controller.ts Request handlers + error shaping
├── jaduCutV2.service.ts Business logic + MIME/size policy + Python dispatch
└── jaduCutV2.validator.ts Zod schemas for every request
Related files outside this folder:
src/models/jaduCutV2StoryVideo.model.ts Overlay Mongo collection
src/models/jaduCutV2UploadedAssets.model.ts Uploaded-asset metadata
src/models/jaduCutV2ExportJobs.model.ts Export job state
src/shared/jaduCutV2Types.ts Wire contract + overlay shapes + enums
API endpoints¶
All endpoints are mounted under /jaduCutV2. Auth middleware on the mount:
JaduAuth.authorizeRequest([], JADU_AUTH_STUDIO_APP_ID) followed by
AuthMiddleware.jaduAuthToReqAuth.
Overlay (JaduCutV2StoryVideo)¶
| Method | Path | Purpose | Notes |
|---|---|---|---|
POST |
/storyVideo |
Create overlay for a story | 409 if one already exists for storyId. projectId set once, then immutable. |
PUT |
/storyVideo/:storyId |
Update timelineElements + settings |
404 if missing. projectId / exports NOT accepted in body. |
GET |
/storyVideo?storyId=xxx |
Fetch overlay by storyId | Miss → { isExistingJaduCutV2StoryVideo: false }. Hit → { isExistingJaduCutV2StoryVideo: true, ...doc }. |
GET |
/storyVideoList |
Paginated list | Query: page, pageSize, search (case-insensitive regex on storyId). |
DELETE |
/storyVideo/:storyId |
Soft-delete overlay | Sets isDeleted: true; 404 if missing. |
Assets (legacy-compatible)¶
| Method | Path | Purpose | Notes |
|---|---|---|---|
POST |
/uploadFile |
Upload a media asset to B2 | multer memory storage, 50 MB multer cap; per-type caps enforced in service. |
GET |
/listAssets |
Paginated asset list for caller | Optional assetType filter (IMAGE / AUDIO / VIDEO). |
POST |
/deleteAsset |
Delete asset by id for caller | Scoped to caller's userId. |
Export¶
| Method | Path | Purpose | Notes |
|---|---|---|---|
POST |
/storyVideo/export |
Queue an export job | Uniqueness enforced per-story (not per-project). After the response is sent, the service fires a fire-and-forget POST to the Python backend. |
GET |
/storyVideo/export/:exportId |
Fetch job status | Scoped to caller's userId. |
Wire contract¶
All request/response and overlay shapes are defined in
src/shared/jaduCutV2Types.ts. The FE mirrors this file under
studio-frontend/jaduCutV2/types/. The BE file is intentionally self-contained —
nested shapes like timeRange: { start: number; end: number } are inlined rather
than imported, so BE ↔ FE wire parity is easy to audit without chasing imports.
| Endpoint | Request type | Response payload |
|---|---|---|
POST /storyVideo |
CreateJaduCutV2StoryVideoRequest |
{ jaduCutV2StoryVideo: JaduCutV2StoryVideoData } |
PUT /storyVideo/:storyId |
UpdateJaduCutV2StoryVideoRequest |
{ jaduCutV2StoryVideo: JaduCutV2StoryVideoData } |
GET /storyVideo |
query storyId |
GetJaduCutV2StoryVideoResponse |
GET /storyVideoList |
query page, pageSize, search |
JaduCutV2StoryVideoListResponse |
Data model¶
| Collection | Model | Purpose |
|---|---|---|
jaduCutV2StoryVideos |
jaduCutV2StoryVideo.model.ts |
Overlay doc keyed by storyId. Fields: projectId, timelineElements, settings, exports, version, createdAt, updatedAt, isDeleted. |
jaduCutV2UploadedAssets |
jaduCutV2UploadedAssets.model.ts |
User-uploaded media metadata: userId, assetType, assetUrlPath. |
jaduCutV2ExportJobs |
jaduCutV2ExportJobs.model.ts |
Export job state: exportId, projectId, storyId, userId, status. |
Overlay document shape¶
interface JaduCutV2StoryVideoData {
projectId: string; // Mirror of storyvideos.projectId. Set once at create; immutable.
storyId: string;
version: string;
timelineElements: JaduCutV2TimelineElements; // effects + transitions + textClips
settings: JaduCutV2Settings; // timelineScale + dimensions + fps
exports: JaduCutV2ExportRef[]; // server-managed; not overwritten by PUT
createdAt?: Date;
updatedAt?: Date;
isDeleted?: boolean;
}
JaduCutV2TimelineElements, JaduCutV2Settings, and JaduCutV2ExportRef are
defined in src/shared/jaduCutV2Types.ts.
Custom audio tracks (customTracks[])¶
Partial mutations under /storyVideo/:storyId/customTracks/.... The bulk
PUT /storyVideo/:storyId does not accept customTracks.
| Concern | Implementation |
|---|---|
| Same-lane overlap | customTracksOverlap.utils.ts — rejected with 400 on add-to-lane, placement update, and move (FE also blocks before API). |
| Phase 1 FE add flow | Global + → POST .../tracks/clips (atomic new lane + clip). POST .../tracks/:trackId/clips (B) exists but is not called from the UI. |
| Lane reorder | PATCH .../reorder — no UI in phase 1. |
| Tests | tests/jaduCutV2/customTracks.test.ts, customTracksOverlap.utils.test.ts |
Concurrency and uniqueness¶
- A partial unique index on
jaduCutV2StoryVideos.storyId(whereisDeleted != true) is expected on the collection. The model performs a pre-check to return a friendly 409; if two concurrent creates slip past the pre-check, the secondinsertOnefails with MongoDB E11000, which the model maps to the same 409 so callers see uniform behavior. Index creation is a DBA / migration concern outside this module. POST /storyVideo/exportrejects withInvalidOperationError(400) when an active job already exists for the story. Uniqueness is per-story, not per-project, because a project can contain many stories.
Asset upload policy¶
Defined in jaduCutV2.service.ts — the code is the source of truth; the table
below is a reference snapshot.
| Asset type | Size limit | Accepted MIME types |
|---|---|---|
IMAGE |
20 MB | image/jpeg, image/jpg, image/png, image/gif, image/webp, image/svg+xml |
AUDIO |
50 MB | audio/mpeg, audio/mp3, audio/wav, audio/ogg, audio/aac, audio/flac |
VIDEO |
500 MB | video/mp4, video/avi, video/mov, video/wmv, video/flv, video/webm |
Multer enforces a 50 MB hard cap at the framework layer; the service enforces the per-type cap after MIME classification.
Interop¶
- Workbench — story-ops (reorder/resize/delete shot, dialogue changes) run on
storyvideosvia the workbench module. This module never mutates the overlay in response to story changes; the FE reconstructs timeline state via its own diff dispatcher when the story updates. - Python backend — export dispatch is a fire-and-forget
POST ${PYTHON_BACKEND_BASE_URL}/jaduCutV2/dispatchExportJobwithX-API-KEY: process.env.PYTHON_BACKEND_API_KEY, called after the HTTP response has already been sent.
Frontend counterpart¶
See jaduCutV2/README.md in studio-frontend for the diff-based rebuild/patch
dispatcher, clip identity system, and store layout that consume these APIs.