@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
@@ -0,0 +1,75 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ /** A painter on a browser, drawing at one pixel per CSS pixel. */
4
+ export function painterOf(browser) {
5
+ return {
6
+ draw: async (file, size, html, clear) => {
7
+ const context = await browser.newContext({ viewport: size, deviceScaleFactor: 1 });
8
+ try {
9
+ const page = await context.newPage();
10
+ await page.setContent(html, { waitUntil: 'load' });
11
+ await page.evaluate('document.fonts.ready.then(() => true)');
12
+ await page.screenshot({ path: file, omitBackground: clear, scale: 'css' });
13
+ }
14
+ finally {
15
+ await context.close();
16
+ }
17
+ },
18
+ };
19
+ }
20
+ const px = (value) => `${String(Math.round(value))}px`;
21
+ const placed = (rect) => `position:absolute;inset-inline-start:${px(rect.x)};inset-block-start:${px(rect.y)};inline-size:${px(rect.width)};block-size:${px(rect.height)}`;
22
+ const pageOf = (body, background) => `<!doctype html><html dir="ltr"><head><style>html,body{margin:0;background:${background};overflow:hidden}</style></head><body>${body}</body></html>`;
23
+ /** The window's shadow or the phone's body, on a clear picture. */
24
+ export function chromeHtml(layout) {
25
+ if (layout.kind === 'window') {
26
+ const { offset, blur } = layout.shadow;
27
+ return pageOf(`<div style="${placed(layout.screen)};border-radius:${px(layout.radius)};background:#000;box-shadow:0 ${px(offset)} ${px(blur)} rgba(0,0,0,.45)"></div>`, 'transparent');
28
+ }
29
+ const outline = Math.max(1, Math.round(layout.body.width * 0.006));
30
+ return pageOf(`<div style="${placed(layout.body)};border-radius:${px(layout.bodyRadius)};background:#111;box-shadow:inset 0 0 0 ${px(outline)} #3a3a3a,0 ${px(layout.body.height * 0.02)} ${px(layout.body.height * 0.05)} rgba(0,0,0,.45)"></div>`, 'transparent');
31
+ }
32
+ /** The clip's rounded corners: white inside, black outside, the size of the clip's place. */
33
+ export function maskHtml(size, radius) {
34
+ return pageOf(`<div style="${placed({ x: 0, y: 0, ...size })};border-radius:${px(radius)};background:#fff"></div>`, '#000');
35
+ }
36
+ /** A highlight over a page of `size`: dimmed to 55% black but the box, with 12 CSS pixels around it, rounded. */
37
+ export function highlightHtml(box, scale) {
38
+ const around = 12 * scale;
39
+ const hole = { x: box.x - around, y: box.y - around, width: box.width + 2 * around, height: box.height + 2 * around };
40
+ return pageOf(`<div style="${placed(hole)};border-radius:${px(around)};box-shadow:0 0 0 99999px rgba(0,0,0,.55)"></div>`, 'transparent');
41
+ }
42
+ const escaped = (text) => text.replace(/[&<>"']/g, (character) => `&#${String(character.codePointAt(0))};`);
43
+ const luminance = (colour) => {
44
+ const [red, green, blue] = [1, 3, 5].map((at) => {
45
+ const value = Number.parseInt(colour.slice(at, at + 2), 16) / 255;
46
+ return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
47
+ });
48
+ return 0.2126 * (red ?? 0) + 0.7152 * (green ?? 0) + 0.0722 * (blue ?? 0);
49
+ };
50
+ /** White or near-black, whichever contrasts more with `background`. */
51
+ export function textOn(background) {
52
+ const light = luminance(background);
53
+ return 1.05 / (light + 0.05) >= (light + 0.05) / (luminance('#111111') + 0.05) ? '#ffffff' : '#111111';
54
+ }
55
+ const fontFace = (font) => `@font-face{font-family:kvvideo;src:url(data:font/${path.extname(font.file).slice(1).toLowerCase()};base64,${readFileSync(font.file).toString('base64')})}`;
56
+ /** A callout's label over a page of `size`, pointing at the box: above it when there is room, else below. */
57
+ export function calloutHtml(size, box, text, colour, font) {
58
+ const fontSize = Math.round(size.height * 0.045);
59
+ const arrow = Math.round(fontSize * 0.5);
60
+ const margin = Math.round(size.width * 0.02);
61
+ const script = `const label = document.getElementById('label'); const tip = document.getElementById('tip');
62
+ const box = ${JSON.stringify(box)}; const width = label.offsetWidth; const height = label.offsetHeight;
63
+ const above = box.y - height - ${String(arrow * 2)} > ${String(margin)};
64
+ const left = Math.min(Math.max(${String(margin)}, box.x + box.width / 2 - width / 2), ${String(size.width - margin)} - width);
65
+ const top = above ? box.y - height - ${String(arrow * 2)} : box.y + box.height + ${String(arrow * 2)};
66
+ label.style.insetInlineStart = left + 'px'; label.style.insetBlockStart = top + 'px';
67
+ tip.style.insetInlineStart = (box.x + box.width / 2 - ${String(arrow)}) + 'px';
68
+ tip.style.insetBlockStart = (above ? top + height : top - ${String(arrow)}) + 'px';
69
+ tip.style.borderBlockStart = above ? '${String(arrow)}px solid ${colour}' : '0'; tip.style.borderBlockEnd = above ? '0' : '${String(arrow)}px solid ${colour}';`;
70
+ // The label's box is placed left to right; its text takes its own direction inside it, so an Arabic label stays put.
71
+ const label = `<div id="label" dir="ltr" style="position:absolute;white-space:nowrap;font:700 ${px(fontSize)}/1.2 kvvideo,sans-serif;color:${textOn(colour)};background:${colour};padding:.45em .9em;border-radius:999px;box-shadow:0 ${px(fontSize * 0.15)} ${px(fontSize * 0.5)} rgba(0,0,0,.35)"><span dir="auto">${escaped(text)}</span></div>`;
72
+ const tip = `<div id="tip" style="position:absolute;inline-size:0;block-size:0;border-inline:${px(arrow)} solid transparent"></div>`;
73
+ return pageOf(`<style>${fontFace(font)}</style>${label}${tip}<script>document.fonts.ready.then(() => {${script}});</script>`, 'transparent');
74
+ }
75
+ //# sourceMappingURL=drawn-images.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drawn-images.js","sourceRoot":"","sources":["../../src/compose/drawn-images.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { z } from '@kvman/sdk';
2
+ import { type SoundEvent } from './sound-effects.ts';
3
+ import { type Transition } from './transitions.ts';
4
+ export type FinalPlan = {
5
+ parts: readonly string[];
6
+ starts: readonly number[];
7
+ transitions: readonly Transition[];
8
+ direction: 'ltr' | 'rtl';
9
+ events: readonly SoundEvent[];
10
+ durationMs: number;
11
+ };
12
+ /** The final pass's inputs: the parts, then each sound used, the key taps looped. */
13
+ export declare function finalInputs(plan: FinalPlan): string[];
14
+ /** The loudness filter: measuring when `measured` is undefined, else applying the measure; silence is left as it is. */
15
+ export declare function loudnessFilter(measured: Loudness | undefined): string;
16
+ /** The final pass's graph: the sound alone to measure, or the picture and the sound to encode. */
17
+ export declare function finalGraph(plan: FinalPlan, measured: Loudness | undefined): string;
18
+ declare const loudnessSchema: z.ZodObject<{
19
+ input_i: z.ZodString;
20
+ input_tp: z.ZodString;
21
+ input_lra: z.ZodString;
22
+ input_thresh: z.ZodString;
23
+ target_offset: z.ZodString;
24
+ }, z.core.$strip>;
25
+ /** What `loudnorm` measured. */
26
+ export type Loudness = z.output<typeof loudnessSchema>;
27
+ /** The measure in a measuring run's output: its last JSON object; one that isn't there fails the final pass. */
28
+ export declare function measuredLoudness(output: string): Loudness;
29
+ export declare function measureArgs(plan: FinalPlan, graph: string): string[];
30
+ export declare function finalArgs(plan: FinalPlan, graph: string, output: string): string[];
31
+ /** The poster: the frame at `atMs`, 1280 px on its longer side. */
32
+ export declare function posterArgs(video: string, atMs: number, output: string): string[];
33
+ export {};
34
+ //# sourceMappingURL=final-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"final-pass.d.ts","sourceRoot":"","sources":["../../src/compose/final-pass.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,YAAY,CAAC;AAE/B,OAAO,EAA4B,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAM9D,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,SAAS,UAAU,EAAE,CAAC;IAAC,SAAS,EAAE,KAAK,GAAG,KAAK,CAAC;IAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAUjM,qFAAqF;AACrF,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,EAAE,CAErD;AAsCD,wHAAwH;AACxH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAIrE;AAED,kGAAkG;AAClG,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAGlF;AAED,QAAA,MAAM,cAAc;;;;;;iBAAsI,CAAC;AAE3J,gCAAgC;AAChC,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,cAAc,CAAC,CAAC;AAWvD,gHAAgH;AAChH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAOzD;AAID,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAEpE;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAUlF;AAED,mEAAmE;AACnE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAEhF"}
@@ -0,0 +1,106 @@
1
+ import path from 'node:path';
2
+ import { z } from '@kvman/sdk';
3
+ import { kvvideoProblem } from "../problems.js";
4
+ import { soundsFolder } from "./sound-effects.js";
5
+ import { xfadeName } from "./transitions.js";
6
+ const seconds = (ms) => (ms / 1000).toFixed(3);
7
+ const target = 'I=-14:TP=-1:LRA=11';
8
+ const gains = { click: '-18dB', keys: '-24dB', whoosh: '-18dB', pop: '-18dB' };
9
+ function soundsUsed(plan) {
10
+ return [...new Set(plan.events.map((event) => event.sound))].sort();
11
+ }
12
+ /** The final pass's inputs: the parts, then each sound used, the key taps looped. */
13
+ export function finalInputs(plan) {
14
+ return [...plan.parts.flatMap((part) => ['-i', part]), ...soundsUsed(plan).flatMap((sound) => [...(sound === 'keys' ? ['-stream_loop', '-1'] : []), '-i', path.join(soundsFolder, `${sound}.wav`)])];
15
+ }
16
+ function joined(plan, media) {
17
+ const filters = [];
18
+ let current = `0:${media}`;
19
+ plan.parts.slice(1).forEach((_, index) => {
20
+ const transition = plan.transitions[index];
21
+ const name = transition === undefined ? undefined : xfadeName(transition, plan.direction);
22
+ const next = `j${media}${String(index + 1)}`;
23
+ const input = `${String(index + 1)}:${media}`;
24
+ if (name === undefined || transition === undefined)
25
+ filters.push(`[${current}][${input}]concat=n=2:v=${media === 'v' ? 1 : 0}:a=${media === 'a' ? 1 : 0}[${next}]`);
26
+ else if (media === 'v')
27
+ filters.push(`[${current}][${input}]xfade=transition=${name}:duration=${seconds(transition.ms)}:offset=${seconds(plan.starts[index + 1] ?? 0)}[${next}]`);
28
+ else
29
+ filters.push(`[${current}][${input}]acrossfade=d=${seconds(transition.ms)}:c1=tri:c2=tri[${next}]`);
30
+ current = next;
31
+ });
32
+ return [...filters, media === 'v' ? `[${current}]format=yuv420p[v]` : `[${current}]anull[joineda]`];
33
+ }
34
+ function effects(plan) {
35
+ const sounds = soundsUsed(plan);
36
+ const filters = [];
37
+ const labels = [];
38
+ sounds.forEach((sound, soundIndex) => {
39
+ const events = plan.events.filter((event) => event.sound === sound);
40
+ const input = `${String(plan.parts.length + soundIndex)}:a`;
41
+ const copies = events.map((_, index) => `${sound}${String(index)}`);
42
+ filters.push(`[${input}]asplit=${String(events.length)}${copies.map((copy) => `[${copy}]`).join('')}`);
43
+ events.forEach((event, index) => {
44
+ const trim = event.untilMs === undefined ? '' : `atrim=0:${seconds(event.untilMs - event.atMs)},asetpts=PTS-STARTPTS,`;
45
+ const label = `e${sound}${String(index)}`;
46
+ filters.push(`[${copies[index] ?? ''}]${trim}aformat=sample_rates=48000:channel_layouts=stereo,adelay=delays=${String(Math.round(event.atMs))}:all=1,volume=${gains[sound]}[${label}]`);
47
+ labels.push(label);
48
+ });
49
+ });
50
+ const mix = labels.length === 0 ? '[joineda]anull[mixed]' : `[joineda]${labels.map((label) => `[${label}]`).join('')}amix=inputs=${String(labels.length + 1)}:duration=first:normalize=0[mixed]`;
51
+ return [...filters, mix];
52
+ }
53
+ /** The loudness filter: measuring when `measured` is undefined, else applying the measure; silence is left as it is. */
54
+ export function loudnessFilter(measured) {
55
+ if (measured === undefined)
56
+ return `loudnorm=${target}:print_format=json`;
57
+ if (!Number.isFinite(Number(measured.input_i)))
58
+ return 'anull';
59
+ return `loudnorm=${target}:measured_I=${measured.input_i}:measured_TP=${measured.input_tp}:measured_LRA=${measured.input_lra}:measured_thresh=${measured.input_thresh}:offset=${measured.target_offset}:linear=true`;
60
+ }
61
+ /** The final pass's graph: the sound alone to measure, or the picture and the sound to encode. */
62
+ export function finalGraph(plan, measured) {
63
+ const picture = measured === undefined ? [] : joined(plan, 'v');
64
+ return [...picture, ...joined(plan, 'a'), ...effects(plan), `[mixed]${loudnessFilter(measured)},aresample=48000[a]`].join(';\n');
65
+ }
66
+ const loudnessSchema = z.object({ input_i: z.string(), input_tp: z.string(), input_lra: z.string(), input_thresh: z.string(), target_offset: z.string() });
67
+ function parsedJson(text) {
68
+ try {
69
+ return JSON.parse(text);
70
+ }
71
+ catch (error) {
72
+ if (error instanceof SyntaxError)
73
+ return undefined;
74
+ throw error;
75
+ }
76
+ }
77
+ /** The measure in a measuring run's output: its last JSON object; one that isn't there fails the final pass. */
78
+ export function measuredLoudness(output) {
79
+ const start = output.lastIndexOf('{');
80
+ const end = output.lastIndexOf('}');
81
+ const text = start === -1 || end < start ? '' : output.slice(start, end + 1);
82
+ const parsed = loudnessSchema.safeParse(text === '' ? undefined : parsedJson(text));
83
+ if (!parsed.success)
84
+ throw kvvideoProblem('RENDER_FAILED', "ffmpeg's loudness measure couldn't be read", { scene: null, reason: "ffmpeg's loudness measure couldn't be read" });
85
+ return parsed.data;
86
+ }
87
+ const quiet = ['-hide_banner', '-loglevel', 'error'];
88
+ export function measureArgs(plan, graph) {
89
+ return ['-hide_banner', '-nostats', '-loglevel', 'info', ...finalInputs(plan), '-filter_complex_script', graph, '-map', '[a]', '-t', seconds(plan.durationMs), '-f', 'null', '-'];
90
+ }
91
+ export function finalArgs(plan, graph, output) {
92
+ return [
93
+ ...quiet,
94
+ ...finalInputs(plan),
95
+ '-filter_complex_script', graph,
96
+ '-map', '[v]', '-map', '[a]',
97
+ '-c:v', 'libx264', '-preset', 'medium', '-crf', '18', '-pix_fmt', 'yuv420p', '-r', '30',
98
+ '-c:a', 'aac', '-b:a', '192k', '-ar', '48000', '-ac', '2',
99
+ '-t', seconds(plan.durationMs), '-movflags', '+faststart', '-progress', 'pipe:1', '-nostats', '-y', output,
100
+ ];
101
+ }
102
+ /** The poster: the frame at `atMs`, 1280 px on its longer side. */
103
+ export function posterArgs(video, atMs, output) {
104
+ return [...quiet, '-ss', seconds(atMs), '-i', video, '-frames:v', '1', '-vf', "scale='if(gte(iw,ih),1280,-2)':'if(gte(iw,ih),-2,1280)'", '-q:v', '3', '-y', output];
105
+ }
106
+ //# sourceMappingURL=final-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"final-pass.js","sourceRoot":"","sources":["../../src/compose/final-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ export type Size = {
2
+ width: number;
3
+ height: number;
4
+ };
5
+ export type Rect = {
6
+ x: number;
7
+ y: number;
8
+ width: number;
9
+ height: number;
10
+ };
11
+ export type WindowLayout = {
12
+ kind: 'window';
13
+ screen: Rect;
14
+ radius: number;
15
+ shadow: {
16
+ offset: number;
17
+ blur: number;
18
+ };
19
+ };
20
+ export type PhoneLayout = {
21
+ kind: 'phone';
22
+ body: Rect;
23
+ bodyRadius: number;
24
+ screen: Rect;
25
+ radius: number;
26
+ };
27
+ export type Layout = WindowLayout | PhoneLayout;
28
+ export declare function windowLayout(picture: Size, clip: Size, captionsAtBottom: boolean): WindowLayout;
29
+ export declare function phoneLayout(picture: Size, clip: Size): PhoneLayout;
30
+ //# sourceMappingURL=frame-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-layout.d.ts","sourceRoot":"","sources":["../../src/compose/frame-layout.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,IAAI,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD,MAAM,MAAM,IAAI,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3E,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAEtH,MAAM,MAAM,WAAW,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1G,MAAM,MAAM,MAAM,GAAG,YAAY,GAAG,WAAW,CAAC;AAWhD,wBAAgB,YAAY,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,GAAG,YAAY,CAM/F;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,GAAG,WAAW,CAclE"}
@@ -0,0 +1,33 @@
1
+ // Where a clip sits in the picture (plan 16 §16.8; ADR 0115, 10): in a window inside margins of 6% of the picture's
2
+ // shorter side, 14% at the bottom when captions are at the bottom, so they sit on the background; in a phone 88% of
3
+ // the picture's height; or, with no frame, as large as fits. Sizes are even, as H.264 needs.
4
+ const even = (value) => Math.max(2, 2 * Math.round(value / 2));
5
+ function fitted(clip, room) {
6
+ const scale = Math.min(room.width / clip.width, room.height / clip.height);
7
+ const width = even(clip.width * scale);
8
+ const height = even(clip.height * scale);
9
+ return { x: Math.round(room.x + (room.width - width) / 2), y: Math.round(room.y + (room.height - height) / 2), width, height };
10
+ }
11
+ export function windowLayout(picture, clip, captionsAtBottom) {
12
+ const shorter = Math.min(picture.width, picture.height);
13
+ const margin = 0.06 * shorter;
14
+ const bottom = captionsAtBottom ? 0.14 * shorter : margin;
15
+ const room = { x: margin, y: margin, width: picture.width - 2 * margin, height: picture.height - margin - bottom };
16
+ return { kind: 'window', screen: fitted(clip, room), radius: Math.round(0.015 * shorter), shadow: { offset: Math.round(0.02 * shorter), blur: Math.round(0.05 * shorter) } };
17
+ }
18
+ export function phoneLayout(picture, clip) {
19
+ let bodyHeight = 0.88 * picture.height;
20
+ let bezel = 0.022 * bodyHeight;
21
+ let screenHeight = bodyHeight - 2 * bezel;
22
+ let bodyWidth = (screenHeight * clip.width) / clip.height + 2 * bezel;
23
+ const widest = 0.88 * picture.width;
24
+ if (bodyWidth > widest) {
25
+ const shrink = widest / bodyWidth;
26
+ [bodyHeight, bezel, screenHeight, bodyWidth] = [bodyHeight * shrink, bezel * shrink, screenHeight * shrink, widest];
27
+ }
28
+ const body = { x: Math.round((picture.width - bodyWidth) / 2), y: Math.round((picture.height - bodyHeight) / 2), width: even(bodyWidth), height: even(bodyHeight) };
29
+ const screen = { x: Math.round(body.x + bezel), y: Math.round(body.y + bezel), width: even(bodyWidth - 2 * bezel), height: even(screenHeight) };
30
+ const bodyRadius = Math.round(0.12 * bodyWidth);
31
+ return { kind: 'phone', body, bodyRadius, screen, radius: Math.max(0, Math.round(bodyRadius - bezel)) };
32
+ }
33
+ //# sourceMappingURL=frame-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-layout.js","sourceRoot":"","sources":["../../src/compose/frame-layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { Rect, Size } from './frame-layout.ts';
2
+ export type PlacedOverlay = {
3
+ kind: 'callout' | 'highlight';
4
+ fromMs: number;
5
+ toMs: number;
6
+ input: number;
7
+ } | {
8
+ kind: 'blur';
9
+ fromMs: number;
10
+ toMs: number;
11
+ box: Rect;
12
+ };
13
+ /** A box kept inside the page, at least 2 × 2 pixels. */
14
+ export declare function insidePage(box: Rect, page: Size): Rect;
15
+ /** The filters that lay the overlays on the stream `input`, in order, and the stream they end in. */
16
+ export declare function overlayFilters(input: string, overlays: readonly PlacedOverlay[]): {
17
+ filters: string[];
18
+ output: string;
19
+ };
20
+ //# sourceMappingURL=overlays.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlays.d.ts","sourceRoot":"","sources":["../../src/compose/overlays.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAMpD,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,SAAS,GAAG,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,IAAI,CAAA;CAAE,CAAC;AAMvK,yDAAyD;AACzD,wBAAgB,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAItD;AAaD,qGAAqG;AACrG,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,aAAa,EAAE,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CASvH"}
@@ -0,0 +1,30 @@
1
+ const seconds = (ms) => (ms / 1000).toFixed(3);
2
+ const shown = (overlay) => `enable='between(t,${seconds(overlay.fromMs)},${seconds(overlay.toMs)})'`;
3
+ /** A box kept inside the page, at least 2 × 2 pixels. */
4
+ export function insidePage(box, page) {
5
+ const x = Math.min(Math.max(0, Math.round(box.x)), page.width - 2);
6
+ const y = Math.min(Math.max(0, Math.round(box.y)), page.height - 2);
7
+ return { x, y, width: Math.max(2, Math.min(Math.round(box.width), page.width - x)), height: Math.max(2, Math.min(Math.round(box.height), page.height - y)) };
8
+ }
9
+ function overlayFilter(overlay, from, to, index) {
10
+ if (overlay.kind === 'blur') {
11
+ const { x, y, width, height } = overlay.box;
12
+ const [whole, part, blurred] = [`ow${String(index)}`, `op${String(index)}`, `ob${String(index)}`];
13
+ return [`[${from}]split[${whole}][${part}]`, `[${part}]crop=${String(width)}:${String(height)}:${String(x)}:${String(y)},gblur=sigma=20[${blurred}]`, `[${whole}][${blurred}]overlay=${String(x)}:${String(y)}:${shown(overlay)}[${to}]`];
14
+ }
15
+ const image = `oi${String(index)}`;
16
+ const fades = overlay.kind === 'callout' ? `,fade=t=in:st=${seconds(overlay.fromMs)}:d=0.2:alpha=1,fade=t=out:st=${seconds(Math.max(overlay.fromMs, overlay.toMs - 200))}:d=0.2:alpha=1` : '';
17
+ return [`[${String(overlay.input)}:v]format=rgba${fades}[${image}]`, `[${from}][${image}]overlay=0:0:${shown(overlay)}[${to}]`];
18
+ }
19
+ /** The filters that lay the overlays on the stream `input`, in order, and the stream they end in. */
20
+ export function overlayFilters(input, overlays) {
21
+ let current = input;
22
+ const filters = [];
23
+ for (const [index, overlay] of overlays.entries()) {
24
+ const next = `oo${String(index)}`;
25
+ filters.push(...overlayFilter(overlay, current, next, index));
26
+ current = next;
27
+ }
28
+ return { filters, output: current };
29
+ }
30
+ //# sourceMappingURL=overlays.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlays.js","sourceRoot":"","sources":["../../src/compose/overlays.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { Browser } from 'playwright-core';
2
+ import type { Program } from '../programs/run-program.ts';
3
+ import type { Size } from './frame-layout.ts';
4
+ import type { Recording } from './scene-look.ts';
5
+ /** Whether a recording's shape differs from the size's by more than 1%. */
6
+ export declare function otherShape(recording: Recording, size: Size): boolean;
7
+ /** The CSS size of a page recorded again for `size`: its shorter side 720, its sides even. */
8
+ export declare function pageSizeFor(size: Size): Size;
9
+ export type PageAgain = {
10
+ root: string;
11
+ folder: string;
12
+ number: number;
13
+ recording: Recording;
14
+ narration: string | undefined;
15
+ size: Size;
16
+ browser: () => Promise<Browser>;
17
+ ffmpeg: Program;
18
+ ownPort: number | null;
19
+ signal: AbortSignal;
20
+ };
21
+ /**
22
+ * Records the page again and answers the new clip's path, with its sidecar beside it. The sidecar is a file of the
23
+ * workspace, so its page is checked as `record` checks one: an HTML file inside the workspace, with no `goto`.
24
+ */
25
+ export declare function recordPageAgain(again: PageAgain): Promise<string>;
26
+ //# sourceMappingURL=page-again.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-again.d.ts","sourceRoot":"","sources":["../../src/compose/page-again.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAO/C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAQjD,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAIpE;AAED,8FAA8F;AAC9F,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAG5C;AAED,MAAM,MAAM,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,CAAC;AAEzO;;;GAGG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBvE"}
@@ -0,0 +1,50 @@
1
+ import { copyFileSync, mkdirSync } from 'node:fs';
2
+ import { performance } from 'node:perf_hooks';
3
+ import path from 'node:path';
4
+ import { placeOutput, workFolder } from "../files/place-output.js";
5
+ import { readSidecar, sidecarPath, writeSidecar } from "../files/sidecar.js";
6
+ import { inputPath } from "../files/workspace-paths.js";
7
+ import { invalid } from "../problems.js";
8
+ import { cuesOf } from "../record/narration-cues.js";
9
+ import { record } from "../record/recorder.js";
10
+ // A recorded page at another shape (plan 16 §16.8; ADR 0115, 44): an HTML scene is laid out for its shape, so for a
11
+ // size of another shape it is recorded again, with the same steps, at that shape with its shorter side 720 CSS
12
+ // pixels, into the size's folder, its sidecar beside it.
13
+ const shorterSide = 720;
14
+ /** Whether a recording's shape differs from the size's by more than 1%. */
15
+ export function otherShape(recording, size) {
16
+ const recorded = recording.width / recording.height;
17
+ const wanted = size.width / size.height;
18
+ return Math.abs(recorded - wanted) / wanted > 0.01;
19
+ }
20
+ /** The CSS size of a page recorded again for `size`: its shorter side 720, its sides even. */
21
+ export function pageSizeFor(size) {
22
+ const even = (value) => 2 * Math.round(value / 2);
23
+ return size.width >= size.height ? { width: even((shorterSide * size.width) / size.height), height: shorterSide } : { width: shorterSide, height: even((shorterSide * size.height) / size.width) };
24
+ }
25
+ /**
26
+ * Records the page again and answers the new clip's path, with its sidecar beside it. The sidecar is a file of the
27
+ * workspace, so its page is checked as `record` checks one: an HTML file inside the workspace, with no `goto`.
28
+ */
29
+ export async function recordPageAgain(again) {
30
+ const file = inputPath(again.root, again.recording.source, ['.html', '.htm']);
31
+ if (again.recording.steps.some((step) => step.do === 'goto'))
32
+ throw invalid(`Scene ${String(again.number)}'s recorded page has a goto step, which a page can't have.`, { scene: again.number });
33
+ const page = pageSizeFor(again.size);
34
+ const narration = again.narration === undefined ? undefined : readSidecar(again.narration);
35
+ const cues = narration?.kind === 'narration' ? cuesOf(narration) : undefined;
36
+ const work = workFolder();
37
+ try {
38
+ const plan = { source: { kind: 'page', file }, steps: again.recording.steps, ...page, device: 'desktop', cues, folder: work.folder, ownPort: again.ownPort, ffmpeg: again.ffmpeg, signal: again.signal, now: () => performance.now(), stepStarted: () => undefined };
39
+ const recording = await record(await again.browser(), plan);
40
+ mkdirSync(again.folder, { recursive: true });
41
+ const clip = path.join(again.folder, `page-${String(again.number)}.mp4`);
42
+ copyFileSync(recording.video, clip);
43
+ placeOutput(writeSidecar(work.folder, { ...again.recording, ...page, durationMs: recording.durationMs, actions: recording.actions }), sidecarPath(clip));
44
+ return clip;
45
+ }
46
+ finally {
47
+ work.remove();
48
+ }
49
+ }
50
+ //# sourceMappingURL=page-again.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-again.js","sourceRoot":"","sources":["../../src/compose/page-again.ts"],"names":[],"mappings":"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"}
@@ -2,5 +2,9 @@ import { type Ctx } from '@kvman/sdk';
2
2
  export declare const largestCopyBytes: number;
3
3
  /** Whether a video of this size gets a copy for the player. */
4
4
  export declare function copied(bytes: number): boolean;
5
- export declare function keepCopy(ctx: Ctx, file: string, shownPath: string, bytes: number): Promise<string | null>;
5
+ export type PlayerCopy = {
6
+ fileId: string | null;
7
+ posterFileId: string | null;
8
+ };
9
+ export declare function keepCopy(ctx: Ctx, file: string, shownPath: string, bytes: number, poster: string): Promise<PlayerCopy>;
6
10
  //# sourceMappingURL=player-copies.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"player-copies.d.ts","sourceRoot":"","sources":["../../src/compose/player-copies.ts"],"names":[],"mappings":"AAEA,OAAO,EAAK,KAAK,GAAG,EAAE,MAAM,YAAY,CAAC;AAMzC,eAAO,MAAM,gBAAgB,QAAoB,CAAC;AAKlD,+DAA+D;AAC/D,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED,wBAAsB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAe/G"}
1
+ {"version":3,"file":"player-copies.d.ts","sourceRoot":"","sources":["../../src/compose/player-copies.ts"],"names":[],"mappings":"AAEA,OAAO,EAAK,KAAK,GAAG,EAAE,MAAM,YAAY,CAAC;AAMzC,eAAO,MAAM,gBAAgB,QAAoB,CAAC;AAKlD,+DAA+D;AAC/D,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED,MAAM,MAAM,UAAU,GAAG;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAQhF,wBAAsB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAU5H"}
@@ -1,31 +1,34 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { z } from '@kvman/sdk';
4
- // The player's copies (plan 16 §16.7, ADR 0107, 13 and 14): a video of at most 512 MiB is copied into the kernel's
5
- // files, so the chat's card can play it. A new copy of a path replaces the old one, and only the newest 10 copies of
6
- // the workspace are kept; the collection's ids rise with time, so the oldest come first.
4
+ // The player's copies (plan 16 §16.8; ADR 0107, 13 and 14; ADR 0115, 45): a video of at most 512 MiB is copied into
5
+ // the kernel's files with its poster, so the chat's card can play it. A new copy of a path replaces the old one, and
6
+ // only the newest 10 copies of the workspace are kept; the collection's ids rise with time, so the oldest come first.
7
7
  export const largestCopyBytes = 512 * 1024 * 1024;
8
8
  const kept = 10;
9
- const playerSchema = z.object({ path: z.string(), fileId: z.string(), createdAt: z.string() });
9
+ const playerSchema = z.object({ path: z.string(), fileId: z.string(), posterFileId: z.string().nullable(), createdAt: z.string() });
10
10
  /** Whether a video of this size gets a copy for the player. */
11
11
  export function copied(bytes) {
12
12
  return bytes <= largestCopyBytes;
13
13
  }
14
- export async function keepCopy(ctx, file, shownPath, bytes) {
14
+ async function forget(ctx, row) {
15
+ await ctx.files.unlink(row.fileId);
16
+ if (row.posterFileId !== null)
17
+ await ctx.files.unlink(row.posterFileId);
18
+ await ctx.store.collection('players', playerSchema).delete(row.id);
19
+ }
20
+ export async function keepCopy(ctx, file, shownPath, bytes, poster) {
15
21
  if (!copied(bytes))
16
- return null;
22
+ return { fileId: null, posterFileId: null };
17
23
  const players = ctx.store.collection('players', playerSchema);
18
24
  const copy = await ctx.files.write(path.basename(file), readFileSync(file), 'video/mp4');
19
- for (const old of await players.find({ path: shownPath }, { limit: 1000 })) {
20
- await ctx.files.unlink(old.fileId);
21
- await players.delete(old.id);
22
- }
23
- await players.insert({ path: shownPath, fileId: copy.id, createdAt: new Date().toISOString() });
25
+ const posterCopy = await ctx.files.write(path.basename(poster), readFileSync(poster), 'image/jpeg');
26
+ for (const old of await players.find({ path: shownPath }, { limit: 1000 }))
27
+ await forget(ctx, old);
28
+ await players.insert({ path: shownPath, fileId: copy.id, posterFileId: posterCopy.id, createdAt: new Date().toISOString() });
24
29
  const all = await players.find({}, { limit: 1000, order: 'asc' });
25
- for (const old of all.slice(0, Math.max(0, all.length - kept))) {
26
- await ctx.files.unlink(old.fileId);
27
- await players.delete(old.id);
28
- }
29
- return copy.id;
30
+ for (const old of all.slice(0, Math.max(0, all.length - kept)))
31
+ await forget(ctx, old);
32
+ return { fileId: copy.id, posterFileId: posterCopy.id };
30
33
  }
31
34
  //# sourceMappingURL=player-copies.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"player-copies.js","sourceRoot":"","sources":["../../src/compose/player-copies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,CAAC,EAAY,MAAM,YAAY,CAAC;AAEzC,mHAAmH;AACnH,qHAAqH;AACrH,yFAAyF;AAEzF,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAClD,MAAM,IAAI,GAAG,EAAE,CAAC;AAEhB,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAE/F,+DAA+D;AAC/D,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO,KAAK,IAAI,gBAAgB,CAAC;AACnC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,GAAQ,EAAE,IAAY,EAAE,SAAiB,EAAE,KAAa;IACrF,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;IACzF,KAAK,MAAM,GAAG,IAAI,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAC3E,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IACD,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAChG,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC;QAC/D,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,CAAC,EAAE,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"player-copies.js","sourceRoot":"","sources":["../../src/compose/player-copies.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"register-compose.d.ts","sourceRoot":"","sources":["../../src/compose/register-compose.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAkCtC,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CA+B9C"}
1
+ {"version":3,"file":"register-compose.d.ts","sourceRoot":"","sources":["../../src/compose/register-compose.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAyDtC,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAuF9C"}