recordable 0.1.0

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.
Files changed (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +303 -0
  3. package/dist/actions.d.ts +140 -0
  4. package/dist/actions.d.ts.map +1 -0
  5. package/dist/actions.js +184 -0
  6. package/dist/actions.js.map +1 -0
  7. package/dist/audio/track.d.ts +45 -0
  8. package/dist/audio/track.d.ts.map +1 -0
  9. package/dist/audio/track.js +61 -0
  10. package/dist/audio/track.js.map +1 -0
  11. package/dist/browser/cursor.d.ts +33 -0
  12. package/dist/browser/cursor.d.ts.map +1 -0
  13. package/dist/browser/cursor.js +118 -0
  14. package/dist/browser/cursor.js.map +1 -0
  15. package/dist/browser/dom.d.ts +31 -0
  16. package/dist/browser/dom.d.ts.map +1 -0
  17. package/dist/browser/dom.js +134 -0
  18. package/dist/browser/dom.js.map +1 -0
  19. package/dist/browser/play-button.d.ts +11 -0
  20. package/dist/browser/play-button.d.ts.map +1 -0
  21. package/dist/browser/play-button.js +87 -0
  22. package/dist/browser/play-button.js.map +1 -0
  23. package/dist/browser/runtime.d.ts +66 -0
  24. package/dist/browser/runtime.d.ts.map +1 -0
  25. package/dist/browser/runtime.js +271 -0
  26. package/dist/browser/runtime.js.map +1 -0
  27. package/dist/cli.d.ts +3 -0
  28. package/dist/cli.d.ts.map +1 -0
  29. package/dist/cli.js +131 -0
  30. package/dist/cli.js.map +1 -0
  31. package/dist/compose/mix.d.ts +13 -0
  32. package/dist/compose/mix.d.ts.map +1 -0
  33. package/dist/compose/mix.js +50 -0
  34. package/dist/compose/mix.js.map +1 -0
  35. package/dist/compose/recordable.d.ts +149 -0
  36. package/dist/compose/recordable.d.ts.map +1 -0
  37. package/dist/compose/recordable.js +337 -0
  38. package/dist/compose/recordable.js.map +1 -0
  39. package/dist/compose/session.d.ts +38 -0
  40. package/dist/compose/session.d.ts.map +1 -0
  41. package/dist/compose/session.js +122 -0
  42. package/dist/compose/session.js.map +1 -0
  43. package/dist/config.d.ts +93 -0
  44. package/dist/config.d.ts.map +1 -0
  45. package/dist/config.js +64 -0
  46. package/dist/config.js.map +1 -0
  47. package/dist/errors.d.ts +13 -0
  48. package/dist/errors.d.ts.map +1 -0
  49. package/dist/errors.js +21 -0
  50. package/dist/errors.js.map +1 -0
  51. package/dist/ffmpeg.d.ts +8 -0
  52. package/dist/ffmpeg.d.ts.map +1 -0
  53. package/dist/ffmpeg.js +55 -0
  54. package/dist/ffmpeg.js.map +1 -0
  55. package/dist/formats/json.d.ts +12 -0
  56. package/dist/formats/json.d.ts.map +1 -0
  57. package/dist/formats/json.js +20 -0
  58. package/dist/formats/json.js.map +1 -0
  59. package/dist/formats/markdown/method.d.ts +25 -0
  60. package/dist/formats/markdown/method.d.ts.map +1 -0
  61. package/dist/formats/markdown/method.js +48 -0
  62. package/dist/formats/markdown/method.js.map +1 -0
  63. package/dist/formats/markdown/parse.d.ts +44 -0
  64. package/dist/formats/markdown/parse.d.ts.map +1 -0
  65. package/dist/formats/markdown/parse.js +143 -0
  66. package/dist/formats/markdown/parse.js.map +1 -0
  67. package/dist/fs.d.ts +9 -0
  68. package/dist/fs.d.ts.map +1 -0
  69. package/dist/fs.js +30 -0
  70. package/dist/fs.js.map +1 -0
  71. package/dist/index.d.ts +10 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +6 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/logger.d.ts +21 -0
  76. package/dist/logger.d.ts.map +1 -0
  77. package/dist/logger.js +45 -0
  78. package/dist/logger.js.map +1 -0
  79. package/dist/schema.d.ts +5 -0
  80. package/dist/schema.d.ts.map +1 -0
  81. package/dist/schema.js +100 -0
  82. package/dist/schema.js.map +1 -0
  83. package/dist/script.d.ts +21 -0
  84. package/dist/script.d.ts.map +1 -0
  85. package/dist/script.js +26 -0
  86. package/dist/script.js.map +1 -0
  87. package/dist/targets.d.ts +6 -0
  88. package/dist/targets.d.ts.map +1 -0
  89. package/dist/targets.js +13 -0
  90. package/dist/targets.js.map +1 -0
  91. package/dist/timing.d.ts +41 -0
  92. package/dist/timing.d.ts.map +1 -0
  93. package/dist/timing.js +149 -0
  94. package/dist/timing.js.map +1 -0
  95. package/dist/utils.d.ts +3 -0
  96. package/dist/utils.d.ts.map +1 -0
  97. package/dist/utils.js +8 -0
  98. package/dist/utils.js.map +1 -0
  99. package/dist/validate.d.ts +8 -0
  100. package/dist/validate.d.ts.map +1 -0
  101. package/dist/validate.js +54 -0
  102. package/dist/validate.js.map +1 -0
  103. package/dist/video/recorder.d.ts +57 -0
  104. package/dist/video/recorder.d.ts.map +1 -0
  105. package/dist/video/recorder.js +238 -0
  106. package/dist/video/recorder.js.map +1 -0
  107. package/dist/video/stitch.d.ts +15 -0
  108. package/dist/video/stitch.d.ts.map +1 -0
  109. package/dist/video/stitch.js +111 -0
  110. package/dist/video/stitch.js.map +1 -0
  111. package/dist/voiceover/alignment.d.ts +14 -0
  112. package/dist/voiceover/alignment.d.ts.map +1 -0
  113. package/dist/voiceover/alignment.js +13 -0
  114. package/dist/voiceover/alignment.js.map +1 -0
  115. package/dist/voiceover/cache.d.ts +22 -0
  116. package/dist/voiceover/cache.d.ts.map +1 -0
  117. package/dist/voiceover/cache.js +55 -0
  118. package/dist/voiceover/cache.js.map +1 -0
  119. package/dist/voiceover/compile.d.ts +35 -0
  120. package/dist/voiceover/compile.d.ts.map +1 -0
  121. package/dist/voiceover/compile.js +194 -0
  122. package/dist/voiceover/compile.js.map +1 -0
  123. package/dist/voiceover/elevenlabs.d.ts +16 -0
  124. package/dist/voiceover/elevenlabs.d.ts.map +1 -0
  125. package/dist/voiceover/elevenlabs.js +66 -0
  126. package/dist/voiceover/elevenlabs.js.map +1 -0
  127. package/dist/voiceover/index.d.ts +7 -0
  128. package/dist/voiceover/index.d.ts.map +1 -0
  129. package/dist/voiceover/index.js +8 -0
  130. package/dist/voiceover/index.js.map +1 -0
  131. package/dist/voiceover/mock.d.ts +15 -0
  132. package/dist/voiceover/mock.d.ts.map +1 -0
  133. package/dist/voiceover/mock.js +41 -0
  134. package/dist/voiceover/mock.js.map +1 -0
  135. package/dist/voiceover/types.d.ts +31 -0
  136. package/dist/voiceover/types.d.ts.map +1 -0
  137. package/dist/voiceover/types.js +10 -0
  138. package/dist/voiceover/types.js.map +1 -0
  139. package/package.json +86 -0
  140. package/recordable.schema.json +738 -0
@@ -0,0 +1,111 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { join as joinPath } from "node:path";
3
+ import { getDuration, runFfmpeg } from "../ffmpeg.js";
4
+ import { moveFile } from "../fs.js";
5
+ /** Stitch `segs` into `out`, choosing move / join / cross-fade automatically. */
6
+ export async function stitch(segs, cfg, log, out, tmpDir) {
7
+ const hasFades = segs.some((s) => s.fadeIn > 0 || s.fadeOut > 0);
8
+ if (hasFades) {
9
+ await stitchWithFades(segs, cfg, log, out);
10
+ }
11
+ else if (segs.length === 1) {
12
+ moveFile(segs[0].path, out);
13
+ }
14
+ else {
15
+ await join(segs, cfg, log, out, tmpDir);
16
+ }
17
+ }
18
+ /** Concatenate segments into one file. Stream-copies (lossless, fast) when the
19
+ * segments are compatible; falls back to a re-encode if they aren't. */
20
+ async function join(segments, cfg, log, out, tmpDir) {
21
+ const listFile = joinPath(tmpDir, "concat.txt");
22
+ const list = segments
23
+ .map((s) => `file '${s.path.replace(/'/g, "'\\''")}'`)
24
+ .join("\n");
25
+ writeFileSync(listFile, list + "\n");
26
+ const base = ["-y", "-f", "concat", "-safe", "0", "-i", listFile];
27
+ try {
28
+ await runFfmpeg([...base, "-c", "copy", out]);
29
+ log("Record", `joined ${segments.length} segments`);
30
+ }
31
+ catch {
32
+ await runFfmpeg([
33
+ ...base,
34
+ "-c:v",
35
+ cfg.videoCodec,
36
+ "-preset",
37
+ cfg.videoPreset,
38
+ "-crf",
39
+ String(cfg.videoCrf),
40
+ out,
41
+ ]);
42
+ log("Record", `joined ${segments.length} segments (re-encoded)`);
43
+ }
44
+ }
45
+ /**
46
+ * Stitch segments into one file with cross-fade transitions. Builds a single
47
+ * `filter_complex` that, walking left to right, `xfade`s across every cross-fade
48
+ * boundary and `concat`s across hard cuts, then fades from/to black at the very
49
+ * ends when the edge clip asked for it.
50
+ *
51
+ * A cross-fade of duration `d` *overlaps* the two pieces by `d` (the outgoing
52
+ * tail dissolves into the incoming head), so the timeline shortens by `d` at each
53
+ * such boundary — tracked in `accDur` to place the next `xfade` offset.
54
+ * Re-encodes throughout, since xfade can't stream-copy.
55
+ */
56
+ async function stitchWithFades(segs, cfg, log, out) {
57
+ const n = segs.length;
58
+ const dur = await Promise.all(segs.map((s) => getDuration(s.path)));
59
+ // The outer ends have no neighbour to dissolve with, so an edge clip's fade
60
+ // there is a fade against black; interior boundaries cross-fade two pieces.
61
+ const startBlack = Math.min(segs[0].fadeIn, dur[0]);
62
+ const endBlack = Math.min(segs[n - 1].fadeOut, dur[n - 1]);
63
+ const boundary = (i) => segs[i].fadeOut || segs[i + 1].fadeIn || 0;
64
+ // 1. Pre-process each input: relabel [i:v] → [vi], baking in the black fades.
65
+ const parts = [];
66
+ for (let i = 0; i < n; i++) {
67
+ const f = [];
68
+ if (i === 0 && startBlack > 0)
69
+ f.push(`fade=t=in:st=0:d=${startBlack.toFixed(3)}`);
70
+ if (i === n - 1 && endBlack > 0)
71
+ f.push(`fade=t=out:st=${(dur[i] - endBlack).toFixed(3)}:d=${endBlack.toFixed(3)}`);
72
+ parts.push(`[${i}:v]${f.length ? f.join(",") : "null"}[v${i}]`);
73
+ }
74
+ // 2. Fold the inputs together left to right, xfade or concat per boundary.
75
+ let acc = "v0";
76
+ let accDur = dur[0];
77
+ for (let i = 1; i < n; i++) {
78
+ const lbl = `x${i}`;
79
+ const d = boundary(i - 1);
80
+ if (d > 0) {
81
+ const dd = Math.min(d, accDur, dur[i]);
82
+ const offset = Math.max(0, accDur - dd);
83
+ parts.push(`[${acc}][v${i}]xfade=transition=fade:duration=${dd.toFixed(3)}:offset=${offset.toFixed(3)}[${lbl}]`);
84
+ accDur += dur[i] - dd;
85
+ }
86
+ else {
87
+ parts.push(`[${acc}][v${i}]concat=n=2:v=1:a=0[${lbl}]`);
88
+ accDur += dur[i];
89
+ }
90
+ acc = lbl;
91
+ }
92
+ await runFfmpeg([
93
+ "-y",
94
+ ...segs.flatMap((s) => ["-i", s.path]),
95
+ "-filter_complex",
96
+ parts.join(";"),
97
+ "-map",
98
+ `[${acc}]`,
99
+ "-c:v",
100
+ cfg.videoCodec,
101
+ "-preset",
102
+ cfg.videoPreset,
103
+ "-crf",
104
+ String(cfg.videoCrf),
105
+ "-pix_fmt",
106
+ "yuv420p",
107
+ out,
108
+ ]);
109
+ log("Record", `joined ${n} segments (cross-faded)`);
110
+ }
111
+ //# sourceMappingURL=stitch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stitch.js","sourceRoot":"","sources":["../../src/video/stitch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { Alignment } from "./types.js";
2
+ /** The raw alignment block as returned by ElevenLabs (either key casing). */
3
+ export interface ElevenLabsAlignment {
4
+ characters?: string[];
5
+ character_start_times_seconds?: number[];
6
+ character_end_times_seconds?: number[];
7
+ characterStartTimesSeconds?: number[];
8
+ characterEndTimesSeconds?: number[];
9
+ }
10
+ /** Normalise a raw ElevenLabs alignment to ms-based {@link Alignment}. */
11
+ export declare function normalizeAlignment(raw: ElevenLabsAlignment): Alignment;
12
+ /** Clip duration implied by an alignment: the last character's end time (ms). */
13
+ export declare function alignmentDurationMs(a: Alignment): number;
14
+ //# sourceMappingURL=alignment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alignment.d.ts","sourceRoot":"","sources":["../../src/voiceover/alignment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAQ5C,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,6BAA6B,CAAC,EAAE,MAAM,EAAE,CAAC;IACzC,2BAA2B,CAAC,EAAE,MAAM,EAAE,CAAC;IAEvC,0BAA0B,CAAC,EAAE,MAAM,EAAE,CAAC;IACtC,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC;CACrC;AAKD,0EAA0E;AAC1E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,mBAAmB,GAAG,SAAS,CAOtE;AAED,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CAExD"}
@@ -0,0 +1,13 @@
1
+ const toMs = (secs) => secs.map((s) => Math.round(s * 1000));
2
+ /** Normalise a raw ElevenLabs alignment to ms-based {@link Alignment}. */
3
+ export function normalizeAlignment(raw) {
4
+ const chars = raw.characters ?? [];
5
+ const startSecs = raw.character_start_times_seconds ?? raw.characterStartTimesSeconds ?? [];
6
+ const endSecs = raw.character_end_times_seconds ?? raw.characterEndTimesSeconds ?? [];
7
+ return { chars, startMs: toMs(startSecs), endMs: toMs(endSecs) };
8
+ }
9
+ /** Clip duration implied by an alignment: the last character's end time (ms). */
10
+ export function alignmentDurationMs(a) {
11
+ return a.endMs.length ? a.endMs[a.endMs.length - 1] : 0;
12
+ }
13
+ //# sourceMappingURL=alignment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alignment.js","sourceRoot":"","sources":["../../src/voiceover/alignment.ts"],"names":[],"mappings":"AAkBA,MAAM,IAAI,GAAG,CAAC,IAAc,EAAY,EAAE,CACxC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;AAExC,0EAA0E;AAC1E,MAAM,UAAU,kBAAkB,CAAC,GAAwB;IACzD,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC;IACnC,MAAM,SAAS,GACb,GAAG,CAAC,6BAA6B,IAAI,GAAG,CAAC,0BAA0B,IAAI,EAAE,CAAC;IAC5E,MAAM,OAAO,GACX,GAAG,CAAC,2BAA2B,IAAI,GAAG,CAAC,wBAAwB,IAAI,EAAE,CAAC;IACxE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;AACnE,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,mBAAmB,CAAC,CAAY;IAC9C,OAAO,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { type TTSResult } from "./types.js";
2
+ /** The inputs that fully determine a synthesis result. */
3
+ export interface CacheKeyParts {
4
+ provider: string;
5
+ voiceId: string;
6
+ modelId?: string;
7
+ voiceSettings?: Record<string, number>;
8
+ format?: string;
9
+ text: string;
10
+ }
11
+ /** Stable hash of everything that affects the audio — order-independent. */
12
+ export declare function cacheKey(parts: CacheKeyParts): string;
13
+ /** A directory-backed cache of {@link TTSResult}s, keyed by {@link cacheKey}. */
14
+ export declare class FileCache {
15
+ private readonly dir;
16
+ constructor(dir: string);
17
+ /** Cached result for `key`, or null on a miss. */
18
+ get(key: string): TTSResult | null;
19
+ /** Store `result` under `key` (audio file + JSON sidecar). */
20
+ put(key: string, result: TTSResult): void;
21
+ }
22
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/voiceover/cache.ts"],"names":[],"mappings":"AAGA,OAAO,EAA0B,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AASpE,0DAA0D;AAC1D,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAcrD;AASD,iFAAiF;AACjF,qBAAa,SAAS;IACR,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,MAAM;IAExC,kDAAkD;IAClD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAclC,8DAA8D;IAC9D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI;CAa1C"}
@@ -0,0 +1,55 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { extFor } from "./types.js";
5
+ /** Stable hash of everything that affects the audio — order-independent. */
6
+ export function cacheKey(parts) {
7
+ const settings = parts.voiceSettings ?? {};
8
+ const sorted = {};
9
+ for (const k of Object.keys(settings).sort())
10
+ sorted[k] = settings[k];
11
+ const canonical = JSON.stringify({
12
+ provider: parts.provider,
13
+ voiceId: parts.voiceId,
14
+ modelId: parts.modelId ?? "",
15
+ voiceSettings: sorted,
16
+ format: parts.format ?? "",
17
+ text: parts.text,
18
+ });
19
+ return createHash("sha256").update(canonical).digest("hex");
20
+ }
21
+ /** A directory-backed cache of {@link TTSResult}s, keyed by {@link cacheKey}. */
22
+ export class FileCache {
23
+ dir;
24
+ constructor(dir) {
25
+ this.dir = dir;
26
+ }
27
+ /** Cached result for `key`, or null on a miss. */
28
+ get(key) {
29
+ const metaPath = join(this.dir, `${key}.json`);
30
+ if (!existsSync(metaPath))
31
+ return null;
32
+ const meta = JSON.parse(readFileSync(metaPath, "utf8"));
33
+ const audioPath = join(this.dir, `${key}.${extFor(meta.format)}`);
34
+ if (!existsSync(audioPath))
35
+ return null;
36
+ return {
37
+ audio: readFileSync(audioPath),
38
+ format: meta.format,
39
+ durationMs: meta.durationMs,
40
+ alignment: meta.alignment,
41
+ };
42
+ }
43
+ /** Store `result` under `key` (audio file + JSON sidecar). */
44
+ put(key, result) {
45
+ mkdirSync(this.dir, { recursive: true });
46
+ writeFileSync(join(this.dir, `${key}.${extFor(result.format)}`), result.audio);
47
+ const meta = {
48
+ format: result.format,
49
+ durationMs: result.durationMs,
50
+ alignment: result.alignment,
51
+ };
52
+ writeFileSync(join(this.dir, `${key}.json`), JSON.stringify(meta));
53
+ }
54
+ }
55
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/voiceover/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAkC,MAAM,YAAY,CAAC;AAmBpE,4EAA4E;AAC5E,MAAM,UAAU,QAAQ,CAAC,KAAoB;IAC3C,MAAM,QAAQ,GAAG,KAAK,CAAC,aAAa,IAAI,EAAE,CAAC;IAC3C,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE;QAAE,MAAM,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAEtE,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAC/B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE;QAC5B,aAAa,EAAE,MAAM;QACrB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,EAAE;QAC1B,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC,CAAC;IACH,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9D,CAAC;AASD,iFAAiF;AACjF,MAAM,OAAO,SAAS;IACS;IAA7B,YAA6B,GAAW;QAAX,QAAG,GAAH,GAAG,CAAQ;IAAG,CAAC;IAE5C,kDAAkD;IAClD,GAAG,CAAC,GAAW;QACb,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC;QAC/C,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAc,CAAC;QACrE,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAClE,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,IAAI,CAAC;QACxC,OAAO;YACL,KAAK,EAAE,YAAY,CAAC,SAAS,CAAC;YAC9B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC;IACJ,CAAC;IAED,8DAA8D;IAC9D,GAAG,CAAC,GAAW,EAAE,MAAiB;QAChC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,aAAa,CACX,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EACjD,MAAM,CAAC,KAAK,CACb,CAAC;QACF,MAAM,IAAI,GAAc;YACtB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,SAAS,EAAE,MAAM,CAAC,SAAS;SAC5B,CAAC;QACF,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IACrE,CAAC;CACF"}
@@ -0,0 +1,35 @@
1
+ import type { Action } from "../actions.js";
2
+ import type { RecordableConfig, VoiceoverConfig } from "../config.js";
3
+ import { type Logger } from "../logger.js";
4
+ import { type TTSProvider } from "./types.js";
5
+ /** Everything the compiler needs that isn't in the document itself. */
6
+ export interface CompileOptions {
7
+ /** Synthesis backend. Omit to build an `ElevenLabsProvider` from the voiceover
8
+ * config; pass one (e.g. `MockTTSProvider`) to synthesize offline. */
9
+ provider?: TTSProvider;
10
+ /** Directory the generated (committable) audio assets are written to. */
11
+ assetsDir: string;
12
+ /** Voiceover settings (provider/voice/model). Falls back to document frontmatter. */
13
+ voiceover?: VoiceoverConfig;
14
+ /** Config merged over frontmatter (e.g. CLI flags). `actionDelay` is always forced to 0. */
15
+ configOverride?: RecordableConfig;
16
+ /** Gitignored timing cache. Default: `<assetsDir>/.cache`. */
17
+ cacheDir?: string;
18
+ /** Where overrun / drift warnings go. Default: the universal `[Recordable]` warn logger. */
19
+ warn?: (message: string) => void;
20
+ /** Progress logger for synthesis/cache events. Omit for silent (progress is opt-in);
21
+ * pass the host's logger to surface the whole voiceover flow. */
22
+ log?: Logger;
23
+ }
24
+ /** The compiled artifact: a runnable script plus the audio files it references. */
25
+ export interface CompiledScript {
26
+ config: RecordableConfig;
27
+ actions: Action[];
28
+ /** Paths of every audio asset written, in document order (deduped by content). */
29
+ assets: string[];
30
+ }
31
+ /** Compile a Markdown document into a runnable core script plus generated audio.
32
+ * Paragraphs become synthesized clips with computed waits; fenced blocks between
33
+ * them are narrative-level pauses. `actionDelay` is forced to 0 to keep timing exact. */
34
+ export declare function compileMarkdown(md: string, options: CompileOptions): Promise<CompiledScript>;
35
+ //# sourceMappingURL=compile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/voiceover/compile.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEtE,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AASzD,OAAO,EAA0B,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAStE,uEAAuE;AACvE,MAAM,WAAW,cAAc;IAC7B;2EACuE;IACvE,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB,qFAAqF;IACrF,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,4FAA4F;IAC5F,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4FAA4F;IAC5F,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC;sEACkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,mFAAmF;AACnF,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kFAAkF;IAClF,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAwLD;;0FAE0F;AAC1F,wBAAsB,eAAe,CACnC,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,cAAc,CAAC,CAgDzB"}
@@ -0,0 +1,194 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { parseMarkdown } from "../formats/markdown/parse.js";
4
+ import { truncate } from "../utils.js";
5
+ import { createLogger } from "../logger.js";
6
+ import { actionDurationMs } from "../timing.js";
7
+ import { cacheKey, FileCache } from "./cache.js";
8
+ import { ElevenLabsProvider, DEFAULT_MODEL, DEFAULT_FORMAT, } from "./elevenlabs.js";
9
+ import { MockTTSProvider } from "./mock.js";
10
+ import { extFor } from "./types.js";
11
+ /** First-character index of the word containing `offset` (scan back to whitespace). */
12
+ function wordStart(text, offset) {
13
+ let i = Math.min(offset, text.length);
14
+ while (i > 0 && !/\s/.test(text[i - 1]))
15
+ i--;
16
+ return i;
17
+ }
18
+ /** Timeline position (ms) of a marker: the start of its enclosing word, or the
19
+ * clip end for a trailing marker clamped past the last char. */
20
+ function markerFireMs(offset, narration, alignment, durationMs) {
21
+ if (offset >= alignment.startMs.length)
22
+ return durationMs;
23
+ const w = wordStart(narration, offset);
24
+ return alignment.startMs[w] ?? durationMs;
25
+ }
26
+ /** A marker as the author wrote it, e.g. `click("#signInBtn")` — for diagnostics. */
27
+ function markerLabel(step) {
28
+ const arg = (step.target ?? step.path ?? "");
29
+ return arg ? `${step.action}("${arg}")` : `${step.action}()`;
30
+ }
31
+ /** The narrated word a marker is anchored to (or "the end" for a trailing one). */
32
+ function wordAt(narration, offset) {
33
+ if (offset >= narration.length)
34
+ return "the end";
35
+ const word = narration.slice(wordStart(narration, offset)).match(/^\S+/);
36
+ return word ? `"${word[0]}"` : "the end";
37
+ }
38
+ /** Cache/asset key for a block — narration is already the stripped, audible text.
39
+ * Model/format default to the same values the provider applies, so the key
40
+ * captures what was actually synthesized (else changing the default would serve
41
+ * stale audio, and naming the default explicitly would force a needless re-run). */
42
+ function keyFor(narration, vo) {
43
+ return cacheKey({
44
+ provider: vo?.provider ?? "mock",
45
+ voiceId: vo?.voiceId ?? "",
46
+ modelId: vo?.modelId ?? DEFAULT_MODEL,
47
+ voiceSettings: vo?.voiceSettings,
48
+ format: vo?.format ?? DEFAULT_FORMAT,
49
+ text: narration,
50
+ });
51
+ }
52
+ /** Synthesize one paragraph (cache-first) → its `audio()` clip, a `wait`/action
53
+ * pair per marker, and a tail `wait` so the voiceover finishes. */
54
+ async function compileNarration(block, opts) {
55
+ const { narration, markers } = block;
56
+ const key = keyFor(narration, opts.voiceover);
57
+ const preview = truncate(narration);
58
+ let result = opts.cache.get(key);
59
+ if (result) {
60
+ opts.log("Voice", `cache hit "${preview}"`);
61
+ }
62
+ else {
63
+ // A miss means a real synthesis — for a network provider, an external API call.
64
+ opts.log("Voice", `synthesize "${preview}" via ${opts.providerName}`);
65
+ result = await opts.provider.synthesize(narration, {
66
+ format: opts.voiceover?.format,
67
+ });
68
+ opts.cache.put(key, result);
69
+ const kb = (result.audio.length / 1024).toFixed(0);
70
+ opts.log("Voice", `generated "${preview}" (${Math.round(result.durationMs)}ms, ${kb} KB)`);
71
+ }
72
+ const asset = join(opts.assetsDir, `${key}.${extFor(result.format)}`);
73
+ mkdirSync(opts.assetsDir, { recursive: true });
74
+ writeFileSync(asset, result.audio);
75
+ const actions = [{ action: "audio", path: asset, wait: false }];
76
+ const alignment = result.alignment ?? {
77
+ chars: [],
78
+ startMs: [],
79
+ endMs: [],
80
+ };
81
+ let elapsed = 0;
82
+ let prevOffset = -1;
83
+ for (const m of markers) {
84
+ const fire = markerFireMs(m.offset, narration, alignment, result.durationMs);
85
+ const gap = Math.round(fire - elapsed);
86
+ if (gap > 0) {
87
+ actions.push({ action: "wait", ms: gap });
88
+ elapsed += gap;
89
+ }
90
+ else if (gap < 0) {
91
+ // The action can't land on its word: earlier actions in this paragraph
92
+ // (their cursor travel, typing, inserts) already ran `-gap`ms past it, so
93
+ // this one and everything after it lag. Point at the exact spot and say why.
94
+ const stacked = m.offset === prevOffset;
95
+ const cause = stacked
96
+ ? `it shares a spot in the narration with the action before it`
97
+ : `the actions before it overrun by that much`;
98
+ const fix = stacked
99
+ ? `move ${markerLabel(m.step)} to the word it actually describes, or add narration between the two`
100
+ : `add words before it, space the actions out, or move it into a fenced block (no audio)`;
101
+ opts.warn(`Voiceover timing — ${markerLabel(m.step)} can't start on ${wordAt(narration, m.offset)} ` +
102
+ `(paragraph "${preview}"): ${cause}, so it and the rest lag ${-gap}ms. Fix: ${fix}.`);
103
+ }
104
+ actions.push(m.step);
105
+ elapsed += await actionDurationMs(m.step, opts.cfg);
106
+ prevOffset = m.offset;
107
+ }
108
+ // Let the narration finish before the next block begins.
109
+ const tail = Math.round(result.durationMs - elapsed);
110
+ if (tail > 0)
111
+ actions.push({ action: "wait", ms: tail });
112
+ return { actions, asset };
113
+ }
114
+ /** Apply env defaults to a voiceover block — frontmatter always wins. The env
115
+ * vars let many files share a provider/voice/model without repeating it; a file
116
+ * stays fully reproducible by spelling the values out. No-op without a block. */
117
+ function withEnvDefaults(vo) {
118
+ if (!vo)
119
+ return vo;
120
+ return {
121
+ ...vo,
122
+ provider: vo.provider || process.env.RECORDABLE_TTS_PROVIDER || "elevenlabs",
123
+ voiceId: vo.voiceId || process.env.RECORDABLE_VOICE_ID || "",
124
+ modelId: vo.modelId ?? process.env.RECORDABLE_MODEL_ID,
125
+ };
126
+ }
127
+ /** Pick the synthesis backend: an explicit provider, else the one named by the
128
+ * voiceover config. `mock` is silent/offline; ElevenLabs requires an API key
129
+ * (config `apiKey`, else `ELEVENLABS_API_KEY`) — without one we throw, since a
130
+ * voiceover run with no key can't do anything useful. */
131
+ function resolveProvider(provider, voiceover) {
132
+ if (provider)
133
+ return provider;
134
+ if (!voiceover) {
135
+ throw new Error("compileMarkdown: no `voiceover` frontmatter and no `provider` — add a voiceover block or pass a provider.");
136
+ }
137
+ if (voiceover.provider === "mock")
138
+ return new MockTTSProvider();
139
+ const hasKey = voiceover.apiKey ?? process.env.ELEVENLABS_API_KEY;
140
+ if (!hasKey) {
141
+ throw new Error("Voiceover: ElevenLabs needs an API key — set ELEVENLABS_API_KEY (e.g. in a .env beside " +
142
+ "the document) or `voiceover.apiKey`, or use RECORDABLE_TTS_PROVIDER=mock for silent audio.");
143
+ }
144
+ const voiceId = voiceover.voiceId;
145
+ if (!voiceId) {
146
+ throw new Error("Voiceover: ElevenLabs needs a voice — set RECORDABLE_VOICE_ID (e.g. in a .env beside " +
147
+ "the document) or `voiceover.voiceId`.");
148
+ }
149
+ return new ElevenLabsProvider({ ...voiceover, voiceId });
150
+ }
151
+ /** Compile a Markdown document into a runnable core script plus generated audio.
152
+ * Paragraphs become synthesized clips with computed waits; fenced blocks between
153
+ * them are narrative-level pauses. `actionDelay` is forced to 0 to keep timing exact. */
154
+ export async function compileMarkdown(md, options) {
155
+ const parsed = parseMarkdown(md);
156
+ // Progress is opt-in (silent default); warnings always surface.
157
+ const log = options.log ?? createLogger(() => true);
158
+ const warn = options.warn ?? options.log?.warn ?? createLogger(() => false).warn;
159
+ const voiceover = withEnvDefaults(options.voiceover ?? parsed.voiceover);
160
+ const provider = resolveProvider(options.provider, voiceover);
161
+ const providerName = provider.constructor?.name?.replace(/(TTS)?Provider$/, "").toLowerCase() ||
162
+ "provider";
163
+ const cache = new FileCache(options.cacheDir ?? join(options.assetsDir, ".cache"));
164
+ const config = {
165
+ ...parsed.config,
166
+ ...options.configOverride,
167
+ actionDelay: 0,
168
+ };
169
+ const actions = [];
170
+ const assets = [];
171
+ const narrationCount = parsed.blocks.filter((b) => b.type !== "actions").length;
172
+ log("Voice", `compiling ${narrationCount} narration block(s) → ${providerName}`);
173
+ for (const block of parsed.blocks) {
174
+ if (block.type === "actions") {
175
+ actions.push(...block.actions); // a fenced pause: actions run sequentially, no audio
176
+ continue;
177
+ }
178
+ const compiled = await compileNarration(block, {
179
+ provider,
180
+ assetsDir: options.assetsDir,
181
+ voiceover,
182
+ cache,
183
+ cfg: config,
184
+ warn,
185
+ log,
186
+ providerName,
187
+ });
188
+ actions.push(...compiled.actions);
189
+ assets.push(compiled.asset);
190
+ }
191
+ log.success("Voice", `done — compiled ${assets.length} clip(s)`);
192
+ return { config, actions, assets };
193
+ }
194
+ //# sourceMappingURL=compile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile.js","sourceRoot":"","sources":["../../src/voiceover/compile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { VoiceoverConfig } from "../config.js";
2
+ import type { SynthOptions, TTSProvider, TTSResult } from "./types.js";
3
+ export declare const DEFAULT_MODEL = "eleven_multilingual_v2";
4
+ export declare const DEFAULT_FORMAT = "mp3_44100_128";
5
+ export interface ElevenLabsOptions extends VoiceoverConfig {
6
+ /** Required here — resolved from config/env before the provider is built. */
7
+ voiceId: string;
8
+ }
9
+ export declare class ElevenLabsProvider implements TTSProvider {
10
+ private readonly cfg;
11
+ constructor(cfg: ElevenLabsOptions);
12
+ synthesize(text: string, opts?: SynthOptions): Promise<TTSResult>;
13
+ /** The raw network call — isolated so everything around it stays testable. */
14
+ private _synthesize;
15
+ }
16
+ //# sourceMappingURL=elevenlabs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elevenlabs.d.ts","sourceRoot":"","sources":["../../src/voiceover/elevenlabs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAOpD,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAUvE,eAAO,MAAM,aAAa,2BAA2B,CAAC;AACtD,eAAO,MAAM,cAAc,kBAAkB,CAAC;AAG9C,MAAM,WAAW,iBAAkB,SAAQ,eAAe;IACxD,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,kBAAmB,YAAW,WAAW;IACxC,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,iBAAiB;IAE7C,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;IAK3E,8EAA8E;YAChE,WAAW;CAyD1B"}
@@ -0,0 +1,66 @@
1
+ import { RecordableError } from "../errors.js";
2
+ import { alignmentDurationMs, normalizeAlignment, } from "./alignment.js";
3
+ // ─── ElevenLabs provider ─────────────────────────────────────────────────────
4
+ //
5
+ // The first TTS implementation. The SDK is an optionalDependency, loaded with a
6
+ // dynamic import only on this path, so a core record-JSON install never pulls it
7
+ // in. Caching lives one level up in the compiler (keyed by the same inputs);
8
+ // this provider just turns text into audio. Network calls run in the user's
9
+ // terminal (secret + connectivity).
10
+ export const DEFAULT_MODEL = "eleven_multilingual_v2";
11
+ export const DEFAULT_FORMAT = "mp3_44100_128";
12
+ const PACKAGE = "@elevenlabs/elevenlabs-js";
13
+ export class ElevenLabsProvider {
14
+ cfg;
15
+ constructor(cfg) {
16
+ this.cfg = cfg;
17
+ }
18
+ async synthesize(text, opts = {}) {
19
+ const format = opts.format ?? this.cfg.format ?? DEFAULT_FORMAT;
20
+ return this._synthesize(text, format);
21
+ }
22
+ /** The raw network call — isolated so everything around it stays testable. */
23
+ async _synthesize(text, format) {
24
+ const apiKey = this.cfg.apiKey ?? process.env.ELEVENLABS_API_KEY;
25
+ if (!apiKey) {
26
+ throw new Error("ElevenLabs: no API key — set ELEVENLABS_API_KEY (or voiceover.apiKey)");
27
+ }
28
+ // Non-literal specifier so tsc doesn't try to resolve the optional dep; it's
29
+ // present only when the voiceover add-on path is actually used.
30
+ let mod;
31
+ try {
32
+ mod = await import(PACKAGE);
33
+ }
34
+ catch {
35
+ throw new Error(`ElevenLabs: the "${PACKAGE}" package is not installed — ` +
36
+ `add it to use voiceover (it is an optional dependency)`);
37
+ }
38
+ const ElevenLabsClient = mod.ElevenLabsClient ?? mod.default?.ElevenLabsClient ?? mod.default;
39
+ const client = new ElevenLabsClient({ apiKey });
40
+ let res;
41
+ try {
42
+ res = await client.textToSpeech.convertWithTimestamps(this.cfg.voiceId, {
43
+ text,
44
+ modelId: this.cfg.modelId ?? DEFAULT_MODEL,
45
+ voiceSettings: this.cfg.voiceSettings,
46
+ outputFormat: format,
47
+ });
48
+ }
49
+ catch (e) {
50
+ throw new RecordableError("TTS_FAILED", `ElevenLabs synthesis failed: ${e.message}`, { cause: e });
51
+ }
52
+ // Response is the object directly in current SDKs, `.data`-wrapped in older.
53
+ // Audio field is `audioBase64` (camelCase SDK) / `audio_base64` (raw REST).
54
+ const data = res?.data ?? res;
55
+ const audioBase64 = data.audioBase64 ?? data.audio_base64 ?? data.audio;
56
+ if (!audioBase64)
57
+ throw new RecordableError("TTS_FAILED", "ElevenLabs returned no audio in its response");
58
+ const audio = Buffer.from(audioBase64, "base64");
59
+ const alignment = data.alignment
60
+ ? normalizeAlignment(data.alignment)
61
+ : undefined;
62
+ const durationMs = alignment ? alignmentDurationMs(alignment) : 0;
63
+ return { audio, format, durationMs, alignment };
64
+ }
65
+ }
66
+ //# sourceMappingURL=elevenlabs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elevenlabs.js","sourceRoot":"","sources":["../../src/voiceover/elevenlabs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export type { Alignment, TTSResult, TTSProvider, SynthOptions, } from "./types.js";
2
+ export { normalizeAlignment, alignmentDurationMs, type ElevenLabsAlignment, } from "./alignment.js";
3
+ export { cacheKey, FileCache, type CacheKeyParts } from "./cache.js";
4
+ export { ElevenLabsProvider, type ElevenLabsOptions } from "./elevenlabs.js";
5
+ export { MockTTSProvider, silentWav, type MockOptions } from "./mock.js";
6
+ export { compileMarkdown, type CompileOptions, type CompiledScript, } from "./compile.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/voiceover/index.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,SAAS,EACT,SAAS,EACT,WAAW,EACX,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AACzE,OAAO,EACL,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC"}
@@ -0,0 +1,8 @@
1
+ // The optional voiceover add-on. Imports core; core never imports this. Pulls in
2
+ // the ElevenLabs SDK (an optional dependency) only when actually used.
3
+ export { normalizeAlignment, alignmentDurationMs, } from "./alignment.js";
4
+ export { cacheKey, FileCache } from "./cache.js";
5
+ export { ElevenLabsProvider } from "./elevenlabs.js";
6
+ export { MockTTSProvider, silentWav } from "./mock.js";
7
+ export { compileMarkdown, } from "./compile.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/voiceover/index.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,uEAAuE;AAQvE,OAAO,EACL,kBAAkB,EAClB,mBAAmB,GAEpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAsB,MAAM,YAAY,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAA0B,MAAM,iBAAiB,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,SAAS,EAAoB,MAAM,WAAW,CAAC;AACzE,OAAO,EACL,eAAe,GAGhB,MAAM,cAAc,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { SynthOptions, TTSProvider, TTSResult } from "./types.js";
2
+ export interface MockOptions {
3
+ /** Synthetic speaking rate: ms of audio per character. Default 55. */
4
+ msPerChar?: number;
5
+ /** WAV sample rate; lower keeps the silent buffer small. Default 8000. */
6
+ sampleRate?: number;
7
+ }
8
+ export declare class MockTTSProvider implements TTSProvider {
9
+ private readonly opts;
10
+ constructor(opts?: MockOptions);
11
+ synthesize(text: string, _opts?: SynthOptions): Promise<TTSResult>;
12
+ }
13
+ /** Build a valid silent 16-bit mono WAV of `durationMs` — ffmpeg-compatible. */
14
+ export declare function silentWav(durationMs: number, sampleRate?: number): Buffer;
15
+ //# sourceMappingURL=mock.d.ts.map