ossclip 0.1.34 → 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/edit-health.ts +51 -0
- package/src/edit-port.ts +296 -0
- package/src/edit.ts +273 -1
- 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 +467 -2
- package/src/program.ts +62 -6
- package/src/replay-argv.ts +30 -0
- package/editor-dist/assets/index-Dn813PGp.js +0 -166
package/src/program.ts
CHANGED
|
@@ -9,13 +9,14 @@ import {
|
|
|
9
9
|
RESOLUTION_CHOICES,
|
|
10
10
|
ResolutionChoiceSchema,
|
|
11
11
|
SceneComponentIdSchema,
|
|
12
|
+
SfxLevelSchema,
|
|
12
13
|
} from "@ossclip/core";
|
|
13
14
|
import { STUDIO_ENTRY } from "@ossclip/renderer";
|
|
14
15
|
import { loadEnvFiles } from "./env";
|
|
15
16
|
import { ExportFormatSchema, runAnalyze } from "./analyze";
|
|
16
17
|
import { expandHome } from "./paths";
|
|
17
18
|
import { phaseBucketProps } from "./phase-timing";
|
|
18
|
-
import { dictionaryFlag, jumpCutsFlag, produce, reviewFlag } from "./produce";
|
|
19
|
+
import { dictionaryFlag, jumpCutsFlag, produce, reviewFlag, sfxFlag } from "./produce";
|
|
19
20
|
import { accountsFlag, atFlag, deliveryFlag, platformsFlag, youtubePrivacyFlag } from "./publish";
|
|
20
21
|
// The one interactive import that is STATIC rather than `await import()`: the
|
|
21
22
|
// `resetInputSource()` run boundary in `buildProgram` has to run synchronously
|
|
@@ -343,6 +344,31 @@ export function buildProgram(): Command {
|
|
|
343
344
|
"runs so the editor's Render replays the same window without an LLM call",
|
|
344
345
|
)
|
|
345
346
|
.option("--intent <text>", "what the video should be ('educational video about agents…')")
|
|
347
|
+
.option(
|
|
348
|
+
"--sfx",
|
|
349
|
+
// No commander default, the `--watermark` contract: untyped must stay
|
|
350
|
+
// UNDEFINED so the config's `sfx` key can supply it — a `false` default
|
|
351
|
+
// here would make every run's flag beat the config it was written for.
|
|
352
|
+
"place sound effects from the bundled pack (and any pack in ~/.ossclip/sfx) " +
|
|
353
|
+
"on the beats the producer planned. Requires --produce. Config key: \"sfx\"",
|
|
354
|
+
)
|
|
355
|
+
.option(
|
|
356
|
+
"--sfx-level <level>",
|
|
357
|
+
"how much sound design: subtle | normal (default) | meme (unlocks the " +
|
|
358
|
+
"meme-tagged sounds). Implies --sfx. Config key: \"sfxLevel\"",
|
|
359
|
+
(v: string) => {
|
|
360
|
+
// Parse, never coerce (CLAUDE.md): a typo'd `--sfx-level mem` must not
|
|
361
|
+
// silently fall back to `normal` — the level decides whether a vine
|
|
362
|
+
// boom can land in the video at all.
|
|
363
|
+
const parsed = SfxLevelSchema.safeParse(v.trim());
|
|
364
|
+
if (!parsed.success) {
|
|
365
|
+
throw new InvalidArgumentError(
|
|
366
|
+
`--sfx-level wants one of ${SfxLevelSchema.options.join(", ")}, got "${v}"`,
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
return parsed.data;
|
|
370
|
+
},
|
|
371
|
+
)
|
|
346
372
|
.option(
|
|
347
373
|
"--llm <provider>",
|
|
348
374
|
// Must state `defaultProviderName`'s real order — the old text omitted
|
|
@@ -661,6 +687,12 @@ export function buildProgram(): Command {
|
|
|
661
687
|
noiseDb: opts.noiseDb,
|
|
662
688
|
produce: opts.produce,
|
|
663
689
|
intent: opts.intent,
|
|
690
|
+
// `--sfx-level` implies `--sfx` (sfxFlag), resolved HERE so the
|
|
691
|
+
// implication is one pure function rather than a condition produce
|
|
692
|
+
// has to remember. Untyped stays undefined, which is what lets the
|
|
693
|
+
// config's `sfx` key decide (`resolveSfx` at the use site).
|
|
694
|
+
sfx: sfxFlag(opts.sfx, opts.sfxLevel),
|
|
695
|
+
sfxLevel: opts.sfxLevel,
|
|
664
696
|
provider,
|
|
665
697
|
llmModel: opts.llmModel,
|
|
666
698
|
llmFastModel: opts.llmFastModel,
|
|
@@ -761,7 +793,13 @@ export function buildProgram(): Command {
|
|
|
761
793
|
// run does write. Reusing this path is what gives --review the edit
|
|
762
794
|
// command's whole posture (resolveEditorPageDir's loud "run pnpm
|
|
763
795
|
// build" degrade, startEditServer, openInBrowser) for free.
|
|
764
|
-
await offerEditor(result, {
|
|
796
|
+
await offerEditor(result, {
|
|
797
|
+
flag: openEditor,
|
|
798
|
+
port: opts.editorPort,
|
|
799
|
+
// Typed-only, like `edit`'s --port: commander's 5174 default is not
|
|
800
|
+
// a choice, so a busy port still attaches or bumps for it.
|
|
801
|
+
portPinned: command.getOptionValueSource("editorPort") === "cli",
|
|
802
|
+
});
|
|
765
803
|
// Deliberately LAST — after the render summary and the editor offer —
|
|
766
804
|
// so the one question ossclip ever asks is the last thing on screen.
|
|
767
805
|
await maybeAskRating(telemetry);
|
|
@@ -956,7 +994,7 @@ export function buildProgram(): Command {
|
|
|
956
994
|
.argument("[workdir]", "a work directory containing render-props.json")
|
|
957
995
|
.option("--port <n>", "port to listen on", (v) => Number.parseInt(v, 10), 5174)
|
|
958
996
|
.option("--no-open", "do not open a browser")
|
|
959
|
-
.action(async (workdir: string | undefined, opts) => {
|
|
997
|
+
.action(async (workdir: string | undefined, opts, command: Command) => {
|
|
960
998
|
const { startEditServer, resolveEditorPageDir } = await import("./edit");
|
|
961
999
|
// An npm install ships the page prebuilt (editor-dist/); a clone builds
|
|
962
1000
|
// it once with `pnpm build`. A server that starts fine but 404s every
|
|
@@ -976,11 +1014,29 @@ export function buildProgram(): Command {
|
|
|
976
1014
|
const target =
|
|
977
1015
|
workdir === undefined ? undefined : await resolveWorkdirArgument(workdir, "edit");
|
|
978
1016
|
|
|
979
|
-
|
|
980
|
-
|
|
1017
|
+
// A busy port is not a crash. `openEditServer` owns the whole ladder —
|
|
1018
|
+
// attach to our own editor already serving THIS project, prompt about
|
|
1019
|
+
// one serving another, step around a stranger — and it needs to know
|
|
1020
|
+
// whether the port was TYPED: someone who passes `--port` has a reason
|
|
1021
|
+
// for that number (a bookmark, a tunnel), so their run refuses rather
|
|
1022
|
+
// than silently landing somewhere else. `getOptionValueSource` is the
|
|
1023
|
+
// same "did the user type it" test `--sort` uses.
|
|
1024
|
+
const { openEditServer, liveEditPortDeps } = await import("./edit-port");
|
|
1025
|
+
const opened = await openEditServer(
|
|
1026
|
+
target ?? null,
|
|
1027
|
+
{ port: opts.port, pinned: command.getOptionValueSource("port") === "cli" },
|
|
1028
|
+
liveEditPortDeps((port) => startEditServer(target, { port, pageDir })),
|
|
1029
|
+
);
|
|
1030
|
+
// The user picked "cancel" at the conflict prompt: nothing started,
|
|
1031
|
+
// nothing to open, exit 0.
|
|
1032
|
+
if (opened.kind === "cancelled") return;
|
|
1033
|
+
const url = opened.kind === "attached" ? opened.url : opened.server.url;
|
|
1034
|
+
// The attach path already printed "▸ already open at …" — saying
|
|
1035
|
+
// "editor at" underneath it would read as a second server.
|
|
1036
|
+
if (opened.kind === "started") console.log(`▸ editor at ${url}`);
|
|
981
1037
|
if (opts.open) {
|
|
982
1038
|
const { openInBrowser } = await import("./open");
|
|
983
|
-
openInBrowser(
|
|
1039
|
+
openInBrowser(url);
|
|
984
1040
|
}
|
|
985
1041
|
// Fire-and-forget on purpose: the server keeps the process alive for
|
|
986
1042
|
// the request's lifetime, and awaiting here would put a metrics POST
|
package/src/replay-argv.ts
CHANGED
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
// Type-only, so no runtime edge back into produce.ts (which imports this
|
|
21
21
|
// module): the tri-state's vocabulary belongs to its resolver.
|
|
22
22
|
import type { JumpCutsMode } from "./produce";
|
|
23
|
+
// Type-only for the same reason: the level's own zod enum lives in core's
|
|
24
|
+
// producer half, and this module must stay a pure argv builder.
|
|
25
|
+
import type { SfxLevel } from "@ossclip/core";
|
|
23
26
|
|
|
24
27
|
let stashed: string[] | null = null;
|
|
25
28
|
|
|
@@ -71,6 +74,10 @@ export function recordedProduceArgs(pins: {
|
|
|
71
74
|
audience?: string;
|
|
72
75
|
/** The RESOLVED thumbnail brief — pinned only when non-empty. */
|
|
73
76
|
thumbnailBrief?: string;
|
|
77
|
+
/** The RESOLVED `--sfx` switch (flag or config), pinned only when ON. */
|
|
78
|
+
sfx?: boolean;
|
|
79
|
+
/** The RESOLVED `--sfx-level`, pinned alongside an ON `sfx`. */
|
|
80
|
+
sfxLevel?: SfxLevel;
|
|
74
81
|
}): string[] {
|
|
75
82
|
// --review and --no-render are stripped at record (cut-review step 1):
|
|
76
83
|
// command.json exists for exactly one consumer — the editor's Render
|
|
@@ -201,5 +208,28 @@ export function recordedProduceArgs(pins: {
|
|
|
201
208
|
) {
|
|
202
209
|
args.push("--thumbnail-brief", pins.thumbnailBrief);
|
|
203
210
|
}
|
|
211
|
+
// The sound-effect pins, the watermark's config-dependent-default rationale
|
|
212
|
+
// on a pair of flags: `sfx`/`sfxLevel` are both config keys, so an unpinned
|
|
213
|
+
// record replays a different amount of sound design — or none — the moment
|
|
214
|
+
// that config is edited or the replay runs on another machine. It matters
|
|
215
|
+
// more here than for the watermark: an editor render carries the REVIEWED
|
|
216
|
+
// plan forward from production.json instead of re-placing (produce's
|
|
217
|
+
// `priorSfxPlan`), so `--sfx` is what decides whether the sound design the
|
|
218
|
+
// user just dragged into place is in the video at all.
|
|
219
|
+
//
|
|
220
|
+
// ON ONLY, and that is the jump-cuts "auto" case rather than the watermark's
|
|
221
|
+
// both-directions rule: there is no `--no-sfx` spelling to pin an off-run
|
|
222
|
+
// with (program.ts declares `--sfx` alone, so the config key can still
|
|
223
|
+
// supply the default). An off-record replayed under a later config-on
|
|
224
|
+
// therefore GAINS sound effects — the accepted cost of a flag with one
|
|
225
|
+
// spelling, and the day `--no-sfx` exists this pin becomes unconditional
|
|
226
|
+
// like the watermark's. `--sfx-level` implies `--sfx`, so a typed level in
|
|
227
|
+
// the argv already settles the switch and the includes-guard leaves it be.
|
|
228
|
+
if (pins.sfx === true) {
|
|
229
|
+
if (!args.includes("--sfx") && !args.includes("--sfx-level")) args.push("--sfx");
|
|
230
|
+
if (pins.sfxLevel !== undefined && !args.includes("--sfx-level")) {
|
|
231
|
+
args.push("--sfx-level", pins.sfxLevel);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
204
234
|
return args;
|
|
205
235
|
}
|