ossclip 0.1.34 → 0.1.36
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-pWbFr8vc.js +168 -0
- package/editor-dist/index.html +1 -1
- package/package.json +4 -4
- package/src/cover.ts +18 -0
- package/src/edit-health.ts +51 -0
- package/src/edit-port.ts +296 -0
- package/src/edit.ts +340 -3
- package/src/interactive/offer-editor.ts +29 -4
- package/src/interactive/produce-argv.ts +18 -1
- package/src/interactive/produce-wizard.ts +47 -4
- package/src/produce.ts +652 -3
- package/src/program.ts +83 -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
|
|
@@ -446,6 +472,21 @@ export function buildProgram(): Command {
|
|
|
446
472
|
"(set it once with coverInVideo: true in ~/.ossclip/config.json)",
|
|
447
473
|
)
|
|
448
474
|
.option("--no-cover-in-video", "no cover overlay, even when the config turns it on")
|
|
475
|
+
// The watermark's tri-state carrying a VALUE: commander folds the pair
|
|
476
|
+
// onto one key (string when typed, false for --no-color-grade, undefined
|
|
477
|
+
// when neither — which is what lets overrides.json and then the config's
|
|
478
|
+
// `colorGrade` decide). The value is NOT parsed here, unlike --sfx-level:
|
|
479
|
+
// it may be a preset id OR a .cube filename, so an enum parse can't hold
|
|
480
|
+
// it — classification and validation live at the consumer
|
|
481
|
+
// (colorGradeFlagValue / resolveProductionColorGrade in produce.ts),
|
|
482
|
+
// where a typo warns and the run proceeds ungraded rather than dying.
|
|
483
|
+
.option(
|
|
484
|
+
"--color-grade <look>",
|
|
485
|
+
"color grade the footage: a preset (talking-head | teal-orange | filmic-fade | " +
|
|
486
|
+
"cwa | punchy | mono) or a .cube LUT filename from ~/.ossclip/luts " +
|
|
487
|
+
'(set it once with colorGrade: {"preset": "..."} in ~/.ossclip/config.json)',
|
|
488
|
+
)
|
|
489
|
+
.option("--no-color-grade", "no color grade, even when the config sets one")
|
|
449
490
|
// Same tri-state shape as --watermark above (positive declared first so
|
|
450
491
|
// commander's default stays undefined = "not typed"): the config's
|
|
451
492
|
// `youtube` key supplies the default (resolveYoutube), and a typed
|
|
@@ -661,6 +702,12 @@ export function buildProgram(): Command {
|
|
|
661
702
|
noiseDb: opts.noiseDb,
|
|
662
703
|
produce: opts.produce,
|
|
663
704
|
intent: opts.intent,
|
|
705
|
+
// `--sfx-level` implies `--sfx` (sfxFlag), resolved HERE so the
|
|
706
|
+
// implication is one pure function rather than a condition produce
|
|
707
|
+
// has to remember. Untyped stays undefined, which is what lets the
|
|
708
|
+
// config's `sfx` key decide (`resolveSfx` at the use site).
|
|
709
|
+
sfx: sfxFlag(opts.sfx, opts.sfxLevel),
|
|
710
|
+
sfxLevel: opts.sfxLevel,
|
|
664
711
|
provider,
|
|
665
712
|
llmModel: opts.llmModel,
|
|
666
713
|
llmFastModel: opts.llmFastModel,
|
|
@@ -690,6 +737,12 @@ export function buildProgram(): Command {
|
|
|
690
737
|
// The watermark's tri-state again, resolved by resolveCoverInVideo
|
|
691
738
|
// at the use site against the config's `coverInVideo`.
|
|
692
739
|
coverInVideo: opts.coverInVideo,
|
|
740
|
+
// string | false | undefined straight through: undefined = "not
|
|
741
|
+
// typed" lets overrides.json and then the config's `colorGrade`
|
|
742
|
+
// decide, and the value itself is classified and validated at the
|
|
743
|
+
// use site (resolveProductionColorGrade) — see the option's own
|
|
744
|
+
// comment for why no parse happens here.
|
|
745
|
+
colorGrade: opts.colorGrade,
|
|
693
746
|
// The same tri-state contract as watermark, resolved by
|
|
694
747
|
// resolveYoutube at the use site; --portrait rides along untyped =
|
|
695
748
|
// undefined so the config's path can supply it.
|
|
@@ -761,7 +814,13 @@ export function buildProgram(): Command {
|
|
|
761
814
|
// run does write. Reusing this path is what gives --review the edit
|
|
762
815
|
// command's whole posture (resolveEditorPageDir's loud "run pnpm
|
|
763
816
|
// build" degrade, startEditServer, openInBrowser) for free.
|
|
764
|
-
await offerEditor(result, {
|
|
817
|
+
await offerEditor(result, {
|
|
818
|
+
flag: openEditor,
|
|
819
|
+
port: opts.editorPort,
|
|
820
|
+
// Typed-only, like `edit`'s --port: commander's 5174 default is not
|
|
821
|
+
// a choice, so a busy port still attaches or bumps for it.
|
|
822
|
+
portPinned: command.getOptionValueSource("editorPort") === "cli",
|
|
823
|
+
});
|
|
765
824
|
// Deliberately LAST — after the render summary and the editor offer —
|
|
766
825
|
// so the one question ossclip ever asks is the last thing on screen.
|
|
767
826
|
await maybeAskRating(telemetry);
|
|
@@ -956,7 +1015,7 @@ export function buildProgram(): Command {
|
|
|
956
1015
|
.argument("[workdir]", "a work directory containing render-props.json")
|
|
957
1016
|
.option("--port <n>", "port to listen on", (v) => Number.parseInt(v, 10), 5174)
|
|
958
1017
|
.option("--no-open", "do not open a browser")
|
|
959
|
-
.action(async (workdir: string | undefined, opts) => {
|
|
1018
|
+
.action(async (workdir: string | undefined, opts, command: Command) => {
|
|
960
1019
|
const { startEditServer, resolveEditorPageDir } = await import("./edit");
|
|
961
1020
|
// An npm install ships the page prebuilt (editor-dist/); a clone builds
|
|
962
1021
|
// it once with `pnpm build`. A server that starts fine but 404s every
|
|
@@ -976,11 +1035,29 @@ export function buildProgram(): Command {
|
|
|
976
1035
|
const target =
|
|
977
1036
|
workdir === undefined ? undefined : await resolveWorkdirArgument(workdir, "edit");
|
|
978
1037
|
|
|
979
|
-
|
|
980
|
-
|
|
1038
|
+
// A busy port is not a crash. `openEditServer` owns the whole ladder —
|
|
1039
|
+
// attach to our own editor already serving THIS project, prompt about
|
|
1040
|
+
// one serving another, step around a stranger — and it needs to know
|
|
1041
|
+
// whether the port was TYPED: someone who passes `--port` has a reason
|
|
1042
|
+
// for that number (a bookmark, a tunnel), so their run refuses rather
|
|
1043
|
+
// than silently landing somewhere else. `getOptionValueSource` is the
|
|
1044
|
+
// same "did the user type it" test `--sort` uses.
|
|
1045
|
+
const { openEditServer, liveEditPortDeps } = await import("./edit-port");
|
|
1046
|
+
const opened = await openEditServer(
|
|
1047
|
+
target ?? null,
|
|
1048
|
+
{ port: opts.port, pinned: command.getOptionValueSource("port") === "cli" },
|
|
1049
|
+
liveEditPortDeps((port) => startEditServer(target, { port, pageDir })),
|
|
1050
|
+
);
|
|
1051
|
+
// The user picked "cancel" at the conflict prompt: nothing started,
|
|
1052
|
+
// nothing to open, exit 0.
|
|
1053
|
+
if (opened.kind === "cancelled") return;
|
|
1054
|
+
const url = opened.kind === "attached" ? opened.url : opened.server.url;
|
|
1055
|
+
// The attach path already printed "▸ already open at …" — saying
|
|
1056
|
+
// "editor at" underneath it would read as a second server.
|
|
1057
|
+
if (opened.kind === "started") console.log(`▸ editor at ${url}`);
|
|
981
1058
|
if (opts.open) {
|
|
982
1059
|
const { openInBrowser } = await import("./open");
|
|
983
|
-
openInBrowser(
|
|
1060
|
+
openInBrowser(url);
|
|
984
1061
|
}
|
|
985
1062
|
// Fire-and-forget on purpose: the server keeps the process alive for
|
|
986
1063
|
// 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
|
}
|