ossclip 0.1.22 → 0.1.24
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/package.json +4 -4
- package/src/edit.ts +1 -1
- package/src/produce.ts +1 -0
- package/src/ui/animation.ts +80 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ossclip",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
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/
|
|
40
|
-
"@ossclip/scenes": "0.1.
|
|
41
|
-
"@ossclip/
|
|
39
|
+
"@ossclip/core": "0.1.24",
|
|
40
|
+
"@ossclip/scenes": "0.1.24",
|
|
41
|
+
"@ossclip/renderer": "0.1.24"
|
|
42
42
|
},
|
|
43
43
|
"homepage": "https://github.com/AhsanAyaz/ossclip#readme",
|
|
44
44
|
"bugs": {
|
package/src/edit.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn, type ChildProcess } from "node:child_process";
|
|
2
|
-
import { createReadStream, existsSync, statSync } from "node:fs";
|
|
2
|
+
import { createReadStream, existsSync, readFileSync, statSync } from "node:fs";
|
|
3
3
|
import { mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
|
|
4
4
|
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
5
5
|
import { homedir } from "node:os";
|
package/src/produce.ts
CHANGED
|
@@ -1257,6 +1257,7 @@ export async function produce(inputArg: string, opts: ProduceOptions): Promise<P
|
|
|
1257
1257
|
graphicsLine = cached.graphics;
|
|
1258
1258
|
beatIssues = cached.issues ?? [];
|
|
1259
1259
|
}
|
|
1260
|
+
} else {
|
|
1260
1261
|
const aiAnim = isInteractive()
|
|
1261
1262
|
? new StageAnimator(
|
|
1262
1263
|
"AI SCENE SYNTHESIZER",
|
package/src/ui/animation.ts
CHANGED
|
@@ -59,7 +59,15 @@ export function formatTimecode(seconds: number, fps = 30): string {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
62
|
+
* Strip ANSI escape codes to calculate visual string width.
|
|
63
|
+
*/
|
|
64
|
+
export function stripAnsi(str: string): string {
|
|
65
|
+
// eslint-disable-next-line no-control-regex
|
|
66
|
+
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Unicode Audio Waves, Synth Oscilloscope & Braille spinners.
|
|
63
71
|
*/
|
|
64
72
|
const AUDIO_WAVE_CHARS = [" ", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
65
73
|
const SPINNERS = {
|
|
@@ -70,6 +78,28 @@ const SPINNERS = {
|
|
|
70
78
|
neural: ["◈◇◇◇", "◇◈◇◇", "◇◇◈◇", "◇◇◇◈", "◇◇◈◇", "◇◈◇◇"],
|
|
71
79
|
};
|
|
72
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Render an organic multi-harmonic analog synthesizer oscilloscope wave.
|
|
83
|
+
*/
|
|
84
|
+
export function renderSynthWave(frame: number, width = 16): string {
|
|
85
|
+
const BRAILLE_WAVE = ["⣀", "⠤", "⠒", "⠊", "⠉", "⠑", "⠒", "⠤", "⣀", "⡀", "⠄", "⠂", "⠁", "⠈", "⠐", "⠠", "⢀"];
|
|
86
|
+
let wave = "";
|
|
87
|
+
for (let i = 0; i < width; i++) {
|
|
88
|
+
const phase = frame * 0.25 + i * 0.45;
|
|
89
|
+
const harmonic = Math.sin(phase) * 0.65 + Math.sin(phase * 2.1 + frame * 0.1) * 0.35;
|
|
90
|
+
const normalized = Math.max(0, Math.min(1, (harmonic + 1) / 2));
|
|
91
|
+
const idx = Math.floor(normalized * (BRAILLE_WAVE.length - 1));
|
|
92
|
+
const char = BRAILLE_WAVE[idx];
|
|
93
|
+
|
|
94
|
+
// Phosphor glow gradient: Neon Amber to Electric Cyan
|
|
95
|
+
const r = Math.round(255 - normalized * 180);
|
|
96
|
+
const g = Math.round(160 + normalized * 70);
|
|
97
|
+
const b = Math.round(40 + normalized * 210);
|
|
98
|
+
wave += `${ansi.rgb(r, g, b)}${char}`;
|
|
99
|
+
}
|
|
100
|
+
return wave + ansi.reset;
|
|
101
|
+
}
|
|
102
|
+
|
|
73
103
|
/**
|
|
74
104
|
* Generate a dynamic gradient progress bar with customizable character resolution.
|
|
75
105
|
*/
|
|
@@ -163,48 +193,54 @@ export class StageAnimator {
|
|
|
163
193
|
const cols = Math.min(100, process.stdout.columns || 80);
|
|
164
194
|
|
|
165
195
|
let visual = "";
|
|
196
|
+
const maxVisualWidth = Math.max(40, (process.stdout.columns || 80) - 4);
|
|
197
|
+
const clamp = (s: string) => {
|
|
198
|
+
const plain = stripAnsi(s);
|
|
199
|
+
if (plain.length <= maxVisualWidth) return s;
|
|
200
|
+
return s.slice(0, maxVisualWidth - 3) + "...";
|
|
201
|
+
};
|
|
202
|
+
|
|
166
203
|
if (this.type === "ai") {
|
|
167
204
|
const spinner = SPINNERS.neural[this.frameIndex % SPINNERS.neural.length];
|
|
168
205
|
const dots = SPINNERS.dots[this.frameIndex % SPINNERS.dots.length];
|
|
169
206
|
visual =
|
|
170
207
|
`${ansi.bold}${ansi.brightMagenta}⚡ GEMINI 3.7 FLASH // NEURAL SCENE PLANNER${ansi.reset} ` +
|
|
171
208
|
`${ansi.cyan}[${spinner}]${ansi.reset} ${ansi.dim}(${elapsedSec}s)${ansi.reset}\n` +
|
|
172
|
-
` ${ansi.brightCyan}${dots}${ansi.reset} ${ansi.brightWhite}${this.subtitle}${ansi.reset}`;
|
|
209
|
+
` ${ansi.brightCyan}${dots}${ansi.reset} ${ansi.brightWhite}${clamp(this.subtitle)}${ansi.reset}`;
|
|
173
210
|
} else if (this.type === "whisper") {
|
|
174
211
|
const dots = SPINNERS.dots[this.frameIndex % SPINNERS.dots.length];
|
|
175
212
|
const eq = SPINNERS.equalizer[this.frameIndex % SPINNERS.equalizer.length];
|
|
176
213
|
visual =
|
|
177
214
|
`${ansi.bold}${ansi.brightCyan}🎙️ WHISPER ASR ENGINE // NEURAL PHONEME STREAM${ansi.reset} ` +
|
|
178
215
|
`${ansi.green}${eq}${ansi.reset} ${ansi.dim}(${elapsedSec}s)${ansi.reset}\n` +
|
|
179
|
-
` ${ansi.cyan}${dots}${ansi.reset} ${ansi.brightWhite}${this.subtitle}${ansi.reset}`;
|
|
216
|
+
` ${ansi.cyan}${dots}${ansi.reset} ${ansi.brightWhite}${clamp(this.subtitle)}${ansi.reset}`;
|
|
180
217
|
} else if (this.type === "audio") {
|
|
181
|
-
const
|
|
182
|
-
const wave = Array.from({ length: 14 }, (_, i) => {
|
|
183
|
-
const h = Math.abs(Math.sin((this.frameIndex + i * 2) * 0.35));
|
|
184
|
-
const idx = Math.floor(h * (AUDIO_WAVE_CHARS.length - 1));
|
|
185
|
-
return AUDIO_WAVE_CHARS[idx];
|
|
186
|
-
}).join("");
|
|
218
|
+
const synth = renderSynthWave(this.frameIndex, 12);
|
|
187
219
|
visual =
|
|
188
|
-
`${ansi.bold}${ansi.brightYellow}🔊
|
|
189
|
-
|
|
190
|
-
` ${ansi.brightYellow}▸${ansi.reset} ${ansi.brightWhite}${this.subtitle}${ansi.reset}
|
|
191
|
-
`${ansi.dim}VU L/R: -14.0 LUFS broadcast target${ansi.reset}`;
|
|
220
|
+
`${ansi.bold}${ansi.brightYellow}🔊 ANALOG SYNTH AUDIO OSCILLOSCOPE${ansi.reset} ` +
|
|
221
|
+
`[${synth}] ${ansi.dim}(${elapsedSec}s)${ansi.reset}\n` +
|
|
222
|
+
` ${ansi.brightYellow}▸${ansi.reset} ${ansi.brightWhite}${clamp(this.subtitle)}${ansi.reset}`;
|
|
192
223
|
} else if (this.type === "master") {
|
|
193
224
|
const dots = SPINNERS.dots[this.frameIndex % SPINNERS.dots.length];
|
|
194
225
|
visual =
|
|
195
226
|
`${ansi.bold}${ansi.brightGreen}🎛️ MASTERING AUDIO // EBU R128 DUAL-PASS LOUDNORM${ansi.reset} ` +
|
|
196
227
|
`${ansi.dim}(${elapsedSec}s)${ansi.reset}\n` +
|
|
197
|
-
` ${ansi.brightGreen}${dots}${ansi.reset} ${ansi.brightWhite}${this.subtitle}${ansi.reset}`;
|
|
228
|
+
` ${ansi.brightGreen}${dots}${ansi.reset} ${ansi.brightWhite}${clamp(this.subtitle)}${ansi.reset}`;
|
|
198
229
|
} else {
|
|
199
230
|
const film = SPINNERS.filmReel[this.frameIndex % SPINNERS.filmReel.length];
|
|
200
231
|
visual =
|
|
201
232
|
`${ansi.bold}${ansi.brightCyan}🎞️ ${this.title}${ansi.reset} ` +
|
|
202
233
|
`${ansi.brightYellow}${film}${ansi.reset} ${ansi.dim}(${elapsedSec}s)${ansi.reset}\n` +
|
|
203
|
-
` ${ansi.brightWhite}${this.subtitle}${ansi.reset}`;
|
|
234
|
+
` ${ansi.brightWhite}${clamp(this.subtitle)}${ansi.reset}`;
|
|
204
235
|
}
|
|
205
236
|
|
|
206
237
|
this.clearRenderedLines();
|
|
207
|
-
const
|
|
238
|
+
const rawLines = visual.split("\n");
|
|
239
|
+
const lines = rawLines.map((l) => {
|
|
240
|
+
const vis = stripAnsi(l);
|
|
241
|
+
if (vis.length <= maxVisualWidth) return l;
|
|
242
|
+
return l.slice(0, maxVisualWidth - 3) + "..." + ansi.reset;
|
|
243
|
+
});
|
|
208
244
|
for (const l of lines) {
|
|
209
245
|
process.stdout.write(`${l}\n`);
|
|
210
246
|
}
|
|
@@ -213,7 +249,7 @@ export class StageAnimator {
|
|
|
213
249
|
|
|
214
250
|
private clearRenderedLines(): void {
|
|
215
251
|
if (this.linesRendered > 0) {
|
|
216
|
-
process.stdout.write(ansi.up(this.linesRendered));
|
|
252
|
+
process.stdout.write(ansi.up(this.linesRendered) + "\r");
|
|
217
253
|
for (let i = 0; i < this.linesRendered; i++) {
|
|
218
254
|
process.stdout.write(ansi.clearLine);
|
|
219
255
|
if (i < this.linesRendered - 1) process.stdout.write("\n");
|
|
@@ -279,7 +315,6 @@ export class RenderTimelineHUD {
|
|
|
279
315
|
public setProgress(progress: number): void {
|
|
280
316
|
this.currentProgress = Math.max(0, Math.min(1, progress));
|
|
281
317
|
this.lastUpdateMs = Date.now();
|
|
282
|
-
if (this.isTty) this.render();
|
|
283
318
|
}
|
|
284
319
|
|
|
285
320
|
private tick(): void {
|
|
@@ -296,18 +331,19 @@ export class RenderTimelineHUD {
|
|
|
296
331
|
const renderedFrames = Math.round(progress * totalFrames);
|
|
297
332
|
const renderFps = (renderedFrames / elapsedSec).toFixed(1);
|
|
298
333
|
const realtimeMultiplier = (renderedFrames / (elapsedSec * this.fps)).toFixed(2);
|
|
299
|
-
const etaSec = progress > 0.02 ? Math.max(0, (elapsedSec / progress) * (1 - progress)).toFixed(1) : "
|
|
334
|
+
const etaSec = progress > 0.02 ? Math.max(0, (elapsedSec / progress) * (1 - progress)).toFixed(1) : "...";
|
|
300
335
|
|
|
301
336
|
const currentTimecode = formatTimecode(progress * this.totalDurationSec, this.fps);
|
|
302
337
|
const totalTimecode = formatTimecode(this.totalDurationSec, this.fps);
|
|
303
338
|
|
|
304
|
-
|
|
305
|
-
const
|
|
339
|
+
// Compute exact bounded terminal box dimensions (guaranteed never to wrap)
|
|
340
|
+
const termCols = Math.min(74, Math.max(50, (process.stdout.columns || 80) - 2));
|
|
341
|
+
const innerWidth = termCols - 4; // between ║ ... ║
|
|
342
|
+
const barWidth = Math.max(12, Math.min(22, innerWidth - 28));
|
|
306
343
|
|
|
307
344
|
// Dynamic track representation
|
|
308
345
|
const reel = SPINNERS.filmReel[this.frameCount % SPINNERS.filmReel.length];
|
|
309
|
-
const
|
|
310
|
-
const wave2 = SPINNERS.equalizer[(this.frameCount + 3) % SPINNERS.equalizer.length];
|
|
346
|
+
const synthWaveA1 = renderSynthWave(this.frameCount, 12);
|
|
311
347
|
|
|
312
348
|
// Identify active scene
|
|
313
349
|
let activeSceneLabel = "Main Composition";
|
|
@@ -322,17 +358,27 @@ export class RenderTimelineHUD {
|
|
|
322
358
|
const pctString = (progress * 100).toFixed(1).padStart(5, " ");
|
|
323
359
|
const bar = renderProgressBar(progress, barWidth, { gradient: "cyberpunk" });
|
|
324
360
|
|
|
361
|
+
const padLine = (content: string) => {
|
|
362
|
+
const vis = stripAnsi(content).length;
|
|
363
|
+
const pad = Math.max(0, innerWidth - vis);
|
|
364
|
+
return `${ansi.bold}║${ansi.reset} ${content}${" ".repeat(pad)} ${ansi.bold}║${ansi.reset}`;
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const headerContent = `${ansi.yellow}${reel}${ansi.reset} ${ansi.bold}${ansi.brightCyan}${currentTimecode}${ansi.reset} / ${ansi.dim}${totalTimecode}${ansi.reset} │ ${ansi.brightMagenta}${this.aspect}${ansi.reset} │ ${ansi.green}${realtimeMultiplier}x Speed${ansi.reset}`;
|
|
368
|
+
const trackV1Content = `${ansi.brightWhite}V1:${ansi.reset} [${bar}] ${ansi.bold}${ansi.brightYellow}${pctString}%${ansi.reset} │ Frame: ${renderedFrames}/${totalFrames}`;
|
|
369
|
+
const trackA1Content = `${ansi.brightWhite}A1:${ansi.reset} [${synthWaveA1}] │ ${renderFps}fps │ ETA:${ansi.brightYellow}${etaSec}s${ansi.reset}`;
|
|
370
|
+
const sceneContent = `${ansi.dim}Scene:${ansi.reset} ${ansi.brightMagenta}${activeSceneLabel.slice(0, innerWidth - 8)}${ansi.reset}`;
|
|
371
|
+
|
|
372
|
+
const topBorder = `${ansi.bold}${ansi.brightCyan}╔══ 🎞️ REMOTION RENDER ENGINE ${"═".repeat(Math.max(2, innerWidth - 27))}╗${ansi.reset}`;
|
|
373
|
+
const bottomBorder = `${ansi.bold}${ansi.brightCyan}╚${"═".repeat(innerWidth + 2)}╝${ansi.reset}`;
|
|
374
|
+
|
|
325
375
|
const lines = [
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
`${ansi.dim}│ Speed:${ansi.reset} ${ansi.brightGreen}${renderFps} fps${ansi.reset} ` +
|
|
333
|
-
`${ansi.dim}│ ETA:${ansi.reset} ${ansi.brightYellow}${etaSec}s${ansi.reset}`,
|
|
334
|
-
`${ansi.bold}║${ansi.reset} ${ansi.dim}▶ Active:${ansi.reset} ${ansi.brightMagenta}${activeSceneLabel}${ansi.reset}`,
|
|
335
|
-
`${ansi.bold}${ansi.brightCyan}╚════════════════════════════════════════════════════════════════════════╝${ansi.reset}`,
|
|
376
|
+
topBorder,
|
|
377
|
+
padLine(headerContent),
|
|
378
|
+
padLine(trackV1Content),
|
|
379
|
+
padLine(trackA1Content),
|
|
380
|
+
padLine(sceneContent),
|
|
381
|
+
bottomBorder,
|
|
336
382
|
];
|
|
337
383
|
|
|
338
384
|
this.clearRenderedLines();
|
|
@@ -344,7 +390,7 @@ export class RenderTimelineHUD {
|
|
|
344
390
|
|
|
345
391
|
private clearRenderedLines(): void {
|
|
346
392
|
if (this.linesRendered > 0) {
|
|
347
|
-
process.stdout.write(ansi.up(this.linesRendered));
|
|
393
|
+
process.stdout.write(ansi.up(this.linesRendered) + "\r");
|
|
348
394
|
for (let i = 0; i < this.linesRendered; i++) {
|
|
349
395
|
process.stdout.write(ansi.clearLine);
|
|
350
396
|
if (i < this.linesRendered - 1) process.stdout.write("\n");
|