voxflow 1.18.1 → 1.18.3

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.
@@ -17,37 +17,27 @@ let _resolvedFfmpegPath = null;
17
17
  /**
18
18
  * Resolve the bundled `ffmpeg-static` binary path.
19
19
  *
20
- * Naively, `require('ffmpeg-static')` returns the path of its sibling `ffmpeg`
21
- * binary. But ffmpeg-static's index.js uses `path.join(__dirname, ...)` to
22
- * compute that path and when we ncc-bundle this CLI into a single
23
- * `dist/index.js`, `__dirname` inside the inlined ffmpeg-static module
24
- * collapses to the *bundle*'s directory (`<install>/voxflow/dist/`), not its
25
- * real `<install>/voxflow/node_modules/ffmpeg-static/` location. The returned
26
- * string then points at a file that doesn't exist.
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
27
  *
28
- * Recovery: `require.resolve('ffmpeg-static/package.json')` honors Node's
29
- * runtime module resolution (not the `__dirname` baked in at bundle time), so
30
- * it finds the real package directory. The binary lives next to that
31
- * package.json. This works in both source-mode (npm test) and ncc-bundled
32
- * mode (the published CLI).
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
33
  *
34
- * Returns: a path string (may or may not exist; caller should fs.existsSync).
35
- * Or null when ffmpeg-static is not installed at all.
34
+ * Returns: a binary path that exists, or null when ffmpeg-static is not
35
+ * installed at all.
36
36
  */
37
37
  function resolveFfmpegStaticBin() {
38
- // 1. Naive path — works in source mode and any non-ncc context.
39
- let direct = null;
40
- try { direct = require('ffmpeg-static'); } catch { /* not installed */ }
41
- if (direct && fs.existsSync(direct)) return direct;
42
-
43
- // 2. ncc-safe recovery via package.json resolution.
44
- try {
45
- const pkgJson = require.resolve('ffmpeg-static/package.json');
46
- const exe = process.platform === 'win32' ? 'ffmpeg.exe' : 'ffmpeg';
47
- const recovered = path.join(path.dirname(pkgJson), exe);
48
- if (fs.existsSync(recovered)) return recovered;
49
- } catch { /* not installed */ }
50
-
38
+ let p = null;
39
+ try { p = require('ffmpeg-static'); } catch { /* not installed */ }
40
+ if (p && fs.existsSync(p)) return p;
51
41
  return null;
52
42
  }
53
43
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "voxflow",
3
- "version": "1.18.1",
3
+ "version": "1.18.3",
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.1",
3
+ "version": "1.18.3",
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",
@@ -460,6 +460,28 @@ Use `references/design-languages.md` to define the card set's visual grammar ind
460
460
  - `--input <path>` / `-o, --output <path>` — operate on / write to a different mp4 (otherwise: replace in place).
461
461
  - **Quota**: 0 — pure FFmpeg pipeline.
462
462
 
463
+ **Card-design constraint when planning to burn subtitles** (applies during Workflow steps 4–6, *before* writing the HTML):
464
+
465
+ The default subtitle style sits at ASS `MarginV=14` with `BorderStyle=3` (opaque box) — roughly the bottom **12–18% of the canvas height** on a 1080×1920 card. Anything the card itself places in that band will be overlapped by the captions. Verified during 1.18 dogfooding: page-number footers, citation chrome, italic closing pull-quotes, and "endcap" lines at the bottom edge all got partially covered.
466
+
467
+ Rules of thumb:
468
+ - **Reserve the bottom 15% of every card as a captioning-safe zone**: no body copy, no citation footer, no closing pull-quote there. Move card-level chrome (page number, citation, kicker) to the *top* band, or shrink the live area so it ends at ~85% of canvas height.
469
+ - The editorial chrome (top kicker / series indicator) is always safe — captions live at the bottom only.
470
+ - **Visual anchors that span full height** (oversized type, edge-cropped illustrations, photo-led covers) are fine: subtitles sit on top of the artwork, which is the intended layering. Only legibility-critical text needs to clear the band.
471
+ - When a card legitimately needs a bottom-edge element (e.g. a stamp, big page number, full-bleed image), skip captions for that card by leaving its `narration` empty in `deck.json` — `card subtitle` will silently absorb the empty card into adjacent windows.
472
+ - If you forget and discover the collision in QA: run `card subtitle --dry-run` to inspect `subs.srt` first, then either redesign the offending card's bottom band or pass `--style 'MarginV=24,...'` to push captions higher (cards with a wider safe zone trade some readability — captions get smaller relative to the canvas).
473
+
474
+ **Card-design constraint when planning to burn subtitles** (applies during Workflow steps 4–6, *before* writing the HTML):
475
+
476
+ The default subtitle style sits at ASS `MarginV=14` with `BorderStyle=3` (opaque box) — roughly the bottom **12–18% of the canvas height** on a 1080×1920 card. Anything the card itself places in that band will be overlapped by the captions. Verified during 1.18 dogfooding: page-number footers, citation chrome, italic closing pull-quotes, and "endcap" lines at the bottom edge all got partially covered.
477
+
478
+ Rules of thumb:
479
+ - **Reserve the bottom 15% of every card as a captioning-safe zone**: no body copy, no citation footer, no closing pull-quote there. Move card-level chrome (page number, citation, kicker) to the *top* band, or shrink the live area so it ends at ~85% of canvas height.
480
+ - The editorial chrome (top kicker / series indicator) is always safe — captions live at the bottom only.
481
+ - **Visual anchors that span full height** (oversized type, edge-cropped illustrations, photo-led covers) are fine: subtitles sit on top of the artwork, which is the intended layering. Only legibility-critical text needs to clear the band.
482
+ - When a card legitimately needs a bottom-edge element (e.g. a stamp, big page number, full-bleed image), skip captions for that card by leaving its `narration` empty in `deck.json` — `card subtitle` will silently absorb the empty card into adjacent windows.
483
+ - If you forget and discover the collision in QA: run `card subtitle --dry-run` to inspect `subs.srt` first, then either redesign the offending card's bottom band or pass `--style 'MarginV=24,...'` to push captions higher (cards with a wider safe zone trade some readability — captions get smaller relative to the canvas).
484
+
463
485
  Note: `card subtitle` also has a `silencedetect` fallback for old mp4s that pre-date the `timeline.json` emission (introduced in CLI 1.18). Prefer the timeline path; it is exact rather than heuristic.
464
486
 
465
487
  ## Asset and Source Discipline