@kvman/kvvideo 0.0.0-stage → 0.1.1

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 (345) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/api.d.ts +105 -0
  4. package/dist/api.d.ts.map +1 -0
  5. package/dist/api.js +2 -0
  6. package/dist/api.js.map +1 -0
  7. package/dist/brand/brand.d.ts +17 -0
  8. package/dist/brand/brand.d.ts.map +1 -0
  9. package/dist/brand/brand.js +44 -0
  10. package/dist/brand/brand.js.map +1 -0
  11. package/dist/browser/channels.d.ts +6 -0
  12. package/dist/browser/channels.d.ts.map +1 -0
  13. package/dist/browser/channels.js +5 -0
  14. package/dist/browser/channels.js.map +1 -0
  15. package/dist/browser/launch.d.ts +6 -0
  16. package/dist/browser/launch.d.ts.map +1 -0
  17. package/dist/browser/launch.js +14 -0
  18. package/dist/browser/launch.js.map +1 -0
  19. package/dist/browser/start-browser.d.ts +5 -0
  20. package/dist/browser/start-browser.d.ts.map +1 -0
  21. package/dist/browser/start-browser.js +17 -0
  22. package/dist/browser/start-browser.js.map +1 -0
  23. package/dist/compose/ass-file.d.ts +17 -0
  24. package/dist/compose/ass-file.d.ts.map +1 -0
  25. package/dist/compose/ass-file.js +65 -0
  26. package/dist/compose/ass-file.js.map +1 -0
  27. package/dist/compose/background.d.ts +23 -0
  28. package/dist/compose/background.d.ts.map +1 -0
  29. package/dist/compose/background.js +31 -0
  30. package/dist/compose/background.js.map +1 -0
  31. package/dist/compose/camera.d.ts +32 -0
  32. package/dist/compose/camera.d.ts.map +1 -0
  33. package/dist/compose/camera.js +98 -0
  34. package/dist/compose/camera.js.map +1 -0
  35. package/dist/compose/caption-font.d.ts +8 -0
  36. package/dist/compose/caption-font.d.ts.map +1 -0
  37. package/dist/compose/caption-font.js +24 -0
  38. package/dist/compose/caption-font.js.map +1 -0
  39. package/dist/compose/compose-drawing.d.ts +19 -0
  40. package/dist/compose/compose-drawing.d.ts.map +1 -0
  41. package/dist/compose/compose-drawing.js +38 -0
  42. package/dist/compose/compose-drawing.js.map +1 -0
  43. package/dist/compose/compose-input.d.ts +187 -0
  44. package/dist/compose/compose-input.d.ts.map +1 -0
  45. package/dist/compose/compose-input.js +76 -0
  46. package/dist/compose/compose-input.js.map +1 -0
  47. package/dist/compose/compose-plan.d.ts +36 -0
  48. package/dist/compose/compose-plan.d.ts.map +1 -0
  49. package/dist/compose/compose-plan.js +55 -0
  50. package/dist/compose/compose-plan.js.map +1 -0
  51. package/dist/compose/compose-size.d.ts +50 -0
  52. package/dist/compose/compose-size.d.ts.map +1 -0
  53. package/dist/compose/compose-size.js +72 -0
  54. package/dist/compose/compose-size.js.map +1 -0
  55. package/dist/compose/drawn-images.d.ts +19 -0
  56. package/dist/compose/drawn-images.d.ts.map +1 -0
  57. package/dist/compose/drawn-images.js +75 -0
  58. package/dist/compose/drawn-images.js.map +1 -0
  59. package/dist/compose/final-pass.d.ts +34 -0
  60. package/dist/compose/final-pass.d.ts.map +1 -0
  61. package/dist/compose/final-pass.js +106 -0
  62. package/dist/compose/final-pass.js.map +1 -0
  63. package/dist/compose/frame-layout.d.ts +30 -0
  64. package/dist/compose/frame-layout.d.ts.map +1 -0
  65. package/dist/compose/frame-layout.js +33 -0
  66. package/dist/compose/frame-layout.js.map +1 -0
  67. package/dist/compose/overlays.d.ts +20 -0
  68. package/dist/compose/overlays.d.ts.map +1 -0
  69. package/dist/compose/overlays.js +30 -0
  70. package/dist/compose/overlays.js.map +1 -0
  71. package/dist/compose/page-again.d.ts +26 -0
  72. package/dist/compose/page-again.d.ts.map +1 -0
  73. package/dist/compose/page-again.js +50 -0
  74. package/dist/compose/page-again.js.map +1 -0
  75. package/dist/compose/player-copies.d.ts +10 -0
  76. package/dist/compose/player-copies.d.ts.map +1 -0
  77. package/dist/compose/player-copies.js +34 -0
  78. package/dist/compose/player-copies.js.map +1 -0
  79. package/dist/compose/register-compose.d.ts +3 -0
  80. package/dist/compose/register-compose.d.ts.map +1 -0
  81. package/dist/compose/register-compose.js +151 -0
  82. package/dist/compose/register-compose.js.map +1 -0
  83. package/dist/compose/render.d.ts +25 -0
  84. package/dist/compose/render.d.ts.map +1 -0
  85. package/dist/compose/render.js +61 -0
  86. package/dist/compose/render.js.map +1 -0
  87. package/dist/compose/scene-args.d.ts +37 -0
  88. package/dist/compose/scene-args.d.ts.map +1 -0
  89. package/dist/compose/scene-args.js +103 -0
  90. package/dist/compose/scene-args.js.map +1 -0
  91. package/dist/compose/scene-look.d.ts +31 -0
  92. package/dist/compose/scene-look.d.ts.map +1 -0
  93. package/dist/compose/scene-look.js +46 -0
  94. package/dist/compose/scene-look.js.map +1 -0
  95. package/dist/compose/scene-plan.d.ts +26 -0
  96. package/dist/compose/scene-plan.d.ts.map +1 -0
  97. package/dist/compose/scene-plan.js +34 -0
  98. package/dist/compose/scene-plan.js.map +1 -0
  99. package/dist/compose/sound-effects.d.ts +23 -0
  100. package/dist/compose/sound-effects.d.ts.map +1 -0
  101. package/dist/compose/sound-effects.js +40 -0
  102. package/dist/compose/sound-effects.js.map +1 -0
  103. package/dist/compose/srt.d.ts +3 -0
  104. package/dist/compose/srt.d.ts.map +1 -0
  105. package/dist/compose/srt.js +10 -0
  106. package/dist/compose/srt.js.map +1 -0
  107. package/dist/compose/transitions.d.ts +16 -0
  108. package/dist/compose/transitions.d.ts.map +1 -0
  109. package/dist/compose/transitions.js +38 -0
  110. package/dist/compose/transitions.js.map +1 -0
  111. package/dist/connector.d.ts +132 -0
  112. package/dist/connector.d.ts.map +1 -0
  113. package/dist/connector.js +63 -0
  114. package/dist/connector.js.map +1 -0
  115. package/dist/files/answer-path.d.ts +3 -0
  116. package/dist/files/answer-path.d.ts.map +1 -0
  117. package/dist/files/answer-path.js +6 -0
  118. package/dist/files/answer-path.js.map +1 -0
  119. package/dist/files/place-output.d.ts +8 -0
  120. package/dist/files/place-output.d.ts.map +1 -0
  121. package/dist/files/place-output.js +24 -0
  122. package/dist/files/place-output.js.map +1 -0
  123. package/dist/files/sidecar.d.ts +209 -0
  124. package/dist/files/sidecar.d.ts.map +1 -0
  125. package/dist/files/sidecar.js +66 -0
  126. package/dist/files/sidecar.js.map +1 -0
  127. package/dist/files/workspace-paths.d.ts +7 -0
  128. package/dist/files/workspace-paths.d.ts.map +1 -0
  129. package/dist/files/workspace-paths.js +48 -0
  130. package/dist/files/workspace-paths.js.map +1 -0
  131. package/dist/fonts/bundled-fonts.d.ts +10 -0
  132. package/dist/fonts/bundled-fonts.d.ts.map +1 -0
  133. package/dist/fonts/bundled-fonts.js +10 -0
  134. package/dist/fonts/bundled-fonts.js.map +1 -0
  135. package/dist/fonts/font-family.d.ts +2 -0
  136. package/dist/fonts/font-family.d.ts.map +1 -0
  137. package/dist/fonts/font-family.js +43 -0
  138. package/dist/fonts/font-family.js.map +1 -0
  139. package/dist/fonts/register-fonts.d.ts +3 -0
  140. package/dist/fonts/register-fonts.d.ts.map +1 -0
  141. package/dist/fonts/register-fonts.js +12 -0
  142. package/dist/fonts/register-fonts.js.map +1 -0
  143. package/dist/guide.d.ts +3 -0
  144. package/dist/guide.d.ts.map +1 -0
  145. package/dist/guide.js +6 -0
  146. package/dist/guide.js.map +1 -0
  147. package/dist/index.d.ts +5 -0
  148. package/dist/index.d.ts.map +1 -0
  149. package/dist/index.js +30 -0
  150. package/dist/index.js.map +1 -0
  151. package/dist/narration/alignment.d.ts +15 -0
  152. package/dist/narration/alignment.d.ts.map +1 -0
  153. package/dist/narration/alignment.js +21 -0
  154. package/dist/narration/alignment.js.map +1 -0
  155. package/dist/narration/captions.d.ts +28 -0
  156. package/dist/narration/captions.d.ts.map +1 -0
  157. package/dist/narration/captions.js +70 -0
  158. package/dist/narration/captions.js.map +1 -0
  159. package/dist/narration/join-args.d.ts +3 -0
  160. package/dist/narration/join-args.d.ts.map +1 -0
  161. package/dist/narration/join-args.js +19 -0
  162. package/dist/narration/join-args.js.map +1 -0
  163. package/dist/narration/narration-cache.d.ts +25 -0
  164. package/dist/narration/narration-cache.d.ts.map +1 -0
  165. package/dist/narration/narration-cache.js +36 -0
  166. package/dist/narration/narration-cache.js.map +1 -0
  167. package/dist/narration/piper.d.ts +4 -0
  168. package/dist/narration/piper.d.ts.map +1 -0
  169. package/dist/narration/piper.js +13 -0
  170. package/dist/narration/piper.js.map +1 -0
  171. package/dist/narration/pronunciations.d.ts +11 -0
  172. package/dist/narration/pronunciations.d.ts.map +1 -0
  173. package/dist/narration/pronunciations.js +40 -0
  174. package/dist/narration/pronunciations.js.map +1 -0
  175. package/dist/narration/register-narration.d.ts +25 -0
  176. package/dist/narration/register-narration.d.ts.map +1 -0
  177. package/dist/narration/register-narration.js +103 -0
  178. package/dist/narration/register-narration.js.map +1 -0
  179. package/dist/narration/register-voice.d.ts +27 -0
  180. package/dist/narration/register-voice.d.ts.map +1 -0
  181. package/dist/narration/register-voice.js +82 -0
  182. package/dist/narration/register-voice.js.map +1 -0
  183. package/dist/narration/sentence-speaker.d.ts +35 -0
  184. package/dist/narration/sentence-speaker.d.ts.map +1 -0
  185. package/dist/narration/sentence-speaker.js +44 -0
  186. package/dist/narration/sentence-speaker.js.map +1 -0
  187. package/dist/narration/speech-services.d.ts +39 -0
  188. package/dist/narration/speech-services.d.ts.map +1 -0
  189. package/dist/narration/speech-services.js +67 -0
  190. package/dist/narration/speech-services.js.map +1 -0
  191. package/dist/narration/voice-args.d.ts +3 -0
  192. package/dist/narration/voice-args.d.ts.map +1 -0
  193. package/dist/narration/voice-args.js +10 -0
  194. package/dist/narration/voice-args.js.map +1 -0
  195. package/dist/problems.d.ts +12 -0
  196. package/dist/problems.d.ts.map +1 -0
  197. package/dist/problems.js +19 -0
  198. package/dist/problems.js.map +1 -0
  199. package/dist/programs/kill-tree.d.ts +16 -0
  200. package/dist/programs/kill-tree.d.ts.map +1 -0
  201. package/dist/programs/kill-tree.js +28 -0
  202. package/dist/programs/kill-tree.js.map +1 -0
  203. package/dist/programs/media-duration.d.ts +22 -0
  204. package/dist/programs/media-duration.d.ts.map +1 -0
  205. package/dist/programs/media-duration.js +23 -0
  206. package/dist/programs/media-duration.js.map +1 -0
  207. package/dist/programs/piped-program.d.ts +7 -0
  208. package/dist/programs/piped-program.d.ts.map +1 -0
  209. package/dist/programs/piped-program.js +45 -0
  210. package/dist/programs/piped-program.js.map +1 -0
  211. package/dist/programs/programs.d.ts +6 -0
  212. package/dist/programs/programs.d.ts.map +1 -0
  213. package/dist/programs/programs.js +6 -0
  214. package/dist/programs/programs.js.map +1 -0
  215. package/dist/programs/run-program.d.ts +23 -0
  216. package/dist/programs/run-program.d.ts.map +1 -0
  217. package/dist/programs/run-program.js +47 -0
  218. package/dist/programs/run-program.js.map +1 -0
  219. package/dist/record/address.d.ts +5 -0
  220. package/dist/record/address.d.ts.map +1 -0
  221. package/dist/record/address.js +26 -0
  222. package/dist/record/address.js.map +1 -0
  223. package/dist/record/cursor-script.d.ts +3 -0
  224. package/dist/record/cursor-script.d.ts.map +1 -0
  225. package/dist/record/cursor-script.js +59 -0
  226. package/dist/record/cursor-script.js.map +1 -0
  227. package/dist/record/encoder-args.d.ts +3 -0
  228. package/dist/record/encoder-args.d.ts.map +1 -0
  229. package/dist/record/encoder-args.js +13 -0
  230. package/dist/record/encoder-args.js.map +1 -0
  231. package/dist/record/frame-writer.d.ts +8 -0
  232. package/dist/record/frame-writer.d.ts.map +1 -0
  233. package/dist/record/frame-writer.js +30 -0
  234. package/dist/record/frame-writer.js.map +1 -0
  235. package/dist/record/motion.d.ts +19 -0
  236. package/dist/record/motion.d.ts.map +1 -0
  237. package/dist/record/motion.js +59 -0
  238. package/dist/record/motion.js.map +1 -0
  239. package/dist/record/narration-cues.d.ts +14 -0
  240. package/dist/record/narration-cues.d.ts.map +1 -0
  241. package/dist/record/narration-cues.js +12 -0
  242. package/dist/record/narration-cues.js.map +1 -0
  243. package/dist/record/own-port.d.ts +2 -0
  244. package/dist/record/own-port.d.ts.map +1 -0
  245. package/dist/record/own-port.js +23 -0
  246. package/dist/record/own-port.js.map +1 -0
  247. package/dist/record/page-ready.d.ts +4 -0
  248. package/dist/record/page-ready.d.ts.map +1 -0
  249. package/dist/record/page-ready.js +15 -0
  250. package/dist/record/page-ready.js.map +1 -0
  251. package/dist/record/page-route.d.ts +13 -0
  252. package/dist/record/page-route.d.ts.map +1 -0
  253. package/dist/record/page-route.js +64 -0
  254. package/dist/record/page-route.js.map +1 -0
  255. package/dist/record/pointer.d.ts +8 -0
  256. package/dist/record/pointer.d.ts.map +1 -0
  257. package/dist/record/pointer.js +24 -0
  258. package/dist/record/pointer.js.map +1 -0
  259. package/dist/record/recorder.d.ts +34 -0
  260. package/dist/record/recorder.d.ts.map +1 -0
  261. package/dist/record/recorder.js +99 -0
  262. package/dist/record/recorder.js.map +1 -0
  263. package/dist/record/register-record.d.ts +3 -0
  264. package/dist/record/register-record.d.ts.map +1 -0
  265. package/dist/record/register-record.js +83 -0
  266. package/dist/record/register-record.js.map +1 -0
  267. package/dist/record/run-step.d.ts +20 -0
  268. package/dist/record/run-step.d.ts.map +1 -0
  269. package/dist/record/run-step.js +69 -0
  270. package/dist/record/run-step.js.map +1 -0
  271. package/dist/record/screencast.d.ts +9 -0
  272. package/dist/record/screencast.d.ts.map +1 -0
  273. package/dist/record/screencast.js +38 -0
  274. package/dist/record/screencast.js.map +1 -0
  275. package/dist/record/steps.d.ts +124 -0
  276. package/dist/record/steps.d.ts.map +1 -0
  277. package/dist/record/steps.js +47 -0
  278. package/dist/record/steps.js.map +1 -0
  279. package/dist/register-with-kvcoder.d.ts +3 -0
  280. package/dist/register-with-kvcoder.d.ts.map +1 -0
  281. package/dist/register-with-kvcoder.js +15 -0
  282. package/dist/register-with-kvcoder.js.map +1 -0
  283. package/dist/review/analysis.d.ts +19 -0
  284. package/dist/review/analysis.d.ts.map +1 -0
  285. package/dist/review/analysis.js +36 -0
  286. package/dist/review/analysis.js.map +1 -0
  287. package/dist/review/frames.d.ts +9 -0
  288. package/dist/review/frames.d.ts.map +1 -0
  289. package/dist/review/frames.js +28 -0
  290. package/dist/review/frames.js.map +1 -0
  291. package/dist/review/register-review.d.ts +24 -0
  292. package/dist/review/register-review.d.ts.map +1 -0
  293. package/dist/review/register-review.js +81 -0
  294. package/dist/review/register-review.js.map +1 -0
  295. package/dist/review/sheet-args.d.ts +11 -0
  296. package/dist/review/sheet-args.d.ts.map +1 -0
  297. package/dist/review/sheet-args.js +24 -0
  298. package/dist/review/sheet-args.js.map +1 -0
  299. package/dist/settings.d.ts +33 -0
  300. package/dist/settings.d.ts.map +1 -0
  301. package/dist/settings.js +29 -0
  302. package/dist/settings.js.map +1 -0
  303. package/dist/setup/browser-readiness.d.ts +14 -0
  304. package/dist/setup/browser-readiness.d.ts.map +1 -0
  305. package/dist/setup/browser-readiness.js +16 -0
  306. package/dist/setup/browser-readiness.js.map +1 -0
  307. package/dist/setup/ffmpeg-readiness.d.ts +19 -0
  308. package/dist/setup/ffmpeg-readiness.d.ts.map +1 -0
  309. package/dist/setup/ffmpeg-readiness.js +33 -0
  310. package/dist/setup/ffmpeg-readiness.js.map +1 -0
  311. package/dist/setup/narration-readiness.d.ts +21 -0
  312. package/dist/setup/narration-readiness.d.ts.map +1 -0
  313. package/dist/setup/narration-readiness.js +18 -0
  314. package/dist/setup/narration-readiness.js.map +1 -0
  315. package/dist/setup/register-setup.d.ts +3 -0
  316. package/dist/setup/register-setup.d.ts.map +1 -0
  317. package/dist/setup/register-setup.js +51 -0
  318. package/dist/setup/register-setup.js.map +1 -0
  319. package/dist/ui.d.ts +3 -0
  320. package/dist/ui.d.ts.map +1 -0
  321. package/dist/ui.js +26 -0
  322. package/dist/ui.js.map +1 -0
  323. package/dist/video-start.d.ts +3 -0
  324. package/dist/video-start.d.ts.map +1 -0
  325. package/dist/video-start.js +28 -0
  326. package/dist/video-start.js.map +1 -0
  327. package/dist/web/components/player.css +2 -0
  328. package/dist/web/components/player.js +55 -0
  329. package/docs/guide.md +28 -0
  330. package/fonts/Amiri-Bold.ttf +0 -0
  331. package/fonts/IBMPlexSansArabic-Bold.ttf +0 -0
  332. package/fonts/Lato-Bold.ttf +0 -0
  333. package/fonts/OFL-Amiri.txt +94 -0
  334. package/fonts/OFL-IBMPlexSansArabic.txt +93 -0
  335. package/fonts/OFL-Lato.txt +93 -0
  336. package/fonts/OFL-Poppins.txt +93 -0
  337. package/fonts/Poppins-Bold.ttf +0 -0
  338. package/locales/ar.json +48 -0
  339. package/locales/en.json +48 -0
  340. package/package.json +73 -3
  341. package/sounds/SOURCES.md +13 -0
  342. package/sounds/click.wav +0 -0
  343. package/sounds/keys.wav +0 -0
  344. package/sounds/pop.wav +0 -0
  345. package/sounds/whoosh.wav +0 -0
@@ -0,0 +1,70 @@
1
+ const maximumLine = 42;
2
+ const length = (text) => [...text].length;
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 !== '');
10
+ }
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) {
13
+ const lines = [];
14
+ let current = '';
15
+ for (const word of sentence.split(' ')) {
16
+ const joined = current === '' ? word : `${current} ${word}`;
17
+ if (current !== '' && length(joined) > maximumLine) {
18
+ lines.push(current);
19
+ current = word;
20
+ }
21
+ else {
22
+ current = joined;
23
+ }
24
+ }
25
+ if (current !== '')
26
+ lines.push(current);
27
+ return lines;
28
+ }
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
+ });
45
+ }
46
+ function proportional(sentence) {
47
+ const lines = linesOf(sentence.text);
48
+ const total = lines.reduce((sum, line) => sum + length(line), 0);
49
+ let before = 0;
50
+ return lines.map((line) => {
51
+ const startMs = sentence.startMs + Math.floor((sentence.durationMs * before) / total);
52
+ before += length(line);
53
+ return { text: line, startMs, endMs: 0 };
54
+ });
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
+ }
70
+ //# sourceMappingURL=captions.js.map
@@ -0,0 +1 @@
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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"}
@@ -0,0 +1,4 @@
1
+ import { type Program } from '../programs/run-program.ts';
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>;
4
+ //# sourceMappingURL=piper.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,13 @@
1
+ import { lastLines, runProgram } from "../programs/run-program.js";
2
+ import { kvvideoProblem } from "../problems.js";
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
+ }
12
+ }
13
+ //# sourceMappingURL=piper.js.map
@@ -0,0 +1 @@
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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"}
@@ -0,0 +1,25 @@
1
+ import { z, type Ctx } from '@kvman/sdk';
2
+ export declare const narrateInputSchema: z.ZodObject<{
3
+ text: z.ZodString;
4
+ file: z.ZodString;
5
+ tone: z.ZodExactOptional<z.ZodString>;
6
+ speed: z.ZodExactOptional<z.ZodNumber>;
7
+ replace: z.ZodExactOptional<z.ZodBoolean>;
8
+ }, z.core.$strict>;
9
+ export declare const narrateOutputSchema: z.ZodObject<{
10
+ file: z.ZodString;
11
+ durationMs: z.ZodNumber;
12
+ captions: z.ZodArray<z.ZodObject<{
13
+ text: z.ZodString;
14
+ startMs: z.ZodNumber;
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>>>;
21
+ }, z.core.$strip>>;
22
+ reused: z.ZodNumber;
23
+ }, z.core.$strip>;
24
+ export declare function registerNarration(ctx: Ctx): void;
25
+ //# sourceMappingURL=register-narration.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,103 @@
1
+ import path from 'node:path';
2
+ import { z } from '@kvman/sdk';
3
+ import { answerPath } from "../files/answer-path.js";
4
+ import { placeOutput, workFolder } from "../files/place-output.js";
5
+ import { sidecarCaptionSchema, sidecarOutputPath, writeSidecar } from "../files/sidecar.js";
6
+ import { outputPath } from "../files/workspace-paths.js";
7
+ import { mediaDuration } from "../programs/media-duration.js";
8
+ import { programOf } from "../programs/programs.js";
9
+ import { lastLines, runProgram } from "../programs/run-program.js";
10
+ import { invalid, kvvideoProblem } from "../problems.js";
11
+ import { keySecrets } from "../setup/narration-readiness.js";
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.
19
+ export const narrateInputSchema = z.strictObject({
20
+ text: z.string().min(1).max(4000).describe('What the narrator says: one scene, a few sentences.'),
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(),
24
+ replace: z.boolean().describe('Write over an existing file.').exactOptional(),
25
+ });
26
+ export const narrateOutputSchema = z.object({ file: z.string(), durationMs: z.number().int().nonnegative(), captions: z.array(sidecarCaptionSchema), reused: z.number().int().nonnegative() });
27
+ async function chosenService(ctx) {
28
+ const service = await ctx.settings.get('kvvideo.narration.service');
29
+ if (service === null)
30
+ throw kvvideoProblem('NARRATION_NOT_SET', 'No narration service is chosen; set kvvideo.narration.service.', { setting: 'kvvideo.narration.service' });
31
+ return service;
32
+ }
33
+ async function voiceOf(ctx, input) {
34
+ const service = await chosenService(ctx);
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');
38
+ if (service === 'piper') {
39
+ if (choice.voice === null)
40
+ throw kvvideoProblem('NARRATION_NOT_SET', "Piper has no voice; set kvvideo.narration.voice to a voice's .onnx file.", { setting: 'kvvideo.narration.voice' });
41
+ return { ...shared, key: null, programs: { ffprobe, piper: await programOf(ctx, 'piper') } };
42
+ }
43
+ const secret = keySecrets[service];
44
+ const key = await ctx.secrets.get(secret);
45
+ if (key === undefined)
46
+ throw kvvideoProblem('NARRATION_KEY_MISSING', `The ${service} key is missing; add the secret ${secret} on kvvideo's extension page.`, { service, secret });
47
+ return { ...shared, key, programs: { ffprobe, piper: null } };
48
+ }
49
+ export function registerNarration(ctx) {
50
+ ctx.registerCommand('kvvideo.narration.make', {
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.',
52
+ input: narrateInputSchema,
53
+ output: narrateOutputSchema,
54
+ public: true,
55
+ retries: 0,
56
+ timeoutMs: 300_000,
57
+ handle: async (input) => {
58
+ const root = ctx.job.workspace.path;
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 });
68
+ const work = workFolder();
69
+ try {
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);
89
+ if (!duration.ok)
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 });
93
+ placeOutput(made, output);
94
+ placeOutput(madeSidecar, sidecar);
95
+ return { file: answerPath(root, output), durationMs: duration.ms, captions, reused };
96
+ }
97
+ finally {
98
+ work.remove();
99
+ }
100
+ },
101
+ });
102
+ }
103
+ //# sourceMappingURL=register-narration.js.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,82 @@
1
+ import path from 'node:path';
2
+ import { z } from '@kvman/sdk';
3
+ import { answerPath } from "../files/answer-path.js";
4
+ import { placeOutput, workFolder } from "../files/place-output.js";
5
+ import { sidecarCaptionSchema, sidecarOutputPath, writeSidecar } from "../files/sidecar.js";
6
+ import { inputPath, outputPath } from "../files/workspace-paths.js";
7
+ import { mediaDuration } from "../programs/media-duration.js";
8
+ import { programOf } from "../programs/programs.js";
9
+ import { lastLines, runProgram } from "../programs/run-program.js";
10
+ import { kvvideoProblem } from "../problems.js";
11
+ import { captionsOf, sentencesOf } from "./captions.js";
12
+ import { joinNarrationArgs, sentenceGapMs } from "./join-args.js";
13
+ import { spokenOf } from "./pronunciations.js";
14
+ import { takeArgs } from "./voice-args.js";
15
+ // `video voice` (plan 16 §16.7; ADR 0115, 28 and 50): the person's takes of a scene, as `ask record` wrote them, each
16
+ // cleaned (or only converted), joined as `narrate` joins sentences into an MP3 of the workspace, with each take's
17
+ // captions lasting its own length; a sidecar beside it. The takes are never changed.
18
+ export const takeExtensions = ['.webm', '.ogg', '.m4a', '.mp4', '.mp3', '.wav'];
19
+ export const voiceInputSchema = z.strictObject({
20
+ lines: z.array(z.strictObject({ take: z.string().min(1).max(1000).describe('A take of the person, as ask record wrote it.'), text: z.string().min(1).max(500).describe('What is said in the take.') })).min(1).max(20),
21
+ file: z.string().min(1).max(1000).describe('The .mp3 file to write, in the workspace.'),
22
+ clean: z.boolean().describe('false only converts the takes; true (the default) removes noise, trims silence, and evens the level.').exactOptional(),
23
+ replace: z.boolean().describe('Write over an existing file.').exactOptional(),
24
+ });
25
+ export const voiceOutputSchema = z.object({ file: z.string(), durationMs: z.number().int().nonnegative(), captions: z.array(sidecarCaptionSchema) });
26
+ function failed(take, reason) {
27
+ return kvvideoProblem('RENDER_FAILED', `ffmpeg couldn't make ${take === null ? 'the joined voice' : `take ${String(take)}`}: ${reason}`, { take, reason });
28
+ }
29
+ export function registerVoice(ctx) {
30
+ ctx.registerCommand('kvvideo.voice.make', {
31
+ description: "Makes a narration from the person's own takes: each cleaned, joined into an MP3 file in the workspace, with its length and captions.",
32
+ input: voiceInputSchema,
33
+ output: voiceOutputSchema,
34
+ public: true,
35
+ retries: 0,
36
+ timeoutMs: 300_000,
37
+ handle: async (input) => {
38
+ const root = ctx.job.workspace.path;
39
+ const replace = input.replace === true;
40
+ const takes = input.lines.map((line) => inputPath(root, line.take, takeExtensions));
41
+ const output = outputPath(root, input.file, '.mp3', replace);
42
+ const sidecar = sidecarOutputPath(output, replace, input.file);
43
+ const [ffmpeg, ffprobe] = [await programOf(ctx, 'ffmpeg'), await programOf(ctx, 'ffprobe')];
44
+ const work = workFolder();
45
+ try {
46
+ const cleaned = [];
47
+ const placed = [];
48
+ let startMs = 0;
49
+ for (const [index, take] of takes.entries()) {
50
+ ctx.job.progress({ key: 'kvvideo.progress.take', params: { n: index + 1, of: takes.length } });
51
+ const made = path.join(work.folder, `take-${String(index + 1)}.wav`);
52
+ const run = await runProgram(ffmpeg, takeArgs(take, input.clean !== false, made), { signal: ctx.job.signal });
53
+ if (run.exitCode !== 0)
54
+ throw failed(index + 1, lastLines(run.output));
55
+ const length = await mediaDuration(ffprobe, made, ctx.job.signal);
56
+ if (!length.ok)
57
+ throw failed(index + 1, length.reason);
58
+ const text = sentencesOf(input.lines[index]?.text ?? '').join(' ');
59
+ placed.push({ text, spoken: spokenOf(text, []), startMs, durationMs: length.ms, alignment: null });
60
+ cleaned.push(made);
61
+ startMs += length.ms + sentenceGapMs;
62
+ }
63
+ const joined = path.join(work.folder, 'voice.mp3');
64
+ const run = await runProgram(ffmpeg, joinNarrationArgs(cleaned, joined), { signal: ctx.job.signal });
65
+ if (run.exitCode !== 0)
66
+ throw failed(null, lastLines(run.output));
67
+ const duration = await mediaDuration(ffprobe, joined, ctx.job.signal);
68
+ if (!duration.ok)
69
+ throw failed(null, duration.reason);
70
+ const captions = captionsOf(placed, duration.ms);
71
+ const madeSidecar = writeSidecar(work.folder, { kind: 'narration', durationMs: duration.ms, captions });
72
+ placeOutput(joined, output);
73
+ placeOutput(madeSidecar, sidecar);
74
+ return { file: answerPath(root, output), durationMs: duration.ms, captions };
75
+ }
76
+ finally {
77
+ work.remove();
78
+ }
79
+ },
80
+ });
81
+ }
82
+ //# sourceMappingURL=register-voice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-voice.js","sourceRoot":"","sources":["../../src/narration/register-voice.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { Ctx } from '@kvman/sdk';
2
+ import type { Program } from '../programs/run-program.ts';
3
+ import type { NarrationService } from '../settings.ts';
4
+ import type { Alignment } from './alignment.ts';
5
+ import { type Fetch } from './speech-services.ts';
6
+ export type Voice = {
7
+ service: NarrationService;
8
+ choice: {
9
+ voice: string | null;
10
+ model: string | null;
11
+ };
12
+ key: string | null;
13
+ tone: string | null;
14
+ speed: number;
15
+ programs: {
16
+ ffprobe: Program;
17
+ piper: Program | null;
18
+ };
19
+ fetch: Fetch;
20
+ signal: AbortSignal;
21
+ };
22
+ export type SaidSentence = {
23
+ file: string;
24
+ durationMs: number;
25
+ alignment: Alignment | null;
26
+ reused: boolean;
27
+ };
28
+ /** The sentence's spoken text and its neighbours, as sent. */
29
+ export type SentenceText = {
30
+ spoken: string;
31
+ previous: string | null;
32
+ next: string | null;
33
+ };
34
+ export declare function saySentence(ctx: Ctx, voice: Voice, text: SentenceText, folder: string, index: number): Promise<SaidSentence>;
35
+ //# sourceMappingURL=sentence-speaker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentence-speaker.d.ts","sourceRoot":"","sources":["../../src/narration/sentence-speaker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAE1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD,OAAO,EAAwC,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAKxF,MAAM,MAAM,KAAK,GAAG;IAClB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACvD,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC;IACtD,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAE9G,8DAA8D;AAC9D,MAAM,MAAM,YAAY,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAyB5F,wBAAsB,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAalI"}