@vosjs/cli 0.81.1 → 0.83.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 +6 -1
- package/dist/{chunk-S3RI2SHH.js → chunk-5VGIQKVT.js} +83 -18
- package/dist/chunk-5VGIQKVT.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/{run-MEDD2Q74.js → run-CPFCYXVL.js} +2 -2
- package/package.json +3 -3
- package/schema/actions.schema.json +3 -2
- package/dist/chunk-S3RI2SHH.js.map +0 -1
- /package/dist/{run-MEDD2Q74.js.map → run-CPFCYXVL.js.map} +0 -0
package/README.md
CHANGED
|
@@ -190,7 +190,7 @@ vos look https://app.acme.com/projects --session acme --click '[data-testid="new
|
|
|
190
190
|
|
|
191
191
|
It writes `<out>/look.png` (the viewport; `--out look` by default) and lists every control in view (buttons, links, tabs, menu items, checkboxes, switches, options, fields, editable regions, and anything with a test id) with its tag, role, accessible name, test id, link path and query, rect in viewport px, whether it is disabled, inside an open dialog or covered by something on top of it, and ONE proposed selector: its test id; else its `aria-label`, inside the open dialog it sits in; else a link's own `href`, query kept (two tiles to `?vos=a` and `?vos=b` are two links); else role and name (`role=button[name="Continue"]`, what `page.locator` reads), narrowed by its dialog or landmark when that is what makes it one; else a field's own `name` or `placeholder`, a written id, or its words inside its dialog or landmark. Never a position in the tree. Each proposal is counted with the recorder's own selector engine and marked `UNIQUE` (it reaches this element and no other) or `AMBIGUOUS` (it reaches others too, and the recorder would take the first). A control whose words hold an address, a key or a card number is listed with those words withheld, and the frame's exposures are said the way a rehearsal says them. Looking never spends or destroys: a `--click` whose target's words begin with a stem that does (delet, remov, revok, eras, discard, publish, deploy, purchas, subscri, unsubscri, pay, buy, generat, submit, send, transfer, upgrad, confirm, leav, destroy, so `Allow deleting` and `Revoke` are caught with `Delete`), or say move to trash, empty trash or cancel subscription, is refused in words before it is pressed (exit 2), and `--type` fills a field without pressing Enter. A look cannot know every spend: `Make a video` or `Create` on a primary button may start paid work and passes, because refusing every Make would refuse half of every app. The list is a floor under looking; in a recorded flow, a step's `guard: "spend"` in `actions.json` is the fence. A sign-in in front of the page is the wall check's verdict, exit 4; the sign-in page is still looked at, since it is what a `setup` step scripts past. `--wait <ms>` adds a pause before the look for a page that animates in; `--json` prints the whole look as one done event (`url`, `screenshot`, `controls`, `exposures`, `wall`).
|
|
192
192
|
|
|
193
|
-
**`setup`: the steps that run before the camera rolls.** A sign-in form, a cookie banner, the "choose your editor" modal, an onboarding tour: things a take must get past and must not show. `actions.json` takes `setup: [...]` beside `steps`, with the verbs `goto`, `click`, `type`, `press`, `wait`. They run after the first navigation and before a frame is captured, as plain actions with no cursor, no frames, no pace and nothing in `meta.steps`; then the recorder opens `url` again and the take begins where the setup left it. A `type` step's `text` may be `{ "env": "DEMO_PASSWORD" }`, read from the shell at run time and never logged or stored (the log names the field, never the value; a literal typed into a password field is refused by `validate`, because `actions.json` is committed and pushed with the take). A selector that never appears fails the take before anything is recorded (exit 2), because a take that begins at a half-finished sign-in is the wall by another name; rehearse the setup with `--dry-run` like everything else. This is rung 2 of the session ladder made scriptable: a local or self-hosted instance with a seeded user, no state file needed.
|
|
193
|
+
**`setup`: the steps that run before the camera rolls.** A sign-in form, a cookie banner, the "choose your editor" modal, an onboarding tour: things a take must get past and must not show. `actions.json` takes `setup: [...]` beside `steps`, with the verbs `goto`, `click`, `type`, `press`, `wait`, and the guard `if`. They run after the first navigation and before a frame is captured, as plain actions with no cursor, no frames, no pace and nothing in `meta.steps`; then the recorder opens `url` again and the take begins where the setup left it. A `type` step's `text` may be `{ "env": "DEMO_PASSWORD" }`, read from the shell at run time and never logged or stored (the log names the field, never the value; a literal typed into a password field is refused by `validate`, because `actions.json` is committed and pushed with the take). A selector that never appears fails the take before anything is recorded (exit 2), because a take that begins at a half-finished sign-in is the wall by another name; rehearse the setup with `--dry-run` like everything else. This is rung 2 of the session ladder made scriptable: a local or self-hosted instance with a seeded user, no state file needed.
|
|
194
194
|
|
|
195
195
|
```json
|
|
196
196
|
"setup": [
|
|
@@ -203,9 +203,12 @@ It writes `<out>/look.png` (the viewport; `--out look` by default) and lists eve
|
|
|
203
203
|
|
|
204
204
|
**`teardown`: the steps that run after it.** A flow that CREATES something in the account (an API key, a project, an invite) creates it on every rehearsal and every take, since `--dry-run` clicks for real. `teardown: [...]` beside `setup` removes it again: the same verbs and the same rules (a `{ "env": "NAME" }` text, no literal in a password field, http and https only), run off camera once the screencast has stopped, on the take's own page and session, so nothing of it is captured or lands in `meta.steps`. It runs after a rehearsal too, and when the take stops early (a failed `await`, `--strict` on a page that stopped answering, the cap) or throws once its first step ran; not when the take never began (a setup that failed, a wall). The steps together get 60 s. A teardown never fails the take or changes the exit code: it stops at the first step that fails, the done event carries `teardown: { ran, of, failed?: { step, do, selector?, error } }`, and the summary says `teardown: 3 step(s) ran` or `TEARDOWN FAILED at teardown[1] (click #confirm): <reason>. What the take created may still be in the product; remove it by hand.` A `{ env }` the shell does not hold refuses the take before it begins, since the take would create what its teardown cannot remove.
|
|
205
205
|
|
|
206
|
+
**`if`: a guard for either list.** `{ "do": "if", "selector": "…" }` looks for its selector (3 s, or its `ms`): on the page, the steps after it run; not on the page, they are skipped and the list ends there, which is never a failure. A take that deletes on camera what it created, and keeps a teardown as the net for a take that stops early, starts the undoing with one, so a good take says `teardown: nothing to undo, <selector> is not on the page` instead of a failure; the done event carries `skipped: { step, selector, rest }`. A setup that signs in guards its sign-in the same way (`if` the email field), so a session already signed in passes. A step that fails after a guard still fails.
|
|
207
|
+
|
|
206
208
|
```json
|
|
207
209
|
"teardown": [
|
|
208
210
|
{ "do": "goto", "url": "http://localhost:3000/settings/keys" },
|
|
211
|
+
{ "do": "if", "selector": "[data-testid=key-row]:first-child" },
|
|
209
212
|
{ "do": "click", "selector": "[data-testid=key-row]:first-child button[aria-label=Revoke]" },
|
|
210
213
|
{ "do": "click", "selector": "role=button[name=\"Revoke key\"]" }
|
|
211
214
|
]
|
|
@@ -452,6 +455,8 @@ Not sure of a selector? `vos look <url>` (with the take's `--session` or `--stor
|
|
|
452
455
|
|
|
453
456
|
**Pinned layers.** A layer ABOUT something on the page names it, and the lowering keeps the two together: `"pin": { "step": "copy", "side": "auto", "mark": "ring", "leader": true }` on any overlay kind places the layer a gap off one side of that step's element and carries it with the element as the camera moves (the layer keeps its screen size; only its place follows). One of `step` (a recorder step's `id`, else its index), `press` (SOURCE seconds; the nearest press names the element, for a human recording) or `rect` (video fractions) names the referent. `side` is `auto` (the first of right, left, below, above whose box fits inside the frame through the layer's life) or a side by name; `gap` is design px (24); `mark` is `ring` or `underline`, a standing highlight on the referent for the layer's life; `leader` draws a hairline from the layer to the referent; `color` is their ink. `transform.x/y` stay the fallback for a pin that cannot resolve, and `vos validate` says why (an unknown step, a step that touched nothing, a scroll inside the layer's window). A layer that cannot name its referent is a caption: place it in the margin, never over the app.
|
|
454
457
|
|
|
458
|
+
**How long a caption stays.** The captions `vos plan --motion` and `--captions` land are about their steps: each lands as its step begins and leaves as the step ends, or once its words are read when that is later (17 characters a second, the subtitle standard, 1.2 to 3 s), so it never lingers over the steps after it. A caption whose words outlast its step is named in the done line with how long it stays past the step; shorter words, or more time on the step (its `ms`, then a re-record), let it leave with it. Cutting its duration instead only takes the time its words need. It gives way to the next caption 0.1 s before it and never outlives the clip it starts in; one with too little room to be read is dropped and named in the done line, with the room it needs.
|
|
459
|
+
|
|
455
460
|
**Captions anchored to a step.** A layer's TIME can name a step the way a pin names its place: `"anchor": { "step": "share", "media": "m1", "at": "end", "offset": 0.2 }` starts the layer where that step plays in the cut. The lowering maps the step's moment (its `tStart`, or `tEnd` with `at: "end"`, in `meta.steps` of the media `media` names; absent = the primary) through the segments, speed spans and freezes exactly as a source-anchored span is mapped, then adds `offset` in OUTPUT seconds, so a trim, a speed change or a re-record moves the caption with its step and nobody recomputes a `start`. `start` is the fallback when the step is missing, skipped or cut out, and an anchored layer may leave it out: one whose step then resolves nowhere plays nowhere, which `vos validate` refuses, naming the step. `duration` is the layer's own; on a cut that replays the step's seconds the layer starts in the pass where the step plays the longest (the first on a tie), so a caption about a step lands in the main play rather than a short cold open. A text layer's `{elapsed}` fills with the anchored step's off-camera wait when the recorder kept one (the entry of `meta.skips` that names it, else `offCameraMs` on the step; never a step's own `ms`, which is its reading time), and a step that waited for nothing itself (the beat that lands after an await) reads the nearest earlier wait on its media, which validate notes, as a rounded duration: `"{elapsed} later"` renders `12 minutes later`. `vos validate` says when an anchor cannot resolve (and which fallback plays), when `{elapsed}` has no wait at or before its step to fill from, when two captions are on screen at the same time, naming both, and when a layer anchored to one media's step starts over another media's footage (a caption landed after its gesture, then the footage after that step trimmed away). A document without an anchor lowers exactly as before. Not the re-record `anchor` on spans, which only `vos plan --reuse` reads.
|
|
456
461
|
|
|
457
462
|
**Image and video overlays.** Media kinds on the same lane: `{ "id": "m0", "kind": "image" | "video", "start": 2, "duration": 4, "key": "/logo.png", "width": 0.35, "radius": 12, "opacity": 1, "loop": false, "transform": { … } }`. `key` is a file inside the take directory (`"/logo.png"`) or a URL; `width` is a fraction of the frame width, height follows the media's aspect; corners in design px. Video time is clip-local and muted by design; soundtracks belong to `doc.audio`.
|
|
@@ -4150,6 +4150,7 @@ import {
|
|
|
4150
4150
|
migrateMotion,
|
|
4151
4151
|
momentOutputs as momentOutputs2,
|
|
4152
4152
|
ratedSegments as ratedSegments4,
|
|
4153
|
+
resolveAnchor as resolveLayerAnchor,
|
|
4153
4154
|
sameMedia,
|
|
4154
4155
|
spanOutputExtent as spanOutputExtent3
|
|
4155
4156
|
} from "@vosjs/studio-core";
|
|
@@ -4158,6 +4159,14 @@ import {
|
|
|
4158
4159
|
var CAPTION_MAX = 3;
|
|
4159
4160
|
var CAPTION_MIN = 1.2;
|
|
4160
4161
|
var CAPTION_GAP = 0.1;
|
|
4162
|
+
var READ_CHARS_PER_SECOND = 17;
|
|
4163
|
+
function readingSeconds(text) {
|
|
4164
|
+
const chars = text.trim().replace(/\s+/g, " ").length;
|
|
4165
|
+
return Math.min(
|
|
4166
|
+
CAPTION_MAX,
|
|
4167
|
+
Math.max(CAPTION_MIN, chars / READ_CHARS_PER_SECOND)
|
|
4168
|
+
);
|
|
4169
|
+
}
|
|
4161
4170
|
function fitCaptions(items, slotOf, opts = {}) {
|
|
4162
4171
|
const max = opts.max ?? CAPTION_MAX;
|
|
4163
4172
|
const min = opts.min ?? CAPTION_MIN;
|
|
@@ -4170,7 +4179,11 @@ function fitCaptions(items, slotOf, opts = {}) {
|
|
|
4170
4179
|
const { item, slot } = ordered[i];
|
|
4171
4180
|
const clipRoom = slot.end - slot.start;
|
|
4172
4181
|
const nextRoom = nextStart - gap - slot.start;
|
|
4173
|
-
const
|
|
4182
|
+
const ownRoom = slot.until === void 0 ? Infinity : Math.max(
|
|
4183
|
+
slot.until - slot.start,
|
|
4184
|
+
slot.text === void 0 ? min : readingSeconds(slot.text)
|
|
4185
|
+
);
|
|
4186
|
+
const duration = Math.min(max, clipRoom, nextRoom, ownRoom);
|
|
4174
4187
|
if (duration < min - 1e-9) {
|
|
4175
4188
|
dropped.unshift({
|
|
4176
4189
|
item,
|
|
@@ -4178,11 +4191,19 @@ function fitCaptions(items, slotOf, opts = {}) {
|
|
|
4178
4191
|
});
|
|
4179
4192
|
continue;
|
|
4180
4193
|
}
|
|
4181
|
-
|
|
4194
|
+
const held = slot.until === void 0 ? 0 : duration - (slot.until - slot.start);
|
|
4195
|
+
kept.unshift({
|
|
4196
|
+
item,
|
|
4197
|
+
duration: +duration.toFixed(3),
|
|
4198
|
+
...held > 0.05 ? { held: +held.toFixed(2) } : {}
|
|
4199
|
+
});
|
|
4182
4200
|
nextStart = slot.start;
|
|
4183
4201
|
}
|
|
4184
4202
|
return { kept, dropped };
|
|
4185
4203
|
}
|
|
4204
|
+
function heldLine(label, held) {
|
|
4205
|
+
return `${label} stays ${held.toFixed(2)}s past its step to be read; to let it leave with the step, shorten its words (17 characters read in a second) or give its step more time (its ms, then record again). Do not cut its duration: the words need that time`;
|
|
4206
|
+
}
|
|
4186
4207
|
|
|
4187
4208
|
// src/plugin/moments.ts
|
|
4188
4209
|
import {
|
|
@@ -4343,6 +4364,7 @@ function proposeMotion(input, opts) {
|
|
|
4343
4364
|
const { words: words2, launch, catalog } = opts;
|
|
4344
4365
|
const notes = [];
|
|
4345
4366
|
const skipped = [];
|
|
4367
|
+
const held = [];
|
|
4346
4368
|
const footage = outputLength(doc);
|
|
4347
4369
|
const range2 = [0, footage];
|
|
4348
4370
|
const entrance = launch.entrance;
|
|
@@ -4459,15 +4481,37 @@ function proposeMotion(input, opts) {
|
|
|
4459
4481
|
(s) => c.id !== void 0 && s.id === c.id || s.step === c.step
|
|
4460
4482
|
);
|
|
4461
4483
|
if (!step || step.skipped) continue;
|
|
4462
|
-
const
|
|
4484
|
+
const ref = step.id ?? step.step;
|
|
4485
|
+
const t = resolveLayerAnchor(doc, { step: ref, at: "start" }, rated).start;
|
|
4463
4486
|
if (t === null || t < 0 || t >= footage) continue;
|
|
4464
|
-
|
|
4487
|
+
const until = resolveLayerAnchor(
|
|
4488
|
+
doc,
|
|
4489
|
+
{ step: ref, at: "end" },
|
|
4490
|
+
rated
|
|
4491
|
+
).start;
|
|
4492
|
+
beats.push({
|
|
4493
|
+
caption: c,
|
|
4494
|
+
t: +t.toFixed(3),
|
|
4495
|
+
...until !== null ? { until } : {}
|
|
4496
|
+
});
|
|
4465
4497
|
}
|
|
4466
|
-
const fit = fitCaptions(
|
|
4467
|
-
|
|
4468
|
-
|
|
4498
|
+
const fit = fitCaptions(
|
|
4499
|
+
beats,
|
|
4500
|
+
(b) => ({
|
|
4501
|
+
start: b.t,
|
|
4502
|
+
end: footage,
|
|
4503
|
+
...b.until !== void 0 ? { until: b.until } : {},
|
|
4504
|
+
text: b.caption.caption
|
|
4505
|
+
}),
|
|
4506
|
+
{ max: CAPTION_BEAT_MAX }
|
|
4507
|
+
);
|
|
4469
4508
|
for (const { item, why } of fit.dropped)
|
|
4470
4509
|
skipped.push(`caption for step ${item.caption.step}: ${why}`);
|
|
4510
|
+
for (const k of fit.kept)
|
|
4511
|
+
if (k.held)
|
|
4512
|
+
held.push(
|
|
4513
|
+
heldLine(`the caption for step ${k.item.caption.step}`, k.held)
|
|
4514
|
+
);
|
|
4471
4515
|
for (const { item, duration } of fit.kept)
|
|
4472
4516
|
captionClips.push(
|
|
4473
4517
|
houseCaption(
|
|
@@ -4531,7 +4575,7 @@ function proposeMotion(input, opts) {
|
|
|
4531
4575
|
if (times.length) notes.push(`${times.length} click sound(s)`);
|
|
4532
4576
|
}
|
|
4533
4577
|
doc.audio = clips;
|
|
4534
|
-
return { doc, notes, skipped };
|
|
4578
|
+
return { doc, notes, skipped, held };
|
|
4535
4579
|
}
|
|
4536
4580
|
function destinationMechanics(d, doc) {
|
|
4537
4581
|
const set = [];
|
|
@@ -6754,7 +6798,7 @@ import {
|
|
|
6754
6798
|
import {
|
|
6755
6799
|
docFreezes,
|
|
6756
6800
|
ratedSegments as ratedSegments6,
|
|
6757
|
-
resolveAnchor as
|
|
6801
|
+
resolveAnchor as resolveLayerAnchor2
|
|
6758
6802
|
} from "@vosjs/studio-core";
|
|
6759
6803
|
var MIN_SPAN = 0.15;
|
|
6760
6804
|
var AFTER_STEP = 1;
|
|
@@ -7050,7 +7094,7 @@ function retimeOverlays(prev, next, report) {
|
|
|
7050
7094
|
};
|
|
7051
7095
|
return overlays.map((o) => {
|
|
7052
7096
|
if (o.anchor && typeof o.anchor === "object") {
|
|
7053
|
-
const r =
|
|
7097
|
+
const r = resolveLayerAnchor2(next, o.anchor, newPieces);
|
|
7054
7098
|
if (r.start === null) {
|
|
7055
7099
|
report.flagged.push(
|
|
7056
7100
|
o.start === void 0 ? `overlay ${o.id}: anchored to step ${String(o.anchor.step)}, which is missing, skipped or cut in the new recording \u2014 it has no start to fall back to, so it plays nowhere; re-anchor it or give it a start` : `overlay ${o.id}: anchored to step ${String(o.anchor.step)}, which is missing, skipped or cut in the new recording \u2014 it plays at its fallback ${o.start.toFixed(2)}s; re-anchor or re-place it`
|
|
@@ -7337,7 +7381,11 @@ async function planTake(dir, opts = {}) {
|
|
|
7337
7381
|
if (opts.motion && (fresh || opts.motion.again)) {
|
|
7338
7382
|
const proposed = proposeMotion(doc, opts.motion);
|
|
7339
7383
|
doc = proposed.doc;
|
|
7340
|
-
motion = {
|
|
7384
|
+
motion = {
|
|
7385
|
+
notes: proposed.notes,
|
|
7386
|
+
skipped: proposed.skipped,
|
|
7387
|
+
held: proposed.held
|
|
7388
|
+
};
|
|
7341
7389
|
}
|
|
7342
7390
|
if (opts.still != null) doc.still = opts.still;
|
|
7343
7391
|
await writeJson(take.paths.doc, doc, true);
|
|
@@ -14659,6 +14707,7 @@ import {
|
|
|
14659
14707
|
docMediaList as docMediaList2,
|
|
14660
14708
|
findStep as findStep3,
|
|
14661
14709
|
mediaKey,
|
|
14710
|
+
overlayPlainText,
|
|
14662
14711
|
ratedSegments as ratedSegments8,
|
|
14663
14712
|
resolveAnchor as resolveAnchor5,
|
|
14664
14713
|
segmentOutputExtents as segmentOutputExtents2
|
|
@@ -14744,7 +14793,12 @@ function captionOf(step, actions, script, recorded = []) {
|
|
|
14744
14793
|
}
|
|
14745
14794
|
var safeId = (v) => String(v).replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "step";
|
|
14746
14795
|
function landStepCaptions(doc, actions, opts = {}) {
|
|
14747
|
-
const report = {
|
|
14796
|
+
const report = {
|
|
14797
|
+
landed: [],
|
|
14798
|
+
skipped: [],
|
|
14799
|
+
held: [],
|
|
14800
|
+
uncaptioned: []
|
|
14801
|
+
};
|
|
14748
14802
|
const mine = (o) => !opts.only || opts.only.has(mediaKey(o.anchor?.media));
|
|
14749
14803
|
const kept = (doc.overlays ?? []).filter(
|
|
14750
14804
|
(o) => o.from !== STEP_CAPTIONS_FROM || !mine(o)
|
|
@@ -14763,6 +14817,7 @@ function landStepCaptions(doc, actions, opts = {}) {
|
|
|
14763
14817
|
};
|
|
14764
14818
|
const ends = /* @__PURE__ */ new Map();
|
|
14765
14819
|
const labels = /* @__PURE__ */ new Map();
|
|
14820
|
+
const untils = /* @__PURE__ */ new Map();
|
|
14766
14821
|
const spoken = (media, step, steps) => kept.some((o) => {
|
|
14767
14822
|
if (o.kind !== "text" || o.preset !== "caption") return false;
|
|
14768
14823
|
if (o.anchor && mediaKey(o.anchor.media) === media)
|
|
@@ -14815,6 +14870,8 @@ function landStepCaptions(doc, actions, opts = {}) {
|
|
|
14815
14870
|
from: STEP_CAPTIONS_FROM
|
|
14816
14871
|
};
|
|
14817
14872
|
ends.set(clip, end);
|
|
14873
|
+
const stepEnd = resolveAnchor5(doc, { ...anchor, at: "end" }, rated);
|
|
14874
|
+
if (stepEnd.start !== null) untils.set(clip, stepEnd.start);
|
|
14818
14875
|
labels.set(clip, label);
|
|
14819
14876
|
fresh.push(clip);
|
|
14820
14877
|
report.landed.push({
|
|
@@ -14828,12 +14885,17 @@ function landStepCaptions(doc, actions, opts = {}) {
|
|
|
14828
14885
|
}
|
|
14829
14886
|
const fit = fitCaptions(fresh, (c) => ({
|
|
14830
14887
|
start: c.start,
|
|
14831
|
-
end: ends.get(c) ?? footageEnd
|
|
14888
|
+
end: ends.get(c) ?? footageEnd,
|
|
14889
|
+
...untils.has(c) ? { until: untils.get(c) } : {},
|
|
14890
|
+
...c.kind === "text" ? { text: overlayPlainText(c) } : {}
|
|
14832
14891
|
}));
|
|
14833
14892
|
for (const { item, why } of fit.dropped) {
|
|
14834
14893
|
report.skipped.push(`${labels.get(item) ?? item.id}: ${why}`);
|
|
14835
14894
|
report.landed = report.landed.filter((l) => l.id !== item.id);
|
|
14836
14895
|
}
|
|
14896
|
+
for (const k of fit.kept)
|
|
14897
|
+
if (k.held)
|
|
14898
|
+
report.held.push(heldLine(labels.get(k.item) ?? k.item.id, k.held));
|
|
14837
14899
|
const landedClips = fit.kept.map(({ item, duration }) => {
|
|
14838
14900
|
item.duration = duration;
|
|
14839
14901
|
return item;
|
|
@@ -16335,6 +16397,7 @@ async function cmdJoin(argv) {
|
|
|
16335
16397
|
let captions = null;
|
|
16336
16398
|
if (withCaptions) {
|
|
16337
16399
|
captions = landStepCaptions(plan.doc, plan.actions, { only });
|
|
16400
|
+
for (const line of captions.held) plan.notes.push(line);
|
|
16338
16401
|
for (const k of captions.uncaptioned)
|
|
16339
16402
|
plan.notes.push(
|
|
16340
16403
|
`no step in ${labelOf.get(k) ?? `media ${k}`} carries a caption`
|
|
@@ -16462,7 +16525,7 @@ import {
|
|
|
16462
16525
|
freezeOutputExtents,
|
|
16463
16526
|
mediaKey as mediaKey5,
|
|
16464
16527
|
migrateMotion as migrateMotion4,
|
|
16465
|
-
overlayPlainText,
|
|
16528
|
+
overlayPlainText as overlayPlainText2,
|
|
16466
16529
|
longestOutputExtent as longestOutputExtent3,
|
|
16467
16530
|
ratedSegments as ratedSegments11,
|
|
16468
16531
|
resolveOverlayAnchors as resolveOverlayAnchors2,
|
|
@@ -16576,7 +16639,7 @@ function takeTimeline(rawDoc, actions = /* @__PURE__ */ new Map()) {
|
|
|
16576
16639
|
const start = o.start;
|
|
16577
16640
|
if (start === void 0) return [];
|
|
16578
16641
|
const raw = rawById.get(o.id);
|
|
16579
|
-
const text = o.kind === "text" ?
|
|
16642
|
+
const text = o.kind === "text" ? overlayPlainText2(o) : o.kind === "html" ? void 0 : o.key;
|
|
16580
16643
|
return [
|
|
16581
16644
|
{
|
|
16582
16645
|
id: o.id,
|
|
@@ -19328,7 +19391,8 @@ async function cmdPlan(argvIn) {
|
|
|
19328
19391
|
const zoomManual = s.doc.zoom.length - zoomAuto;
|
|
19329
19392
|
const captionLines = captions ? `
|
|
19330
19393
|
${captions.landed.length} step caption${captions.landed.length === 1 ? "" : "s"} anchored to their steps` + (captions.skipped.length ? `
|
|
19331
|
-
not landed: ${captions.skipped.join("; ")}` : "") + (captions.
|
|
19394
|
+
not landed: ${captions.skipped.join("; ")}` : "") + (captions.held.length ? `
|
|
19395
|
+
${captions.held.join("\n ")}` : "") + (captions.uncaptioned.length ? `
|
|
19332
19396
|
no step in ${captions.uncaptioned.length === 1 && captions.uncaptioned[0] === "" ? "this take" : captions.uncaptioned.map((m) => m ? `media ${m}` : "the primary").join(", ")} carries a caption` : "") : "";
|
|
19333
19397
|
const layoutLines = s.layout ? `
|
|
19334
19398
|
layout from ${s.styleFrom}: ${[
|
|
@@ -19339,7 +19403,8 @@ async function cmdPlan(argvIn) {
|
|
|
19339
19403
|
${s.layout.notes.join("\n ")}` : "") : "";
|
|
19340
19404
|
const motionLines = s.motion ? (s.motion.notes.length ? `
|
|
19341
19405
|
proposed: ${s.motion.notes.join(", ")}` : "") + (s.motion.skipped.length ? `
|
|
19342
|
-
not proposed: ${s.motion.skipped.join("; ")}` : "")
|
|
19406
|
+
not proposed: ${s.motion.skipped.join("; ")}` : "") + (s.motion.held.length ? `
|
|
19407
|
+
${s.motion.held.join("\n ")}` : "") : "";
|
|
19343
19408
|
const reuseLines = s.reuse ? `
|
|
19344
19409
|
reused ${s.reuse.from}: ${s.reuse.anchored} anchored + ${s.reuse.mapped} mapped span(s)` + (s.reuse.flagged.length ? `
|
|
19345
19410
|
flagged:
|
|
@@ -20617,4 +20682,4 @@ export {
|
|
|
20617
20682
|
verbHelp,
|
|
20618
20683
|
run
|
|
20619
20684
|
};
|
|
20620
|
-
//# sourceMappingURL=chunk-
|
|
20685
|
+
//# sourceMappingURL=chunk-5VGIQKVT.js.map
|