@kvman/kvvideo 0.1.0 → 0.1.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.
Files changed (258) hide show
  1. package/README.md +1 -1
  2. package/dist/api.d.ts +31 -0
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/brand/brand.d.ts +17 -0
  5. package/dist/brand/brand.d.ts.map +1 -0
  6. package/dist/brand/brand.js +44 -0
  7. package/dist/brand/brand.js.map +1 -0
  8. package/dist/browser/launch.d.ts +2 -0
  9. package/dist/browser/launch.d.ts.map +1 -1
  10. package/dist/browser/launch.js +7 -1
  11. package/dist/browser/launch.js.map +1 -1
  12. package/dist/compose/ass-file.d.ts +5 -0
  13. package/dist/compose/ass-file.d.ts.map +1 -1
  14. package/dist/compose/ass-file.js +32 -3
  15. package/dist/compose/ass-file.js.map +1 -1
  16. package/dist/compose/background.d.ts +23 -0
  17. package/dist/compose/background.d.ts.map +1 -0
  18. package/dist/compose/background.js +31 -0
  19. package/dist/compose/background.js.map +1 -0
  20. package/dist/compose/camera.d.ts +32 -0
  21. package/dist/compose/camera.d.ts.map +1 -0
  22. package/dist/compose/camera.js +98 -0
  23. package/dist/compose/camera.js.map +1 -0
  24. package/dist/compose/caption-font.d.ts +2 -0
  25. package/dist/compose/caption-font.d.ts.map +1 -1
  26. package/dist/compose/caption-font.js +2 -1
  27. package/dist/compose/caption-font.js.map +1 -1
  28. package/dist/compose/compose-drawing.d.ts +19 -0
  29. package/dist/compose/compose-drawing.d.ts.map +1 -0
  30. package/dist/compose/compose-drawing.js +38 -0
  31. package/dist/compose/compose-drawing.js.map +1 -0
  32. package/dist/compose/compose-input.d.ts +139 -0
  33. package/dist/compose/compose-input.d.ts.map +1 -1
  34. package/dist/compose/compose-input.js +48 -9
  35. package/dist/compose/compose-input.js.map +1 -1
  36. package/dist/compose/compose-plan.d.ts +36 -0
  37. package/dist/compose/compose-plan.d.ts.map +1 -0
  38. package/dist/compose/compose-plan.js +55 -0
  39. package/dist/compose/compose-plan.js.map +1 -0
  40. package/dist/compose/compose-size.d.ts +49 -0
  41. package/dist/compose/compose-size.d.ts.map +1 -0
  42. package/dist/compose/compose-size.js +67 -0
  43. package/dist/compose/compose-size.js.map +1 -0
  44. package/dist/compose/drawn-images.d.ts +19 -0
  45. package/dist/compose/drawn-images.d.ts.map +1 -0
  46. package/dist/compose/drawn-images.js +75 -0
  47. package/dist/compose/drawn-images.js.map +1 -0
  48. package/dist/compose/final-pass.d.ts +34 -0
  49. package/dist/compose/final-pass.d.ts.map +1 -0
  50. package/dist/compose/final-pass.js +106 -0
  51. package/dist/compose/final-pass.js.map +1 -0
  52. package/dist/compose/frame-layout.d.ts +30 -0
  53. package/dist/compose/frame-layout.d.ts.map +1 -0
  54. package/dist/compose/frame-layout.js +33 -0
  55. package/dist/compose/frame-layout.js.map +1 -0
  56. package/dist/compose/overlays.d.ts +20 -0
  57. package/dist/compose/overlays.d.ts.map +1 -0
  58. package/dist/compose/overlays.js +30 -0
  59. package/dist/compose/overlays.js.map +1 -0
  60. package/dist/compose/page-again.d.ts +26 -0
  61. package/dist/compose/page-again.d.ts.map +1 -0
  62. package/dist/compose/page-again.js +50 -0
  63. package/dist/compose/page-again.js.map +1 -0
  64. package/dist/compose/player-copies.d.ts +5 -1
  65. package/dist/compose/player-copies.d.ts.map +1 -1
  66. package/dist/compose/player-copies.js +19 -16
  67. package/dist/compose/player-copies.js.map +1 -1
  68. package/dist/compose/register-compose.d.ts.map +1 -1
  69. package/dist/compose/register-compose.js +104 -20
  70. package/dist/compose/register-compose.js.map +1 -1
  71. package/dist/compose/render.d.ts +13 -4
  72. package/dist/compose/render.d.ts.map +1 -1
  73. package/dist/compose/render.js +48 -22
  74. package/dist/compose/render.js.map +1 -1
  75. package/dist/compose/scene-args.d.ts +32 -7
  76. package/dist/compose/scene-args.d.ts.map +1 -1
  77. package/dist/compose/scene-args.js +77 -16
  78. package/dist/compose/scene-args.js.map +1 -1
  79. package/dist/compose/scene-look.d.ts +31 -0
  80. package/dist/compose/scene-look.d.ts.map +1 -0
  81. package/dist/compose/scene-look.js +46 -0
  82. package/dist/compose/scene-look.js.map +1 -0
  83. package/dist/compose/scene-plan.d.ts +0 -2
  84. package/dist/compose/scene-plan.d.ts.map +1 -1
  85. package/dist/compose/scene-plan.js +7 -5
  86. package/dist/compose/scene-plan.js.map +1 -1
  87. package/dist/compose/sound-effects.d.ts +23 -0
  88. package/dist/compose/sound-effects.d.ts.map +1 -0
  89. package/dist/compose/sound-effects.js +40 -0
  90. package/dist/compose/sound-effects.js.map +1 -0
  91. package/dist/compose/transitions.d.ts +16 -0
  92. package/dist/compose/transitions.d.ts.map +1 -0
  93. package/dist/compose/transitions.js +38 -0
  94. package/dist/compose/transitions.js.map +1 -0
  95. package/dist/connector.d.ts +79 -14
  96. package/dist/connector.d.ts.map +1 -1
  97. package/dist/connector.js +33 -8
  98. package/dist/connector.js.map +1 -1
  99. package/dist/files/sidecar.d.ts +209 -0
  100. package/dist/files/sidecar.d.ts.map +1 -0
  101. package/dist/files/sidecar.js +66 -0
  102. package/dist/files/sidecar.js.map +1 -0
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.js +9 -1
  105. package/dist/index.js.map +1 -1
  106. package/dist/look/look-at.d.ts +15 -0
  107. package/dist/look/look-at.d.ts.map +1 -0
  108. package/dist/look/look-at.js +20 -0
  109. package/dist/look/look-at.js.map +1 -0
  110. package/dist/look/look-input.d.ts +22 -0
  111. package/dist/look/look-input.d.ts.map +1 -0
  112. package/dist/look/look-input.js +25 -0
  113. package/dist/look/look-input.js.map +1 -0
  114. package/dist/look/register-look.d.ts +3 -0
  115. package/dist/look/register-look.d.ts.map +1 -0
  116. package/dist/look/register-look.js +52 -0
  117. package/dist/look/register-look.js.map +1 -0
  118. package/dist/narration/alignment.d.ts +15 -0
  119. package/dist/narration/alignment.d.ts.map +1 -0
  120. package/dist/narration/alignment.js +21 -0
  121. package/dist/narration/alignment.js.map +1 -0
  122. package/dist/narration/captions.d.ts +22 -4
  123. package/dist/narration/captions.d.ts.map +1 -1
  124. package/dist/narration/captions.js +44 -16
  125. package/dist/narration/captions.js.map +1 -1
  126. package/dist/narration/join-args.d.ts +3 -0
  127. package/dist/narration/join-args.d.ts.map +1 -0
  128. package/dist/narration/join-args.js +19 -0
  129. package/dist/narration/join-args.js.map +1 -0
  130. package/dist/narration/narration-cache.d.ts +25 -0
  131. package/dist/narration/narration-cache.d.ts.map +1 -0
  132. package/dist/narration/narration-cache.js +36 -0
  133. package/dist/narration/narration-cache.js.map +1 -0
  134. package/dist/narration/piper.d.ts +2 -6
  135. package/dist/narration/piper.d.ts.map +1 -1
  136. package/dist/narration/piper.js +9 -15
  137. package/dist/narration/piper.js.map +1 -1
  138. package/dist/narration/pronunciations.d.ts +11 -0
  139. package/dist/narration/pronunciations.d.ts.map +1 -0
  140. package/dist/narration/pronunciations.js +40 -0
  141. package/dist/narration/pronunciations.js.map +1 -0
  142. package/dist/narration/register-narration.d.ts +8 -0
  143. package/dist/narration/register-narration.d.ts.map +1 -1
  144. package/dist/narration/register-narration.js +54 -26
  145. package/dist/narration/register-narration.js.map +1 -1
  146. package/dist/narration/register-voice.d.ts +27 -0
  147. package/dist/narration/register-voice.d.ts.map +1 -0
  148. package/dist/narration/register-voice.js +82 -0
  149. package/dist/narration/register-voice.js.map +1 -0
  150. package/dist/narration/sentence-speaker.d.ts +35 -0
  151. package/dist/narration/sentence-speaker.d.ts.map +1 -0
  152. package/dist/narration/sentence-speaker.js +44 -0
  153. package/dist/narration/sentence-speaker.js.map +1 -0
  154. package/dist/narration/speech-services.d.ts +23 -3
  155. package/dist/narration/speech-services.d.ts.map +1 -1
  156. package/dist/narration/speech-services.js +27 -10
  157. package/dist/narration/speech-services.js.map +1 -1
  158. package/dist/narration/voice-args.d.ts +3 -0
  159. package/dist/narration/voice-args.d.ts.map +1 -0
  160. package/dist/narration/voice-args.js +10 -0
  161. package/dist/narration/voice-args.js.map +1 -0
  162. package/dist/programs/media-duration.d.ts +12 -0
  163. package/dist/programs/media-duration.d.ts.map +1 -1
  164. package/dist/programs/media-duration.js +13 -0
  165. package/dist/programs/media-duration.js.map +1 -1
  166. package/dist/programs/run-program.d.ts +1 -0
  167. package/dist/programs/run-program.d.ts.map +1 -1
  168. package/dist/programs/run-program.js +1 -0
  169. package/dist/programs/run-program.js.map +1 -1
  170. package/dist/record/address.d.ts +1 -1
  171. package/dist/record/address.d.ts.map +1 -1
  172. package/dist/record/address.js +5 -7
  173. package/dist/record/address.js.map +1 -1
  174. package/dist/record/cursor-script.d.ts +2 -1
  175. package/dist/record/cursor-script.d.ts.map +1 -1
  176. package/dist/record/cursor-script.js +52 -14
  177. package/dist/record/cursor-script.js.map +1 -1
  178. package/dist/record/encoder-args.js +1 -1
  179. package/dist/record/motion.d.ts +19 -0
  180. package/dist/record/motion.d.ts.map +1 -0
  181. package/dist/record/motion.js +59 -0
  182. package/dist/record/motion.js.map +1 -0
  183. package/dist/record/narration-cues.d.ts +14 -0
  184. package/dist/record/narration-cues.d.ts.map +1 -0
  185. package/dist/record/narration-cues.js +12 -0
  186. package/dist/record/narration-cues.js.map +1 -0
  187. package/dist/record/page-clock.d.ts +7 -0
  188. package/dist/record/page-clock.d.ts.map +1 -0
  189. package/dist/record/page-clock.js +13 -0
  190. package/dist/record/page-clock.js.map +1 -0
  191. package/dist/record/page-ready.d.ts +4 -0
  192. package/dist/record/page-ready.d.ts.map +1 -0
  193. package/dist/record/page-ready.js +15 -0
  194. package/dist/record/page-ready.js.map +1 -0
  195. package/dist/record/page-route.d.ts +13 -0
  196. package/dist/record/page-route.d.ts.map +1 -0
  197. package/dist/record/page-route.js +64 -0
  198. package/dist/record/page-route.js.map +1 -0
  199. package/dist/record/pointer.d.ts +6 -7
  200. package/dist/record/pointer.d.ts.map +1 -1
  201. package/dist/record/pointer.js +11 -10
  202. package/dist/record/pointer.js.map +1 -1
  203. package/dist/record/record-source.d.ts +16 -0
  204. package/dist/record/record-source.d.ts.map +1 -0
  205. package/dist/record/record-source.js +11 -0
  206. package/dist/record/record-source.js.map +1 -0
  207. package/dist/record/recorder.d.ts +19 -7
  208. package/dist/record/recorder.d.ts.map +1 -1
  209. package/dist/record/recorder.js +52 -18
  210. package/dist/record/recorder.js.map +1 -1
  211. package/dist/record/register-record.d.ts.map +1 -1
  212. package/dist/record/register-record.js +45 -13
  213. package/dist/record/register-record.js.map +1 -1
  214. package/dist/record/run-step.d.ts +16 -3
  215. package/dist/record/run-step.d.ts.map +1 -1
  216. package/dist/record/run-step.js +43 -23
  217. package/dist/record/run-step.js.map +1 -1
  218. package/dist/record/screencast.js +1 -1
  219. package/dist/record/steps.d.ts +56 -1
  220. package/dist/record/steps.d.ts.map +1 -1
  221. package/dist/record/steps.js +36 -18
  222. package/dist/record/steps.js.map +1 -1
  223. package/dist/review/analysis.d.ts +19 -0
  224. package/dist/review/analysis.d.ts.map +1 -0
  225. package/dist/review/analysis.js +36 -0
  226. package/dist/review/analysis.js.map +1 -0
  227. package/dist/review/frames.d.ts +9 -0
  228. package/dist/review/frames.d.ts.map +1 -0
  229. package/dist/review/frames.js +28 -0
  230. package/dist/review/frames.js.map +1 -0
  231. package/dist/review/register-review.d.ts +24 -0
  232. package/dist/review/register-review.d.ts.map +1 -0
  233. package/dist/review/register-review.js +81 -0
  234. package/dist/review/register-review.js.map +1 -0
  235. package/dist/review/sheet-args.d.ts +11 -0
  236. package/dist/review/sheet-args.d.ts.map +1 -0
  237. package/dist/review/sheet-args.js +24 -0
  238. package/dist/review/sheet-args.js.map +1 -0
  239. package/dist/settings.d.ts +4 -0
  240. package/dist/settings.d.ts.map +1 -1
  241. package/dist/settings.js +4 -2
  242. package/dist/settings.js.map +1 -1
  243. package/dist/setup/ffmpeg-readiness.js +1 -1
  244. package/dist/setup/ffmpeg-readiness.js.map +1 -1
  245. package/dist/ui.d.ts.map +1 -1
  246. package/dist/ui.js +3 -2
  247. package/dist/ui.js.map +1 -1
  248. package/dist/web/components/player.css +1 -1
  249. package/dist/web/components/player.js +33 -21
  250. package/docs/guide.md +39 -10
  251. package/locales/ar.json +12 -1
  252. package/locales/en.json +12 -1
  253. package/package.json +11 -5
  254. package/sounds/SOURCES.md +13 -0
  255. package/sounds/click.wav +0 -0
  256. package/sounds/keys.wav +0 -0
  257. package/sounds/pop.wav +0 -0
  258. package/sounds/whoosh.wav +0 -0
@@ -1,11 +1,15 @@
1
- // A narration's captions (plan 16 §16.6, ADR 0107, 7): lines of whole words, each shown for the share of the
2
- // narration's length that its characters have, one after another from 0 with no gap.
3
1
  const maximumLine = 42;
4
2
  const length = (text) => [...text].length;
5
- function sentencesOf(line) {
6
- return line.split(/(?<=[.!?؟…])\s+/u).filter((sentence) => sentence !== '');
3
+ /** The text's sentences, in order: cut at line breaks and after `.`, `!`, `?`, `؟`, and `…` followed by a space. */
4
+ export function sentencesOf(text) {
5
+ return text
6
+ .split(/\r?\n/u)
7
+ .map((line) => line.replace(/\s+/gu, ' ').trim())
8
+ .flatMap((line) => line.split(/(?<=[.!?؟…])\s+/u))
9
+ .filter((sentence) => sentence !== '');
7
10
  }
8
- function linesOf(sentence) {
11
+ /** A sentence's caption lines: whole words, each line as long as it can be up to 42 characters. */
12
+ export function linesOf(sentence) {
9
13
  const lines = [];
10
14
  let current = '';
11
15
  for (const word of sentence.split(' ')) {
@@ -22,21 +26,45 @@ function linesOf(sentence) {
22
26
  lines.push(current);
23
27
  return lines;
24
28
  }
25
- /** The caption lines of a text, in order. */
26
- export function captionLines(text) {
27
- const textLines = text.split(/\r?\n/u).map((line) => line.replace(/\s+/gu, ' ').trim());
28
- return textLines.flatMap(sentencesOf).flatMap(linesOf);
29
+ // Each line's start, in characters of the sentence, and its words with their starts and ends.
30
+ function lineSpans(sentence) {
31
+ const characters = [...sentence];
32
+ let at = 0;
33
+ return linesOf(sentence).map((line) => {
34
+ while (characters[at] === ' ')
35
+ at += 1;
36
+ const from = at;
37
+ const words = [];
38
+ for (const word of line.split(' ')) {
39
+ words.push({ text: word, from: at, to: at + length(word) - 1 });
40
+ at += length(word) + 1;
41
+ }
42
+ at -= 1;
43
+ return { text: line, from, words };
44
+ });
29
45
  }
30
- /** The text's captions over a narration of `durationMs`. */
31
- export function timedCaptions(text, durationMs) {
32
- const lines = captionLines(text);
46
+ function proportional(sentence) {
47
+ const lines = linesOf(sentence.text);
33
48
  const total = lines.reduce((sum, line) => sum + length(line), 0);
34
49
  let before = 0;
35
- return lines.map((line, index) => {
36
- const startMs = Math.floor((durationMs * before) / total);
50
+ return lines.map((line) => {
51
+ const startMs = sentence.startMs + Math.floor((sentence.durationMs * before) / total);
37
52
  before += length(line);
38
- const endMs = index === lines.length - 1 ? durationMs : Math.floor((durationMs * before) / total);
39
- return { text: line, startMs, endMs };
53
+ return { text: line, startMs, endMs: 0 };
40
54
  });
41
55
  }
56
+ function aligned(sentence, alignment) {
57
+ const at = (index, edge) => sentence.startMs + Math.floor(alignment[edge][sentence.spoken.spokenIndex(index, edge === 'startsMs' ? 'first' : 'last')] ?? 0);
58
+ return lineSpans(sentence.text).map((line) => ({
59
+ text: line.text,
60
+ startMs: at(line.from, 'startsMs'),
61
+ endMs: 0,
62
+ words: line.words.map((word) => ({ text: word.text, startMs: at(word.from, 'startsMs'), endMs: at(word.to, 'endsMs') })),
63
+ }));
64
+ }
65
+ /** The captions of placed sentences over a narration of `durationMs`: each line ends where the next starts. */
66
+ export function captionsOf(sentences, durationMs) {
67
+ const lines = sentences.flatMap((sentence) => (sentence.alignment === null ? proportional(sentence) : aligned(sentence, sentence.alignment)));
68
+ return lines.map((line, index) => ({ ...line, endMs: index === lines.length - 1 ? durationMs : (lines[index + 1]?.startMs ?? durationMs) }));
69
+ }
42
70
  //# sourceMappingURL=captions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"captions.js","sourceRoot":"","sources":["../../src/narration/captions.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAC7G,qFAAqF;AAIrF,MAAM,WAAW,GAAG,EAAE,CAAC;AAEvB,MAAM,MAAM,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC;AAE1D,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,OAAO,CAAC,QAAgB;IAC/B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,IAAI,EAAE,CAAC;QAC5D,IAAI,OAAO,KAAK,EAAE,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,WAAW,EAAE,CAAC;YACnD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACpB,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,MAAM,CAAC;QACnB,CAAC;IACH,CAAC;IACD,IAAI,OAAO,KAAK,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACxC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACxF,OAAO,SAAS,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AACzD,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,UAAkB;IAC5D,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACjE,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,UAAU,GAAG,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC;QAC1D,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC;QACvB,MAAM,KAAK,GAAG,KAAK,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,UAAU,GAAG,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC;QAClG,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACxC,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"captions.js","sourceRoot":"","sources":["../../src/narration/captions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare const sentenceGapMs = 250;
2
+ export declare function joinNarrationArgs(inputs: readonly string[], output: string): string[];
3
+ //# sourceMappingURL=join-args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"join-args.d.ts","sourceRoot":"","sources":["../../src/narration/join-args.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAcrF"}
@@ -0,0 +1,19 @@
1
+ // ffmpeg's arguments to join a narration's sentences (plan 16 §16.6 and §16.7; ADR 0115, 41): each brought to the
2
+ // same sound, 250 ms of silence after every one but the last, joined in order and encoded as MP3 at 192 kb/s.
3
+ export const sentenceGapMs = 250;
4
+ export function joinNarrationArgs(inputs, output) {
5
+ const shaped = inputs.map((_, index) => {
6
+ const pad = index === inputs.length - 1 ? '' : `,apad=pad_dur=${String(sentenceGapMs / 1000)}`;
7
+ return `[${String(index)}:a]aformat=sample_rates=44100:channel_layouts=mono${pad}[s${String(index)}]`;
8
+ });
9
+ const joined = `${inputs.map((_, index) => `[s${String(index)}]`).join('')}concat=n=${String(inputs.length)}:v=0:a=1[out]`;
10
+ return [
11
+ '-hide_banner', '-loglevel', 'error',
12
+ ...inputs.flatMap((input) => ['-i', input]),
13
+ '-filter_complex', [...shaped, joined].join(';'),
14
+ '-map', '[out]',
15
+ '-codec:a', 'libmp3lame', '-b:a', '192k',
16
+ '-y', output,
17
+ ];
18
+ }
19
+ //# sourceMappingURL=join-args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"join-args.js","sourceRoot":"","sources":["../../src/narration/join-args.ts"],"names":[],"mappings":"AAAA,kHAAkH;AAClH,8GAA8G;AAE9G,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC;AAEjC,MAAM,UAAU,iBAAiB,CAAC,MAAyB,EAAE,MAAc;IACzE,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,KAAK,KAAK,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,iBAAiB,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC,EAAE,CAAC;QAC/F,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,qDAAqD,GAAG,KAAK,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;IACxG,CAAC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC;IAC3H,OAAO;QACL,cAAc,EAAE,WAAW,EAAE,OAAO;QACpC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC3C,iBAAiB,EAAE,CAAC,GAAG,MAAM,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAChD,MAAM,EAAE,OAAO;QACf,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM;QACxC,IAAI,EAAE,MAAM;KACb,CAAC;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { type Collection, type Ctx, type Files, type JsonObject } from '@kvman/sdk';
2
+ import { type Alignment } from './alignment.ts';
3
+ /** What makes two sentences sound the same. */
4
+ export type SentenceKey = {
5
+ service: string;
6
+ voice: string | null;
7
+ model: string | null;
8
+ tone: string | null;
9
+ speed: number;
10
+ spoken: string;
11
+ };
12
+ export type CachedSentence = {
13
+ audio: Buffer;
14
+ type: string;
15
+ durationMs: number;
16
+ alignment: Alignment | null;
17
+ };
18
+ export declare function sentenceKey(key: SentenceKey): string;
19
+ export declare function cachedSentence(ctx: Ctx, key: string): Promise<CachedSentence | undefined>;
20
+ export declare function keepSentence(ctx: Ctx, key: string, sentence: CachedSentence): Promise<void>;
21
+ /** Deletes the oldest rows past `limit`, with their files. */
22
+ export declare function trimOldest<Row extends JsonObject & {
23
+ fileId: string;
24
+ }>(collection: Pick<Collection<Row>, 'count' | 'find' | 'delete'>, files: Pick<Files, 'unlink'>, limit: number): Promise<void>;
25
+ //# sourceMappingURL=narration-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"narration-cache.d.ts","sourceRoot":"","sources":["../../src/narration/narration-cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAK,KAAK,UAAU,EAAE,KAAK,GAAG,EAAE,KAAK,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AACvF,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAUjE,+CAA+C;AAC/C,MAAM,MAAM,WAAW,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9I,MAAM,MAAM,cAAc,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAA;CAAE,CAAC;AAE9G,wBAAgB,WAAW,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAEpD;AAID,wBAAsB,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAI/F;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAMjG;AAED,8DAA8D;AAC9D,wBAAsB,UAAU,CAAC,GAAG,SAAS,UAAU,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOxM"}
@@ -0,0 +1,36 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { z } from '@kvman/sdk';
3
+ import { alignmentSchema } from "./alignment.js";
4
+ // The narration cache (plan 16 §16.6; ADR 0115, 40): each spoken sentence's audio, kept in the kernel's files with a
5
+ // row in the workspace collection `narrations`, so an unchanged sentence is never paid for twice. Past 2000 rows the
6
+ // oldest go with their files; the collection's ids rise with time, so the oldest come first.
7
+ const kept = 2000;
8
+ const rowSchema = z.object({ key: z.string(), fileId: z.string(), type: z.string(), durationMs: z.number().int().nonnegative(), alignment: alignmentSchema.nullable(), createdAt: z.string() });
9
+ export function sentenceKey(key) {
10
+ return createHash('sha256').update(JSON.stringify([key.service, key.voice, key.model, key.tone, key.speed, key.spoken])).digest('hex');
11
+ }
12
+ const rows = (ctx) => ctx.store.collection('narrations', rowSchema);
13
+ export async function cachedSentence(ctx, key) {
14
+ const [row] = await rows(ctx).find({ key }, { limit: 1 });
15
+ if (row === undefined)
16
+ return undefined;
17
+ return { audio: await ctx.files.read(row.fileId), type: row.type, durationMs: row.durationMs, alignment: row.alignment };
18
+ }
19
+ export async function keepSentence(ctx, key, sentence) {
20
+ const extension = sentence.type === 'audio/wav' ? 'wav' : 'mp3';
21
+ const file = await ctx.files.write(`narration-${key.slice(0, 16)}.${extension}`, sentence.audio, sentence.type);
22
+ const collection = rows(ctx);
23
+ await collection.insert({ key, fileId: file.id, type: sentence.type, durationMs: sentence.durationMs, alignment: sentence.alignment, createdAt: new Date().toISOString() });
24
+ await trimOldest(collection, ctx.files, kept);
25
+ }
26
+ /** Deletes the oldest rows past `limit`, with their files. */
27
+ export async function trimOldest(collection, files, limit) {
28
+ const extra = (await collection.count({})) - limit;
29
+ if (extra <= 0)
30
+ return;
31
+ for (const old of await collection.find({}, { limit: extra, order: 'asc' })) {
32
+ await files.unlink(old.fileId);
33
+ await collection.delete(old.id);
34
+ }
35
+ }
36
+ //# sourceMappingURL=narration-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"narration-cache.js","sourceRoot":"","sources":["../../src/narration/narration-cache.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,4 @@
1
1
  import { type Program } from '../programs/run-program.ts';
2
- export type PiperPrograms = {
3
- piper: Program;
4
- ffmpeg: Program;
5
- };
6
- /** Narrates `text` with the voice into `<folder>/narration.mp3`, and answers that file. */
7
- export declare function piperNarration(programs: PiperPrograms, voice: string, text: string, folder: string, signal: AbortSignal): Promise<string>;
2
+ /** Speaks `sentence` with the voice into the WAV file `wav`. */
3
+ export declare function piperSentence(piper: Program, voice: string, sentence: string, wav: string, speed: number, signal: AbortSignal): Promise<void>;
8
4
  //# sourceMappingURL=piper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"piper.d.ts","sourceRoot":"","sources":["../../src/narration/piper.ts"],"names":[],"mappings":"AACA,OAAO,EAAyB,KAAK,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAKjF,MAAM,MAAM,aAAa,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAMhE,2FAA2F;AAC3F,wBAAsB,cAAc,CAAC,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ/I"}
1
+ {"version":3,"file":"piper.d.ts","sourceRoot":"","sources":["../../src/narration/piper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAMjF,gEAAgE;AAChE,wBAAsB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAMnJ"}
@@ -1,19 +1,13 @@
1
- import path from 'node:path';
2
1
  import { lastLines, runProgram } from "../programs/run-program.js";
3
2
  import { kvvideoProblem } from "../problems.js";
4
- function failed(reason) {
5
- return kvvideoProblem('NARRATION_FAILED', `piper couldn't narrate: ${reason}`, { service: 'piper', reason });
6
- }
7
- /** Narrates `text` with the voice into `<folder>/narration.mp3`, and answers that file. */
8
- export async function piperNarration(programs, voice, text, folder, signal) {
9
- const wav = path.join(folder, 'narration.wav');
10
- const mp3 = path.join(folder, 'narration.mp3');
11
- const spoken = await runProgram(programs.piper, ['-m', voice, '-f', wav, '--', text], { signal });
12
- if (spoken.exitCode !== 0)
13
- throw failed(lastLines(spoken.output));
14
- const encoded = await runProgram(programs.ffmpeg, ['-hide_banner', '-loglevel', 'error', '-i', wav, '-codec:a', 'libmp3lame', '-b:a', '128k', '-y', mp3], { signal });
15
- if (encoded.exitCode !== 0)
16
- throw failed(lastLines(encoded.output));
17
- return mp3;
3
+ // Piper on this computer (plan 16 §16.6; ADR 0107, 22): it speaks one sentence into a WAV, slower or faster by its
4
+ // length scale, the inverse of the speed.
5
+ /** Speaks `sentence` with the voice into the WAV file `wav`. */
6
+ export async function piperSentence(piper, voice, sentence, wav, speed, signal) {
7
+ const spoken = await runProgram(piper, ['-m', voice, '-f', wav, '--length-scale', String(Math.round((1 / speed) * 1000) / 1000), '--', sentence], { signal });
8
+ if (spoken.exitCode !== 0) {
9
+ const reason = lastLines(spoken.output);
10
+ throw kvvideoProblem('NARRATION_FAILED', `piper couldn't narrate: ${reason}`, { service: 'piper', reason });
11
+ }
18
12
  }
19
13
  //# sourceMappingURL=piper.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"piper.js","sourceRoot":"","sources":["../../src/narration/piper.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAgB,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAMhD,SAAS,MAAM,CAAC,MAAc;IAC5B,OAAO,cAAc,CAAC,kBAAkB,EAAE,2BAA2B,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;AAC/G,CAAC;AAED,2FAA2F;AAC3F,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAuB,EAAE,KAAa,EAAE,IAAY,EAAE,MAAc,EAAE,MAAmB;IAC5H,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAClG,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC;QAAE,MAAM,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAClE,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,cAAc,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACtK,IAAI,OAAO,CAAC,QAAQ,KAAK,CAAC;QAAE,MAAM,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IACpE,OAAO,GAAG,CAAC;AACb,CAAC"}
1
+ {"version":3,"file":"piper.js","sourceRoot":"","sources":["../../src/narration/piper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAgB,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,mHAAmH;AACnH,0CAA0C;AAE1C,gEAAgE;AAChE,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAc,EAAE,KAAa,EAAE,QAAgB,EAAE,GAAW,EAAE,KAAa,EAAE,MAAmB;IAClI,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,gBAAgB,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9J,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxC,MAAM,cAAc,CAAC,kBAAkB,EAAE,2BAA2B,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9G,CAAC;AACH,CAAC"}
@@ -0,0 +1,11 @@
1
+ export type Pronunciation = {
2
+ word: string;
3
+ say: string;
4
+ };
5
+ /** A sentence as spoken, and where each of its characters (by code point) is in it. */
6
+ export type Spoken = {
7
+ text: string;
8
+ spokenIndex(index: number, edge: 'first' | 'last'): number;
9
+ };
10
+ export declare function spokenOf(sentence: string, pronunciations: readonly Pronunciation[]): Spoken;
11
+ //# sourceMappingURL=pronunciations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pronunciations.d.ts","sourceRoot":"","sources":["../../src/narration/pronunciations.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D,uFAAuF;AACvF,MAAM,MAAM,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC;AAUlG,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,SAAS,aAAa,EAAE,GAAG,MAAM,CA2B3F"}
@@ -0,0 +1,40 @@
1
+ // The pronunciation list (plan 16 §16.2; ADR 0115, 15): each word is replaced by what the voice should say, as a whole
2
+ // word, ignoring case. The captions keep the text as written, so each character of the sentence keeps where it is in
3
+ // what is spoken: a replaced word's characters span the whole replacement.
4
+ const escaped = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
5
+ function matcher(pronunciations) {
6
+ if (pronunciations.length === 0)
7
+ return undefined;
8
+ const words = [...pronunciations].map((entry) => entry.word).sort((a, b) => b.length - a.length);
9
+ return new RegExp(`(?<![\\p{L}\\p{N}_])(?:${words.map(escaped).join('|')})(?![\\p{L}\\p{N}_])`, 'giu');
10
+ }
11
+ export function spokenOf(sentence, pronunciations) {
12
+ const says = new Map(pronunciations.map((entry) => [entry.word.toLowerCase(), entry.say]));
13
+ const spans = [];
14
+ let spoken = '';
15
+ let spokenLength = 0;
16
+ let from = 0;
17
+ const keep = (text) => {
18
+ for (const character of text) {
19
+ spans.push([spokenLength, spokenLength]);
20
+ spoken += character;
21
+ spokenLength += 1;
22
+ }
23
+ };
24
+ const pattern = matcher(pronunciations);
25
+ const matches = pattern === undefined ? [] : [...sentence.matchAll(pattern)];
26
+ for (const match of matches) {
27
+ keep(sentence.slice(from, match.index));
28
+ const say = says.get(match[0].toLowerCase()) ?? match[0];
29
+ const sayLength = [...say].length;
30
+ const replaced = [...match[0]].length;
31
+ for (let index = 0; index < replaced; index += 1)
32
+ spans.push([spokenLength, spokenLength + sayLength - 1]);
33
+ spoken += say;
34
+ spokenLength += sayLength;
35
+ from = match.index + match[0].length;
36
+ }
37
+ keep(sentence.slice(from));
38
+ return { text: spoken, spokenIndex: (index, edge) => spans[index]?.[edge === 'first' ? 0 : 1] ?? 0 };
39
+ }
40
+ //# sourceMappingURL=pronunciations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pronunciations.js","sourceRoot":"","sources":["../../src/narration/pronunciations.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,8 @@ import { z, type Ctx } from '@kvman/sdk';
2
2
  export declare const narrateInputSchema: z.ZodObject<{
3
3
  text: z.ZodString;
4
4
  file: z.ZodString;
5
+ tone: z.ZodExactOptional<z.ZodString>;
6
+ speed: z.ZodExactOptional<z.ZodNumber>;
5
7
  replace: z.ZodExactOptional<z.ZodBoolean>;
6
8
  }, z.core.$strict>;
7
9
  export declare const narrateOutputSchema: z.ZodObject<{
@@ -11,7 +13,13 @@ export declare const narrateOutputSchema: z.ZodObject<{
11
13
  text: z.ZodString;
12
14
  startMs: z.ZodNumber;
13
15
  endMs: z.ZodNumber;
16
+ words: z.ZodExactOptional<z.ZodArray<z.ZodObject<{
17
+ text: z.ZodString;
18
+ startMs: z.ZodNumber;
19
+ endMs: z.ZodNumber;
20
+ }, z.core.$strip>>>;
14
21
  }, z.core.$strip>>;
22
+ reused: z.ZodNumber;
15
23
  }, z.core.$strip>;
16
24
  export declare function registerNarration(ctx: Ctx): void;
17
25
  //# sourceMappingURL=register-narration.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"register-narration.d.ts","sourceRoot":"","sources":["../../src/narration/register-narration.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,KAAK,GAAG,EAAE,MAAM,YAAY,CAAC;AAgBzC,eAAO,MAAM,kBAAkB;;;;kBAI7B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;iBAI9B,CAAC;AAuBH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAwBhD"}
1
+ {"version":3,"file":"register-narration.d.ts","sourceRoot":"","sources":["../../src/narration/register-narration.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,KAAK,GAAG,EAAE,MAAM,YAAY,CAAC;AAoBzC,eAAO,MAAM,kBAAkB;;;;;;kBAM7B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;iBAA8J,CAAC;AAuB/L,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAgDhD"}
@@ -1,70 +1,98 @@
1
- import { writeFileSync } from 'node:fs';
2
1
  import path from 'node:path';
3
2
  import { z } from '@kvman/sdk';
4
3
  import { answerPath } from "../files/answer-path.js";
5
4
  import { placeOutput, workFolder } from "../files/place-output.js";
5
+ import { sidecarCaptionSchema, sidecarOutputPath, writeSidecar } from "../files/sidecar.js";
6
6
  import { outputPath } from "../files/workspace-paths.js";
7
7
  import { mediaDuration } from "../programs/media-duration.js";
8
8
  import { programOf } from "../programs/programs.js";
9
- import { kvvideoProblem } from "../problems.js";
9
+ import { lastLines, runProgram } from "../programs/run-program.js";
10
+ import { invalid, kvvideoProblem } from "../problems.js";
10
11
  import { keySecrets } from "../setup/narration-readiness.js";
11
- import { timedCaptions } from "./captions.js";
12
- import { piperNarration } from "./piper.js";
13
- import { speak, speechRequest } from "./speech-services.js";
14
- // `video narrate` (plan 16 §16.6): the chosen service speaks the text into an MP3 in the workspace, and the answer has
15
- // its length and its captions. The output is checked before anything runs or is paid for.
12
+ import { captionsOf, sentencesOf } from "./captions.js";
13
+ import { joinNarrationArgs, sentenceGapMs } from "./join-args.js";
14
+ import { spokenOf } from "./pronunciations.js";
15
+ import { saySentence } from "./sentence-speaker.js";
16
+ // `video narrate` (plan 16 §16.6; ADR 0115, 15, 17, 39 to 41): the text is said sentence by sentence, each from the
17
+ // cache or the chosen service, then joined with 250 ms between them into an MP3 in the workspace, with its captions
18
+ // in a sidecar beside it. Everything that can fail before a service is paid for is checked first.
16
19
  export const narrateInputSchema = z.strictObject({
17
20
  text: z.string().min(1).max(4000).describe('What the narrator says: one scene, a few sentences.'),
18
21
  file: z.string().min(1).max(1000).describe('The .mp3 file to write, in the workspace.'),
22
+ tone: z.string().min(1).max(300).describe('How it is said, as "warm and upbeat"; OpenAI only.').exactOptional(),
23
+ speed: z.number().min(0.7).max(1.2).describe('How fast it is said: 0.7 to 1.2, 1 by default.').exactOptional(),
19
24
  replace: z.boolean().describe('Write over an existing file.').exactOptional(),
20
25
  });
21
- export const narrateOutputSchema = z.object({
22
- file: z.string(),
23
- durationMs: z.number().int().nonnegative(),
24
- captions: z.array(z.object({ text: z.string(), startMs: z.number().int(), endMs: z.number().int() })),
25
- });
26
+ export const narrateOutputSchema = z.object({ file: z.string(), durationMs: z.number().int().nonnegative(), captions: z.array(sidecarCaptionSchema), reused: z.number().int().nonnegative() });
26
27
  async function chosenService(ctx) {
27
28
  const service = await ctx.settings.get('kvvideo.narration.service');
28
29
  if (service === null)
29
30
  throw kvvideoProblem('NARRATION_NOT_SET', 'No narration service is chosen; set kvvideo.narration.service.', { setting: 'kvvideo.narration.service' });
30
31
  return service;
31
32
  }
32
- async function narrateInto(ctx, service, text, folder) {
33
+ async function voiceOf(ctx, input) {
34
+ const service = await chosenService(ctx);
33
35
  const choice = { voice: await ctx.settings.get('kvvideo.narration.voice'), model: await ctx.settings.get('kvvideo.narration.model') };
36
+ const shared = { service, choice, tone: input.tone ?? null, speed: input.speed ?? 1, fetch, signal: ctx.job.signal };
37
+ const ffprobe = await programOf(ctx, 'ffprobe');
34
38
  if (service === 'piper') {
35
39
  if (choice.voice === null)
36
40
  throw kvvideoProblem('NARRATION_NOT_SET', "Piper has no voice; set kvvideo.narration.voice to a voice's .onnx file.", { setting: 'kvvideo.narration.voice' });
37
- return piperNarration({ piper: await programOf(ctx, 'piper'), ffmpeg: await programOf(ctx, 'ffmpeg') }, choice.voice, text, folder, ctx.job.signal);
41
+ return { ...shared, key: null, programs: { ffprobe, piper: await programOf(ctx, 'piper') } };
38
42
  }
39
43
  const secret = keySecrets[service];
40
44
  const key = await ctx.secrets.get(secret);
41
45
  if (key === undefined)
42
46
  throw kvvideoProblem('NARRATION_KEY_MISSING', `The ${service} key is missing; add the secret ${secret} on kvvideo's extension page.`, { service, secret });
43
- const audio = await speak(service, speechRequest(service, text, choice, key), key, fetch, ctx.job.signal);
44
- const file = path.join(folder, 'narration.mp3');
45
- writeFileSync(file, audio);
46
- return file;
47
+ return { ...shared, key, programs: { ffprobe, piper: null } };
47
48
  }
48
49
  export function registerNarration(ctx) {
49
50
  ctx.registerCommand('kvvideo.narration.make', {
50
- description: 'Narrates text with the chosen text-to-speech service into an MP3 file in the workspace, with its length and captions.',
51
+ description: 'Narrates text sentence by sentence with the chosen text-to-speech service into an MP3 file in the workspace, with its length and captions.',
51
52
  input: narrateInputSchema,
52
53
  output: narrateOutputSchema,
53
54
  public: true,
54
55
  retries: 0,
55
- timeoutMs: 120_000,
56
+ timeoutMs: 300_000,
56
57
  handle: async (input) => {
57
58
  const root = ctx.job.workspace.path;
58
- const output = outputPath(root, input.file, '.mp3', input.replace === true);
59
- const service = await chosenService(ctx);
59
+ const replace = input.replace === true;
60
+ const output = outputPath(root, input.file, '.mp3', replace);
61
+ const sidecar = sidecarOutputPath(output, replace, input.file);
62
+ const voice = await voiceOf(ctx, input);
63
+ const ffmpeg = await programOf(ctx, 'ffmpeg');
64
+ const pronunciations = await ctx.settings.get('kvvideo.narration.pronunciations');
65
+ const sentences = sentencesOf(input.text).map((text) => ({ text, spoken: spokenOf(text, pronunciations) }));
66
+ if (sentences.length === 0)
67
+ throw invalid('The text has nothing to say.', { text: input.text });
60
68
  const work = workFolder();
61
69
  try {
62
- const made = await narrateInto(ctx, service, input.text, work.folder);
63
- const duration = await mediaDuration(await programOf(ctx, 'ffprobe'), made, ctx.job.signal);
70
+ const placed = [];
71
+ const files = [];
72
+ let startMs = 0;
73
+ let reused = 0;
74
+ for (const [index, sentence] of sentences.entries()) {
75
+ ctx.job.progress({ key: 'kvvideo.progress.sentence', params: { n: index + 1, of: sentences.length } });
76
+ const text = { spoken: sentence.spoken.text, previous: sentences[index - 1]?.spoken.text ?? null, next: sentences[index + 1]?.spoken.text ?? null };
77
+ const said = await saySentence(ctx, voice, text, work.folder, index + 1);
78
+ if (said.reused)
79
+ reused += 1;
80
+ files.push(said.file);
81
+ placed.push({ text: sentence.text, spoken: sentence.spoken, startMs, durationMs: said.durationMs, alignment: said.alignment });
82
+ startMs += said.durationMs + sentenceGapMs;
83
+ }
84
+ const made = path.join(work.folder, 'narration.mp3');
85
+ const joined = await runProgram(ffmpeg, joinNarrationArgs(files, made), { signal: ctx.job.signal });
86
+ if (joined.exitCode !== 0)
87
+ throw kvvideoProblem('NARRATION_FAILED', `ffmpeg couldn't join the narration: ${lastLines(joined.output)}`, { service: voice.service, reason: lastLines(joined.output) });
88
+ const duration = await mediaDuration(voice.programs.ffprobe, made, ctx.job.signal);
64
89
  if (!duration.ok)
65
- throw kvvideoProblem('NARRATION_FAILED', `ffprobe couldn't read the narration: ${duration.reason}`, { service, reason: duration.reason });
90
+ throw kvvideoProblem('NARRATION_FAILED', `ffprobe couldn't read the narration: ${duration.reason}`, { service: voice.service, reason: duration.reason });
91
+ const captions = captionsOf(placed, duration.ms);
92
+ const madeSidecar = writeSidecar(work.folder, { kind: 'narration', durationMs: duration.ms, captions });
66
93
  placeOutput(made, output);
67
- return { file: answerPath(root, output), durationMs: duration.ms, captions: timedCaptions(input.text, duration.ms) };
94
+ placeOutput(madeSidecar, sidecar);
95
+ return { file: answerPath(root, output), durationMs: duration.ms, captions, reused };
68
96
  }
69
97
  finally {
70
98
  work.remove();
@@ -1 +1 @@
1
- {"version":3,"file":"register-narration.js","sourceRoot":"","sources":["../../src/narration/register-narration.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"register-narration.js","sourceRoot":"","sources":["../../src/narration/register-narration.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { z, type Ctx } from '@kvman/sdk';
2
+ export declare const takeExtensions: string[];
3
+ export declare const voiceInputSchema: z.ZodObject<{
4
+ lines: z.ZodArray<z.ZodObject<{
5
+ take: z.ZodString;
6
+ text: z.ZodString;
7
+ }, z.core.$strict>>;
8
+ file: z.ZodString;
9
+ clean: z.ZodExactOptional<z.ZodBoolean>;
10
+ replace: z.ZodExactOptional<z.ZodBoolean>;
11
+ }, z.core.$strict>;
12
+ export declare const voiceOutputSchema: z.ZodObject<{
13
+ file: z.ZodString;
14
+ durationMs: z.ZodNumber;
15
+ captions: z.ZodArray<z.ZodObject<{
16
+ text: z.ZodString;
17
+ startMs: z.ZodNumber;
18
+ endMs: z.ZodNumber;
19
+ words: z.ZodExactOptional<z.ZodArray<z.ZodObject<{
20
+ text: z.ZodString;
21
+ startMs: z.ZodNumber;
22
+ endMs: z.ZodNumber;
23
+ }, z.core.$strip>>>;
24
+ }, z.core.$strip>>;
25
+ }, z.core.$strip>;
26
+ export declare function registerVoice(ctx: Ctx): void;
27
+ //# sourceMappingURL=register-voice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-voice.d.ts","sourceRoot":"","sources":["../../src/narration/register-voice.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,KAAK,GAAG,EAAE,MAAM,YAAY,CAAC;AAkBzC,eAAO,MAAM,cAAc,UAAoD,CAAC;AAEhF,eAAO,MAAM,gBAAgB;;;;;;;;kBAK3B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;iBAAsH,CAAC;AAMrJ,wBAAgB,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CA+C5C"}