ossclip 0.1.33 → 0.1.35
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/editor-dist/assets/index-DSB_SCmp.js +166 -0
- package/editor-dist/index.html +1 -1
- package/package.json +4 -4
- package/src/caption-regen-panel.ts +74 -0
- package/src/edit-health.ts +51 -0
- package/src/edit-port.ts +296 -0
- package/src/edit.ts +905 -10
- package/src/interactive/offer-editor.ts +29 -4
- package/src/interactive/produce-argv.ts +18 -1
- package/src/interactive/produce-wizard.ts +40 -3
- package/src/produce.ts +595 -6
- package/src/program.ts +119 -8
- package/src/publish.ts +350 -6
- package/src/render-replay-args.ts +50 -0
- package/src/replay-argv.ts +30 -0
- package/editor-dist/assets/index-DA7vDWn_.js +0 -166
|
@@ -11,7 +11,17 @@ import { renderCommand } from "./render";
|
|
|
11
11
|
*/
|
|
12
12
|
export async function offerEditor(
|
|
13
13
|
result: ProduceResult,
|
|
14
|
-
opts: {
|
|
14
|
+
opts: {
|
|
15
|
+
flag: boolean | undefined;
|
|
16
|
+
port: number;
|
|
17
|
+
/**
|
|
18
|
+
* Whether the user TYPED `--editor-port`. Commander's 5174 default is not
|
|
19
|
+
* a choice anybody made, so the common case must be free to attach or step
|
|
20
|
+
* around a busy port; only a typed port is defended (edit-port.ts's
|
|
21
|
+
* `pinned`, and the `--port` half of the same rule in program.ts).
|
|
22
|
+
*/
|
|
23
|
+
portPinned: boolean;
|
|
24
|
+
},
|
|
15
25
|
): Promise<void> {
|
|
16
26
|
const pref: OpenEditorPref = loadConfig().openEditorAfterProduce ?? "ask";
|
|
17
27
|
const decision = decideOpenEditor({
|
|
@@ -64,8 +74,23 @@ export async function offerEditor(
|
|
|
64
74
|
);
|
|
65
75
|
return;
|
|
66
76
|
}
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
// The same busy-port ladder `ossclip edit` runs (edit-port.ts): a produce
|
|
78
|
+
// finishing into an already-open editor on THIS project attaches to it
|
|
79
|
+
// instead of dying on EADDRINUSE — which here would throw away the run's
|
|
80
|
+
// whole summary behind a stack trace. `liveEditPortDeps` reads the real
|
|
81
|
+
// `isInteractive()`, so the three-way prompt is available exactly where this
|
|
82
|
+
// offer already asks questions, and absent in a piped run.
|
|
83
|
+
const { openEditServer, liveEditPortDeps } = await import("../edit-port");
|
|
84
|
+
const opened = await openEditServer(
|
|
85
|
+
result.workdir,
|
|
86
|
+
{ port: opts.port, pinned: opts.portPinned },
|
|
87
|
+
liveEditPortDeps((port) => startEditServer(result.workdir, { port, pageDir })),
|
|
88
|
+
);
|
|
89
|
+
if (opened.kind === "cancelled") return;
|
|
90
|
+
const url = opened.kind === "attached" ? opened.url : opened.server.url;
|
|
91
|
+
// The attach path already printed "▸ already open at …"; a second line
|
|
92
|
+
// underneath it would read as a second server.
|
|
93
|
+
if (opened.kind === "started") console.log(`▸ editor at ${url}`);
|
|
69
94
|
const { openInBrowser } = await import("../open");
|
|
70
|
-
openInBrowser(
|
|
95
|
+
openInBrowser(url);
|
|
71
96
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProviderName } from "@ossclip/core";
|
|
1
|
+
import type { ProviderName, SfxLevel } from "@ossclip/core";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Wizard answers → the argv a user could have typed.
|
|
@@ -35,6 +35,16 @@ export interface ProduceExtras {
|
|
|
35
35
|
* it exists to beat a future config-off, and emitting it here would
|
|
36
36
|
* restate the default. */
|
|
37
37
|
jumpCuts?: boolean;
|
|
38
|
+
/** Sound effects, the watermark's polarity with a level attached: PRESENT
|
|
39
|
+
* means on (the wizard only ever turns it ON — off is the default, there is
|
|
40
|
+
* no `--no-sfx` spelling to mirror, and a config-on user who wants silence
|
|
41
|
+
* for one run edits the config's `sfx` key), and the value is whatever the
|
|
42
|
+
* level follow-up answered. Typed as core's `SfxLevel`, not an inline
|
|
43
|
+
* union, for `llm`'s reason: a level added to `SfxLevelSchema` must not be
|
|
44
|
+
* silently unofferable here. Only reachable under graphics — sound effects
|
|
45
|
+
* are placed against the producer's beat sheet, so `extrasFor` gates the
|
|
46
|
+
* entry the way it gates `--clip`. */
|
|
47
|
+
sfx?: SfxLevel;
|
|
38
48
|
/** The YouTube pack (Y2): the wizard only ever turns it ON — off is the
|
|
39
49
|
* default, and a config-on user who wants it off for one run types
|
|
40
50
|
* `--no-youtube`, flags-only like `--no-watermark`. */
|
|
@@ -116,6 +126,13 @@ export function produceArgv(a: ProduceAnswers): string[] {
|
|
|
116
126
|
// negative spelling — auto must stay an ABSENT flag, or the taught
|
|
117
127
|
// command line restates a default.
|
|
118
128
|
if (e.jumpCuts === false) argv.push("--no-jump-cuts");
|
|
129
|
+
// One flag, never both: `--sfx-level` already implies `--sfx` (program.ts's
|
|
130
|
+
// `sfxFlag`), which is why replay-argv pins a level WITHOUT the switch too —
|
|
131
|
+
// emitting the pair would teach a flag that changes nothing. And `normal` is
|
|
132
|
+
// the CLI's own default, so naming it would restate a default per the
|
|
133
|
+
// elision rule above: a normal-level run's whole sound design is `--sfx`.
|
|
134
|
+
if (e.sfx === "normal") argv.push("--sfx");
|
|
135
|
+
else if (e.sfx !== undefined) argv.push("--sfx-level", e.sfx);
|
|
119
136
|
// Watermark's shape: only the ON tick emits (off is the default, elided),
|
|
120
137
|
// and the portrait only rides along with a value — the wizard already
|
|
121
138
|
// dropped empty answers.
|
|
@@ -12,8 +12,9 @@ import { assertInteractive, confirm, intro, multiselect, select, text, unwrap }
|
|
|
12
12
|
/**
|
|
13
13
|
* The produce wizard. Forty-one flags (plus the positional input path)
|
|
14
14
|
* sorted into three tiers: six prompts asked directly — the input path, plus
|
|
15
|
-
* five flags (--out, --cleanup, --aspect, --produce, --intent) —
|
|
16
|
-
* behind one "anything else?" multiselect
|
|
15
|
+
* five flags (--out, --cleanup, --aspect, --produce, --intent) — twelve
|
|
16
|
+
* behind one "anything else?" multiselect (--sfx being the twelfth, with
|
|
17
|
+
* --sfx-level as its follow-up prompt rather than an entry of its own), and the remaining stay flags-only:
|
|
17
18
|
* debug/internal surfaces, replay-only fields, --no-watermark (the
|
|
18
19
|
* multiselect only turns the credit ON; off is already the default),
|
|
19
20
|
* --no-youtube (the same shape: the pack entry only turns it ON),
|
|
@@ -48,6 +49,11 @@ const EXTRAS = [
|
|
|
48
49
|
{ value: "sourceIsEdited", label: "Source already has burned-in text", hint: "--source-is-edited" },
|
|
49
50
|
{ value: "captionsOff", label: "Turn the burned-in captions off", hint: "--no-captions" },
|
|
50
51
|
{ value: "jumpCutsOff", label: "No punch-in zooms at cuts", hint: "--no-jump-cuts" },
|
|
52
|
+
// Watermark/youtube polarity: OFF is the default and the entry only ever
|
|
53
|
+
// turns it ON — `--no-sfx` does not exist (program.ts declares `--sfx`
|
|
54
|
+
// alone so the config's `sfx` key can still supply it), so there is no OFF
|
|
55
|
+
// spelling to mirror in a second entry.
|
|
56
|
+
{ value: "sfx", label: "Add sound effects (whoosh, ding…)", hint: "--sfx" },
|
|
51
57
|
{ value: "watermark", label: 'Credit the tool with a small "made with ossclip"', hint: "--watermark" },
|
|
52
58
|
// The hint says the approval part out loud (thumbnail UX, 2026-08-16):
|
|
53
59
|
// ticking this adds an interactive stop before the render, and a surprise
|
|
@@ -69,6 +75,15 @@ const EXTRAS = [
|
|
|
69
75
|
* graphics is already on. Exported and kept pure so this can be asserted
|
|
70
76
|
* without a TTY.
|
|
71
77
|
*
|
|
78
|
+
* The sfx entry is gated on the same precedent (2026-08-29). Sound effects
|
|
79
|
+
* are placed against the producer's beat sheet, so a `--sfx` run without
|
|
80
|
+
* `--produce` reaches produce.ts's own "sound effects are placed against the
|
|
81
|
+
* producer's beat sheet — add --produce" warning and renders silent (a
|
|
82
|
+
* previous run's plan in the workdir is the only thing that saves it, which
|
|
83
|
+
* is not something a menu can promise). A guaranteed-inert menu item is the
|
|
84
|
+
* same broken offer as the clip one, softer only in that it warns instead of
|
|
85
|
+
* throwing — so it is only ever listed once graphics is already on.
|
|
86
|
+
*
|
|
72
87
|
* `watermarkFromConfig` (review, minor a): on a config-on machine the
|
|
73
88
|
* watermark entry sits UNCHECKED while the credit will render anyway —
|
|
74
89
|
* unchecked is "don't emit the flag", not "off", and the multiselect has no
|
|
@@ -81,7 +96,9 @@ export function extrasFor(
|
|
|
81
96
|
graphics: boolean,
|
|
82
97
|
opts: { watermarkFromConfig?: boolean } = {},
|
|
83
98
|
): { value: (typeof EXTRAS)[number]["value"]; label: string; hint: string }[] {
|
|
84
|
-
const list = graphics
|
|
99
|
+
const list = graphics
|
|
100
|
+
? [...EXTRAS]
|
|
101
|
+
: EXTRAS.filter((e) => e.value !== "graphicsClip" && e.value !== "sfx");
|
|
85
102
|
if (opts.watermarkFromConfig !== true) return [...list];
|
|
86
103
|
return list.map((e) =>
|
|
87
104
|
e.value === "watermark"
|
|
@@ -346,6 +363,26 @@ export async function produceWizard(
|
|
|
346
363
|
// Same OFF-switch shape (the punch defaults ON, face-only): a tick maps
|
|
347
364
|
// to `jumpCuts: false` and produceArgv emits `--no-jump-cuts`.
|
|
348
365
|
if (chosen.includes("jumpCutsOff")) extras.jumpCuts = false;
|
|
366
|
+
if (chosen.includes("sfx")) {
|
|
367
|
+
// Follow-up under the same extra, like --clip's seconds prompt: the level
|
|
368
|
+
// only means anything once effects are on. `normal` is preselected
|
|
369
|
+
// because it is the CLI's own default — and picking it still emits the
|
|
370
|
+
// bare `--sfx` rather than `--sfx-level normal`, produceArgv's
|
|
371
|
+
// default-elision rule. The hints say what the level actually CHANGES
|
|
372
|
+
// (density, and whether the meme-tagged sounds are eligible at all),
|
|
373
|
+
// because "subtle/normal/meme" alone reads as a volume knob.
|
|
374
|
+
extras.sfx = unwrap(
|
|
375
|
+
await select({
|
|
376
|
+
message: "How much sound design?",
|
|
377
|
+
initialValue: "normal",
|
|
378
|
+
options: [
|
|
379
|
+
{ value: "subtle", label: "subtle", hint: "a couple of accents a minute" },
|
|
380
|
+
{ value: "normal", label: "normal", hint: "recommended" },
|
|
381
|
+
{ value: "meme", label: "meme", hint: "denser, and meme-tagged sounds become eligible" },
|
|
382
|
+
],
|
|
383
|
+
}),
|
|
384
|
+
) as ProduceExtras["sfx"];
|
|
385
|
+
}
|
|
349
386
|
if (chosen.includes("watermark")) extras.watermark = true;
|
|
350
387
|
if (chosen.includes("youtube")) {
|
|
351
388
|
extras.youtube = true;
|