ossclip 0.1.7 → 0.1.10
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-Bo0WME7k.js +159 -0
- package/editor-dist/assets/index-C8IPo60X.css +1 -0
- package/editor-dist/index.html +2 -1
- package/package.json +4 -4
- package/src/edit.ts +14 -4
- package/src/interactive/produce-argv.ts +2 -0
- package/src/interactive/produce-wizard.ts +44 -16
- package/src/produce.ts +600 -40
- package/src/program.ts +104 -27
- package/editor-dist/assets/index-otiVWeTH.js +0 -159
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.ossclip-scroll-list{scrollbar-width:thin;scrollbar-color:#3a3a44 transparent}.ossclip-scroll-list::-webkit-scrollbar{width:8px}.ossclip-scroll-list::-webkit-scrollbar-track{background:transparent}.ossclip-scroll-list::-webkit-scrollbar-thumb{background:#3a3a44;border-radius:4px}
|
package/editor-dist/index.html
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>ossclip editor</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-Bo0WME7k.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C8IPo60X.css">
|
|
8
9
|
</head>
|
|
9
10
|
<body>
|
|
10
11
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ossclip",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Local-first CLI video producer: cuts silence and fillers, word-timed captions, face-aware framing, and LLM-planned code-rendered graphics — transcription and rendering never leave your machine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"commander": "^12.1.0",
|
|
37
37
|
"tsx": "^4.19.0",
|
|
38
38
|
"zod": "^3.25.76",
|
|
39
|
-
"@ossclip/core": "0.1.
|
|
40
|
-
"@ossclip/renderer": "0.1.
|
|
41
|
-
"@ossclip/scenes": "0.1.
|
|
39
|
+
"@ossclip/core": "0.1.10",
|
|
40
|
+
"@ossclip/renderer": "0.1.10",
|
|
41
|
+
"@ossclip/scenes": "0.1.10"
|
|
42
42
|
},
|
|
43
43
|
"homepage": "https://github.com/AhsanAyaz/ossclip#readme",
|
|
44
44
|
"bugs": {
|
package/src/edit.ts
CHANGED
|
@@ -14,13 +14,23 @@ import { OverrideDocSchema, emptyOverrideDoc } from "@ossclip/core";
|
|
|
14
14
|
* `ossclip edit` works with no build step), else the monorepo sibling's
|
|
15
15
|
* `dist/` for a clone. Null when neither exists — callers own the loud
|
|
16
16
|
* error, and `ossclip doctor` reports it as a check.
|
|
17
|
+
*
|
|
18
|
+
* When BOTH exist, the newer index.html wins (§126): in a clone, a stale
|
|
19
|
+
* `editor-dist/` left behind by a local prepack silently shadowed a fresh
|
|
20
|
+
* `pnpm build` for a whole field session — the user reported a feature
|
|
21
|
+
* missing that had shipped, because the server kept serving the old page.
|
|
22
|
+
* In an npm install only `editor-dist/` exists, so the mtime tiebreak
|
|
23
|
+
* never changes behavior there.
|
|
17
24
|
*/
|
|
18
25
|
export function resolveEditorPageDir(): string | null {
|
|
19
26
|
const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
const candidates = [join(pkgRoot, "editor-dist"), join(pkgRoot, "../editor/dist")]
|
|
28
|
+
.filter((dir) => existsSync(join(dir, "index.html")))
|
|
29
|
+
.sort(
|
|
30
|
+
(a, b) =>
|
|
31
|
+
statSync(join(b, "index.html")).mtimeMs - statSync(join(a, "index.html")).mtimeMs,
|
|
32
|
+
);
|
|
33
|
+
return candidates[0] ?? null;
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
/**
|
|
@@ -12,6 +12,7 @@ export interface ProduceExtras {
|
|
|
12
12
|
speaker?: string;
|
|
13
13
|
whisperModel?: string;
|
|
14
14
|
blooperMarker?: string;
|
|
15
|
+
collapseRetakes?: boolean;
|
|
15
16
|
sourceIsEdited?: boolean;
|
|
16
17
|
llm?: "claude" | "claude-cli" | "gemini" | "mock";
|
|
17
18
|
}
|
|
@@ -50,6 +51,7 @@ export function produceArgv(a: ProduceAnswers): string[] {
|
|
|
50
51
|
if (e.speaker) argv.push("--speaker", e.speaker);
|
|
51
52
|
if (e.whisperModel) argv.push("--whisper-model", e.whisperModel);
|
|
52
53
|
if (e.blooperMarker) argv.push("--blooper-marker", e.blooperMarker);
|
|
54
|
+
if (e.collapseRetakes === true) argv.push("--collapse-retakes");
|
|
53
55
|
if (e.sourceIsEdited === true) argv.push("--source-is-edited");
|
|
54
56
|
if (e.llm) argv.push("--llm", e.llm);
|
|
55
57
|
|
|
@@ -4,9 +4,17 @@ import { produceArgv, type ProduceAnswers, type ProduceExtras } from "./produce-
|
|
|
4
4
|
import { assertInteractive, confirm, intro, multiselect, select, text, unwrap } from "./prompts";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* The produce wizard.
|
|
8
|
-
*
|
|
9
|
-
* flags
|
|
7
|
+
* The produce wizard. Thirty flags (plus the positional input path) sorted
|
|
8
|
+
* into three tiers: six prompts asked directly — the input path, plus five
|
|
9
|
+
* flags (--out, --cleanup, --aspect, --produce, --intent) — eight behind one
|
|
10
|
+
* "anything else?" multiselect, and the remaining seventeen stay flags-only:
|
|
11
|
+
* debug/internal surfaces, replay-only fields, or (final-review fix wave,
|
|
12
|
+
* Finding 1) --sort. A folder's clip order only means anything once the
|
|
13
|
+
* folder has been enumerated, and that enumeration happens inside
|
|
14
|
+
* `produce()` — after the wizard has already returned argv — so there is
|
|
15
|
+
* nothing for a prompt to offer a choice about beforehand. --sort stays
|
|
16
|
+
* typed-only, same tier as --clip-window below, not a tenth multiselect
|
|
17
|
+
* entry.
|
|
10
18
|
*
|
|
11
19
|
* --clip-window is deliberately NOT offered: --clip runs write it into
|
|
12
20
|
* command.json so the editor's Render replays the same window without an LLM
|
|
@@ -20,6 +28,11 @@ const EXTRAS = [
|
|
|
20
28
|
{ value: "speaker", label: "Say who is on camera", hint: "--speaker" },
|
|
21
29
|
{ value: "whisperModel", label: "Pick a transcription model", hint: "--whisper-model" },
|
|
22
30
|
{ value: "blooperMarker", label: "Cut flubbed takes on a spoken word", hint: "--blooper-marker" },
|
|
31
|
+
{
|
|
32
|
+
value: "collapseRetakes",
|
|
33
|
+
label: "Collapse repeated takes automatically",
|
|
34
|
+
hint: "--collapse-retakes",
|
|
35
|
+
},
|
|
23
36
|
{ value: "sourceIsEdited", label: "Source already has burned-in text", hint: "--source-is-edited" },
|
|
24
37
|
{ value: "llm", label: "Choose the LLM provider", hint: "--llm" },
|
|
25
38
|
] as const;
|
|
@@ -37,22 +50,36 @@ export function extrasFor(graphics: boolean): (typeof EXTRAS)[number][] {
|
|
|
37
50
|
return graphics ? [...EXTRAS] : EXTRAS.filter((e) => e.value !== "graphicsClip");
|
|
38
51
|
}
|
|
39
52
|
|
|
40
|
-
export async function produceWizard(cfg: { speaker?: string } = {}): Promise<string[]> {
|
|
53
|
+
export async function produceWizard(cfg: { speaker?: string; input?: string } = {}): Promise<string[]> {
|
|
41
54
|
assertInteractive("produce wizard");
|
|
42
55
|
intro("ossclip produce");
|
|
43
56
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
// Pre-supplied by bare `ossclip <path>` (0.1.9 first-contact, 2026-08-05):
|
|
58
|
+
// the user already TYPED the input on the command line, and the old flow
|
|
59
|
+
// dropped it and asked again — the re-ask is where "./Anyhropic c Compiler"
|
|
60
|
+
// became "./" (all of ~/Downloads). The router checks existence before the
|
|
61
|
+
// wizard ever opens, so a prefilled path skips the prompt entirely.
|
|
62
|
+
const input =
|
|
63
|
+
cfg.input ??
|
|
64
|
+
(unwrap(
|
|
65
|
+
await text({
|
|
66
|
+
// Finding 1 (final-review fix wave): `ossclip produce <folder>` shipped
|
|
67
|
+
// (folder-input-brief.md) but this prompt still rejected a directory —
|
|
68
|
+
// the wizard was the only way in that couldn't do what the CLI could.
|
|
69
|
+
// A folder is concatenated by name (codepoint order, like `ls`); --sort
|
|
70
|
+
// mtime reorders it but stays a typed flag, not a wizard question (see
|
|
71
|
+
// the file-level comment above for why).
|
|
72
|
+
message: "Video file, or a folder of clips to concatenate (by name; --sort mtime is a typed flag)",
|
|
73
|
+
placeholder: "./raw/take1.mp4",
|
|
74
|
+
validate: (v) => {
|
|
75
|
+
if (!v) return "a path is required";
|
|
76
|
+
if (!existsSync(v)) return `no such path: ${v}`;
|
|
77
|
+
const st = statSync(v);
|
|
78
|
+
if (!st.isFile() && !st.isDirectory()) return `${v} is neither a video file nor a folder`;
|
|
79
|
+
return undefined;
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
) as string);
|
|
56
83
|
|
|
57
84
|
const aspect = unwrap(
|
|
58
85
|
await select({
|
|
@@ -124,6 +151,7 @@ export async function produceWizard(cfg: { speaker?: string } = {}): Promise<str
|
|
|
124
151
|
);
|
|
125
152
|
}
|
|
126
153
|
if (chosen.includes("sourceFit")) extras.sourceFit = "contain";
|
|
154
|
+
if (chosen.includes("collapseRetakes")) extras.collapseRetakes = true;
|
|
127
155
|
if (chosen.includes("sourceIsEdited")) extras.sourceIsEdited = true;
|
|
128
156
|
if (chosen.includes("speaker")) {
|
|
129
157
|
extras.speaker = unwrap(
|