@clipwright/core 0.41.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/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 +10 -0
- 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/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/moves.js +2 -1
- package/dist/moves.js.map +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 +2 -1
- package/dist/reference-profile.js +27 -5
- package/dist/reference-profile.js.map +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 +34 -1
- package/dist/skills.js +34 -5
- package/dist/skills.js.map +1 -1
- package/dist/templates/index.d.ts +8 -0
- package/dist/templates/index.js +10 -0
- package/dist/templates/index.js.map +1 -1
- package/dist/ugc-scenes.js +2 -1
- 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 +98 -7
- 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
|
@@ -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,14 +4,15 @@ 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
18
|
import { MAX_SCENES, SCENE_TEXT_MAX_CHARS } from '../ugc-scenes.js';
|
|
@@ -62,6 +63,10 @@ const remakeFields = {
|
|
|
62
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 ' +
|
|
63
64
|
'presenter-second price in list_prices, shown on the plan\'s quote.'),
|
|
64
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.'),
|
|
65
70
|
};
|
|
66
71
|
const revisionPair = (value, context) => {
|
|
67
72
|
if ((value.plan_id === undefined) !== (value.base_version === undefined)) {
|
|
@@ -124,6 +129,8 @@ function shotFramingOf(size, take) {
|
|
|
124
129
|
}
|
|
125
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. */
|
|
126
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;
|
|
127
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. */
|
|
128
135
|
const UNMEASURED_CAPTIONS_BELOW = [0.03, 0.15];
|
|
129
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. */
|
|
@@ -554,6 +561,15 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
554
561
|
gaps.fitted.push(beat);
|
|
555
562
|
return { fit: 'auto', ...(box ? { box } : {}) };
|
|
556
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
|
+
};
|
|
557
573
|
const spans = planSpans({ profile, words });
|
|
558
574
|
const beatAt = (word) => Math.max(0, spans.findIndex(span => span !== null && word >= span.start && word < span.end));
|
|
559
575
|
/** The first script words a quote of the request names, on the beat holding them. */
|
|
@@ -776,7 +792,7 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
776
792
|
push(field === 'overlays' ? { ...item, position: placed }
|
|
777
793
|
: screen ? { ...item, ...screenSplitOf(measured, graphic.position, index) }
|
|
778
794
|
: field === 'layout_windows' ? { ...item, layout: 'split', ...(measured ? splitOf(measured, index, media) : {}) }
|
|
779
|
-
: { ...item, ...insertFrameOf(measured, asset.media_type, index, media) });
|
|
795
|
+
: { ...item, ...insertFrameOf(measured, asset.media_type, index, media), ...(field === 'inserts' ? flashed(measured, span) : {}) });
|
|
780
796
|
layered = true;
|
|
781
797
|
fullFrame ||= field === 'inserts';
|
|
782
798
|
windowed ||= field === 'layout_windows';
|
|
@@ -806,6 +822,8 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
806
822
|
...(built.placement ? { placement: built.placement } : {}), data: built.data };
|
|
807
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).
|
|
808
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));
|
|
809
827
|
const { position: _place, ...unplaced } = built.data, own = definition?.data.safeParse(unplaced);
|
|
810
828
|
const staged = element && own?.success && (built.placement ?? definition.placements[0]) === 'over'
|
|
811
829
|
? stagedPlace(element, definition.rect(own.data, DEFAULT_ASPECT_RATIO, undefined, framing)) : undefined;
|
|
@@ -1289,10 +1307,11 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
1289
1307
|
const action = [...new Set([...beat.gestures ?? []].sort((a, b) => a.at_s - b.at_s).flatMap(entry => GESTURE_ACTIONS[entry.kind] ?? []))].join(', then ');
|
|
1290
1308
|
const acted = beat.presenter_behaviour === 'reacting' || !!action;
|
|
1291
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.
|
|
1292
1311
|
const shot = { id: `ref-${index + 1}-shot`, anchor: { startWord: span.start, endWord: span.end },
|
|
1293
1312
|
...beat.presenter_behaviour === 'reacting' ? { kind: 'action', action: action || REACTION }
|
|
1294
1313
|
: action ? { kind: 'talking_action', action } : { kind: 'talking' },
|
|
1295
|
-
...acted && wider ? { framing: wider } : {} };
|
|
1314
|
+
...acted && wider ? { framing: wider } : {}, ...acted && request.cast ? { cast: request.cast } : {} };
|
|
1296
1315
|
return [{ beat: index, words: span.end - span.start, shot }];
|
|
1297
1316
|
});
|
|
1298
1317
|
const kept = new Set([...wanted].sort((a, b) => b.words - a.words || a.beat - b.beat).slice(0, MAX_LIVE_SHOTS));
|
|
@@ -1386,6 +1405,23 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
1386
1405
|
note(0, `captions at the reference's ${referenceAt} height (there they would cover the presenter's face)`, captionPosition !== referenceAt
|
|
1387
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');
|
|
1388
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
|
+
}
|
|
1389
1425
|
let native = {}, marked = false;
|
|
1390
1426
|
// Every pass that does not break drops one layer or adds the one marker, so the loop ends.
|
|
1391
1427
|
for (;;) {
|
|
@@ -1432,13 +1468,26 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
1432
1468
|
continue;
|
|
1433
1469
|
}
|
|
1434
1470
|
// A move the run refuses goes before the layer does: the refused layer's own, else one on a layer the refusal names.
|
|
1435
|
-
|
|
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);
|
|
1436
1481
|
if (moved) {
|
|
1437
|
-
const
|
|
1482
|
+
const entry = moves.get(moved), keys = blamed(moved), item = { ...moved.item };
|
|
1438
1483
|
for (const key of keys)
|
|
1439
1484
|
delete item[key];
|
|
1440
1485
|
moved.item = item;
|
|
1441
|
-
|
|
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);
|
|
1442
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 '));
|
|
1443
1492
|
continue;
|
|
1444
1493
|
}
|
|
@@ -1462,6 +1511,12 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
1462
1511
|
})) : undefined;
|
|
1463
1512
|
const shots = plan ? countActorShots(plan) : 1;
|
|
1464
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
|
+
}
|
|
1465
1520
|
const layerOf = (entry) => inserts.find(layer => layer.item.id === entry.insertId);
|
|
1466
1521
|
const candidates = plan.inserts.filter(entry => !requested.has(layerOf(entry)));
|
|
1467
1522
|
if (!candidates.length) {
|
|
@@ -1474,6 +1529,38 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
1474
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');
|
|
1475
1530
|
continue;
|
|
1476
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
|
+
}
|
|
1477
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.
|
|
1478
1565
|
const lead = longForm && !marked && !isNarratedUgcInput(native) ? quote(0, 6, words.length) : undefined;
|
|
1479
1566
|
if (lead) {
|
|
@@ -1483,6 +1570,9 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
1483
1570
|
}
|
|
1484
1571
|
break;
|
|
1485
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
|
+
}
|
|
1486
1576
|
const sounded = [...new Set(motion.sounded)].map(beat => beat + 1).join(', ');
|
|
1487
1577
|
if ((motion.sounded.length || request.edit_sounds === true) && !native.edit_sounds) {
|
|
1488
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')
|
|
@@ -1490,6 +1580,7 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
|
|
|
1490
1580
|
: 'none unless you ask: compile again with edit_sounds: true for a sound on every cut');
|
|
1491
1581
|
}
|
|
1492
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),
|
|
1493
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}` : ''}`] : [],
|
|
1494
1585
|
...native.music ? [`music under the voice, as you asked${music >= 0 ? '; the reference has a music bed' : ''}`] : [],
|
|
1495
1586
|
...native.shots ? [`live shots of the presenter on beats ${beatsOf(layers.filter(layer => layer.field === 'shots').map(layer => layer.beat))}, as you asked; ` +
|