@clipwright/core 0.40.0 → 0.42.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/account-actors.d.ts +1 -0
- package/dist/account-actors.js +33 -31
- package/dist/account-actors.js.map +1 -1
- package/dist/actor-bounds.d.ts +3 -3
- package/dist/actor-bounds.js +3 -3
- package/dist/actor-bounds.js.map +1 -1
- package/dist/actors.d.ts +5 -0
- package/dist/actors.js +22 -2
- package/dist/actors.js.map +1 -1
- package/dist/contract-dispositions.d.ts +5 -0
- package/dist/contracts/v1/workflow-brief.d.ts +12 -0
- package/dist/contracts/v1/workflow-brief.js +5 -2
- package/dist/contracts/v1/workflow-brief.js.map +1 -1
- package/dist/contracts/v1/workflow-catalog.d.ts +10 -0
- package/dist/contracts/v1/workflow-client.d.ts +10 -0
- package/dist/contracts/v1/workflow-plan.d.ts +15 -0
- package/dist/faceless-price.d.ts +2 -2
- package/dist/faceless-price.js +2 -2
- package/dist/faceless-price.js.map +1 -1
- package/dist/faceless-public.js +5 -5
- package/dist/faceless-public.js.map +1 -1
- package/dist/generations.d.ts +1 -1
- package/dist/generations.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/live-shot-kinds.d.ts +12 -0
- package/dist/live-shot-kinds.js +11 -0
- package/dist/live-shot-kinds.js.map +1 -1
- package/dist/long-form-profile.d.ts +1 -1
- package/dist/long-form-profile.js +1 -1
- package/dist/motion-styles.d.ts +1 -1
- package/dist/motion-styles.js +1 -1
- package/dist/motion-styles.js.map +1 -1
- package/dist/moves.d.ts +1 -1
- package/dist/moves.js +4 -3
- package/dist/moves.js.map +1 -1
- package/dist/music.d.ts +1 -1
- package/dist/music.js +1 -1
- package/dist/presenter-takes.d.ts +10 -0
- package/dist/presenter-takes.js +59 -0
- package/dist/presenter-takes.js.map +1 -0
- package/dist/reference-profile.d.ts +4 -1
- package/dist/reference-profile.js +33 -9
- package/dist/reference-profile.js.map +1 -1
- package/dist/resolved-timeline.js +1 -1
- package/dist/run-static-warnings.js +1 -1
- package/dist/run-static-warnings.js.map +1 -1
- package/dist/scene-graph.d.ts +8 -0
- package/dist/scene-graph.js +35 -0
- package/dist/scene-graph.js.map +1 -1
- package/dist/scene-place.d.ts +3 -0
- package/dist/scene-place.js +3 -0
- package/dist/scene-place.js.map +1 -1
- package/dist/skills.d.ts +43 -8
- package/dist/skills.js +45 -14
- package/dist/skills.js.map +1 -1
- package/dist/templates/index.d.ts +9 -1
- package/dist/templates/index.js +15 -5
- package/dist/templates/index.js.map +1 -1
- package/dist/ugc-call.d.ts +4 -4
- package/dist/ugc-call.js +4 -4
- package/dist/ugc-call.js.map +1 -1
- package/dist/ugc-framing.d.ts +1 -1
- package/dist/ugc-framing.js +1 -1
- package/dist/ugc-interview.d.ts +3 -3
- package/dist/ugc-interview.js +3 -3
- package/dist/ugc-interview.js.map +1 -1
- package/dist/ugc-layers.d.ts +7 -3
- package/dist/ugc-layers.js +17 -6
- package/dist/ugc-layers.js.map +1 -1
- package/dist/ugc-scenes.d.ts +3 -3
- package/dist/ugc-scenes.js +5 -4
- package/dist/ugc-scenes.js.map +1 -1
- package/dist/voice-admission.d.ts +5 -0
- package/dist/workflows/methods/faceless-explainer.d.ts +5 -0
- package/dist/workflows/methods/kinetic-launch.d.ts +5 -0
- package/dist/workflows/methods/product-image-story.d.ts +5 -0
- package/dist/workflows/methods/reference-remake.d.ts +5 -0
- package/dist/workflows/methods/shared.d.ts +5 -0
- package/dist/workflows/methods/ugc-faq-answer.d.ts +5 -0
- package/dist/workflows/methods/ugc-product-intro.d.ts +5 -0
- package/dist/workflows/methods/website-proof-demo.d.ts +5 -0
- package/dist/workflows/reference-remake.d.ts +12 -0
- package/dist/workflows/reference-remake.js +108 -16
- package/dist/workflows/reference-remake.js.map +1 -1
- package/dist/workflows/remake-motion.d.ts +2 -0
- package/dist/workflows/remake-motion.js +2 -1
- package/dist/workflows/remake-motion.js.map +1 -1
- package/package.json +1 -1
|
@@ -1706,6 +1706,11 @@ export declare const kineticLaunch: {
|
|
|
1706
1706
|
product?: boolean | {
|
|
1707
1707
|
placement: "beside" | "hand";
|
|
1708
1708
|
} | undefined;
|
|
1709
|
+
cast?: {
|
|
1710
|
+
asset_id: string;
|
|
1711
|
+
role: string;
|
|
1712
|
+
action?: string | undefined;
|
|
1713
|
+
}[] | undefined;
|
|
1709
1714
|
transition?: "cut" | "flash" | "light_leak" | "whoosh" | undefined;
|
|
1710
1715
|
}[] | undefined;
|
|
1711
1716
|
caption_emphasis?: (string | {
|
|
@@ -1706,6 +1706,11 @@ export declare const productImageStory: {
|
|
|
1706
1706
|
product?: boolean | {
|
|
1707
1707
|
placement: "beside" | "hand";
|
|
1708
1708
|
} | undefined;
|
|
1709
|
+
cast?: {
|
|
1710
|
+
asset_id: string;
|
|
1711
|
+
role: string;
|
|
1712
|
+
action?: string | undefined;
|
|
1713
|
+
}[] | undefined;
|
|
1709
1714
|
transition?: "cut" | "flash" | "light_leak" | "whoosh" | undefined;
|
|
1710
1715
|
}[] | undefined;
|
|
1711
1716
|
caption_emphasis?: (string | {
|
|
@@ -1706,6 +1706,11 @@ export declare const referenceRemake: {
|
|
|
1706
1706
|
product?: boolean | {
|
|
1707
1707
|
placement: "beside" | "hand";
|
|
1708
1708
|
} | undefined;
|
|
1709
|
+
cast?: {
|
|
1710
|
+
asset_id: string;
|
|
1711
|
+
role: string;
|
|
1712
|
+
action?: string | undefined;
|
|
1713
|
+
}[] | undefined;
|
|
1709
1714
|
transition?: "cut" | "flash" | "light_leak" | "whoosh" | undefined;
|
|
1710
1715
|
}[] | undefined;
|
|
1711
1716
|
caption_emphasis?: (string | {
|
|
@@ -1721,6 +1721,11 @@ export declare function defineWorkflowMethod(value: MethodDescriptorArgs): {
|
|
|
1721
1721
|
product?: boolean | {
|
|
1722
1722
|
placement: "beside" | "hand";
|
|
1723
1723
|
} | undefined;
|
|
1724
|
+
cast?: {
|
|
1725
|
+
asset_id: string;
|
|
1726
|
+
role: string;
|
|
1727
|
+
action?: string | undefined;
|
|
1728
|
+
}[] | undefined;
|
|
1724
1729
|
transition?: "cut" | "flash" | "light_leak" | "whoosh" | undefined;
|
|
1725
1730
|
}[] | undefined;
|
|
1726
1731
|
caption_emphasis?: (string | {
|
|
@@ -1706,6 +1706,11 @@ export declare const ugcFaqAnswer: {
|
|
|
1706
1706
|
product?: boolean | {
|
|
1707
1707
|
placement: "beside" | "hand";
|
|
1708
1708
|
} | undefined;
|
|
1709
|
+
cast?: {
|
|
1710
|
+
asset_id: string;
|
|
1711
|
+
role: string;
|
|
1712
|
+
action?: string | undefined;
|
|
1713
|
+
}[] | undefined;
|
|
1709
1714
|
transition?: "cut" | "flash" | "light_leak" | "whoosh" | undefined;
|
|
1710
1715
|
}[] | undefined;
|
|
1711
1716
|
caption_emphasis?: (string | {
|
|
@@ -1706,6 +1706,11 @@ export declare const ugcProductIntro: {
|
|
|
1706
1706
|
product?: boolean | {
|
|
1707
1707
|
placement: "beside" | "hand";
|
|
1708
1708
|
} | undefined;
|
|
1709
|
+
cast?: {
|
|
1710
|
+
asset_id: string;
|
|
1711
|
+
role: string;
|
|
1712
|
+
action?: string | undefined;
|
|
1713
|
+
}[] | undefined;
|
|
1709
1714
|
transition?: "cut" | "flash" | "light_leak" | "whoosh" | undefined;
|
|
1710
1715
|
}[] | undefined;
|
|
1711
1716
|
caption_emphasis?: (string | {
|
|
@@ -1706,6 +1706,11 @@ export declare const websiteProofDemo: {
|
|
|
1706
1706
|
product?: boolean | {
|
|
1707
1707
|
placement: "beside" | "hand";
|
|
1708
1708
|
} | undefined;
|
|
1709
|
+
cast?: {
|
|
1710
|
+
asset_id: string;
|
|
1711
|
+
role: string;
|
|
1712
|
+
action?: string | undefined;
|
|
1713
|
+
}[] | undefined;
|
|
1709
1714
|
transition?: "cut" | "flash" | "light_leak" | "whoosh" | undefined;
|
|
1710
1715
|
}[] | undefined;
|
|
1711
1716
|
caption_emphasis?: (string | {
|
|
@@ -950,6 +950,11 @@ export declare const planReferenceRemake: z.ZodObject<{
|
|
|
950
950
|
cover_words: z.ZodOptional<z.ZodNumber>;
|
|
951
951
|
}, z.core.$strict>>>;
|
|
952
952
|
}, z.core.$strict>>;
|
|
953
|
+
cast: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
954
|
+
asset_id: z.ZodString;
|
|
955
|
+
role: z.ZodString;
|
|
956
|
+
action: z.ZodOptional<z.ZodString>;
|
|
957
|
+
}, z.core.$strict>>>;
|
|
953
958
|
}, z.core.$strict>;
|
|
954
959
|
/** What a client sends (#1172): the profile, or the analysis_id of an analysis the server reads it from. */
|
|
955
960
|
export declare const planReferenceRemakeRequest: z.ZodObject<{
|
|
@@ -1084,6 +1089,11 @@ export declare const planReferenceRemakeRequest: z.ZodObject<{
|
|
|
1084
1089
|
cover_words: z.ZodOptional<z.ZodNumber>;
|
|
1085
1090
|
}, z.core.$strict>>>;
|
|
1086
1091
|
}, z.core.$strict>>;
|
|
1092
|
+
cast: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
1093
|
+
asset_id: z.ZodString;
|
|
1094
|
+
role: z.ZodString;
|
|
1095
|
+
action: z.ZodOptional<z.ZodString>;
|
|
1096
|
+
}, z.core.$strict>>>;
|
|
1087
1097
|
reference_profile: z.ZodOptional<z.ZodObject<{
|
|
1088
1098
|
version: z.ZodLiteral<"reference-profile-v1">;
|
|
1089
1099
|
source: z.ZodObject<{
|
|
@@ -1928,9 +1938,11 @@ export type ReferenceSpanPlanner = (input: {
|
|
|
1928
1938
|
}) => ReferenceSpan[];
|
|
1929
1939
|
type ReferenceGraphic = ReferenceProfile['beats'][number]['graphics'][number];
|
|
1930
1940
|
/** What the asset library says of a request's asset (#1170): the server reads it, a direct compile may pass none. */
|
|
1941
|
+
/** `seconds`: a video asset's measured length, which caps the words its clip covers. */
|
|
1931
1942
|
export type ReferenceAssetFacts = ReadonlyMap<string, {
|
|
1932
1943
|
name: string;
|
|
1933
1944
|
role?: string;
|
|
1945
|
+
seconds?: number;
|
|
1934
1946
|
}>;
|
|
1935
1947
|
/** The analysis's name for a graphic in its gap list: its device, else its own kind. */
|
|
1936
1948
|
export declare const sceneGapWhat: (graphic: ReferenceGraphic) => string;
|
|
@@ -4,23 +4,25 @@ import { assetId } from '../asset-identity.js';
|
|
|
4
4
|
import { captionLanguageOf } from '../caption-language.js';
|
|
5
5
|
import { LONG_FORM_QUALIFICATION_MAX_DURATION_SEC } from '../long-form-profile.js';
|
|
6
6
|
import { CANVAS_FPS } from '../media-timebase.js';
|
|
7
|
-
import { estimateDurationSec, WORDS_PER_SECOND_MAX } from '../pacing.js';
|
|
7
|
+
import { estimateDurationSec, WORDS_PER_SECOND_MAX, WORDS_PER_SECOND_MIN } from '../pacing.js';
|
|
8
8
|
import { referenceAnalysisId } from '../reference-analysis.js';
|
|
9
9
|
import { actorIdScope } from '../account-actors.js';
|
|
10
10
|
import { referenceProfileInput } from '../reference-profile.js';
|
|
11
11
|
import { narrationPaceFromVoice, PAUSE_TRIM_CEILING_S, pauseTrimFromVoice } from '../reference-voice.js';
|
|
12
12
|
import { AD_PAUSE_TRIM } from '../ad-pause-trim.js';
|
|
13
13
|
import { countActorShots, coverWordOf, lexicalTokens, planSemanticInserts, quoteOccurrences } from '../semantic-inserts.js';
|
|
14
|
-
import { actorId, callStage, insertBox, isNarratedUgcInput, makeUgcInput, makeUgcInputShape, MAX_INSERTS, MAX_LIVE_SHOTS, MAX_NARRATED_ACTOR_SHOTS, motionStyle, ugcCall, ugcTemplate } from '../skills.js';
|
|
14
|
+
import { actorId, callStage, insertBox, isNarratedUgcInput, makeUgcInput, makeUgcInputShape, MAX_INSERTS, MAX_LIVE_SHOTS, MAX_NARRATED_ACTOR_SHOTS, MAX_PRESENTER_TAKES, MAX_SHOT_CAST, motionStyle, ugcCall, ugcLiveShotCastMember, ugcTemplate } from '../skills.js';
|
|
15
|
+
import { ugcMarkedComponentsRefusal } from '../presenter-takes.js';
|
|
15
16
|
import { CALL_PHONE_DEFAULTS, CALL_STAGE_SIDES } from '../ugc-call.js';
|
|
16
17
|
import { MAX_TEMPLATES, TEMPLATE_ENTER_FRAMES, TEMPLATE_KIND_IDS, TEMPLATE_KINDS, TEMPLATE_PLACEMENTS, templateFaceWarnings, templateMediaOf, templateTextRefusal } from '../templates/index.js';
|
|
17
|
-
import { MAX_SCENES,
|
|
18
|
+
import { MAX_SCENES, SCENE_TEXT_MAX_CHARS } from '../ugc-scenes.js';
|
|
18
19
|
import { MAX_FRAMING } from '../ugc-framing.js';
|
|
19
20
|
import { CAPTION_POSITION_VERTICAL, captionBandBox, captionEdgeAt } from '../caption-geometry.js';
|
|
20
21
|
import { fitPlacedRect, place, plannedFace, presenterPlace, resolvePlace, sharedArea } from '../scene-place.js';
|
|
21
22
|
import { FACE_COVER_SHARE, faceZone, frameCanvasOf, LAYER_ENTERS, layerWordRange, MAX_ENTER_OFFSET_S, MAX_OVERLAYS, OVERLAY_POSITIONS, overlayRect, ownPixels, MAX_LAYOUT_WINDOWS, rectsOverlap, SPLIT_MEDIA_SHARE } from '../ugc-layers.js';
|
|
22
23
|
import { isEnglishScript, measuredPaceOf, PACE_MATCH_SHARE, VOICE_PACE_RANGE, voicesNearestPace } from '../voice-pace.js';
|
|
23
24
|
import { BREAK_TAG_SOURCE, FIXED_PACE_MODELS, resolveTtsModel } from '../voices.js';
|
|
25
|
+
import { WORKFLOW_ADDED_MAX } from '../contracts/v1/workflow-brief.js';
|
|
24
26
|
import { saveWorkflowPlan, workflowPlanDocument } from '../contracts/v1/workflow-plan.js';
|
|
25
27
|
import { workflowId, workflowText } from '../contracts/v1/workflow-primitives.js';
|
|
26
28
|
import { queryWorkflowMethod } from './catalog.js';
|
|
@@ -32,20 +34,20 @@ import { beatCue, FLASH_SNAP_S, planMotion } from './remake-motion.js';
|
|
|
32
34
|
// The reference-remake compiler (#857 S2a): a profile and an accepted script become a complete plan whose native input is a make_ugc input.
|
|
33
35
|
const remakeFields = {
|
|
34
36
|
plan_id: workflowId.optional(), base_version: saveWorkflowPlan.shape.base_version.optional(),
|
|
35
|
-
review_notes: z.array(workflowText(2000)).max(
|
|
37
|
+
review_notes: z.array(workflowText(2000)).max(50).optional(),
|
|
36
38
|
reference_profile: referenceProfileInput.describe('The reference_profile analyze_reference returned, or one you wrote to the same schema.'),
|
|
37
39
|
script: workflowText(10_000).describe('The accepted narration, copied into the plan unchanged.'),
|
|
38
40
|
actor_id: actorId, voice: makeUgcInputShape.voice, voice_id: makeUgcInputShape.voice_id,
|
|
39
41
|
assets: z.array(z.strictObject({ asset_id: assetId, media_type: z.enum(['image', 'video']),
|
|
40
|
-
anchor: z.string().trim().min(1).max(
|
|
41
|
-
.max(
|
|
42
|
+
anchor: z.string().trim().min(1).max(2000).optional().describe('Words of the script, as written, it shows on: always placed there.') }))
|
|
43
|
+
.max(60).default([])
|
|
42
44
|
.describe('Your ready ast_* media. Without anchor each goes to the line its name matches, else in order (named in warnings). Nothing is generated.'),
|
|
43
|
-
line_edits: z.array(z.strictObject({ quote: z.string().trim().min(1).max(
|
|
45
|
+
line_edits: z.array(z.strictObject({ quote: z.string().trim().min(1).max(2000).describe('Words of the script, as written.'),
|
|
44
46
|
template: z.strictObject({ kind: z.enum(TEMPLATE_KIND_IDS), data: z.record(z.string(), z.unknown()), placement: z.enum(TEMPLATE_PLACEMENTS).optional() })
|
|
45
47
|
.optional().describe('The graphic on these words, as a make_ugc template: kind, data, placement.') }))
|
|
46
|
-
.max(MAX_TEMPLATES).optional()
|
|
48
|
+
.max(60).refine(edits => edits.filter(edit => edit.template).length <= MAX_TEMPLATES, { message: `at most ${MAX_TEMPLATES} line edits carry a template, the most a run draws` }).optional()
|
|
47
49
|
.describe('The user\'s shot notes: on these words the reference\'s graphics go, and the template shows, if given. Pass all of them on every compile.'),
|
|
48
|
-
site: z.string().min(1).max(
|
|
50
|
+
site: z.string().min(1).max(64).optional().describe('Your site, for an end card on the closing beat.'),
|
|
49
51
|
audience: workflowText(2000).optional().describe('Who watches and whom they trust where, as shown to the user: for example a homeowner ' +
|
|
50
52
|
'who trusts an electrician at the fuse box. Kept in the plan brief.'),
|
|
51
53
|
message: workflowText(4000).optional(),
|
|
@@ -61,6 +63,10 @@ const remakeFields = {
|
|
|
61
63
|
'on screen; true: every beat where they speak or react on screen; false: none. Costs more than the usual take: each shot kind has its own ' +
|
|
62
64
|
'presenter-second price in list_prices, shown on the plan\'s quote.'),
|
|
63
65
|
motion_style: motionStyle.optional().describe('As make_ugc motion_style; spans sit on the script\'s words. Free.'),
|
|
66
|
+
cast: z.array(ugcLiveShotCastMember).min(1).max(MAX_SHOT_CAST)
|
|
67
|
+
.refine(cast => new Set(cast.map(person => person.asset_id)).size === cast.length, { message: 'each cast picture appears once' }).optional()
|
|
68
|
+
.describe(`Up to ${MAX_SHOT_CAST} more people beside the actor in every acting live shot, as make_ugc shots cast: a picture, a role and ` +
|
|
69
|
+
'what they do. Without an acting live shot the plan names it undrawn.'),
|
|
64
70
|
};
|
|
65
71
|
const revisionPair = (value, context) => {
|
|
66
72
|
if ((value.plan_id === undefined) !== (value.base_version === undefined)) {
|
|
@@ -123,6 +129,8 @@ function shotFramingOf(size, take) {
|
|
|
123
129
|
}
|
|
124
130
|
/** 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. */
|
|
125
131
|
const UNDER_CAPTIONS = 0.03;
|
|
132
|
+
/** A full-frame picture or card the reference showed this long or less was flashed, not held (#1196). */
|
|
133
|
+
const FLASH_HOLD_S = 1.2;
|
|
126
134
|
/** 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. */
|
|
127
135
|
const UNMEASURED_CAPTIONS_BELOW = [0.03, 0.15];
|
|
128
136
|
/** The cuts a hook's live shot may take of ours, flash first: both are pictures only, so none adds a sound the customer did not ask for. */
|
|
@@ -195,7 +203,7 @@ export function sceneBriefOf(graphic, asked, site, words, role) {
|
|
|
195
203
|
(role && role !== 'other' ? `; for the ${role.replace(/_/g, ' ')} beat` : '');
|
|
196
204
|
const text = site && /\b(site|url|link|domain|website|web address)\b|\.(com|io|ai|app|co)\b/iu.test(graphic.label_hint) ? site
|
|
197
205
|
: WORD_DEVICES.has(device) ? words.slice(0, SCENE_TEXT_MAX_CHARS) : undefined;
|
|
198
|
-
return { brief: brief.replace(/[\p{Cc}]/gu, ' ')
|
|
206
|
+
return { brief: brief.replace(/[\p{Cc}]/gu, ' '), ...(text ? { text } : {}) };
|
|
199
207
|
}
|
|
200
208
|
/** 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. */
|
|
201
209
|
function referenceFaceOf(profile) {
|
|
@@ -553,6 +561,15 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
553
561
|
gaps.fitted.push(beat);
|
|
554
562
|
return { fit: 'auto', ...(box ? { box } : {}) };
|
|
555
563
|
};
|
|
564
|
+
/** A full-frame picture or card the reference only flashed covers the words its measured time takes at our pace, from the beat's first;
|
|
565
|
+
* the presenter or the next shot says the rest (#1196). */
|
|
566
|
+
const flashed = (element, span) => {
|
|
567
|
+
const shown = element?.entry?.first_visible_s, gone = element?.exit?.last_visible_s;
|
|
568
|
+
if (shown === undefined || gone === undefined || gone - shown > FLASH_HOLD_S)
|
|
569
|
+
return {};
|
|
570
|
+
const found = quote(span.start, Math.max(1, Math.ceil((gone - shown) / (duration / words.length))), span.end);
|
|
571
|
+
return found && found.end < span.end ? { anchor: found.anchor, cover_words: found.end - found.start } : {};
|
|
572
|
+
};
|
|
556
573
|
const spans = planSpans({ profile, words });
|
|
557
574
|
const beatAt = (word) => Math.max(0, spans.findIndex(span => span !== null && word >= span.start && word < span.end));
|
|
558
575
|
/** The first script words a quote of the request names, on the beat holding them. */
|
|
@@ -775,7 +792,7 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
775
792
|
push(field === 'overlays' ? { ...item, position: placed }
|
|
776
793
|
: screen ? { ...item, ...screenSplitOf(measured, graphic.position, index) }
|
|
777
794
|
: field === 'layout_windows' ? { ...item, layout: 'split', ...(measured ? splitOf(measured, index, media) : {}) }
|
|
778
|
-
: { ...item, ...insertFrameOf(measured, asset.media_type, index, media) });
|
|
795
|
+
: { ...item, ...insertFrameOf(measured, asset.media_type, index, media), ...(field === 'inserts' ? flashed(measured, span) : {}) });
|
|
779
796
|
layered = true;
|
|
780
797
|
fullFrame ||= field === 'inserts';
|
|
781
798
|
windowed ||= field === 'layout_windows';
|
|
@@ -805,6 +822,8 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
805
822
|
...(built.placement ? { placement: built.placement } : {}), data: built.data };
|
|
806
823
|
// A measured box over the presenter takes the template whole, drawn as its kind draws with no place of its own (#973 S4, S5).
|
|
807
824
|
const element = elementOf(index, beat.graphics.indexOf(graphic)), definition = TEMPLATE_KINDS.find(entry => entry.kind === built.kind);
|
|
825
|
+
if ((built.placement ?? definition?.placements[0]) === 'full' && !definition?.quotes && !definition?.spreads)
|
|
826
|
+
Object.assign(item, flashed(element, span));
|
|
808
827
|
const { position: _place, ...unplaced } = built.data, own = definition?.data.safeParse(unplaced);
|
|
809
828
|
const staged = element && own?.success && (built.placement ?? definition.placements[0]) === 'over'
|
|
810
829
|
? stagedPlace(element, definition.rect(own.data, DEFAULT_ASPECT_RATIO, undefined, framing)) : undefined;
|
|
@@ -1288,10 +1307,11 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
1288
1307
|
const action = [...new Set([...beat.gestures ?? []].sort((a, b) => a.at_s - b.at_s).flatMap(entry => GESTURE_ACTIONS[entry.kind] ?? []))].join(', then ');
|
|
1289
1308
|
const acted = beat.presenter_behaviour === 'reacting' || !!action;
|
|
1290
1309
|
const wider = shotFramingOf(beat.shot_size, framing);
|
|
1310
|
+
// The request's cast stands beside the actor in every acting shot (#1192): those shots share one setup, so one cast.
|
|
1291
1311
|
const shot = { id: `ref-${index + 1}-shot`, anchor: { startWord: span.start, endWord: span.end },
|
|
1292
1312
|
...beat.presenter_behaviour === 'reacting' ? { kind: 'action', action: action || REACTION }
|
|
1293
1313
|
: action ? { kind: 'talking_action', action } : { kind: 'talking' },
|
|
1294
|
-
...acted && wider ? { framing: wider } : {} };
|
|
1314
|
+
...acted && wider ? { framing: wider } : {}, ...acted && request.cast ? { cast: request.cast } : {} };
|
|
1295
1315
|
return [{ beat: index, words: span.end - span.start, shot }];
|
|
1296
1316
|
});
|
|
1297
1317
|
const kept = new Set([...wanted].sort((a, b) => b.words - a.words || a.beat - b.beat).slice(0, MAX_LIVE_SHOTS));
|
|
@@ -1385,6 +1405,23 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
1385
1405
|
note(0, `captions at the reference's ${referenceAt} height (there they would cover the presenter's face)`, captionPosition !== referenceAt
|
|
1386
1406
|
? `${captionPosition} captions, clear of the face` : byCaptions ? 'kept there: graphics stand by the captions' : 'kept there: no caption height is clear of the face');
|
|
1387
1407
|
}
|
|
1408
|
+
// A clip shorter than its words would freeze on its last frame: it covers only the words it outlasts at the slowest pace, and the presenter
|
|
1409
|
+
// says the rest (#1198). A fit that leaves the run more presenter shots than it renders is undone first, below.
|
|
1410
|
+
const fitted = new Map();
|
|
1411
|
+
for (const layer of layers.filter(entry => entry.field === 'inserts')) {
|
|
1412
|
+
const item = layer.item;
|
|
1413
|
+
const seconds = item.media_type === 'video' && item.asset_id ? facts?.get(item.asset_id)?.seconds : undefined;
|
|
1414
|
+
const range = seconds === undefined ? undefined : layerWordRange(tokens, item);
|
|
1415
|
+
if (seconds === undefined || range === undefined || typeof range === 'string')
|
|
1416
|
+
continue;
|
|
1417
|
+
const most = Math.floor(seconds * WORDS_PER_SECOND_MIN + 1e-9), short = range.coverWord - range.startWord > most
|
|
1418
|
+
? quote(range.startWord, most, range.startWord + most) : undefined;
|
|
1419
|
+
if (!short)
|
|
1420
|
+
continue;
|
|
1421
|
+
fitted.set(layer, { item, line: `the clip on "${'quote' in item.anchor ? item.anchor.quote : short.anchor.quote}" covers its first ` +
|
|
1422
|
+
`${short.end - short.start} words, as long as it runs, so it never freezes; the presenter says the rest` });
|
|
1423
|
+
layer.item = { ...item, anchor: short.anchor, cover_words: short.end - short.start };
|
|
1424
|
+
}
|
|
1388
1425
|
let native = {}, marked = false;
|
|
1389
1426
|
// Every pass that does not break drops one layer or adds the one marker, so the loop ends.
|
|
1390
1427
|
for (;;) {
|
|
@@ -1431,13 +1468,26 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
1431
1468
|
continue;
|
|
1432
1469
|
}
|
|
1433
1470
|
// A move the run refuses goes before the layer does: the refused layer's own, else one on a layer the refusal names.
|
|
1434
|
-
|
|
1471
|
+
// A refusal of a transition takes only the transition it names: the refused layer's own, or the cited layer's "whose …" (#1198 item 7).
|
|
1472
|
+
const passing = /would show (a hidden presenter frame|the presenter between)/.test(issue.message);
|
|
1473
|
+
const blamed = (layer) => {
|
|
1474
|
+
const { keys } = moves.get(layer), id = layer.item.id;
|
|
1475
|
+
if (!passing)
|
|
1476
|
+
return layer === dropped || issue.message.includes(`"${id}"`) ? keys : [];
|
|
1477
|
+
return keys.includes('transition') && (layer === dropped ? issue.message.includes(' has a ') : issue.message.includes(`"${id}", whose `))
|
|
1478
|
+
? ['transition'] : [];
|
|
1479
|
+
};
|
|
1480
|
+
const moved = [dropped, ...moves.keys()].find(layer => moves.has(layer) && blamed(layer).length > 0);
|
|
1435
1481
|
if (moved) {
|
|
1436
|
-
const
|
|
1482
|
+
const entry = moves.get(moved), keys = blamed(moved), item = { ...moved.item };
|
|
1437
1483
|
for (const key of keys)
|
|
1438
1484
|
delete item[key];
|
|
1439
1485
|
moved.item = item;
|
|
1440
|
-
|
|
1486
|
+
const left = entry.keys.filter(key => !keys.includes(key));
|
|
1487
|
+
if (left.length)
|
|
1488
|
+
moves.set(moved, { keys: left, added: entry.added.filter(line => line.key === undefined || left.includes(line.key)) });
|
|
1489
|
+
else
|
|
1490
|
+
moves.delete(moved);
|
|
1441
1491
|
note(moved.beat, `${moved.kind}: its ${keys.map(key => key.replace('_', ' ')).join(' and ')} (${issue.message})`, keys.map(key => key === 'transition' ? 'a cut' : 'a still picture').join(' and '));
|
|
1442
1492
|
continue;
|
|
1443
1493
|
}
|
|
@@ -1461,6 +1511,12 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
1461
1511
|
})) : undefined;
|
|
1462
1512
|
const shots = plan ? countActorShots(plan) : 1;
|
|
1463
1513
|
if (plan && (shots === 0 || shots > MAX_NARRATED_ACTOR_SHOTS)) {
|
|
1514
|
+
const undo = [...fitted.keys()].filter(layer => layers.includes(layer)).at(-1);
|
|
1515
|
+
if (undo) {
|
|
1516
|
+
undo.item = fitted.get(undo).item;
|
|
1517
|
+
fitted.delete(undo);
|
|
1518
|
+
continue;
|
|
1519
|
+
}
|
|
1464
1520
|
const layerOf = (entry) => inserts.find(layer => layer.item.id === entry.insertId);
|
|
1465
1521
|
const candidates = plan.inserts.filter(entry => !requested.has(layerOf(entry)));
|
|
1466
1522
|
if (!candidates.length) {
|
|
@@ -1473,6 +1529,38 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
1473
1529
|
note(dropped.beat, `${dropped.kind} (inserts leave ${shots} actor appearances; a run renders 1 to ${MAX_NARRATED_ACTOR_SHOTS})`, 'left out; the presenter speaks these words');
|
|
1474
1530
|
continue;
|
|
1475
1531
|
}
|
|
1532
|
+
// The quote's take budget (#1197), so the plan is never refused at quote: past it a live shot goes, the one on the fewest words first;
|
|
1533
|
+
// with no shot left, a picture the plan placed itself; with only the request's own pictures left, the compile is refused in the quote's words.
|
|
1534
|
+
const overBudget = ugcMarkedComponentsRefusal(checked.data, executor?.field_dispositions.visible_ai_label === 'implemented');
|
|
1535
|
+
if (overBudget) {
|
|
1536
|
+
const wordsOf = (layer) => { const range = rangeOf(layer); return range ? range.coverWord - range.startWord : 0; };
|
|
1537
|
+
const fewest = (candidates) => candidates.reduce((best, layer) => wordsOf(layer) < wordsOf(best) ? layer : best);
|
|
1538
|
+
const shotLayers = layers.filter(layer => layer.field === 'shots');
|
|
1539
|
+
if (shotLayers.length) {
|
|
1540
|
+
const shot = fewest(shotLayers);
|
|
1541
|
+
layers.splice(layers.indexOf(shot), 1);
|
|
1542
|
+
moves.delete(shot);
|
|
1543
|
+
note(shot.beat, `a live shot on this beat (with it the video makes more presenter takes than the ${MAX_PRESENTER_TAKES} one video ` +
|
|
1544
|
+
'renders, or more than it holds beside your pictures; the shortest shots go first)', 'the presenter\'s usual take');
|
|
1545
|
+
continue;
|
|
1546
|
+
}
|
|
1547
|
+
const pictures = layers.filter(layer => ['inserts', 'overlays', 'layout_windows'].includes(layer.field)
|
|
1548
|
+
|| (layer.field === 'templates' && templateMediaOf([layer.item]).length > 0));
|
|
1549
|
+
const placed = pictures.filter(layer => !requested.has(layer));
|
|
1550
|
+
if (!placed.length) {
|
|
1551
|
+
return { kind: 'invalid', issues: [{ kind: 'invalid_request', path: pictures.map(layer => requested.get(layer)).find(Boolean) ?? 'assets',
|
|
1552
|
+
message: overBudget }] };
|
|
1553
|
+
}
|
|
1554
|
+
const picture = fewest(placed);
|
|
1555
|
+
for (const gone of ours.get(picture) ?? [picture])
|
|
1556
|
+
if (layers.includes(gone)) {
|
|
1557
|
+
layers.splice(layers.indexOf(gone), 1);
|
|
1558
|
+
moves.delete(gone);
|
|
1559
|
+
ours.delete(gone);
|
|
1560
|
+
}
|
|
1561
|
+
note(picture.beat, `${picture.kind} (${overBudget})`, 'left out; the presenter speaks these words');
|
|
1562
|
+
continue;
|
|
1563
|
+
}
|
|
1476
1564
|
// A remake bills on the long-form tariff: with nothing else narrated, a medium framing (scale 1) over the whole script makes it so.
|
|
1477
1565
|
const lead = longForm && !marked && !isNarratedUgcInput(native) ? quote(0, 6, words.length) : undefined;
|
|
1478
1566
|
if (lead) {
|
|
@@ -1482,6 +1570,9 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
1482
1570
|
}
|
|
1483
1571
|
break;
|
|
1484
1572
|
}
|
|
1573
|
+
if (request.cast && !layers.some(layer => layer.field === 'shots' && layer.item.cast)) {
|
|
1574
|
+
note(0, `your cast (${request.cast.map(person => person.role).join(', ')})`, 'not drawn: the plan has no acting live shot, where the reference presenter gestures or reacts');
|
|
1575
|
+
}
|
|
1485
1576
|
const sounded = [...new Set(motion.sounded)].map(beat => beat + 1).join(', ');
|
|
1486
1577
|
if ((motion.sounded.length || request.edit_sounds === true) && !native.edit_sounds) {
|
|
1487
1578
|
note(motion.sounded[0] ?? 0, motion.sounded.length ? `the whoosh and click sounds on beats ${sounded}` : 'edit_sounds, a sound on every cut', !soundsOn ? (request.edit_sounds !== true ? 'none' : longForm ? 'none: edit_sounds is not available on this server' : 'none: edit_sounds needs a long-form run')
|
|
@@ -1489,6 +1580,7 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
1489
1580
|
: 'none unless you ask: compile again with edit_sounds: true for a sound on every cut');
|
|
1490
1581
|
}
|
|
1491
1582
|
const added = [...layers.flatMap(layer => moves.get(layer)?.added ?? []).sort((a, b) => a.beat_index - b.beat_index).map(entry => entry.what),
|
|
1583
|
+
...[...fitted].filter(([layer]) => layers.includes(layer)).map(([, fit]) => fit.line),
|
|
1492
1584
|
...native.edit_sounds ? [`a sound on every cut, as you asked${motion.sounded.length ? `; the reference has a whoosh or a click on beats ${sounded}` : ''}`] : [],
|
|
1493
1585
|
...native.music ? [`music under the voice, as you asked${music >= 0 ? '; the reference has a music bed' : ''}`] : [],
|
|
1494
1586
|
...native.shots ? [`live shots of the presenter on beats ${beatsOf(layers.filter(layer => layer.field === 'shots').map(layer => layer.beat))}, as you asked; ` +
|
|
@@ -1580,7 +1672,7 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
1580
1672
|
reference_profile: { ...withoutWords(profile), music: '', unmapped,
|
|
1581
1673
|
beats: profile.beats.map((beat, index) => ({ ...beat, spoken_summary: '', graphics: beat.graphics.map((graphic, at) => ({ ...graphic, label_hint: '',
|
|
1582
1674
|
...(converted.has(graphic) ? { kind: converted.get(graphic) } : scened.has(`${index}:${at}`) ? { kind: 'scene' } : {}) })) })) },
|
|
1583
|
-
...(added.length ? { added: added.slice(0,
|
|
1675
|
+
...(added.length ? { added: added.slice(0, WORKFLOW_ADDED_MAX) } : {}), ...(request.review_notes?.length ? { review_notes: request.review_notes } : {}) },
|
|
1584
1676
|
beats, claims: [], assets: [] });
|
|
1585
1677
|
return document.success ? { kind: 'compiled', document: document.data }
|
|
1586
1678
|
: { kind: 'invalid', issues: document.error.issues.map(issue => ({ kind: 'invalid_plan', path: issue.path.join('.'), message: issue.message })) };
|