@vosjs/cli 0.19.0 → 0.21.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 +12 -12
- package/dist/{chunk-4LLBPFCW.js → chunk-4IWHZBIY.js} +247 -70
- package/dist/chunk-4IWHZBIY.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +18 -22
- package/dist/index.js +1 -1
- package/dist/{run-PJOOKJE2.js → run-7AOC4JUE.js} +2 -2
- package/package.json +5 -5
- package/schema/doc.schema.json +51 -2
- package/dist/chunk-4LLBPFCW.js.map +0 -1
- /package/dist/{run-PJOOKJE2.js.map → run-7AOC4JUE.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 {
|
|
@@ -189,6 +188,8 @@ import {
|
|
|
189
188
|
TEXT_ENTER_KINDS,
|
|
190
189
|
TEXT_EXIT_KINDS,
|
|
191
190
|
EXPORT_RESOLUTION_OPTIONS,
|
|
191
|
+
FREEZE_SECONDS_MAX,
|
|
192
|
+
FREEZE_SECONDS_MIN,
|
|
192
193
|
SPEED_RATE_MAX,
|
|
193
194
|
SPEED_RATE_MIN,
|
|
194
195
|
TILT_DEG_MAX,
|
|
@@ -399,14 +400,45 @@ function lintDoc(docIn) {
|
|
|
399
400
|
}
|
|
400
401
|
for (const [i, seg] of (Array.isArray(doc.segments) ? doc.segments : []).entries()) {
|
|
401
402
|
const hold = seg.hold;
|
|
402
|
-
if (hold
|
|
403
|
+
if (hold === void 0) continue;
|
|
404
|
+
if (!isNum(hold) || hold < 0 || hold > FREEZE_SECONDS_MAX) {
|
|
403
405
|
problems.push(
|
|
404
|
-
`segments[${i}].hold must be 0
|
|
406
|
+
`segments[${i}].hold must be 0..${FREEZE_SECONDS_MAX} output seconds (got ${String(hold)})`
|
|
407
|
+
);
|
|
408
|
+
} else if (hold > 0) {
|
|
409
|
+
warnings.push(
|
|
410
|
+
`segments[${i}].hold is a legacy spelling, read as a freeze at ${String(seg.out)}s (doc.freeze); vos plan writes that shape`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (doc.freeze !== void 0 && !Array.isArray(doc.freeze)) {
|
|
415
|
+
problems.push("freeze must be an array of {id, at, seconds}");
|
|
416
|
+
}
|
|
417
|
+
const freezes = Array.isArray(doc.freeze) ? doc.freeze : [];
|
|
418
|
+
const freezeIds = /* @__PURE__ */ new Set();
|
|
419
|
+
for (const [i, raw] of freezes.entries()) {
|
|
420
|
+
const f = raw;
|
|
421
|
+
const name = `freeze[${i}]${typeof f.id === "string" ? ` (${f.id})` : ""}`;
|
|
422
|
+
if (typeof f.id !== "string" || !f.id)
|
|
423
|
+
problems.push(`${name}: id must be a string`);
|
|
424
|
+
else if (freezeIds.has(f.id)) problems.push(`${name}: duplicate id`);
|
|
425
|
+
else freezeIds.add(f.id);
|
|
426
|
+
if (!isNum(f.at) || f.at < 0 || f.at > duration + EPS) {
|
|
427
|
+
problems.push(
|
|
428
|
+
`${name}: at must be a SOURCE moment inside the recording (0..${duration.toFixed(2)}s, got ${String(f.at)})`
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
if (!isNum(f.seconds) || f.seconds < FREEZE_SECONDS_MIN - EPS || f.seconds > FREEZE_SECONDS_MAX + EPS) {
|
|
432
|
+
problems.push(
|
|
433
|
+
`${name}: seconds must be ${FREEZE_SECONDS_MIN}..${FREEZE_SECONDS_MAX} output seconds (got ${String(f.seconds)})`
|
|
405
434
|
);
|
|
406
435
|
}
|
|
407
436
|
}
|
|
408
437
|
const endCard = doc.endCard;
|
|
409
438
|
if (endCard !== void 0) {
|
|
439
|
+
warnings.push(
|
|
440
|
+
"endCard is a legacy spelling, read as clips after the footage plus a card exit (frame.anim.exit); vos plan writes that shape"
|
|
441
|
+
);
|
|
410
442
|
if (typeof endCard !== "object" || endCard === null) {
|
|
411
443
|
problems.push(
|
|
412
444
|
"endCard must be an object: {seconds?, headline?, sub?, wordmark?}"
|
|
@@ -653,6 +685,9 @@ function lintDoc(docIn) {
|
|
|
653
685
|
}
|
|
654
686
|
const ent = frame.entrance;
|
|
655
687
|
if (ent !== void 0) {
|
|
688
|
+
warnings.push(
|
|
689
|
+
"frame.entrance is a legacy spelling; write frame.anim.enter"
|
|
690
|
+
);
|
|
656
691
|
const kinds = ["tilt-in", "pull-out", "rise", "none"];
|
|
657
692
|
if (typeof ent !== "object" || ent === null || !kinds.includes(String(ent.kind))) {
|
|
658
693
|
problems.push(
|
|
@@ -925,6 +960,10 @@ function lintDoc(docIn) {
|
|
|
925
960
|
problems.push(`${name}.${key} must be one of ${TRANSITIONS.join("|")}`);
|
|
926
961
|
}
|
|
927
962
|
}
|
|
963
|
+
if (o.enter !== void 0 || o.exit !== void 0 || o.fx !== void 0)
|
|
964
|
+
warnings.push(
|
|
965
|
+
`${name}: enter, exit and fx are legacy spellings; write anim.enter / anim.exit`
|
|
966
|
+
);
|
|
928
967
|
lintAnim(
|
|
929
968
|
name,
|
|
930
969
|
o.anim,
|
|
@@ -1072,6 +1111,8 @@ function lintDoc(docIn) {
|
|
|
1072
1111
|
if (o.animation !== void 0 && o.animation !== null && o.animation !== "spin" && o.animation !== "float") {
|
|
1073
1112
|
problems.push(`${name}.animation must be "spin" | "float" | null`);
|
|
1074
1113
|
}
|
|
1114
|
+
if (o.animation !== void 0)
|
|
1115
|
+
warnings.push(`${name}.animation is a legacy spelling; write anim.idle`);
|
|
1075
1116
|
lintAnim(
|
|
1076
1117
|
name,
|
|
1077
1118
|
o.anim,
|
|
@@ -2759,7 +2800,6 @@ async function writeIndexJson(result) {
|
|
|
2759
2800
|
import { mkdir as mkdir4, mkdtemp, rename as rename4, rm as rm3, stat, writeFile as writeFile6 } from "fs/promises";
|
|
2760
2801
|
import { tmpdir } from "os";
|
|
2761
2802
|
import { join as join7, relative, resolve as resolve4 } from "path";
|
|
2762
|
-
import { totalDuration } from "@vosjs/timeline";
|
|
2763
2803
|
import { existsSync as existsSync6 } from "fs";
|
|
2764
2804
|
import { readFile as readFile6 } from "fs/promises";
|
|
2765
2805
|
import { parseFrontmatter } from "@vosjs/shared/frontmatter";
|
|
@@ -2771,7 +2811,7 @@ import {
|
|
|
2771
2811
|
houseLook,
|
|
2772
2812
|
isLookKind,
|
|
2773
2813
|
lookFromBrand,
|
|
2774
|
-
|
|
2814
|
+
docOutputDuration as docOutputDuration2
|
|
2775
2815
|
} from "@vosjs/studio-core";
|
|
2776
2816
|
|
|
2777
2817
|
// src/plugin/renderTake.ts
|
|
@@ -3387,7 +3427,18 @@ function pickMoments(measured, opts = {}) {
|
|
|
3387
3427
|
}
|
|
3388
3428
|
|
|
3389
3429
|
// src/plugin/motionPlan.ts
|
|
3390
|
-
import {
|
|
3430
|
+
import {
|
|
3431
|
+
END_CARD_FROM,
|
|
3432
|
+
END_CARD_RECEDE,
|
|
3433
|
+
END_CARD_SECONDS,
|
|
3434
|
+
applyTemplate,
|
|
3435
|
+
docOutputDuration,
|
|
3436
|
+
dropTemplate,
|
|
3437
|
+
endCardClips,
|
|
3438
|
+
migrateMotion,
|
|
3439
|
+
ratedSegments as ratedSegments4,
|
|
3440
|
+
spanOutputExtent as spanOutputExtent4
|
|
3441
|
+
} from "@vosjs/studio-core";
|
|
3391
3442
|
var SOUND_DESTINATIONS = /* @__PURE__ */ new Set([
|
|
3392
3443
|
"x-feed-cut",
|
|
3393
3444
|
"youtube-main-demo",
|
|
@@ -3422,41 +3473,103 @@ var outputLength = (doc) => ratedSegments4(doc).reduce((acc, s) => {
|
|
|
3422
3473
|
const rate = s.rate && s.rate > 0 ? s.rate : 1;
|
|
3423
3474
|
return acc + (s.out - s.in) / rate;
|
|
3424
3475
|
}, 0);
|
|
3476
|
+
function templateWords(words) {
|
|
3477
|
+
const headline = (words.headline ?? "").trim();
|
|
3478
|
+
const kicker = (words.kicker ?? "").trim();
|
|
3479
|
+
const brand = (words.brand ?? "").trim();
|
|
3480
|
+
const sub = [brand, (words.release ?? "").trim()].filter(Boolean).join(" ");
|
|
3481
|
+
const out = {};
|
|
3482
|
+
if (headline) {
|
|
3483
|
+
out["stage-title"] = headline;
|
|
3484
|
+
out["endcard-title"] = headline;
|
|
3485
|
+
}
|
|
3486
|
+
if (kicker) out["stage-kicker"] = kicker;
|
|
3487
|
+
if (brand) {
|
|
3488
|
+
out["stage-brand"] = brand;
|
|
3489
|
+
out["endcard-mark"] = brand;
|
|
3490
|
+
}
|
|
3491
|
+
if (sub && sub !== headline) out["endcard-sub"] = sub;
|
|
3492
|
+
return out;
|
|
3493
|
+
}
|
|
3494
|
+
function resolveAnchor(doc, at2) {
|
|
3495
|
+
if (typeof at2 !== "object") return at2;
|
|
3496
|
+
const steps = doc.source.meta.steps ?? [];
|
|
3497
|
+
const step = steps.find((s) => s.id === at2.step || String(s.step) === at2.step);
|
|
3498
|
+
if (!step || step.skipped) return null;
|
|
3499
|
+
const t = stepOutputTime(ratedSegments4(doc), step, 0.2);
|
|
3500
|
+
return t === null ? null : +t.toFixed(3);
|
|
3501
|
+
}
|
|
3502
|
+
var anchorWord = (at2) => typeof at2 === "object" ? `step ${at2.step}` : typeof at2 === "number" ? `${at2}s` : `the ${at2}`;
|
|
3503
|
+
var onWord = (v) => v === void 0 || /^(on|yes|true)$/i.test(v.trim());
|
|
3425
3504
|
function proposeMotion(input, opts) {
|
|
3426
|
-
|
|
3505
|
+
let doc = migrateMotion(structuredClone(input));
|
|
3427
3506
|
const { words, launch, catalog } = opts;
|
|
3428
3507
|
const notes = [];
|
|
3429
3508
|
const skipped = [];
|
|
3430
|
-
const
|
|
3431
|
-
const range = [0,
|
|
3509
|
+
const footage = outputLength(doc);
|
|
3510
|
+
const range = [0, footage];
|
|
3432
3511
|
const entrance = launch.entrance;
|
|
3512
|
+
delete doc.frame.entrance;
|
|
3433
3513
|
if (!off(entrance)) {
|
|
3434
|
-
const kind = entrance && /^(tilt-in|pull-out|rise)$/.test(entrance.trim()) ? entrance.trim() : "tilt-in";
|
|
3435
|
-
doc.frame.
|
|
3436
|
-
notes.push(`
|
|
3437
|
-
} else {
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3514
|
+
const kind = entrance && /^(tilt-in|pull-out|rise|fade)$/.test(entrance.trim()) ? entrance.trim() : "tilt-in";
|
|
3515
|
+
doc.frame.anim = { ...doc.frame.anim ?? {}, enter: kind };
|
|
3516
|
+
notes.push(`enter ${kind}`);
|
|
3517
|
+
} else if (doc.frame.anim?.enter !== void 0) {
|
|
3518
|
+
const { enter: _enter, ...rest } = doc.frame.anim;
|
|
3519
|
+
if (Object.keys(rest).length) doc.frame.anim = rest;
|
|
3520
|
+
else delete doc.frame.anim;
|
|
3521
|
+
}
|
|
3522
|
+
const tWords = templateWords(words);
|
|
3523
|
+
const keys = opts.mark ? { "stage-mark": opts.mark.key, "endcard-markimg": opts.mark.key } : void 0;
|
|
3524
|
+
for (const t of opts.templates ?? []) {
|
|
3525
|
+
const at2 = resolveAnchor(doc, t.at);
|
|
3526
|
+
if (at2 === null) {
|
|
3527
|
+
skipped.push(`${t.from}: ${anchorWord(t.at)} was not recorded`);
|
|
3528
|
+
continue;
|
|
3529
|
+
}
|
|
3530
|
+
const applied = applyTemplate(t.doc, doc, {
|
|
3531
|
+
at: at2,
|
|
3532
|
+
from: t.from,
|
|
3533
|
+
words: tWords,
|
|
3534
|
+
keys
|
|
3535
|
+
});
|
|
3536
|
+
doc = applied.doc;
|
|
3537
|
+
notes.push(`${t.from} at ${anchorWord(t.at)}`);
|
|
3538
|
+
for (const n of applied.notes) skipped.push(`${t.from}: ${n}`);
|
|
3539
|
+
}
|
|
3540
|
+
const endCardRole = launch.endCard;
|
|
3541
|
+
doc = dropTemplate(doc, END_CARD_FROM);
|
|
3542
|
+
if (off(endCardRole)) {
|
|
3543
|
+
if (doc.frame.anim?.exit !== void 0) {
|
|
3544
|
+
const { exit: _exit, ...rest } = doc.frame.anim;
|
|
3545
|
+
if (Object.keys(rest).length) doc.frame.anim = rest;
|
|
3546
|
+
else delete doc.frame.anim;
|
|
3547
|
+
}
|
|
3548
|
+
} else if (onWord(endCardRole)) {
|
|
3441
3549
|
const headline = (words.headline ?? "").trim();
|
|
3442
3550
|
const brand = (words.brand ?? "").trim();
|
|
3443
3551
|
const sub = [brand, (words.release ?? "").trim()].filter(Boolean).join(" ");
|
|
3444
3552
|
if (headline || brand) {
|
|
3445
|
-
const card = { seconds:
|
|
3553
|
+
const card = { seconds: END_CARD_SECONDS };
|
|
3446
3554
|
if (opts.ink) card.ink = opts.ink;
|
|
3447
3555
|
if (headline) card.headline = headline;
|
|
3448
3556
|
if (sub && sub !== headline) card.sub = sub;
|
|
3449
3557
|
if (brand) card.wordmark = brand;
|
|
3450
3558
|
if (opts.mark) card.mark = opts.mark;
|
|
3451
|
-
doc.
|
|
3559
|
+
doc.overlays = [
|
|
3560
|
+
...doc.overlays ?? [],
|
|
3561
|
+
...endCardClips(card, doc, outputLength(doc))
|
|
3562
|
+
];
|
|
3563
|
+
doc.frame.anim = {
|
|
3564
|
+
...doc.frame.anim ?? {},
|
|
3565
|
+
exit: { kind: "recede", seconds: END_CARD_RECEDE }
|
|
3566
|
+
};
|
|
3452
3567
|
notes.push("end card");
|
|
3453
3568
|
} else {
|
|
3454
3569
|
skipped.push(
|
|
3455
3570
|
"no end card (no headline or wordmark in LAUNCH.md, BRAND.md or the flags)"
|
|
3456
3571
|
);
|
|
3457
3572
|
}
|
|
3458
|
-
} else {
|
|
3459
|
-
delete doc.endCard;
|
|
3460
3573
|
}
|
|
3461
3574
|
const kept = (doc.overlays ?? []).filter(
|
|
3462
3575
|
(o) => !o.id.startsWith(CAPTION_ID_PREFIX)
|
|
@@ -3471,17 +3584,16 @@ function proposeMotion(input, opts) {
|
|
|
3471
3584
|
);
|
|
3472
3585
|
if (!step || step.skipped) continue;
|
|
3473
3586
|
const t = stepOutputTime(rated, step, 0.2);
|
|
3474
|
-
if (t === null || t < 0 || t >
|
|
3587
|
+
if (t === null || t < 0 || t > footage - 1) continue;
|
|
3475
3588
|
captionClips.push({
|
|
3476
3589
|
id: `${CAPTION_ID_PREFIX}${c.step}`,
|
|
3477
3590
|
kind: "text",
|
|
3478
3591
|
text: c.caption,
|
|
3479
3592
|
preset: "caption",
|
|
3480
3593
|
start: +t.toFixed(3),
|
|
3481
|
-
duration: Math.min(3.5, Math.max(2.5,
|
|
3594
|
+
duration: Math.min(3.5, Math.max(2.5, footage - t - 0.2)),
|
|
3482
3595
|
transform: { x: 0.5, y: 0.86, scale: 1, rotation: 0 },
|
|
3483
|
-
enter: "rise",
|
|
3484
|
-
exit: "fade",
|
|
3596
|
+
anim: { enter: "rise", exit: "fade" },
|
|
3485
3597
|
align: "center",
|
|
3486
3598
|
box: { color: "rgba(17,17,17,0.72)" }
|
|
3487
3599
|
});
|
|
@@ -3491,6 +3603,7 @@ function proposeMotion(input, opts) {
|
|
|
3491
3603
|
const overlays = [...kept, ...captionClips];
|
|
3492
3604
|
if (overlays.length) doc.overlays = overlays;
|
|
3493
3605
|
else delete doc.overlays;
|
|
3606
|
+
const length = docOutputDuration(doc);
|
|
3494
3607
|
const clips = (doc.audio ?? []).filter(
|
|
3495
3608
|
(a) => a.id !== BED_ID && !a.id.startsWith(CLICK_ID_PREFIX)
|
|
3496
3609
|
);
|
|
@@ -3550,8 +3663,8 @@ function destinationMechanics(d, doc) {
|
|
|
3550
3663
|
const sound = SOUND_DESTINATIONS.has(d.id);
|
|
3551
3664
|
const portrait = d.px.w / d.px.h < 0.9;
|
|
3552
3665
|
if (loop) {
|
|
3553
|
-
unset.push("frame.entrance", "endCard");
|
|
3554
|
-
notes.push("loop: no
|
|
3666
|
+
unset.push("frame.anim", "frame.entrance", "endCard");
|
|
3667
|
+
notes.push("loop: no card motion");
|
|
3555
3668
|
}
|
|
3556
3669
|
if (loop || !sound) {
|
|
3557
3670
|
set.push("audio=[]");
|
|
@@ -3559,11 +3672,11 @@ function destinationMechanics(d, doc) {
|
|
|
3559
3672
|
}
|
|
3560
3673
|
if (loop || d.text === "none") {
|
|
3561
3674
|
const kept = (doc.overlays ?? []).filter(
|
|
3562
|
-
(o) => !o.id.startsWith(CAPTION_ID_PREFIX)
|
|
3675
|
+
(o) => !o.id.startsWith(CAPTION_ID_PREFIX) && !(loop && o.from)
|
|
3563
3676
|
);
|
|
3564
3677
|
if (kept.length !== (doc.overlays ?? []).length) {
|
|
3565
3678
|
set.push(`overlays=${JSON.stringify(kept)}`);
|
|
3566
|
-
notes.push("no captions");
|
|
3679
|
+
notes.push(loop ? "no template clips, no captions" : "no captions");
|
|
3567
3680
|
}
|
|
3568
3681
|
}
|
|
3569
3682
|
if (portrait) {
|
|
@@ -4087,7 +4200,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4087
4200
|
const take = await loadTake(dir);
|
|
4088
4201
|
if (!take.doc) throw new Error(`${dir} has no doc.json \u2014 run plan first`);
|
|
4089
4202
|
const doc = take.doc;
|
|
4090
|
-
const duration =
|
|
4203
|
+
const duration = docOutputDuration2(doc);
|
|
4091
4204
|
const videoSeconds = opts.range ? Math.min(opts.range[1], duration) - Math.min(opts.range[0], duration) : duration;
|
|
4092
4205
|
const outDir = resolve4(opts.outDir ?? join7(dir, "kit"));
|
|
4093
4206
|
await mkdir4(outDir, { recursive: true });
|
|
@@ -4145,7 +4258,7 @@ async function deliverTake(browser, dir, opts) {
|
|
|
4145
4258
|
}
|
|
4146
4259
|
posterCardIds.add(d.id);
|
|
4147
4260
|
const label = `${d.channel} ${d.asset}`;
|
|
4148
|
-
const posterDuration =
|
|
4261
|
+
const posterDuration = docOutputDuration2(ref.doc);
|
|
4149
4262
|
const time = posterStillTime(ref.doc, posterDuration);
|
|
4150
4263
|
opts.onPhase?.(
|
|
4151
4264
|
`${label} (${specWords(d)}) from ${ref.from}${ref.vosId ? ` ${ref.vosId}` : ""}, the rest at ${time.toFixed(2)}s`
|
|
@@ -5701,10 +5814,12 @@ import {
|
|
|
5701
5814
|
planAutoSpeed,
|
|
5702
5815
|
planAutoZoom,
|
|
5703
5816
|
projectFromArtifact,
|
|
5704
|
-
withBackdrop
|
|
5817
|
+
withBackdrop,
|
|
5818
|
+
migrateMotion as migrateMotion2
|
|
5705
5819
|
} from "@vosjs/studio-core";
|
|
5706
5820
|
|
|
5707
5821
|
// src/plugin/reuse.ts
|
|
5822
|
+
import { docFreezes } from "@vosjs/studio-core";
|
|
5708
5823
|
var MIN_SPAN = 0.15;
|
|
5709
5824
|
function matchStep(old, newSteps) {
|
|
5710
5825
|
const usable = (s) => !!s && s.skipped !== true;
|
|
@@ -5763,7 +5878,7 @@ function buildStepMap(oldSteps, newSteps, oldDuration, newDuration) {
|
|
|
5763
5878
|
};
|
|
5764
5879
|
return { map, unmatched, matched };
|
|
5765
5880
|
}
|
|
5766
|
-
function
|
|
5881
|
+
function resolveAnchor2(anchor, newSteps) {
|
|
5767
5882
|
const step = typeof anchor.step === "string" ? newSteps.find((s) => s.id === anchor.step) : newSteps.find((s) => s.step === anchor.step);
|
|
5768
5883
|
if (!step || step.skipped) return null;
|
|
5769
5884
|
const base = anchor.at === "end" ? step.tEnd : step.tStart;
|
|
@@ -5775,7 +5890,7 @@ function retimeSpans(kind, spans, stepMap, newSteps, newDuration, report) {
|
|
|
5775
5890
|
const length = span.out - span.in;
|
|
5776
5891
|
let nextIn = null;
|
|
5777
5892
|
if (span.anchor) {
|
|
5778
|
-
nextIn =
|
|
5893
|
+
nextIn = resolveAnchor2(span.anchor, newSteps);
|
|
5779
5894
|
if (nextIn === null) {
|
|
5780
5895
|
report.flagged.push(
|
|
5781
5896
|
`${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`
|
|
@@ -5866,6 +5981,30 @@ function retimeCut(prev, newSteps, newDurationMs) {
|
|
|
5866
5981
|
newDuration,
|
|
5867
5982
|
report
|
|
5868
5983
|
);
|
|
5984
|
+
const freeze = [];
|
|
5985
|
+
for (const f of docFreezes(prev)) {
|
|
5986
|
+
let at2 = null;
|
|
5987
|
+
if (f.anchor) {
|
|
5988
|
+
at2 = resolveAnchor2(f.anchor, newSteps);
|
|
5989
|
+
if (at2 === null) {
|
|
5990
|
+
report.flagged.push(
|
|
5991
|
+
`freeze ${f.id}: its anchored step (${String(f.anchor.step)}) is missing or skipped in the new recording \u2014 fell back to the step map`
|
|
5992
|
+
);
|
|
5993
|
+
} else report.anchored++;
|
|
5994
|
+
}
|
|
5995
|
+
if (at2 === null) {
|
|
5996
|
+
at2 = stepMap.map(f.at);
|
|
5997
|
+
report.mapped++;
|
|
5998
|
+
}
|
|
5999
|
+
if (at2 < 0 || at2 > newDuration) {
|
|
6000
|
+
report.flagged.push(
|
|
6001
|
+
`freeze ${f.id}: lands outside the new recording (${at2.toFixed(2)}s) \u2014 dropped`
|
|
6002
|
+
);
|
|
6003
|
+
continue;
|
|
6004
|
+
}
|
|
6005
|
+
freeze.push({ ...f, at: +at2.toFixed(3) });
|
|
6006
|
+
}
|
|
6007
|
+
freeze.sort((a, b) => a.at - b.at);
|
|
5869
6008
|
const rejected = [];
|
|
5870
6009
|
for (const lane of ["zoom", "tilt", "speed"]) {
|
|
5871
6010
|
rejected.push(
|
|
@@ -5879,7 +6018,7 @@ function retimeCut(prev, newSteps, newDurationMs) {
|
|
|
5879
6018
|
)
|
|
5880
6019
|
);
|
|
5881
6020
|
}
|
|
5882
|
-
return { segments, zoom, speed, tilt, rejected, report };
|
|
6021
|
+
return { segments, zoom, speed, freeze, tilt, rejected, report };
|
|
5883
6022
|
}
|
|
5884
6023
|
|
|
5885
6024
|
// src/plugin/plan.ts
|
|
@@ -5908,7 +6047,7 @@ function patchStageWords(doc, words) {
|
|
|
5908
6047
|
}
|
|
5909
6048
|
function applyStyle(seed, doc, opts) {
|
|
5910
6049
|
const parts = layoutOf(seed);
|
|
5911
|
-
const carries = parts.clips.length > 0 || parts.lean || parts.
|
|
6050
|
+
const carries = parts.clips.length > 0 || parts.lean || parts.freeze;
|
|
5912
6051
|
if (!carries) return { doc: copyStyle(seed, doc), layout: void 0 };
|
|
5913
6052
|
const keys = opts.mark ? { "stage-mark": opts.mark.key } : void 0;
|
|
5914
6053
|
const { doc: next, notes } = copyLayout(seed, copyStyle(seed, doc), { keys });
|
|
@@ -5924,7 +6063,7 @@ async function planTake(dir, opts = {}) {
|
|
|
5924
6063
|
let fresh;
|
|
5925
6064
|
let layout;
|
|
5926
6065
|
if (opts.reuse) {
|
|
5927
|
-
const prev = opts.reuse.doc;
|
|
6066
|
+
const prev = migrateMotion2(opts.reuse.doc);
|
|
5928
6067
|
const artifact = {
|
|
5929
6068
|
videoKey: RECORDING_NAME,
|
|
5930
6069
|
cursor,
|
|
@@ -5934,10 +6073,7 @@ async function planTake(dir, opts = {}) {
|
|
|
5934
6073
|
doc = copyStyle(prev, doc);
|
|
5935
6074
|
const rt = retimeCut(prev, meta.steps ?? [], meta.durationMs);
|
|
5936
6075
|
doc.segments = rt.segments;
|
|
5937
|
-
|
|
5938
|
-
const last = doc.segments.at(-1);
|
|
5939
|
-
if (typeof prevHold === "number" && prevHold > 0 && last)
|
|
5940
|
-
last.hold = prevHold;
|
|
6076
|
+
if (rt.freeze.length) doc.freeze = rt.freeze;
|
|
5941
6077
|
if (rt.rejected.length) doc.rejected = rt.rejected;
|
|
5942
6078
|
const manualZoom = rt.zoom;
|
|
5943
6079
|
const autoZoom = planAutoZoom(doc.source.cursor, {
|
|
@@ -5959,7 +6095,6 @@ async function planTake(dir, opts = {}) {
|
|
|
5959
6095
|
if (prev.objects?.length) doc.objects = prev.objects;
|
|
5960
6096
|
if (prev.audio.length) doc.audio = prev.audio;
|
|
5961
6097
|
if (prev.camMotion?.length) doc.camMotion = prev.camMotion;
|
|
5962
|
-
if (prev.endCard) doc.endCard = structuredClone(prev.endCard);
|
|
5963
6098
|
await writeJson(take.paths.doc, doc, true);
|
|
5964
6099
|
return {
|
|
5965
6100
|
doc,
|
|
@@ -8657,7 +8792,7 @@ var HELP = `vos \u2014 record a browser flow, plan effects, render a product vid
|
|
|
8657
8792
|
Take pipeline
|
|
8658
8793
|
vos create --actions actions.json [--url <url>] [--out take] [out.webm] [--strict] [--max-duration <s>] [--background <slug|url|none>] [render flags] [--json]
|
|
8659
8794
|
vos record --actions actions.json [--url <url>] [--out take] [--strict] [--max-duration <s>] [--background <slug|url|none>] [--json]
|
|
8660
|
-
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]
|
|
8795
|
+
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]
|
|
8661
8796
|
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]
|
|
8662
8797
|
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]
|
|
8663
8798
|
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]
|
|
@@ -8772,7 +8907,7 @@ Screenshot-genre stills never take a look.
|
|
|
8772
8907
|
A POSTER is a document, never a template: a plain take whose card sits
|
|
8773
8908
|
where the poster wants it (frame.inset), leans (the tilt track), carries
|
|
8774
8909
|
the words and the mark as clips (ids stage-title, stage-kicker,
|
|
8775
|
-
stage-brand, stage-mark) and ends on a trailing
|
|
8910
|
+
stage-brand, stage-mark) and ends on a trailing freeze whose start is the
|
|
8776
8911
|
still. Card-genre destinations (OG, LinkedIn, X, YouTube thumbnail, the
|
|
8777
8912
|
CWS tile + marquee, GitHub social preview) render from the poster
|
|
8778
8913
|
document of their aspect CLASS (landscape, square, portrait, tile), found
|
|
@@ -8785,20 +8920,28 @@ deliver renders and verifies; it composes nothing. The composition is
|
|
|
8785
8920
|
made with plan --style <poster>, which copies a poster's layout onto a
|
|
8786
8921
|
take (its card placement, its stage clips with the release's words
|
|
8787
8922
|
patched in from LAUNCH.md's headline and kicker roles or --headline and
|
|
8788
|
-
--kicker, its rest lean, its
|
|
8789
|
-
The cut's MOTION is the document's too
|
|
8790
|
-
(
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8923
|
+
--kicker, its rest lean, its freeze), or by hand in doc.json.
|
|
8924
|
+
The cut's MOTION is the document's too, in ONE vocabulary: every visual
|
|
8925
|
+
thing (the card, a text, image or video clip, a prop) carries anim.enter,
|
|
8926
|
+
anim.exit and anim.idle; the output lasts until the last clip ends, and
|
|
8927
|
+
past its footage the card holds its last frame at the pose its exit
|
|
8928
|
+
settled into. There is no end-card field and no entrance field: a
|
|
8929
|
+
COMPONENT is a TEMPLATE, a plain take on a shelf whose clips carry stable
|
|
8930
|
+
ids, laid onto the take at an anchor (LAUNCH.md with: <ref>[@end|@start|
|
|
8931
|
+
@step:<id>|@<s>], comma list; --with the same, repeatable; endCard: <ref>
|
|
8932
|
+
names one at the end), its clips stamped with where they came from
|
|
8933
|
+
(from). plan proposes on a fresh plan (--motion re-proposes, replacing
|
|
8934
|
+
only its own work): the card's ENTER (tilt-in by default), the templates
|
|
8935
|
+
named, the house END CARD when endCard is on or absent (clips after the
|
|
8936
|
+
footage: the headline, the release line, the wordmark, the mark from
|
|
8937
|
+
BRAND.md logoUrl, over a card that recedes; from: endcard), a CAPTION per
|
|
8938
|
+
actions.json step at the step's moment, a music BED from LAUNCH.md's music
|
|
8939
|
+
role (a catalog slug or a mood) and a click sound on every press when the
|
|
8940
|
+
take has no mic. LAUNCH.md's entrance, endCard, captions, music and clicks
|
|
8941
|
+
roles, or the flags, change or switch each off; a deleted proposal stays
|
|
8942
|
+
deleted on a refresh. deliver applies each destination's MECHANICS and
|
|
8943
|
+
nothing more: the README loop drops the card's motion, every clip a
|
|
8944
|
+
template placed and every sound; a channel that autoplays muted drops the
|
|
8802
8945
|
bed; the 9:16 cut is a reframe, not a letterbox, and the crop follows
|
|
8803
8946
|
the camera. Screenshot-genre
|
|
8804
8947
|
destinations (CWS screenshots, the PH gallery) are the real page at that
|
|
@@ -9109,11 +9252,15 @@ async function cmdCreate2(argv) {
|
|
|
9109
9252
|
}
|
|
9110
9253
|
}
|
|
9111
9254
|
async function cmdPlan(argv) {
|
|
9112
|
-
const { positionals, flags } = parseArgs(
|
|
9255
|
+
const { positionals, flags, multi } = parseArgs(
|
|
9256
|
+
argv,
|
|
9257
|
+
BOOLEAN_FLAGS5,
|
|
9258
|
+
/* @__PURE__ */ new Set(["with"])
|
|
9259
|
+
);
|
|
9113
9260
|
const dir = positionals[0];
|
|
9114
9261
|
if (!dir)
|
|
9115
9262
|
throw new UsageError(
|
|
9116
|
-
'vos plan <take> [--fresh] [--reuse [--from <doc.json>]] [--style <doc.json|vosId>] [--motion] [--headline "\u2026"] [--launch LAUNCH.md] [--brand BRAND.md]'
|
|
9263
|
+
'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]'
|
|
9117
9264
|
);
|
|
9118
9265
|
const r = createReporter(flags.json === true);
|
|
9119
9266
|
if (flags.fresh === true) {
|
|
@@ -9136,7 +9283,7 @@ async function cmdPlan(argv) {
|
|
|
9136
9283
|
const style = await resolveStyleRef(flags);
|
|
9137
9284
|
const hasDoc = existsSync14(join17(dir, "doc.json"));
|
|
9138
9285
|
const backdrop = hasDoc ? null : await takeBackdrop(flags, r);
|
|
9139
|
-
const release = await releaseInputs(dir, flags, r);
|
|
9286
|
+
const release = await releaseInputs(dir, flags, r, multi.with ?? []);
|
|
9140
9287
|
const motionWanted = !hasDoc || flags.motion === true || flags.fresh === true;
|
|
9141
9288
|
const s = await planTake(dir, {
|
|
9142
9289
|
...style ? { style } : {},
|
|
@@ -9152,6 +9299,7 @@ async function cmdPlan(argv) {
|
|
|
9152
9299
|
mark: release.mark,
|
|
9153
9300
|
captions: release.captions,
|
|
9154
9301
|
catalog: release.catalog,
|
|
9302
|
+
templates: release.templates,
|
|
9155
9303
|
again: flags.motion === true
|
|
9156
9304
|
}
|
|
9157
9305
|
} : {}
|
|
@@ -9160,7 +9308,7 @@ async function cmdPlan(argv) {
|
|
|
9160
9308
|
layout from ${s.styleFrom}: ${[
|
|
9161
9309
|
s.layout.clips.length ? `${s.layout.clips.length} stage clip(s)` : "",
|
|
9162
9310
|
s.layout.lean ? "the rest lean" : "",
|
|
9163
|
-
s.layout.
|
|
9311
|
+
s.layout.freeze ? "the freeze" : ""
|
|
9164
9312
|
].filter(Boolean).join(", ")}` + (s.layout.notes.length ? `
|
|
9165
9313
|
${s.layout.notes.join("\n ")}` : "") : "";
|
|
9166
9314
|
const motionLines = s.motion ? (s.motion.notes.length ? `
|
|
@@ -9188,7 +9336,7 @@ async function cmdPlan(argv) {
|
|
|
9188
9336
|
);
|
|
9189
9337
|
return EXIT_OK;
|
|
9190
9338
|
}
|
|
9191
|
-
async function releaseInputs(dir, flags, r) {
|
|
9339
|
+
async function releaseInputs(dir, flags, r, withRefs = []) {
|
|
9192
9340
|
let lookPick;
|
|
9193
9341
|
try {
|
|
9194
9342
|
lookPick = await resolveLook(dir, {
|
|
@@ -9243,13 +9391,36 @@ async function releaseInputs(dir, flags, r) {
|
|
|
9243
9391
|
const step = st;
|
|
9244
9392
|
return typeof step.caption === "string" && step.caption.trim() ? [{ step: i, id: step.id, caption: step.caption.trim() }] : [];
|
|
9245
9393
|
});
|
|
9394
|
+
const refs = [];
|
|
9395
|
+
const parseRef = (raw, fallback) => {
|
|
9396
|
+
const m = /^(.*?)(?:@(end|start|step:[^@\s]+|\d+(?:\.\d+)?))?$/.exec(
|
|
9397
|
+
raw.trim()
|
|
9398
|
+
);
|
|
9399
|
+
const ref = m?.[1]?.trim() ?? raw.trim();
|
|
9400
|
+
const anchor = m?.[2];
|
|
9401
|
+
const at2 = !anchor ? fallback : anchor === "end" || anchor === "start" ? anchor : anchor.startsWith("step:") ? { step: anchor.slice(5) } : Number(anchor);
|
|
9402
|
+
if (ref) refs.push({ ref, at: at2 });
|
|
9403
|
+
};
|
|
9404
|
+
for (const raw of withRefs) parseRef(raw, "end");
|
|
9405
|
+
for (const raw of (launchRoles.with ?? "").split(","))
|
|
9406
|
+
if (raw.trim()) parseRef(raw, "end");
|
|
9407
|
+
const endCardRole = launchRoles.endCard;
|
|
9408
|
+
if (endCardRole && !/^(none|off|no|false|on|yes|true)$/i.test(endCardRole.trim()))
|
|
9409
|
+
parseRef(endCardRole, "end");
|
|
9410
|
+
const templates = [];
|
|
9411
|
+
for (const { ref, at: at2 } of refs) {
|
|
9412
|
+
const found = await resolveDocRef(ref, flags, "--with");
|
|
9413
|
+
templates.push({ from: found.from, doc: found.doc, at: at2 });
|
|
9414
|
+
r.log(`template: ${found.from}`);
|
|
9415
|
+
}
|
|
9246
9416
|
return {
|
|
9247
9417
|
words,
|
|
9248
9418
|
launchRoles,
|
|
9249
9419
|
ink: endCardInk(lookPick.look, lookPick.roles),
|
|
9250
9420
|
mark,
|
|
9251
9421
|
captions,
|
|
9252
|
-
catalog
|
|
9422
|
+
catalog,
|
|
9423
|
+
templates
|
|
9253
9424
|
};
|
|
9254
9425
|
}
|
|
9255
9426
|
async function cmdRender(argv) {
|
|
@@ -9344,7 +9515,7 @@ async function cmdFrames(argv) {
|
|
|
9344
9515
|
if (!m) throw new UsageError("--size expects WxH (e.g. --size 1280x800)");
|
|
9345
9516
|
size = { width: Number(m[1]), height: Number(m[2]) };
|
|
9346
9517
|
}
|
|
9347
|
-
const duration =
|
|
9518
|
+
const duration = docOutputDuration3(take.doc);
|
|
9348
9519
|
const frameRaw = strFlag(flags, "frame");
|
|
9349
9520
|
const timesRaw = strFlag(flags, "times");
|
|
9350
9521
|
const atZooms = flags["at-zooms"] === true;
|
|
@@ -9431,7 +9602,7 @@ async function cmdDeliver(argv) {
|
|
|
9431
9602
|
}
|
|
9432
9603
|
const take = await loadTake(dir);
|
|
9433
9604
|
if (!take.doc) throw new UsageError(`${dir} has no doc.json \u2014 run plan first`);
|
|
9434
|
-
const duration =
|
|
9605
|
+
const duration = docOutputDuration3(take.doc);
|
|
9435
9606
|
const timesRaw = strFlag(flags, "times");
|
|
9436
9607
|
let times;
|
|
9437
9608
|
if (timesRaw !== void 0) {
|
|
@@ -9509,6 +9680,10 @@ Store uploads stay manual: hand the human this directory and the manifest.`
|
|
|
9509
9680
|
async function resolveStyleRef(flags) {
|
|
9510
9681
|
const styleRef = strFlag(flags, "style");
|
|
9511
9682
|
if (!styleRef) return null;
|
|
9683
|
+
return resolveDocRef(styleRef, flags, "--style");
|
|
9684
|
+
}
|
|
9685
|
+
async function resolveDocRef(ref, flags, what) {
|
|
9686
|
+
const styleRef = ref;
|
|
9512
9687
|
const file = existsSync14(styleRef) ? resolve9(
|
|
9513
9688
|
styleRef,
|
|
9514
9689
|
existsSync14(join17(styleRef, "doc.json")) ? "doc.json" : ""
|
|
@@ -9516,7 +9691,9 @@ async function resolveStyleRef(flags) {
|
|
|
9516
9691
|
if (file && existsSync14(file)) {
|
|
9517
9692
|
return {
|
|
9518
9693
|
from: file,
|
|
9519
|
-
doc:
|
|
9694
|
+
doc: migrateHostedDoc5(
|
|
9695
|
+
JSON.parse(await readFile14(file, "utf8"))
|
|
9696
|
+
)
|
|
9520
9697
|
};
|
|
9521
9698
|
}
|
|
9522
9699
|
const origin = platformOrigin({
|
|
@@ -9528,7 +9705,7 @@ async function resolveStyleRef(flags) {
|
|
|
9528
9705
|
const head = meta.body.vos?.currentVersionId;
|
|
9529
9706
|
if (meta.status !== 200 || !head)
|
|
9530
9707
|
throw new UsageError(
|
|
9531
|
-
|
|
9708
|
+
`${what}: ${styleRef} is neither a doc.json nor a vos I can read`
|
|
9532
9709
|
);
|
|
9533
9710
|
const doc = await apiJson(
|
|
9534
9711
|
origin,
|
|
@@ -9536,7 +9713,7 @@ async function resolveStyleRef(flags) {
|
|
|
9536
9713
|
{ key }
|
|
9537
9714
|
);
|
|
9538
9715
|
if (doc.status !== 200)
|
|
9539
|
-
throw new UsageError(
|
|
9716
|
+
throw new UsageError(`${what}: ${styleRef} carries no doc (is it a take?)`);
|
|
9540
9717
|
return {
|
|
9541
9718
|
from: `${origin}/vos/${styleRef}`,
|
|
9542
9719
|
doc: migrateHostedDoc5(doc.body)
|
|
@@ -9960,4 +10137,4 @@ export {
|
|
|
9960
10137
|
convertAgentBrowser,
|
|
9961
10138
|
run
|
|
9962
10139
|
};
|
|
9963
|
-
//# sourceMappingURL=chunk-
|
|
10140
|
+
//# sourceMappingURL=chunk-4IWHZBIY.js.map
|