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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ossclip",
3
- "version": "0.1.22",
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/renderer": "0.1.22",
40
- "@ossclip/scenes": "0.1.22",
41
- "@ossclip/core": "0.1.22"
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",
@@ -59,7 +59,15 @@ export function formatTimecode(seconds: number, fps = 30): string {
59
59
  }
60
60
 
61
61
  /**
62
- * Unicode Audio Waves & Braille spinners.
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 eq = SPINNERS.equalizer[this.frameIndex % SPINNERS.equalizer.length];
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}🔊 AUDIO ANALYSIS & LEVELING${ansi.reset} ` +
189
- `${ansi.yellow}[${wave}]${ansi.reset} ${ansi.dim}(${elapsedSec}s)${ansi.reset}\n` +
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} ` +
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) : "calculating...";
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
- const termWidth = Math.max(60, Math.min(100, process.stdout.columns || 80));
305
- const barWidth = Math.max(20, Math.min(36, termWidth - 46));
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 wave1 = SPINNERS.equalizer[this.frameCount % SPINNERS.equalizer.length];
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
- `${ansi.bold}${ansi.brightCyan}╔══ 🎞️ REMOTION MULTI-THREADED RENDER ENGINE ═══════════════════════════╗${ansi.reset}`,
327
- `${ansi.bold}║${ansi.reset} ${ansi.yellow}${reel}${ansi.reset} ${ansi.brightWhite}TIMECODE:${ansi.reset} ${ansi.bold}${ansi.brightCyan}${currentTimecode}${ansi.reset} / ${ansi.dim}${totalTimecode}${ansi.reset} ` +
328
- `│ ${ansi.brightMagenta}${this.aspect}${ansi.reset} @ ${this.fps}fps │ ${ansi.green}${realtimeMultiplier}x Speed${ansi.reset}`,
329
- `${ansi.bold}║${ansi.reset} ${ansi.brightWhite}TRACK V1:${ansi.reset} [${bar}] ${ansi.bold}${ansi.brightYellow}${pctString}%${ansi.reset}`,
330
- `${ansi.bold}║${ansi.reset} ${ansi.brightWhite}TRACK A1:${ansi.reset} ${ansi.cyan}${wave1}${wave2}${wave1}${wave2}${wave1}${ansi.reset} ` +
331
- `${ansi.dim}│ Frame:${ansi.reset} ${ansi.brightWhite}${renderedFrames}/${totalFrames}${ansi.reset} ` +
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");