@nodaro/sdk 2.54.0 → 2.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-P6AM42UB.cjs → chunk-2KX5ZE6M.cjs} +2 -2
- package/dist/chunk-2KX5ZE6M.cjs.map +1 -0
- package/dist/{chunk-JOVJAKBT.cjs → chunk-2LOTDOUV.cjs} +6 -6
- package/dist/chunk-2LOTDOUV.cjs.map +1 -0
- package/dist/{chunk-AN36M6HO.js → chunk-37LL6BPM.js} +5 -5
- package/dist/chunk-37LL6BPM.js.map +1 -0
- package/dist/{chunk-22P4X3N5.cjs → chunk-3CVWTUK5.cjs} +26 -8
- package/dist/chunk-3CVWTUK5.cjs.map +1 -0
- package/dist/{chunk-7CUWAVWB.js → chunk-4FSWJTRS.js} +3 -3
- package/dist/{chunk-7CUWAVWB.js.map → chunk-4FSWJTRS.js.map} +1 -1
- package/dist/{chunk-DV6GTEX5.js → chunk-4SZUSZXK.js} +2 -2
- package/dist/{chunk-DV6GTEX5.js.map → chunk-4SZUSZXK.js.map} +1 -1
- package/dist/{chunk-L6T44A3G.js → chunk-4VP2GFIZ.js} +20 -5
- package/dist/chunk-4VP2GFIZ.js.map +1 -0
- package/dist/{chunk-NKH2UFSD.cjs → chunk-4WOT5OAY.cjs} +5 -5
- package/dist/{chunk-NKH2UFSD.cjs.map → chunk-4WOT5OAY.cjs.map} +1 -1
- package/dist/{chunk-W5RMFTPA.cjs → chunk-57GANTLV.cjs} +2 -2
- package/dist/chunk-57GANTLV.cjs.map +1 -0
- package/dist/{chunk-HK5FSTKG.cjs → chunk-A3XA6UNM.cjs} +2 -2
- package/dist/chunk-A3XA6UNM.cjs.map +1 -0
- package/dist/{chunk-AUKHMT53.js → chunk-BGOZRZHY.js} +3 -3
- package/dist/{chunk-AUKHMT53.js.map → chunk-BGOZRZHY.js.map} +1 -1
- package/dist/{chunk-PBFODIFT.js → chunk-CBDUHYTR.js} +2 -2
- package/dist/chunk-CBDUHYTR.js.map +1 -0
- package/dist/{chunk-OSMKIRKI.cjs → chunk-DFT4LD2H.cjs} +4 -4
- package/dist/{chunk-OSMKIRKI.cjs.map → chunk-DFT4LD2H.cjs.map} +1 -1
- package/dist/{chunk-RBX3QTKB.js → chunk-E6X6BGRO.js} +3 -3
- package/dist/{chunk-RBX3QTKB.js.map → chunk-E6X6BGRO.js.map} +1 -1
- package/dist/{chunk-47ULSVO2.js → chunk-FTCAT2LK.js} +3 -3
- package/dist/chunk-FTCAT2LK.js.map +1 -0
- package/dist/{chunk-QGVOAPUI.js → chunk-GIQDM2MH.js} +2 -2
- package/dist/chunk-GIQDM2MH.js.map +1 -0
- package/dist/{chunk-65VIIZEF.cjs → chunk-GJH73EGF.cjs} +2 -2
- package/dist/{chunk-65VIIZEF.cjs.map → chunk-GJH73EGF.cjs.map} +1 -1
- package/dist/{chunk-HAVIVOLO.js → chunk-HFYNZ4SO.js} +2 -2
- package/dist/chunk-HFYNZ4SO.js.map +1 -0
- package/dist/{chunk-THLTFB6I.js → chunk-HJLSOT5C.js} +2 -2
- package/dist/chunk-HJLSOT5C.js.map +1 -0
- package/dist/{chunk-D2ILCSOQ.js → chunk-HK6XP5EN.js} +3 -3
- package/dist/{chunk-D2ILCSOQ.js.map → chunk-HK6XP5EN.js.map} +1 -1
- package/dist/{chunk-HWSYNUSC.js → chunk-HUYI5G3D.js} +2 -2
- package/dist/chunk-HUYI5G3D.js.map +1 -0
- package/dist/{chunk-JVDUGVLX.cjs → chunk-MIXRL6I2.cjs} +2 -2
- package/dist/chunk-MIXRL6I2.cjs.map +1 -0
- package/dist/{chunk-SF2CMKWA.cjs → chunk-OMJ45CW4.cjs} +9 -9
- package/dist/{chunk-SF2CMKWA.cjs.map → chunk-OMJ45CW4.cjs.map} +1 -1
- package/dist/{chunk-DAONQZPI.cjs → chunk-PDX742CC.cjs} +2 -2
- package/dist/chunk-PDX742CC.cjs.map +1 -0
- package/dist/{chunk-EZXFDCII.js → chunk-PQ6G5EM7.js} +15 -15
- package/dist/{chunk-EZXFDCII.js.map → chunk-PQ6G5EM7.js.map} +1 -1
- package/dist/{chunk-FRQJNGJS.cjs → chunk-Q4MFQC2J.cjs} +5 -5
- package/dist/{chunk-FRQJNGJS.cjs.map → chunk-Q4MFQC2J.cjs.map} +1 -1
- package/dist/{chunk-VHTDOACT.cjs → chunk-Q7W4QNE5.cjs} +22 -22
- package/dist/chunk-Q7W4QNE5.cjs.map +1 -0
- package/dist/{chunk-XZRBRXGJ.cjs → chunk-RIMDKJSQ.cjs} +28 -28
- package/dist/{chunk-XZRBRXGJ.cjs.map → chunk-RIMDKJSQ.cjs.map} +1 -1
- package/dist/{chunk-BQAW4RX2.js → chunk-VEQLP5TA.js} +25 -7
- package/dist/chunk-VEQLP5TA.js.map +1 -0
- package/dist/{chunk-I3MEBVLP.js → chunk-XZPSJG2X.js} +2 -2
- package/dist/chunk-XZPSJG2X.js.map +1 -0
- package/dist/{chunk-O74MSMFH.cjs → chunk-XZQ6Z4T6.cjs} +2 -2
- package/dist/chunk-XZQ6Z4T6.cjs.map +1 -0
- package/dist/{chunk-FM2KM5TY.cjs → chunk-Z4B4SZ33.cjs} +21 -4
- package/dist/chunk-Z4B4SZ33.cjs.map +1 -0
- package/dist/{developer-apps-zSQXWmEG.d.ts → developer-apps-CMUzKpE9.d.cts} +2 -0
- package/dist/{developer-apps-zSQXWmEG.d.cts → developer-apps-CMUzKpE9.d.ts} +2 -0
- package/dist/entries/apps.d.cts +1 -1
- package/dist/entries/apps.d.ts +1 -1
- package/dist/entries/audio.d.cts +1 -1
- package/dist/entries/audio.d.ts +1 -1
- package/dist/entries/billing.d.cts +1 -1
- package/dist/entries/billing.d.ts +1 -1
- package/dist/entries/cast-swap.cjs +2 -2
- package/dist/entries/cast-swap.d.cts +13 -1
- package/dist/entries/cast-swap.d.ts +13 -1
- package/dist/entries/cast-swap.js +1 -1
- package/dist/entries/catalogs.d.cts +1 -1
- package/dist/entries/catalogs.d.ts +1 -1
- package/dist/entries/characters.cjs +10 -10
- package/dist/entries/characters.d.cts +11 -3
- package/dist/entries/characters.d.ts +11 -3
- package/dist/entries/characters.js +1 -1
- package/dist/entries/client.cjs +21 -21
- package/dist/entries/client.d.cts +5 -5
- package/dist/entries/client.d.ts +5 -5
- package/dist/entries/client.js +16 -16
- package/dist/entries/collections.d.cts +1 -1
- package/dist/entries/collections.d.ts +1 -1
- package/dist/entries/community.d.cts +1 -1
- package/dist/entries/community.d.ts +1 -1
- package/dist/entries/competitors.d.cts +1 -1
- package/dist/entries/competitors.d.ts +1 -1
- package/dist/entries/copilot.cjs +4 -4
- package/dist/entries/copilot.d.cts +1 -1
- package/dist/entries/copilot.d.ts +1 -1
- package/dist/entries/copilot.js +3 -3
- package/dist/entries/core.cjs +6 -6
- package/dist/entries/core.d.cts +1 -1
- package/dist/entries/core.d.ts +1 -1
- package/dist/entries/core.js +2 -2
- package/dist/entries/creatures.cjs +6 -6
- package/dist/entries/creatures.d.cts +11 -3
- package/dist/entries/creatures.d.ts +11 -3
- package/dist/entries/creatures.js +1 -1
- package/dist/entries/credits.d.cts +1 -1
- package/dist/entries/credits.d.ts +1 -1
- package/dist/entries/developer-apps.d.cts +1 -1
- package/dist/entries/developer-apps.d.ts +1 -1
- package/dist/entries/edit.cjs +7 -7
- package/dist/entries/edit.d.cts +1 -1
- package/dist/entries/edit.d.ts +1 -1
- package/dist/entries/edit.js +2 -2
- package/dist/entries/errors.cjs +32 -24
- package/dist/entries/errors.d.cts +28 -4
- package/dist/entries/errors.d.ts +28 -4
- package/dist/entries/errors.js +1 -1
- package/dist/entries/executions.d.cts +1 -1
- package/dist/entries/executions.d.ts +1 -1
- package/dist/entries/gallery.d.cts +1 -1
- package/dist/entries/gallery.d.ts +1 -1
- package/dist/entries/jobs.cjs +2 -2
- package/dist/entries/jobs.d.cts +7 -4
- package/dist/entries/jobs.d.ts +7 -4
- package/dist/entries/jobs.js +1 -1
- package/dist/entries/keepers.d.cts +1 -1
- package/dist/entries/keepers.d.ts +1 -1
- package/dist/entries/library.d.cts +1 -1
- package/dist/entries/library.d.ts +1 -1
- package/dist/entries/llm.d.cts +1 -1
- package/dist/entries/llm.d.ts +1 -1
- package/dist/entries/locations.cjs +5 -5
- package/dist/entries/locations.d.cts +11 -3
- package/dist/entries/locations.d.ts +11 -3
- package/dist/entries/locations.js +1 -1
- package/dist/entries/media.cjs +4 -4
- package/dist/entries/media.d.cts +34 -5
- package/dist/entries/media.d.ts +34 -5
- package/dist/entries/media.js +3 -3
- package/dist/entries/models.d.cts +1 -1
- package/dist/entries/models.d.ts +1 -1
- package/dist/entries/moodboards.d.cts +1 -1
- package/dist/entries/moodboards.d.ts +1 -1
- package/dist/entries/node-presets.d.cts +1 -1
- package/dist/entries/node-presets.d.ts +1 -1
- package/dist/entries/nodes.cjs +3 -3
- package/dist/entries/nodes.d.cts +3 -3
- package/dist/entries/nodes.d.ts +3 -3
- package/dist/entries/nodes.js +2 -2
- package/dist/entries/oauth.d.cts +1 -1
- package/dist/entries/oauth.d.ts +1 -1
- package/dist/entries/objects.cjs +6 -6
- package/dist/entries/objects.d.cts +11 -3
- package/dist/entries/objects.d.ts +11 -3
- package/dist/entries/objects.js +1 -1
- package/dist/entries/organizations.d.cts +1 -1
- package/dist/entries/organizations.d.ts +1 -1
- package/dist/entries/palettes.d.cts +1 -1
- package/dist/entries/palettes.d.ts +1 -1
- package/dist/entries/picker-catalogs.d.cts +1 -1
- package/dist/entries/picker-catalogs.d.ts +1 -1
- package/dist/entries/pipelines.d.cts +1 -1
- package/dist/entries/pipelines.d.ts +1 -1
- package/dist/entries/projects.d.cts +1 -1
- package/dist/entries/projects.d.ts +1 -1
- package/dist/entries/prompt-helper.d.cts +1 -1
- package/dist/entries/prompt-helper.d.ts +1 -1
- package/dist/entries/recast.d.cts +1 -1
- package/dist/entries/recast.d.ts +1 -1
- package/dist/entries/reduce.d.cts +1 -1
- package/dist/entries/reduce.d.ts +1 -1
- package/dist/entries/saved-posts.d.cts +1 -1
- package/dist/entries/saved-posts.d.ts +1 -1
- package/dist/entries/scene3d.d.cts +4 -4
- package/dist/entries/scene3d.d.ts +4 -4
- package/dist/entries/sessions.d.cts +1 -1
- package/dist/entries/sessions.d.ts +1 -1
- package/dist/entries/shots.d.cts +1 -1
- package/dist/entries/shots.d.ts +1 -1
- package/dist/entries/studio.cjs +5 -5
- package/dist/entries/studio.d.cts +3 -3
- package/dist/entries/studio.d.ts +3 -3
- package/dist/entries/studio.js +2 -2
- package/dist/entries/templates.d.cts +1 -1
- package/dist/entries/templates.d.ts +1 -1
- package/dist/entries/trash.d.cts +1 -1
- package/dist/entries/trash.d.ts +1 -1
- package/dist/entries/trends.cjs +2 -2
- package/dist/entries/trends.d.cts +37 -17
- package/dist/entries/trends.d.ts +37 -17
- package/dist/entries/trends.js +1 -1
- package/dist/entries/tutorials.d.cts +1 -1
- package/dist/entries/tutorials.d.ts +1 -1
- package/dist/entries/uploads.d.cts +1 -1
- package/dist/entries/uploads.d.ts +1 -1
- package/dist/entries/user-settings.d.cts +1 -1
- package/dist/entries/user-settings.d.ts +1 -1
- package/dist/entries/video-pro.d.cts +1 -1
- package/dist/entries/video-pro.d.ts +1 -1
- package/dist/entries/voices.d.cts +1 -1
- package/dist/entries/voices.d.ts +1 -1
- package/dist/entries/webhooks.d.cts +3 -3
- package/dist/entries/webhooks.d.ts +3 -3
- package/dist/entries/workflows.d.cts +1 -1
- package/dist/entries/workflows.d.ts +1 -1
- package/dist/entries/workspaces.d.cts +1 -1
- package/dist/entries/workspaces.d.ts +1 -1
- package/dist/index.cjs +90 -82
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +16 -16
- package/dist/{nodes-DuXF6XVv.d.ts → nodes-1u7MPrD5.d.ts} +11 -1
- package/dist/{nodes-bY51Z1Os.d.cts → nodes-BZavad1T.d.cts} +11 -1
- package/dist/{studio-productions-r2-VZsKj.d.cts → studio-productions-CMiS827q.d.cts} +1 -1
- package/dist/{studio-productions-DtTE8mdl.d.ts → studio-productions-DkI51Z9M.d.ts} +1 -1
- package/dist/{webhooks-BJawEIz8.d.ts → webhooks-C2DZTK4n.d.ts} +1 -1
- package/dist/{webhooks-Dn_DtNTD.d.cts → webhooks-tg3VN39V.d.cts} +1 -1
- package/package.json +3 -3
- package/dist/chunk-22P4X3N5.cjs.map +0 -1
- package/dist/chunk-47ULSVO2.js.map +0 -1
- package/dist/chunk-AN36M6HO.js.map +0 -1
- package/dist/chunk-BQAW4RX2.js.map +0 -1
- package/dist/chunk-DAONQZPI.cjs.map +0 -1
- package/dist/chunk-FM2KM5TY.cjs.map +0 -1
- package/dist/chunk-HAVIVOLO.js.map +0 -1
- package/dist/chunk-HK5FSTKG.cjs.map +0 -1
- package/dist/chunk-HWSYNUSC.js.map +0 -1
- package/dist/chunk-I3MEBVLP.js.map +0 -1
- package/dist/chunk-JOVJAKBT.cjs.map +0 -1
- package/dist/chunk-JVDUGVLX.cjs.map +0 -1
- package/dist/chunk-L6T44A3G.js.map +0 -1
- package/dist/chunk-O74MSMFH.cjs.map +0 -1
- package/dist/chunk-P6AM42UB.cjs.map +0 -1
- package/dist/chunk-PBFODIFT.js.map +0 -1
- package/dist/chunk-QGVOAPUI.js.map +0 -1
- package/dist/chunk-THLTFB6I.js.map +0 -1
- package/dist/chunk-VHTDOACT.cjs.map +0 -1
- package/dist/chunk-W5RMFTPA.cjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/resources/locations.ts"],"names":[],"mappings":"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","file":"chunk-MIXRL6I2.cjs","sourcesContent":["import type { CharacterAspectRatio, SurroundDirection, LocationAssetType, LocationAttachColumn } from \"@nodaro/shared/entities\"\nexport type { LocationAssetType, LocationAttachColumn, CharacterAspectRatio, SurroundDirection } from \"@nodaro/shared/entities\"\nexport { LOCATION_ASSET_TYPES, LOCATION_ATTACH_COLUMNS, SURROUND_DIRECTIONS } from \"@nodaro/shared/entities\"\nimport type { NodaroClientCore } from \"../core.js\"\nimport type { SetKeeperCatalogInput, SetKeeperCatalogResult } from \"./keepers.js\"\nimport type { GenerationSessionParams } from \"./nodes.js\"\n\n/**\n * Re-export the shared `LocationAssetType` / `LocationAttachColumn` unions and\n * their runtime tuples so SDK consumers don't have to add `@nodaro/shared` as a\n * second dependency just to typecheck the `assetType` / `attachToColumn`\n * fields. Single source of truth lives in `@nodaro/shared/entity-prompts`.\n *\n * `CharacterAspectRatio` is re-exported alongside them — `generateMotion`'s\n * `aspectRatio` field reuses the same 4-value enum (1:1 / 3:4 / 16:9 / 9:16)\n * as characters; the route enforces this with `z.enum(CHARACTER_ASPECT_OPTIONS)`.\n */\n\n/**\n * Reference-photo kind discriminator — the mood-board roles a user can attach\n * to a location. Mirrors the `reference_photos.kind` Zod enum in\n * `backend/src/routes/locations.ts`. `other` is the free-form bucket.\n */\nexport type LocationReferencePhotoKind =\n | \"wide\"\n | \"interior\"\n | \"exterior\"\n | \"detail\"\n | \"moodBoard\"\n | \"other\"\n\nexport interface LocationReferencePhoto {\n url: string\n kind: LocationReferencePhotoKind\n}\n\n/**\n * A location record returned by Nodaro's REST API. Mirrors the camelCase\n * shape produced by `backend/src/routes/locations.ts::toCamel()`.\n *\n * Asset buckets (`timeOfDay`, `weather`, `angles`, `lighting`, `seasons`,\n * `atmosphereMotions`) are independent JSONB arrays keyed by a human-readable\n * variant name (e.g. `\"dawn\"`, `\"clear\"`, `\"wide\"`). Each entry's `url` points\n * at an R2-hosted asset.\n *\n * Identity-foundation fields:\n * - `referencePhotos` — caller-supplied mood-board refs (cap 20).\n * - `canonicalDescription` — ~80–120-word LLM-authored visual caption,\n * populated by `approveMainImage()` / `recaption()`. The wire still sends\n * `\"\"` on caption sub-failure (the breaking wire change is deferred to a\n * major bump), but `get()` normalizes `\"\"` → `null` so consumers see the\n * same `string | null` semantics as characters.\n * - `styleLock` — whether asset gens should anchor to the canonical style\n * captured at approval time. Defaults to `true` on new rows.\n */\nexport interface Location {\n id: string\n userId: string\n nodeId: string\n projectId: string | null\n name: string\n description: string | null\n category: string | null\n style: string | null\n sourceImageUrl: string | null\n /** MODEL_CATALOG image-model id the main image was generated with (or `null`).\n * Set on create + editable via the update route. */\n imageProvider: string | null\n timeOfDay: Array<{ name: string; url: string }>\n weather: Array<{ name: string; url: string }>\n angles: Array<{ name: string; url: string }>\n lighting: Array<{ name: string; url: string }>\n seasons: Array<{ name: string; url: string }>\n atmosphereMotions: Array<{ name: string; url: string }>\n /** Named Location Boards — dense reference sheets, one per variant/mood\n * (the `generate-image/location-board` factory preset rendered from the\n * location's images). A first-class bucket: community publish snapshots it\n * and clone hands the consumer their own copy to extend. Defaults to `[]`.\n * `type` marks an image-collage `\"identity\"` sheet vs a plain `\"looks\"`\n * board; `sourceImages` are the R2 URLs it was collaged from. Both optional\n * + backward-compatible (legacy boards have neither). */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }> | null\n referencePhotos: LocationReferencePhoto[]\n /** `null` when no caption is set (or the LLM caption sub-failed) — the wire\n * sends `\"\"`, normalized to `null` in `get()` to match character semantics. */\n canonicalDescription: string | null\n styleLock: boolean\n /** The user's chosen DEFAULT asset take per variant (Studio version history).\n * OPAQUE map: key `\"<bucket>:<variant>\"` (e.g. `\"timeOfDay:dawn\"`) → the chosen\n * asset URL (one already present in that bucket). Stored verbatim — keys are\n * NOT normalized; soft-capped server-side at 200 keys / 2048-char values\n * (overflow dropped silently). Defaults to `{}`. */\n selectedAssetByVariant?: Record<string, string> | null\n /** Nodaro Keepers: whether this row is on the shared shelf (`client.keepers`).\n * Admin-set through `setCatalog`; `false` on everyone else's rows and on\n * every copy. Optional on the read surface for older servers. */\n catalog?: boolean\n /** The shelf order (lowest first); `0` when never set. */\n catalogSort?: number\n /** The shelf heading, or `null`. */\n catalogCategory?: string | null\n deletedAt: string | null\n createdAt: string\n updatedAt: string\n}\n\n/**\n * GET /v1/locations/:id appends a `pendingJobs` bucket the studio uses to\n * rehydrate spinners after a reload. Optional on the SDK surface — it doesn't\n * appear on `list()` rows.\n */\nexport interface LocationDetail extends Location {\n pendingJobs?: Array<{\n jobId: string\n assetType: string\n name: string\n status: string\n }>\n /**\n * Completed candidate main images for this location whose URL differs from\n * the current `sourceImageUrl`, newest first (max 5). Surfaced so a\n * \"pick from N\" UI can offer alternatives the user can promote via\n * `approveMainImage(id, jobId)`; until they pick, the current main image is\n * left untouched. Mirrors `CharacterDetail.previousCandidates`. Optional -\n * absent on `list()` rows, present (possibly empty) on `get()`.\n */\n previousCandidates?: Array<{\n jobId: string\n url: string\n createdAt: string\n }>\n}\n\n/**\n * Body for `client.locations.create()`. Mirrors the INSERT branch of\n * `upsertLocationBody` in `backend/src/routes/locations.ts`. `name` + `nodeId`\n * are required on create.\n */\nexport interface CreateLocationInput {\n nodeId: string\n name: string\n description?: string\n category?: string\n style?: string\n workflowId?: string\n projectId?: string\n sourceImageUrl?: string\n /** Persistent image-model id (a MODEL_CATALOG image model). Validated\n * server-side — unknown / non-image / \"\" is stored as `null`. */\n imageProvider?: string | null\n referencePhotos?: LocationReferencePhoto[]\n canonicalDescription?: string\n styleLock?: boolean\n /** The user's chosen DEFAULT asset take per variant. OPAQUE\n * `\"<bucket>:<variant>\"` → chosen-URL map; a write REPLACES the whole map.\n * Keys stored verbatim; soft-capped server-side at 200 keys / 2048-char values. */\n selectedAssetByVariant?: Record<string, string>\n}\n\n/**\n * Body for `client.locations.update()`. Mirrors the UPDATE branch of\n * `upsertLocationBody` in `backend/src/routes/locations.ts`.\n *\n * Worker-owned asset buckets (`timeOfDay`/`weather`/`angles`/`lighting`/\n * `seasons`/`atmosphereMotions`) are deliberately omitted — the route drops\n * them on UPDATE so a Studio auto-save with a stale snapshot cannot clobber\n * the worker's atomic `append_location_asset()` writes.\n *\n * `expectedUpdatedAt` is the optimistic-concurrency token: when present, the\n * UPDATE only succeeds if the row's `updated_at` still matches; on mismatch\n * the route returns 409 so the studio can re-fetch + merge.\n */\nexport interface UpdateLocationInput {\n name?: string\n description?: string\n category?: string\n style?: string\n sourceImageUrl?: string\n /** Persistent image-model id (a MODEL_CATALOG image model). Validated\n * server-side — unknown / non-image / \"\" is stored as `null`. */\n imageProvider?: string | null\n referencePhotos?: LocationReferencePhoto[]\n canonicalDescription?: string\n styleLock?: boolean\n /** The user's chosen DEFAULT asset take per variant. OPAQUE\n * `\"<bucket>:<variant>\"` → chosen-URL map; a write REPLACES the whole map\n * (omit to leave untouched). Keys stored verbatim; soft-capped server-side\n * at 200 keys / 2048-char values. */\n selectedAssetByVariant?: Record<string, string>\n /** Named Location Boards (see `Location.boards`) — whole-array replace,\n * USER-owned (unlike the worker-owned buckets it flows through UPDATE).\n * Server caps: 24 boards, 200-char names, 30 sourceImages per board. */\n boards?: Array<{ name: string; url: string; type?: \"looks\" | \"identity\"; sourceImages?: string[] }>\n /** ISO-8601 timestamp recording when PII consent was captured for this location. */\n piiConsentAt?: string\n expectedUpdatedAt?: string\n}\n\nexport interface UpdateLocationResult {\n id: string\n updatedAt: string\n}\n\nexport interface ListLocationsParams {\n /** When true, return archived locations instead of active ones. */\n archived?: boolean\n /**\n * Search words: only the locations whose name or description contains\n * every word (split on spaces, the first 5), in any order, ignoring case.\n * Each word is matched as written (`%`, `_` and `*` are not wildcards;\n * punctuation is kept); blank means no search; up to 200 characters.\n * Combines with the other filters and with `cursor` paging.\n */\n q?: string\n /**\n * Max rows to return (cap 500). OPT-IN: omitted, the server returns the\n * full legacy listing with no `nextCursor`; supplied, the response is one\n * page plus `nextCursor` — keep passing it back until it is `null`.\n */\n limit?: number\n /** Opaque cursor from a prior page's `nextCursor` (fetches the next page). */\n cursor?: string\n}\n\n/**\n * Input for `client.locations.generate()` — fires the\n * `POST /v1/generate-location` route. Produces 1–10 candidate\n * establishing shots; each lands as one `jobs` row in `pending` state and\n * is then enqueued for the worker.\n *\n * When `attachToLocationId` is set AND `count === 1`, the worker writes the\n * resulting URL directly to `locations.source_image_url` on completion —\n * caller doesn't need a separate `approveMainImage` call. Multi-candidate\n * batches MUST go through explicit approval so the user picks the winner.\n */\nexport interface GenerateLocationInput extends GenerationSessionParams {\n name: string\n description?: string\n userPrompt?: string\n category?: \"indoor\" | \"outdoor\" | \"urban\" | \"nature\" | \"fantasy\" | \"sci-fi\" | \"historical\" | \"futuristic\" | \"other\"\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n sourceImageUrl?: string\n provider?: string\n /** Number of candidate images to generate (1–10; server-validated). */\n count?: number\n /** Auto-attach the result to this location row (single-candidate only). */\n attachToLocationId?: string\n /**\n * Credit-affecting quality tier (e.g. `\"high\"` for gpt-image). Priced like\n * generate-image (composite ids such as `gpt-image:high`); values the chosen\n * model doesn't support are ignored server-side, never rejected.\n */\n quality?: string\n /**\n * Credit-affecting output resolution (e.g. `\"2K\"` / `\"4K\"` / `\"2 MP\"`).\n * Priced like generate-image (composite ids such as `nano-banana-pro:4K`);\n * values the chosen model doesn't support are ignored server-side, never\n * rejected.\n */\n resolution?: string\n}\n\n/**\n * `generate()` response — `jobIds` is ALWAYS present (the harmonized contract,\n * matching characters). `jobId` is a deprecated back-compat alias populated only\n * on `count === 1`; prefer `jobIds`. (Will be removed on the next major.)\n */\nexport interface GenerateLocationResult {\n jobIds: string[]\n /** @deprecated count===1 back-compat alias — use `jobIds`. */\n jobId?: string\n}\n\n/**\n * Input for `client.locations.generateAsset()` — fires the\n * `POST /v1/generate-location-asset` route. Produces a single\n * timeOfDay / weather / seasons / angles / lighting / custom variant.\n *\n * When all three studio-path fields are set (`attachToLocationId` +\n * `attachToColumn` + `attachName`), the worker appends\n * `{ name: attachName, url: <result> }` to the named JSONB array column on\n * the user's location row on completion. `attachToColumn` is REQUIRED for\n * `assetType === \"custom\"` — the worker can't infer the bucket from the\n * asset type.\n */\nexport interface GenerateLocationAssetInput extends GenerationSessionParams {\n assetType: LocationAssetType\n variant: string\n name: string\n description?: string\n userPrompt?: string\n category?: string\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n sourceImageUrl?: string\n provider?: string\n /** Optional framing override (the same 4-value enum as `generateMotion`).\n * Absent = the image model's default. The studio's 360° surround path pins\n * `\"16:9\"` so every ring view matches the establishing shot's frame. */\n aspectRatio?: CharacterAspectRatio\n attachToLocationId?: string\n attachToColumn?: LocationAttachColumn\n attachName?: string\n /**\n * Credit-affecting quality tier (e.g. `\"high\"` for gpt-image). Values the\n * chosen model doesn't support are ignored server-side, never rejected.\n */\n quality?: string\n /**\n * Credit-affecting output resolution (e.g. `\"2K\"` / `\"4K\"` / `\"2 MP\"`).\n * Values the chosen model doesn't support are ignored server-side, never\n * rejected.\n */\n resolution?: string\n}\n\n/**\n * Input for `client.locations.generateSurroundContinuation()` — fires the\n * `POST /v1/generate-surround-continuation` route. Generates one seamless 360°\n * ring view as an image-to-image continuation of `referenceImageUrl` (the\n * previous ring view, or the establishing shot for the first ring).\n *\n * The platform owns the whole pipeline: it builds the half-carry composite\n * server-side (carry the reference's trailing half into the new frame's leading\n * half per `direction`, gray the rest), paints the gray region, then\n * color-harmonizes the painted half to the carried half so there is no tonal\n * seam down the frame's center. The carried half stays pixel-exact, so a\n * panorama viewer stitching adjacent ring views stays geometrically seamless.\n *\n * When the studio path is set (`attachToLocationId` + `attachToColumn` +\n * `attachName`), the worker appends `{ name: attachName, url: <result> }` to the\n * location's bucket (studio uses `attachToColumn: \"angles\"`,\n * `attachName: \"Surround 45°\"`).\n */\nexport interface GenerateSurroundContinuationInput extends GenerationSessionParams {\n /** The previous ring view to continue from (i2i anchor). */\n referenceImageUrl: string\n /** Carry/paint axis: turn right, turn left, tilt up, or tilt down. Tilts\n * render the sky/ground (a thin horizon strip), not a horizontal continuation. */\n direction: SurroundDirection\n /** Ring angle (45, 90, …) — stored on the result as metadata. */\n degrees?: number\n /** Fraction of the frame carried from the reference. Omitted ⇒ per-direction\n * default (0.5 for a pan, 0.12 thin strip for a tilt). */\n carriedFraction?: number\n /** Upscale/denoise the result before it's chained as the next reference, to\n * slow cumulative softening down a long ring chain. Default false. */\n refine?: boolean\n /** Refine model when `refine` is set. `recraft-upscale` (1 cr, default) or\n * `topaz-image-upscale` (3 cr). */\n refineProvider?: \"recraft-upscale\" | \"topaz-image-upscale\"\n /** Optional free-form scene hint woven into the fill prompt. */\n userPrompt?: string\n /** Image model. Defaults to the platform's general image model (`gpt-image-2`). */\n provider?: string\n /** Studio pins `\"16:9\"` so every ring view matches the establishing frame. */\n aspectRatio?: CharacterAspectRatio\n attachToLocationId?: string\n attachToColumn?: LocationAttachColumn\n attachName?: string\n}\n\n/**\n * Input for `client.locations.generateMotion()` — fires the\n * `POST /v1/generate-location-motion` route. Produces a single atmospheric\n * motion clip (drifting fog, snowfall, rolling waves, etc.) animated FROM a\n * static establishing-shot image.\n *\n * Mirrors `client.characters.generateMotion()` minus the character-specific\n * fields (gender / baseOutfit / realLifeRefs). The route hardcodes the attach\n * column to `atmosphere_motions` — callers supply `attachToLocationId` +\n * `attachName` only.\n *\n * `sourceImageUrl` is REQUIRED — image-to-video needs a source frame and the\n * route has no fallback (no `source_image_url` column to pull from on the\n * locations row; the studio path supplies the canonical establishing-shot URL\n * explicitly).\n *\n * When the studio path is set (`attachToLocationId` + `attachName`), the\n * worker appends `{ name: attachName, url: <result> }` to the location row's\n * `atmosphere_motions` JSONB column on completion.\n */\nexport interface GenerateLocationMotionInput extends GenerationSessionParams {\n motionPrompt: string\n sourceImageUrl: string\n provider?: string\n name: string\n category?: string\n style?: \"realistic\" | \"anime\" | \"3d-pixar\" | \"illustration\"\n canonicalDescription?: string\n attachToLocationId?: string\n attachName?: string\n /**\n * Optional aspect ratio override. Defaults to 16:9 server-side via\n * `resolveLocationAspectRatio` (locations are cinematic establishing shots).\n * One of the 4-value `CharacterAspectRatio` union — locations reuse the\n * character aspect enum since the supported ratios are identical.\n */\n aspectRatio?: CharacterAspectRatio\n}\n\nexport interface ApproveMainImageResult {\n sourceImageUrl: string\n /**\n * LLM-authored caption. `null` when the LLM caption sub-failed — the wire\n * sends `\"\"`, normalized to `null` here so consumers see the same\n * `string | null` semantics as characters. The main image is still set; call\n * `recaption()` to retry.\n */\n canonicalDescription: string | null\n}\n\nexport interface RecaptionLocationResult {\n canonicalDescription: string\n}\n\nexport class LocationsResource {\n constructor(private client: NodaroClientCore) {}\n\n /**\n * List the caller's locations. By default returns active locations only;\n * pass `archived: true` to fetch soft-deleted rows for an \"archive\" view,\n * and `q` to keep the ones whose name or description contains it.\n */\n list(params: ListLocationsParams = {}): Promise<{ locations: Location[]; nextCursor?: string | null }> {\n const query: Record<string, string | undefined> = {}\n if (params.archived) query.archived = \"true\"\n if (params.q) query.q = params.q\n if (params.limit !== undefined) query.limit = String(params.limit)\n if (params.cursor) query.cursor = params.cursor\n return this.client.request(\"GET\", \"/v1/locations\", { query })\n }\n\n /**\n * Convenience wrapper for `list({ archived: true })`. Returns soft-deleted\n * rows so callers can drive a UI \"Archived\" tab without re-encoding the\n * query param. Mirrors `ObjectsResource.listArchived`.\n *\n * `archived` is omitted from the param type — it's always set to `true` here.\n */\n listArchived(params: Omit<ListLocationsParams, \"archived\"> = {}): Promise<{ locations: Location[]; nextCursor?: string | null }> {\n return this.list({ ...params, archived: true })\n }\n\n /**\n * Fetch a single location including in-flight asset job state. Soft-deleted\n * (archived) rows are returned by id intentionally so canvas nodes that\n * hold a stale `locationDbId` keep loading.\n */\n async get(id: string): Promise<LocationDetail> {\n const res = await this.client.request<LocationDetail>(\n \"GET\",\n `/v1/locations/${encodeURIComponent(id)}`,\n )\n // Normalize the wire `\"\"` caption (DB null / LLM sub-failure) → null so\n // consumers see the same `string | null` semantics as characters. New\n // object — never mutate the response.\n return { ...res, canonicalDescription: res.canonicalDescription || null }\n }\n\n /**\n * Create a new location. `name` + `nodeId` are required — the route 400s\n * otherwise. Returns the new row's id.\n *\n * Note: the underlying route is the same `POST /v1/locations` upsert that\n * powers `update()`. This convenience wrapper enforces the INSERT-required\n * fields at the type level and never sends an `id`.\n */\n create(data: CreateLocationInput): Promise<{ id: string }> {\n return this.client.request(\"POST\", \"/v1/locations\", { body: data })\n }\n\n /**\n * Update a location. Only the fields you pass are written — undefined keys\n * are NOT touched on the row. Worker-owned asset buckets are intentionally\n * not exposed on this surface (see `UpdateLocationInput` for the rationale).\n *\n * Optimistic-concurrency: pass `expectedUpdatedAt` to require the row's\n * `updated_at` still matches; on mismatch the route returns 409\n * `concurrent_modification`. The SDK surfaces that as a generic\n * `NodaroError` with the same code.\n */\n update(id: string, data: UpdateLocationInput): Promise<UpdateLocationResult> {\n return this.client.request(\"POST\", \"/v1/locations\", {\n body: { id, ...data },\n })\n }\n\n /**\n * Soft-delete (archive) a location. The row is hidden from `list()` by\n * default but still loadable via `get(id)` so canvas nodes pointing at it\n * keep working. Restore with `restore(id)`.\n */\n delete(id: string): Promise<{ success: true; archived: true }> {\n return this.client.request(\"DELETE\", `/v1/locations/${encodeURIComponent(id)}`)\n }\n\n /**\n * Un-archive a location. If the original name now collides (case-\n * insensitive) with an active row, the server auto-suffixes \"(restored)\"\n * and returns the effective name.\n */\n restore(id: string): Promise<{ id: string; name: string }> {\n return this.client.request(\"POST\", `/v1/locations/${encodeURIComponent(id)}/restore`)\n }\n\n /**\n * Admin only: put one of your own locations on the shared Nodaro Keepers\n * shelf (`client.keepers.catalog()`), or take it off. A first-party session\n * or your personal token; `forbidden` for anyone who is not a platform\n * admin, `not_found` for a row that is not yours.\n */\n setCatalog(id: string, input: SetKeeperCatalogInput): Promise<SetKeeperCatalogResult> {\n return this.client.request(\"PUT\", `/v1/locations/${encodeURIComponent(id)}/catalog`, { body: input })\n }\n\n /**\n * Fire `POST /v1/generate-location` to produce one or more candidate main\n * images. With `count > 1`, all jobs are reserved up-front before any\n * is enqueued — mid-batch failures roll back atomically.\n *\n * When `attachToLocationId` is set AND `count === 1`, the worker writes\n * the result directly to the row's `source_image_url`; otherwise you must\n * call `approveMainImage()` after picking a candidate.\n */\n async generate(data: GenerateLocationInput): Promise<GenerateLocationResult> {\n const res = await this.client.request<{ jobId?: string; jobIds?: string[] }>(\n \"POST\", \"/v1/generate-location\", { body: data },\n )\n // Tolerate the legacy `{ jobId }`-only shape (older server): synthesize jobIds.\n const jobIds = res.jobIds ?? (res.jobId ? [res.jobId] : [])\n return res.jobId ? { jobIds, jobId: res.jobId } : { jobIds }\n }\n\n /**\n * Fire `POST /v1/generate-location-asset` to produce a single variant.\n * When the studio path is set (`attachToLocationId` + `attachToColumn` +\n * `attachName`), the worker appends `{ name: attachName, url: <result> }`\n * to the named JSONB array column on completion.\n */\n generateAsset(data: GenerateLocationAssetInput): Promise<{ jobId: string }> {\n return this.client.request(\"POST\", \"/v1/generate-location-asset\", { body: data })\n }\n\n /**\n * Fire `POST /v1/generate-surround-continuation` to produce one seamless 360°\n * ring view as an i2i continuation of `referenceImageUrl`. The platform builds\n * the half-carry composite, paints the missing half, and color-harmonizes it\n * to the carried half (no tonal seam; carried half stays pixel-exact). When the\n * studio path is set, the worker appends the result to the location's bucket.\n */\n generateSurroundContinuation(data: GenerateSurroundContinuationInput): Promise<{ jobId: string }> {\n return this.client.request(\"POST\", \"/v1/generate-surround-continuation\", { body: data })\n }\n\n /**\n * Fire `POST /v1/generate-location-motion` to animate the location's\n * establishing shot into an atmospheric motion clip. Image-to-video, single\n * clip per call; the attach column is hardcoded to `atmosphere_motions`\n * server-side (locations have a single motion bucket so the caller doesn't\n * supply `attachToColumn`). When the studio path is set\n * (`attachToLocationId` + `attachName`), the worker appends\n * `{ name: attachName, url: <result> }` to the row's `atmosphere_motions`\n * column on completion.\n */\n generateMotion(data: GenerateLocationMotionInput): Promise<{ jobId: string }> {\n return this.client.request(\"POST\", \"/v1/generate-location-motion\", { body: data })\n }\n\n /**\n * Atomically remove ONE asset take (every entry matching `url`) from a\n * worker-owned bucket column — `POST /v1/locations/:id/remove-asset`. The\n * worker-owned buckets are deliberately not writable through `update()`\n * (a stale snapshot would race concurrent worker appends), so deleting a\n * take — e.g. a 360° surround view being regenerated — goes through this\n * single-statement server-side filter instead. 404s (`NotFoundError`) when\n * the url isn't in that bucket or the location isn't yours.\n */\n removeAsset(\n id: string,\n data: { column: LocationAttachColumn; url: string },\n ): Promise<{ removed: true }> {\n return this.client.request(\n \"POST\",\n `/v1/locations/${encodeURIComponent(id)}/remove-asset`,\n { body: data },\n )\n }\n\n /**\n * Approve a completed `generate-location` job as the location's main image.\n * Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)\n * inline. Returns the new main-image URL plus the caption.\n *\n * Caption-failure semantics: the route still sends `\"\"` on LLM sub-failure,\n * but the SDK normalizes `\"\"` → `null` here so `canonicalDescription` carries\n * the same `string | null` semantics as characters. The main image is still\n * set; call `recaption()` to retry.\n */\n async approveMainImage(id: string, candidateJobId: string): Promise<ApproveMainImageResult> {\n const res = await this.client.request<{ sourceImageUrl: string; canonicalDescription: string | null }>(\n \"POST\",\n `/v1/locations/${encodeURIComponent(id)}/approve-main-image`,\n { body: { candidateJobId } },\n )\n // Normalize the wire `\"\"` (LLM sub-failure) → null; build a new object\n // rather than mutating the response.\n return { sourceImageUrl: res.sourceImageUrl, canonicalDescription: res.canonicalDescription || null }\n }\n\n /**\n * Re-fire the LLM caption against the location's current main image. 502s\n * on LLM failure (unlike `approveMainImage` which preserves the side-effect\n * and returns \"\"); returns 400 `no_source_image` if no main image is set\n * yet.\n */\n recaption(id: string): Promise<RecaptionLocationResult> {\n return this.client.request(\n \"POST\",\n `/v1/locations/${encodeURIComponent(id)}/llm-caption`,\n )\n }\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkZ4B4SZ33_cjs = require('./chunk-Z4B4SZ33.cjs');
|
|
4
4
|
var edl = require('@nodaro/shared/edl');
|
|
5
5
|
var nodes = require('@nodaro/shared/nodes');
|
|
6
6
|
|
|
@@ -277,7 +277,7 @@ var EditResource = class {
|
|
|
277
277
|
*/
|
|
278
278
|
async continueUseCase(input, options) {
|
|
279
279
|
if (input.nodeType !== void 0 && !nodes.isUseCaseNodeType(input.nodeType)) {
|
|
280
|
-
throw new
|
|
280
|
+
throw new chunkZ4B4SZ33_cjs.NodaroError(
|
|
281
281
|
`continueUseCase: nodeType "${String(input.nodeType)}" is not a use-case node type (${nodes.USE_CASE_NODE_TYPES.join(", ")})`,
|
|
282
282
|
"invalid_node_type",
|
|
283
283
|
400
|
|
@@ -298,12 +298,12 @@ var EditResource = class {
|
|
|
298
298
|
* Any other failure of the read, any other 403 included, is thrown as is.
|
|
299
299
|
*/
|
|
300
300
|
async runTypeOf(jobId) {
|
|
301
|
-
const unknown = () => new
|
|
301
|
+
const unknown = () => new chunkZ4B4SZ33_cjs.NodaroError("continueUseCase: the run's node type cannot be read \u2014 pass nodeType", "use_case_type_unknown", 400);
|
|
302
302
|
let row;
|
|
303
303
|
try {
|
|
304
304
|
row = (await this.client.request("GET", `/v1/jobs/${encodeURIComponent(jobId)}`)).data;
|
|
305
305
|
} catch (err) {
|
|
306
|
-
if (err instanceof
|
|
306
|
+
if (err instanceof chunkZ4B4SZ33_cjs.ForbiddenError && err.missingScope === "jobs:read") throw unknown();
|
|
307
307
|
throw err;
|
|
308
308
|
}
|
|
309
309
|
const input = row?.input_data;
|
|
@@ -330,7 +330,7 @@ var EditResource = class {
|
|
|
330
330
|
});
|
|
331
331
|
if (!planned.ok) {
|
|
332
332
|
const message = planned.issues.map((issue) => edl.describeAudioSyncOffsetIssue(issue)).join("; ");
|
|
333
|
-
return Promise.reject(new
|
|
333
|
+
return Promise.reject(new chunkZ4B4SZ33_cjs.NodaroError(`editPlan: ${message}`, "edit_plan_sources", 400));
|
|
334
334
|
}
|
|
335
335
|
const transcript = planned.transcriptSourceId ? { ...input.transcript, sourceId: planned.transcriptSourceId } : input.transcript;
|
|
336
336
|
return this.client.request("POST", "/v1/edit-plan", {
|
|
@@ -360,14 +360,14 @@ var EditResource = class {
|
|
|
360
360
|
*/
|
|
361
361
|
cameraSwitch(input) {
|
|
362
362
|
if (edl.transcriptSpeakerLabels(input.transcript).length === 0) {
|
|
363
|
-
return Promise.reject(new
|
|
363
|
+
return Promise.reject(new chunkZ4B4SZ33_cjs.NodaroError(
|
|
364
364
|
"cameraSwitch: the transcript has no speaker labels \u2014 transcribe with speaker detection on",
|
|
365
365
|
"no_speakers",
|
|
366
366
|
422
|
|
367
367
|
));
|
|
368
368
|
}
|
|
369
369
|
const result = input.speakerMapResult;
|
|
370
|
-
if (result !== void 0 && !edl.speakerMapResultOf(result)) return Promise.reject(new
|
|
370
|
+
if (result !== void 0 && !edl.speakerMapResultOf(result)) return Promise.reject(new chunkZ4B4SZ33_cjs.NodaroError("cameraSwitch: speakerMapResult is not a Speaker Map result \u2014 pass that job's output_data.json", "not_a_speaker_map", 400));
|
|
371
371
|
const speakerMap = result !== void 0 ? edl.cameraSwitchSpeakerMap({ speakerMap: input.speakerMap }, input.edl, input.transcript, { speakerMap: result }) : input.speakerMap;
|
|
372
372
|
return this.client.request("POST", "/v1/camera-switch", {
|
|
373
373
|
body: {
|
|
@@ -404,5 +404,5 @@ exports.CLIP_PACK_MAX_TARGET_SEC = CLIP_PACK_MAX_TARGET_SEC;
|
|
|
404
404
|
exports.CLIP_PACK_MIN_TARGET_SEC = CLIP_PACK_MIN_TARGET_SEC;
|
|
405
405
|
exports.EditResource = EditResource;
|
|
406
406
|
exports.TRAILER_FORMATS = TRAILER_FORMATS;
|
|
407
|
-
//# sourceMappingURL=chunk-
|
|
408
|
-
//# sourceMappingURL=chunk-
|
|
407
|
+
//# sourceMappingURL=chunk-OMJ45CW4.cjs.map
|
|
408
|
+
//# sourceMappingURL=chunk-OMJ45CW4.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/resources/edit-use-case-inputs.ts","../src/resources/edit.ts"],"names":["isUseCaseNodeType","NodaroError","USE_CASE_NODE_TYPES","ForbiddenError","resolveEditPlanSources","describeAudioSyncOffsetIssue","transcriptSpeakerLabels","speakerMapResultOf","cameraSwitchSpeakerMap","edl","remapTranscriptThroughEdl"],"mappings":"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","file":"chunk-SF2CMKWA.cjs","sourcesContent":["/**\n * The inputs of the podcast use-case runs on the `edit` resource (Tighten\n * Episode, Chapters, Trailer, Clip Pack, Speaker Layout) and the request body each one sends.\n * Split from `edit.ts`, which holds the methods, the type list and the\n * continue routes; every name here is re-exported from there.\n */\nimport type { EditPlanTier, EdlTargetAspect, SpeakerEmphasisStyle, SpeakerSwitchId } from \"@nodaro/shared/edl\"\nimport type { CaptionStyle } from \"@nodaro/shared/captions\"\n\n/**\n * The recordings of a use-case run: one (`mediaUrl`), or the recordings of one\n * conversation (`mediaUrls`, in order — the cameras; one may be sound only),\n * an optional Master audio and the Cameras settings. Pass `mediaUrl` or\n * `mediaUrls`, not both.\n *\n * Several recordings or a Master audio need Speaker Map, which tells who is on\n * which camera: where it is not available to you (not yet on nodaro.ai) the\n * request is refused `403 node_not_available` — send one recording.\n */\nexport interface UseCaseRecordingsInput {\n /** The recording — a video or audio URL, up to 3 hours. */\n mediaUrl?: string\n /**\n * Instead of `mediaUrl`: the recordings of one conversation, in order — up\n * to six in all with the Master audio; without one, two or more cameras keep\n * a place for the sound, so up to five. With two or more the run lines them\n * up, works out who is on which camera and cuts to whoever is talking.\n */\n mediaUrls?: string[]\n /** The mixer or recorder track: the episode's sound and the clock the recordings are lined up to. */\n masterAudioUrl?: string\n /** With several recordings or a Master audio: the Cameras settings (ignored with one recording and no Master audio). */\n cameras?: {\n /** Which recording (1-based) is the wide shot, shown when a speaker has no camera of their own; `null` for none. */\n wide?: number | null\n /** The recordings already share one clock: skip lining them up. */\n synced?: boolean\n /** Who is on which camera: speaker number (`\"1\"` = the first voice heard) → recording number, 0 = the wide shot. Overrides the automatic map. */\n speakerCameras?: Record<string, number>\n }\n}\n\n/**\n * Input for {@link EditResource.tightenEpisode} and\n * {@link EditResource.estimateTightenEpisode}: the recording (or the\n * recordings of one conversation, see {@link UseCaseRecordingsInput}) in, the\n * tightened episode out. Only the recording is required; every other field\n * left out takes the node's default.\n */\nexport interface TightenEpisodeInput extends UseCaseRecordingsInput {\n /** Optional music bed, ducked under the voice and ending with the episode. */\n musicUrl?: string\n /** `\"video\"` or `\"audio\"`; an audio recording always makes an audio episode. */\n output?: \"video\" | \"audio\"\n /**\n * Frame a video episode with Speaker View: an output aspect and a layout,\n * over automatic boxes on the speakers on screen. A layout the aspect does\n * not draw is snapped to one it does. `null` or absent: no framing.\n */\n framing?: {\n aspect: EdlTargetAspect\n layout: \"auto\" | \"single\" | \"side-by-side\" | \"stacked\" | \"grid\" | \"pip\"\n } | null\n /**\n * With a framing, how it follows the speakers: `\"static\"` (the default)\n * splits the frame evenly by speaker; `\"tracking\"` follows faces with\n * Speaker Frames (charged per started block of 1,000 sampled frames; a\n * camera shared by several speakers keeps the even split until faces are\n * named; refused 403 where Speaker Frames is not available to the caller);\n * `\"auto\"` follows faces while Speaker Frames is available to the caller,\n * else the even split. Ignored without a framing.\n */\n framingSource?: \"auto\" | \"static\" | \"tracking\"\n /** Captions on a video episode (on by default). */\n captions?: boolean\n /** The captions' look. */\n captionStyle?: CaptionStyle\n /**\n * Stop at a quick preview for a person to review; render the full-quality\n * episode afterwards with {@link EditResource.continueUseCase}\n * (`mode: \"render-final\"`).\n */\n reviewBeforeRender?: boolean\n /** The planner's tier. */\n planTier?: EditPlanTier\n /** Editing instructions for the planner (up to 4 000 characters). */\n instructions?: string\n /** A style guide for the planner (up to 4 000 characters). */\n styleGuide?: string\n /** How far the music dips under the voice, 0–100 (default 75). */\n musicDuckAmount?: number\n}\n\n/**\n * Input for {@link EditResource.episodeChapters} and\n * {@link EditResource.estimateEpisodeChapters}: one recording in, its chapters\n * out. Only `mediaUrl` is required; every other field left out takes the\n * node's default.\n */\nexport interface EpisodeChaptersInput {\n /** The recording — a video or audio URL, up to 3 hours. */\n mediaUrl: string\n /** The planner's tier (default `\"economy\"`). */\n planTier?: EditPlanTier\n /** Instructions for the chapters, e.g. \"5 to 8 chapters\" (up to 4 000 characters). */\n instructions?: string\n /** A style guide for the chapter titles (up to 4 000 characters). */\n styleGuide?: string\n}\n\n/** The formats a Trailer is delivered in, in delivery order (16:9 is a 1920×1080 landscape). */\nexport const TRAILER_FORMATS = [\"9:16\", \"1:1\", \"4:5\", \"16:9\"] as const\n/** One Trailer format (see {@link TRAILER_FORMATS}). */\nexport type TrailerFormat = (typeof TRAILER_FORMATS)[number]\n\n/**\n * Input for {@link EditResource.episodeTrailer} and\n * {@link EditResource.estimateEpisodeTrailer}: one video recording (or the\n * recordings of one conversation, see {@link UseCaseRecordingsInput}) in, a\n * 20–40 s trailer out in each chosen format. Only the recording is required.\n */\nexport interface EpisodeTrailerInput extends UseCaseRecordingsInput {\n /** The recording — a video URL, up to 3 hours (a run with no picture is refused). Or `mediaUrls`. */\n mediaUrl?: string\n /** An optional intro card: a short video (15 s at most) played before the trailer. */\n introCardUrl?: string\n /** The formats to deliver, padded, never cropped (default `[\"9:16\", \"1:1\", \"4:5\"]`; an empty list is refused). */\n formats?: TrailerFormat[]\n /** Stop at a preview of the trailer; render every format with {@link EditResource.continueUseCase} (`mode: \"render-final\"`). */\n reviewBeforeRender?: boolean\n /** The planner's tier (default `\"standard\"`). */\n planTier?: EditPlanTier\n /** Instructions for the trailer (up to 4 000 characters). */\n instructions?: string\n /** A style guide for the planner (up to 4 000 characters). */\n styleGuide?: string\n}\n\n/** The shortest and longest clip a Clip Pack asks the plan for, in seconds. */\nexport const CLIP_PACK_MIN_TARGET_SEC = 5\nexport const CLIP_PACK_MAX_TARGET_SEC = 180\n\n/**\n * A Clip Pack emphasis: `\"none\"`, or one or more of `\"scale\"`, `\"border\"` and\n * `\"dim\"` joined with `\"+\"` (each once — the route refuses a repeat).\n */\nexport type ClipPackEmphasis = SpeakerEmphasisStyle | `${Exclude<SpeakerEmphasisStyle, \"none\">}+${string}`\n\n/**\n * Input for {@link EditResource.clipPack} and\n * {@link EditResource.estimateClipPack}: the recording (or the recordings of\n * one conversation, see {@link UseCaseRecordingsInput} — each clip then cuts\n * to whoever is talking) in, a pack of short clips out. An audio recording\n * makes audio clips (no framing, no captions). Only the recording is\n * required; every other field left out takes the node's default.\n */\nexport interface ClipPackInput extends UseCaseRecordingsInput {\n /** How many clips to find, 1–50 (default 8). The plan may find fewer. */\n count?: number\n /** Each clip's target length in seconds, {@link CLIP_PACK_MIN_TARGET_SEC}–{@link CLIP_PACK_MAX_TARGET_SEC} (default 60). */\n targetDurationSec?: number\n /**\n * Frame each video clip with Speaker View on automatic boxes over the\n * episode's speakers: the shape, and optionally the layout (default\n * `\"stacked\"`; one the shape does not draw is snapped), the switch at each\n * speaker change (default `\"pan\"`) and the emphasis (default `\"scale\"`;\n * `\"none\"`, or `\"scale\"`, `\"border\"` and `\"dim\"` joined with `\"+\"`). Left\n * out: 9:16, stacked, pan, scale. `null`: no framing (the recording's shape).\n */\n framing?: {\n aspect: EdlTargetAspect\n layout?: \"auto\" | \"single\" | \"side-by-side\" | \"stacked\" | \"grid\" | \"pip\"\n switchType?: SpeakerSwitchId\n emphasisStyle?: ClipPackEmphasis\n } | null\n /**\n * With a framing, how it follows the speakers: `\"static\"` splits the frame\n * evenly by speaker (what a request that leaves this out gets); `\"tracking\"`\n * runs Speaker Frames once over the whole pack to follow faces (charged per\n * started block of 1,000 sampled frames; refused 403 where Speaker Frames is\n * not available to the caller); `\"auto\"` follows faces while Speaker Frames\n * is available to the caller, else the even split. Ignored without a framing.\n */\n framingSource?: \"auto\" | \"static\" | \"tracking\"\n /** Caption each video clip (on by default). */\n captions?: boolean\n /** The captions' look (default `\"karaoke\"`). */\n captionStyle?: CaptionStyle\n /**\n * Stop at quick previews of the clips for a person to review; render the\n * kept clips at full quality with {@link EditResource.continueUseCase}\n * (`mode: \"render-final\"`, an `editedEdl` that keeps or drops clips).\n */\n reviewBeforeRender?: boolean\n /** The planner's tier (default `\"standard\"`). */\n planTier?: EditPlanTier\n /** Instructions for picking the clips (up to 4 000 characters). */\n instructions?: string\n /** A style guide for the planner (up to 4 000 characters). */\n styleGuide?: string\n}\n\n/**\n * Input for {@link EditResource.speakerLayout} and\n * {@link EditResource.estimateSpeakerLayout}: one video recording (or the\n * recordings of one conversation, see {@link UseCaseRecordingsInput}) in, the\n * whole recording framed by speaker out — the same length, nothing cut. Only\n * the recording is required; every other field left out takes the node's\n * default.\n */\nexport interface SpeakerLayoutInput extends UseCaseRecordingsInput {\n /** The recording — a video URL, up to 3 hours (an audio-only recording is refused). Or `mediaUrls`. */\n mediaUrl?: string\n /** The video's shape (default `\"9:16\"`). With one recording, a video already as narrow as the shape (or narrower) is refused `400 media_shape`. */\n targetAspect?: EdlTargetAspect\n /** How the speakers are laid out (default `\"stacked\"`; one the shape does not draw is snapped to one it does). */\n layout?: \"auto\" | \"single\" | \"side-by-side\" | \"stacked\" | \"grid\" | \"pip\"\n /** The switch at each speaker change (default `\"cut\"`). */\n switchType?: SpeakerSwitchId\n /** How the speaker talking is emphasised (default `\"scale\"`): `\"none\"`, or `\"scale\"`, `\"border\"` and `\"dim\"` joined with `\"+\"`. */\n emphasisStyle?: ClipPackEmphasis\n /**\n * How the speakers are followed: `\"auto\"` (the default) follows faces with\n * Speaker Frames while it is available to the caller, else splits the frame\n * evenly; `\"static\"` splits the frame evenly by speaker; `\"tracking\"`\n * follows faces (charged per started block of 1,000 sampled frames;\n * refused 403 where Speaker Frames is not available to the caller). Faces\n * aren't named after a speaker yet, so a camera shared by several speakers\n * keeps the even split.\n */\n framingSource?: \"auto\" | \"static\" | \"tracking\"\n /**\n * Stop at a quick preview for a person to review; render it at full\n * quality with {@link EditResource.continueUseCase}\n * (`mode: \"render-final\"`, no `editedEdl`: the layout has no cut to change).\n */\n reviewBeforeRender?: boolean\n}\n\n/**\n * The body's recordings (`mediaUrls`, then `masterAudioUrl` and `cameras` when\n * given), and the rest of the input as it came: the one mapper every use-case\n * body goes through. Throws before any request when the input names both\n * `mediaUrl` and `mediaUrls`, or neither.\n */\nfunction withRecordings<T extends UseCaseRecordingsInput>(input: T): Record<string, unknown> {\n const { mediaUrl, mediaUrls, masterAudioUrl, cameras, ...rest } = input\n if (mediaUrl !== undefined && mediaUrls !== undefined) throw new TypeError(\"pass mediaUrl or mediaUrls, not both\")\n const recordings = mediaUrls ?? (mediaUrl !== undefined ? [mediaUrl] : undefined)\n if (!recordings) throw new TypeError(\"pass mediaUrl or mediaUrls\")\n return {\n mediaUrls: [...recordings],\n ...(masterAudioUrl !== undefined ? { masterAudioUrl } : {}),\n ...(cameras !== undefined ? { cameras } : {}),\n ...Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== undefined)),\n }\n}\n\n/**\n * Sends `send` the body `build` makes, or rejects (never throws) with the\n * error of an input the body cannot take — before any request, as every input\n * check of the `edit` resource rejects.\n */\nexport function withUseCaseBody<T>(build: () => Record<string, unknown>, send: (body: Record<string, unknown>) => Promise<T>): Promise<T> {\n let body: Record<string, unknown>\n try {\n body = build()\n } catch (err) {\n return Promise.reject(err)\n }\n return send(body)\n}\n\nexport function tightenBody(input: TightenEpisodeInput): Record<string, unknown> {\n return withRecordings(input)\n}\n\nexport function chaptersBody(input: EpisodeChaptersInput): Record<string, unknown> {\n return withRecordings({\n mediaUrl: input.mediaUrl,\n ...(input.planTier !== undefined ? { planTier: input.planTier } : {}),\n ...(input.instructions !== undefined ? { instructions: input.instructions } : {}),\n ...(input.styleGuide !== undefined ? { styleGuide: input.styleGuide } : {}),\n })\n}\n\nexport function trailerBody(input: EpisodeTrailerInput): Record<string, unknown> {\n return withRecordings({\n mediaUrl: input.mediaUrl,\n mediaUrls: input.mediaUrls,\n masterAudioUrl: input.masterAudioUrl,\n cameras: input.cameras,\n ...(input.introCardUrl !== undefined ? { introCardUrl: input.introCardUrl } : {}),\n // An empty list is sent as is: the route refuses it (at least one format).\n ...(input.formats !== undefined ? { formats: input.formats } : {}),\n ...(input.reviewBeforeRender !== undefined ? { reviewBeforeRender: input.reviewBeforeRender } : {}),\n ...(input.planTier !== undefined ? { planTier: input.planTier } : {}),\n ...(input.instructions !== undefined ? { instructions: input.instructions } : {}),\n ...(input.styleGuide !== undefined ? { styleGuide: input.styleGuide } : {}),\n })\n}\n\n/** Clip Pack's body: what the caller set, `framing: null` included (no framing). */\nexport function clipPackBody(input: ClipPackInput): Record<string, unknown> {\n return withRecordings(input)\n}\n\n/** Speaker Layout's body: the recordings, then what the caller set. */\nexport function speakerLayoutBody(input: SpeakerLayoutInput): Record<string, unknown> {\n return withRecordings(input)\n}\n","import type { NodaroClientCore } from \"../core.js\"\nimport { ForbiddenError, NodaroError } from \"../errors.js\"\nimport {\n remapTranscriptThroughEdl,\n unwrapEditPlanOutput,\n resolveEditPlanSources,\n describeAudioSyncOffsetIssue,\n transcriptSpeakerLabels,\n cameraSwitchSpeakerMap, speakerMapResultOf, type SpeakerMapResult,\n type Edl,\n type Transcript,\n type EditPlanMode,\n type EditPlanTier,\n type EdlClipSet,\n type ChapterSet,\n type EdlTargetAspect\n} from \"@nodaro/shared/edl\"\nimport { USE_CASE_NODE_TYPES, isUseCaseNodeType, type UseCaseNodeType } from \"@nodaro/shared/nodes\"\nimport type { SpeakerMapInput } from \"./speaker-map-inputs.js\"\nimport {\n chaptersBody,\n clipPackBody,\n speakerLayoutBody,\n tightenBody,\n trailerBody,\n withUseCaseBody,\n type ClipPackInput,\n type EpisodeChaptersInput,\n type EpisodeTrailerInput,\n type SpeakerLayoutInput,\n type TightenEpisodeInput,\n} from \"./edit-use-case-inputs.js\"\n\nexport { CLIP_PACK_MAX_TARGET_SEC, CLIP_PACK_MIN_TARGET_SEC, TRAILER_FORMATS } from \"./edit-use-case-inputs.js\"\nexport type { ClipPackEmphasis, ClipPackInput, EpisodeChaptersInput, EpisodeTrailerInput, SpeakerLayoutInput, TightenEpisodeInput, TrailerFormat, UseCaseRecordingsInput } from \"./edit-use-case-inputs.js\"\n\n// Re-export the canonical EDL / transcript vocabulary from `@nodaro/shared`\n// (single source of truth — the shapes live at `packages/shared/src/edl.ts`).\n// `@nodaro/shared` is already a hard dep of this package, so there is no\n// bundle-size cost to importing from it, and an integration builds requests and\n// reads results with one dependency. `EdlClipSet` / `ChapterSet` are the raw\n// `output_data` shapes of the `clips` / `chapters` modes, and\n// `unwrapEditPlanOutput` is the sanctioned way to normalize any edit-plan job's\n// `output_data` (it also strips the relay's `viaNodaroCloud` marker).\nexport type { Edl, Transcript, EditPlanMode, EditPlanTier, EdlClipSet, ChapterSet }\nexport { unwrapEditPlanOutput }\n\n/**\n * A silence-detect job's `output_data.json` — the value to pass as\n * {@link EditPlanInput.silence}. It is NOT what {@link EditResource.silenceDetect}\n * returns (that is `{ jobId }`); fetch the finished job and read its\n * `output_data.json`. The planner reads the `ranges` array — an input without\n * one is silently ignored, so pass this whole object.\n */\nexport interface SilenceRanges {\n /** Wire version of the payload. */\n version?: number\n /** Silence spans on the source clock, in ms. */\n ranges: ReadonlyArray<{ startMs: number; endMs: number }>\n /** Total source duration, in ms. */\n durationMs?: number\n}\n\n/** Every editorial route answers with the queued job to poll. */\nexport interface EditJobResult {\n /** The queued job id — follow it via `client.jobs.getStatus(jobId)`. */\n jobId: string\n}\n\nexport interface SilenceDetectInput {\n /**\n * An audio OR video source. A single ffmpeg `silencedetect` pass runs over the\n * source's audio proxy either way — no provider key required.\n */\n audioUrl: string\n /** dBFS threshold, always ≤ 0. Default -35 (a good podcast default). */\n thresholdDb?: number\n /** Minimum silence length to report, in ms. Default 700. */\n minSilenceMs?: number\n /** Padding kept around speech, in ms — shrinks each reported range inward. Default 120. */\n padMs?: number\n /**\n * Optionally associate this run with a workflow execution (display only — it\n * ties the standalone job back to a workflow for the execution history).\n */\n workflowId?: string\n}\n\n/** One recording for {@link EditResource.audioSync}. */\nexport interface AudioSyncSource {\n /**\n * Your id for this recording (1–200 chars, unique within the request) —\n * echoed back as its offset's `sourceId`. Use the id your EDL gives the same\n * recording (`EdlSource.id`) and the result folds straight into it.\n */\n id: string\n /** An audio OR video URL — the audio track is read either way. */\n url: string\n}\n\nexport interface AudioSyncInput {\n /** The 2–6 recordings of one conversation to line up (camera files and/or a master mic). */\n sources: AudioSyncSource[]\n /**\n * The id of the source every offset is measured against (its own offset is\n * 0). Must be one of `sources`' ids; default: the first source.\n */\n reference?: string\n /** Optionally associate this run with a workflow execution (display only). */\n workflowId?: string\n}\n\n/** One recording's measured clock offset in an {@link AudioSyncResult}. */\nexport interface AudioSyncOffset {\n /** The recording's `id` from the request. */\n sourceId: string\n /**\n * Where this recording sits on the reference's clock, in integer ms:\n * `referenceMs = sourceMs + offsetMs` (the EDL's D19 sign — the value\n * `EdlSource.offsetMs` takes when the reference is the master).\n */\n offsetMs: number\n /** 0–1. Below 0.5 a `notes` line asks for a check by ear. */\n confidence: number\n /**\n * Measured clock drift against the reference, in ms gained per hour; `null`\n * when the shared stretch was too short to measure it. Drift is reported,\n * never corrected — the offset is taken at the middle of the overlap.\n */\n driftMsPerHour: number | null\n}\n\n/**\n * An audio-sync job's `output_data.json`. It is NOT what\n * {@link EditResource.audioSync} returns (that is `{ jobId }`); fetch the\n * finished job and read its `output_data.json`.\n */\nexport interface AudioSyncResult {\n /** Wire version of the payload. */\n version: number\n /** The source every offset is measured against (its own offset is 0). */\n reference: string\n offsets: AudioSyncOffset[]\n /** Human-readable warnings: low confidence, measured drift, no shared sound. */\n notes: string[]\n}\n\nexport interface ApplyEdlInput {\n /** The edit decision list to render. Media resolves from each `edl.sources[i].url`. */\n edl: Edl\n /**\n * Optional media-URL overrides for `edl.sources[i].url`, POSITIONAL in this\n * array's order. Each URL is SSRF-guarded server-side like any media the\n * platform fetches.\n */\n sources?: string[]\n /**\n * Optional transcript to remap through the cut. The rendered job returns the\n * remapped transcript on its `json` output. A word-level multi-hour transcript\n * is several MB — if you only need the re-timed transcript (not a render), use\n * {@link EditResource.remapTranscript} locally instead of sending it here.\n */\n transcript?: Transcript\n /** Render a full video (default) or an audio-only cut. */\n output?: \"video\" | \"audio\"\n /** Fast proxy draft or the final render. Default \"final\". */\n quality?: \"proxy\" | \"final\"\n /**\n * Default crossfade (ms) on boundaries with no explicit transition; 0 = hard\n * cuts. Per-boundary clamped to the ffmpeg-xfade limit server-side. Default 0.\n */\n crossfadeMs?: number\n /**\n * The plan clip this render cuts — `edlSpanKey(clip)` from `@nodaro/shared`\n * (`\"<first inMs>-<last outMs>\"`). The job's result carries it back as\n * `output_data.clipKey`, so renders of a clip set can be matched to their\n * clips. Omit for a render of no plan clip.\n */\n clipKey?: string\n /**\n * The plan value this render cuts — `renderReadBasis(value)` from\n * `@nodaro/shared` (16 lowercase hex digits), where `value` is the Edit\n * Plan's Tighten EDL with any review applied, or the clip this render cuts.\n * The job's result carries it back as `output_data.planBasis`, so a take can\n * be told apart from one cut from an earlier plan. Send it only when the EDL\n * you send IS that value (not one an intermediate step re-cut from an older\n * plan); omit it otherwise. The result's `output_data.renderBasis` (the\n * render's own settings and sources) is stamped by the server.\n */\n planBasis?: string\n /** Optionally associate this run with a workflow execution (display only). */\n workflowId?: string\n}\n\n/**\n * One media source for an edit-plan run. Mirrors the shape the workflow engine\n * sends: a stable `id` (adopted verbatim as the plan's `EdlSource` id), the\n * media `url`, and the `kind`, plus optional per-source annotations the planner\n * honors.\n */\nexport interface EditPlanSource {\n /** Stable id for this source — becomes the `EdlSource` id in the returned plan. */\n id: string\n /** The source's media URL (SSRF-guarded server-side). */\n url: string\n /** This source's medium. */\n kind: \"video\" | \"audio\"\n /** Role annotation. `\"master-audio\"` marks the clock the plan is reserved on. */\n role?: \"master-audio\" | \"camera\" | \"wide\" | \"screen\"\n /** Speaker labels present in this source. */\n speakers?: string[]\n /**\n * This source's origin on the master clock (masterMs = sourceMs + offsetMs).\n * Set by hand, it wins over a measured offset from `offsets`.\n */\n offsetMs?: number\n}\n\nexport interface EditPlanInput {\n /**\n * Planning mode. The finished job's `output_data` carries the plan — read it\n * with {@link unwrapEditPlanOutput}, which also strips the relay's\n * `viaNodaroCloud` marker:\n * - `\"tighten\"` → an `Edl`.\n * - `\"clips\"` → an {@link EdlClipSet} (`{ version, clips: Edl[] }`);\n * `unwrapEditPlanOutput` returns the bare `Edl[]`.\n * - `\"chapters\"` → a {@link ChapterSet} (`{ version, chapters: [...] }`).\n * - `\"trailer\"` → an `Edl` (one short teaser cut from the strongest moments).\n */\n mode: EditPlanMode\n /** Reasoning tier — affects plan quality AND the credit bucket. */\n planTier: EditPlanTier\n /** The timed transcript driving the plan. */\n transcript: Transcript\n /**\n * Optional detected silence ranges. Pass the silence-detect JOB'S RESULT —\n * `getStatus(jobId).data.output_data.json` (a {@link SilenceRanges} object),\n * NOT the `{ jobId }` that {@link EditResource.silenceDetect} returns. The\n * planner reads the `ranges` array; an input without one is silently ignored.\n */\n silence?: SilenceRanges\n /** The recording's media sources (1–6). */\n sources: EditPlanSource[]\n /**\n * Multicam: an {@link AudioSyncResult} (the audio-sync job's\n * `output_data.json`) measured over these same source ids. Each source's\n * measured offset is written onto its `offsetMs` BEFORE the request, on the\n * master's clock (the `master-audio` source, else the first) — a source's\n * own `offsetMs` wins. `editPlan` rejects with a {@link NodaroError}\n * (`code: \"edit_plan_sources\"`), before any request or charge, when a\n * source was not measured or matched weakly (confidence < 0.5 — set its\n * `offsetMs` by hand), or the master was not measured.\n */\n offsets?: AudioSyncResult | string\n /**\n * The id of the source the transcript was made from, when you know it. The\n * plan follows the master's clock, so a transcript made from a source that\n * is off it is rejected the same way.\n */\n transcriptSourceId?: string\n /** Free-text editing steer. */\n instructions?: string\n /** Style-guide text applied to the plan. */\n styleGuide?: string\n /** `\"clips\"` mode only: how many clips to cut. */\n count?: number\n /** `\"clips\"` mode only: target duration per clip, in seconds. */\n targetDurationSec?: number\n /** Target aspect for cut clips (`\"clips\"`) or the teaser (`\"trailer\"`). */\n targetAspect?: \"16:9\" | \"9:16\" | \"1:1\" | \"4:5\"\n /** Target platform hint (e.g. a social platform name). */\n platform?: string\n /** Optionally associate this run with a workflow execution (display only). */\n workflowId?: string\n}\n\n/** Input for {@link EditResource.cameraSwitch} (podcast B5). */\nexport interface CameraSwitchInput {\n /** One EDL — an `editPlan` result (`unwrapEditPlanOutput`), not a clip set. */\n edl: Edl\n /** The word transcript WITH speaker labels (transcribe with speaker detection). */\n transcript: Transcript\n /** Speaker label → the EDL source id of that speaker's camera. A speaker\n * left out shows the source whose `speakers` list names them, else the\n * `wide` camera, else any camera that filmed them. */\n speakerMap?: Record<string, string>\n /** A {@link EditResource.speakerMap} job's `output_data.json`: a label in `speakerMap`\n * wins, then this result, then the n-th speaker heard gets the n-th camera. */\n speakerMapResult?: SpeakerMapResult | string\n /** Speaker label → display name (on the segments and the returned transcript). */\n speakerNames?: Record<string, string>\n /** The shortest shot in ms (default 2 500): a shorter turn holds the shot. */\n minShotMs?: number\n /** Cut this long before the new speaker starts, ms (default 200). */\n leadMs?: number\n /** With a `wide` source: break to it after this long on one camera, ms (default 20 000). */\n maxShotMs?: number\n /** With a `wide` source: every N-th cut goes to the wide (default 0 = off). */\n wideEvery?: number\n /** Overlapping speech → a layout hint (default false; Apply EDL renders cut-only edits). */\n layoutHints?: boolean\n /** Optionally associate this run with a workflow execution (display only). */\n workflowId?: string\n}\n\n/**\n * The podcast use-case node types, each one a route (`POST /v1/<type>`, and\n * `POST /v1/<type>/continue` to continue one of its runs).\n */\nexport { USE_CASE_NODE_TYPES, type UseCaseNodeType }\n\n/** The route that continues a run of `type`. */\nconst continuePathOf = (type: UseCaseNodeType): `/v1/${UseCaseNodeType}/continue` => `/v1/${type}/continue`\n\n/** What a use-case run (`tightenEpisode`, `episodeChapters`, `episodeTrailer`, `clipPack`, `speakerLayout`) and `continueUseCase` answer: the run's job to poll. */\nexport interface UseCaseJobResult extends EditJobResult {\n /** The run's execution (absent on a deduplicated retry). */\n executionId?: string\n /** `true` when an `Idempotency-Key` retry found the run already started: `jobId` is that run. */\n deduped?: boolean\n}\n\n/** What `tightenEpisode` answers (a {@link UseCaseJobResult}). */\nexport type TightenEpisodeJobResult = UseCaseJobResult\n\n/**\n * What a use-case run is estimated at (`data`). Every credit amount is 0\n * on an edition without credits; `durationSec` is `null` when the recording's\n * length could not be read.\n */\nexport interface UseCaseEstimate {\n /** The whole run: its steps plus the fee. */\n totalCredits: number\n /** The steps at their own rates. */\n stepsCredits: number\n /** The per-run fee. */\n feeCredits: number\n /** A step is a metered LLM call's ESTIMATE (charged its actual usage), so the\n * total is one: show it `≈N`. Absent for a fixed figure. */\n estimate?: true\n /** The recording's length, in seconds. */\n durationSec: number | null\n}\n\n/** What a Tighten Episode run is estimated at (a {@link UseCaseEstimate}). */\nexport type TightenEpisodeEstimate = UseCaseEstimate\n\n/** Input for {@link EditResource.continueUseCase}. */\nexport interface UseCaseContinueInput {\n /** The run to continue: the job `tightenEpisode`, `episodeChapters`, `episodeTrailer`, `clipPack` or `speakerLayout` (or an earlier continuation) answered. */\n jobId: string\n /**\n * `\"from-failed\"` re-runs what did not finish of a failed run;\n * `\"render-final\"` renders the full-quality result after a review-on preview\n * (a Chapters run has no preview: it is refused `render_final_not_preview`;\n * a Clip Pack edit that drops every clip is refused `use_case_nothing_kept`).\n */\n mode: \"from-failed\" | \"render-final\"\n /** Render final only: the reviewer's edit of the plan (an EDL). A Speaker Layout run has no plan: an edit is refused `400 use_case_no_plan`. */\n editedEdl?: unknown\n /**\n * The run's node type: its continuation goes to `POST /v1/<nodeType>/continue`.\n * Optional: when absent the job is read first (`GET /v1/jobs/:id`, which\n * needs the `jobs:read` scope on an app token) for the type the run records.\n * Required when that read cannot answer: a key without `jobs:read`, or a job\n * that records no use-case type (the call rejects with `use_case_type_unknown`).\n */\n nodeType?: UseCaseNodeType\n}\n\n/** Per-call options for the use-case requests (Tighten Episode, Chapters, Trailer, Clip Pack, Speaker Layout, and their continuations). */\nexport interface UseCaseRequestOptions {\n /**\n * Sent as the `Idempotency-Key` header. Reuse the same value when retrying\n * the same request and the server answers the run it already started\n * (`deduped: true`) instead of starting a second one.\n */\n idempotencyKey?: string\n}\n\nfunction idempotencyHeaders(options?: UseCaseRequestOptions): { headers?: Record<string, string> } {\n return options?.idempotencyKey ? { headers: { \"Idempotency-Key\": options.idempotencyKey } } : {}\n}\n\n/**\n * Editorial primitives for podcast / long-form video editing, single-camera\n * and multicam: measure the cameras' offsets ({@link audioSync}), plan the cut\n * ({@link editPlan}), put each cut on the speaker's camera\n * ({@link cameraSwitch}), render a Preview to review (`applyEdl` with\n * `quality: \"proxy\"`), then the final (`quality: \"final\"`).\n *\n * - {@link silenceDetect}, {@link audioSync} and {@link applyEdl} are core nodes\n * available on every edition.\n * - {@link editPlan} and {@link cameraSwitch} run on nodaro.ai; a self-hosted\n * install relays them once it is connected (a 503\n * `code: \"nodaro_connection_required\"` otherwise).\n * - {@link tightenEpisode} runs the whole chain (transcribe, plan, render, and\n * optionally captions, music and framing) in one call. It is core, but a\n * self-hosted install needs a nodaro.ai connection for the steps that run\n * there (a 503 `code: \"nodaro_connection_required\"` otherwise).\n * {@link continueUseCase} continues one of its runs.\n * - {@link episodeChapters} lists a recording's chapters (transcribe, then the\n * plan in chapters mode) in one call; nothing renders. Like\n * {@link tightenEpisode}, a self-hosted install needs a nodaro.ai connection.\n * - {@link episodeTrailer} cuts a 20–40 s trailer of a recording and delivers\n * it in each chosen format in one call; the same connection applies.\n * - {@link clipPack} cuts a recording into a pack of short clips (framed and\n * captioned by default) in one call; the same connection applies.\n * - {@link speakerLayout} frames a whole recording by speaker (the same\n * length, nothing cut) in one call; the same connection applies.\n * - {@link remapTranscript} is a PURE local transform (no request) — the same\n * remap `applyEdl` performs on its `transcript`, exposed for callers that hold\n * an EDL and a transcript and only want the re-timed transcript.\n *\n * Every request method except the estimates ({@link estimateTightenEpisode},\n * {@link estimateEpisodeChapters}, {@link estimateEpisodeTrailer},\n * {@link estimateClipPack}, {@link estimateSpeakerLayout}: a quote, `{ data }`) returns\n * {@link EditJobResult} (the use-case runs a {@link UseCaseJobResult}); poll the\n * `jobId` with `client.jobs.getStatus(jobId)`.\n */\nexport class EditResource {\n constructor(private client: NodaroClientCore) {}\n\n /**\n * Detect silence ranges in an audio/video source (`POST /v1/silence-detect`).\n * Keyless — one ffmpeg pass over the source's audio proxy.\n */\n silenceDetect(input: SilenceDetectInput): Promise<EditJobResult> {\n return this.client.request(\"POST\", \"/v1/silence-detect\", {\n body: {\n audioUrl: input.audioUrl,\n ...(input.thresholdDb !== undefined ? { thresholdDb: input.thresholdDb } : {}),\n ...(input.minSilenceMs !== undefined ? { minSilenceMs: input.minSilenceMs } : {}),\n ...(input.padMs !== undefined ? { padMs: input.padMs } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Measure how far apart the clocks of 2–6 recordings of one conversation are\n * (`POST /v1/audio-sync`), by cross-correlating their audio — keyless, one\n * ffmpeg decode per source. The finished job's `output_data.json` is an\n * {@link AudioSyncResult}. A malformed request (fewer than 2 or more than 6\n * sources, a repeated id, a `reference` that is not one of the ids) throws a\n * typed `NodaroError` (400, `code: \"validation_error\"`) before any credits\n * are reserved.\n */\n audioSync(input: AudioSyncInput): Promise<EditJobResult> {\n return this.client.request(\"POST\", \"/v1/audio-sync\", {\n body: {\n sources: input.sources.map((s) => ({ id: s.id, url: s.url })),\n ...(input.reference !== undefined ? { reference: input.reference } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Which camera each speaker is on, from the cameras' own microphones (`POST\n * /v1/speaker-map`), keyless. `output_data.json` is a `SpeakerMapResult`: pass\n * it to {@link cameraSwitch} as `speakerMapResult`. A malformed request throws\n * a `NodaroError` (400 `validation_error`; 422 `no_speakers` for a transcript\n * with no speaker labels) before any credits are reserved.\n */\n speakerMap(input: SpeakerMapInput): Promise<EditJobResult> {\n return this.client.request(\"POST\", \"/v1/speaker-map\", {\n body: {\n sources: input.sources.map((s) => ({ id: s.id, url: s.url, ...(s.kind ? { kind: s.kind } : {}) })),\n transcript: input.transcript,\n ...(input.offsets !== undefined ? { offsets: input.offsets } : {}),\n ...(input.masterSourceId !== undefined ? { masterSourceId: input.masterSourceId } : {}),\n ...(input.wideSourceId !== undefined ? { wideSourceId: input.wideSourceId } : {}),\n ...(input.marginDb !== undefined ? { marginDb: input.marginDb } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Render an edit decision list into a video or audio cut (`POST /v1/apply-edl`).\n * The EDL is validated at ingress — an unresolvable source or a picture-less\n * segment on a video edit throws a typed `NodaroError` (400, `code:\n * \"invalid_edl\"`) before any credits are reserved.\n */\n applyEdl(input: ApplyEdlInput): Promise<EditJobResult> {\n return this.client.request(\"POST\", \"/v1/apply-edl\", {\n body: {\n edl: input.edl,\n ...(input.sources !== undefined ? { sources: input.sources } : {}),\n ...(input.transcript !== undefined ? { transcript: input.transcript } : {}),\n ...(input.output !== undefined ? { output: input.output } : {}),\n ...(input.quality !== undefined ? { quality: input.quality } : {}),\n ...(input.crossfadeMs !== undefined ? { crossfadeMs: input.crossfadeMs } : {}),\n ...(input.clipKey !== undefined ? { clipKey: input.clipKey } : {}),\n ...(input.planBasis !== undefined ? { planBasis: input.planBasis } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Tighten Episode (`POST /v1/tighten-episode`): tighten a podcast recording\n * in one call — transcribe, cut the silences and filler, render, with\n * optional captions, music and framing. Answers the run's job to poll; its\n * `output_data` holds the episode. The run is priced as its steps at their own\n * rates plus the listed per-run fee. Quote it first with\n * {@link estimateTightenEpisode}.\n */\n tightenEpisode(input: TightenEpisodeInput, options?: UseCaseRequestOptions): Promise<TightenEpisodeJobResult> {\n return withUseCaseBody(() => tightenBody(input), (body) => this.client.request(\"POST\", \"/v1/tighten-episode\", { body, ...idempotencyHeaders(options) }))\n }\n\n /**\n * What a Tighten Episode run is estimated at (`POST /v1/tighten-episode/estimate`):\n * its steps at their own rates plus its per-run fee. Nothing is created or\n * charged. The estimate leans high: the render is priced at the recording's\n * full length, while the charge follows the minutes actually rendered.\n */\n estimateTightenEpisode(input: TightenEpisodeInput): Promise<{ data: TightenEpisodeEstimate }> {\n return withUseCaseBody(() => tightenBody(input), (body) => this.client.request(\"POST\", \"/v1/tighten-episode/estimate\", { body }))\n }\n\n /**\n * Chapters (`POST /v1/episode-chapters`): transcribe a recording and list its\n * chapters in one call. Answers the run's job to poll; the finished job's\n * `output_data.text` is the list as show notes (one `0:00 Title` line per\n * chapter; once a chapter starts at 1:00:00 or later, every line is\n * `H:MM:SS`), `output_data.json` the list itself (`{ startMs, title }[]`, the\n * same rows; the first starts at 0). A run that finds no chapters fails,\n * and its fee is refunded. Priced as its steps at their own rates plus the\n * listed per-run fee; quote it first with {@link estimateEpisodeChapters}.\n */\n episodeChapters(input: EpisodeChaptersInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n return withUseCaseBody(() => chaptersBody(input), (body) => this.client.request(\"POST\", \"/v1/episode-chapters\", { body, ...idempotencyHeaders(options) }))\n }\n\n /**\n * What a Chapters run is estimated at (`POST /v1/episode-chapters/estimate`):\n * its steps at their own rates plus its per-run fee. Nothing is created or\n * charged. A recording whose length cannot be read is estimated at the\n * 180-minute ceiling.\n */\n estimateEpisodeChapters(input: EpisodeChaptersInput): Promise<{ data: UseCaseEstimate }> {\n return withUseCaseBody(() => chaptersBody(input), (body) => this.client.request(\"POST\", \"/v1/episode-chapters/estimate\", { body }))\n }\n\n /**\n * Trailer (`POST /v1/episode-trailer`): cut a 20–40 s trailer of a recording\n * and deliver it in each chosen format (padded, never cropped), optionally\n * after an intro card, in one call. The finished job's `output_data.formats`\n * is `{ format, url }[]` in format order, the first also `output_data.videoUrl`;\n * a format that failed is named in `output_data.failedFormats` (the run keeps\n * the others). With `reviewBeforeRender` the run stops at a preview\n * (`videoUrl`, no formats yet). Priced as its steps at their own rates plus\n * the listed per-run fee; quote it first with {@link estimateEpisodeTrailer}.\n */\n episodeTrailer(input: EpisodeTrailerInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n return withUseCaseBody(() => trailerBody(input), (body) => this.client.request(\"POST\", \"/v1/episode-trailer\", { body, ...idempotencyHeaders(options) }))\n }\n\n /**\n * What a Trailer run is estimated at (`POST /v1/episode-trailer/estimate`):\n * its steps at their own rates plus its per-run fee. Nothing is created or\n * charged. The trailer is quoted at 40 seconds, the longest trailer the\n * plan cuts; a Render final is quoted on the trailer the run cut (with the\n * review's edits), plus the intro card.\n */\n estimateEpisodeTrailer(input: EpisodeTrailerInput): Promise<{ data: UseCaseEstimate }> {\n return withUseCaseBody(() => trailerBody(input), (body) => this.client.request(\"POST\", \"/v1/episode-trailer/estimate\", { body }))\n }\n\n /**\n * Clip Pack (`POST /v1/clip-pack`): cut a recording into a pack of short\n * clips in one call — the plan finds the best moments (`count`, default 8,\n * of about `targetDurationSec`, default 60), each is rendered (framed 9:16\n * with the speakers stacked by default; `framing: null` keeps the\n * recording's shape) and captioned. An audio recording makes audio clips.\n * The finished job's `output_data.clips` is `{ url, title?, hook?, clipKey,\n * thumbnailUrl?, captionsFailed? }[]` in plan order, the first also\n * `output_data.videoUrl` (`audioUrl` for audio clips). Every clip is tried:\n * one that cannot be rendered is skipped, not charged and counted on\n * `failedClips` (the pack keeps the others and its fee); a clip whose\n * captions failed comes uncaptioned (`captionsFailed`, counted on\n * `failedCaptions`). When no clip renders the run fails and its fee is\n * refunded. With `reviewBeforeRender` the run stops at previews; Render\n * final ({@link continueUseCase}) renders only the clips the edit keeps.\n * Priced as its steps at their own rates plus the listed per-run fee;\n * quote it first with {@link estimateClipPack}.\n */\n clipPack(input: ClipPackInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n return withUseCaseBody(() => clipPackBody(input), (body) => this.client.request(\"POST\", \"/v1/clip-pack\", { body, ...idempotencyHeaders(options) }))\n }\n\n /**\n * What a Clip Pack run is estimated at (`POST /v1/clip-pack/estimate`): its\n * steps at their own rates plus its per-run fee. Nothing is created or\n * charged. Each clip is quoted at twice the target length (the plan may cut\n * a clip longer than asked); the charge follows each clip's real length.\n */\n estimateClipPack(input: ClipPackInput): Promise<{ data: UseCaseEstimate }> {\n return withUseCaseBody(() => clipPackBody(input), (body) => this.client.request(\"POST\", \"/v1/clip-pack/estimate\", { body }))\n }\n\n /**\n * Speaker Layout (`POST /v1/speaker-layout`): frame a whole recording by\n * speaker in one call — the same length, nothing cut. One recording: each\n * speaker framed in turn; several recordings of one conversation: lined up,\n * each speaker shown on their own camera. The finished job's\n * `output_data.videoUrl` is the framed video, `output_data.json` the edit\n * Speaker View drew. With `reviewBeforeRender` the run stops at a preview;\n * {@link continueUseCase} (`mode: \"render-final\"`, no `editedEdl`) renders\n * it at full quality. Priced as its steps at their own rates plus the listed\n * per-run fee; quote it first with {@link estimateSpeakerLayout}.\n */\n speakerLayout(input: SpeakerLayoutInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n return withUseCaseBody(() => speakerLayoutBody(input), (body) => this.client.request(\"POST\", \"/v1/speaker-layout\", { body, ...idempotencyHeaders(options) }))\n }\n\n /**\n * What a Speaker Layout run is estimated at (`POST /v1/speaker-layout/estimate`):\n * its steps at their own rates plus its per-run fee. Nothing is created or\n * charged. A recording whose length cannot be read is estimated at the\n * 180-minute ceiling.\n */\n estimateSpeakerLayout(input: SpeakerLayoutInput): Promise<{ data: UseCaseEstimate }> {\n return withUseCaseBody(() => speakerLayoutBody(input), (body) => this.client.request(\"POST\", \"/v1/speaker-layout/estimate\", { body }))\n }\n\n /**\n * Continue a use-case run as a new job (`POST /v1/<type>/continue`, the\n * run's own type: `input.nodeType`, else the type the job records, read with\n * `GET /v1/jobs/:id`; when that read cannot tell, this rejects with a\n * `NodaroError` coded `use_case_type_unknown` — pass `nodeType` then):\n * `\"from-failed\"` keeps the steps that finished and runs the rest (the fee\n * is held again; the failed run's was refunded);\n * `\"render-final\"` renders the full-quality result after a review-on preview,\n * with the preview's settings and no fee. A refusal that names another job\n * (`already_running`, `continuation_superseded`,\n * `render_final_already_rendered`) throws a `ContinuationRefusedError`\n * carrying that `jobId`.\n */\n async continueUseCase(input: UseCaseContinueInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n if (input.nodeType !== undefined && !isUseCaseNodeType(input.nodeType)) {\n throw new NodaroError(\n `continueUseCase: nodeType \"${String(input.nodeType)}\" is not a use-case node type (${USE_CASE_NODE_TYPES.join(\", \")})`,\n \"invalid_node_type\",\n 400,\n )\n }\n const type = input.nodeType ?? (await this.runTypeOf(input.jobId))\n return this.client.request(\"POST\", continuePathOf(type), {\n body: { jobId: input.jobId, mode: input.mode, ...(input.editedEdl !== undefined ? { editedEdl: input.editedEdl } : {}) },\n ...idempotencyHeaders(options),\n })\n }\n\n /**\n * The use-case type a run records (`input_data.type`, else its `job_type`).\n * When the key lacks the `jobs:read` scope (403 naming that scope) or the job\n * names no use-case type, the type cannot be read: this rejects with a\n * `NodaroError` (`use_case_type_unknown`, 400) asking for `nodeType`, and\n * nothing is posted (decided 2026-10-18: no route is guessed for the run).\n * Any other failure of the read, any other 403 included, is thrown as is.\n */\n private async runTypeOf(jobId: string): Promise<UseCaseNodeType> {\n const unknown = () =>\n new NodaroError(\"continueUseCase: the run's node type cannot be read — pass nodeType\", \"use_case_type_unknown\", 400)\n let row: { job_type?: unknown; input_data?: unknown } | undefined\n try {\n row = (await this.client.request<{ data?: { job_type?: unknown; input_data?: unknown } }>(\"GET\", `/v1/jobs/${encodeURIComponent(jobId)}`)).data\n } catch (err) {\n if (err instanceof ForbiddenError && err.missingScope === \"jobs:read\") throw unknown()\n throw err\n }\n const input = row?.input_data\n const recorded = input && typeof input === \"object\" && !Array.isArray(input) ? (input as { type?: unknown }).type : undefined\n const type = typeof recorded === \"string\" && recorded.length > 0 ? recorded : row?.job_type\n if (!isUseCaseNodeType(type)) throw unknown()\n return type\n }\n\n /**\n * Plan a transcript-driven cut (`POST /v1/edit-plan`). Reads a timed\n * transcript (plus optional silence ranges) and the media sources, and plans\n * the edit. The finished job's `output_data` holds the plan: an `Edl`\n * (`\"tighten\"` or `\"trailer\"`), an {@link EdlClipSet} (`\"clips\"`), or a\n * {@link ChapterSet} (`\"chapters\"`) — normalize it with {@link unwrapEditPlanOutput}.\n *\n * On a self-hosted install the request relays to nodaro.ai and needs the\n * install connected (a 503 `code: \"nodaro_connection_required\"` otherwise);\n * on nodaro.ai it runs directly.\n */\n editPlan(input: EditPlanInput): Promise<EditJobResult> {\n // The same pre-dispatch check the canvas and MCP run (B4): the server never\n // sees `offsets` — they are written onto the sources here.\n const planned = resolveEditPlanSources(input.sources, {\n offsets: input.offsets,\n transcriptSourceId: input.transcriptSourceId,\n })\n if (!planned.ok) {\n const message = planned.issues.map((issue) => describeAudioSyncOffsetIssue(issue)).join(\"; \")\n return Promise.reject(new NodaroError(`editPlan: ${message}`, \"edit_plan_sources\", 400))\n }\n const transcript = planned.transcriptSourceId\n ? { ...input.transcript, sourceId: planned.transcriptSourceId }\n : input.transcript\n return this.client.request(\"POST\", \"/v1/edit-plan\", {\n body: {\n mode: input.mode,\n planTier: input.planTier,\n transcript,\n sources: planned.sources,\n ...(input.silence !== undefined ? { silence: input.silence } : {}),\n ...(input.instructions !== undefined ? { instructions: input.instructions } : {}),\n ...(input.styleGuide !== undefined ? { styleGuide: input.styleGuide } : {}),\n ...(input.count !== undefined ? { count: input.count } : {}),\n ...(input.targetDurationSec !== undefined ? { targetDurationSec: input.targetDurationSec } : {}),\n ...(input.targetAspect !== undefined ? { targetAspect: input.targetAspect } : {}),\n ...(input.platform !== undefined ? { platform: input.platform } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Camera Switch (podcast B5): choose which camera shows each cut of `edl`\n * by who is speaking — the sound never changes. Cloud edition; a flat\n * price. The finished job's `output_data.json` is the switched EDL (feed it\n * to {@link applyEdl}) and `output_data.transcript` the transcript with\n * `speakerNames` applied. A transcript with no speaker labels rejects with\n * a {@link NodaroError} (`code: \"no_speakers\"`) before any request or charge.\n */\n cameraSwitch(input: CameraSwitchInput): Promise<EditJobResult> {\n if (transcriptSpeakerLabels(input.transcript).length === 0) {\n return Promise.reject(new NodaroError(\n \"cameraSwitch: the transcript has no speaker labels — transcribe with speaker detection on\",\n \"no_speakers\",\n 422,\n ))\n }\n const result = input.speakerMapResult\n if (result !== undefined && !speakerMapResultOf(result)) return Promise.reject(new NodaroError(\"cameraSwitch: speakerMapResult is not a Speaker Map result — pass that job's output_data.json\", \"not_a_speaker_map\", 400))\n const speakerMap = result !== undefined ? cameraSwitchSpeakerMap({ speakerMap: input.speakerMap }, input.edl, input.transcript, { speakerMap: result }) : input.speakerMap\n return this.client.request(\"POST\", \"/v1/camera-switch\", {\n body: {\n edl: input.edl,\n transcript: input.transcript,\n ...(speakerMap !== undefined ? { speakerMap } : {}),\n ...(input.speakerNames !== undefined ? { speakerNames: input.speakerNames } : {}),\n ...(input.minShotMs !== undefined ? { minShotMs: input.minShotMs } : {}),\n ...(input.leadMs !== undefined ? { leadMs: input.leadMs } : {}),\n ...(input.maxShotMs !== undefined ? { maxShotMs: input.maxShotMs } : {}),\n ...(input.wideEvery !== undefined ? { wideEvery: input.wideEvery } : {}),\n ...(input.layoutHints !== undefined ? { layoutHints: input.layoutHints } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Remap a transcript through an EDL — a PURE client-side transform (NO\n * request). Returns a new transcript whose word (and segment) timings are on\n * the EDL's rendered output clock, dropping words that fall in cut regions and\n * clipping straddlers. Runs `@nodaro/shared`'s canonical\n * `remapTranscriptThroughEdl`, the same remap `applyEdl` performs server-side.\n */\n remapTranscript(edl: Edl, transcript: Transcript): Transcript {\n return remapTranscriptThroughEdl(edl, transcript)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/resources/edit-use-case-inputs.ts","../src/resources/edit.ts"],"names":["isUseCaseNodeType","NodaroError","USE_CASE_NODE_TYPES","ForbiddenError","resolveEditPlanSources","describeAudioSyncOffsetIssue","transcriptSpeakerLabels","speakerMapResultOf","cameraSwitchSpeakerMap","edl","remapTranscriptThroughEdl"],"mappings":"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","file":"chunk-OMJ45CW4.cjs","sourcesContent":["/**\n * The inputs of the podcast use-case runs on the `edit` resource (Tighten\n * Episode, Chapters, Trailer, Clip Pack, Speaker Layout) and the request body each one sends.\n * Split from `edit.ts`, which holds the methods, the type list and the\n * continue routes; every name here is re-exported from there.\n */\nimport type { EditPlanTier, EdlTargetAspect, SpeakerEmphasisStyle, SpeakerSwitchId } from \"@nodaro/shared/edl\"\nimport type { CaptionStyle } from \"@nodaro/shared/captions\"\n\n/**\n * The recordings of a use-case run: one (`mediaUrl`), or the recordings of one\n * conversation (`mediaUrls`, in order — the cameras; one may be sound only),\n * an optional Master audio and the Cameras settings. Pass `mediaUrl` or\n * `mediaUrls`, not both.\n *\n * Several recordings or a Master audio need Speaker Map, which tells who is on\n * which camera: where it is not available to you (not yet on nodaro.ai) the\n * request is refused `403 node_not_available` — send one recording.\n */\nexport interface UseCaseRecordingsInput {\n /** The recording — a video or audio URL, up to 3 hours. */\n mediaUrl?: string\n /**\n * Instead of `mediaUrl`: the recordings of one conversation, in order — up\n * to six in all with the Master audio; without one, two or more cameras keep\n * a place for the sound, so up to five. With two or more the run lines them\n * up, works out who is on which camera and cuts to whoever is talking.\n */\n mediaUrls?: string[]\n /** The mixer or recorder track: the episode's sound and the clock the recordings are lined up to. */\n masterAudioUrl?: string\n /** With several recordings or a Master audio: the Cameras settings (ignored with one recording and no Master audio). */\n cameras?: {\n /** Which recording (1-based) is the wide shot, shown when a speaker has no camera of their own; `null` for none. */\n wide?: number | null\n /** The recordings already share one clock: skip lining them up. */\n synced?: boolean\n /** Who is on which camera: speaker number (`\"1\"` = the first voice heard) → recording number, 0 = the wide shot. Overrides the automatic map. */\n speakerCameras?: Record<string, number>\n }\n}\n\n/**\n * Input for {@link EditResource.tightenEpisode} and\n * {@link EditResource.estimateTightenEpisode}: the recording (or the\n * recordings of one conversation, see {@link UseCaseRecordingsInput}) in, the\n * tightened episode out. Only the recording is required; every other field\n * left out takes the node's default.\n */\nexport interface TightenEpisodeInput extends UseCaseRecordingsInput {\n /** Optional music bed, ducked under the voice and ending with the episode. */\n musicUrl?: string\n /** `\"video\"` or `\"audio\"`; an audio recording always makes an audio episode. */\n output?: \"video\" | \"audio\"\n /**\n * Frame a video episode with Speaker View: an output aspect and a layout,\n * over automatic boxes on the speakers on screen. A layout the aspect does\n * not draw is snapped to one it does. `null` or absent: no framing.\n */\n framing?: {\n aspect: EdlTargetAspect\n layout: \"auto\" | \"single\" | \"side-by-side\" | \"stacked\" | \"grid\" | \"pip\"\n } | null\n /**\n * With a framing, how it follows the speakers: `\"static\"` (the default)\n * splits the frame evenly by speaker; `\"tracking\"` follows faces with\n * Speaker Frames (charged per started block of 1,000 sampled frames; a\n * camera shared by several speakers keeps the even split until faces are\n * named; refused 403 where Speaker Frames is not available to the caller);\n * `\"auto\"` follows faces while Speaker Frames is available to the caller,\n * else the even split. Ignored without a framing.\n */\n framingSource?: \"auto\" | \"static\" | \"tracking\"\n /** Captions on a video episode (on by default). */\n captions?: boolean\n /** The captions' look. */\n captionStyle?: CaptionStyle\n /**\n * Stop at a quick preview for a person to review; render the full-quality\n * episode afterwards with {@link EditResource.continueUseCase}\n * (`mode: \"render-final\"`).\n */\n reviewBeforeRender?: boolean\n /** The planner's tier. */\n planTier?: EditPlanTier\n /** Editing instructions for the planner (up to 4 000 characters). */\n instructions?: string\n /** A style guide for the planner (up to 4 000 characters). */\n styleGuide?: string\n /** How far the music dips under the voice, 0–100 (default 75). */\n musicDuckAmount?: number\n}\n\n/**\n * Input for {@link EditResource.episodeChapters} and\n * {@link EditResource.estimateEpisodeChapters}: one recording in, its chapters\n * out. Only `mediaUrl` is required; every other field left out takes the\n * node's default.\n */\nexport interface EpisodeChaptersInput {\n /** The recording — a video or audio URL, up to 3 hours. */\n mediaUrl: string\n /** The planner's tier (default `\"economy\"`). */\n planTier?: EditPlanTier\n /** Instructions for the chapters, e.g. \"5 to 8 chapters\" (up to 4 000 characters). */\n instructions?: string\n /** A style guide for the chapter titles (up to 4 000 characters). */\n styleGuide?: string\n}\n\n/** The formats a Trailer is delivered in, in delivery order (16:9 is a 1920×1080 landscape). */\nexport const TRAILER_FORMATS = [\"9:16\", \"1:1\", \"4:5\", \"16:9\"] as const\n/** One Trailer format (see {@link TRAILER_FORMATS}). */\nexport type TrailerFormat = (typeof TRAILER_FORMATS)[number]\n\n/**\n * Input for {@link EditResource.episodeTrailer} and\n * {@link EditResource.estimateEpisodeTrailer}: one video recording (or the\n * recordings of one conversation, see {@link UseCaseRecordingsInput}) in, a\n * 20–40 s trailer out in each chosen format. Only the recording is required.\n */\nexport interface EpisodeTrailerInput extends UseCaseRecordingsInput {\n /** The recording — a video URL, up to 3 hours (a run with no picture is refused). Or `mediaUrls`. */\n mediaUrl?: string\n /** An optional intro card: a short video (15 s at most) played before the trailer. */\n introCardUrl?: string\n /** The formats to deliver, padded, never cropped (default `[\"9:16\", \"1:1\", \"4:5\"]`; an empty list is refused). */\n formats?: TrailerFormat[]\n /** Stop at a preview of the trailer; render every format with {@link EditResource.continueUseCase} (`mode: \"render-final\"`). */\n reviewBeforeRender?: boolean\n /** The planner's tier (default `\"standard\"`). */\n planTier?: EditPlanTier\n /** Instructions for the trailer (up to 4 000 characters). */\n instructions?: string\n /** A style guide for the planner (up to 4 000 characters). */\n styleGuide?: string\n}\n\n/** The shortest and longest clip a Clip Pack asks the plan for, in seconds. */\nexport const CLIP_PACK_MIN_TARGET_SEC = 5\nexport const CLIP_PACK_MAX_TARGET_SEC = 180\n\n/**\n * A Clip Pack emphasis: `\"none\"`, or one or more of `\"scale\"`, `\"border\"` and\n * `\"dim\"` joined with `\"+\"` (each once — the route refuses a repeat).\n */\nexport type ClipPackEmphasis = SpeakerEmphasisStyle | `${Exclude<SpeakerEmphasisStyle, \"none\">}+${string}`\n\n/**\n * Input for {@link EditResource.clipPack} and\n * {@link EditResource.estimateClipPack}: the recording (or the recordings of\n * one conversation, see {@link UseCaseRecordingsInput} — each clip then cuts\n * to whoever is talking) in, a pack of short clips out. An audio recording\n * makes audio clips (no framing, no captions). Only the recording is\n * required; every other field left out takes the node's default.\n */\nexport interface ClipPackInput extends UseCaseRecordingsInput {\n /** How many clips to find, 1–50 (default 8). The plan may find fewer. */\n count?: number\n /** Each clip's target length in seconds, {@link CLIP_PACK_MIN_TARGET_SEC}–{@link CLIP_PACK_MAX_TARGET_SEC} (default 60). */\n targetDurationSec?: number\n /**\n * Frame each video clip with Speaker View on automatic boxes over the\n * episode's speakers: the shape, and optionally the layout (default\n * `\"stacked\"`; one the shape does not draw is snapped), the switch at each\n * speaker change (default `\"pan\"`) and the emphasis (default `\"scale\"`;\n * `\"none\"`, or `\"scale\"`, `\"border\"` and `\"dim\"` joined with `\"+\"`). Left\n * out: 9:16, stacked, pan, scale. `null`: no framing (the recording's shape).\n */\n framing?: {\n aspect: EdlTargetAspect\n layout?: \"auto\" | \"single\" | \"side-by-side\" | \"stacked\" | \"grid\" | \"pip\"\n switchType?: SpeakerSwitchId\n emphasisStyle?: ClipPackEmphasis\n } | null\n /**\n * With a framing, how it follows the speakers: `\"static\"` splits the frame\n * evenly by speaker (what a request that leaves this out gets); `\"tracking\"`\n * runs Speaker Frames once over the whole pack to follow faces (charged per\n * started block of 1,000 sampled frames; refused 403 where Speaker Frames is\n * not available to the caller); `\"auto\"` follows faces while Speaker Frames\n * is available to the caller, else the even split. Ignored without a framing.\n */\n framingSource?: \"auto\" | \"static\" | \"tracking\"\n /** Caption each video clip (on by default). */\n captions?: boolean\n /** The captions' look (default `\"karaoke\"`). */\n captionStyle?: CaptionStyle\n /**\n * Stop at quick previews of the clips for a person to review; render the\n * kept clips at full quality with {@link EditResource.continueUseCase}\n * (`mode: \"render-final\"`, an `editedEdl` that keeps or drops clips).\n */\n reviewBeforeRender?: boolean\n /** The planner's tier (default `\"standard\"`). */\n planTier?: EditPlanTier\n /** Instructions for picking the clips (up to 4 000 characters). */\n instructions?: string\n /** A style guide for the planner (up to 4 000 characters). */\n styleGuide?: string\n}\n\n/**\n * Input for {@link EditResource.speakerLayout} and\n * {@link EditResource.estimateSpeakerLayout}: one video recording (or the\n * recordings of one conversation, see {@link UseCaseRecordingsInput}) in, the\n * whole recording framed by speaker out — the same length, nothing cut. Only\n * the recording is required; every other field left out takes the node's\n * default.\n */\nexport interface SpeakerLayoutInput extends UseCaseRecordingsInput {\n /** The recording — a video URL, up to 3 hours (an audio-only recording is refused). Or `mediaUrls`. */\n mediaUrl?: string\n /** The video's shape (default `\"9:16\"`). With one recording, a video already as narrow as the shape (or narrower) is refused `400 media_shape`. */\n targetAspect?: EdlTargetAspect\n /** How the speakers are laid out (default `\"stacked\"`; one the shape does not draw is snapped to one it does). */\n layout?: \"auto\" | \"single\" | \"side-by-side\" | \"stacked\" | \"grid\" | \"pip\"\n /** The switch at each speaker change (default `\"cut\"`). */\n switchType?: SpeakerSwitchId\n /** How the speaker talking is emphasised (default `\"scale\"`): `\"none\"`, or `\"scale\"`, `\"border\"` and `\"dim\"` joined with `\"+\"`. */\n emphasisStyle?: ClipPackEmphasis\n /**\n * How the speakers are followed: `\"auto\"` (the default) follows faces with\n * Speaker Frames while it is available to the caller, else splits the frame\n * evenly; `\"static\"` splits the frame evenly by speaker; `\"tracking\"`\n * follows faces (charged per started block of 1,000 sampled frames;\n * refused 403 where Speaker Frames is not available to the caller). Faces\n * aren't named after a speaker yet, so a camera shared by several speakers\n * keeps the even split.\n */\n framingSource?: \"auto\" | \"static\" | \"tracking\"\n /**\n * Stop at a quick preview for a person to review; render it at full\n * quality with {@link EditResource.continueUseCase}\n * (`mode: \"render-final\"`, no `editedEdl`: the layout has no cut to change).\n */\n reviewBeforeRender?: boolean\n}\n\n/**\n * The body's recordings (`mediaUrls`, then `masterAudioUrl` and `cameras` when\n * given), and the rest of the input as it came: the one mapper every use-case\n * body goes through. Throws before any request when the input names both\n * `mediaUrl` and `mediaUrls`, or neither.\n */\nfunction withRecordings<T extends UseCaseRecordingsInput>(input: T): Record<string, unknown> {\n const { mediaUrl, mediaUrls, masterAudioUrl, cameras, ...rest } = input\n if (mediaUrl !== undefined && mediaUrls !== undefined) throw new TypeError(\"pass mediaUrl or mediaUrls, not both\")\n const recordings = mediaUrls ?? (mediaUrl !== undefined ? [mediaUrl] : undefined)\n if (!recordings) throw new TypeError(\"pass mediaUrl or mediaUrls\")\n return {\n mediaUrls: [...recordings],\n ...(masterAudioUrl !== undefined ? { masterAudioUrl } : {}),\n ...(cameras !== undefined ? { cameras } : {}),\n ...Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== undefined)),\n }\n}\n\n/**\n * Sends `send` the body `build` makes, or rejects (never throws) with the\n * error of an input the body cannot take — before any request, as every input\n * check of the `edit` resource rejects.\n */\nexport function withUseCaseBody<T>(build: () => Record<string, unknown>, send: (body: Record<string, unknown>) => Promise<T>): Promise<T> {\n let body: Record<string, unknown>\n try {\n body = build()\n } catch (err) {\n return Promise.reject(err)\n }\n return send(body)\n}\n\nexport function tightenBody(input: TightenEpisodeInput): Record<string, unknown> {\n return withRecordings(input)\n}\n\nexport function chaptersBody(input: EpisodeChaptersInput): Record<string, unknown> {\n return withRecordings({\n mediaUrl: input.mediaUrl,\n ...(input.planTier !== undefined ? { planTier: input.planTier } : {}),\n ...(input.instructions !== undefined ? { instructions: input.instructions } : {}),\n ...(input.styleGuide !== undefined ? { styleGuide: input.styleGuide } : {}),\n })\n}\n\nexport function trailerBody(input: EpisodeTrailerInput): Record<string, unknown> {\n return withRecordings({\n mediaUrl: input.mediaUrl,\n mediaUrls: input.mediaUrls,\n masterAudioUrl: input.masterAudioUrl,\n cameras: input.cameras,\n ...(input.introCardUrl !== undefined ? { introCardUrl: input.introCardUrl } : {}),\n // An empty list is sent as is: the route refuses it (at least one format).\n ...(input.formats !== undefined ? { formats: input.formats } : {}),\n ...(input.reviewBeforeRender !== undefined ? { reviewBeforeRender: input.reviewBeforeRender } : {}),\n ...(input.planTier !== undefined ? { planTier: input.planTier } : {}),\n ...(input.instructions !== undefined ? { instructions: input.instructions } : {}),\n ...(input.styleGuide !== undefined ? { styleGuide: input.styleGuide } : {}),\n })\n}\n\n/** Clip Pack's body: what the caller set, `framing: null` included (no framing). */\nexport function clipPackBody(input: ClipPackInput): Record<string, unknown> {\n return withRecordings(input)\n}\n\n/** Speaker Layout's body: the recordings, then what the caller set. */\nexport function speakerLayoutBody(input: SpeakerLayoutInput): Record<string, unknown> {\n return withRecordings(input)\n}\n","import type { NodaroClientCore } from \"../core.js\"\nimport { ForbiddenError, NodaroError } from \"../errors.js\"\nimport {\n remapTranscriptThroughEdl,\n unwrapEditPlanOutput,\n resolveEditPlanSources,\n describeAudioSyncOffsetIssue,\n transcriptSpeakerLabels,\n cameraSwitchSpeakerMap, speakerMapResultOf, type SpeakerMapResult,\n type Edl,\n type Transcript,\n type EditPlanMode,\n type EditPlanTier,\n type EdlClipSet,\n type ChapterSet,\n type EdlTargetAspect\n} from \"@nodaro/shared/edl\"\nimport { USE_CASE_NODE_TYPES, isUseCaseNodeType, type UseCaseNodeType } from \"@nodaro/shared/nodes\"\nimport type { SpeakerMapInput } from \"./speaker-map-inputs.js\"\nimport {\n chaptersBody,\n clipPackBody,\n speakerLayoutBody,\n tightenBody,\n trailerBody,\n withUseCaseBody,\n type ClipPackInput,\n type EpisodeChaptersInput,\n type EpisodeTrailerInput,\n type SpeakerLayoutInput,\n type TightenEpisodeInput,\n} from \"./edit-use-case-inputs.js\"\n\nexport { CLIP_PACK_MAX_TARGET_SEC, CLIP_PACK_MIN_TARGET_SEC, TRAILER_FORMATS } from \"./edit-use-case-inputs.js\"\nexport type { ClipPackEmphasis, ClipPackInput, EpisodeChaptersInput, EpisodeTrailerInput, SpeakerLayoutInput, TightenEpisodeInput, TrailerFormat, UseCaseRecordingsInput } from \"./edit-use-case-inputs.js\"\n\n// Re-export the canonical EDL / transcript vocabulary from `@nodaro/shared`\n// (single source of truth — the shapes live at `packages/shared/src/edl.ts`).\n// `@nodaro/shared` is already a hard dep of this package, so there is no\n// bundle-size cost to importing from it, and an integration builds requests and\n// reads results with one dependency. `EdlClipSet` / `ChapterSet` are the raw\n// `output_data` shapes of the `clips` / `chapters` modes, and\n// `unwrapEditPlanOutput` is the sanctioned way to normalize any edit-plan job's\n// `output_data` (it also strips the relay's `viaNodaroCloud` marker).\nexport type { Edl, Transcript, EditPlanMode, EditPlanTier, EdlClipSet, ChapterSet }\nexport { unwrapEditPlanOutput }\n\n/**\n * A silence-detect job's `output_data.json` — the value to pass as\n * {@link EditPlanInput.silence}. It is NOT what {@link EditResource.silenceDetect}\n * returns (that is `{ jobId }`); fetch the finished job and read its\n * `output_data.json`. The planner reads the `ranges` array — an input without\n * one is silently ignored, so pass this whole object.\n */\nexport interface SilenceRanges {\n /** Wire version of the payload. */\n version?: number\n /** Silence spans on the source clock, in ms. */\n ranges: ReadonlyArray<{ startMs: number; endMs: number }>\n /** Total source duration, in ms. */\n durationMs?: number\n}\n\n/** Every editorial route answers with the queued job to poll. */\nexport interface EditJobResult {\n /** The queued job id — follow it via `client.jobs.getStatus(jobId)`. */\n jobId: string\n}\n\nexport interface SilenceDetectInput {\n /**\n * An audio OR video source. A single ffmpeg `silencedetect` pass runs over the\n * source's audio proxy either way — no provider key required.\n */\n audioUrl: string\n /** dBFS threshold, always ≤ 0. Default -35 (a good podcast default). */\n thresholdDb?: number\n /** Minimum silence length to report, in ms. Default 700. */\n minSilenceMs?: number\n /** Padding kept around speech, in ms — shrinks each reported range inward. Default 120. */\n padMs?: number\n /**\n * Optionally associate this run with a workflow execution (display only — it\n * ties the standalone job back to a workflow for the execution history).\n */\n workflowId?: string\n}\n\n/** One recording for {@link EditResource.audioSync}. */\nexport interface AudioSyncSource {\n /**\n * Your id for this recording (1–200 chars, unique within the request) —\n * echoed back as its offset's `sourceId`. Use the id your EDL gives the same\n * recording (`EdlSource.id`) and the result folds straight into it.\n */\n id: string\n /** An audio OR video URL — the audio track is read either way. */\n url: string\n}\n\nexport interface AudioSyncInput {\n /** The 2–6 recordings of one conversation to line up (camera files and/or a master mic). */\n sources: AudioSyncSource[]\n /**\n * The id of the source every offset is measured against (its own offset is\n * 0). Must be one of `sources`' ids; default: the first source.\n */\n reference?: string\n /** Optionally associate this run with a workflow execution (display only). */\n workflowId?: string\n}\n\n/** One recording's measured clock offset in an {@link AudioSyncResult}. */\nexport interface AudioSyncOffset {\n /** The recording's `id` from the request. */\n sourceId: string\n /**\n * Where this recording sits on the reference's clock, in integer ms:\n * `referenceMs = sourceMs + offsetMs` (the EDL's D19 sign — the value\n * `EdlSource.offsetMs` takes when the reference is the master).\n */\n offsetMs: number\n /** 0–1. Below 0.5 a `notes` line asks for a check by ear. */\n confidence: number\n /**\n * Measured clock drift against the reference, in ms gained per hour; `null`\n * when the shared stretch was too short to measure it. Drift is reported,\n * never corrected — the offset is taken at the middle of the overlap.\n */\n driftMsPerHour: number | null\n}\n\n/**\n * An audio-sync job's `output_data.json`. It is NOT what\n * {@link EditResource.audioSync} returns (that is `{ jobId }`); fetch the\n * finished job and read its `output_data.json`.\n */\nexport interface AudioSyncResult {\n /** Wire version of the payload. */\n version: number\n /** The source every offset is measured against (its own offset is 0). */\n reference: string\n offsets: AudioSyncOffset[]\n /** Human-readable warnings: low confidence, measured drift, no shared sound. */\n notes: string[]\n}\n\nexport interface ApplyEdlInput {\n /** The edit decision list to render. Media resolves from each `edl.sources[i].url`. */\n edl: Edl\n /**\n * Optional media-URL overrides for `edl.sources[i].url`, POSITIONAL in this\n * array's order. Each URL is SSRF-guarded server-side like any media the\n * platform fetches.\n */\n sources?: string[]\n /**\n * Optional transcript to remap through the cut. The rendered job returns the\n * remapped transcript on its `json` output. A word-level multi-hour transcript\n * is several MB — if you only need the re-timed transcript (not a render), use\n * {@link EditResource.remapTranscript} locally instead of sending it here.\n */\n transcript?: Transcript\n /** Render a full video (default) or an audio-only cut. */\n output?: \"video\" | \"audio\"\n /** Fast proxy draft or the final render. Default \"final\". */\n quality?: \"proxy\" | \"final\"\n /**\n * Default crossfade (ms) on boundaries with no explicit transition; 0 = hard\n * cuts. Per-boundary clamped to the ffmpeg-xfade limit server-side. Default 0.\n */\n crossfadeMs?: number\n /**\n * The plan clip this render cuts — `edlSpanKey(clip)` from `@nodaro/shared`\n * (`\"<first inMs>-<last outMs>\"`). The job's result carries it back as\n * `output_data.clipKey`, so renders of a clip set can be matched to their\n * clips. Omit for a render of no plan clip.\n */\n clipKey?: string\n /**\n * The plan value this render cuts — `renderReadBasis(value)` from\n * `@nodaro/shared` (16 lowercase hex digits), where `value` is the Edit\n * Plan's Tighten EDL with any review applied, or the clip this render cuts.\n * The job's result carries it back as `output_data.planBasis`, so a take can\n * be told apart from one cut from an earlier plan. Send it only when the EDL\n * you send IS that value (not one an intermediate step re-cut from an older\n * plan); omit it otherwise. The result's `output_data.renderBasis` (the\n * render's own settings and sources) is stamped by the server.\n */\n planBasis?: string\n /** Optionally associate this run with a workflow execution (display only). */\n workflowId?: string\n}\n\n/**\n * One media source for an edit-plan run. Mirrors the shape the workflow engine\n * sends: a stable `id` (adopted verbatim as the plan's `EdlSource` id), the\n * media `url`, and the `kind`, plus optional per-source annotations the planner\n * honors.\n */\nexport interface EditPlanSource {\n /** Stable id for this source — becomes the `EdlSource` id in the returned plan. */\n id: string\n /** The source's media URL (SSRF-guarded server-side). */\n url: string\n /** This source's medium. */\n kind: \"video\" | \"audio\"\n /** Role annotation. `\"master-audio\"` marks the clock the plan is reserved on. */\n role?: \"master-audio\" | \"camera\" | \"wide\" | \"screen\"\n /** Speaker labels present in this source. */\n speakers?: string[]\n /**\n * This source's origin on the master clock (masterMs = sourceMs + offsetMs).\n * Set by hand, it wins over a measured offset from `offsets`.\n */\n offsetMs?: number\n}\n\nexport interface EditPlanInput {\n /**\n * Planning mode. The finished job's `output_data` carries the plan — read it\n * with {@link unwrapEditPlanOutput}, which also strips the relay's\n * `viaNodaroCloud` marker:\n * - `\"tighten\"` → an `Edl`.\n * - `\"clips\"` → an {@link EdlClipSet} (`{ version, clips: Edl[] }`);\n * `unwrapEditPlanOutput` returns the bare `Edl[]`.\n * - `\"chapters\"` → a {@link ChapterSet} (`{ version, chapters: [...] }`).\n * - `\"trailer\"` → an `Edl` (one short teaser cut from the strongest moments).\n */\n mode: EditPlanMode\n /** Reasoning tier — affects plan quality AND the credit bucket. */\n planTier: EditPlanTier\n /** The timed transcript driving the plan. */\n transcript: Transcript\n /**\n * Optional detected silence ranges. Pass the silence-detect JOB'S RESULT —\n * `getStatus(jobId).data.output_data.json` (a {@link SilenceRanges} object),\n * NOT the `{ jobId }` that {@link EditResource.silenceDetect} returns. The\n * planner reads the `ranges` array; an input without one is silently ignored.\n */\n silence?: SilenceRanges\n /** The recording's media sources (1–6). */\n sources: EditPlanSource[]\n /**\n * Multicam: an {@link AudioSyncResult} (the audio-sync job's\n * `output_data.json`) measured over these same source ids. Each source's\n * measured offset is written onto its `offsetMs` BEFORE the request, on the\n * master's clock (the `master-audio` source, else the first) — a source's\n * own `offsetMs` wins. `editPlan` rejects with a {@link NodaroError}\n * (`code: \"edit_plan_sources\"`), before any request or charge, when a\n * source was not measured or matched weakly (confidence < 0.5 — set its\n * `offsetMs` by hand), or the master was not measured.\n */\n offsets?: AudioSyncResult | string\n /**\n * The id of the source the transcript was made from, when you know it. The\n * plan follows the master's clock, so a transcript made from a source that\n * is off it is rejected the same way.\n */\n transcriptSourceId?: string\n /** Free-text editing steer. */\n instructions?: string\n /** Style-guide text applied to the plan. */\n styleGuide?: string\n /** `\"clips\"` mode only: how many clips to cut. */\n count?: number\n /** `\"clips\"` mode only: target duration per clip, in seconds. */\n targetDurationSec?: number\n /** Target aspect for cut clips (`\"clips\"`) or the teaser (`\"trailer\"`). */\n targetAspect?: \"16:9\" | \"9:16\" | \"1:1\" | \"4:5\"\n /** Target platform hint (e.g. a social platform name). */\n platform?: string\n /** Optionally associate this run with a workflow execution (display only). */\n workflowId?: string\n}\n\n/** Input for {@link EditResource.cameraSwitch} (podcast B5). */\nexport interface CameraSwitchInput {\n /** One EDL — an `editPlan` result (`unwrapEditPlanOutput`), not a clip set. */\n edl: Edl\n /** The word transcript WITH speaker labels (transcribe with speaker detection). */\n transcript: Transcript\n /** Speaker label → the EDL source id of that speaker's camera. A speaker\n * left out shows the source whose `speakers` list names them, else the\n * `wide` camera, else any camera that filmed them. */\n speakerMap?: Record<string, string>\n /** A {@link EditResource.speakerMap} job's `output_data.json`: a label in `speakerMap`\n * wins, then this result, then the n-th speaker heard gets the n-th camera. */\n speakerMapResult?: SpeakerMapResult | string\n /** Speaker label → display name (on the segments and the returned transcript). */\n speakerNames?: Record<string, string>\n /** The shortest shot in ms (default 2 500): a shorter turn holds the shot. */\n minShotMs?: number\n /** Cut this long before the new speaker starts, ms (default 200). */\n leadMs?: number\n /** With a `wide` source: break to it after this long on one camera, ms (default 20 000). */\n maxShotMs?: number\n /** With a `wide` source: every N-th cut goes to the wide (default 0 = off). */\n wideEvery?: number\n /** Overlapping speech → a layout hint (default false; Apply EDL renders cut-only edits). */\n layoutHints?: boolean\n /** Optionally associate this run with a workflow execution (display only). */\n workflowId?: string\n}\n\n/**\n * The podcast use-case node types, each one a route (`POST /v1/<type>`, and\n * `POST /v1/<type>/continue` to continue one of its runs).\n */\nexport { USE_CASE_NODE_TYPES, type UseCaseNodeType }\n\n/** The route that continues a run of `type`. */\nconst continuePathOf = (type: UseCaseNodeType): `/v1/${UseCaseNodeType}/continue` => `/v1/${type}/continue`\n\n/** What a use-case run (`tightenEpisode`, `episodeChapters`, `episodeTrailer`, `clipPack`, `speakerLayout`) and `continueUseCase` answer: the run's job to poll. */\nexport interface UseCaseJobResult extends EditJobResult {\n /** The run's execution (absent on a deduplicated retry). */\n executionId?: string\n /** `true` when an `Idempotency-Key` retry found the run already started: `jobId` is that run. */\n deduped?: boolean\n}\n\n/** What `tightenEpisode` answers (a {@link UseCaseJobResult}). */\nexport type TightenEpisodeJobResult = UseCaseJobResult\n\n/**\n * What a use-case run is estimated at (`data`). Every credit amount is 0\n * on an edition without credits; `durationSec` is `null` when the recording's\n * length could not be read.\n */\nexport interface UseCaseEstimate {\n /** The whole run: its steps plus the fee. */\n totalCredits: number\n /** The steps at their own rates. */\n stepsCredits: number\n /** The per-run fee. */\n feeCredits: number\n /** A step is a metered LLM call's ESTIMATE (charged its actual usage), so the\n * total is one: show it `≈N`. Absent for a fixed figure. */\n estimate?: true\n /** The recording's length, in seconds. */\n durationSec: number | null\n}\n\n/** What a Tighten Episode run is estimated at (a {@link UseCaseEstimate}). */\nexport type TightenEpisodeEstimate = UseCaseEstimate\n\n/** Input for {@link EditResource.continueUseCase}. */\nexport interface UseCaseContinueInput {\n /** The run to continue: the job `tightenEpisode`, `episodeChapters`, `episodeTrailer`, `clipPack` or `speakerLayout` (or an earlier continuation) answered. */\n jobId: string\n /**\n * `\"from-failed\"` re-runs what did not finish of a failed run;\n * `\"render-final\"` renders the full-quality result after a review-on preview\n * (a Chapters run has no preview: it is refused `render_final_not_preview`;\n * a Clip Pack edit that drops every clip is refused `use_case_nothing_kept`).\n */\n mode: \"from-failed\" | \"render-final\"\n /** Render final only: the reviewer's edit of the plan (an EDL). A Speaker Layout run has no plan: an edit is refused `400 use_case_no_plan`. */\n editedEdl?: unknown\n /**\n * The run's node type: its continuation goes to `POST /v1/<nodeType>/continue`.\n * Optional: when absent the job is read first (`GET /v1/jobs/:id`, which\n * needs the `jobs:read` scope on an app token) for the type the run records.\n * Required when that read cannot answer: a key without `jobs:read`, or a job\n * that records no use-case type (the call rejects with `use_case_type_unknown`).\n */\n nodeType?: UseCaseNodeType\n}\n\n/** Per-call options for the use-case requests (Tighten Episode, Chapters, Trailer, Clip Pack, Speaker Layout, and their continuations). */\nexport interface UseCaseRequestOptions {\n /**\n * Sent as the `Idempotency-Key` header. Reuse the same value when retrying\n * the same request and the server answers the run it already started\n * (`deduped: true`) instead of starting a second one.\n */\n idempotencyKey?: string\n}\n\nfunction idempotencyHeaders(options?: UseCaseRequestOptions): { headers?: Record<string, string> } {\n return options?.idempotencyKey ? { headers: { \"Idempotency-Key\": options.idempotencyKey } } : {}\n}\n\n/**\n * Editorial primitives for podcast / long-form video editing, single-camera\n * and multicam: measure the cameras' offsets ({@link audioSync}), plan the cut\n * ({@link editPlan}), put each cut on the speaker's camera\n * ({@link cameraSwitch}), render a Preview to review (`applyEdl` with\n * `quality: \"proxy\"`), then the final (`quality: \"final\"`).\n *\n * - {@link silenceDetect}, {@link audioSync} and {@link applyEdl} are core nodes\n * available on every edition.\n * - {@link editPlan} and {@link cameraSwitch} run on nodaro.ai; a self-hosted\n * install relays them once it is connected (a 503\n * `code: \"nodaro_connection_required\"` otherwise).\n * - {@link tightenEpisode} runs the whole chain (transcribe, plan, render, and\n * optionally captions, music and framing) in one call. It is core, but a\n * self-hosted install needs a nodaro.ai connection for the steps that run\n * there (a 503 `code: \"nodaro_connection_required\"` otherwise).\n * {@link continueUseCase} continues one of its runs.\n * - {@link episodeChapters} lists a recording's chapters (transcribe, then the\n * plan in chapters mode) in one call; nothing renders. Like\n * {@link tightenEpisode}, a self-hosted install needs a nodaro.ai connection.\n * - {@link episodeTrailer} cuts a 20–40 s trailer of a recording and delivers\n * it in each chosen format in one call; the same connection applies.\n * - {@link clipPack} cuts a recording into a pack of short clips (framed and\n * captioned by default) in one call; the same connection applies.\n * - {@link speakerLayout} frames a whole recording by speaker (the same\n * length, nothing cut) in one call; the same connection applies.\n * - {@link remapTranscript} is a PURE local transform (no request) — the same\n * remap `applyEdl` performs on its `transcript`, exposed for callers that hold\n * an EDL and a transcript and only want the re-timed transcript.\n *\n * Every request method except the estimates ({@link estimateTightenEpisode},\n * {@link estimateEpisodeChapters}, {@link estimateEpisodeTrailer},\n * {@link estimateClipPack}, {@link estimateSpeakerLayout}: a quote, `{ data }`) returns\n * {@link EditJobResult} (the use-case runs a {@link UseCaseJobResult}); poll the\n * `jobId` with `client.jobs.getStatus(jobId)`.\n */\nexport class EditResource {\n constructor(private client: NodaroClientCore) {}\n\n /**\n * Detect silence ranges in an audio/video source (`POST /v1/silence-detect`).\n * Keyless — one ffmpeg pass over the source's audio proxy.\n */\n silenceDetect(input: SilenceDetectInput): Promise<EditJobResult> {\n return this.client.request(\"POST\", \"/v1/silence-detect\", {\n body: {\n audioUrl: input.audioUrl,\n ...(input.thresholdDb !== undefined ? { thresholdDb: input.thresholdDb } : {}),\n ...(input.minSilenceMs !== undefined ? { minSilenceMs: input.minSilenceMs } : {}),\n ...(input.padMs !== undefined ? { padMs: input.padMs } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Measure how far apart the clocks of 2–6 recordings of one conversation are\n * (`POST /v1/audio-sync`), by cross-correlating their audio — keyless, one\n * ffmpeg decode per source. The finished job's `output_data.json` is an\n * {@link AudioSyncResult}. A malformed request (fewer than 2 or more than 6\n * sources, a repeated id, a `reference` that is not one of the ids) throws a\n * typed `NodaroError` (400, `code: \"validation_error\"`) before any credits\n * are reserved.\n */\n audioSync(input: AudioSyncInput): Promise<EditJobResult> {\n return this.client.request(\"POST\", \"/v1/audio-sync\", {\n body: {\n sources: input.sources.map((s) => ({ id: s.id, url: s.url })),\n ...(input.reference !== undefined ? { reference: input.reference } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Which camera each speaker is on, from the cameras' own microphones (`POST\n * /v1/speaker-map`), keyless. `output_data.json` is a `SpeakerMapResult`: pass\n * it to {@link cameraSwitch} as `speakerMapResult`. A malformed request throws\n * a `NodaroError` (400 `validation_error`; 422 `no_speakers` for a transcript\n * with no speaker labels) before any credits are reserved.\n */\n speakerMap(input: SpeakerMapInput): Promise<EditJobResult> {\n return this.client.request(\"POST\", \"/v1/speaker-map\", {\n body: {\n sources: input.sources.map((s) => ({ id: s.id, url: s.url, ...(s.kind ? { kind: s.kind } : {}) })),\n transcript: input.transcript,\n ...(input.offsets !== undefined ? { offsets: input.offsets } : {}),\n ...(input.masterSourceId !== undefined ? { masterSourceId: input.masterSourceId } : {}),\n ...(input.wideSourceId !== undefined ? { wideSourceId: input.wideSourceId } : {}),\n ...(input.marginDb !== undefined ? { marginDb: input.marginDb } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Render an edit decision list into a video or audio cut (`POST /v1/apply-edl`).\n * The EDL is validated at ingress — an unresolvable source or a picture-less\n * segment on a video edit throws a typed `NodaroError` (400, `code:\n * \"invalid_edl\"`) before any credits are reserved.\n */\n applyEdl(input: ApplyEdlInput): Promise<EditJobResult> {\n return this.client.request(\"POST\", \"/v1/apply-edl\", {\n body: {\n edl: input.edl,\n ...(input.sources !== undefined ? { sources: input.sources } : {}),\n ...(input.transcript !== undefined ? { transcript: input.transcript } : {}),\n ...(input.output !== undefined ? { output: input.output } : {}),\n ...(input.quality !== undefined ? { quality: input.quality } : {}),\n ...(input.crossfadeMs !== undefined ? { crossfadeMs: input.crossfadeMs } : {}),\n ...(input.clipKey !== undefined ? { clipKey: input.clipKey } : {}),\n ...(input.planBasis !== undefined ? { planBasis: input.planBasis } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Tighten Episode (`POST /v1/tighten-episode`): tighten a podcast recording\n * in one call — transcribe, cut the silences and filler, render, with\n * optional captions, music and framing. Answers the run's job to poll; its\n * `output_data` holds the episode. The run is priced as its steps at their own\n * rates plus the listed per-run fee. Quote it first with\n * {@link estimateTightenEpisode}.\n */\n tightenEpisode(input: TightenEpisodeInput, options?: UseCaseRequestOptions): Promise<TightenEpisodeJobResult> {\n return withUseCaseBody(() => tightenBody(input), (body) => this.client.request(\"POST\", \"/v1/tighten-episode\", { body, ...idempotencyHeaders(options) }))\n }\n\n /**\n * What a Tighten Episode run is estimated at (`POST /v1/tighten-episode/estimate`):\n * its steps at their own rates plus its per-run fee. Nothing is created or\n * charged. The estimate leans high: the render is priced at the recording's\n * full length, while the charge follows the minutes actually rendered.\n */\n estimateTightenEpisode(input: TightenEpisodeInput): Promise<{ data: TightenEpisodeEstimate }> {\n return withUseCaseBody(() => tightenBody(input), (body) => this.client.request(\"POST\", \"/v1/tighten-episode/estimate\", { body }))\n }\n\n /**\n * Chapters (`POST /v1/episode-chapters`): transcribe a recording and list its\n * chapters in one call. Answers the run's job to poll; the finished job's\n * `output_data.text` is the list as show notes (one `0:00 Title` line per\n * chapter; once a chapter starts at 1:00:00 or later, every line is\n * `H:MM:SS`), `output_data.json` the list itself (`{ startMs, title }[]`, the\n * same rows; the first starts at 0). A run that finds no chapters fails,\n * and its fee is refunded. Priced as its steps at their own rates plus the\n * listed per-run fee; quote it first with {@link estimateEpisodeChapters}.\n */\n episodeChapters(input: EpisodeChaptersInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n return withUseCaseBody(() => chaptersBody(input), (body) => this.client.request(\"POST\", \"/v1/episode-chapters\", { body, ...idempotencyHeaders(options) }))\n }\n\n /**\n * What a Chapters run is estimated at (`POST /v1/episode-chapters/estimate`):\n * its steps at their own rates plus its per-run fee. Nothing is created or\n * charged. A recording whose length cannot be read is estimated at the\n * 180-minute ceiling.\n */\n estimateEpisodeChapters(input: EpisodeChaptersInput): Promise<{ data: UseCaseEstimate }> {\n return withUseCaseBody(() => chaptersBody(input), (body) => this.client.request(\"POST\", \"/v1/episode-chapters/estimate\", { body }))\n }\n\n /**\n * Trailer (`POST /v1/episode-trailer`): cut a 20–40 s trailer of a recording\n * and deliver it in each chosen format (padded, never cropped), optionally\n * after an intro card, in one call. The finished job's `output_data.formats`\n * is `{ format, url }[]` in format order, the first also `output_data.videoUrl`;\n * a format that failed is named in `output_data.failedFormats` (the run keeps\n * the others). With `reviewBeforeRender` the run stops at a preview\n * (`videoUrl`, no formats yet). Priced as its steps at their own rates plus\n * the listed per-run fee; quote it first with {@link estimateEpisodeTrailer}.\n */\n episodeTrailer(input: EpisodeTrailerInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n return withUseCaseBody(() => trailerBody(input), (body) => this.client.request(\"POST\", \"/v1/episode-trailer\", { body, ...idempotencyHeaders(options) }))\n }\n\n /**\n * What a Trailer run is estimated at (`POST /v1/episode-trailer/estimate`):\n * its steps at their own rates plus its per-run fee. Nothing is created or\n * charged. The trailer is quoted at 40 seconds, the longest trailer the\n * plan cuts; a Render final is quoted on the trailer the run cut (with the\n * review's edits), plus the intro card.\n */\n estimateEpisodeTrailer(input: EpisodeTrailerInput): Promise<{ data: UseCaseEstimate }> {\n return withUseCaseBody(() => trailerBody(input), (body) => this.client.request(\"POST\", \"/v1/episode-trailer/estimate\", { body }))\n }\n\n /**\n * Clip Pack (`POST /v1/clip-pack`): cut a recording into a pack of short\n * clips in one call — the plan finds the best moments (`count`, default 8,\n * of about `targetDurationSec`, default 60), each is rendered (framed 9:16\n * with the speakers stacked by default; `framing: null` keeps the\n * recording's shape) and captioned. An audio recording makes audio clips.\n * The finished job's `output_data.clips` is `{ url, title?, hook?, clipKey,\n * thumbnailUrl?, captionsFailed? }[]` in plan order, the first also\n * `output_data.videoUrl` (`audioUrl` for audio clips). Every clip is tried:\n * one that cannot be rendered is skipped, not charged and counted on\n * `failedClips` (the pack keeps the others and its fee); a clip whose\n * captions failed comes uncaptioned (`captionsFailed`, counted on\n * `failedCaptions`). When no clip renders the run fails and its fee is\n * refunded. With `reviewBeforeRender` the run stops at previews; Render\n * final ({@link continueUseCase}) renders only the clips the edit keeps.\n * Priced as its steps at their own rates plus the listed per-run fee;\n * quote it first with {@link estimateClipPack}.\n */\n clipPack(input: ClipPackInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n return withUseCaseBody(() => clipPackBody(input), (body) => this.client.request(\"POST\", \"/v1/clip-pack\", { body, ...idempotencyHeaders(options) }))\n }\n\n /**\n * What a Clip Pack run is estimated at (`POST /v1/clip-pack/estimate`): its\n * steps at their own rates plus its per-run fee. Nothing is created or\n * charged. Each clip is quoted at twice the target length (the plan may cut\n * a clip longer than asked); the charge follows each clip's real length.\n */\n estimateClipPack(input: ClipPackInput): Promise<{ data: UseCaseEstimate }> {\n return withUseCaseBody(() => clipPackBody(input), (body) => this.client.request(\"POST\", \"/v1/clip-pack/estimate\", { body }))\n }\n\n /**\n * Speaker Layout (`POST /v1/speaker-layout`): frame a whole recording by\n * speaker in one call — the same length, nothing cut. One recording: each\n * speaker framed in turn; several recordings of one conversation: lined up,\n * each speaker shown on their own camera. The finished job's\n * `output_data.videoUrl` is the framed video, `output_data.json` the edit\n * Speaker View drew. With `reviewBeforeRender` the run stops at a preview;\n * {@link continueUseCase} (`mode: \"render-final\"`, no `editedEdl`) renders\n * it at full quality. Priced as its steps at their own rates plus the listed\n * per-run fee; quote it first with {@link estimateSpeakerLayout}.\n */\n speakerLayout(input: SpeakerLayoutInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n return withUseCaseBody(() => speakerLayoutBody(input), (body) => this.client.request(\"POST\", \"/v1/speaker-layout\", { body, ...idempotencyHeaders(options) }))\n }\n\n /**\n * What a Speaker Layout run is estimated at (`POST /v1/speaker-layout/estimate`):\n * its steps at their own rates plus its per-run fee. Nothing is created or\n * charged. A recording whose length cannot be read is estimated at the\n * 180-minute ceiling.\n */\n estimateSpeakerLayout(input: SpeakerLayoutInput): Promise<{ data: UseCaseEstimate }> {\n return withUseCaseBody(() => speakerLayoutBody(input), (body) => this.client.request(\"POST\", \"/v1/speaker-layout/estimate\", { body }))\n }\n\n /**\n * Continue a use-case run as a new job (`POST /v1/<type>/continue`, the\n * run's own type: `input.nodeType`, else the type the job records, read with\n * `GET /v1/jobs/:id`; when that read cannot tell, this rejects with a\n * `NodaroError` coded `use_case_type_unknown` — pass `nodeType` then):\n * `\"from-failed\"` keeps the steps that finished and runs the rest (the fee\n * is held again; the failed run's was refunded);\n * `\"render-final\"` renders the full-quality result after a review-on preview,\n * with the preview's settings and no fee. A refusal that names another job\n * (`already_running`, `continuation_superseded`,\n * `render_final_already_rendered`) throws a `ContinuationRefusedError`\n * carrying that `jobId`.\n */\n async continueUseCase(input: UseCaseContinueInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n if (input.nodeType !== undefined && !isUseCaseNodeType(input.nodeType)) {\n throw new NodaroError(\n `continueUseCase: nodeType \"${String(input.nodeType)}\" is not a use-case node type (${USE_CASE_NODE_TYPES.join(\", \")})`,\n \"invalid_node_type\",\n 400,\n )\n }\n const type = input.nodeType ?? (await this.runTypeOf(input.jobId))\n return this.client.request(\"POST\", continuePathOf(type), {\n body: { jobId: input.jobId, mode: input.mode, ...(input.editedEdl !== undefined ? { editedEdl: input.editedEdl } : {}) },\n ...idempotencyHeaders(options),\n })\n }\n\n /**\n * The use-case type a run records (`input_data.type`, else its `job_type`).\n * When the key lacks the `jobs:read` scope (403 naming that scope) or the job\n * names no use-case type, the type cannot be read: this rejects with a\n * `NodaroError` (`use_case_type_unknown`, 400) asking for `nodeType`, and\n * nothing is posted (decided 2026-10-18: no route is guessed for the run).\n * Any other failure of the read, any other 403 included, is thrown as is.\n */\n private async runTypeOf(jobId: string): Promise<UseCaseNodeType> {\n const unknown = () =>\n new NodaroError(\"continueUseCase: the run's node type cannot be read — pass nodeType\", \"use_case_type_unknown\", 400)\n let row: { job_type?: unknown; input_data?: unknown } | undefined\n try {\n row = (await this.client.request<{ data?: { job_type?: unknown; input_data?: unknown } }>(\"GET\", `/v1/jobs/${encodeURIComponent(jobId)}`)).data\n } catch (err) {\n if (err instanceof ForbiddenError && err.missingScope === \"jobs:read\") throw unknown()\n throw err\n }\n const input = row?.input_data\n const recorded = input && typeof input === \"object\" && !Array.isArray(input) ? (input as { type?: unknown }).type : undefined\n const type = typeof recorded === \"string\" && recorded.length > 0 ? recorded : row?.job_type\n if (!isUseCaseNodeType(type)) throw unknown()\n return type\n }\n\n /**\n * Plan a transcript-driven cut (`POST /v1/edit-plan`). Reads a timed\n * transcript (plus optional silence ranges) and the media sources, and plans\n * the edit. The finished job's `output_data` holds the plan: an `Edl`\n * (`\"tighten\"` or `\"trailer\"`), an {@link EdlClipSet} (`\"clips\"`), or a\n * {@link ChapterSet} (`\"chapters\"`) — normalize it with {@link unwrapEditPlanOutput}.\n *\n * On a self-hosted install the request relays to nodaro.ai and needs the\n * install connected (a 503 `code: \"nodaro_connection_required\"` otherwise);\n * on nodaro.ai it runs directly.\n */\n editPlan(input: EditPlanInput): Promise<EditJobResult> {\n // The same pre-dispatch check the canvas and MCP run (B4): the server never\n // sees `offsets` — they are written onto the sources here.\n const planned = resolveEditPlanSources(input.sources, {\n offsets: input.offsets,\n transcriptSourceId: input.transcriptSourceId,\n })\n if (!planned.ok) {\n const message = planned.issues.map((issue) => describeAudioSyncOffsetIssue(issue)).join(\"; \")\n return Promise.reject(new NodaroError(`editPlan: ${message}`, \"edit_plan_sources\", 400))\n }\n const transcript = planned.transcriptSourceId\n ? { ...input.transcript, sourceId: planned.transcriptSourceId }\n : input.transcript\n return this.client.request(\"POST\", \"/v1/edit-plan\", {\n body: {\n mode: input.mode,\n planTier: input.planTier,\n transcript,\n sources: planned.sources,\n ...(input.silence !== undefined ? { silence: input.silence } : {}),\n ...(input.instructions !== undefined ? { instructions: input.instructions } : {}),\n ...(input.styleGuide !== undefined ? { styleGuide: input.styleGuide } : {}),\n ...(input.count !== undefined ? { count: input.count } : {}),\n ...(input.targetDurationSec !== undefined ? { targetDurationSec: input.targetDurationSec } : {}),\n ...(input.targetAspect !== undefined ? { targetAspect: input.targetAspect } : {}),\n ...(input.platform !== undefined ? { platform: input.platform } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Camera Switch (podcast B5): choose which camera shows each cut of `edl`\n * by who is speaking — the sound never changes. Cloud edition; a flat\n * price. The finished job's `output_data.json` is the switched EDL (feed it\n * to {@link applyEdl}) and `output_data.transcript` the transcript with\n * `speakerNames` applied. A transcript with no speaker labels rejects with\n * a {@link NodaroError} (`code: \"no_speakers\"`) before any request or charge.\n */\n cameraSwitch(input: CameraSwitchInput): Promise<EditJobResult> {\n if (transcriptSpeakerLabels(input.transcript).length === 0) {\n return Promise.reject(new NodaroError(\n \"cameraSwitch: the transcript has no speaker labels — transcribe with speaker detection on\",\n \"no_speakers\",\n 422,\n ))\n }\n const result = input.speakerMapResult\n if (result !== undefined && !speakerMapResultOf(result)) return Promise.reject(new NodaroError(\"cameraSwitch: speakerMapResult is not a Speaker Map result — pass that job's output_data.json\", \"not_a_speaker_map\", 400))\n const speakerMap = result !== undefined ? cameraSwitchSpeakerMap({ speakerMap: input.speakerMap }, input.edl, input.transcript, { speakerMap: result }) : input.speakerMap\n return this.client.request(\"POST\", \"/v1/camera-switch\", {\n body: {\n edl: input.edl,\n transcript: input.transcript,\n ...(speakerMap !== undefined ? { speakerMap } : {}),\n ...(input.speakerNames !== undefined ? { speakerNames: input.speakerNames } : {}),\n ...(input.minShotMs !== undefined ? { minShotMs: input.minShotMs } : {}),\n ...(input.leadMs !== undefined ? { leadMs: input.leadMs } : {}),\n ...(input.maxShotMs !== undefined ? { maxShotMs: input.maxShotMs } : {}),\n ...(input.wideEvery !== undefined ? { wideEvery: input.wideEvery } : {}),\n ...(input.layoutHints !== undefined ? { layoutHints: input.layoutHints } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Remap a transcript through an EDL — a PURE client-side transform (NO\n * request). Returns a new transcript whose word (and segment) timings are on\n * the EDL's rendered output clock, dropping words that fall in cut regions and\n * clipping straddlers. Runs `@nodaro/shared`'s canonical\n * `remapTranscriptThroughEdl`, the same remap `applyEdl` performs server-side.\n */\n remapTranscript(edl: Edl, transcript: Transcript): Transcript {\n return remapTranscriptThroughEdl(edl, transcript)\n }\n}\n"]}
|
|
@@ -100,5 +100,5 @@ var TrendsResource = class {
|
|
|
100
100
|
};
|
|
101
101
|
|
|
102
102
|
exports.TrendsResource = TrendsResource;
|
|
103
|
-
//# sourceMappingURL=chunk-
|
|
104
|
-
//# sourceMappingURL=chunk-
|
|
103
|
+
//# sourceMappingURL=chunk-PDX742CC.cjs.map
|
|
104
|
+
//# sourceMappingURL=chunk-PDX742CC.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/resources/trends.ts"],"names":[],"mappings":"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","file":"chunk-PDX742CC.cjs","sourcesContent":["import type { NodaroClientCore } from \"../core.js\"\nimport type { CastSwapEngine } from \"./cast-swap.js\"\n\n/**\n * Trends — ready-made Nodaro Motion recipes. A trend is a short clip with slots\n * (the people, pets, products and place you can replace); fill its slots with\n * your own pictures and run it to get the clip back with them in it.\n *\n * Cloud edition only: `/v1/trends` 404s on self-hosted installs. Only\n * published trends exist here: a draft, unpublished or unknown slug is a 404\n * on every route. `\"runs\"` and `\"tags\"` are never slugs (`/v1/trends/runs` is\n * the run list, `/v1/trends/tags` the tag list): the slug methods throw a\n * `TypeError` for them before any request. `run()` answers 202 with a job id; follow it with\n * `client.jobs.get(jobId)` / `client.jobs.getStatus(jobId)` like any other\n * job, or read it back as a trend run with `getRun(jobId)` / `runs()`.\n *\n * Repeats: pass `{ idempotencyKey }` to `run()` and it is sent as the\n * `Idempotency-Key` header. A repeat with the same key answers with the first\n * run's `jobId`; nothing new is started or charged.\n *\n * Refusals (thrown as a `NodaroError` subclass; body `{ error: { code, message } }`):\n * - 400 `validation_error` (the body, a query, or a bad `cursor`);\n * `estimate()` / `run()` only, before anything is charged:\n * `missing_slot` (a required slot without pictures), `unknown_slot`,\n * `invalid_choice` (a chip the slot's kind does not take),\n * `chip_not_offered` (a chip of the kind that the slot's\n * {@link TrendDetailSlot.chips} does not list: the trend fixes it),\n * `example_out_of_range` (`example` names no example of the trend, or is\n * sent at all on a trend with none), `preview_unavailable` (the trend offers no\n * preview), `invalid_slot_mention` (a `{slot:<key>}` in `details` naming no\n * slot), `prompt_too_long` (shorten `details`), `draft_unsupported`\n * (`draft: true` on a trend whose `engine` has no draft: Nodaro Motion Pulse),\n * `resolution_unsupported` (a `resolution` not in the trend's\n * `resolutions`), `too_many_images` (a Nodaro Motion Pulse trend's run would send more\n * than 10 pictures: send fewer per slot). The slot errors carry the\n * slot's key as `slot` (in `NodaroError.details`), and `chip_not_offered`\n * the chip as `chip` too.\n * - 401 `unauthorized`.\n * - 404 `not_found`: the trend is not published (every method), or `run()`'s\n * `sessionId` is not a session you may use.\n * - 409 `trend_unavailable` (`estimate()` / `run()`): the trend's setup is out\n * of date — it cannot run until it is updated, so a retry gets the same answer.\n * - 502 `engine_error` (`estimate()` / `run()`).\n * - 503 `engine_unsupported` (this platform version cannot run trends yet),\n * `trend_unavailable` (`estimate()` / `run()`), `not_available` (this\n * server has no trend catalog yet).\n * - `estimate()` / `run()` answer the video engine's refusals with the trend\n * routes' OWN codes and fixed messages — never the engine's text or\n * `issues`: 400 `trend_run_rejected` (the inputs could not run), or\n * `clip_out_of_range` / `preview_out_of_range` / `video_unreadable` (this\n * trend's clip), `unsafe_url` (a picture is not a public http(s) address),\n * `raw_reference_token` (`details` holds `{image:` / `{video:` /\n * `{audio:`); 402 `insufficient_credits` (or a workspace cap) as given;\n * 413 `storage_limit_exceeded`; 422 `job_blocked` (the job policy\n * blocked it); 429 `rate_limited`; 503 `engine_unsupported`,\n * `estimate_unavailable`; 502 `engine_error` for anything else. `run()`\n * also answers 400 `validation_error` for an `idempotencyKey` outside\n * 8–200 characters.\n */\n\n/** `/v1/trends/runs` is the run list and `/v1/trends/tags` the tag list, so neither can be a trend's slug. */\nconst RESERVED_SLUGS: Readonly<Record<string, string>> = {\n runs: \"the run list; use runs()\",\n tags: \"the tag list; use tags()\",\n}\n\nfunction trendPath(slug: string, rest = \"\"): string {\n if (Object.hasOwn(RESERVED_SLUGS, slug)) {\n throw new TypeError(`\"${slug}\" is not a trend slug (it is reserved for ${RESERVED_SLUGS[slug]})`)\n }\n return `/v1/trends/${encodeURIComponent(slug)}${rest}`\n}\n\n/** What a slot holds. */\nexport type TrendSlotKind = \"person\" | \"location\" | \"product\" | \"pet\"\n\n/** A chip: make this trait anew from your pictures (`\"new\"`), or keep the clip's (`\"original\"`). */\nexport type TrendChoice = \"new\" | \"original\"\n\n/**\n * The chips a slot can take, by its kind: a person `body` / `outfit` / `hair`,\n * a pet `body` / `coat`, a product `shape` / `finish`, a location none.\n */\nexport interface TrendChips {\n body?: TrendChoice\n outfit?: TrendChoice\n hair?: TrendChoice\n coat?: TrendChoice\n shape?: TrendChoice\n finish?: TrendChoice\n}\n\n/** A chip's name: `body`, `outfit`, `hair`, `coat`, `shape` or `finish`. */\nexport type TrendChipKey = keyof TrendChips\n\nexport type TrendResolution = \"480p\" | \"720p\" | \"1080p\"\n\n/**\n * The engine a trend runs on, by its public id ({@link CastSwapEngine}):\n * `\"motion-prism\"` (Nodaro Motion Prism) or `\"motion-pulse\"` (Nodaro Motion Pulse).\n * The trend chooses it, never the person running it.\n */\nexport type TrendEngine = CastSwapEngine\n\n/** A trend in the catalog (`list()`). Text is in the resolved locale. */\nexport interface TrendCard {\n id: string\n slug: string\n name: string\n description?: string\n tags: string[]\n /** e.g. `\"trending\"`, `\"new\"`. */\n badges: string[]\n /**\n * The trend's first example, else (a trend with no example) its original's\n * display copy, as {@link TrendDetail.original}.\n */\n cover: { videoUrl: string; posterUrl?: string; width: number; height: number }\n /**\n * The original's display copy, the same address as {@link TrendDetail.original}\n * (without its length and preview start). Absent on a version without a\n * display copy (and from an older server). On a trend that hides its\n * original, its first example instead (then the same as {@link cover}).\n */\n original?: { videoUrl: string; posterUrl?: string; width: number; height: number }\n /**\n * Every example's video, in the page's order, so a list can show each clip\n * without reading the trend's page. Empty on a trend with only its original;\n * absent from an older server. The pictures each slot used are on the page\n * ({@link TrendDetail.examples}). On a trend that hides its original, from\n * its second example (the first is {@link original}).\n */\n examples?: Array<{ videoUrl: string; posterUrl?: string; width: number; height: number }>\n /** How many slots of each kind the trend has. */\n needs: Record<TrendSlotKind, number>\n /** The length of a full run, in seconds. */\n durationSec: number\n isFavorite: boolean\n}\n\n/** One slot of a trend: what it asks you for. */\nexport interface TrendDetailSlot {\n /** The key to fill in {@link TrendRunRequest.slots}, and to mention in `details` as `{slot:<key>}`. */\n key: string\n label: string\n kind: TrendSlotKind\n /**\n * A required slot needs pictures; an optional one left empty takes the\n * chosen example's, or, on a trend with no example (or when that example has\n * none for it), stays as in the original;\n * except in Full depth trends, where it uses the trend's default image for that role if one is set, otherwise the original's look.\n */\n required: boolean\n hint?: string\n /**\n * A picture showing who or what this slot is in the original (e.g. a frame\n * of that actor), when the trend has one. Show it on an empty slot; a run\n * never sends it.\n */\n hintImageUrl?: string\n /**\n * The chips you may set on this slot, in its kind's order; `[]` = none (show\n * no chips row). A chip of the kind that is not listed is fixed by the\n * trend: sending it is refused with `400 chip_not_offered`.\n */\n chips: TrendChipKey[]\n /** The starting value of the chips in {@link chips} (only those). */\n defaults?: TrendChips\n}\n\n/** A finished run shown on a trend's page, with the pictures it used per slot. */\nexport interface TrendExample {\n videoUrl: string\n posterUrl?: string\n width: number\n height: number\n /**\n * Slot key → the pictures this example used for it. An optional slot the\n * example kept as in the original has no key; a run that leaves that slot\n * empty with this example keeps it as in the original too.\n */\n slots: Record<string, string[]>\n}\n\n/**\n * The clip a trend recasts, as you may show it: a browser-safe copy (H.264\n * MP4) of the clip every run starts from. On a trend that hides its original,\n * its first example instead (the original clip is then never sent). Its fields\n * parallel {@link TrendExample}'s.\n */\nexport interface TrendOriginal {\n videoUrl: string\n posterUrl?: string\n width: number\n height: number\n /** The copy's length (on a trend that hides its original, the first example's), in seconds. */\n durationSec: number\n /**\n * Where the 4-second preview starts in the clip, in seconds (`0` included),\n * when the trend offers one ({@link TrendDetail.previewSec} is `4`); absent\n * otherwise.\n */\n previewStartSec?: number\n}\n\n/** A trend's page (`get()`). */\nexport interface TrendDetail extends TrendCard {\n /** The published version this page shows. */\n version: number\n /** `4` when the trend offers a 4-second preview run, else `null`. */\n previewSec: 4 | null\n /**\n * The original clip every run starts from, to show beside the examples.\n * Absent only on a version without a display copy (and from an older server).\n * On a trend that hides its original, its first example (see {@link TrendOriginal}).\n */\n original?: TrendOriginal\n /** The engine every run of this trend uses. Absent (an older server) = `\"motion-prism\"`. */\n engine?: TrendEngine\n /** Its name to show: \"Nodaro Motion Prism\" or \"Nodaro Motion Pulse\". Absent from an older server. */\n engineName?: string\n /**\n * The resolutions a run may ask for, the default first. Nodaro Motion Prism:\n * `[\"480p\", \"720p\", \"1080p\"]`, where 480p is a draft (show it as one);\n * Nodaro Motion Pulse: `[\"720p\", \"1080p\"]`, no draft. Offer exactly these.\n */\n resolutions: TrendResolution[]\n slots: TrendDetailSlot[]\n /**\n * Finished runs to show beside the original; may be empty (a trend with only\n * its original). On a trend that hides its original, from its second example\n * (the first is {@link original}).\n */\n examples: TrendExample[]\n}\n\n/** What you put in one slot of a run: 1–3 pictures, and the chips the slot offers ({@link TrendDetailSlot.chips}). */\nexport interface TrendRunSlot extends TrendChips {\n images?: string[]\n}\n\n/** The body of `estimate()` and `run()`. */\nexport interface TrendRunRequest {\n /**\n * Slot key → what you put in it. Every required slot needs pictures; an\n * optional slot left out takes the chosen example's, or, on a trend with no\n * example (or when that example has none for it), stays as in the original; except in Full depth trends, where it uses the trend's default image for that role if one is set, otherwise the original's look.\n * 1–3 pictures per slot.\n */\n slots: Record<string, TrendRunSlot>\n /**\n * 1-based example whose pictures fill the optional slots you leave empty\n * (never in a Full depth trend, which records `example: 0`), numbering the\n * examples as {@link TrendDetail.examples} lists them. Default 1. Leave it out on a trend with no examples: sending one there\n * answers 400 `example_out_of_range` (and `0` is a `validation_error`). On a\n * trend that hides its original ({@link TrendDetail.original} is its first\n * example), `1` is still the first entry of `examples`, and leaving it out\n * fills the slots from the example shown as the original.\n */\n example?: number\n /** Your own words, at most 1,000 characters. Mention a slot as `{slot:<key>}`. */\n details?: string\n /** `true` runs a 4-second preview (only when the trend's `previewSec` is `4`). */\n preview?: boolean\n /**\n * One of the trend's {@link TrendDetail.resolutions}; default its first\n * (`\"480p\"` on Nodaro Motion Prism, `\"720p\"` on Nodaro Motion Pulse). Another answers 400\n * `resolution_unsupported`.\n */\n resolution?: TrendResolution\n /**\n * A draft run, only at 480p on a Nodaro Motion Prism trend. Default `true` at\n * 480p. `true` on a Nodaro Motion Pulse trend answers 400 `draft_unsupported`.\n */\n draft?: boolean\n /**\n * A generation session of yours. Accepted (and checked: one you may not use\n * is a 404), but not yet applied: trend runs are not filed into sessions yet.\n */\n sessionId?: string\n /** Accepted, but not used by the run today. */\n projectId?: string\n /**\n * A full run's own preview: the `jobId` of a completed preview run of this\n * trend that you started. When that preview needed the trend's retry (the\n * clip refused at first), the full run is made that way from the start (the\n * quote includes it). Any other job is ignored, without an error. Must be a uuid\n * (else 400 `validation_error`).\n */\n previewJobId?: string\n /**\n * `run()`: your client app's slug (e.g. `\"tools\"`: lowercase letters,\n * digits and `-`, 1–40 characters, starting with a letter or digit), as on\n * any job: the run's job carries it as `input_data.origin`, so\n * `client.jobs.list({ origin })` finds it. Not a slug is a 400\n * `validation_error`; without it the run has no origin. `estimate()`\n * ignores it.\n */\n origin?: string\n}\n\n/** What a run would cost, in credits (`estimate()`). */\nexport interface TrendEstimate {\n /** The charge the run is expected to come to. */\n expectedCredits: number\n /** What is reserved when the run starts; the run is charged what it actually costs. */\n holdCredits: number\n /**\n * The parts of the quote, by name, for display only: some are alternatives\n * or sit outside the totals (`privacy`, the retry's, beside\n * `privacyCredits`), so read `expectedCredits` and `holdCredits`, never\n * their sum.\n */\n breakdown: Record<string, number>\n /**\n * Only on a trend that retries a clip the video model refuses: the extra\n * credits charged if that retry runs. Not included in `expectedCredits` or\n * `holdCredits`. 0 (or absent) when there is nothing extra to charge.\n */\n privacyCredits?: number\n /** @deprecated read {@link privacyCredits} (an older server answered this name). */\n maskingCredits?: number\n}\n\n/** Per-call options for {@link TrendsResource.run}. */\nexport interface TrendRunOptions {\n /**\n * Sent as the `Idempotency-Key` header, unchanged (an empty string sends no\n * header). The server takes 8–200 characters and answers 400 for any other\n * length. Reuse it when retrying the same run.\n */\n idempotencyKey?: string\n}\n\n/** One slot of a past run, as it was run. Labels are English. */\nexport interface TrendRunDisplaySlot extends TrendChips {\n key: string\n label: string\n kind: TrendSlotKind\n images: string[]\n /**\n * `true` when the slot was left empty and the example's pictures filled it.\n * `false` with no `images`: left empty on a trend with no example, or the\n * chosen example has no pictures for it, so it stayed as in the original.\n */\n fromExample: boolean\n /**\n * `true` when the slot was left empty and the trend's own default pictures\n * filled it (`fromExample` is then `false`). Absent otherwise.\n */\n fromDefault?: true\n}\n\n/**\n * One of your trend runs (`runs()`, `getRun()`). Stays readable after the\n * trend moves on or is unpublished.\n *\n * A Full Quality version of a draft is a run of its own, with its own status,\n * result and charge: `fullQualityOf` names the draft it was made from, and it\n * stays listed after that draft is deleted.\n */\nexport interface TrendRun {\n jobId: string\n status: string\n progress: number | null\n /**\n * Why a failed or cancelled run did not finish; `null` otherwise. `code` is\n * `safety-block` or `policy-block` when the job was blocked, `credits` when\n * it ran out of credits partway (it was refunded: ask the person to top up,\n * then run it again), else `cancelled` or `failed`. `message` is the same\n * fixed sentence for every code — branch on `code`, not on the text.\n */\n error: { code?: string; message: string } | null\n createdAt: string\n completedAt: string | null\n /** The trend and version it ran (name in English). */\n trend: { id: string; slug: string; version: number; name: string }\n /**\n * The 1-based example used for empty optional slots, numbered as the trend's\n * page listed its examples for the version the run ran on ({@link trend}'s\n * `version`); `0` = none was used (the trend had no example, or is a Full\n * depth trend): never show it as \"Example 0\". Absent when the run used the\n * example a trend that hides its original shows as its original.\n */\n example?: number\n slots: TrendRunDisplaySlot[]\n details?: string\n preview: boolean\n resolution: string\n draft: boolean\n /** The engine the run was made on. Absent (an older server) = `\"motion-prism\"`. */\n engine?: TrendEngine\n /** Its name to show: \"Nodaro Motion Prism\" or \"Nodaro Motion Pulse\". Absent from an older server. */\n engineName?: string\n /** The result, once completed. */\n videoUrl: string | null\n posterUrl: string | null\n /**\n * What the run was charged once settled, every step it ran included (its\n * reference preparation, mask and video); `null` until the run's own hold\n * settles. A step adds its charge once it settles.\n */\n creditsCharged: number | null\n /** The run's hold: its own plus its steps'. Absent from an older server. */\n creditsHeld?: number | null\n /** What went back from `creditsHeld` once every hold in the run settled; `null` until then. Absent from an older server. */\n creditsReleased?: number | null\n /** A Full Quality version: the job id of the run (a draft, or a Nodaro Motion Pulse 720p run) it was made from. `null` for any other run. */\n fullQualityOf: string | null\n /**\n * A draft's (or Nodaro Motion Pulse 720p run's) newest Full Quality try, if one was started: a pointer only,\n * to know an upgrade exists and how it is going. Its video and charge are on\n * that run, listed on its own (its `fullQualityOf` is this run's `jobId`) —\n * read it there (`getRun(fullQuality.jobId)`). Older tries of the draft (a\n * failed or cancelled one tried again) are not listed by `runs()`;\n * `getRun()` still reads one by its id.\n */\n fullQuality: { jobId: string; status: string } | null\n /**\n * Until when a completed run can be redone in Full Quality — a Nodaro Motion\n * Prism draft (the same take at 1080p) or a Nodaro Motion Pulse run at 720p (a new take at\n * 1080p); `null` otherwise.\n */\n fullQualityUntil: string | null\n}\n\nexport interface ListTrendsParams {\n /** Search text. */\n q?: string\n tag?: string\n /** `true` lists only your favorites. */\n favorites?: boolean\n /** A platform language for names and descriptions; default your profile's, then English. */\n locale?: string\n /** The previous page's `next`. */\n cursor?: string\n /** Page size, 1–60 (server default 30). */\n limit?: number\n}\n\nexport interface ListTrendRunsParams {\n /** Only runs of this trend (its slug). */\n trend?: string\n /** The previous page's `next`. */\n cursor?: string\n /** Page size, 1–60 (server default 30). */\n limit?: number\n}\n\n/** A tag in use on the published catalog (`tags()`). */\nexport interface TrendTag {\n /** The tag itself: pass it as `list({ tag })`. */\n tag: string\n /** The tag's label in the resolved locale; the tag itself when it has none. */\n label: string\n /** How many published trends carry it. */\n count: number\n}\n\nexport interface TrendPage<T> {\n data: T[]\n /** Pass back as `cursor` for the next page; `null` on the last one. */\n next: string | null\n}\n\nexport class TrendsResource {\n constructor(private client: NodaroClientCore) {}\n\n /** The published catalog in its curated order (`GET /v1/trends`), a page at a time. */\n list(params: ListTrendsParams = {}): Promise<TrendPage<TrendCard>> {\n const query: Record<string, string> = {}\n if (params.q) query.q = params.q\n if (params.tag) query.tag = params.tag\n if (params.favorites) query.favorites = \"1\"\n if (params.locale) query.locale = params.locale\n if (params.cursor) query.cursor = params.cursor\n if (params.limit !== undefined) query.limit = String(params.limit)\n return this.client.request(\"GET\", \"/v1/trends\", { query })\n }\n\n /** A published trend's page (`GET /v1/trends/:slug`). */\n async get(slug: string, params: { locale?: string } = {}): Promise<TrendDetail> {\n const query: Record<string, string> = {}\n if (params.locale) query.locale = params.locale\n const res = await this.client.request<{ data: TrendDetail }>(\"GET\", trendPath(slug), { query })\n return res.data\n }\n\n /** Quote a run before starting it (`POST /v1/trends/:slug/estimate`); spends nothing. */\n async estimate(slug: string, request: TrendRunRequest): Promise<TrendEstimate> {\n const res = await this.client.request<{ data: TrendEstimate }>(\n \"POST\",\n trendPath(slug, \"/estimate\"),\n { body: request },\n )\n return res.data\n }\n\n /**\n * Start a run (`POST /v1/trends/:slug/runs`, 202). Returns the job to follow.\n * With `options.idempotencyKey` (sent as the `Idempotency-Key` header) a\n * repeat with the same key answers with the first run's `jobId`.\n */\n async run(slug: string, request: TrendRunRequest, options?: TrendRunOptions): Promise<{ jobId: string }> {\n const res = await this.client.request<{ data: { jobId: string } }>(\n \"POST\",\n trendPath(slug, \"/runs\"),\n {\n body: request,\n ...(options?.idempotencyKey ? { headers: { \"Idempotency-Key\": options.idempotencyKey } } : {}),\n },\n )\n return res.data\n }\n\n /**\n * Your trend runs, newest first (`GET /v1/trends/runs`). A Full Quality\n * version of a draft is listed as its own run (`fullQualityOf`).\n */\n runs(params: ListTrendRunsParams = {}): Promise<TrendPage<TrendRun>> {\n const query: Record<string, string> = {}\n if (params.trend) query.trend = params.trend\n if (params.cursor) query.cursor = params.cursor\n if (params.limit !== undefined) query.limit = String(params.limit)\n return this.client.request(\"GET\", \"/v1/trends/runs\", { query })\n }\n\n /**\n * One of your trend runs by its job id (`GET /v1/trends/runs/:jobId`), in\n * the same shape as `runs()`. A job that is not your own trend run is a\n * `NotFoundError`.\n */\n async getRun(jobId: string): Promise<TrendRun> {\n const res = await this.client.request<{ data: TrendRun }>(\"GET\", `/v1/trends/runs/${encodeURIComponent(jobId)}`)\n return res.data\n }\n\n /**\n * The tags in use on the published catalog, each with its label in the\n * resolved locale (`GET /v1/trends/tags`), most used first.\n */\n async tags(params: { locale?: string } = {}): Promise<TrendTag[]> {\n const query: Record<string, string> = {}\n if (params.locale) query.locale = params.locale\n const res = await this.client.request<{ data: TrendTag[] }>(\"GET\", \"/v1/trends/tags\", { query })\n return res.data\n }\n\n /** Add a trend to your favorites (`PUT /v1/trends/:slug/favorite`). */\n async favorite(slug: string): Promise<void> {\n await this.client.request<void>(\"PUT\", trendPath(slug, \"/favorite\"))\n }\n\n /** Remove a trend from your favorites (`DELETE /v1/trends/:slug/favorite`). */\n async unfavorite(slug: string): Promise<void> {\n await this.client.request<void>(\"DELETE\", trendPath(slug, \"/favorite\"))\n }\n}\n"]}
|