@nodaro/prompts 1.21.0 → 1.23.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/index.cjs +132 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +94 -1
- package/dist/index.d.ts +94 -1
- package/dist/index.js +126 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/catalog-packs.test.ts +13 -0
- package/src/__tests__/factory-presets.test.ts +28 -3
- package/src/__tests__/fixtures/parameter-hint-golden.json +19 -3
- package/src/__tests__/node-prompt-fields.test.ts +2 -2
- package/src/__tests__/transitions-instant.test.ts +112 -0
- package/src/ad-creative-analysis.ts +99 -0
- package/src/catalog-packs.ts +13 -2
- package/src/factory-presets/generate-video.ts +22 -0
- package/src/index.ts +3 -0
- package/src/node-prompt-fields.ts +4 -0
- package/src/picker-catalogs.ts +13 -0
- package/src/ref-binding.ts +19 -0
- package/src/transitions.ts +47 -10
- package/src/video-reference-resolver.ts +1 -1
package/src/transitions.ts
CHANGED
|
@@ -41,6 +41,18 @@ export interface Transition {
|
|
|
41
41
|
* "invisible cut"). Everywhere else the label IS the term.
|
|
42
42
|
*/
|
|
43
43
|
readonly term?: string
|
|
44
|
+
/**
|
|
45
|
+
* `true` on rows whose mechanism IS A CUT — the change happens between two
|
|
46
|
+
* frames, so it has no duration to time. A duration clause ("lasting
|
|
47
|
+
* approximately 1 second") on such a row tells the video model to spend a
|
|
48
|
+
* second on the change, and it obliges with a dissolve: a match cut rendered
|
|
49
|
+
* as a 1.75 s cross-dissolve in QA. The composer therefore skips the duration
|
|
50
|
+
* lever when every picked transition is instant, and consumers (the picker
|
|
51
|
+
* UI, Studio) read `isInstantTransition` to hide that lever. Position and
|
|
52
|
+
* intensity still apply — WHERE the cut lands, and how hard it hits, are
|
|
53
|
+
* real choices.
|
|
54
|
+
*/
|
|
55
|
+
readonly instant?: boolean
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
/**
|
|
@@ -73,7 +85,7 @@ export const TRANSITIONS: ReadonlyArray<Transition> = [
|
|
|
73
85
|
// ============================================================================
|
|
74
86
|
{ id: "auto", label: "Auto", category: "standard", description: "Let the model choose", promptHint: "" },
|
|
75
87
|
{ id: "none", label: "None / Hard Cut", category: "standard", description: "Instantaneous switch, no transition",
|
|
76
|
-
promptHint: "no transition, hard cut, instantaneous switch from first shot to second shot", term: "hard cut" },
|
|
88
|
+
promptHint: "no transition, hard cut, instantaneous switch from first shot to second shot", term: "hard cut" , instant: true },
|
|
77
89
|
{ id: "cross-dissolve", label: "Cross-Dissolve", category: "standard", description: "Gradual blend between shots",
|
|
78
90
|
promptHint: "smooth cross-dissolve transition where the first shot gradually fades out as the second shot fades in" },
|
|
79
91
|
{ id: "fade-to-black", label: "Fade to Black", category: "standard", description: "Darkens to black, second emerges",
|
|
@@ -81,11 +93,11 @@ export const TRANSITIONS: ReadonlyArray<Transition> = [
|
|
|
81
93
|
{ id: "fade-to-white", label: "Fade to White", category: "standard", description: "Blooms to white, second emerges",
|
|
82
94
|
promptHint: "fade to white: the first shot brightens until the frame is pure white, then the second shot resolves out of the white" },
|
|
83
95
|
{ id: "snap-to-black", label: "Snap to Black", category: "standard", description: "Instant cut to full black for a beat, then the next shot",
|
|
84
|
-
promptHint: "snap to black: the first shot cuts instantly to full black with no fade, the frame holds pure black for a single beat, then the second shot cuts in at full brightness", term: "snap to black" },
|
|
96
|
+
promptHint: "snap to black: the first shot cuts instantly to full black with no fade, the frame holds pure black for a single beat, then the second shot cuts in at full brightness", term: "snap to black" , instant: true },
|
|
85
97
|
{ id: "match-cut", label: "Match Cut", category: "standard", description: "Shape or motion match across shots",
|
|
86
|
-
promptHint: "match cut: the final composition of the first shot matches the opening composition of the second shot in shape, color, and motion, so the cut feels like a visual rhyme" },
|
|
98
|
+
promptHint: "match cut: the final composition of the first shot matches the opening composition of the second shot in shape, color, and motion, so the cut feels like a visual rhyme" , instant: true },
|
|
87
99
|
{ id: "smash-cut", label: "Smash Cut", category: "standard", description: "Jarring abrupt cut between contrasting shots",
|
|
88
|
-
promptHint: "smash cut: an abrupt jarring transition between two visually or tonally contrasting shots with no fade, on a beat" },
|
|
100
|
+
promptHint: "smash cut: an abrupt jarring transition between two visually or tonally contrasting shots with no fade, on a beat" , instant: true },
|
|
89
101
|
{ id: "iris", label: "Iris", category: "standard", description: "Circular iris closes, then opens on second",
|
|
90
102
|
promptHint: "iris transition: a circular vignette closes inward over the first shot until the frame is black, then opens outward to reveal the second shot", term: "iris wipe" },
|
|
91
103
|
{ id: "wipe", label: "Wipe", category: "standard", description: "Linear wipe replaces first shot",
|
|
@@ -93,11 +105,11 @@ export const TRANSITIONS: ReadonlyArray<Transition> = [
|
|
|
93
105
|
{ id: "roll-transition", label: "Roll", category: "standard", description: "Frame rolls 90-180°, second shot upright on landing",
|
|
94
106
|
promptHint: "the frame rolls along the camera axis with a smooth 90 to 180 degree rotation, motion-blurred during the roll, and as the rotation completes the new shot is upright and stable in frame", term: "camera roll transition" },
|
|
95
107
|
{ id: "seamless-match", label: "Seamless Match", category: "standard", description: "Hidden cut disguised by matched motion and color",
|
|
96
|
-
promptHint: "hidden seamless transition: the camera motion, color palette, and on-screen motion at the end of the first shot continue exactly across the cut into the second shot, so the boundary is invisible and the two shots feel like one unbroken take", term: "invisible cut" },
|
|
108
|
+
promptHint: "hidden seamless transition: the camera motion, color palette, and on-screen motion at the end of the first shot continue exactly across the cut into the second shot, so the boundary is invisible and the two shots feel like one unbroken take", term: "invisible cut" , instant: true },
|
|
97
109
|
{ id: "whip-pan", label: "Whip Pan", category: "standard", description: "Camera whips sideways into blur, next shot rides the same direction",
|
|
98
110
|
promptHint: "whip pan transition: the camera whips sideways at high speed, smearing the frame into heavy horizontal motion blur, and the second shot enters already travelling in the same direction before it settles into its framing", term: "whip pan" },
|
|
99
111
|
{ id: "jump-cut", label: "Jump Cut", category: "standard", description: "Same framing, time skips forward",
|
|
100
|
-
promptHint: "jump cut: the framing, lens, and camera position stay identical across the cut while time skips abruptly forward, so the subject snaps to a new position inside what still reads as one continuous shot", term: "jump cut" },
|
|
112
|
+
promptHint: "jump cut: the framing, lens, and camera position stay identical across the cut while time skips abruptly forward, so the subject snaps to a new position inside what still reads as one continuous shot", term: "jump cut" , instant: true },
|
|
101
113
|
|
|
102
114
|
// ============================================================================
|
|
103
115
|
// TIME — 8 entries — temporal shifts (same or related scene, different time, or memory)
|
|
@@ -219,11 +231,11 @@ export const TRANSITIONS: ReadonlyArray<Transition> = [
|
|
|
219
231
|
{ id: "vehicle-explosion", label: "Vehicle Explosion", category: "physics", description: "Vehicle detonates in foreground, scene changes behind",
|
|
220
232
|
promptHint: "a vehicle in the foreground erupts in a violent explosion of fire and twisted metal, the fireball expands toward the camera and washes the frame in orange flame, and as the smoke parts the second scene resolves" },
|
|
221
233
|
{ id: "jump-match", label: "Jump Match", category: "physics", description: "Subject jumps, landing matches into new scene",
|
|
222
|
-
promptHint: "the subject jumps upward and out of frame at the end of the first shot, with matched velocity the camera follows the arc, and on landing the subject is in a new location seamlessly continuing the same jump", term: "match cut on a jump" },
|
|
234
|
+
promptHint: "the subject jumps upward and out of frame at the end of the first shot, with matched velocity the camera follows the arc, and on landing the subject is in a new location seamlessly continuing the same jump", term: "match cut on a jump" , instant: true },
|
|
223
235
|
{ id: "hand-swipe", label: "Hand Swipe", category: "physics", description: "Hand swipes across lens, scene changes during occlusion",
|
|
224
236
|
promptHint: "a hand sweeps across the camera lens at close range, fully occluding the frame in motion blur for a single beat, and as the hand exits the opposite side the scene has changed to the new setting" },
|
|
225
237
|
{ id: "action-relay", label: "Action Match", category: "physics", description: "Subject exits on an action and lands in the new scene mid-move",
|
|
226
|
-
promptHint: "match cut on action: the subject exits the frame on a committed action — a stride, a throw, a turn — and enters the new scene on the same beat continuing that movement at matched speed and direction, so the action carries unbroken across the cut", term: "match cut on action" },
|
|
238
|
+
promptHint: "match cut on action: the subject exits the frame on a committed action — a stride, a throw, a turn — and enters the new scene on the same beat continuing that movement at matched speed and direction, so the action carries unbroken across the cut", term: "match cut on action" , instant: true },
|
|
227
239
|
|
|
228
240
|
// ============================================================================
|
|
229
241
|
// LIGHT — 8 entries — flash and lens FX
|
|
@@ -314,6 +326,23 @@ export function getTransitionTerm(id: string | undefined | null): string {
|
|
|
314
326
|
|
|
315
327
|
export const TRANSITION_IDS: ReadonlyArray<string> = TRANSITIONS.map((t) => t.id)
|
|
316
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Whether a transition is a CUT — instantaneous by nature, so it takes no
|
|
331
|
+
* duration (see `Transition.instant`). Reads through `getTransition`, so it
|
|
332
|
+
* answers for the same entry every other getter describes; an unknown id, the
|
|
333
|
+
* no-op "auto" and an empty value are all `false`.
|
|
334
|
+
*
|
|
335
|
+
* A multi-pick (`string[]`) is instant only when EVERY picked id is: a cut
|
|
336
|
+
* paired with a dissolve still has a dissolve to time.
|
|
337
|
+
*/
|
|
338
|
+
export function isInstantTransition(
|
|
339
|
+
id: string | ReadonlyArray<string> | undefined | null,
|
|
340
|
+
): boolean {
|
|
341
|
+
const ids = typeof id === "string" ? [id] : id ? [...id] : []
|
|
342
|
+
if (ids.length === 0) return false
|
|
343
|
+
return ids.every((one) => getTransition(one)?.instant === true)
|
|
344
|
+
}
|
|
345
|
+
|
|
317
346
|
// ---------------------------------------------------------------------------
|
|
318
347
|
// Graph-aware composer — start/end input handles + timing fields + multi-pick
|
|
319
348
|
// ---------------------------------------------------------------------------
|
|
@@ -395,6 +424,8 @@ const INTENSITY_CLAUSES = clausesOf(TRANSITION_INTENSITIES)
|
|
|
395
424
|
* - 0 hints (no transition, empty array, or all-empty hints) → ""
|
|
396
425
|
* - n base hints joined with ", and "
|
|
397
426
|
* - Timing/start/end clauses apply ONCE at the outer layer, not per-id
|
|
427
|
+
* - The duration clause is dropped when every picked id is instant (a cut —
|
|
428
|
+
* see `isInstantTransition`); position and intensity still apply
|
|
398
429
|
* - null input is treated like undefined (falsy short-circuit → returns "")
|
|
399
430
|
*
|
|
400
431
|
* @param mode `"compact"` builds the base from each transition's short
|
|
@@ -416,7 +447,10 @@ export function composeTransitionHintFromConnections(
|
|
|
416
447
|
// ONLY the base fragment swaps in compact mode — the multi-pick join, the
|
|
417
448
|
// timing clauses and the start/end clauses below are identical either way.
|
|
418
449
|
const resolveBase = mode === "compact" ? getTransitionTerm : getTransitionPromptHint
|
|
419
|
-
|
|
450
|
+
// Only ids that contribute a base hint count below — a no-op "auto" beside a
|
|
451
|
+
// cut must not make the pick look non-instant.
|
|
452
|
+
const picked = ids.filter((id) => resolveBase(id).length > 0)
|
|
453
|
+
const baseHints = picked.map(resolveBase)
|
|
420
454
|
if (baseHints.length === 0) return ""
|
|
421
455
|
|
|
422
456
|
const combinedBase = baseHints.join(", and ")
|
|
@@ -425,7 +459,10 @@ export function composeTransitionHintFromConnections(
|
|
|
425
459
|
if (timing?.position && timing.position !== "auto") {
|
|
426
460
|
parts.push(POSITION_CLAUSES[timing.position])
|
|
427
461
|
}
|
|
428
|
-
|
|
462
|
+
// A cut has no duration: "lasting approximately 1 second" on a match cut
|
|
463
|
+
// makes the model render a one-second dissolve. Skipped only when EVERY
|
|
464
|
+
// picked id is instant — a mixed pick still has a non-cut to time.
|
|
465
|
+
if (timing?.duration && timing.duration !== "auto" && !isInstantTransition(picked)) {
|
|
429
466
|
parts.push(DURATION_CLAUSES[timing.duration])
|
|
430
467
|
}
|
|
431
468
|
if (timing?.intensity && timing.intensity !== "auto") {
|
|
@@ -46,7 +46,7 @@ import { insertBeforeStyleSection } from "./prompt-style-section.js"
|
|
|
46
46
|
// The binding surface string and the id-addressed token resolver live in their
|
|
47
47
|
// own modules (see them for the contracts); re-exported here so every existing
|
|
48
48
|
// importer of this module — and the package index's `export *` — keeps working.
|
|
49
|
-
export { REF_BINDING, identityRefsSentence } from "./ref-binding.js"
|
|
49
|
+
export { REF_BINDING, identityRefsSentence, SEEDANCE_VIDEO_EDIT_PREFIX, buildSeedanceVideoEditPrompt } from "./ref-binding.js"
|
|
50
50
|
export { resolveRefIdTokens, type RefIdTokenContext } from "./ref-id-tokens.js"
|
|
51
51
|
|
|
52
52
|
|