egaki 0.6.0 → 0.7.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 (271) hide show
  1. package/CHANGELOG.md +145 -9
  2. package/dist/cli/cache-utils.d.ts +33 -0
  3. package/dist/cli/cache-utils.d.ts.map +1 -0
  4. package/dist/cli/cache-utils.js +157 -0
  5. package/dist/cli/cached-generate.d.ts +105 -0
  6. package/dist/cli/cached-generate.d.ts.map +1 -0
  7. package/dist/cli/cached-generate.js +208 -0
  8. package/dist/cli/cached-generate.test.d.ts +11 -0
  9. package/dist/cli/cached-generate.test.d.ts.map +1 -0
  10. package/dist/cli/cached-generate.test.js +431 -0
  11. package/dist/cli/cartesia-provider.d.ts +16 -0
  12. package/dist/cli/cartesia-provider.d.ts.map +1 -0
  13. package/dist/cli/cartesia-provider.js +184 -0
  14. package/dist/cli/chatgpt-auth.d.ts.map +1 -0
  15. package/dist/cli/cli.d.ts +4 -0
  16. package/dist/cli/cli.d.ts.map +1 -0
  17. package/dist/{cli.js → cli/cli.js} +626 -600
  18. package/dist/cli/clone-generate.d.ts +26 -0
  19. package/dist/cli/clone-generate.d.ts.map +1 -0
  20. package/dist/cli/clone-generate.js +148 -0
  21. package/dist/cli/credentials.d.ts.map +1 -0
  22. package/dist/{credentials.js → cli/credentials.js} +20 -0
  23. package/dist/cli/elevenlabs-speech-provider.d.ts +26 -0
  24. package/dist/cli/elevenlabs-speech-provider.d.ts.map +1 -0
  25. package/dist/cli/elevenlabs-speech-provider.js +199 -0
  26. package/dist/cli/generate.d.ts +140 -0
  27. package/dist/cli/generate.d.ts.map +1 -0
  28. package/dist/cli/generate.js +604 -0
  29. package/dist/cli/generate.test.d.ts +2 -0
  30. package/dist/cli/generate.test.d.ts.map +1 -0
  31. package/dist/cli/generate.test.js +131 -0
  32. package/dist/cli/login.d.ts.map +1 -0
  33. package/dist/cli/main.d.ts +3 -0
  34. package/dist/cli/main.d.ts.map +1 -0
  35. package/dist/cli/main.js +7 -0
  36. package/dist/{model-catalog.d.ts → cli/model-catalog.d.ts} +6 -0
  37. package/dist/cli/model-catalog.d.ts.map +1 -0
  38. package/dist/{model-catalog.js → cli/model-catalog.js} +714 -4
  39. package/dist/{models.d.ts → cli/models.d.ts} +12 -5
  40. package/dist/cli/models.d.ts.map +1 -0
  41. package/dist/{models.js → cli/models.js} +58 -57
  42. package/dist/cli/open-browser.d.ts.map +1 -0
  43. package/dist/cli/openai-speech-provider.d.ts +3 -0
  44. package/dist/cli/openai-speech-provider.d.ts.map +1 -0
  45. package/dist/cli/openai-speech-provider.js +28 -0
  46. package/dist/cli/separation-generate.d.ts +34 -0
  47. package/dist/cli/separation-generate.d.ts.map +1 -0
  48. package/dist/cli/separation-generate.js +117 -0
  49. package/dist/cli/speech-catalog.d.ts +35 -0
  50. package/dist/cli/speech-catalog.d.ts.map +1 -0
  51. package/dist/cli/speech-catalog.js +195 -0
  52. package/dist/cli/speech-generate.d.ts +86 -0
  53. package/dist/cli/speech-generate.d.ts.map +1 -0
  54. package/dist/cli/speech-generate.js +156 -0
  55. package/dist/cli/speech-models.d.ts +8 -0
  56. package/dist/cli/speech-models.d.ts.map +1 -0
  57. package/dist/cli/speech-models.js +109 -0
  58. package/dist/cli/speech-provider.test.d.ts +2 -0
  59. package/dist/cli/speech-provider.test.d.ts.map +1 -0
  60. package/dist/cli/speech-provider.test.js +197 -0
  61. package/dist/cli/subscription.d.ts.map +1 -0
  62. package/dist/cli/transcription-catalog.d.ts +35 -0
  63. package/dist/cli/transcription-catalog.d.ts.map +1 -0
  64. package/dist/cli/transcription-catalog.js +192 -0
  65. package/dist/cli/transcription-generate.d.ts +62 -0
  66. package/dist/cli/transcription-generate.d.ts.map +1 -0
  67. package/dist/cli/transcription-generate.js +144 -0
  68. package/dist/cli/transcription-models.d.ts +9 -0
  69. package/dist/cli/transcription-models.d.ts.map +1 -0
  70. package/dist/cli/transcription-models.js +100 -0
  71. package/dist/cli/transcription-providers.d.ts +22 -0
  72. package/dist/cli/transcription-providers.d.ts.map +1 -0
  73. package/dist/cli/transcription-providers.js +290 -0
  74. package/dist/cli/video-model-catalog.d.ts.map +1 -0
  75. package/dist/cli/xai-auth.d.ts.map +1 -0
  76. package/dist/{xai-auth.js → cli/xai-auth.js} +1 -1
  77. package/dist/vite/angled-screen.d.ts +57 -0
  78. package/dist/vite/angled-screen.d.ts.map +1 -0
  79. package/dist/vite/angled-screen.js +115 -0
  80. package/dist/vite/app.d.ts +51 -0
  81. package/dist/vite/app.d.ts.map +1 -0
  82. package/dist/vite/app.js +150 -0
  83. package/dist/vite/bands-shader.d.ts +47 -0
  84. package/dist/vite/bands-shader.d.ts.map +1 -0
  85. package/dist/vite/bands-shader.js +267 -0
  86. package/dist/vite/code-block.d.ts +107 -0
  87. package/dist/vite/code-block.d.ts.map +1 -0
  88. package/dist/vite/code-block.js +641 -0
  89. package/dist/vite/components.d.ts +36 -0
  90. package/dist/vite/components.d.ts.map +1 -0
  91. package/dist/vite/components.js +113 -0
  92. package/dist/vite/dispersion-rings-shader.d.ts +44 -0
  93. package/dist/vite/dispersion-rings-shader.d.ts.map +1 -0
  94. package/dist/vite/dispersion-rings-shader.js +282 -0
  95. package/dist/vite/easing-curves.d.ts +129 -0
  96. package/dist/vite/easing-curves.d.ts.map +1 -0
  97. package/dist/vite/easing-curves.js +584 -0
  98. package/dist/vite/easing-curves.test.d.ts +2 -0
  99. package/dist/vite/easing-curves.test.d.ts.map +1 -0
  100. package/dist/vite/easing-curves.test.js +200 -0
  101. package/dist/vite/generated-media-client.d.ts +35 -0
  102. package/dist/vite/generated-media-client.d.ts.map +1 -0
  103. package/dist/vite/generated-media-client.js +76 -0
  104. package/dist/vite/keyframes.d.ts +129 -0
  105. package/dist/vite/keyframes.d.ts.map +1 -0
  106. package/dist/vite/keyframes.js +194 -0
  107. package/dist/vite/layout-editor.d.ts +45 -0
  108. package/dist/vite/layout-editor.d.ts.map +1 -0
  109. package/dist/vite/layout-editor.js +587 -0
  110. package/dist/vite/layout-transition.d.ts +136 -0
  111. package/dist/vite/layout-transition.d.ts.map +1 -0
  112. package/dist/vite/layout-transition.js +581 -0
  113. package/dist/vite/liquid-gradient-shader.d.ts +62 -0
  114. package/dist/vite/liquid-gradient-shader.d.ts.map +1 -0
  115. package/dist/vite/liquid-gradient-shader.js +413 -0
  116. package/dist/vite/mdx-client.d.ts +15 -0
  117. package/dist/vite/mdx-client.d.ts.map +1 -0
  118. package/dist/vite/mdx-client.js +265 -0
  119. package/dist/vite/mdx-parse.d.ts +102 -0
  120. package/dist/vite/mdx-parse.d.ts.map +1 -0
  121. package/dist/vite/mdx-parse.js +251 -0
  122. package/dist/vite/mdx-provided-components.d.ts +18 -0
  123. package/dist/vite/mdx-provided-components.d.ts.map +1 -0
  124. package/dist/vite/mdx-provided-components.js +14 -0
  125. package/dist/vite/mdx-video.d.ts +327 -0
  126. package/dist/vite/mdx-video.d.ts.map +1 -0
  127. package/dist/vite/mdx-video.js +596 -0
  128. package/dist/vite/mdx-video.test.d.ts +9 -0
  129. package/dist/vite/mdx-video.test.d.ts.map +1 -0
  130. package/dist/vite/mdx-video.test.js +1945 -0
  131. package/dist/vite/media-components.d.ts +45 -0
  132. package/dist/vite/media-components.d.ts.map +1 -0
  133. package/dist/vite/media-components.js +355 -0
  134. package/dist/vite/media-duration-store.d.ts +66 -0
  135. package/dist/vite/media-duration-store.d.ts.map +1 -0
  136. package/dist/vite/media-duration-store.js +204 -0
  137. package/dist/vite/player-page.d.ts +35 -0
  138. package/dist/vite/player-page.d.ts.map +1 -0
  139. package/dist/vite/player-page.js +720 -0
  140. package/dist/vite/render-client.d.ts +18 -0
  141. package/dist/vite/render-client.d.ts.map +1 -0
  142. package/dist/vite/render-client.js +67 -0
  143. package/dist/vite/sdk.d.ts +221 -0
  144. package/dist/vite/sdk.d.ts.map +1 -0
  145. package/dist/vite/sdk.js +355 -0
  146. package/dist/vite/server-components.d.ts +55 -0
  147. package/dist/vite/server-components.d.ts.map +1 -0
  148. package/dist/vite/server-components.js +156 -0
  149. package/dist/vite/server-mdx.d.ts +74 -0
  150. package/dist/vite/server-mdx.d.ts.map +1 -0
  151. package/dist/vite/server-mdx.js +259 -0
  152. package/dist/vite/shader-renderer.d.ts +48 -0
  153. package/dist/vite/shader-renderer.d.ts.map +1 -0
  154. package/dist/vite/shader-renderer.js +411 -0
  155. package/dist/vite/store-hooks.d.ts +12 -0
  156. package/dist/vite/store-hooks.d.ts.map +1 -0
  157. package/dist/vite/store-hooks.js +93 -0
  158. package/dist/vite/store.d.ts +75 -0
  159. package/dist/vite/store.d.ts.map +1 -0
  160. package/dist/vite/store.js +52 -0
  161. package/dist/vite/store.test.d.ts +6 -0
  162. package/dist/vite/store.test.d.ts.map +1 -0
  163. package/dist/vite/store.test.js +172 -0
  164. package/dist/vite/tweakpane-hook.d.ts +94 -0
  165. package/dist/vite/tweakpane-hook.d.ts.map +1 -0
  166. package/dist/vite/tweakpane-hook.js +450 -0
  167. package/dist/vite/vite-plugin.d.ts +28 -0
  168. package/dist/vite/vite-plugin.d.ts.map +1 -0
  169. package/dist/vite/vite-plugin.js +539 -0
  170. package/dist/vite/wave-gradient-shader.d.ts +41 -0
  171. package/dist/vite/wave-gradient-shader.d.ts.map +1 -0
  172. package/dist/vite/wave-gradient-shader.js +207 -0
  173. package/package.json +101 -24
  174. package/skills/egaki/SKILL.md +162 -1
  175. package/src/cli/cache-utils.ts +169 -0
  176. package/src/cli/cached-generate.test.ts +531 -0
  177. package/src/cli/cached-generate.ts +348 -0
  178. package/src/cli/cartesia-provider.ts +249 -0
  179. package/src/{cli.ts → cli/cli.ts} +836 -884
  180. package/src/cli/clone-generate.ts +202 -0
  181. package/src/{credentials.ts → cli/credentials.ts} +20 -0
  182. package/src/cli/elevenlabs-speech-provider.ts +243 -0
  183. package/src/cli/generate.test.ts +166 -0
  184. package/src/cli/generate.ts +912 -0
  185. package/src/cli/main.ts +8 -0
  186. package/src/{model-catalog.ts → cli/model-catalog.ts} +787 -58
  187. package/src/{models.ts → cli/models.ts} +65 -86
  188. package/src/cli/openai-speech-provider.ts +34 -0
  189. package/src/cli/separation-generate.ts +187 -0
  190. package/src/cli/speech-catalog.ts +253 -0
  191. package/src/cli/speech-generate.ts +275 -0
  192. package/src/cli/speech-models.ts +146 -0
  193. package/src/cli/speech-provider.test.ts +226 -0
  194. package/src/cli/transcription-catalog.ts +250 -0
  195. package/src/cli/transcription-generate.ts +214 -0
  196. package/src/cli/transcription-models.ts +136 -0
  197. package/src/cli/transcription-providers.ts +427 -0
  198. package/src/{xai-auth.ts → cli/xai-auth.ts} +1 -1
  199. package/src/vite/angled-screen.tsx +214 -0
  200. package/src/vite/app.tsx +197 -0
  201. package/src/vite/bands-shader.tsx +319 -0
  202. package/src/vite/code-block.tsx +939 -0
  203. package/src/vite/components.tsx +278 -0
  204. package/src/vite/dispersion-rings-shader.tsx +331 -0
  205. package/src/vite/easing-curves.test.ts +232 -0
  206. package/src/vite/easing-curves.ts +743 -0
  207. package/src/vite/generated-media-client.tsx +121 -0
  208. package/src/vite/keyframes.tsx +347 -0
  209. package/src/vite/layout-editor.tsx +709 -0
  210. package/src/vite/layout-transition.tsx +706 -0
  211. package/src/vite/liquid-gradient-shader.tsx +474 -0
  212. package/src/vite/mdx-client.tsx +420 -0
  213. package/src/vite/mdx-parse.ts +343 -0
  214. package/src/vite/mdx-provided-components.ts +19 -0
  215. package/src/vite/mdx-video.test.tsx +2219 -0
  216. package/src/vite/mdx-video.tsx +976 -0
  217. package/src/vite/media-components.tsx +473 -0
  218. package/src/vite/media-duration-store.ts +225 -0
  219. package/src/vite/motion-timing.ts +194 -0
  220. package/src/vite/player-page.tsx +1107 -0
  221. package/src/vite/render-client.ts +84 -0
  222. package/src/vite/sdk.ts +598 -0
  223. package/src/vite/server-components.tsx +241 -0
  224. package/src/vite/server-mdx.ts +285 -0
  225. package/src/vite/shader-renderer.tsx +535 -0
  226. package/src/vite/store-hooks.ts +117 -0
  227. package/src/vite/store.test.ts +202 -0
  228. package/src/vite/store.ts +115 -0
  229. package/src/vite/styles.css +21 -0
  230. package/src/vite/tweakpane-hook.tsx +593 -0
  231. package/src/vite/virtual-modules.d.ts +30 -0
  232. package/src/vite/vite-plugin.ts +585 -0
  233. package/src/vite/wave-gradient-shader.tsx +253 -0
  234. package/README.md +0 -370
  235. package/dist/antigravity-auth.d.ts +0 -15
  236. package/dist/antigravity-auth.d.ts.map +0 -1
  237. package/dist/antigravity-auth.js +0 -544
  238. package/dist/antigravity-image.d.ts +0 -11
  239. package/dist/antigravity-image.d.ts.map +0 -1
  240. package/dist/antigravity-image.js +0 -285
  241. package/dist/chatgpt-auth.d.ts.map +0 -1
  242. package/dist/cli.d.ts +0 -3
  243. package/dist/cli.d.ts.map +0 -1
  244. package/dist/credentials.d.ts.map +0 -1
  245. package/dist/gateway-model-candidates.generated.d.ts +0 -11
  246. package/dist/gateway-model-candidates.generated.d.ts.map +0 -1
  247. package/dist/gateway-model-candidates.generated.js +0 -64
  248. package/dist/login.d.ts.map +0 -1
  249. package/dist/model-catalog.d.ts.map +0 -1
  250. package/dist/models.d.ts.map +0 -1
  251. package/dist/open-browser.d.ts.map +0 -1
  252. package/dist/subscription.d.ts.map +0 -1
  253. package/dist/video-model-catalog.d.ts.map +0 -1
  254. package/dist/xai-auth.d.ts.map +0 -1
  255. /package/dist/{chatgpt-auth.d.ts → cli/chatgpt-auth.d.ts} +0 -0
  256. /package/dist/{chatgpt-auth.js → cli/chatgpt-auth.js} +0 -0
  257. /package/dist/{credentials.d.ts → cli/credentials.d.ts} +0 -0
  258. /package/dist/{login.d.ts → cli/login.d.ts} +0 -0
  259. /package/dist/{login.js → cli/login.js} +0 -0
  260. /package/dist/{open-browser.d.ts → cli/open-browser.d.ts} +0 -0
  261. /package/dist/{open-browser.js → cli/open-browser.js} +0 -0
  262. /package/dist/{subscription.d.ts → cli/subscription.d.ts} +0 -0
  263. /package/dist/{subscription.js → cli/subscription.js} +0 -0
  264. /package/dist/{video-model-catalog.d.ts → cli/video-model-catalog.d.ts} +0 -0
  265. /package/dist/{video-model-catalog.js → cli/video-model-catalog.js} +0 -0
  266. /package/dist/{xai-auth.d.ts → cli/xai-auth.d.ts} +0 -0
  267. /package/src/{chatgpt-auth.ts → cli/chatgpt-auth.ts} +0 -0
  268. /package/src/{login.ts → cli/login.ts} +0 -0
  269. /package/src/{open-browser.ts → cli/open-browser.ts} +0 -0
  270. /package/src/{subscription.ts → cli/subscription.ts} +0 -0
  271. /package/src/{video-model-catalog.ts → cli/video-model-catalog.ts} +0 -0
@@ -0,0 +1,343 @@
1
+ /**
2
+ * Server-safe MDX parsing utilities for the video framework.
3
+ *
4
+ * This module contains ONLY parsing logic (frontmatter, section splitting,
5
+ * duration calculation). It does NOT import remotion or any client-only
6
+ * modules, so it can safely run in the RSC server environment.
7
+ *
8
+ * The rendering counterpart is mdx-video.tsx which re-exports these
9
+ * functions alongside the Remotion components.
10
+ */
11
+
12
+ import YAML from 'yaml'
13
+
14
+ // Inline mdast types to avoid requiring @types/mdast as a dependency
15
+ type RootContent = any
16
+ type Root = { type: 'root'; children: RootContent[] }
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Constants
20
+ // ---------------------------------------------------------------------------
21
+
22
+ const DEFAULT_FPS = 30
23
+ const DEFAULT_BPM = 120
24
+ const DEFAULT_SECTION_BEATS = 10
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Frontmatter parsing
28
+ // ---------------------------------------------------------------------------
29
+
30
+ const DEFAULT_WIDTH = 1920
31
+ const DEFAULT_HEIGHT = 1080
32
+
33
+ export interface VideoFrontmatter {
34
+ fps: number
35
+ bpm: number
36
+ width: number
37
+ height: number
38
+ /** Pixel density / scale multiplier for rendering. Default 1.
39
+ * Setting scale=2 renders at 2x resolution (e.g. 3840×2160 for a 1920×1080 comp). */
40
+ scale: number
41
+ }
42
+
43
+ /** Scope variables injected into MDX expressions via safe-mdx's `scope` prop.
44
+ * Both server (app.tsx) and client (mdx-client.tsx) must pass the same shape. */
45
+ export interface MdxScope {
46
+ FPS: number
47
+ BEAT: number
48
+ // JS globals available in MDX expressions (safe, side-effect-free)
49
+ Math: typeof Math
50
+ Number: typeof Number
51
+ parseInt: typeof parseInt
52
+ parseFloat: typeof parseFloat
53
+ Infinity: typeof Infinity
54
+ NaN: typeof NaN
55
+ Boolean: typeof Boolean
56
+ String: typeof String
57
+ Array: typeof Array
58
+ Object: typeof Object
59
+ JSON: typeof JSON
60
+ }
61
+
62
+ /** Build the MDX scope from frontmatter values.
63
+ * Includes JS globals so MDX expressions can use Math.round(), parseInt(), etc. */
64
+ export function buildMdxScope(fps: number, bpm: number): MdxScope {
65
+ return {
66
+ FPS: fps,
67
+ BEAT: fps / (bpm / 60),
68
+ Math,
69
+ Number,
70
+ parseInt,
71
+ parseFloat,
72
+ Infinity,
73
+ NaN,
74
+ Boolean,
75
+ String,
76
+ Array,
77
+ Object,
78
+ JSON,
79
+ }
80
+ }
81
+
82
+ /** Parse YAML frontmatter from mdast. Extracts fps, bpm, width, height, scale. */
83
+ export function parseFrontmatter(mdast: Root): VideoFrontmatter {
84
+ const result: VideoFrontmatter = {
85
+ fps: DEFAULT_FPS,
86
+ bpm: DEFAULT_BPM,
87
+ width: DEFAULT_WIDTH,
88
+ height: DEFAULT_HEIGHT,
89
+ scale: 1,
90
+ }
91
+ for (const node of mdast.children) {
92
+ if (node.type !== 'yaml') continue
93
+ const text = (node as any).value as string
94
+ if (!text) continue
95
+ const parsed = YAML.parse(text)
96
+ if (parsed && typeof parsed === 'object') {
97
+ if (typeof parsed.fps === 'number' && parsed.fps > 0) result.fps = parsed.fps
98
+ if (typeof parsed.bpm === 'number' && parsed.bpm > 0) result.bpm = parsed.bpm
99
+ if (typeof parsed.width === 'number') {
100
+ const w = Math.round(parsed.width)
101
+ if (w > 0) result.width = w
102
+ }
103
+ if (typeof parsed.height === 'number') {
104
+ const h = Math.round(parsed.height)
105
+ if (h > 0) result.height = h
106
+ }
107
+ if (typeof parsed.scale === 'number' && parsed.scale > 0) result.scale = parsed.scale
108
+ }
109
+ }
110
+ return result
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Aspect ratio from composition dimensions
115
+ // ---------------------------------------------------------------------------
116
+
117
+ const STANDARD_RATIOS: [number, number][] = [
118
+ [1, 1], [3, 4], [4, 3], [9, 16], [16, 9],
119
+ [2, 3], [3, 2], [4, 5], [5, 4], [9, 21], [21, 9],
120
+ ]
121
+
122
+ function gcd(a: number, b: number): number {
123
+ while (b) { [a, b] = [b, a % b] }
124
+ return a
125
+ }
126
+
127
+ function parseRatio(s: string): [number, number] | undefined {
128
+ const [a, b] = s.split(':').map(Number)
129
+ if (a && b && a > 0 && b > 0) return [a, b]
130
+ }
131
+
132
+ /** Compute the closest aspect ratio string from pixel dimensions.
133
+ * When `allowedRatios` is provided (e.g. from a model's supported list),
134
+ * picks the best match from that list. Otherwise uses STANDARD_RATIOS.
135
+ * Reduces to the exact ratio first (e.g. 1920×1080 → 16:9). If the exact
136
+ * ratio doesn't match, picks the closest by comparing decimal values.
137
+ * Returns '16:9' for invalid inputs. */
138
+ export function aspectRatioFromDimensions(width: number, height: number, allowedRatios?: string[]): string {
139
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
140
+ return '16:9'
141
+ }
142
+ const candidates: [number, number][] = allowedRatios?.length
143
+ ? allowedRatios.map(parseRatio).filter((r): r is [number, number] => r !== undefined)
144
+ : STANDARD_RATIOS
145
+ if (candidates.length === 0) return '16:9'
146
+
147
+ const d = gcd(width, height)
148
+ const rw = width / d
149
+ const rh = height / d
150
+ // Check exact match first
151
+ if (candidates.some(([w, h]) => w === rw && h === rh)) {
152
+ return `${rw}:${rh}`
153
+ }
154
+ // Find closest by decimal value
155
+ const target = width / height
156
+ let best = candidates[0]!
157
+ let bestDiff = Infinity
158
+ for (const ratio of candidates) {
159
+ const diff = Math.abs(ratio[0] / ratio[1] - target)
160
+ if (diff < bestDiff) {
161
+ bestDiff = diff
162
+ best = ratio
163
+ }
164
+ }
165
+ return `${best[0]}:${best[1]}`
166
+ }
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // Duration parsing from heading text
170
+ // ---------------------------------------------------------------------------
171
+
172
+ const HEADING_PROP_RE = /\s+(duration)=(\d+(?:\.\d+)?)(s|fps|frames?|f|beats?)?/gi
173
+
174
+ interface ParsedHeading {
175
+ label: string
176
+ durationInFrames: number | null
177
+ }
178
+
179
+ function parseHeadingProps(
180
+ rawText: string,
181
+ fps: number,
182
+ bpm: number,
183
+ ): ParsedHeading {
184
+ let label = rawText
185
+ let durationInFrames: number | null = null
186
+ const framesPerBeat = fps / (bpm / 60)
187
+
188
+ // Strip all key=value props from the heading text
189
+ label = label.replace(HEADING_PROP_RE, (_match, key, value, unit) => {
190
+ const v = Number(value)
191
+ const u = (unit || '').toLowerCase()
192
+ let frames: number
193
+ if (u === 's') {
194
+ frames = Math.round(v * fps)
195
+ } else if (u === 'beat' || u === 'beats') {
196
+ frames = Math.round(v * framesPerBeat)
197
+ } else {
198
+ // bare number, fps, f, frame, frames — all mean raw frames
199
+ frames = Math.round(v)
200
+ }
201
+
202
+ if (key.toLowerCase() === 'duration') {
203
+ durationInFrames = frames
204
+ }
205
+ return ''
206
+ }).trim()
207
+
208
+ return { label: label || 'Untitled', durationInFrames }
209
+ }
210
+
211
+ // ---------------------------------------------------------------------------
212
+ // Section splitting
213
+ // ---------------------------------------------------------------------------
214
+
215
+ export interface MdxSection {
216
+ heading: string | null
217
+ nodes: RootContent[]
218
+ /** Duration in frames. `null` means "auto-infer from media content". */
219
+ durationInFrames: number | null
220
+ }
221
+
222
+ export interface SplitResult {
223
+ sections: MdxSection[]
224
+ frontmatter: VideoFrontmatter
225
+ /** ESM import nodes from the document, needed by SafeMdxRenderer to resolve modules */
226
+ imports: RootContent[]
227
+ /** Content nodes before the first heading. Rendered at composition level,
228
+ * outside the Series, spanning the full video duration. Use for soundtracks,
229
+ * ambient background videos, or any component that should persist across
230
+ * all sections. */
231
+ preamble: RootContent[]
232
+ }
233
+
234
+ function extractHeadingText(node: RootContent): string {
235
+ if (node.type !== 'heading') return ''
236
+ const parts: string[] = []
237
+ for (const child of (node as any).children || []) {
238
+ if (child.type === 'text') parts.push(child.value)
239
+ }
240
+ return parts.join('') || 'Untitled'
241
+ }
242
+
243
+ export function splitIntoSections(mdast: Root): SplitResult {
244
+ const frontmatter = parseFrontmatter(mdast)
245
+ const { fps, bpm } = frontmatter
246
+ const framesPerBeat = fps / (bpm / 60)
247
+ const defaultDuration = Math.round(DEFAULT_SECTION_BEATS * framesPerBeat)
248
+
249
+ const sections: MdxSection[] = []
250
+ const imports: RootContent[] = []
251
+ const preamble: RootContent[] = []
252
+ let current: MdxSection | null = null
253
+ let beforeFirstHeading = true
254
+
255
+ for (const node of mdast.children) {
256
+ if (node.type === 'yaml' || node.type === 'toml') {
257
+ continue
258
+ }
259
+ if (node.type === 'mdxjsEsm') {
260
+ imports.push(node)
261
+ continue
262
+ }
263
+
264
+ if (node.type === 'heading') {
265
+ beforeFirstHeading = false
266
+ const rawText = extractHeadingText(node)
267
+ const parsed = parseHeadingProps(rawText, fps, bpm)
268
+ current = {
269
+ heading: parsed.label,
270
+ nodes: [],
271
+ durationInFrames: parsed.durationInFrames ?? null,
272
+ }
273
+ sections.push(current)
274
+ continue
275
+ }
276
+
277
+ // Content before the first heading goes into the preamble, which is
278
+ // rendered at composition level (outside Series) so it spans the
279
+ // full video duration.
280
+ if (beforeFirstHeading) {
281
+ preamble.push(node)
282
+ } else if (current) {
283
+ current.nodes.push(node)
284
+ }
285
+ }
286
+
287
+ return { sections, frontmatter, imports, preamble }
288
+ }
289
+
290
+ /** Calculate total composition duration.
291
+ * All sections must have resolved (non-null) durations. */
292
+ export function calculateTotalDuration(sections: { durationInFrames: number }[]): number {
293
+ let total = 0
294
+ for (const s of sections) {
295
+ total += s.durationInFrames
296
+ }
297
+ return total
298
+ }
299
+
300
+ // ---------------------------------------------------------------------------
301
+ // Auto-duration resolution
302
+ // ---------------------------------------------------------------------------
303
+
304
+ /** MdxSection with a guaranteed non-null durationInFrames. */
305
+ export type ResolvedMdxSection = MdxSection & { durationInFrames: number }
306
+
307
+ /**
308
+ * Resolve null (auto) durations using per-section media durations.
309
+ *
310
+ * `sectionDurations` is keyed by section index (as string) → max media
311
+ * duration in seconds. Audio/Video components populate this at runtime
312
+ * via the media duration store after fetching metadata with mediabunny.
313
+ *
314
+ * For each section with `durationInFrames === null`:
315
+ * 1. Look up the section's index in `sectionDurations`.
316
+ * 2. If found, set duration = Math.round(seconds * fps).
317
+ * 3. Otherwise fall back to DEFAULT_SECTION_BEATS * framesPerBeat.
318
+ *
319
+ * Sections with explicit durations are returned unchanged.
320
+ * Generic so extra fields (like `jsx`) are preserved in the return type.
321
+ */
322
+ export function resolveAutoDurations<T extends { durationInFrames: number | null }>(
323
+ sections: T[],
324
+ fps: number,
325
+ bpm: number,
326
+ sectionDurations: Record<string, number> = {},
327
+ ): (T & { durationInFrames: number })[] {
328
+ const framesPerBeat = fps / (bpm / 60)
329
+ const defaultDuration = Math.round(DEFAULT_SECTION_BEATS * framesPerBeat)
330
+
331
+ return sections.map((section, i): T & { durationInFrames: number } => {
332
+ if (section.durationInFrames !== null) {
333
+ return section as T & { durationInFrames: number }
334
+ }
335
+
336
+ const seconds = sectionDurations[String(i)]
337
+ const durationInFrames = seconds !== undefined && seconds > 0
338
+ ? Math.round(seconds * fps)
339
+ : defaultDuration
340
+
341
+ return { ...section, durationInFrames }
342
+ })
343
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Global MDXProvidedComponents type for MDX LSP support.
3
+ *
4
+ * The MDX analyzer (mdx-analyzer / vscode-mdx) uses the special global type
5
+ * `MDXProvidedComponents` to know which components are available in MDX files
6
+ * without explicit imports. This file declares that type from the same
7
+ * MDX_BUILTIN_COMPONENTS map that the runtime uses, keeping them in sync.
8
+ *
9
+ * Projects opt in by having an `egaki-env.d.ts` with:
10
+ * /// <reference types="egaki/mdx-components" />
11
+ *
12
+ * The egaki vite plugin auto-generates this file on first run.
13
+ */
14
+
15
+ import { MDX_BUILTIN_COMPONENTS } from './mdx-video.tsx'
16
+
17
+ declare global {
18
+ type MDXProvidedComponents = typeof MDX_BUILTIN_COMPONENTS
19
+ }