@nodaro/prompts 1.0.1
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/LICENSE +105 -0
- package/README.md +27 -0
- package/dist/index.cjs +21240 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3599 -0
- package/dist/index.d.ts +3599 -0
- package/dist/index.js +20854 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
- package/src/__tests__/__snapshots__/prompt-builder-segments.test.ts.snap +65 -0
- package/src/__tests__/action-fx.test.ts +155 -0
- package/src/__tests__/apply-picker-json.test.ts +50 -0
- package/src/__tests__/assemble-image-input.test.ts +248 -0
- package/src/__tests__/assemble-suno-input.test.ts +283 -0
- package/src/__tests__/brand-tokens.test.ts +81 -0
- package/src/__tests__/build-image-prompt-element-injection.test.ts +118 -0
- package/src/__tests__/build-image-prompt-hybrid-format.test.ts +131 -0
- package/src/__tests__/build-image-prompt-mentions.test.ts +809 -0
- package/src/__tests__/build-image-prompt-reference-cap.test.ts +57 -0
- package/src/__tests__/build-image-prompt-reference-numbering.test.ts +237 -0
- package/src/__tests__/build-image-prompt-reference-order.test.ts +313 -0
- package/src/__tests__/camera-motions-from-connections.test.ts +71 -0
- package/src/__tests__/catalog-gapfill.test.ts +53 -0
- package/src/__tests__/character-convergence-image.test.ts +217 -0
- package/src/__tests__/character-default-role-image.test.ts +167 -0
- package/src/__tests__/character-default-role-video.test.ts +168 -0
- package/src/__tests__/character-default-role.test.ts +70 -0
- package/src/__tests__/character-fx.test.ts +200 -0
- package/src/__tests__/entity-prompts-location.test.ts +97 -0
- package/src/__tests__/entity-prompts.test.ts +240 -0
- package/src/__tests__/expand-extra-refs-role.test.ts +50 -0
- package/src/__tests__/factory-presets.test.ts +1045 -0
- package/src/__tests__/factory-snippets.test.ts +68 -0
- package/src/__tests__/framing-multi.test.ts +71 -0
- package/src/__tests__/framing-vantage.test.ts +39 -0
- package/src/__tests__/i18n-entry-completeness.test.ts +269 -0
- package/src/__tests__/identity-lock.test.ts +46 -0
- package/src/__tests__/instrumentation.test.ts +68 -0
- package/src/__tests__/lighting-multi.test.ts +68 -0
- package/src/__tests__/location-convergence-image.test.ts +124 -0
- package/src/__tests__/mention-lock-flag.test.ts +499 -0
- package/src/__tests__/multi-picker-spec.test.ts +71 -0
- package/src/__tests__/music-genre.test.ts +103 -0
- package/src/__tests__/music-mood.test.ts +87 -0
- package/src/__tests__/object-creature-convergence-image.test.ts +105 -0
- package/src/__tests__/parameter-prompt-hint.test.ts +270 -0
- package/src/__tests__/parameter-registry-sync.test.ts +243 -0
- package/src/__tests__/person-age.test.ts +80 -0
- package/src/__tests__/person-analyzer-invariants.test.ts +47 -0
- package/src/__tests__/person-body-axes.test.ts +67 -0
- package/src/__tests__/person-facial-geometry.test.ts +156 -0
- package/src/__tests__/person-regional-aesthetic.test.ts +161 -0
- package/src/__tests__/person-sections.test.ts +18 -0
- package/src/__tests__/picker-analyzer-registry.test.ts +108 -0
- package/src/__tests__/picker-catalogs-project.test.ts +85 -0
- package/src/__tests__/picker-catalogs.test.ts +53 -0
- package/src/__tests__/picker-limits.test.ts +20 -0
- package/src/__tests__/prompt-builder-segments.test.ts +183 -0
- package/src/__tests__/prompt-builder-structured-fields.test.ts +40 -0
- package/src/__tests__/prompt-builder.test.ts +1773 -0
- package/src/__tests__/prompt-wizard-categories.test.ts +31 -0
- package/src/__tests__/provider-prompt-doctrine.test.ts +49 -0
- package/src/__tests__/resolve-prompt-append.test.ts +58 -0
- package/src/__tests__/resolve-prompt.test.ts +44 -0
- package/src/__tests__/role-picker-shared.test.ts +208 -0
- package/src/__tests__/seedance-2-inputs.test.ts +173 -0
- package/src/__tests__/seedance-extend.test.ts +44 -0
- package/src/__tests__/sound-aggregator.test.ts +350 -0
- package/src/__tests__/style-presets.test.ts +34 -0
- package/src/__tests__/temporal-multi.test.ts +74 -0
- package/src/__tests__/transitions.test.ts +213 -0
- package/src/__tests__/video-reference-features.test.ts +41 -0
- package/src/__tests__/video-reference-leading-refs.test.ts +90 -0
- package/src/__tests__/video-reference-resolver.test.ts +233 -0
- package/src/__tests__/video-reference-roles.test.ts +96 -0
- package/src/__tests__/voice-character.test.ts +48 -0
- package/src/__tests__/voice-delivery.test.ts +41 -0
- package/src/__tests__/wardrobe.test.ts +25 -0
- package/src/action-fx.ts +255 -0
- package/src/aesthetic.ts +435 -0
- package/src/assemble-image-input.ts +236 -0
- package/src/assemble-suno-input.ts +147 -0
- package/src/atmosphere.ts +104 -0
- package/src/backdrop.ts +131 -0
- package/src/brand-tokens.ts +154 -0
- package/src/camera-format.ts +76 -0
- package/src/camera-motions.ts +615 -0
- package/src/character-fx.ts +274 -0
- package/src/color-look.ts +103 -0
- package/src/composition-effects.ts +67 -0
- package/src/entity-prompts.ts +231 -0
- package/src/era.ts +292 -0
- package/src/exposure-settings.ts +142 -0
- package/src/factory-presets/generate-image.ts +1644 -0
- package/src/factory-presets/generate-video.ts +1116 -0
- package/src/factory-presets/index.ts +46 -0
- package/src/factory-presets/lottie-overlay.ts +166 -0
- package/src/factory-presets/motion-graphics.ts +350 -0
- package/src/factory-presets/music.ts +734 -0
- package/src/factory-presets/sfx.ts +136 -0
- package/src/factory-presets/shared-image.ts +207 -0
- package/src/factory-presets/switchx.ts +65 -0
- package/src/factory-presets/text.ts +292 -0
- package/src/factory-presets/types.ts +51 -0
- package/src/factory-presets/video-edit.ts +136 -0
- package/src/factory-presets/voice.ts +172 -0
- package/src/factory-presets.ts +2 -0
- package/src/factory-snippets/catalog.ts +105 -0
- package/src/factory-snippets/index.ts +16 -0
- package/src/factory-snippets/types.ts +33 -0
- package/src/framing.ts +634 -0
- package/src/held-prop.ts +187 -0
- package/src/identity-lock.ts +213 -0
- package/src/index.ts +66 -0
- package/src/instrumentation.ts +337 -0
- package/src/lens.ts +59 -0
- package/src/lighting.ts +229 -0
- package/src/loop-subject.ts +276 -0
- package/src/materials.ts +184 -0
- package/src/mood.ts +186 -0
- package/src/music-genre.ts +662 -0
- package/src/music-mood.ts +137 -0
- package/src/object-asset-presets.ts +81 -0
- package/src/parameter-prompt-hint.ts +281 -0
- package/src/person.ts +1368 -0
- package/src/photo-genre.ts +151 -0
- package/src/photographer.ts +612 -0
- package/src/picker-analyzer-registry.ts +374 -0
- package/src/picker-catalogs.ts +858 -0
- package/src/pose.ts +246 -0
- package/src/post-process-effects.ts +94 -0
- package/src/prompt-builder-structured-fields.ts +116 -0
- package/src/prompt-builder.ts +2964 -0
- package/src/prompt-templates.ts +50 -0
- package/src/prompt-wizard-categories.ts +334 -0
- package/src/provider-prompt-doctrine.ts +85 -0
- package/src/render-quality.ts +89 -0
- package/src/resolve-prompt.ts +120 -0
- package/src/seedance-2-inputs.ts +100 -0
- package/src/setting.ts +130 -0
- package/src/sound-aggregator.ts +241 -0
- package/src/style-presets.ts +162 -0
- package/src/style.ts +99 -0
- package/src/styling.ts +585 -0
- package/src/temporal.ts +151 -0
- package/src/transitions.ts +333 -0
- package/src/video-reference-resolver.ts +823 -0
- package/src/voice-character.ts +237 -0
- package/src/voice-delivery.ts +142 -0
- package/src/wardrobe.ts +179 -0
package/src/framing.ts
ADDED
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical catalog of framing / shot composition choices.
|
|
3
|
+
*
|
|
4
|
+
* Complements `camera-motions.ts`: motion is HOW the camera moves, framing is
|
|
5
|
+
* WHAT the camera sees and from WHICH angle. They're independent dimensions
|
|
6
|
+
* of a shot — users can pick both (e.g. "Dolly In" + "Medium Close-up").
|
|
7
|
+
*
|
|
8
|
+
* Shared between the picker UI and the prompt-hint injection on both the
|
|
9
|
+
* frontend DAG executor and the backend orchestrator.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type FramingCategory =
|
|
13
|
+
| "shot-size"
|
|
14
|
+
| "angle"
|
|
15
|
+
| "coverage"
|
|
16
|
+
| "composition"
|
|
17
|
+
| "vantage"
|
|
18
|
+
|
|
19
|
+
export interface Framing {
|
|
20
|
+
readonly id: string
|
|
21
|
+
readonly label: string
|
|
22
|
+
readonly category: FramingCategory
|
|
23
|
+
readonly description: string
|
|
24
|
+
readonly promptHint: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const FRAMINGS: ReadonlyArray<Framing> = [
|
|
28
|
+
// Shot size (how much of the subject is in frame)
|
|
29
|
+
{
|
|
30
|
+
id: "extreme-wide-shot",
|
|
31
|
+
label: "Extreme Wide Shot",
|
|
32
|
+
category: "shot-size",
|
|
33
|
+
description: "Subject tiny in vast environment",
|
|
34
|
+
promptHint: "extreme wide shot, subject very small within a vast landscape, environment dominates",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "wide-shot",
|
|
38
|
+
label: "Wide Shot",
|
|
39
|
+
category: "shot-size",
|
|
40
|
+
description: "Full body with surroundings",
|
|
41
|
+
promptHint: "wide shot, full body of the subject with visible surroundings",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "medium-wide-shot",
|
|
45
|
+
label: "Medium Wide",
|
|
46
|
+
category: "shot-size",
|
|
47
|
+
description: "Subject from the knees up",
|
|
48
|
+
promptHint: "medium wide shot, subject framed from the knees up",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "medium-shot",
|
|
52
|
+
label: "Medium Shot",
|
|
53
|
+
category: "shot-size",
|
|
54
|
+
description: "Subject from the waist up",
|
|
55
|
+
promptHint: "medium shot, subject framed from the waist up",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: "medium-close-up",
|
|
59
|
+
label: "Medium Close-up",
|
|
60
|
+
category: "shot-size",
|
|
61
|
+
description: "Subject from the chest up",
|
|
62
|
+
promptHint: "medium close-up, subject framed from the chest up",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "close-up",
|
|
66
|
+
label: "Close-up",
|
|
67
|
+
category: "shot-size",
|
|
68
|
+
description: "Subject's face filling frame",
|
|
69
|
+
promptHint: "close-up shot, the subject's face fills the frame",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "extreme-close-up",
|
|
73
|
+
label: "Extreme Close-up",
|
|
74
|
+
category: "shot-size",
|
|
75
|
+
description: "Tight detail of face feature",
|
|
76
|
+
promptHint: "extreme close-up, tight on a single feature of the face such as the eyes or mouth",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "ecu-eye",
|
|
80
|
+
label: "ECU: Eye",
|
|
81
|
+
category: "shot-size",
|
|
82
|
+
description: "Sergio Leone-style ECU on a single eye",
|
|
83
|
+
promptHint: "Sergio Leone-style extreme close-up tight on a single eye, iris filling the frame, every eyelash and reflection visible",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "ecu-mouth",
|
|
87
|
+
label: "ECU: Lips/Mouth",
|
|
88
|
+
category: "shot-size",
|
|
89
|
+
description: "ECU tight on the mouth",
|
|
90
|
+
promptHint: "extreme close-up tight on the mouth, lips and teeth filling the frame, breath visible",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "ecu-hands",
|
|
94
|
+
label: "ECU: Hands",
|
|
95
|
+
category: "shot-size",
|
|
96
|
+
description: "ECU tight on the hands",
|
|
97
|
+
promptHint: "extreme close-up tight on the hands, every finger and crease visible",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: "big-close-up",
|
|
101
|
+
label: "Big Close-up (BCU)",
|
|
102
|
+
category: "shot-size",
|
|
103
|
+
description: "Tighter than CU: chin to forehead, no headroom",
|
|
104
|
+
promptHint: "big close-up, tighter than a standard close-up with just the face from chin to forehead, no headroom",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "choker",
|
|
108
|
+
label: "Choker Shot",
|
|
109
|
+
category: "shot-size",
|
|
110
|
+
description: "Head and neck only, intimate intensity",
|
|
111
|
+
promptHint: "choker shot, framed at the throat with head and neck only, intimate intensity",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: "italian-shot",
|
|
115
|
+
label: "Italian Shot / Trinity Shot",
|
|
116
|
+
category: "shot-size",
|
|
117
|
+
description: "Sergio Leone Western: ECU at the eyes alone",
|
|
118
|
+
promptHint: "Italian shot, Sergio Leone Western trope, extreme close-up cropping at the eyes alone leaving the rest of the face out of frame",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: "insert",
|
|
122
|
+
label: "Insert",
|
|
123
|
+
category: "shot-size",
|
|
124
|
+
description: "Detail shot of an object",
|
|
125
|
+
promptHint: "insert shot, tight on a specific object or detail relevant to the scene",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "macro",
|
|
129
|
+
label: "Macro",
|
|
130
|
+
category: "shot-size",
|
|
131
|
+
description: "Extreme close detail of a small subject",
|
|
132
|
+
promptHint: "macro shot, extreme close-up of fine detail filling the frame, magnifying small subject features",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: "full-shot",
|
|
136
|
+
label: "Full Shot",
|
|
137
|
+
category: "shot-size",
|
|
138
|
+
description: "Whole body head-to-toe in frame",
|
|
139
|
+
promptHint: "full shot, the subject's entire body framed head to toe with minimal headroom and footroom",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: "cowboy-shot",
|
|
143
|
+
label: "Cowboy Shot",
|
|
144
|
+
category: "shot-size",
|
|
145
|
+
description: "Mid-thigh up, classic Western framing",
|
|
146
|
+
promptHint: "cowboy shot, subject framed from mid-thigh up, classic Western framing that leaves the holster visible",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: "head-to-knees",
|
|
150
|
+
label: "Head to Knees",
|
|
151
|
+
category: "shot-size",
|
|
152
|
+
description: "From head down to the knees",
|
|
153
|
+
promptHint: "head-to-knees framing, subject visible from the top of the head down to just above the knees",
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: "head-to-hip",
|
|
157
|
+
label: "Head to Hip",
|
|
158
|
+
category: "shot-size",
|
|
159
|
+
description: "From head down to the hips",
|
|
160
|
+
promptHint: "head-to-hip framing, subject visible from the top of the head down to the hipline, slightly tighter than waist-up",
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: "half-body",
|
|
164
|
+
label: "Half Body",
|
|
165
|
+
category: "shot-size",
|
|
166
|
+
description: "Clean waist-up portrait",
|
|
167
|
+
promptHint: "half body portrait, subject framed from the waist up with clean portrait composition",
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
// Angle (camera height / orientation)
|
|
171
|
+
{
|
|
172
|
+
id: "eye-level",
|
|
173
|
+
label: "Eye Level",
|
|
174
|
+
category: "angle",
|
|
175
|
+
description: "Camera at subject's eye height",
|
|
176
|
+
promptHint: "eye level shot, camera at the same height as the subject's eyes, neutral perspective",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: "high-angle",
|
|
180
|
+
label: "High Angle",
|
|
181
|
+
category: "angle",
|
|
182
|
+
description: "Camera above subject looking down",
|
|
183
|
+
promptHint: "high angle shot, camera positioned above the subject looking down at them",
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: "low-angle",
|
|
187
|
+
label: "Low Angle",
|
|
188
|
+
category: "angle",
|
|
189
|
+
description: "Camera below subject looking up",
|
|
190
|
+
promptHint: "low angle shot, camera positioned below the subject looking up, making them feel powerful",
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: "overhead",
|
|
194
|
+
label: "Overhead",
|
|
195
|
+
category: "angle",
|
|
196
|
+
description: "Direct top-down god's eye view",
|
|
197
|
+
promptHint: "overhead shot, direct top-down god's eye view looking straight down at the scene",
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: "worms-eye-angle",
|
|
201
|
+
label: "Worm's Eye",
|
|
202
|
+
category: "angle",
|
|
203
|
+
description: "Extreme low angle from the ground",
|
|
204
|
+
promptHint: "worm's eye view, extreme low angle from ground level looking up",
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: "dutch-angle",
|
|
208
|
+
label: "Dutch Angle",
|
|
209
|
+
category: "angle",
|
|
210
|
+
description: "Tilted canted horizon",
|
|
211
|
+
promptHint: "Dutch angle shot, the camera rolled so the horizon is canted, conveying tension or unease",
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: "birds-eye",
|
|
215
|
+
label: "Bird's Eye",
|
|
216
|
+
category: "angle",
|
|
217
|
+
description: "High aerial overhead view",
|
|
218
|
+
promptHint: "bird's eye view, high aerial perspective looking down on the scene from far above",
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: "slightly-downward",
|
|
222
|
+
label: "Slightly Downward",
|
|
223
|
+
category: "angle",
|
|
224
|
+
description: "Gentle tilt from above, selfie-style",
|
|
225
|
+
promptHint: "slightly downward angle, camera tilted gently down toward the subject from just above their eyeline, the natural angle of a held-out phone",
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
// Coverage (dialog / multi-subject framing)
|
|
229
|
+
{
|
|
230
|
+
id: "single",
|
|
231
|
+
label: "Single",
|
|
232
|
+
category: "coverage",
|
|
233
|
+
description: "Clean shot of one subject",
|
|
234
|
+
promptHint: "single shot, clean framing of one subject with nothing else in frame",
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: "two-shot",
|
|
238
|
+
label: "Two-Shot",
|
|
239
|
+
category: "coverage",
|
|
240
|
+
description: "Both subjects in frame",
|
|
241
|
+
promptHint: "two-shot, both subjects in frame at roughly equal prominence",
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: "three-shot",
|
|
245
|
+
label: "Three-Shot",
|
|
246
|
+
category: "coverage",
|
|
247
|
+
description: "Three subjects in frame",
|
|
248
|
+
promptHint: "three-shot, three subjects in the same frame",
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: "over-the-shoulder-framing",
|
|
252
|
+
label: "Over The Shoulder",
|
|
253
|
+
category: "coverage",
|
|
254
|
+
description: "Past one subject's shoulder onto another",
|
|
255
|
+
promptHint: "over the shoulder framing, camera looks past one character's shoulder onto the subject opposite them",
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: "reverse-shot",
|
|
259
|
+
label: "Reverse Shot",
|
|
260
|
+
category: "coverage",
|
|
261
|
+
description: "Opposite POV to previous shot",
|
|
262
|
+
promptHint: "reverse shot, camera on the opposite side of the conversation from the previous framing",
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: "pov-framing",
|
|
266
|
+
label: "POV",
|
|
267
|
+
category: "coverage",
|
|
268
|
+
description: "Through subject's eyes",
|
|
269
|
+
promptHint: "POV framing, scene viewed through the subject's own eyes, first person perspective",
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
id: "selfie-framing",
|
|
273
|
+
label: "Selfie",
|
|
274
|
+
category: "coverage",
|
|
275
|
+
description: "Arm's-length self-portrait",
|
|
276
|
+
promptHint: "selfie framing, subject holding the camera at arm's length to frame themselves, slightly high angle from above, phone-camera perspective with the subject's face and upper body dominant in frame, arm or phone edge sometimes visible",
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: "mirror-selfie",
|
|
280
|
+
label: "Mirror Selfie",
|
|
281
|
+
category: "coverage",
|
|
282
|
+
description: "Phone visible in a mirror reflection",
|
|
283
|
+
promptHint: "mirror selfie composition, the subject photographs themselves in a mirror with the phone clearly visible in front of their face or chest, the reflection itself is the photograph, often a slight phone-flash bloom",
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: "gym-mirror-selfie",
|
|
287
|
+
label: "Gym Mirror Selfie",
|
|
288
|
+
category: "coverage",
|
|
289
|
+
description: "3/4 side-back angle through a gym mirror",
|
|
290
|
+
promptHint: "gym mirror selfie, subject photographed in a full-length gym mirror from a three-quarter side-back angle, phone held near the chest or hip, fluorescent gym lighting and equipment in the background",
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: "through-glass",
|
|
294
|
+
label: "Through Glass",
|
|
295
|
+
category: "coverage",
|
|
296
|
+
description: "Framed through a foreground glass pane",
|
|
297
|
+
promptHint: "shot through a foreground pane of glass, subject seen with subtle optical refraction, reflections, and surface distortions across the image, glass texture frames the composition",
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
id: "top-down-flat-lay",
|
|
301
|
+
label: "Top-down Flat Lay",
|
|
302
|
+
category: "coverage",
|
|
303
|
+
description: "Overhead arrangement of items on a surface",
|
|
304
|
+
promptHint: "top-down flat lay composition, camera directly overhead looking down at items neatly arranged on a flat surface, every object parallel to the picture plane",
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
id: "establishing-shot",
|
|
308
|
+
label: "Establishing Shot",
|
|
309
|
+
category: "coverage",
|
|
310
|
+
description: "Wide environmental shot, subject small",
|
|
311
|
+
promptHint: "establishing shot, wide environmental framing that prioritizes the location, the subject sits small within the larger scene to set context",
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
id: "dirty-single",
|
|
315
|
+
label: "Dirty Single",
|
|
316
|
+
category: "coverage",
|
|
317
|
+
description: "Single with another character at the edge",
|
|
318
|
+
promptHint: "dirty single framing, the shot is on one character but a slice of another character — a shoulder, ear, or back of head — intrudes into the foreground edge of frame",
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
// Composition (where the subject sits in the frame)
|
|
322
|
+
{
|
|
323
|
+
id: "rule-of-thirds",
|
|
324
|
+
label: "Rule of Thirds",
|
|
325
|
+
category: "composition",
|
|
326
|
+
description: "Subject on a thirds intersection",
|
|
327
|
+
promptHint: "rule of thirds composition, subject placed on a thirds intersection for natural balance",
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
id: "centered",
|
|
331
|
+
label: "Centered",
|
|
332
|
+
category: "composition",
|
|
333
|
+
description: "Subject dead center, symmetrical",
|
|
334
|
+
promptHint: "centered composition, subject positioned exactly in the middle of the frame, symmetrical",
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
id: "headroom-tight",
|
|
338
|
+
label: "Headroom Tight",
|
|
339
|
+
category: "composition",
|
|
340
|
+
description: "Subject's head near top of frame",
|
|
341
|
+
promptHint: "tight headroom, subject's head positioned near the top of the frame with little space above",
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
id: "negative-space",
|
|
345
|
+
label: "Negative Space",
|
|
346
|
+
category: "composition",
|
|
347
|
+
description: "Subject offset with empty space",
|
|
348
|
+
promptHint: "negative space composition, subject offset to one side with a large area of empty space opposite",
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
id: "leading-lines",
|
|
352
|
+
label: "Leading Lines",
|
|
353
|
+
category: "composition",
|
|
354
|
+
description: "Lines draw eye to subject",
|
|
355
|
+
promptHint: "leading lines composition, environmental lines converge toward the subject to draw the viewer's eye",
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
id: "3x3-grid-collage",
|
|
359
|
+
label: "3×3 Grid Collage",
|
|
360
|
+
category: "composition",
|
|
361
|
+
description: "Subject in a 3×3 grid of variations",
|
|
362
|
+
promptHint: "3 by 3 grid collage layout, nine equal-sized panels arranged in a three-by-three grid, each cell shows the same subject in a different pose, expression, or outfit, clean white gutters between panels",
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
id: "diptych",
|
|
366
|
+
label: "Diptych",
|
|
367
|
+
category: "composition",
|
|
368
|
+
description: "Two-frame side-by-side composition",
|
|
369
|
+
promptHint: "diptych composition, two equal panels placed side by side as a single image, each panel a separate but related shot of the subject",
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
id: "triptych",
|
|
373
|
+
label: "Triptych",
|
|
374
|
+
category: "composition",
|
|
375
|
+
description: "Three-frame composition",
|
|
376
|
+
promptHint: "triptych composition, three equal vertical panels arranged side by side as one image, each panel a related but distinct shot of the subject",
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
id: "multi-frame-mosaic",
|
|
380
|
+
label: "Multi-frame Mosaic",
|
|
381
|
+
category: "composition",
|
|
382
|
+
description: "Face built from a mosaic of small tiles",
|
|
383
|
+
promptHint: "multi-frame mosaic, the subject's face assembled from many small photographic tiles arranged in a grid, each tile a tiny image that combines from a distance into the larger portrait",
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
id: "contact-sheet",
|
|
387
|
+
label: "Contact Sheet",
|
|
388
|
+
category: "composition",
|
|
389
|
+
description: "Photo contact sheet of thumbnails",
|
|
390
|
+
promptHint: "contact sheet layout, multiple sequential thumbnail frames arranged in rows on a black or dark grey background, frame numbers along the edge, mimicking a 35mm film proof sheet",
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
id: "magazine-spread",
|
|
394
|
+
label: "Magazine Spread",
|
|
395
|
+
category: "composition",
|
|
396
|
+
description: "Two-page magazine layout with typography",
|
|
397
|
+
promptHint: "magazine spread layout, two-page editorial composition with bold display typography overlaid on the image, headline and pull quotes integrated with the photograph, visible page gutter down the middle",
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
id: "cutaway-cross-section",
|
|
401
|
+
label: "Cutaway / Cross-Section",
|
|
402
|
+
category: "composition",
|
|
403
|
+
description: "Architectural cross-section with walls peeled away",
|
|
404
|
+
promptHint: "cutaway cross-section composition, the building's near wall peeled away to reveal the interior in dollhouse fashion, multiple rooms visible at once with floors and furnishings exposed in clean architectural section, the subject inhabiting one of the rooms",
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
id: "golden-spiral",
|
|
408
|
+
label: "Golden Spiral / Fibonacci",
|
|
409
|
+
category: "composition",
|
|
410
|
+
description: "Composition on the Fibonacci spiral",
|
|
411
|
+
promptHint: "composed on the golden spiral / Fibonacci ratio, the eye guided through a nested logarithmic curve from the focal subject outward through progressively larger arcs",
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
id: "frame-within-frame",
|
|
415
|
+
label: "Frame Within a Frame",
|
|
416
|
+
category: "composition",
|
|
417
|
+
description: "Subject framed by an interior architectural element",
|
|
418
|
+
promptHint: "frame within a frame composition, the subject framed by an interior architectural element such as a doorway, window, or archway that creates a secondary border around the focal point",
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
id: "s-curve",
|
|
422
|
+
label: "S-Curve / Serpentine",
|
|
423
|
+
category: "composition",
|
|
424
|
+
description: "Sinuous diagonal flow through the frame",
|
|
425
|
+
promptHint: "S-curve serpentine composition, a sinuous diagonal flow winds through the frame, the eye guided along the curve from foreground to background",
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
id: "diagonal-composition",
|
|
429
|
+
label: "Diagonal Composition",
|
|
430
|
+
category: "composition",
|
|
431
|
+
description: "Strong diagonal line cutting across the frame",
|
|
432
|
+
promptHint: "diagonal composition, a strong diagonal line cuts across the frame from corner toward corner, generating dynamism and forward momentum",
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
id: "triangular-composition",
|
|
436
|
+
label: "Triangular Composition",
|
|
437
|
+
category: "composition",
|
|
438
|
+
description: "Three-point triangular arrangement",
|
|
439
|
+
promptHint: "triangular composition, three-point triangular arrangement of subjects or elements, the implicit triangle anchors the frame and balances visual weight",
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
id: "symmetrical-mirror",
|
|
443
|
+
label: "Symmetrical / Mirror",
|
|
444
|
+
category: "composition",
|
|
445
|
+
description: "Exact left-right symmetry",
|
|
446
|
+
promptHint: "symmetrical mirror composition, exact left-right symmetry across a central vertical axis, both halves of the frame mirror each other in shape, mass, and tone",
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
id: "vignette-composition",
|
|
450
|
+
label: "Vignette Composition",
|
|
451
|
+
category: "composition",
|
|
452
|
+
description: "Heavy peripheral darkening focuses the center",
|
|
453
|
+
promptHint: "vignette composition, heavy peripheral darkening of the frame edges and corners draws attention inward to the centered subject, the falloff is built into the staging and lighting rather than added in post",
|
|
454
|
+
},
|
|
455
|
+
|
|
456
|
+
// Vantage (horizontal camera direction — azimuth around subject)
|
|
457
|
+
{
|
|
458
|
+
id: "front-on",
|
|
459
|
+
label: "Front-on",
|
|
460
|
+
category: "vantage",
|
|
461
|
+
description: "Subject facing camera",
|
|
462
|
+
promptHint: "front-on shot, subject facing the camera directly",
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
id: "three-quarter-front",
|
|
466
|
+
label: "Three-Quarter Front",
|
|
467
|
+
category: "vantage",
|
|
468
|
+
description: "Slightly off-axis from front",
|
|
469
|
+
promptHint: "three-quarter view of the subject, camera slightly off-axis from the front",
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
id: "profile-left",
|
|
473
|
+
label: "Profile Left",
|
|
474
|
+
category: "vantage",
|
|
475
|
+
description: "Side view, subject's left",
|
|
476
|
+
promptHint: "profile shot from the subject's left side",
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
id: "profile-right",
|
|
480
|
+
label: "Profile Right",
|
|
481
|
+
category: "vantage",
|
|
482
|
+
description: "Side view, subject's right",
|
|
483
|
+
promptHint: "profile shot from the subject's right side",
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
id: "three-quarter-back",
|
|
487
|
+
label: "Three-Quarter Back",
|
|
488
|
+
category: "vantage",
|
|
489
|
+
description: "Off-axis from behind",
|
|
490
|
+
promptHint: "three-quarter view from behind, camera angled off-axis from the rear",
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
id: "behind",
|
|
494
|
+
label: "Behind",
|
|
495
|
+
category: "vantage",
|
|
496
|
+
description: "Direct rear view",
|
|
497
|
+
promptHint: "shot from directly behind the subject, looking at their back",
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
id: "side-back-angle",
|
|
501
|
+
label: "Side-Back Angle",
|
|
502
|
+
category: "vantage",
|
|
503
|
+
description: "3/4 view from behind one shoulder",
|
|
504
|
+
promptHint: "side-back angle, three-quarter view from behind one shoulder of the subject, partial face visible in profile, the back and shoulder line dominate the framing",
|
|
505
|
+
},
|
|
506
|
+
]
|
|
507
|
+
|
|
508
|
+
export const FRAMING_CATEGORY_ORDER: ReadonlyArray<FramingCategory> = [
|
|
509
|
+
"shot-size",
|
|
510
|
+
"angle",
|
|
511
|
+
"coverage",
|
|
512
|
+
"composition",
|
|
513
|
+
"vantage",
|
|
514
|
+
]
|
|
515
|
+
|
|
516
|
+
export const FRAMING_CATEGORY_LABELS: Record<FramingCategory, string> = {
|
|
517
|
+
"shot-size": "Shot Size",
|
|
518
|
+
angle: "Angle",
|
|
519
|
+
coverage: "Coverage",
|
|
520
|
+
composition: "Composition",
|
|
521
|
+
vantage: "Vantage",
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const framingById = new Map<string, Framing>(FRAMINGS.map((f) => [f.id, f]))
|
|
525
|
+
|
|
526
|
+
export function getFraming(id: string | undefined | null): Framing | undefined {
|
|
527
|
+
if (!id) return undefined
|
|
528
|
+
return framingById.get(id)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export function getFramingLabel(id: string | undefined | null, fallback?: string): string {
|
|
532
|
+
const f = getFraming(id)
|
|
533
|
+
if (f) return f.label
|
|
534
|
+
if (fallback !== undefined) return fallback
|
|
535
|
+
return (id ?? "").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export function getFramingPromptHint(id: string | undefined | null): string {
|
|
539
|
+
return getFraming(id)?.promptHint ?? ""
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export const FRAMING_IDS: ReadonlyArray<string> = FRAMINGS.map((f) => f.id)
|
|
543
|
+
|
|
544
|
+
export function isVantageFraming(id: string | undefined | null): boolean {
|
|
545
|
+
return getFraming(id)?.category === "vantage"
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Maps each FramingCategory to the consumer data field name that holds the
|
|
550
|
+
* selected entry id for that category. Multi-category framing: a consumer
|
|
551
|
+
* (image/video) can independently set a value in each of the 5 dimensions.
|
|
552
|
+
*/
|
|
553
|
+
export const FRAMING_FIELD_BY_CATEGORY: Record<
|
|
554
|
+
FramingCategory,
|
|
555
|
+
"shotSize" | "angle" | "coverage" | "composition" | "vantage"
|
|
556
|
+
> = {
|
|
557
|
+
"shot-size": "shotSize",
|
|
558
|
+
angle: "angle",
|
|
559
|
+
coverage: "coverage",
|
|
560
|
+
composition: "composition",
|
|
561
|
+
vantage: "vantage",
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** Per-category multi-select cap. composition supports 2 picks
|
|
565
|
+
* (rule-of-thirds + leading-lines, centered + negative-space). All other
|
|
566
|
+
* categories single-select (absent → 1). */
|
|
567
|
+
export const MAX_SELECTED_BY_FRAMING_CATEGORY: Partial<Record<FramingCategory, number>> = {
|
|
568
|
+
composition: 2,
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/** Selection limit for a framing category (1 when not multi-select). */
|
|
572
|
+
export function getFramingCategoryLimit(category: FramingCategory): number {
|
|
573
|
+
return MAX_SELECTED_BY_FRAMING_CATEGORY[category] ?? 1
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Shape of the per-category framing fields on FramingData and all 9 consumer
|
|
578
|
+
* data types. All fields optional — user may set zero, one, or all categories.
|
|
579
|
+
*/
|
|
580
|
+
export interface FramingValue {
|
|
581
|
+
shotSize?: string
|
|
582
|
+
angle?: string
|
|
583
|
+
coverage?: string
|
|
584
|
+
/** Composition — single id or up to 2 ids for layered compositions
|
|
585
|
+
* (e.g. ["rule-of-thirds", "leading-lines"], ["centered", "negative-space"]). */
|
|
586
|
+
composition?: string | ReadonlyArray<string>
|
|
587
|
+
vantage?: string
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Aggregate all enabled per-category framing prompt hints from a consumer's
|
|
592
|
+
* data, in canonical category order (shot-size, angle, coverage, composition,
|
|
593
|
+
* vantage).
|
|
594
|
+
*
|
|
595
|
+
* Accepts a loosely typed record (the helper is shared between strongly typed
|
|
596
|
+
* frontend node data and the backend's `Record<string, unknown>` workflow
|
|
597
|
+
* data). Non-string values are ignored.
|
|
598
|
+
*
|
|
599
|
+
* @param data the consumer data record (must include optional shotSize / angle
|
|
600
|
+
* / coverage / composition / vantage fields)
|
|
601
|
+
* @param skipVantage when true, skip the vantage hint (used by video consumers
|
|
602
|
+
* where camera-motion v2 already declares spatial positioning — keeps the
|
|
603
|
+
* pre-existing conflict-resolution behavior identical).
|
|
604
|
+
*/
|
|
605
|
+
export function buildFramingHints(
|
|
606
|
+
data: Record<string, unknown> & {
|
|
607
|
+
shotSize?: unknown
|
|
608
|
+
angle?: unknown
|
|
609
|
+
coverage?: unknown
|
|
610
|
+
composition?: unknown
|
|
611
|
+
vantage?: unknown
|
|
612
|
+
},
|
|
613
|
+
skipVantage = false,
|
|
614
|
+
): string[] {
|
|
615
|
+
const hints: string[] = []
|
|
616
|
+
for (const category of FRAMING_CATEGORY_ORDER) {
|
|
617
|
+
if (category === "vantage" && skipVantage) continue
|
|
618
|
+
const field = FRAMING_FIELD_BY_CATEGORY[category]
|
|
619
|
+
const raw = data[field]
|
|
620
|
+
// composition accepts string | string[] (multi-pick max 2). Other
|
|
621
|
+
// categories are single-pick; arrays are tolerated defensively.
|
|
622
|
+
if (typeof raw === "string" && raw.length > 0) {
|
|
623
|
+
const hint = getFramingPromptHint(raw)
|
|
624
|
+
if (hint) hints.push(hint)
|
|
625
|
+
} else if (Array.isArray(raw)) {
|
|
626
|
+
for (const item of raw) {
|
|
627
|
+
if (typeof item !== "string" || item.length === 0) continue
|
|
628
|
+
const hint = getFramingPromptHint(item)
|
|
629
|
+
if (hint) hints.push(hint)
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return hints
|
|
634
|
+
}
|