@vosjs/cli 0.18.0 → 0.20.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/README.md +11 -11
- package/dist/{chunk-HYUVNBEM.js → chunk-ZONOZZZY.js} +316 -63
- package/dist/chunk-ZONOZZZY.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +18 -4
- package/dist/index.js +1 -1
- package/dist/{run-R7KDKIVK.js → run-ZIMJOQRN.js} +2 -2
- package/package.json +7 -7
- package/schema/doc.schema.json +163 -296
- package/dist/chunk-HYUVNBEM.js.map +0 -1
- /package/dist/{run-R7KDKIVK.js.map → run-ZIMJOQRN.js.map} +0 -0
|
@@ -9,8 +9,7 @@ import {
|
|
|
9
9
|
import { mkdir as mkdir9, readFile as readFile14, rm as rm4 } from "fs/promises";
|
|
10
10
|
import { existsSync as existsSync14 } from "fs";
|
|
11
11
|
import { join as join17, resolve as resolve9 } from "path";
|
|
12
|
-
import {
|
|
13
|
-
import { migrateHostedDoc as migrateHostedDoc5, ratedSegments as ratedSegments6 } from "@vosjs/studio-core";
|
|
12
|
+
import { docOutputDuration as docOutputDuration3, migrateHostedDoc as migrateHostedDoc5 } from "@vosjs/studio-core";
|
|
14
13
|
|
|
15
14
|
// src/plugin/args.ts
|
|
16
15
|
var UsageError = class extends Error {
|
|
@@ -181,6 +180,13 @@ import {
|
|
|
181
180
|
CAM_SIZE_MAX,
|
|
182
181
|
CAM_SIZE_MIN,
|
|
183
182
|
CAM_SPAN_MIN,
|
|
183
|
+
CARD_ENTER_KINDS,
|
|
184
|
+
CARD_EXIT_KINDS,
|
|
185
|
+
IDLE_KINDS,
|
|
186
|
+
MEDIA_ENTER_KINDS,
|
|
187
|
+
MEDIA_EXIT_KINDS,
|
|
188
|
+
TEXT_ENTER_KINDS,
|
|
189
|
+
TEXT_EXIT_KINDS,
|
|
184
190
|
EXPORT_RESOLUTION_OPTIONS,
|
|
185
191
|
SPEED_RATE_MAX,
|
|
186
192
|
SPEED_RATE_MIN,
|
|
@@ -196,6 +202,79 @@ import {
|
|
|
196
202
|
zoomCoversRect
|
|
197
203
|
} from "@vosjs/studio-core";
|
|
198
204
|
import { TYPEFACE_CATALOG, findFontFamily, findTypeface } from "@vosjs/shared";
|
|
205
|
+
var STEP_KINDS_ALL = [
|
|
206
|
+
"none",
|
|
207
|
+
"fade",
|
|
208
|
+
"rise",
|
|
209
|
+
"pop",
|
|
210
|
+
"blur",
|
|
211
|
+
"typewriter",
|
|
212
|
+
"tilt-in",
|
|
213
|
+
"pull-out",
|
|
214
|
+
"recede"
|
|
215
|
+
];
|
|
216
|
+
var STEP_UNITS = ["block", "line", "word", "char"];
|
|
217
|
+
var STEP_DIRS = ["forward", "reverse", "center"];
|
|
218
|
+
function lintAnim(name, anim, kinds, problems) {
|
|
219
|
+
if (anim === void 0) return;
|
|
220
|
+
if (typeof anim !== "object" || anim === null || Array.isArray(anim)) {
|
|
221
|
+
problems.push(`${name}.anim must be { enter?, exit?, idle? }`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const a = anim;
|
|
225
|
+
for (const side of ["enter", "exit"]) {
|
|
226
|
+
const step = a[side];
|
|
227
|
+
if (step === void 0) continue;
|
|
228
|
+
const allowed = kinds[side];
|
|
229
|
+
const spelled = typeof step === "string" ? step : null;
|
|
230
|
+
const obj = typeof step === "object" && step !== null && !Array.isArray(step) ? step : null;
|
|
231
|
+
const kind = spelled ?? (obj ? obj.kind : void 0);
|
|
232
|
+
if (typeof kind !== "string" || !STEP_KINDS_ALL.includes(kind)) {
|
|
233
|
+
problems.push(
|
|
234
|
+
`${name}.anim.${side} must be a kind or { kind, seconds?${kinds.words ? ", unit?, direction?, stagger?" : ""} } (got ${JSON.stringify(step)})`
|
|
235
|
+
);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (!allowed.includes(kind)) {
|
|
239
|
+
problems.push(
|
|
240
|
+
`${name}.anim.${side} cannot be "${kind}" here; one of ${allowed.join(" | ")}`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
if (!obj) continue;
|
|
244
|
+
const secs = obj.seconds;
|
|
245
|
+
if (secs !== void 0 && (typeof secs !== "number" || !Number.isFinite(secs) || secs < 0.05 || secs > 3)) {
|
|
246
|
+
problems.push(`${name}.anim.${side}.seconds must be 0.05..3`);
|
|
247
|
+
}
|
|
248
|
+
for (const key of ["unit", "direction", "stagger"]) {
|
|
249
|
+
if (obj[key] === void 0) continue;
|
|
250
|
+
if (!kinds.words) {
|
|
251
|
+
problems.push(`${name}.anim.${side}.${key} is for words only`);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (key === "unit" && (typeof obj.unit !== "string" || !STEP_UNITS.includes(obj.unit)))
|
|
255
|
+
problems.push(
|
|
256
|
+
`${name}.anim.${side}.unit must be ${STEP_UNITS.join("|")}`
|
|
257
|
+
);
|
|
258
|
+
if (key === "direction" && (typeof obj.direction !== "string" || !STEP_DIRS.includes(obj.direction)))
|
|
259
|
+
problems.push(
|
|
260
|
+
`${name}.anim.${side}.direction must be ${STEP_DIRS.join("|")}`
|
|
261
|
+
);
|
|
262
|
+
if (key === "stagger" && (typeof obj.stagger !== "number" || obj.stagger < 0 || obj.stagger > 2))
|
|
263
|
+
problems.push(`${name}.anim.${side}.stagger must be seconds in 0..2`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (a.idle !== void 0 && a.idle !== null) {
|
|
267
|
+
if (!kinds.idle) problems.push(`${name}.anim.idle is for props only`);
|
|
268
|
+
else if (typeof a.idle !== "string" || !kinds.idle.includes(a.idle))
|
|
269
|
+
problems.push(
|
|
270
|
+
`${name}.anim.idle must be ${kinds.idle.join(" | ")} | null`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function lintFrom(name, from, problems) {
|
|
275
|
+
if (from !== void 0 && (typeof from !== "string" || !from.length))
|
|
276
|
+
problems.push(`${name}.from must be the id of the template that placed it`);
|
|
277
|
+
}
|
|
199
278
|
var EPS = 1e-3;
|
|
200
279
|
var isNum = (v) => typeof v === "number" && Number.isFinite(v);
|
|
201
280
|
var isObj = (v) => typeof v === "object" && v !== null;
|
|
@@ -327,20 +406,31 @@ function lintDoc(docIn) {
|
|
|
327
406
|
}
|
|
328
407
|
const endCard = doc.endCard;
|
|
329
408
|
if (endCard !== void 0) {
|
|
409
|
+
warnings.push(
|
|
410
|
+
"endCard is a legacy spelling, read as clips after the footage plus a card exit (frame.anim.exit); vos plan writes that shape"
|
|
411
|
+
);
|
|
330
412
|
if (typeof endCard !== "object" || endCard === null) {
|
|
331
|
-
problems.push(
|
|
413
|
+
problems.push(
|
|
414
|
+
"endCard must be an object: {seconds?, headline?, sub?, wordmark?}"
|
|
415
|
+
);
|
|
332
416
|
} else {
|
|
333
417
|
const ec = endCard;
|
|
334
418
|
if (ec.seconds !== void 0 && (!isNum(ec.seconds) || ec.seconds < 1 || ec.seconds > 8)) {
|
|
335
|
-
problems.push(
|
|
419
|
+
problems.push(
|
|
420
|
+
`endCard.seconds must be 1..8 (got ${String(ec.seconds)}); absent = 2.5`
|
|
421
|
+
);
|
|
336
422
|
}
|
|
337
423
|
for (const k of ["headline", "sub", "wordmark"]) {
|
|
338
424
|
if (ec[k] !== void 0 && typeof ec[k] !== "string") {
|
|
339
425
|
problems.push(`endCard.${k} must be a string`);
|
|
340
426
|
}
|
|
341
427
|
}
|
|
342
|
-
if (!["headline", "sub", "wordmark"].some(
|
|
343
|
-
|
|
428
|
+
if (!["headline", "sub", "wordmark"].some(
|
|
429
|
+
(k) => typeof ec[k] === "string" && ec[k].trim()
|
|
430
|
+
)) {
|
|
431
|
+
warnings.push(
|
|
432
|
+
"endCard carries no words: it holds the last frame and recedes the card over nothing"
|
|
433
|
+
);
|
|
344
434
|
}
|
|
345
435
|
}
|
|
346
436
|
}
|
|
@@ -565,6 +655,9 @@ function lintDoc(docIn) {
|
|
|
565
655
|
}
|
|
566
656
|
const ent = frame.entrance;
|
|
567
657
|
if (ent !== void 0) {
|
|
658
|
+
warnings.push(
|
|
659
|
+
"frame.entrance is a legacy spelling; write frame.anim.enter"
|
|
660
|
+
);
|
|
568
661
|
const kinds = ["tilt-in", "pull-out", "rise", "none"];
|
|
569
662
|
if (typeof ent !== "object" || ent === null || !kinds.includes(String(ent.kind))) {
|
|
570
663
|
problems.push(
|
|
@@ -579,6 +672,17 @@ function lintDoc(docIn) {
|
|
|
579
672
|
}
|
|
580
673
|
}
|
|
581
674
|
}
|
|
675
|
+
lintAnim(
|
|
676
|
+
"frame",
|
|
677
|
+
frame.anim,
|
|
678
|
+
{
|
|
679
|
+
enter: CARD_ENTER_KINDS,
|
|
680
|
+
exit: CARD_EXIT_KINDS,
|
|
681
|
+
idle: null,
|
|
682
|
+
words: false
|
|
683
|
+
},
|
|
684
|
+
problems
|
|
685
|
+
);
|
|
582
686
|
if (frame.focusFollow !== void 0 && frame.focusFollow !== "camera") {
|
|
583
687
|
problems.push(
|
|
584
688
|
`frame.focusFollow must be "camera" (got ${String(frame.focusFollow)}); it reads under fit: cover only`
|
|
@@ -826,6 +930,27 @@ function lintDoc(docIn) {
|
|
|
826
930
|
problems.push(`${name}.${key} must be one of ${TRANSITIONS.join("|")}`);
|
|
827
931
|
}
|
|
828
932
|
}
|
|
933
|
+
if (o.enter !== void 0 || o.exit !== void 0 || o.fx !== void 0)
|
|
934
|
+
warnings.push(
|
|
935
|
+
`${name}: enter, exit and fx are legacy spellings; write anim.enter / anim.exit`
|
|
936
|
+
);
|
|
937
|
+
lintAnim(
|
|
938
|
+
name,
|
|
939
|
+
o.anim,
|
|
940
|
+
o.kind === "text" ? {
|
|
941
|
+
enter: TEXT_ENTER_KINDS,
|
|
942
|
+
exit: TEXT_EXIT_KINDS,
|
|
943
|
+
idle: null,
|
|
944
|
+
words: true
|
|
945
|
+
} : {
|
|
946
|
+
enter: MEDIA_ENTER_KINDS,
|
|
947
|
+
exit: MEDIA_EXIT_KINDS,
|
|
948
|
+
idle: null,
|
|
949
|
+
words: false
|
|
950
|
+
},
|
|
951
|
+
problems
|
|
952
|
+
);
|
|
953
|
+
lintFrom(name, o.from, problems);
|
|
829
954
|
if (o.motion !== void 0) {
|
|
830
955
|
if (!Array.isArray(o.motion)) {
|
|
831
956
|
problems.push(`${name}.motion must be an array of poses`);
|
|
@@ -956,6 +1081,20 @@ function lintDoc(docIn) {
|
|
|
956
1081
|
if (o.animation !== void 0 && o.animation !== null && o.animation !== "spin" && o.animation !== "float") {
|
|
957
1082
|
problems.push(`${name}.animation must be "spin" | "float" | null`);
|
|
958
1083
|
}
|
|
1084
|
+
if (o.animation !== void 0)
|
|
1085
|
+
warnings.push(`${name}.animation is a legacy spelling; write anim.idle`);
|
|
1086
|
+
lintAnim(
|
|
1087
|
+
name,
|
|
1088
|
+
o.anim,
|
|
1089
|
+
{
|
|
1090
|
+
enter: MEDIA_ENTER_KINDS,
|
|
1091
|
+
exit: MEDIA_EXIT_KINDS,
|
|
1092
|
+
idle: IDLE_KINDS,
|
|
1093
|
+
words: false
|
|
1094
|
+
},
|
|
1095
|
+
problems
|
|
1096
|
+
);
|
|
1097
|
+
lintFrom(name, o.from, problems);
|
|
959
1098
|
if (o.motion !== void 0) {
|
|
960
1099
|
if (!Array.isArray(o.motion)) {
|
|
961
1100
|
problems.push(`${name}.motion must be an array of poses`);
|
|
@@ -2631,7 +2770,6 @@ async function writeIndexJson(result) {
|
|
|
2631
2770
|
import { mkdir as mkdir4, mkdtemp, rename as rename4, rm as rm3, stat, writeFile as writeFile6 } from "fs/promises";
|
|
2632
2771
|
import { tmpdir } from "os";
|
|
2633
2772
|
import { join as join7, relative, resolve as resolve4 } from "path";
|
|
2634
|
-
import { totalDuration } from "@vosjs/timeline";
|
|
2635
2773
|
import { existsSync as existsSync6 } from "fs";
|
|
2636
2774
|
import { readFile as readFile6 } from "fs/promises";
|
|
2637
2775
|
import { parseFrontmatter } from "@vosjs/shared/frontmatter";
|
|
@@ -2643,7 +2781,7 @@ import {
|
|
|
2643
2781
|
houseLook,
|
|
2644
2782
|
isLookKind,
|
|
2645
2783
|
lookFromBrand,
|
|
2646
|
-
|
|
2784
|
+
docOutputDuration as docOutputDuration2
|
|
2647
2785
|
} from "@vosjs/studio-core";
|
|
2648
2786
|
|
|
2649
2787
|
// src/plugin/renderTake.ts
|
|
@@ -3259,7 +3397,18 @@ function pickMoments(measured, opts = {}) {
|
|
|
3259
3397
|
}
|
|
3260
3398
|
|
|
3261
3399
|
// src/plugin/motionPlan.ts
|
|
3262
|
-
import {
|
|
3400
|
+
import {
|
|
3401
|
+
END_CARD_FROM,
|
|
3402
|
+
END_CARD_RECEDE,
|
|
3403
|
+
END_CARD_SECONDS,
|
|
3404
|
+
applyTemplate,
|
|
3405
|
+
docOutputDuration,
|
|
3406
|
+
dropTemplate,
|
|
3407
|
+
endCardClips,
|
|
3408
|
+
migrateMotion,
|
|
3409
|
+
ratedSegments as ratedSegments4,
|
|
3410
|
+
spanOutputExtent as spanOutputExtent4
|
|
3411
|
+
} from "@vosjs/studio-core";
|
|
3263
3412
|
var SOUND_DESTINATIONS = /* @__PURE__ */ new Set([
|
|
3264
3413
|
"x-feed-cut",
|
|
3265
3414
|
"youtube-main-demo",
|
|
@@ -3294,41 +3443,103 @@ var outputLength = (doc) => ratedSegments4(doc).reduce((acc, s) => {
|
|
|
3294
3443
|
const rate = s.rate && s.rate > 0 ? s.rate : 1;
|
|
3295
3444
|
return acc + (s.out - s.in) / rate;
|
|
3296
3445
|
}, 0);
|
|
3446
|
+
function templateWords(words) {
|
|
3447
|
+
const headline = (words.headline ?? "").trim();
|
|
3448
|
+
const kicker = (words.kicker ?? "").trim();
|
|
3449
|
+
const brand = (words.brand ?? "").trim();
|
|
3450
|
+
const sub = [brand, (words.release ?? "").trim()].filter(Boolean).join(" ");
|
|
3451
|
+
const out = {};
|
|
3452
|
+
if (headline) {
|
|
3453
|
+
out["stage-title"] = headline;
|
|
3454
|
+
out["endcard-title"] = headline;
|
|
3455
|
+
}
|
|
3456
|
+
if (kicker) out["stage-kicker"] = kicker;
|
|
3457
|
+
if (brand) {
|
|
3458
|
+
out["stage-brand"] = brand;
|
|
3459
|
+
out["endcard-mark"] = brand;
|
|
3460
|
+
}
|
|
3461
|
+
if (sub && sub !== headline) out["endcard-sub"] = sub;
|
|
3462
|
+
return out;
|
|
3463
|
+
}
|
|
3464
|
+
function resolveAnchor(doc, at2) {
|
|
3465
|
+
if (typeof at2 !== "object") return at2;
|
|
3466
|
+
const steps = doc.source.meta.steps ?? [];
|
|
3467
|
+
const step = steps.find((s) => s.id === at2.step || String(s.step) === at2.step);
|
|
3468
|
+
if (!step || step.skipped) return null;
|
|
3469
|
+
const t = stepOutputTime(ratedSegments4(doc), step, 0.2);
|
|
3470
|
+
return t === null ? null : +t.toFixed(3);
|
|
3471
|
+
}
|
|
3472
|
+
var anchorWord = (at2) => typeof at2 === "object" ? `step ${at2.step}` : typeof at2 === "number" ? `${at2}s` : `the ${at2}`;
|
|
3473
|
+
var onWord = (v) => v === void 0 || /^(on|yes|true)$/i.test(v.trim());
|
|
3297
3474
|
function proposeMotion(input, opts) {
|
|
3298
|
-
|
|
3475
|
+
let doc = migrateMotion(structuredClone(input));
|
|
3299
3476
|
const { words, launch, catalog } = opts;
|
|
3300
3477
|
const notes = [];
|
|
3301
3478
|
const skipped = [];
|
|
3302
|
-
const
|
|
3303
|
-
const range = [0,
|
|
3479
|
+
const footage = outputLength(doc);
|
|
3480
|
+
const range = [0, footage];
|
|
3304
3481
|
const entrance = launch.entrance;
|
|
3482
|
+
delete doc.frame.entrance;
|
|
3305
3483
|
if (!off(entrance)) {
|
|
3306
|
-
const kind = entrance && /^(tilt-in|pull-out|rise)$/.test(entrance.trim()) ? entrance.trim() : "tilt-in";
|
|
3307
|
-
doc.frame.
|
|
3308
|
-
notes.push(`
|
|
3309
|
-
} else {
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3484
|
+
const kind = entrance && /^(tilt-in|pull-out|rise|fade)$/.test(entrance.trim()) ? entrance.trim() : "tilt-in";
|
|
3485
|
+
doc.frame.anim = { ...doc.frame.anim ?? {}, enter: kind };
|
|
3486
|
+
notes.push(`enter ${kind}`);
|
|
3487
|
+
} else if (doc.frame.anim?.enter !== void 0) {
|
|
3488
|
+
const { enter: _enter, ...rest } = doc.frame.anim;
|
|
3489
|
+
if (Object.keys(rest).length) doc.frame.anim = rest;
|
|
3490
|
+
else delete doc.frame.anim;
|
|
3491
|
+
}
|
|
3492
|
+
const tWords = templateWords(words);
|
|
3493
|
+
const keys = opts.mark ? { "stage-mark": opts.mark.key, "endcard-markimg": opts.mark.key } : void 0;
|
|
3494
|
+
for (const t of opts.templates ?? []) {
|
|
3495
|
+
const at2 = resolveAnchor(doc, t.at);
|
|
3496
|
+
if (at2 === null) {
|
|
3497
|
+
skipped.push(`${t.from}: ${anchorWord(t.at)} was not recorded`);
|
|
3498
|
+
continue;
|
|
3499
|
+
}
|
|
3500
|
+
const applied = applyTemplate(t.doc, doc, {
|
|
3501
|
+
at: at2,
|
|
3502
|
+
from: t.from,
|
|
3503
|
+
words: tWords,
|
|
3504
|
+
keys
|
|
3505
|
+
});
|
|
3506
|
+
doc = applied.doc;
|
|
3507
|
+
notes.push(`${t.from} at ${anchorWord(t.at)}`);
|
|
3508
|
+
for (const n of applied.notes) skipped.push(`${t.from}: ${n}`);
|
|
3509
|
+
}
|
|
3510
|
+
const endCardRole = launch.endCard;
|
|
3511
|
+
doc = dropTemplate(doc, END_CARD_FROM);
|
|
3512
|
+
if (off(endCardRole)) {
|
|
3513
|
+
if (doc.frame.anim?.exit !== void 0) {
|
|
3514
|
+
const { exit: _exit, ...rest } = doc.frame.anim;
|
|
3515
|
+
if (Object.keys(rest).length) doc.frame.anim = rest;
|
|
3516
|
+
else delete doc.frame.anim;
|
|
3517
|
+
}
|
|
3518
|
+
} else if (onWord(endCardRole)) {
|
|
3313
3519
|
const headline = (words.headline ?? "").trim();
|
|
3314
3520
|
const brand = (words.brand ?? "").trim();
|
|
3315
3521
|
const sub = [brand, (words.release ?? "").trim()].filter(Boolean).join(" ");
|
|
3316
3522
|
if (headline || brand) {
|
|
3317
|
-
const card = { seconds:
|
|
3523
|
+
const card = { seconds: END_CARD_SECONDS };
|
|
3318
3524
|
if (opts.ink) card.ink = opts.ink;
|
|
3319
3525
|
if (headline) card.headline = headline;
|
|
3320
3526
|
if (sub && sub !== headline) card.sub = sub;
|
|
3321
3527
|
if (brand) card.wordmark = brand;
|
|
3322
3528
|
if (opts.mark) card.mark = opts.mark;
|
|
3323
|
-
doc.
|
|
3529
|
+
doc.overlays = [
|
|
3530
|
+
...doc.overlays ?? [],
|
|
3531
|
+
...endCardClips(card, doc, outputLength(doc))
|
|
3532
|
+
];
|
|
3533
|
+
doc.frame.anim = {
|
|
3534
|
+
...doc.frame.anim ?? {},
|
|
3535
|
+
exit: { kind: "recede", seconds: END_CARD_RECEDE }
|
|
3536
|
+
};
|
|
3324
3537
|
notes.push("end card");
|
|
3325
3538
|
} else {
|
|
3326
3539
|
skipped.push(
|
|
3327
3540
|
"no end card (no headline or wordmark in LAUNCH.md, BRAND.md or the flags)"
|
|
3328
3541
|
);
|
|
3329
3542
|
}
|
|
3330
|
-
} else {
|
|
3331
|
-
delete doc.endCard;
|
|
3332
3543
|
}
|
|
3333
3544
|
const kept = (doc.overlays ?? []).filter(
|
|
3334
3545
|
(o) => !o.id.startsWith(CAPTION_ID_PREFIX)
|
|
@@ -3343,17 +3554,16 @@ function proposeMotion(input, opts) {
|
|
|
3343
3554
|
);
|
|
3344
3555
|
if (!step || step.skipped) continue;
|
|
3345
3556
|
const t = stepOutputTime(rated, step, 0.2);
|
|
3346
|
-
if (t === null || t < 0 || t >
|
|
3557
|
+
if (t === null || t < 0 || t > footage - 1) continue;
|
|
3347
3558
|
captionClips.push({
|
|
3348
3559
|
id: `${CAPTION_ID_PREFIX}${c.step}`,
|
|
3349
3560
|
kind: "text",
|
|
3350
3561
|
text: c.caption,
|
|
3351
3562
|
preset: "caption",
|
|
3352
3563
|
start: +t.toFixed(3),
|
|
3353
|
-
duration: Math.min(3.5, Math.max(2.5,
|
|
3564
|
+
duration: Math.min(3.5, Math.max(2.5, footage - t - 0.2)),
|
|
3354
3565
|
transform: { x: 0.5, y: 0.86, scale: 1, rotation: 0 },
|
|
3355
|
-
enter: "rise",
|
|
3356
|
-
exit: "fade",
|
|
3566
|
+
anim: { enter: "rise", exit: "fade" },
|
|
3357
3567
|
align: "center",
|
|
3358
3568
|
box: { color: "rgba(17,17,17,0.72)" }
|
|
3359
3569
|
});
|
|
@@ -3363,6 +3573,7 @@ function proposeMotion(input, opts) {
|
|
|
3363
3573
|
const overlays = [...kept, ...captionClips];
|
|
3364
3574
|
if (overlays.length) doc.overlays = overlays;
|
|
3365
3575
|
else delete doc.overlays;
|
|
3576
|
+
const length = docOutputDuration(doc);
|
|
3366
3577
|
const clips = (doc.audio ?? []).filter(
|
|
3367
3578
|
(a) => a.id !== BED_ID && !a.id.startsWith(CLICK_ID_PREFIX)
|
|
3368
3579
|
);
|
|
@@ -3422,8 +3633,8 @@ function destinationMechanics(d, doc) {
|
|
|
3422
3633
|
const sound = SOUND_DESTINATIONS.has(d.id);
|
|
3423
3634
|
const portrait = d.px.w / d.px.h < 0.9;
|
|
3424
3635
|
if (loop) {
|
|
3425
|
-
unset.push("frame.entrance", "endCard");
|
|
3426
|
-
notes.push("loop: no
|
|
3636
|
+
unset.push("frame.anim", "frame.entrance", "endCard");
|
|
3637
|
+
notes.push("loop: no card motion");
|
|
3427
3638
|
}
|
|
3428
3639
|
if (loop || !sound) {
|
|
3429
3640
|
set.push("audio=[]");
|
|
@@ -3431,11 +3642,11 @@ function destinationMechanics(d, doc) {
|
|
|
3431
3642
|
}
|
|
3432
3643
|
if (loop || d.text === "none") {
|
|
3433
3644
|
const kept = (doc.overlays ?? []).filter(
|
|
3434
|
-
(o) => !o.id.startsWith(CAPTION_ID_PREFIX)
|
|
3645
|
+
(o) => !o.id.startsWith(CAPTION_ID_PREFIX) && !(loop && o.from)
|
|
3435
3646
|
);
|
|
3436
3647
|
if (kept.length !== (doc.overlays ?? []).length) {
|
|
3437
3648
|
set.push(`overlays=${JSON.stringify(kept)}`);
|
|
3438
|
-
notes.push("no captions");
|
|
3649
|
+
notes.push(loop ? "no template clips, no captions" : "no captions");
|
|
3439
3650
|
}
|
|
3440
3651
|
}
|
|
3441
3652
|
if (portrait) {
|
|
@@ -3959,7 +4170,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
3959
4170
|
const take = await loadTake(dir);
|
|
3960
4171
|
if (!take.doc) throw new Error(`${dir} has no doc.json \u2014 run plan first`);
|
|
3961
4172
|
const doc = take.doc;
|
|
3962
|
-
const duration =
|
|
4173
|
+
const duration = docOutputDuration2(doc);
|
|
3963
4174
|
const videoSeconds = opts.range ? Math.min(opts.range[1], duration) - Math.min(opts.range[0], duration) : duration;
|
|
3964
4175
|
const outDir = resolve4(opts.outDir ?? join7(dir, "kit"));
|
|
3965
4176
|
await mkdir4(outDir, { recursive: true });
|
|
@@ -4017,7 +4228,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4017
4228
|
}
|
|
4018
4229
|
posterCardIds.add(d.id);
|
|
4019
4230
|
const label = `${d.channel} ${d.asset}`;
|
|
4020
|
-
const posterDuration =
|
|
4231
|
+
const posterDuration = docOutputDuration2(ref.doc);
|
|
4021
4232
|
const time = posterStillTime(ref.doc, posterDuration);
|
|
4022
4233
|
opts.onPhase?.(
|
|
4023
4234
|
`${label} (${specWords(d)}) from ${ref.from}${ref.vosId ? ` ${ref.vosId}` : ""}, the rest at ${time.toFixed(2)}s`
|
|
@@ -5573,7 +5784,8 @@ import {
|
|
|
5573
5784
|
planAutoSpeed,
|
|
5574
5785
|
planAutoZoom,
|
|
5575
5786
|
projectFromArtifact,
|
|
5576
|
-
withBackdrop
|
|
5787
|
+
withBackdrop,
|
|
5788
|
+
migrateMotion as migrateMotion2
|
|
5577
5789
|
} from "@vosjs/studio-core";
|
|
5578
5790
|
|
|
5579
5791
|
// src/plugin/reuse.ts
|
|
@@ -5635,7 +5847,7 @@ function buildStepMap(oldSteps, newSteps, oldDuration, newDuration) {
|
|
|
5635
5847
|
};
|
|
5636
5848
|
return { map, unmatched, matched };
|
|
5637
5849
|
}
|
|
5638
|
-
function
|
|
5850
|
+
function resolveAnchor2(anchor, newSteps) {
|
|
5639
5851
|
const step = typeof anchor.step === "string" ? newSteps.find((s) => s.id === anchor.step) : newSteps.find((s) => s.step === anchor.step);
|
|
5640
5852
|
if (!step || step.skipped) return null;
|
|
5641
5853
|
const base = anchor.at === "end" ? step.tEnd : step.tStart;
|
|
@@ -5647,7 +5859,7 @@ function retimeSpans(kind, spans, stepMap, newSteps, newDuration, report) {
|
|
|
5647
5859
|
const length = span.out - span.in;
|
|
5648
5860
|
let nextIn = null;
|
|
5649
5861
|
if (span.anchor) {
|
|
5650
|
-
nextIn =
|
|
5862
|
+
nextIn = resolveAnchor2(span.anchor, newSteps);
|
|
5651
5863
|
if (nextIn === null) {
|
|
5652
5864
|
report.flagged.push(
|
|
5653
5865
|
`${kind} ${span.id}: its anchored step (${String(span.anchor.step)}) is missing or skipped in the new recording \u2014 fell back to the step map`
|
|
@@ -5796,7 +6008,7 @@ async function planTake(dir, opts = {}) {
|
|
|
5796
6008
|
let fresh;
|
|
5797
6009
|
let layout;
|
|
5798
6010
|
if (opts.reuse) {
|
|
5799
|
-
const prev = opts.reuse.doc;
|
|
6011
|
+
const prev = migrateMotion2(opts.reuse.doc);
|
|
5800
6012
|
const artifact = {
|
|
5801
6013
|
videoKey: RECORDING_NAME,
|
|
5802
6014
|
cursor,
|
|
@@ -5831,7 +6043,6 @@ async function planTake(dir, opts = {}) {
|
|
|
5831
6043
|
if (prev.objects?.length) doc.objects = prev.objects;
|
|
5832
6044
|
if (prev.audio.length) doc.audio = prev.audio;
|
|
5833
6045
|
if (prev.camMotion?.length) doc.camMotion = prev.camMotion;
|
|
5834
|
-
if (prev.endCard) doc.endCard = structuredClone(prev.endCard);
|
|
5835
6046
|
await writeJson(take.paths.doc, doc, true);
|
|
5836
6047
|
return {
|
|
5837
6048
|
doc,
|
|
@@ -8529,7 +8740,7 @@ var HELP = `vos \u2014 record a browser flow, plan effects, render a product vid
|
|
|
8529
8740
|
Take pipeline
|
|
8530
8741
|
vos create --actions actions.json [--url <url>] [--out take] [out.webm] [--strict] [--max-duration <s>] [--background <slug|url|none>] [render flags] [--json]
|
|
8531
8742
|
vos record --actions actions.json [--url <url>] [--out take] [--strict] [--max-duration <s>] [--background <slug|url|none>] [--json]
|
|
8532
|
-
vos plan <take> [--fresh] [--reuse [--from <doc.json>]] [--style <doc.json|vosId>] [--background <slug|url|none>] [--motion] [--headline "\u2026"] [--kicker "\u2026"] [--launch LAUNCH.md] [--brand BRAND.md] [--music <slug|mood|none>] [--entrance tilt-in|pull-out|rise|none] [--end-card none] [--captions none] [--clicks none] [--release v2.1] [--json]
|
|
8743
|
+
vos plan <take> [--fresh] [--reuse [--from <doc.json>]] [--style <doc.json|vosId>] [--with <doc.json|vosId>[@end|@start|@step:<id>|@<s>]]... [--background <slug|url|none>] [--motion] [--headline "\u2026"] [--kicker "\u2026"] [--launch LAUNCH.md] [--brand BRAND.md] [--music <slug|mood|none>] [--entrance tilt-in|pull-out|rise|fade|none] [--end-card on|none|<doc.json|vosId>] [--captions none] [--clicks none] [--release v2.1] [--json]
|
|
8533
8744
|
vos render <take> [out.webm] [--width] [--height] [--fps] [--format webm|mp4] [--parallel N] [--range a..b] [--draft] [--frame <kind>] [--background <url|slug>] [--set <path=value>]... [--json]
|
|
8534
8745
|
vos frames <take> [--times 0,25%,50%,75%,100%] [--frame <t>] [--at-zooms] [--at-moments] [--size WxH] [--out dir] [--background <url|slug>] [--set <path=value>]... [--json]
|
|
8535
8746
|
vos deliver <take> --to cws,producthunt,x,linkedin,og,github,youtube (or all) [--launch LAUNCH.md] [--look plate|gradient|dark|none] [--brand BRAND.md] [--composed] [--set path=value] [--release v2.1] [--out dir] [--times a,b] [--range a..b] [--parallel N] [--json]
|
|
@@ -8658,19 +8869,27 @@ made with plan --style <poster>, which copies a poster's layout onto a
|
|
|
8658
8869
|
take (its card placement, its stage clips with the release's words
|
|
8659
8870
|
patched in from LAUNCH.md's headline and kicker roles or --headline and
|
|
8660
8871
|
--kicker, its rest lean, its hold), or by hand in doc.json.
|
|
8661
|
-
The cut's MOTION is the document's too
|
|
8662
|
-
(
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8872
|
+
The cut's MOTION is the document's too, in ONE vocabulary: every visual
|
|
8873
|
+
thing (the card, a text, image or video clip, a prop) carries anim.enter,
|
|
8874
|
+
anim.exit and anim.idle; the output lasts until the last clip ends, and
|
|
8875
|
+
past its footage the card holds its last frame at the pose its exit
|
|
8876
|
+
settled into. There is no end-card field and no entrance field: a
|
|
8877
|
+
COMPONENT is a TEMPLATE, a plain take on a shelf whose clips carry stable
|
|
8878
|
+
ids, laid onto the take at an anchor (LAUNCH.md with: <ref>[@end|@start|
|
|
8879
|
+
@step:<id>|@<s>], comma list; --with the same, repeatable; endCard: <ref>
|
|
8880
|
+
names one at the end), its clips stamped with where they came from
|
|
8881
|
+
(from). plan proposes on a fresh plan (--motion re-proposes, replacing
|
|
8882
|
+
only its own work): the card's ENTER (tilt-in by default), the templates
|
|
8883
|
+
named, the house END CARD when endCard is on or absent (clips after the
|
|
8884
|
+
footage: the headline, the release line, the wordmark, the mark from
|
|
8885
|
+
BRAND.md logoUrl, over a card that recedes; from: endcard), a CAPTION per
|
|
8886
|
+
actions.json step at the step's moment, a music BED from LAUNCH.md's music
|
|
8887
|
+
role (a catalog slug or a mood) and a click sound on every press when the
|
|
8888
|
+
take has no mic. LAUNCH.md's entrance, endCard, captions, music and clicks
|
|
8889
|
+
roles, or the flags, change or switch each off; a deleted proposal stays
|
|
8890
|
+
deleted on a refresh. deliver applies each destination's MECHANICS and
|
|
8891
|
+
nothing more: the README loop drops the card's motion, every clip a
|
|
8892
|
+
template placed and every sound; a channel that autoplays muted drops the
|
|
8674
8893
|
bed; the 9:16 cut is a reframe, not a letterbox, and the crop follows
|
|
8675
8894
|
the camera. Screenshot-genre
|
|
8676
8895
|
destinations (CWS screenshots, the PH gallery) are the real page at that
|
|
@@ -8981,11 +9200,15 @@ async function cmdCreate2(argv) {
|
|
|
8981
9200
|
}
|
|
8982
9201
|
}
|
|
8983
9202
|
async function cmdPlan(argv) {
|
|
8984
|
-
const { positionals, flags } = parseArgs(
|
|
9203
|
+
const { positionals, flags, multi } = parseArgs(
|
|
9204
|
+
argv,
|
|
9205
|
+
BOOLEAN_FLAGS5,
|
|
9206
|
+
/* @__PURE__ */ new Set(["with"])
|
|
9207
|
+
);
|
|
8985
9208
|
const dir = positionals[0];
|
|
8986
9209
|
if (!dir)
|
|
8987
9210
|
throw new UsageError(
|
|
8988
|
-
'vos plan <take> [--fresh] [--reuse [--from <doc.json>]] [--style <doc.json|vosId>] [--motion] [--headline "\u2026"] [--launch LAUNCH.md] [--brand BRAND.md]'
|
|
9211
|
+
'vos plan <take> [--fresh] [--reuse [--from <doc.json>]] [--style <doc.json|vosId>] [--with <doc.json|vosId>[@end|@start|@step:<id>|@<seconds>]]... [--motion] [--headline "\u2026"] [--launch LAUNCH.md] [--brand BRAND.md]'
|
|
8989
9212
|
);
|
|
8990
9213
|
const r = createReporter(flags.json === true);
|
|
8991
9214
|
if (flags.fresh === true) {
|
|
@@ -9008,7 +9231,7 @@ async function cmdPlan(argv) {
|
|
|
9008
9231
|
const style = await resolveStyleRef(flags);
|
|
9009
9232
|
const hasDoc = existsSync14(join17(dir, "doc.json"));
|
|
9010
9233
|
const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
|
|
9011
|
-
const release = await releaseInputs(dir, flags, r);
|
|
9234
|
+
const release = await releaseInputs(dir, flags, r, multi.with ?? []);
|
|
9012
9235
|
const motionWanted = !hasDoc || flags.motion === true || flags.fresh === true;
|
|
9013
9236
|
const s = await planTake(dir, {
|
|
9014
9237
|
...style ? { style } : {},
|
|
@@ -9024,6 +9247,7 @@ async function cmdPlan(argv) {
|
|
|
9024
9247
|
mark: release.mark,
|
|
9025
9248
|
captions: release.captions,
|
|
9026
9249
|
catalog: release.catalog,
|
|
9250
|
+
templates: release.templates,
|
|
9027
9251
|
again: flags.motion === true
|
|
9028
9252
|
}
|
|
9029
9253
|
} : {}
|
|
@@ -9060,7 +9284,7 @@ async function cmdPlan(argv) {
|
|
|
9060
9284
|
);
|
|
9061
9285
|
return EXIT_OK;
|
|
9062
9286
|
}
|
|
9063
|
-
async function releaseInputs(dir, flags, r) {
|
|
9287
|
+
async function releaseInputs(dir, flags, r, withRefs = []) {
|
|
9064
9288
|
let lookPick;
|
|
9065
9289
|
try {
|
|
9066
9290
|
lookPick = await resolveLook(dir, {
|
|
@@ -9115,13 +9339,36 @@ async function releaseInputs(dir, flags, r) {
|
|
|
9115
9339
|
const step = st;
|
|
9116
9340
|
return typeof step.caption === "string" && step.caption.trim() ? [{ step: i, id: step.id, caption: step.caption.trim() }] : [];
|
|
9117
9341
|
});
|
|
9342
|
+
const refs = [];
|
|
9343
|
+
const parseRef = (raw, fallback) => {
|
|
9344
|
+
const m = /^(.*?)(?:@(end|start|step:[^@\s]+|\d+(?:\.\d+)?))?$/.exec(
|
|
9345
|
+
raw.trim()
|
|
9346
|
+
);
|
|
9347
|
+
const ref = m?.[1]?.trim() ?? raw.trim();
|
|
9348
|
+
const anchor = m?.[2];
|
|
9349
|
+
const at2 = !anchor ? fallback : anchor === "end" || anchor === "start" ? anchor : anchor.startsWith("step:") ? { step: anchor.slice(5) } : Number(anchor);
|
|
9350
|
+
if (ref) refs.push({ ref, at: at2 });
|
|
9351
|
+
};
|
|
9352
|
+
for (const raw of withRefs) parseRef(raw, "end");
|
|
9353
|
+
for (const raw of (launchRoles.with ?? "").split(","))
|
|
9354
|
+
if (raw.trim()) parseRef(raw, "end");
|
|
9355
|
+
const endCardRole = launchRoles.endCard;
|
|
9356
|
+
if (endCardRole && !/^(none|off|no|false|on|yes|true)$/i.test(endCardRole.trim()))
|
|
9357
|
+
parseRef(endCardRole, "end");
|
|
9358
|
+
const templates = [];
|
|
9359
|
+
for (const { ref, at: at2 } of refs) {
|
|
9360
|
+
const found = await resolveDocRef(ref, flags, "--with");
|
|
9361
|
+
templates.push({ from: found.from, doc: found.doc, at: at2 });
|
|
9362
|
+
r.log(`template: ${found.from}`);
|
|
9363
|
+
}
|
|
9118
9364
|
return {
|
|
9119
9365
|
words,
|
|
9120
9366
|
launchRoles,
|
|
9121
9367
|
ink: endCardInk(lookPick.look, lookPick.roles),
|
|
9122
9368
|
mark,
|
|
9123
9369
|
captions,
|
|
9124
|
-
catalog
|
|
9370
|
+
catalog,
|
|
9371
|
+
templates
|
|
9125
9372
|
};
|
|
9126
9373
|
}
|
|
9127
9374
|
async function cmdRender(argv) {
|
|
@@ -9216,7 +9463,7 @@ async function cmdFrames(argv) {
|
|
|
9216
9463
|
if (!m) throw new UsageError("--size expects WxH (e.g. --size 1280x800)");
|
|
9217
9464
|
size = { width: Number(m[1]), height: Number(m[2]) };
|
|
9218
9465
|
}
|
|
9219
|
-
const duration =
|
|
9466
|
+
const duration = docOutputDuration3(take.doc);
|
|
9220
9467
|
const frameRaw = strFlag(flags, "frame");
|
|
9221
9468
|
const timesRaw = strFlag(flags, "times");
|
|
9222
9469
|
const atZooms = flags["at-zooms"] === true;
|
|
@@ -9303,7 +9550,7 @@ async function cmdDeliver(argv) {
|
|
|
9303
9550
|
}
|
|
9304
9551
|
const take = await loadTake(dir);
|
|
9305
9552
|
if (!take.doc) throw new UsageError(`${dir} has no doc.json \u2014 run plan first`);
|
|
9306
|
-
const duration =
|
|
9553
|
+
const duration = docOutputDuration3(take.doc);
|
|
9307
9554
|
const timesRaw = strFlag(flags, "times");
|
|
9308
9555
|
let times;
|
|
9309
9556
|
if (timesRaw !== void 0) {
|
|
@@ -9381,6 +9628,10 @@ Store uploads stay manual: hand the human this directory and the manifest.`
|
|
|
9381
9628
|
async function resolveStyleRef(flags) {
|
|
9382
9629
|
const styleRef = strFlag(flags, "style");
|
|
9383
9630
|
if (!styleRef) return null;
|
|
9631
|
+
return resolveDocRef(styleRef, flags, "--style");
|
|
9632
|
+
}
|
|
9633
|
+
async function resolveDocRef(ref, flags, what) {
|
|
9634
|
+
const styleRef = ref;
|
|
9384
9635
|
const file = existsSync14(styleRef) ? resolve9(
|
|
9385
9636
|
styleRef,
|
|
9386
9637
|
existsSync14(join17(styleRef, "doc.json")) ? "doc.json" : ""
|
|
@@ -9388,7 +9639,9 @@ async function resolveStyleRef(flags) {
|
|
|
9388
9639
|
if (file && existsSync14(file)) {
|
|
9389
9640
|
return {
|
|
9390
9641
|
from: file,
|
|
9391
|
-
doc:
|
|
9642
|
+
doc: migrateHostedDoc5(
|
|
9643
|
+
JSON.parse(await readFile14(file, "utf8"))
|
|
9644
|
+
)
|
|
9392
9645
|
};
|
|
9393
9646
|
}
|
|
9394
9647
|
const origin = platformOrigin({
|
|
@@ -9400,7 +9653,7 @@ async function resolveStyleRef(flags) {
|
|
|
9400
9653
|
const head = meta.body.vos?.currentVersionId;
|
|
9401
9654
|
if (meta.status !== 200 || !head)
|
|
9402
9655
|
throw new UsageError(
|
|
9403
|
-
|
|
9656
|
+
`${what}: ${styleRef} is neither a doc.json nor a vos I can read`
|
|
9404
9657
|
);
|
|
9405
9658
|
const doc = await apiJson(
|
|
9406
9659
|
origin,
|
|
@@ -9408,7 +9661,7 @@ async function resolveStyleRef(flags) {
|
|
|
9408
9661
|
{ key }
|
|
9409
9662
|
);
|
|
9410
9663
|
if (doc.status !== 200)
|
|
9411
|
-
throw new UsageError(
|
|
9664
|
+
throw new UsageError(`${what}: ${styleRef} carries no doc (is it a take?)`);
|
|
9412
9665
|
return {
|
|
9413
9666
|
from: `${origin}/vos/${styleRef}`,
|
|
9414
9667
|
doc: migrateHostedDoc5(doc.body)
|
|
@@ -9832,4 +10085,4 @@ export {
|
|
|
9832
10085
|
convertAgentBrowser,
|
|
9833
10086
|
run
|
|
9834
10087
|
};
|
|
9835
|
-
//# sourceMappingURL=chunk-
|
|
10088
|
+
//# sourceMappingURL=chunk-ZONOZZZY.js.map
|