voxflow 1.18.0 → 1.18.2

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.
@@ -29,7 +29,7 @@ const path = require('path');
29
29
  const { execFile } = require('child_process');
30
30
 
31
31
  const { parseFlag } = require('../core/args');
32
- const { runCommand, checkFfmpeg } = require('../core/ffmpeg');
32
+ const { runCommand, checkFfmpeg, resolveFfmpegStaticBin } = require('../core/ffmpeg');
33
33
 
34
34
  // ── ffmpeg binary capability probe ────────────────────────────────────────────
35
35
 
@@ -62,10 +62,14 @@ function probeSubtitlesCapableFfmpeg() {
62
62
 
63
63
  // 1. System ffmpeg via PATH (or whatever core/ffmpeg.js already resolved)
64
64
  tryBinary('ffmpeg', 'system', () => {
65
- // 2. Bundled ffmpeg-static
66
- let staticPath = null;
67
- try { staticPath = require('ffmpeg-static'); } catch { /* not installed */ }
68
- if (!staticPath || !fs.existsSync(staticPath)) {
65
+ // 2. Bundled ffmpeg-static.
66
+ // NOTE: cannot use `require('ffmpeg-static')` directly here — when this
67
+ // module is ncc-bundled into dist/index.js, ffmpeg-static's own
68
+ // `path.join(__dirname, ...)` collapses to the bundle's directory and
69
+ // returns a non-existent path. resolveFfmpegStaticBin() handles the
70
+ // ncc-safe recovery via require.resolve('ffmpeg-static/package.json').
71
+ const staticPath = resolveFfmpegStaticBin();
72
+ if (!staticPath) {
69
73
  return reject(new Error(
70
74
  'No ffmpeg with libass / `subtitles` filter found.\n' +
71
75
  ' System ffmpeg lacks libass (e.g. Homebrew default formula).\n' +
@@ -14,6 +14,33 @@ const fs = require('fs');
14
14
 
15
15
  let _resolvedFfmpegPath = null;
16
16
 
17
+ /**
18
+ * Resolve the bundled `ffmpeg-static` binary path.
19
+ *
20
+ * ffmpeg-static is marked external in our ncc build (see package.json
21
+ * `build` script: `-e ffmpeg-static`), so at runtime `require('ffmpeg-static')`
22
+ * goes through Node's actual module resolution algorithm rather than the
23
+ * bundled-module map. That matters because ffmpeg-static's index.js computes
24
+ * its binary path with `path.join(__dirname, ...)` — and only when the module
25
+ * is loaded by the real Node loader does `__dirname` resolve to the actual
26
+ * package directory (e.g. `<install>/voxflow/node_modules/ffmpeg-static/`).
27
+ *
28
+ * If we let ncc inline ffmpeg-static, `__dirname` collapses to the bundle's
29
+ * own directory and the returned path points at a file that does not exist.
30
+ * Worse, `require.resolve(...)` inside an ncc bundle is rewritten to return
31
+ * a numeric module ID, so it cannot be used to recover the real package
32
+ * directory. The only robust fix is keeping ffmpeg-static external.
33
+ *
34
+ * Returns: a binary path that exists, or null when ffmpeg-static is not
35
+ * installed at all.
36
+ */
37
+ function resolveFfmpegStaticBin() {
38
+ let p = null;
39
+ try { p = require('ffmpeg-static'); } catch { /* not installed */ }
40
+ if (p && fs.existsSync(p)) return p;
41
+ return null;
42
+ }
43
+
17
44
  /**
18
45
  * Resolve the ffmpeg binary path. Priority:
19
46
  * 1. System `ffmpeg` on PATH
@@ -30,13 +57,11 @@ function resolveFfmpegBin() {
30
57
  } catch { /* not on PATH */ }
31
58
 
32
59
  // Fall back to ffmpeg-static
33
- try {
34
- const staticPath = require('ffmpeg-static');
35
- if (staticPath && fs.existsSync(staticPath)) {
36
- _resolvedFfmpegPath = staticPath;
37
- return _resolvedFfmpegPath;
38
- }
39
- } catch { /* not installed */ }
60
+ const staticPath = resolveFfmpegStaticBin();
61
+ if (staticPath) {
62
+ _resolvedFfmpegPath = staticPath;
63
+ return _resolvedFfmpegPath;
64
+ }
40
65
 
41
66
  // Nothing found — return 'ffmpeg' and let it fail with a helpful error
42
67
  _resolvedFfmpegPath = 'ffmpeg';
@@ -536,4 +561,6 @@ module.exports = {
536
561
  concatVideos,
537
562
  normalizeVideo,
538
563
  detectCjkFont,
564
+ // ffmpeg-static path resolution (used by card-subtitle's libass fallback)
565
+ resolveFfmpegStaticBin,
539
566
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "voxflow",
3
- "version": "1.18.0",
3
+ "version": "1.18.2",
4
4
  "description": "AI audio content creation CLI — stories, podcasts, narration, dubbing, transcription, translation, and video translation with TTS",
5
5
  "bin": {
6
6
  "voxflow": "./dist/index.js"
@@ -44,7 +44,7 @@
44
44
  "voxflow"
45
45
  ],
46
46
  "scripts": {
47
- "build": "ncc build bin/voxflow.js -o dist --minify -e @remotion/renderer && rm -rf dist/cli && node scripts/check-no-asset-rewrite.js",
47
+ "build": "ncc build bin/voxflow.js -o dist --minify -e @remotion/renderer -e ffmpeg-static && rm -rf dist/cli && node scripts/check-no-asset-rewrite.js",
48
48
  "build:bundle": "node scripts/build-bundle.mjs",
49
49
  "prepublishOnly": "npm run build:bundle && npm run build",
50
50
  "lint": "eslint lib/ bin/ tests/",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "voxflow",
3
- "version": "1.18.0",
3
+ "version": "1.18.2",
4
4
  "description": "AI voice CLI bundled as 6 skills (hub, podcast, transcribe, video, slice, card). Synthesize speech in 200+ voices across 40+ languages, generate multi-speaker AI podcasts, transcribe audio/video with word-level timestamps, dub videos from SRT subtitles, run end-to-end video translation, turn long articles into vertical card video reels via Remotion, and turn text into polished shareable card images or narrated card videos. Backed by a hosted TTS/ASR/LLM/render service with per-user quota (free tier 10K/mo).",
5
5
  "author": {
6
6
  "name": "VoxFlow",