ossclip 0.1.22 → 0.1.23
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/produce.ts +1 -0
- package/src/ui/animation.ts +75 -33
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ossclip",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
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/
|
|
41
|
-
"@ossclip/
|
|
39
|
+
"@ossclip/core": "0.1.23",
|
|
40
|
+
"@ossclip/renderer": "0.1.23",
|
|
41
|
+
"@ossclip/scenes": "0.1.23"
|
|
42
42
|
},
|
|
43
43
|
"homepage": "https://github.com/AhsanAyaz/ossclip#readme",
|
|
44
44
|
"bugs": {
|
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,44 +193,46 @@ 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, 14);
|
|
187
219
|
visual =
|
|
188
|
-
`${ansi.bold}${ansi.brightYellow}🔊
|
|
189
|
-
|
|
190
|
-
` ${ansi.brightYellow}▸${ansi.reset} ${ansi.brightWhite}${this.subtitle}${ansi.reset} ` +
|
|
191
|
-
`${ansi.dim}
|
|
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} ` +
|
|
223
|
+
`${ansi.dim}48kHz 24-bit PCM │ EBU R128 (-14.0 LUFS)${ansi.reset}`;
|
|
192
224
|
} else if (this.type === "master") {
|
|
193
225
|
const dots = SPINNERS.dots[this.frameIndex % SPINNERS.dots.length];
|
|
194
226
|
visual =
|
|
195
227
|
`${ansi.bold}${ansi.brightGreen}🎛️ MASTERING AUDIO // EBU R128 DUAL-PASS LOUDNORM${ansi.reset} ` +
|
|
196
228
|
`${ansi.dim}(${elapsedSec}s)${ansi.reset}\n` +
|
|
197
|
-
` ${ansi.brightGreen}${dots}${ansi.reset} ${ansi.brightWhite}${this.subtitle}${ansi.reset}`;
|
|
229
|
+
` ${ansi.brightGreen}${dots}${ansi.reset} ${ansi.brightWhite}${clamp(this.subtitle)}${ansi.reset}`;
|
|
198
230
|
} else {
|
|
199
231
|
const film = SPINNERS.filmReel[this.frameIndex % SPINNERS.filmReel.length];
|
|
200
232
|
visual =
|
|
201
233
|
`${ansi.bold}${ansi.brightCyan}🎞️ ${this.title}${ansi.reset} ` +
|
|
202
234
|
`${ansi.brightYellow}${film}${ansi.reset} ${ansi.dim}(${elapsedSec}s)${ansi.reset}\n` +
|
|
203
|
-
` ${ansi.brightWhite}${this.subtitle}${ansi.reset}`;
|
|
235
|
+
` ${ansi.brightWhite}${clamp(this.subtitle)}${ansi.reset}`;
|
|
204
236
|
}
|
|
205
237
|
|
|
206
238
|
this.clearRenderedLines();
|
|
@@ -213,7 +245,7 @@ export class StageAnimator {
|
|
|
213
245
|
|
|
214
246
|
private clearRenderedLines(): void {
|
|
215
247
|
if (this.linesRendered > 0) {
|
|
216
|
-
process.stdout.write(ansi.up(this.linesRendered));
|
|
248
|
+
process.stdout.write(ansi.up(this.linesRendered) + "\r");
|
|
217
249
|
for (let i = 0; i < this.linesRendered; i++) {
|
|
218
250
|
process.stdout.write(ansi.clearLine);
|
|
219
251
|
if (i < this.linesRendered - 1) process.stdout.write("\n");
|
|
@@ -279,7 +311,6 @@ export class RenderTimelineHUD {
|
|
|
279
311
|
public setProgress(progress: number): void {
|
|
280
312
|
this.currentProgress = Math.max(0, Math.min(1, progress));
|
|
281
313
|
this.lastUpdateMs = Date.now();
|
|
282
|
-
if (this.isTty) this.render();
|
|
283
314
|
}
|
|
284
315
|
|
|
285
316
|
private tick(): void {
|
|
@@ -296,18 +327,19 @@ export class RenderTimelineHUD {
|
|
|
296
327
|
const renderedFrames = Math.round(progress * totalFrames);
|
|
297
328
|
const renderFps = (renderedFrames / elapsedSec).toFixed(1);
|
|
298
329
|
const realtimeMultiplier = (renderedFrames / (elapsedSec * this.fps)).toFixed(2);
|
|
299
|
-
const etaSec = progress > 0.02 ? Math.max(0, (elapsedSec / progress) * (1 - progress)).toFixed(1) : "
|
|
330
|
+
const etaSec = progress > 0.02 ? Math.max(0, (elapsedSec / progress) * (1 - progress)).toFixed(1) : "...";
|
|
300
331
|
|
|
301
332
|
const currentTimecode = formatTimecode(progress * this.totalDurationSec, this.fps);
|
|
302
333
|
const totalTimecode = formatTimecode(this.totalDurationSec, this.fps);
|
|
303
334
|
|
|
304
|
-
|
|
305
|
-
const
|
|
335
|
+
// Compute exact bounded terminal box dimensions (guaranteed never to wrap)
|
|
336
|
+
const termCols = Math.min(74, Math.max(50, (process.stdout.columns || 80) - 2));
|
|
337
|
+
const innerWidth = termCols - 4; // between ║ ... ║
|
|
338
|
+
const barWidth = Math.max(12, Math.min(22, innerWidth - 28));
|
|
306
339
|
|
|
307
340
|
// Dynamic track representation
|
|
308
341
|
const reel = SPINNERS.filmReel[this.frameCount % SPINNERS.filmReel.length];
|
|
309
|
-
const
|
|
310
|
-
const wave2 = SPINNERS.equalizer[(this.frameCount + 3) % SPINNERS.equalizer.length];
|
|
342
|
+
const synthWaveA1 = renderSynthWave(this.frameCount, 12);
|
|
311
343
|
|
|
312
344
|
// Identify active scene
|
|
313
345
|
let activeSceneLabel = "Main Composition";
|
|
@@ -322,17 +354,27 @@ export class RenderTimelineHUD {
|
|
|
322
354
|
const pctString = (progress * 100).toFixed(1).padStart(5, " ");
|
|
323
355
|
const bar = renderProgressBar(progress, barWidth, { gradient: "cyberpunk" });
|
|
324
356
|
|
|
357
|
+
const padLine = (content: string) => {
|
|
358
|
+
const vis = stripAnsi(content).length;
|
|
359
|
+
const pad = Math.max(0, innerWidth - vis);
|
|
360
|
+
return `${ansi.bold}║${ansi.reset} ${content}${" ".repeat(pad)} ${ansi.bold}║${ansi.reset}`;
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
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}`;
|
|
364
|
+
const trackV1Content = `${ansi.brightWhite}V1:${ansi.reset} [${bar}] ${ansi.bold}${ansi.brightYellow}${pctString}%${ansi.reset} │ Frame: ${renderedFrames}/${totalFrames}`;
|
|
365
|
+
const trackA1Content = `${ansi.brightWhite}A1:${ansi.reset} [${synthWaveA1}] │ ${renderFps}fps │ ETA:${ansi.brightYellow}${etaSec}s${ansi.reset}`;
|
|
366
|
+
const sceneContent = `${ansi.dim}Scene:${ansi.reset} ${ansi.brightMagenta}${activeSceneLabel.slice(0, innerWidth - 8)}${ansi.reset}`;
|
|
367
|
+
|
|
368
|
+
const topBorder = `${ansi.bold}${ansi.brightCyan}╔══ 🎞️ REMOTION RENDER ENGINE ${"═".repeat(Math.max(2, innerWidth - 27))}╗${ansi.reset}`;
|
|
369
|
+
const bottomBorder = `${ansi.bold}${ansi.brightCyan}╚${"═".repeat(innerWidth + 2)}╝${ansi.reset}`;
|
|
370
|
+
|
|
325
371
|
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}`,
|
|
372
|
+
topBorder,
|
|
373
|
+
padLine(headerContent),
|
|
374
|
+
padLine(trackV1Content),
|
|
375
|
+
padLine(trackA1Content),
|
|
376
|
+
padLine(sceneContent),
|
|
377
|
+
bottomBorder,
|
|
336
378
|
];
|
|
337
379
|
|
|
338
380
|
this.clearRenderedLines();
|
|
@@ -344,7 +386,7 @@ export class RenderTimelineHUD {
|
|
|
344
386
|
|
|
345
387
|
private clearRenderedLines(): void {
|
|
346
388
|
if (this.linesRendered > 0) {
|
|
347
|
-
process.stdout.write(ansi.up(this.linesRendered));
|
|
389
|
+
process.stdout.write(ansi.up(this.linesRendered) + "\r");
|
|
348
390
|
for (let i = 0; i < this.linesRendered; i++) {
|
|
349
391
|
process.stdout.write(ansi.clearLine);
|
|
350
392
|
if (i < this.linesRendered - 1) process.stdout.write("\n");
|