@clipwright/core 0.36.0 → 0.37.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/actors.d.ts +8 -0
- package/dist/actors.js +2 -0
- package/dist/actors.js.map +1 -1
- package/dist/caption-geometry.d.ts +43 -0
- package/dist/caption-geometry.js +41 -0
- package/dist/caption-geometry.js.map +1 -0
- package/dist/contract-dispositions.d.ts +997 -35
- package/dist/contract-dispositions.js +25 -5
- package/dist/contract-dispositions.js.map +1 -1
- package/dist/contracts/v1/workflow-brief.d.ts +2024 -38
- package/dist/contracts/v1/workflow-brief.js +2 -2
- package/dist/contracts/v1/workflow-brief.js.map +1 -1
- package/dist/contracts/v1/workflow-catalog.d.ts +2154 -72
- package/dist/contracts/v1/workflow-client.d.ts +1207 -70
- package/dist/contracts/v1/workflow-plan.d.ts +3234 -111
- package/dist/edit-billing.d.ts +1 -0
- package/dist/edit-billing.js +2 -0
- package/dist/edit-billing.js.map +1 -1
- package/dist/edit-document.d.ts +22 -0
- package/dist/edit-document.js +3 -0
- package/dist/edit-document.js.map +1 -1
- package/dist/edit-long-form.js +1 -1
- package/dist/edit-long-form.js.map +1 -1
- package/dist/edit-revision.d.ts +9 -0
- package/dist/edit-rules.d.ts +1 -0
- package/dist/edit-rules.js +3 -0
- package/dist/edit-rules.js.map +1 -1
- package/dist/edit-shots.d.ts +2 -0
- package/dist/edit-validate.d.ts +4 -2
- package/dist/edit-validate.js +18 -2
- package/dist/edit-validate.js.map +1 -1
- package/dist/errors.d.ts +1 -0
- package/dist/faceless-media-record.d.ts +27 -0
- package/dist/faceless-media-record.js +15 -1
- package/dist/faceless-media-record.js.map +1 -1
- package/dist/faceless-public.d.ts +107 -0
- package/dist/faceless-public.js +34 -1
- package/dist/faceless-public.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/long-form-revision.d.ts +8 -0
- package/dist/openapi.js +2 -2
- package/dist/openapi.js.map +1 -1
- package/dist/presenter-beats.d.ts +1 -1
- package/dist/reference-analysis.d.ts +436 -75
- package/dist/reference-analysis.js +149 -28
- package/dist/reference-analysis.js.map +1 -1
- package/dist/reference-display.d.ts +36 -0
- package/dist/reference-display.js +78 -0
- package/dist/reference-display.js.map +1 -0
- package/dist/reference-profile.d.ts +2352 -36
- package/dist/reference-profile.js +138 -10
- package/dist/reference-profile.js.map +1 -1
- package/dist/reference-staging.d.ts +59 -57
- package/dist/reference-staging.js +6 -5
- package/dist/reference-staging.js.map +1 -1
- package/dist/resolved-timeline.d.ts +199 -2
- package/dist/resolved-timeline.js +17 -5
- package/dist/resolved-timeline.js.map +1 -1
- package/dist/scene-place.d.ts +225 -0
- package/dist/scene-place.js +120 -0
- package/dist/scene-place.js.map +1 -0
- package/dist/skills.d.ts +4677 -22
- package/dist/skills.js +130 -17
- package/dist/skills.js.map +1 -1
- package/dist/templates/index.d.ts +14 -4
- package/dist/templates/index.js +49 -5
- package/dist/templates/index.js.map +1 -1
- package/dist/ugc-interview.d.ts +122 -0
- package/dist/ugc-interview.js +260 -0
- package/dist/ugc-interview.js.map +1 -0
- package/dist/ugc-layers.d.ts +88 -6
- package/dist/ugc-layers.js +193 -5
- package/dist/ugc-layers.js.map +1 -1
- package/dist/voice-admission.d.ts +947 -2
- package/dist/workflows/methods/faceless-explainer.d.ts +599 -13
- package/dist/workflows/methods/kinetic-launch.d.ts +599 -13
- package/dist/workflows/methods/product-image-story.d.ts +599 -13
- package/dist/workflows/methods/reference-remake.d.ts +599 -13
- package/dist/workflows/methods/shared.d.ts +599 -13
- package/dist/workflows/methods/ugc-faq-answer.d.ts +599 -13
- package/dist/workflows/methods/ugc-product-intro.d.ts +599 -13
- package/dist/workflows/methods/website-proof-demo.d.ts +599 -13
- package/dist/workflows/reference-remake.d.ts +131 -35
- package/dist/workflows/reference-remake.js +179 -18
- package/dist/workflows/reference-remake.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { type ReferenceProfile } from '../reference-profile.js';
|
|
2
|
+
import { type ReferenceProfileInput as ReferenceProfile } from '../reference-profile.js';
|
|
3
3
|
import type { CapabilityProfile } from '../contracts/v1/workflow-catalog.js';
|
|
4
4
|
import { type WorkflowPlanDocument } from '../contracts/v1/workflow-plan.js';
|
|
5
5
|
import { type WorkflowIssue } from '../contracts/v1/workflow-primitives.js';
|
|
@@ -159,6 +159,43 @@ export declare const planReferenceRemake: z.ZodObject<{
|
|
|
159
159
|
reacting: "reacting";
|
|
160
160
|
speaking: "speaking";
|
|
161
161
|
}>;
|
|
162
|
+
key_frame_s: z.ZodOptional<z.ZodNumber>;
|
|
163
|
+
frames_s: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
164
|
+
speaker: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
165
|
+
speakers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
166
|
+
id: z.ZodString;
|
|
167
|
+
seconds: z.ZodNumber;
|
|
168
|
+
}, z.core.$strict>>>;
|
|
169
|
+
gaze: z.ZodOptional<z.ZodObject<{
|
|
170
|
+
direction: z.ZodEnum<{
|
|
171
|
+
camera: "camera";
|
|
172
|
+
closed: "closed";
|
|
173
|
+
down: "down";
|
|
174
|
+
mixed: "mixed";
|
|
175
|
+
not_visible: "not_visible";
|
|
176
|
+
off_left: "off_left";
|
|
177
|
+
off_right: "off_right";
|
|
178
|
+
up: "up";
|
|
179
|
+
}>;
|
|
180
|
+
frames_s: z.ZodArray<z.ZodNumber>;
|
|
181
|
+
}, z.core.$strict>>;
|
|
182
|
+
gestures: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
183
|
+
kind: z.ZodEnum<{
|
|
184
|
+
both_hands_to_viewer: "both_hands_to_viewer";
|
|
185
|
+
count_on_fingers: "count_on_fingers";
|
|
186
|
+
hold_object: "hold_object";
|
|
187
|
+
open_palms: "open_palms";
|
|
188
|
+
other: "other";
|
|
189
|
+
point_at_element: "point_at_element";
|
|
190
|
+
point_at_viewer: "point_at_viewer";
|
|
191
|
+
shrug: "shrug";
|
|
192
|
+
thumbs_up: "thumbs_up";
|
|
193
|
+
wave: "wave";
|
|
194
|
+
}>;
|
|
195
|
+
at_s: z.ZodNumber;
|
|
196
|
+
label: z.ZodString;
|
|
197
|
+
frames_s: z.ZodArray<z.ZodNumber>;
|
|
198
|
+
}, z.core.$strict>>>;
|
|
162
199
|
}, z.core.$strict>>;
|
|
163
200
|
captions: z.ZodObject<{
|
|
164
201
|
present: z.ZodBoolean;
|
|
@@ -176,6 +213,12 @@ export declare const planReferenceRemake: z.ZodObject<{
|
|
|
176
213
|
punch: "punch";
|
|
177
214
|
}>;
|
|
178
215
|
hidden_under_graphics: z.ZodBoolean;
|
|
216
|
+
box: z.ZodOptional<z.ZodObject<{
|
|
217
|
+
left: z.ZodNumber;
|
|
218
|
+
top: z.ZodNumber;
|
|
219
|
+
width: z.ZodNumber;
|
|
220
|
+
height: z.ZodNumber;
|
|
221
|
+
}, z.core.$strict>>;
|
|
179
222
|
}, z.core.$strict>;
|
|
180
223
|
pacing: z.ZodObject<{
|
|
181
224
|
layer_change_every_s: z.ZodNumber;
|
|
@@ -188,12 +231,6 @@ export declare const planReferenceRemake: z.ZodObject<{
|
|
|
188
231
|
beat_index: z.ZodNumber;
|
|
189
232
|
suggested_substitute: z.ZodString;
|
|
190
233
|
}, z.core.$strict>>;
|
|
191
|
-
analysis: z.ZodObject<{
|
|
192
|
-
models: z.ZodArray<z.ZodString>;
|
|
193
|
-
cost_usd: z.ZodOptional<z.ZodNumber>;
|
|
194
|
-
latency_ms: z.ZodNumber;
|
|
195
|
-
warnings: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
196
|
-
}, z.core.$strict>;
|
|
197
234
|
voice: z.ZodOptional<z.ZodObject<{
|
|
198
235
|
version: z.ZodLiteral<"voice-profile-v1">;
|
|
199
236
|
method: z.ZodObject<{
|
|
@@ -390,13 +427,13 @@ export declare const planReferenceRemake: z.ZodObject<{
|
|
|
390
427
|
height: z.ZodNumber;
|
|
391
428
|
}, z.core.$strict>>;
|
|
392
429
|
rect_method: z.ZodOptional<z.ZodEnum<{
|
|
393
|
-
"
|
|
394
|
-
"
|
|
395
|
-
"
|
|
396
|
-
"
|
|
397
|
-
"
|
|
398
|
-
"
|
|
399
|
-
"
|
|
430
|
+
"estimated-still": "estimated-still";
|
|
431
|
+
"estimated-video": "estimated-video";
|
|
432
|
+
"frames-box/v1": "frames-box/v1";
|
|
433
|
+
"frames-face/v1": "frames-face/v1";
|
|
434
|
+
"frames-layer/v1": "frames-layer/v1";
|
|
435
|
+
"frames-motion/v1": "frames-motion/v1";
|
|
436
|
+
"frames-split/v1": "frames-split/v1";
|
|
400
437
|
}>>;
|
|
401
438
|
rects: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
402
439
|
t_s: z.ZodNumber;
|
|
@@ -441,13 +478,13 @@ export declare const planReferenceRemake: z.ZodObject<{
|
|
|
441
478
|
displacement: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
|
|
442
479
|
scale_from: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
443
480
|
method: z.ZodOptional<z.ZodEnum<{
|
|
444
|
-
"
|
|
445
|
-
"
|
|
446
|
-
"
|
|
447
|
-
"
|
|
448
|
-
"
|
|
449
|
-
"
|
|
450
|
-
"
|
|
481
|
+
"estimated-still": "estimated-still";
|
|
482
|
+
"estimated-video": "estimated-video";
|
|
483
|
+
"frames-box/v1": "frames-box/v1";
|
|
484
|
+
"frames-face/v1": "frames-face/v1";
|
|
485
|
+
"frames-layer/v1": "frames-layer/v1";
|
|
486
|
+
"frames-motion/v1": "frames-motion/v1";
|
|
487
|
+
"frames-split/v1": "frames-split/v1";
|
|
451
488
|
}>>;
|
|
452
489
|
}, z.core.$strict>>;
|
|
453
490
|
exit: z.ZodOptional<z.ZodObject<{
|
|
@@ -483,13 +520,13 @@ export declare const planReferenceRemake: z.ZodObject<{
|
|
|
483
520
|
duration_s: z.ZodOptional<z.ZodNumber>;
|
|
484
521
|
displacement: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
|
|
485
522
|
method: z.ZodOptional<z.ZodEnum<{
|
|
486
|
-
"
|
|
487
|
-
"
|
|
488
|
-
"
|
|
489
|
-
"
|
|
490
|
-
"
|
|
491
|
-
"
|
|
492
|
-
"
|
|
523
|
+
"estimated-still": "estimated-still";
|
|
524
|
+
"estimated-video": "estimated-video";
|
|
525
|
+
"frames-box/v1": "frames-box/v1";
|
|
526
|
+
"frames-face/v1": "frames-face/v1";
|
|
527
|
+
"frames-layer/v1": "frames-layer/v1";
|
|
528
|
+
"frames-motion/v1": "frames-motion/v1";
|
|
529
|
+
"frames-split/v1": "frames-split/v1";
|
|
493
530
|
}>>;
|
|
494
531
|
}, z.core.$strict>>;
|
|
495
532
|
layer: z.ZodOptional<z.ZodObject<{
|
|
@@ -501,13 +538,13 @@ export declare const planReferenceRemake: z.ZodObject<{
|
|
|
501
538
|
unknown: "unknown";
|
|
502
539
|
}>;
|
|
503
540
|
method: z.ZodEnum<{
|
|
504
|
-
"
|
|
505
|
-
"
|
|
506
|
-
"
|
|
507
|
-
"
|
|
508
|
-
"
|
|
509
|
-
"
|
|
510
|
-
"
|
|
541
|
+
"estimated-still": "estimated-still";
|
|
542
|
+
"estimated-video": "estimated-video";
|
|
543
|
+
"frames-box/v1": "frames-box/v1";
|
|
544
|
+
"frames-face/v1": "frames-face/v1";
|
|
545
|
+
"frames-layer/v1": "frames-layer/v1";
|
|
546
|
+
"frames-motion/v1": "frames-motion/v1";
|
|
547
|
+
"frames-split/v1": "frames-split/v1";
|
|
511
548
|
}>;
|
|
512
549
|
covered_by: z.ZodArray<z.ZodEnum<{
|
|
513
550
|
body: "body";
|
|
@@ -610,11 +647,70 @@ export declare const planReferenceRemake: z.ZodObject<{
|
|
|
610
647
|
end_s: z.ZodNumber;
|
|
611
648
|
speaker: z.ZodOptional<z.ZodString>;
|
|
612
649
|
}, z.core.$strict>>>;
|
|
650
|
+
transcript_language: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
613
651
|
hook: z.ZodOptional<z.ZodObject<{
|
|
614
652
|
text: z.ZodString;
|
|
615
653
|
end_s: z.ZodNumber;
|
|
616
654
|
caption: z.ZodOptional<z.ZodString>;
|
|
617
655
|
}, z.core.$strict>>;
|
|
656
|
+
speakers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
657
|
+
id: z.ZodString;
|
|
658
|
+
on_screen: z.ZodBoolean;
|
|
659
|
+
role: z.ZodEnum<{
|
|
660
|
+
guest: "guest";
|
|
661
|
+
interviewer: "interviewer";
|
|
662
|
+
narrator: "narrator";
|
|
663
|
+
other: "other";
|
|
664
|
+
presenter: "presenter";
|
|
665
|
+
}>;
|
|
666
|
+
word_count: z.ZodNumber;
|
|
667
|
+
caption: z.ZodNullable<z.ZodObject<{
|
|
668
|
+
colour: z.ZodNullable<z.ZodString>;
|
|
669
|
+
style: z.ZodString;
|
|
670
|
+
position: z.ZodNullable<z.ZodEnum<{
|
|
671
|
+
bottom: "bottom";
|
|
672
|
+
lower: "lower";
|
|
673
|
+
middle: "middle";
|
|
674
|
+
top: "top";
|
|
675
|
+
}>>;
|
|
676
|
+
box: z.ZodOptional<z.ZodObject<{
|
|
677
|
+
left: z.ZodNumber;
|
|
678
|
+
top: z.ZodNumber;
|
|
679
|
+
width: z.ZodNumber;
|
|
680
|
+
height: z.ZodNumber;
|
|
681
|
+
}, z.core.$strict>>;
|
|
682
|
+
}, z.core.$strict>>;
|
|
683
|
+
frames_s: z.ZodArray<z.ZodNumber>;
|
|
684
|
+
}, z.core.$strict>>>;
|
|
685
|
+
segments: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
686
|
+
start_s: z.ZodNumber;
|
|
687
|
+
end_s: z.ZodNumber;
|
|
688
|
+
text: z.ZodString;
|
|
689
|
+
speaker: z.ZodString;
|
|
690
|
+
mouth: z.ZodOptional<z.ZodObject<{
|
|
691
|
+
vision: z.ZodNullable<z.ZodEnum<{
|
|
692
|
+
closed: "closed";
|
|
693
|
+
moving: "moving";
|
|
694
|
+
not_visible: "not_visible";
|
|
695
|
+
}>>;
|
|
696
|
+
measured: z.ZodEnum<{
|
|
697
|
+
moving: "moving";
|
|
698
|
+
not_measured: "not_measured";
|
|
699
|
+
still: "still";
|
|
700
|
+
}>;
|
|
701
|
+
score: z.ZodNullable<z.ZodNumber>;
|
|
702
|
+
}, z.core.$strict>>;
|
|
703
|
+
frames_s: z.ZodArray<z.ZodNumber>;
|
|
704
|
+
}, z.core.$strict>>>;
|
|
705
|
+
spellings: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
706
|
+
heard: z.ZodString;
|
|
707
|
+
shown: z.ZodString;
|
|
708
|
+
start_s: z.ZodNumber;
|
|
709
|
+
end_s: z.ZodNumber;
|
|
710
|
+
}, z.core.$strict>>>;
|
|
711
|
+
analysis: z.ZodObject<{
|
|
712
|
+
warnings: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
713
|
+
}, z.core.$strip>;
|
|
618
714
|
}, z.core.$strict>;
|
|
619
715
|
script: z.ZodString;
|
|
620
716
|
actor_id: z.ZodString;
|
|
@@ -5,13 +5,15 @@ import { assetId } from '../asset-identity.js';
|
|
|
5
5
|
import { captionLanguageOf } from '../caption-language.js';
|
|
6
6
|
import { LONG_FORM_QUALIFICATION_MAX_DURATION_SEC } from '../long-form-profile.js';
|
|
7
7
|
import { estimateDurationSec } from '../pacing.js';
|
|
8
|
-
import {
|
|
8
|
+
import { referenceProfileInput } from '../reference-profile.js';
|
|
9
9
|
import { narrationPaceFromVoice, PAUSE_TRIM_CEILING_S, pauseTrimFromVoice } from '../reference-voice.js';
|
|
10
10
|
import { countActorShots, coverWordOf, lexicalTokens, planSemanticInserts, quoteOccurrences } from '../semantic-inserts.js';
|
|
11
11
|
import { actorId, callStage, isNarratedUgcInput, makeUgcInput, makeUgcInputShape, MAX_NARRATED_ACTOR_SHOTS, ugcCall } from '../skills.js';
|
|
12
12
|
import { CALL_PHONE_DEFAULTS, CALL_STAGE_SIDES } from '../ugc-call.js';
|
|
13
|
-
import { templateFaceWarnings, templateMediaOf, templateTextRefusal } from '../templates/index.js';
|
|
14
|
-
import {
|
|
13
|
+
import { TEMPLATE_KINDS, templateFaceWarnings, templateMediaOf, templateTextRefusal } from '../templates/index.js';
|
|
14
|
+
import { CAPTION_POSITION_VERTICAL, captionEdgeAt } from '../caption-geometry.js';
|
|
15
|
+
import { fitPlacedRect, place, plannedFace, presenterPlace, resolvePlace, sharedArea } from '../scene-place.js';
|
|
16
|
+
import { FACE_COVER_SHARE, faceZone, frameCanvasOf, LAYER_ENTERS, MAX_ENTER_OFFSET_S, MAX_OVERLAYS, OVERLAY_POSITIONS, overlayRect, ownPixels, rectsOverlap } from '../ugc-layers.js';
|
|
15
17
|
import { isEnglishScript, measuredPaceOf, PACE_MATCH_SHARE, VOICE_PACE_RANGE, voicesNearestPace } from '../voice-pace.js';
|
|
16
18
|
import { BREAK_TAG_SOURCE, FIXED_PACE_MODELS, resolveTtsModel } from '../voices.js';
|
|
17
19
|
import { workflowPlanDocument } from '../contracts/v1/workflow-plan.js';
|
|
@@ -19,7 +21,7 @@ import { workflowText } from '../contracts/v1/workflow-primitives.js';
|
|
|
19
21
|
import { queryWorkflowMethod } from './catalog.js';
|
|
20
22
|
// The reference-remake compiler (#857 S2a): a profile and an accepted script become a complete plan whose native input is a make_ugc input.
|
|
21
23
|
export const planReferenceRemake = z.strictObject({
|
|
22
|
-
reference_profile:
|
|
24
|
+
reference_profile: referenceProfileInput.describe('The reference_profile analyze_reference returned, or one you wrote to the same schema.'),
|
|
23
25
|
script: workflowText(10_000).describe('The accepted narration, copied into the plan unchanged.'),
|
|
24
26
|
actor_id: actorId, voice: makeUgcInputShape.voice, voice_id: makeUgcInputShape.voice_id,
|
|
25
27
|
assets: z.array(z.strictObject({ asset_id: assetId, media_type: z.enum(['image', 'video']) })).max(12).default([])
|
|
@@ -37,6 +39,34 @@ const MARKER = 'ref-long-form';
|
|
|
37
39
|
const GENERATE = 'pass a ready ast_* asset, or make one separately with generate_image or generate_video (its own quote), then compile again';
|
|
38
40
|
/** The saved plan keeps the reference's techniques, not its words: the remake copies no line of it, and the analysis keeps them already. */
|
|
39
41
|
const withoutWords = ({ transcript: _transcript, hook: _hook, ...profile }) => profile;
|
|
42
|
+
/** A layer sits under the captions when its top is at most this share of the height below them and its centre this share of the width off theirs. */
|
|
43
|
+
const UNDER_CAPTIONS = 0.03;
|
|
44
|
+
/** With no caption box measured, a centred layer whose top lies this far below where top-anchored captions start: a line to a short block. */
|
|
45
|
+
const UNMEASURED_CAPTIONS_BELOW = [0.03, 0.15];
|
|
46
|
+
const ENTER_OF = { pop: 'pop', fade: 'fade', slide: 'slide', scale_up: 'scale_up', tilt_in: 'tilt_in' };
|
|
47
|
+
const EXIT_OF = { cut: 'cut', fade: 'fade', slide: 'slide', pop: 'pop_out' };
|
|
48
|
+
const FRAME = { left: 0, top: 0, width: 1, height: 1 };
|
|
49
|
+
const round = (value, places = 4) => Math.round(value * 10 ** places) / 10 ** places;
|
|
50
|
+
const median = (values) => {
|
|
51
|
+
const sorted = [...values].sort((a, b) => a - b), mid = sorted.length >> 1;
|
|
52
|
+
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
53
|
+
};
|
|
54
|
+
/** The reference presenter's face while its graphics show (#973 S5): each element's box over its face-unit twin gives the face; freezes stand in. */
|
|
55
|
+
function referenceFaceOf(profile) {
|
|
56
|
+
const staging = profile.staging, { width, height } = profile.source;
|
|
57
|
+
if (!staging)
|
|
58
|
+
return undefined;
|
|
59
|
+
const twins = staging.elements.flatMap(({ rect_hold: rect, rect_hold_face: twin }) => {
|
|
60
|
+
if (!rect || !twin)
|
|
61
|
+
return [];
|
|
62
|
+
const unit = rect.height / twin.height;
|
|
63
|
+
return [{ height: unit, x: rect.left - (twin.left * unit * height) / width, y: rect.top - twin.top * unit }];
|
|
64
|
+
});
|
|
65
|
+
const faces = twins.length ? twins : staging.freezes.map(({ face_box_start: box }) => ({ height: box.height, x: box.left + box.width / 2, y: box.top + box.height / 2 }));
|
|
66
|
+
if (!faces.length)
|
|
67
|
+
return undefined;
|
|
68
|
+
return { face_height: round(median(faces.map(face => face.height))), face_centre: [round(median(faces.map(face => face.x))), round(median(faces.map(face => face.y)))] };
|
|
69
|
+
}
|
|
40
70
|
export function referenceScriptWords(script) {
|
|
41
71
|
const tags = [...script.matchAll(new RegExp(BREAK_TAG_SOURCE, 'g'))].map(match => [match.index, match.index + match[0].length]);
|
|
42
72
|
const words = [];
|
|
@@ -215,6 +245,81 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
215
245
|
return ranges.filter(range => words.slice(range.start, range.end).some(word => !word.tag));
|
|
216
246
|
};
|
|
217
247
|
const wordAt = (span, share) => quote(Math.min(span.end - 1, span.start + Math.max(1, Math.floor(share * (span.end - span.start)))), 2, span.end);
|
|
248
|
+
// Measured staging drawn as the reference drew it (#973 S5): a box under the captions, on the face or on the frame, its entry from our list.
|
|
249
|
+
const canvas = frameCanvasOf(DEFAULT_ASPECT_RATIO), measuredFace = referenceFaceOf(profile);
|
|
250
|
+
const presenterOk = longForm && executor?.field_dispositions.presenter_place === 'implemented';
|
|
251
|
+
const presenter = measuredFace && presenterOk ? presenterPlace.safeParse(measuredFace).data : undefined;
|
|
252
|
+
if (measuredFace && !presenter) {
|
|
253
|
+
note(0, `the reference presenter's face, ${measuredFace.face_height} of the frame's height`, !longForm
|
|
254
|
+
? 'the actor\'s own framing: framing the presenter needs long-form qualification, not available right now'
|
|
255
|
+
: presenterOk ? 'the actor\'s own framing: the face size is outside what the presenter can be framed to' : 'the actor\'s own framing');
|
|
256
|
+
}
|
|
257
|
+
const face = plannedFace(DEFAULT_ASPECT_RATIO, canvas, framing, presenter);
|
|
258
|
+
/** Elements whose staging the plan draws or names on its own; the rest get the one note below. */
|
|
259
|
+
const drawn = new Set();
|
|
260
|
+
const gaps = { entry: [], exit: [], styles: new Map(), templates: [], inferred: [] };
|
|
261
|
+
// A box never measured on the frames is an estimate, and a note says so (#930 S2).
|
|
262
|
+
const sourceOf = (element) => element.rect_method?.startsWith('estimated') ? 'vision-pass' : 'measured';
|
|
263
|
+
const elementOf = (beat, graphic) => profile.staging?.elements.find(entry => entry.beat_index === beat && entry.graphic_index === graphic && entry.rect_hold);
|
|
264
|
+
const captionsSeen = profile.captions.present && !profile.captions.hidden_under_graphics;
|
|
265
|
+
const underCaptions = (rect) => {
|
|
266
|
+
const centre = rect.left + rect.width / 2, box = profile.captions.box;
|
|
267
|
+
if (!captionsSeen)
|
|
268
|
+
return undefined;
|
|
269
|
+
if (box) {
|
|
270
|
+
const gap = rect.top - box.top - box.height;
|
|
271
|
+
return Math.abs(centre - box.left - box.width / 2) <= UNDER_CAPTIONS && gap >= -0.005 && gap <= UNDER_CAPTIONS ? { gap: round(Math.max(0, gap)) } : undefined;
|
|
272
|
+
}
|
|
273
|
+
// No box: caption lines are centred, and a top-anchored block starts at its position's height while a bottom-anchored one ends there.
|
|
274
|
+
const at = CAPTION_POSITION_VERTICAL[profile.captions.position] / 100, topEdge = captionEdgeAt(at * 100) === 'top';
|
|
275
|
+
const [low, high] = topEdge ? [at + UNMEASURED_CAPTIONS_BELOW[0], at + UNMEASURED_CAPTIONS_BELOW[1]] : [at - 0.005, at + UNDER_CAPTIONS];
|
|
276
|
+
return Math.abs(centre - 0.5) <= UNDER_CAPTIONS && rect.top >= low && rect.top <= high ? {} : undefined;
|
|
277
|
+
};
|
|
278
|
+
/** The element's place on our presenter, or 'face' when it would cover our face; `own` is a template's own rect, which never stands by captions. */
|
|
279
|
+
const stagedPlace = (element, own) => {
|
|
280
|
+
const rect = element.rect_hold, under = own ? undefined : underCaptions(rect);
|
|
281
|
+
const parsed = place.safeParse(under ? { relative_to: 'captions', side: 'below', ...(under.gap === undefined ? {} : { gap: under.gap }), width: round(rect.width) }
|
|
282
|
+
: element.rect_hold_face ? { relative_to: 'face', rect: element.rect_hold_face } : { relative_to: 'frame', rect });
|
|
283
|
+
if (!parsed.success)
|
|
284
|
+
return undefined;
|
|
285
|
+
if (under)
|
|
286
|
+
return { place: parsed.data, inferred: under.gap === undefined };
|
|
287
|
+
const box = resolvePlace(parsed.data, { canvas, face }), shown = own ? fitPlacedRect(box, ownPixels(own, canvas), canvas) : box;
|
|
288
|
+
if (sharedArea(shown, FRAME) < (shown.width * shown.height) / 2)
|
|
289
|
+
return undefined;
|
|
290
|
+
return sharedArea(shown, face) > FACE_COVER_SHARE * face.width * face.height ? 'face' : { place: parsed.data, inferred: false };
|
|
291
|
+
};
|
|
292
|
+
/** The word the reference showed it on, at the same share of our beat's words, and its measured lead or lag on that word. */
|
|
293
|
+
const enterOf = (element, beat, span) => {
|
|
294
|
+
const shown = element.entry?.first_visible_s, source = profile.beats[beat];
|
|
295
|
+
if (shown === undefined)
|
|
296
|
+
return {};
|
|
297
|
+
const said = (profile.transcript ?? []).filter(word => word.start_s >= source.start_s && word.start_s < source.end_s);
|
|
298
|
+
let at = 0;
|
|
299
|
+
said.forEach((word, index) => { if (word.start_s <= shown)
|
|
300
|
+
at = index; });
|
|
301
|
+
const share = said.length ? (at + 0.5) / said.length : Math.min(1, Math.max(0, (shown - source.start_s) / (source.end_s - source.start_s)));
|
|
302
|
+
const found = quote(span.start + Math.min(span.end - span.start - 1, Math.floor(share * (span.end - span.start))), 2, span.end);
|
|
303
|
+
if (!found)
|
|
304
|
+
return {};
|
|
305
|
+
const offset = said.length ? round(Math.min(MAX_ENTER_OFFSET_S, Math.max(-MAX_ENTER_OFFSET_S, shown - said[at].start_s)), 2) : 0;
|
|
306
|
+
return { anchor: found.anchor, cover_words: span.end - found.start, ...(offset ? { enter_offset_s: offset } : {}) };
|
|
307
|
+
};
|
|
308
|
+
const motionOf = (element, beat) => {
|
|
309
|
+
const style = element.entry?.style, leave = element.exit?.style, enter = style && ENTER_OF[style], exit = leave && EXIT_OF[leave];
|
|
310
|
+
const unmapped = (what) => gaps.styles.set(what, [...gaps.styles.get(what) ?? [], beat]);
|
|
311
|
+
if (!style || style === 'unknown')
|
|
312
|
+
gaps.entry.push(beat);
|
|
313
|
+
else if (!enter)
|
|
314
|
+
unmapped(`${style} entries`);
|
|
315
|
+
if (!leave || leave === 'unknown')
|
|
316
|
+
gaps.exit.push(beat);
|
|
317
|
+
else if (!exit)
|
|
318
|
+
unmapped(`${leave} exits`);
|
|
319
|
+
const duration = element.entry?.duration_s;
|
|
320
|
+
return { ...(enter ? { enter, ...(duration ? { enter_duration_s: Math.min(1.5, Math.max(0.05, round(duration, 2))) } : {}) } : {}),
|
|
321
|
+
...(exit && exit !== 'cut' ? { exit } : {}) };
|
|
322
|
+
};
|
|
218
323
|
const spans = planSpans({ profile, words });
|
|
219
324
|
const layers = [];
|
|
220
325
|
const pool = [...request.assets], images = () => pool.filter(asset => asset.media_type === 'image').length;
|
|
@@ -242,11 +347,22 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
242
347
|
const kind = graphic.kind;
|
|
243
348
|
if (kind === 'particles' || kind === 'unmapped')
|
|
244
349
|
continue;
|
|
245
|
-
|
|
350
|
+
// A logo card is a brand picture: it shows as an overlay while an image is left in assets, else as the text card it maps to (#973 S5).
|
|
351
|
+
const room = layers.filter(layer => layer.field === 'overlays').length < MAX_OVERLAYS;
|
|
352
|
+
const wantsLogo = graphic.source_kind === 'logo_card' && longForm && !layered, logo = wantsLogo && images() > 0 && room;
|
|
353
|
+
if (wantsLogo && !logo) {
|
|
354
|
+
note(index, `logo_card (${room ? 'no image left in assets' : `a run shows at most ${MAX_OVERLAYS} overlays`})`, 'a text card with its words');
|
|
355
|
+
}
|
|
356
|
+
if (kind in MEDIA_KINDS || logo) {
|
|
357
|
+
const media = logo ? 'logo_card' : kind;
|
|
246
358
|
if (layered) {
|
|
247
359
|
note(index, `${kind} (one media layer per beat)`, 'the first one shows here');
|
|
248
360
|
continue;
|
|
249
361
|
}
|
|
362
|
+
if (MEDIA_KINDS[kind] === 'overlays' && !room) {
|
|
363
|
+
note(index, `${kind} (a run shows at most ${MAX_OVERLAYS} overlays)`, 'the presenter stays on screen on these words');
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
250
366
|
if (pool.length === 0) {
|
|
251
367
|
note(index, `${kind} (no image or video left in assets)`, GENERATE);
|
|
252
368
|
continue;
|
|
@@ -255,16 +371,28 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
255
371
|
note(index, `${kind} (media layers need long-form qualification, not available right now)`, 'the presenter stays on screen on these words');
|
|
256
372
|
continue;
|
|
257
373
|
}
|
|
258
|
-
const asset = take(), field =
|
|
259
|
-
const { kind: _kind, ...item } =
|
|
374
|
+
const asset = take(logo), field = logo ? 'overlays' : MEDIA_KINDS[kind];
|
|
375
|
+
const { kind: _kind, ...item } = { ...envelope(media), asset_id: asset.asset_id, media_type: asset.media_type };
|
|
376
|
+
const element = field === 'overlays' ? elementOf(index, beat.graphics.indexOf(graphic)) : undefined, staged = element && stagedPlace(element);
|
|
377
|
+
if (element && staged)
|
|
378
|
+
drawn.add(element.id);
|
|
379
|
+
layered = true;
|
|
380
|
+
if (element && staged && staged !== 'face') {
|
|
381
|
+
if (staged.inferred)
|
|
382
|
+
gaps.inferred.push(index);
|
|
383
|
+
layers.push({ field, beat: index, kind: media, item: { ...item, ...enterOf(element, index, span), ...motionOf(element, index), place: staged.place } });
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
260
386
|
const position = graphic.position.replace(/^split_/, ''), wanted = OVERLAY_POSITIONS.includes(position) ? position : 'top_right';
|
|
261
387
|
// Off the face: the reference place, else a corner on its side (right by default), a band last.
|
|
262
388
|
const side = wanted.endsWith('left') ? 'left' : 'right', rank = (place) => place.endsWith(side) ? 0 : place.includes('_') ? 1 : 2;
|
|
263
389
|
const clear = (place) => !rectsOverlap(overlayRect(place, frameCanvasOf(DEFAULT_ASPECT_RATIO)), faceZone(DEFAULT_ASPECT_RATIO, framing));
|
|
264
390
|
const placed = clear(wanted) ? wanted : [...OVERLAY_POSITIONS].sort((left, right) => rank(left) - rank(right)).find(clear) ?? wanted;
|
|
265
|
-
if (
|
|
266
|
-
note(index, `${
|
|
267
|
-
|
|
391
|
+
if (staged === 'face')
|
|
392
|
+
note(index, `${media}: its ${sourceOf(element)} place, over the presenter's face`, `${placed}, to keep the face clear`);
|
|
393
|
+
else if (field === 'overlays' && placed !== position)
|
|
394
|
+
note(index, `${media} at ${graphic.position}`, `${placed}, to keep the face clear`);
|
|
395
|
+
layers.push({ field, beat: index, kind: media, item: field === 'overlays' ? { ...item, position: placed }
|
|
268
396
|
: field === 'layout_windows' ? { ...item, layout: 'split' } : item });
|
|
269
397
|
layered = true;
|
|
270
398
|
fullFrame ||= field === 'inserts';
|
|
@@ -289,9 +417,25 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
289
417
|
if ((built.kind === 'phone_call' || built.kind === 'notification') && /[^\p{Script=Latin}\p{N}\p{P}\p{S}\s]/u.test(script)) {
|
|
290
418
|
note(index, `${built.kind} card text stays English ("${built.kind === 'phone_call' ? 'Incoming call' : 'Messages'}")`, 'edit the plan to change it');
|
|
291
419
|
}
|
|
292
|
-
const item = { ...envelope(built.kind),
|
|
420
|
+
const item = { ...envelope(built.kind),
|
|
421
|
+
...(built.placement ? { placement: built.placement } : {}), data: built.data };
|
|
422
|
+
// A measured box over the presenter takes the template whole, drawn as its kind draws with no place of its own (#973 S4, S5).
|
|
423
|
+
const element = elementOf(index, beat.graphics.indexOf(graphic)), definition = TEMPLATE_KINDS.find(entry => entry.kind === built.kind);
|
|
424
|
+
const { position: _place, ...unplaced } = built.data, own = definition?.data.safeParse(unplaced);
|
|
425
|
+
const staged = element && own?.success && (built.placement ?? definition.placements[0]) === 'over'
|
|
426
|
+
? stagedPlace(element, definition.rect(own.data, DEFAULT_ASPECT_RATIO, undefined, framing)) : undefined;
|
|
427
|
+
if (element && staged)
|
|
428
|
+
drawn.add(element.id);
|
|
429
|
+
if (staged)
|
|
430
|
+
item.data = unplaced;
|
|
431
|
+
if (staged === 'face')
|
|
432
|
+
note(index, `${built.kind}: its ${sourceOf(element)} place, over the presenter's face`, `the ${built.kind}'s default place, to keep the face clear`);
|
|
433
|
+
else if (staged) {
|
|
434
|
+
item.place = staged.place;
|
|
435
|
+
gaps.templates.push(index);
|
|
436
|
+
}
|
|
293
437
|
// A reference place over the face gives way to the kind's default, which every kind keeps off it.
|
|
294
|
-
if ('position' in built.data && templateFaceWarnings({ templates: [item], presenter_framing: framing }).length) {
|
|
438
|
+
if (!item.place && 'position' in built.data && templateFaceWarnings({ templates: [item], presenter_framing: framing }).length) {
|
|
295
439
|
const { position: _position, ...data } = built.data;
|
|
296
440
|
item.data = data;
|
|
297
441
|
note(index, `${built.kind} at ${graphic.position}`, `the ${built.kind}'s default place, to keep the face clear`);
|
|
@@ -418,20 +562,35 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
418
562
|
phone: { ...CALL_PHONE_DEFAULTS, ...request.call.phone }, ...(stage ? { stage } : {}) };
|
|
419
563
|
for (const layer of layers.filter(layer => layer.beat === listening && layer.kind === 'phone_call'))
|
|
420
564
|
layers.splice(layers.indexOf(layer), 1);
|
|
565
|
+
// The call's own phone draws the staging, so the dropped card's word on it goes too.
|
|
566
|
+
for (const entry of notes.filter(entry => entry.beat_index === listening && entry.what.startsWith('phone_call: its ')))
|
|
567
|
+
notes.splice(notes.indexOf(entry), 1);
|
|
421
568
|
}
|
|
422
|
-
// Measured staging
|
|
423
|
-
// so once per beat and kind (rule 3).
|
|
569
|
+
// Measured staging is drawn for overlays, templates over the presenter and the call's phone: any other measured graphic the plan places keeps its
|
|
570
|
+
// default place, and says so once per beat and kind (rule 3).
|
|
424
571
|
const placed = new Set(layers.filter(layer => layer.field !== 'framing' && layer.field !== 'caption_emphasis').map(layer => layer.beat));
|
|
425
572
|
const named = new Set();
|
|
426
573
|
for (const element of profile.staging?.elements ?? []) {
|
|
427
574
|
const graphic = element.graphic_index === null ? undefined : profile.beats[element.beat_index]?.graphics[element.graphic_index];
|
|
428
575
|
const key = `${element.beat_index}:${graphic?.kind}`;
|
|
429
|
-
if (!graphic || !placed.has(element.beat_index) || named.has(key) || (
|
|
576
|
+
if (!graphic || !placed.has(element.beat_index) || named.has(key) || drawn.has(element.id)
|
|
577
|
+
|| (call && element.beat_index === listening && element.kind === 'phone_call'))
|
|
430
578
|
continue;
|
|
431
579
|
named.add(key);
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
580
|
+
note(element.beat_index, `${graphic.kind}: its ${sourceOf(element)} place and entry (reference staging)`, 'its default place; measured staging is drawn for overlays, templates over the presenter and the call\'s phone');
|
|
581
|
+
}
|
|
582
|
+
const beatsOf = (list) => [...new Set(list)].map(beat => beat + 1).join(', ');
|
|
583
|
+
if (gaps.entry.length)
|
|
584
|
+
note(gaps.entry[0], `how the overlays on beats ${beatsOf(gaps.entry)} enter (reference staging did not measure it)`, 'a cut');
|
|
585
|
+
if (gaps.exit.length)
|
|
586
|
+
note(gaps.exit[0], `how the overlays on beats ${beatsOf(gaps.exit)} leave (reference staging did not measure it)`, 'a cut');
|
|
587
|
+
for (const [what, list] of gaps.styles) {
|
|
588
|
+
note(list[0], `${what} of the overlays on beats ${beatsOf(list)}`, what.endsWith('exits') ? 'a cut' : `a cut: overlays enter by ${LAYER_ENTERS.join(', ')}`);
|
|
589
|
+
}
|
|
590
|
+
if (gaps.templates.length)
|
|
591
|
+
note(gaps.templates[0], `the measured entry of the templates on beats ${beatsOf(gaps.templates)}`, 'each template\'s own entry');
|
|
592
|
+
if (gaps.inferred.length) {
|
|
593
|
+
note(gaps.inferred[0], `the reference's caption box, for the graphics on beats ${beatsOf(gaps.inferred)} (not measured)`, `placed under the captions: each sits centred just below where the reference's ${profile.captions.position} captions are`);
|
|
435
594
|
}
|
|
436
595
|
const aggregate = (values, keep, what, substitute) => {
|
|
437
596
|
for (const value of new Set(values.filter((entry) => entry !== undefined && keep(entry)))) {
|
|
@@ -504,6 +663,8 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
504
663
|
...(captionsPlaced ? { caption_position: profile.captions.position } : {}) } : {}),
|
|
505
664
|
...field('caption_emphasis'), ...field('templates'), ...field('overlays'), ...field('inserts'), ...field('layout_windows'), ...field('framing'),
|
|
506
665
|
...(call ? { call } : {}) };
|
|
666
|
+
if (presenter && isNarratedUgcInput(native))
|
|
667
|
+
native.presenter_place = presenter;
|
|
507
668
|
// The quote refuses customer media without this policy; the note below discloses it (rule 3).
|
|
508
669
|
if (['overlays', 'inserts', 'layout_windows'].some(name => native[name]) || templateMediaOf(native.templates).length) {
|
|
509
670
|
native.broll_policy = 'anyone';
|