egaki 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. package/CHANGELOG.md +188 -0
  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/cli.js +1329 -0
  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/{credentials.d.ts → cli/credentials.d.ts} +11 -2
  22. package/dist/cli/credentials.d.ts.map +1 -0
  23. package/dist/{credentials.js → cli/credentials.js} +88 -5
  24. package/dist/cli/elevenlabs-speech-provider.d.ts +26 -0
  25. package/dist/cli/elevenlabs-speech-provider.d.ts.map +1 -0
  26. package/dist/cli/elevenlabs-speech-provider.js +199 -0
  27. package/dist/cli/generate.d.ts +140 -0
  28. package/dist/cli/generate.d.ts.map +1 -0
  29. package/dist/cli/generate.js +604 -0
  30. package/dist/cli/generate.test.d.ts +2 -0
  31. package/dist/cli/generate.test.d.ts.map +1 -0
  32. package/dist/cli/generate.test.js +131 -0
  33. package/dist/cli/login.d.ts.map +1 -0
  34. package/dist/{login.js → cli/login.js} +45 -4
  35. package/dist/cli/main.d.ts +3 -0
  36. package/dist/cli/main.d.ts.map +1 -0
  37. package/dist/cli/main.js +7 -0
  38. package/dist/cli/model-catalog.d.ts +141 -0
  39. package/dist/cli/model-catalog.d.ts.map +1 -0
  40. package/dist/cli/model-catalog.js +2135 -0
  41. package/dist/{models.d.ts → cli/models.d.ts} +16 -9
  42. package/dist/cli/models.d.ts.map +1 -0
  43. package/dist/cli/models.js +313 -0
  44. package/dist/cli/open-browser.d.ts.map +1 -0
  45. package/dist/cli/openai-speech-provider.d.ts +3 -0
  46. package/dist/cli/openai-speech-provider.d.ts.map +1 -0
  47. package/dist/cli/openai-speech-provider.js +28 -0
  48. package/dist/cli/separation-generate.d.ts +34 -0
  49. package/dist/cli/separation-generate.d.ts.map +1 -0
  50. package/dist/cli/separation-generate.js +117 -0
  51. package/dist/cli/speech-catalog.d.ts +35 -0
  52. package/dist/cli/speech-catalog.d.ts.map +1 -0
  53. package/dist/cli/speech-catalog.js +195 -0
  54. package/dist/cli/speech-generate.d.ts +86 -0
  55. package/dist/cli/speech-generate.d.ts.map +1 -0
  56. package/dist/cli/speech-generate.js +156 -0
  57. package/dist/cli/speech-models.d.ts +8 -0
  58. package/dist/cli/speech-models.d.ts.map +1 -0
  59. package/dist/cli/speech-models.js +109 -0
  60. package/dist/cli/speech-provider.test.d.ts +2 -0
  61. package/dist/cli/speech-provider.test.d.ts.map +1 -0
  62. package/dist/cli/speech-provider.test.js +197 -0
  63. package/dist/cli/subscription.d.ts.map +1 -0
  64. package/dist/cli/transcription-catalog.d.ts +35 -0
  65. package/dist/cli/transcription-catalog.d.ts.map +1 -0
  66. package/dist/cli/transcription-catalog.js +192 -0
  67. package/dist/cli/transcription-generate.d.ts +62 -0
  68. package/dist/cli/transcription-generate.d.ts.map +1 -0
  69. package/dist/cli/transcription-generate.js +144 -0
  70. package/dist/cli/transcription-models.d.ts +9 -0
  71. package/dist/cli/transcription-models.d.ts.map +1 -0
  72. package/dist/cli/transcription-models.js +100 -0
  73. package/dist/cli/transcription-providers.d.ts +22 -0
  74. package/dist/cli/transcription-providers.d.ts.map +1 -0
  75. package/dist/cli/transcription-providers.js +290 -0
  76. package/dist/cli/video-model-catalog.d.ts +2 -0
  77. package/dist/cli/video-model-catalog.d.ts.map +1 -0
  78. package/dist/cli/video-model-catalog.js +5 -0
  79. package/dist/cli/xai-auth.d.ts +29 -0
  80. package/dist/cli/xai-auth.d.ts.map +1 -0
  81. package/dist/cli/xai-auth.js +325 -0
  82. package/dist/vite/angled-screen.d.ts +57 -0
  83. package/dist/vite/angled-screen.d.ts.map +1 -0
  84. package/dist/vite/angled-screen.js +115 -0
  85. package/dist/vite/app.d.ts +51 -0
  86. package/dist/vite/app.d.ts.map +1 -0
  87. package/dist/vite/app.js +150 -0
  88. package/dist/vite/bands-shader.d.ts +47 -0
  89. package/dist/vite/bands-shader.d.ts.map +1 -0
  90. package/dist/vite/bands-shader.js +267 -0
  91. package/dist/vite/code-block.d.ts +107 -0
  92. package/dist/vite/code-block.d.ts.map +1 -0
  93. package/dist/vite/code-block.js +641 -0
  94. package/dist/vite/components.d.ts +36 -0
  95. package/dist/vite/components.d.ts.map +1 -0
  96. package/dist/vite/components.js +113 -0
  97. package/dist/vite/dispersion-rings-shader.d.ts +44 -0
  98. package/dist/vite/dispersion-rings-shader.d.ts.map +1 -0
  99. package/dist/vite/dispersion-rings-shader.js +282 -0
  100. package/dist/vite/easing-curves.d.ts +129 -0
  101. package/dist/vite/easing-curves.d.ts.map +1 -0
  102. package/dist/vite/easing-curves.js +584 -0
  103. package/dist/vite/easing-curves.test.d.ts +2 -0
  104. package/dist/vite/easing-curves.test.d.ts.map +1 -0
  105. package/dist/vite/easing-curves.test.js +200 -0
  106. package/dist/vite/generated-media-client.d.ts +35 -0
  107. package/dist/vite/generated-media-client.d.ts.map +1 -0
  108. package/dist/vite/generated-media-client.js +76 -0
  109. package/dist/vite/keyframes.d.ts +129 -0
  110. package/dist/vite/keyframes.d.ts.map +1 -0
  111. package/dist/vite/keyframes.js +194 -0
  112. package/dist/vite/layout-editor.d.ts +45 -0
  113. package/dist/vite/layout-editor.d.ts.map +1 -0
  114. package/dist/vite/layout-editor.js +587 -0
  115. package/dist/vite/layout-transition.d.ts +136 -0
  116. package/dist/vite/layout-transition.d.ts.map +1 -0
  117. package/dist/vite/layout-transition.js +581 -0
  118. package/dist/vite/liquid-gradient-shader.d.ts +62 -0
  119. package/dist/vite/liquid-gradient-shader.d.ts.map +1 -0
  120. package/dist/vite/liquid-gradient-shader.js +413 -0
  121. package/dist/vite/mdx-client.d.ts +15 -0
  122. package/dist/vite/mdx-client.d.ts.map +1 -0
  123. package/dist/vite/mdx-client.js +265 -0
  124. package/dist/vite/mdx-parse.d.ts +102 -0
  125. package/dist/vite/mdx-parse.d.ts.map +1 -0
  126. package/dist/vite/mdx-parse.js +251 -0
  127. package/dist/vite/mdx-provided-components.d.ts +18 -0
  128. package/dist/vite/mdx-provided-components.d.ts.map +1 -0
  129. package/dist/vite/mdx-provided-components.js +14 -0
  130. package/dist/vite/mdx-video.d.ts +327 -0
  131. package/dist/vite/mdx-video.d.ts.map +1 -0
  132. package/dist/vite/mdx-video.js +596 -0
  133. package/dist/vite/mdx-video.test.d.ts +9 -0
  134. package/dist/vite/mdx-video.test.d.ts.map +1 -0
  135. package/dist/vite/mdx-video.test.js +1945 -0
  136. package/dist/vite/media-components.d.ts +45 -0
  137. package/dist/vite/media-components.d.ts.map +1 -0
  138. package/dist/vite/media-components.js +355 -0
  139. package/dist/vite/media-duration-store.d.ts +66 -0
  140. package/dist/vite/media-duration-store.d.ts.map +1 -0
  141. package/dist/vite/media-duration-store.js +204 -0
  142. package/dist/vite/player-page.d.ts +35 -0
  143. package/dist/vite/player-page.d.ts.map +1 -0
  144. package/dist/vite/player-page.js +720 -0
  145. package/dist/vite/render-client.d.ts +18 -0
  146. package/dist/vite/render-client.d.ts.map +1 -0
  147. package/dist/vite/render-client.js +67 -0
  148. package/dist/vite/sdk.d.ts +221 -0
  149. package/dist/vite/sdk.d.ts.map +1 -0
  150. package/dist/vite/sdk.js +355 -0
  151. package/dist/vite/server-components.d.ts +55 -0
  152. package/dist/vite/server-components.d.ts.map +1 -0
  153. package/dist/vite/server-components.js +156 -0
  154. package/dist/vite/server-mdx.d.ts +74 -0
  155. package/dist/vite/server-mdx.d.ts.map +1 -0
  156. package/dist/vite/server-mdx.js +259 -0
  157. package/dist/vite/shader-renderer.d.ts +48 -0
  158. package/dist/vite/shader-renderer.d.ts.map +1 -0
  159. package/dist/vite/shader-renderer.js +411 -0
  160. package/dist/vite/store-hooks.d.ts +12 -0
  161. package/dist/vite/store-hooks.d.ts.map +1 -0
  162. package/dist/vite/store-hooks.js +93 -0
  163. package/dist/vite/store.d.ts +75 -0
  164. package/dist/vite/store.d.ts.map +1 -0
  165. package/dist/vite/store.js +52 -0
  166. package/dist/vite/store.test.d.ts +6 -0
  167. package/dist/vite/store.test.d.ts.map +1 -0
  168. package/dist/vite/store.test.js +172 -0
  169. package/dist/vite/tweakpane-hook.d.ts +94 -0
  170. package/dist/vite/tweakpane-hook.d.ts.map +1 -0
  171. package/dist/vite/tweakpane-hook.js +450 -0
  172. package/dist/vite/vite-plugin.d.ts +28 -0
  173. package/dist/vite/vite-plugin.d.ts.map +1 -0
  174. package/dist/vite/vite-plugin.js +538 -0
  175. package/dist/vite/wave-gradient-shader.d.ts +41 -0
  176. package/dist/vite/wave-gradient-shader.d.ts.map +1 -0
  177. package/dist/vite/wave-gradient-shader.js +207 -0
  178. package/package.json +103 -25
  179. package/skills/egaki/SKILL.md +162 -1
  180. package/src/cli/cache-utils.ts +169 -0
  181. package/src/cli/cached-generate.test.ts +531 -0
  182. package/src/cli/cached-generate.ts +348 -0
  183. package/src/cli/cartesia-provider.ts +249 -0
  184. package/src/cli/cli.ts +1818 -0
  185. package/src/cli/clone-generate.ts +202 -0
  186. package/src/{credentials.ts → cli/credentials.ts} +90 -7
  187. package/src/cli/elevenlabs-speech-provider.ts +243 -0
  188. package/src/cli/generate.test.ts +166 -0
  189. package/src/cli/generate.ts +912 -0
  190. package/src/{login.ts → cli/login.ts} +48 -3
  191. package/src/cli/main.ts +8 -0
  192. package/src/cli/model-catalog.ts +2336 -0
  193. package/src/cli/models.ts +408 -0
  194. package/src/cli/openai-speech-provider.ts +34 -0
  195. package/src/cli/separation-generate.ts +187 -0
  196. package/src/cli/speech-catalog.ts +253 -0
  197. package/src/cli/speech-generate.ts +275 -0
  198. package/src/cli/speech-models.ts +146 -0
  199. package/src/cli/speech-provider.test.ts +226 -0
  200. package/src/cli/transcription-catalog.ts +250 -0
  201. package/src/cli/transcription-generate.ts +214 -0
  202. package/src/cli/transcription-models.ts +136 -0
  203. package/src/cli/transcription-providers.ts +427 -0
  204. package/src/cli/video-model-catalog.ts +14 -0
  205. package/src/cli/xai-auth.ts +396 -0
  206. package/src/vite/angled-screen.tsx +214 -0
  207. package/src/vite/app.tsx +197 -0
  208. package/src/vite/bands-shader.tsx +319 -0
  209. package/src/vite/code-block.tsx +939 -0
  210. package/src/vite/components.tsx +278 -0
  211. package/src/vite/dispersion-rings-shader.tsx +331 -0
  212. package/src/vite/easing-curves.test.ts +232 -0
  213. package/src/vite/easing-curves.ts +743 -0
  214. package/src/vite/generated-media-client.tsx +121 -0
  215. package/src/vite/keyframes.tsx +347 -0
  216. package/src/vite/layout-editor.tsx +709 -0
  217. package/src/vite/layout-transition.tsx +706 -0
  218. package/src/vite/liquid-gradient-shader.tsx +474 -0
  219. package/src/vite/mdx-client.tsx +420 -0
  220. package/src/vite/mdx-parse.ts +343 -0
  221. package/src/vite/mdx-provided-components.ts +19 -0
  222. package/src/vite/mdx-video.test.tsx +2219 -0
  223. package/src/vite/mdx-video.tsx +976 -0
  224. package/src/vite/media-components.tsx +473 -0
  225. package/src/vite/media-duration-store.ts +225 -0
  226. package/src/vite/motion-timing.ts +194 -0
  227. package/src/vite/player-page.tsx +1107 -0
  228. package/src/vite/render-client.ts +84 -0
  229. package/src/vite/sdk.ts +598 -0
  230. package/src/vite/server-components.tsx +241 -0
  231. package/src/vite/server-mdx.ts +285 -0
  232. package/src/vite/shader-renderer.tsx +535 -0
  233. package/src/vite/store-hooks.ts +117 -0
  234. package/src/vite/store.test.ts +202 -0
  235. package/src/vite/store.ts +115 -0
  236. package/src/vite/styles.css +21 -0
  237. package/src/vite/tweakpane-hook.tsx +593 -0
  238. package/src/vite/virtual-modules.d.ts +30 -0
  239. package/src/vite/vite-plugin.ts +584 -0
  240. package/src/vite/wave-gradient-shader.tsx +253 -0
  241. package/README.md +0 -266
  242. package/dist/antigravity-auth.d.ts +0 -15
  243. package/dist/antigravity-auth.d.ts.map +0 -1
  244. package/dist/antigravity-auth.js +0 -544
  245. package/dist/antigravity-image.d.ts +0 -11
  246. package/dist/antigravity-image.d.ts.map +0 -1
  247. package/dist/antigravity-image.js +0 -285
  248. package/dist/chatgpt-auth.d.ts.map +0 -1
  249. package/dist/cli.d.ts +0 -3
  250. package/dist/cli.d.ts.map +0 -1
  251. package/dist/cli.js +0 -968
  252. package/dist/credentials.d.ts.map +0 -1
  253. package/dist/gateway-model-candidates.generated.d.ts +0 -11
  254. package/dist/gateway-model-candidates.generated.d.ts.map +0 -1
  255. package/dist/gateway-model-candidates.generated.js +0 -64
  256. package/dist/login.d.ts.map +0 -1
  257. package/dist/model-catalog.d.ts +0 -42
  258. package/dist/model-catalog.d.ts.map +0 -1
  259. package/dist/model-catalog.js +0 -1001
  260. package/dist/models.d.ts.map +0 -1
  261. package/dist/models.js +0 -246
  262. package/dist/open-browser.d.ts.map +0 -1
  263. package/dist/subscription.d.ts.map +0 -1
  264. package/dist/video-model-catalog.d.ts +0 -52
  265. package/dist/video-model-catalog.d.ts.map +0 -1
  266. package/dist/video-model-catalog.js +0 -306
  267. package/src/cli.ts +0 -1378
  268. package/src/model-catalog.ts +0 -1077
  269. package/src/models.ts +0 -309
  270. package/src/video-model-catalog.ts +0 -363
  271. /package/dist/{chatgpt-auth.d.ts → cli/chatgpt-auth.d.ts} +0 -0
  272. /package/dist/{chatgpt-auth.js → cli/chatgpt-auth.js} +0 -0
  273. /package/dist/{login.d.ts → cli/login.d.ts} +0 -0
  274. /package/dist/{open-browser.d.ts → cli/open-browser.d.ts} +0 -0
  275. /package/dist/{open-browser.js → cli/open-browser.js} +0 -0
  276. /package/dist/{subscription.d.ts → cli/subscription.d.ts} +0 -0
  277. /package/dist/{subscription.js → cli/subscription.js} +0 -0
  278. /package/src/{chatgpt-auth.ts → cli/chatgpt-auth.ts} +0 -0
  279. /package/src/{open-browser.ts → cli/open-browser.ts} +0 -0
  280. /package/src/{subscription.ts → cli/subscription.ts} +0 -0
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Client-side rendering entry point.
3
+ * Renders any Remotion composition entirely in the browser using WebCodecs
4
+ * via @remotion/web-renderer's renderMediaOnWeb().
5
+ */
6
+
7
+ import { renderMediaOnWeb } from '@remotion/web-renderer'
8
+ import { ExportContext } from './mdx-video.tsx'
9
+ import React from 'react'
10
+
11
+ /**
12
+ * Cover the web-renderer scaffold during export. The scaffold wrapper has
13
+ * position:fixed + visibility:hidden + z-index:-9999, but allowHtmlInCanvas
14
+ * sets visibility:visible on the inner layoutsubtree canvas, which can
15
+ * make it peek through on the page.
16
+ *
17
+ * Do NOT modify the scaffold wrapper's own styles (translate, clip-path,
18
+ * width/height, opacity). All of those break Chrome's drawElementImage
19
+ * paint pipeline and produce black frames in the exported video. The
20
+ * layout subtree canvas must remain at full size, in its original position,
21
+ * with normal visibility for the capture to work.
22
+ *
23
+ * Instead we place an opaque overlay on top (z-index:-9998, one above the
24
+ * scaffold's -9999) that visually hides the rendering without touching
25
+ * the scaffold's layout or paint properties.
26
+ */
27
+ function injectScaffoldCover(): () => void {
28
+ const cover = document.createElement('div')
29
+ cover.style.cssText = [
30
+ 'position:fixed',
31
+ 'inset:0',
32
+ 'z-index:-9998',
33
+ 'background:#050505',
34
+ 'pointer-events:none',
35
+ ].join(';')
36
+ document.body.appendChild(cover)
37
+ return () => cover.remove()
38
+ }
39
+
40
+ export async function renderInBrowser(options: {
41
+ component: React.FC
42
+ durationInFrames: number
43
+ fps?: number
44
+ width?: number
45
+ height?: number
46
+ /** Pixel density / scale multiplier. Default 1. */
47
+ scale?: number
48
+ onProgress?: (progress: number) => void
49
+ signal?: AbortSignal
50
+ }) {
51
+ const removeCover = injectScaffoldCover()
52
+
53
+ const ExportWrapped: React.FC = () =>
54
+ React.createElement(ExportContext.Provider, { value: true },
55
+ React.createElement(options.component))
56
+
57
+ try {
58
+ const { getBlob } = await renderMediaOnWeb({
59
+ composition: {
60
+ component: ExportWrapped,
61
+ durationInFrames: options.durationInFrames,
62
+ fps: options.fps ?? 30,
63
+ width: options.width ?? 1920,
64
+ height: options.height ?? 1080,
65
+ id: 'MdxVideo',
66
+ calculateMetadata: null,
67
+ },
68
+ inputProps: {},
69
+ container: 'mp4',
70
+ videoCodec: 'h264',
71
+ videoBitrate: 'high',
72
+ allowHtmlInCanvas: true,
73
+ scale: options.scale,
74
+ signal: options.signal,
75
+ onProgress: ({ progress }) => {
76
+ options.onProgress?.(progress)
77
+ },
78
+ })
79
+
80
+ return getBlob()
81
+ } finally {
82
+ removeCover()
83
+ }
84
+ }
@@ -0,0 +1,598 @@
1
+ /**
2
+ * Client-side SDK singleton for agent-driven rendering.
3
+ *
4
+ * Exposes screenshot() and export() methods on window.egakiSDK so agents
5
+ * can call them via Playwriter's page.evaluate(). PlayerPage registers
6
+ * the live composition on mount; the SDK forwards calls to
7
+ * @remotion/web-renderer's renderStillOnWeb / renderMediaOnWeb.
8
+ *
9
+ * Returns data URLs (not Blobs) because page.evaluate() cannot serialize
10
+ * binary types across the CDP boundary. Agents decode the data URL in
11
+ * the Playwriter sandbox with fetch().then(r => r.arrayBuffer()).
12
+ */
13
+
14
+ import React from 'react'
15
+ import { renderStillOnWeb, renderMediaOnWeb } from '@remotion/web-renderer'
16
+ import { ExportContext } from './mdx-video.tsx'
17
+ import type {
18
+ FrameRange,
19
+ RenderStillOnWebImageFormat,
20
+ WebRendererContainer,
21
+ WebRendererVideoCodec,
22
+ WebRendererAudioCodec,
23
+ WebRendererQuality,
24
+ WebRendererHardwareAcceleration,
25
+ RenderMediaOnWebProgressCallback,
26
+ } from '@remotion/web-renderer'
27
+
28
+ // Re-export Remotion types so consumers can reference them
29
+ export type {
30
+ FrameRange,
31
+ RenderStillOnWebImageFormat,
32
+ WebRendererContainer,
33
+ WebRendererVideoCodec,
34
+ WebRendererAudioCodec,
35
+ WebRendererQuality,
36
+ WebRendererHardwareAcceleration,
37
+ RenderMediaOnWebProgressCallback,
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Composition registration — PlayerPage calls this on mount
42
+ // ---------------------------------------------------------------------------
43
+
44
+ interface PlayerHandle {
45
+ seekTo: (frame: number) => void
46
+ getCurrentFrame: () => number
47
+ play: () => void
48
+ pause: () => void
49
+ toggle: () => void
50
+ isPlaying: () => boolean
51
+ }
52
+
53
+ interface SectionDescriptor {
54
+ heading: string | null
55
+ durationInFrames: number
56
+ }
57
+
58
+ interface CompositionConfig {
59
+ component: React.FC
60
+ totalDuration: number
61
+ fps: number
62
+ width: number
63
+ height: number
64
+ sectionCount: number
65
+ /** Per-section metadata: heading and duration. Ordered by section index. */
66
+ sections: SectionDescriptor[]
67
+ /** Pixel density / scale multiplier from frontmatter. Default 1. */
68
+ scale: number
69
+ playerRef: { current: PlayerHandle | null }
70
+ /** The DOM element wrapping the Remotion Player (used for coordinate mapping) */
71
+ playerContainerRef: { current: HTMLElement | null }
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // SDK option types
76
+ // ---------------------------------------------------------------------------
77
+
78
+ export interface ScreenshotOptions {
79
+ /** Frame number to capture (0-indexed). Default 0. */
80
+ frame?: number
81
+ /** Output format. Default 'png'. */
82
+ format?: RenderStillOnWebImageFormat
83
+ /** Encoder quality for jpeg/webp, 0-1. Ignored for png. */
84
+ quality?: number
85
+ /** Scale multiplier. Default 1. */
86
+ scale?: number
87
+ /** Use Chromium experimental HTML-in-canvas. Default true. */
88
+ allowHtmlInCanvas?: boolean
89
+ }
90
+
91
+ export interface ExportOptions {
92
+ /** Single frame or frame range. null = all frames. */
93
+ frameRange?: FrameRange | null
94
+ /** Container format. Default 'mp4'. */
95
+ container?: WebRendererContainer
96
+ /** Video codec. Default depends on container. */
97
+ videoCodec?: WebRendererVideoCodec
98
+ /** Video bitrate — number (bps) or preset name. Default 'high'. */
99
+ videoBitrate?: number | WebRendererQuality
100
+ /** Audio codec. Default depends on container. */
101
+ audioCodec?: WebRendererAudioCodec
102
+ /** Audio bitrate — number (bps) or preset name. */
103
+ audioBitrate?: number | WebRendererQuality
104
+ /** Audio sample rate in Hz. Default 48000. */
105
+ sampleRate?: number
106
+ /** Strip audio. Default false. */
107
+ muted?: boolean
108
+ /** Encode with alpha channel (webm/mkv only). Default false. */
109
+ transparent?: boolean
110
+ /** Scale multiplier. Default 1. */
111
+ scale?: number
112
+ /** Seconds between keyframes. Default 5. */
113
+ keyframeIntervalInSeconds?: number
114
+ /** Hardware acceleration preference. */
115
+ hardwareAcceleration?: WebRendererHardwareAcceleration
116
+ /** Use Chromium experimental HTML-in-canvas. Default true. */
117
+ allowHtmlInCanvas?: boolean
118
+ /** If set, triggers a browser download with this filename. */
119
+ path?: string
120
+ /** Progress callback. */
121
+ onProgress?: RenderMediaOnWebProgressCallback
122
+ }
123
+
124
+ export interface SectionInfo {
125
+ index: number
126
+ heading: string | null
127
+ durationInFrames: number
128
+ startFrame: number
129
+ }
130
+
131
+ export interface CompositionInfo {
132
+ totalDuration: number
133
+ fps: number
134
+ width: number
135
+ height: number
136
+ sectionCount: number
137
+ /** Per-section metadata with start frames computed from durations. */
138
+ sections: SectionInfo[]
139
+ /** Duration in seconds */
140
+ durationSeconds: number
141
+ /** Current frame the player is on */
142
+ currentFrame: number
143
+ /** Whether the player is currently playing */
144
+ isPlaying: boolean
145
+ }
146
+
147
+ export interface FilmstripOptions {
148
+ /** Scene indices (0-based). */
149
+ scenes: number[]
150
+ /** Frames to capture per scene. Total grid cells = scenes.length * framesPerScene. */
151
+ framesPerScene: number
152
+ /** Output format. Default 'png'. */
153
+ format?: 'png' | 'jpeg' | 'webp'
154
+ /** Encoder quality for jpeg/webp, 0-1. */
155
+ quality?: number
156
+ /** Scale multiplier for each frame render. Default 0.33 (overview size). */
157
+ scale?: number
158
+ /** Use Chromium experimental HTML-in-canvas. Default true. */
159
+ allowHtmlInCanvas?: boolean
160
+ }
161
+
162
+ export interface ElementPosition {
163
+ /** X offset from the left edge of the composition, in composition pixels */
164
+ x: number
165
+ /** Y offset from the top edge of the composition, in composition pixels */
166
+ y: number
167
+ /** Element width in composition pixels */
168
+ width: number
169
+ /** Element height in composition pixels */
170
+ height: number
171
+ /** X offset as a percentage of composition width (0-100) */
172
+ xPercent: number
173
+ /** Y offset as a percentage of composition height (0-100) */
174
+ yPercent: number
175
+ /** Element width as a percentage of composition width (0-100) */
176
+ widthPercent: number
177
+ /** Element height as a percentage of composition height (0-100) */
178
+ heightPercent: number
179
+ /** Center X in composition pixels */
180
+ centerX: number
181
+ /** Center Y in composition pixels */
182
+ centerY: number
183
+ /** Center X as a percentage of composition width (0-100) */
184
+ centerXPercent: number
185
+ /** Center Y as a percentage of composition height (0-100) */
186
+ centerYPercent: number
187
+ }
188
+
189
+ // ---------------------------------------------------------------------------
190
+ // Helper: blob → data URL
191
+ // ---------------------------------------------------------------------------
192
+
193
+ async function blobToDataUrl(blob: Blob): Promise<string> {
194
+ return new Promise((resolve, reject) => {
195
+ const reader = new FileReader()
196
+ reader.onload = () => resolve(reader.result as string)
197
+ reader.onerror = reject
198
+ reader.readAsDataURL(blob)
199
+ })
200
+ }
201
+
202
+ function triggerDownload(blob: Blob, filename: string) {
203
+ const url = URL.createObjectURL(blob)
204
+ const a = document.createElement('a')
205
+ a.href = url
206
+ a.download = filename
207
+ a.click()
208
+ URL.revokeObjectURL(url)
209
+ }
210
+
211
+ // ---------------------------------------------------------------------------
212
+ // EgakiSDK class
213
+ // ---------------------------------------------------------------------------
214
+
215
+ class EgakiSDK {
216
+ private config: CompositionConfig | null = null
217
+
218
+ /** Called by PlayerPage to wire the SDK to the live composition. */
219
+ register(config: CompositionConfig) {
220
+ this.config = config
221
+ }
222
+
223
+ private getConfig(): CompositionConfig {
224
+ if (!this.config) {
225
+ throw new Error(
226
+ 'egakiSDK: no composition registered. ' +
227
+ 'Make sure the video page is loaded and PlayerPage has mounted.',
228
+ )
229
+ }
230
+ return this.config
231
+ }
232
+
233
+ private getPlayer(): PlayerHandle {
234
+ const player = this.getConfig().playerRef.current
235
+ if (!player) {
236
+ throw new Error(
237
+ 'egakiSDK: player ref is null. The <Player> component has not mounted yet.',
238
+ )
239
+ }
240
+ return player
241
+ }
242
+
243
+ // -------------------------------------------------------------------------
244
+ // Player controls
245
+ // -------------------------------------------------------------------------
246
+
247
+ /** Seek the player to a specific frame (0-indexed). */
248
+ seekTo(frame: number) {
249
+ this.getPlayer().seekTo(frame)
250
+ }
251
+
252
+ /** Returns the frame number the player is currently displaying. */
253
+ getCurrentFrame(): number {
254
+ return this.getPlayer().getCurrentFrame()
255
+ }
256
+
257
+ /** Start playback. */
258
+ play() {
259
+ this.getPlayer().play()
260
+ }
261
+
262
+ /** Pause playback. */
263
+ pause() {
264
+ this.getPlayer().pause()
265
+ }
266
+
267
+ /** Toggle play/pause. */
268
+ toggle() {
269
+ this.getPlayer().toggle()
270
+ }
271
+
272
+ /** Whether the player is currently playing. */
273
+ isPlaying(): boolean {
274
+ return this.getPlayer().isPlaying()
275
+ }
276
+
277
+ // -------------------------------------------------------------------------
278
+ // Info
279
+ // -------------------------------------------------------------------------
280
+
281
+ /** Compute section start frames from durations. */
282
+ private getSectionInfos(): SectionInfo[] {
283
+ const c = this.getConfig()
284
+ let startFrame = 0
285
+ return c.sections.map((s, i) => {
286
+ const info: SectionInfo = {
287
+ index: i,
288
+ heading: s.heading,
289
+ durationInFrames: s.durationInFrames,
290
+ startFrame,
291
+ }
292
+ startFrame += s.durationInFrames
293
+ return info
294
+ })
295
+ }
296
+
297
+ /** Returns metadata about the current composition and player state. */
298
+ getInfo(): CompositionInfo {
299
+ const c = this.getConfig()
300
+ const player = c.playerRef.current
301
+ return {
302
+ totalDuration: c.totalDuration,
303
+ fps: c.fps,
304
+ width: c.width,
305
+ height: c.height,
306
+ sectionCount: c.sectionCount,
307
+ sections: this.getSectionInfos(),
308
+ durationSeconds: c.totalDuration / c.fps,
309
+ currentFrame: player?.getCurrentFrame() ?? 0,
310
+ isPlaying: player?.isPlaying() ?? false,
311
+ }
312
+ }
313
+
314
+ // -------------------------------------------------------------------------
315
+ // Element position
316
+ // -------------------------------------------------------------------------
317
+
318
+ /**
319
+ * Get a DOM element's position mapped to composition coordinates.
320
+ *
321
+ * The Remotion Player scales the 1920×1080 composition to fit the browser
322
+ * viewport. This method reads the element's bounding rect, computes the
323
+ * scale factor from the player container, and returns position/size in
324
+ * both composition pixels and percentages.
325
+ *
326
+ * Use case: match an element's position across scenes (e.g. a logo in
327
+ * scene 1 should animate to the same spot in scene 2).
328
+ */
329
+ getElementPosition(element: Element): ElementPosition {
330
+ const c = this.getConfig()
331
+ const container = c.playerContainerRef.current
332
+ if (!container) {
333
+ throw new Error(
334
+ 'egakiSDK: player container ref is null. The player has not mounted yet.',
335
+ )
336
+ }
337
+
338
+ // The Remotion Player renders inside the container. Find the actual
339
+ // player viewport element — it's the first child with the composition's
340
+ // aspect ratio, or just use the container itself as the reference frame.
341
+ // The container wraps the <Player> which renders at 100% width and scales
342
+ // the composition to fit.
343
+ const containerRect = container.getBoundingClientRect()
344
+ const elementRect = element.getBoundingClientRect()
345
+
346
+ // Scale factor: how many display pixels per composition pixel.
347
+ // The Player scales uniformly (preserves aspect ratio), so we derive
348
+ // the scale from width. The composition fills the container width and
349
+ // the height is determined by aspect ratio.
350
+ const scale = containerRect.width / c.width
351
+
352
+ const x = (elementRect.left - containerRect.left) / scale
353
+ const y = (elementRect.top - containerRect.top) / scale
354
+ const width = elementRect.width / scale
355
+ const height = elementRect.height / scale
356
+ const centerX = x + width / 2
357
+ const centerY = y + height / 2
358
+
359
+ return {
360
+ x,
361
+ y,
362
+ width,
363
+ height,
364
+ xPercent: (x / c.width) * 100,
365
+ yPercent: (y / c.height) * 100,
366
+ widthPercent: (width / c.width) * 100,
367
+ heightPercent: (height / c.height) * 100,
368
+ centerX,
369
+ centerY,
370
+ centerXPercent: (centerX / c.width) * 100,
371
+ centerYPercent: (centerY / c.height) * 100,
372
+ }
373
+ }
374
+
375
+ // -------------------------------------------------------------------------
376
+ // Rendering
377
+ // -------------------------------------------------------------------------
378
+
379
+ /** Screenshot whatever frame the player is currently showing. */
380
+ async screenshotCurrentFrame(
381
+ options: Omit<ScreenshotOptions, 'frame'> = {},
382
+ ): Promise<string> {
383
+ return this.screenshot({ ...options, frame: this.getCurrentFrame() })
384
+ }
385
+
386
+ /** Wrap the composition component with ExportContext so children can
387
+ * detect they're inside an export render via useIsExporting(). */
388
+ private wrapForExport(component: React.FC): React.FC {
389
+ const Wrapped: React.FC = () =>
390
+ React.createElement(ExportContext.Provider, { value: true },
391
+ React.createElement(component))
392
+ return Wrapped
393
+ }
394
+
395
+ /** Render a single frame and return a data URL string.
396
+ * Intentionally does NOT wrap in ExportContext — screenshots capture the
397
+ * current visual state including Suspense fallbacks. This matches what
398
+ * the user sees in the Player. Use export() for production renders that
399
+ * wait for all generated media to resolve via delayRender. */
400
+ async screenshot(options: ScreenshotOptions = {}): Promise<string> {
401
+ const c = this.getConfig()
402
+ const format = options.format ?? 'png'
403
+
404
+ const still = await renderStillOnWeb({
405
+ composition: {
406
+ component: c.component,
407
+ durationInFrames: c.totalDuration,
408
+ fps: c.fps,
409
+ width: c.width,
410
+ height: c.height,
411
+ id: 'EgakiSDK',
412
+ calculateMetadata: null,
413
+ },
414
+ frame: options.frame ?? 0,
415
+ scale: options.scale ?? c.scale,
416
+ allowHtmlInCanvas: options.allowHtmlInCanvas ?? true,
417
+ })
418
+
419
+ const blob = await still.blob({
420
+ format,
421
+ quality: options.quality,
422
+ })
423
+ return blobToDataUrl(blob)
424
+ }
425
+
426
+ /**
427
+ * Render equidistant frames from specified scenes and composite them
428
+ * into a single grid image. Designed for agents to quickly understand
429
+ * scene structure and animations from a single image.
430
+ *
431
+ * Frame selection: for each scene, `framesPerScene` equidistant frames
432
+ * are captured starting from the scene's first frame. With framesPerScene=2,
433
+ * captures the start frame and the midpoint. With 3, captures at 0/3,
434
+ * 1/3, and 2/3 of the scene duration.
435
+ *
436
+ * Grid layout: the smallest square grid that fits all frames.
437
+ * 4 frames → 2×2, 6 frames → 3×2, 9 frames → 3×3.
438
+ */
439
+ async filmstrip(options: FilmstripOptions): Promise<string> {
440
+ const c = this.getConfig()
441
+ const sectionInfos = this.getSectionInfos()
442
+ const format = options.format ?? 'png'
443
+ // Default to 1/3 scale for overview images to save memory.
444
+ // A 1920×1080 composition at scale 0.33 produces ~634×356 tiles,
445
+ // keeping even large grids well within browser canvas limits.
446
+ const renderScale = options.scale ?? 0.33
447
+
448
+ // Validate inputs
449
+ if (!Array.isArray(options.scenes) || options.scenes.length === 0) {
450
+ throw new Error('filmstrip: scenes must contain at least one scene index.')
451
+ }
452
+
453
+ if (!Number.isInteger(options.framesPerScene) || options.framesPerScene < 1) {
454
+ throw new Error('filmstrip: framesPerScene must be a positive integer.')
455
+ }
456
+
457
+ if (!Number.isFinite(renderScale) || renderScale <= 0) {
458
+ throw new Error('filmstrip: scale must be a positive finite number.')
459
+ }
460
+
461
+ for (const idx of options.scenes) {
462
+ if (!Number.isInteger(idx) || idx < 0 || idx >= sectionInfos.length) {
463
+ throw new Error(
464
+ `filmstrip: scene index ${idx} is invalid. ` +
465
+ `Must be an integer in range 0-${sectionInfos.length - 1}.`,
466
+ )
467
+ }
468
+ }
469
+
470
+ // Compute which absolute frames to capture
471
+ const framesToCapture: number[] = []
472
+ for (const sceneIdx of options.scenes) {
473
+ const section = sectionInfos[sceneIdx]!
474
+ for (let i = 0; i < options.framesPerScene; i++) {
475
+ const frame = section.startFrame +
476
+ Math.floor(i * section.durationInFrames / options.framesPerScene)
477
+ framesToCapture.push(frame)
478
+ }
479
+ }
480
+
481
+ // Compute grid dimensions (smallest square grid that fits all items)
482
+ const totalItems = framesToCapture.length
483
+ const cols = Math.max(1, Math.ceil(Math.sqrt(totalItems)))
484
+ const rows = Math.max(1, Math.ceil(totalItems / cols))
485
+
486
+ // Create the output canvas upfront, then render and draw each tile
487
+ // one at a time to avoid storing all intermediate bitmaps in memory.
488
+ // A small gap between tiles makes the grid structure visible even when
489
+ // adjacent scenes share the same background color.
490
+ const tileW = Math.round(c.width * renderScale)
491
+ const tileH = Math.round(c.height * renderScale)
492
+ const gap = Math.max(2, Math.round(tileW * 0.006))
493
+ const canvasW = cols * tileW + (cols + 1) * gap
494
+ const canvasH = rows * tileH + (rows + 1) * gap
495
+ const canvas = new OffscreenCanvas(canvasW, canvasH)
496
+ const ctx = canvas.getContext('2d')!
497
+
498
+ // White background visible through gaps and empty cells
499
+ ctx.fillStyle = '#ffffff'
500
+ ctx.fillRect(0, 0, canvasW, canvasH)
501
+
502
+ for (let i = 0; i < framesToCapture.length; i++) {
503
+ const still = await renderStillOnWeb({
504
+ composition: {
505
+ component: c.component,
506
+ durationInFrames: c.totalDuration,
507
+ fps: c.fps,
508
+ width: c.width,
509
+ height: c.height,
510
+ id: 'EgakiSDK',
511
+ calculateMetadata: null,
512
+ },
513
+ frame: framesToCapture[i]!,
514
+ scale: renderScale,
515
+ allowHtmlInCanvas: options.allowHtmlInCanvas ?? true,
516
+ })
517
+ // Draw directly from the rendered OffscreenCanvas to the grid canvas.
518
+ // No PNG encode/decode roundtrip, no intermediate ImageBitmap storage.
519
+ const tileCanvas = await still.canvas()
520
+ const col = i % cols
521
+ const row = Math.floor(i / cols)
522
+ const x = gap + col * (tileW + gap)
523
+ const y = gap + row * (tileH + gap)
524
+ ctx.drawImage(tileCanvas, x, y, tileW, tileH)
525
+ }
526
+
527
+ // Convert to data URL
528
+ const mimeType = format === 'jpeg' ? 'image/jpeg'
529
+ : format === 'webp' ? 'image/webp'
530
+ : 'image/png'
531
+ const gridBlob = await canvas.convertToBlob({
532
+ type: mimeType,
533
+ quality: options.quality,
534
+ })
535
+ return blobToDataUrl(gridBlob)
536
+ }
537
+
538
+ /** Render a video (or segment) and return a data URL string.
539
+ * If `path` is set, also triggers a browser download. */
540
+ async export(options: ExportOptions = {}): Promise<string> {
541
+ const c = this.getConfig()
542
+
543
+ const { getBlob } = await renderMediaOnWeb({
544
+ composition: {
545
+ component: this.wrapForExport(c.component),
546
+ durationInFrames: c.totalDuration,
547
+ fps: c.fps,
548
+ width: c.width,
549
+ height: c.height,
550
+ id: 'EgakiSDK',
551
+ calculateMetadata: null,
552
+ },
553
+ inputProps: {},
554
+ container: options.container ?? 'mp4',
555
+ videoCodec: options.videoCodec,
556
+ videoBitrate: options.videoBitrate ?? 'high',
557
+ audioCodec: options.audioCodec,
558
+ audioBitrate: options.audioBitrate,
559
+ sampleRate: options.sampleRate,
560
+ muted: options.muted,
561
+ transparent: options.transparent,
562
+ scale: options.scale ?? c.scale,
563
+ keyframeIntervalInSeconds: options.keyframeIntervalInSeconds,
564
+ hardwareAcceleration: options.hardwareAcceleration,
565
+ frameRange: options.frameRange ?? undefined,
566
+ allowHtmlInCanvas: options.allowHtmlInCanvas ?? true,
567
+ onProgress: options.onProgress,
568
+ })
569
+
570
+ const blob = await getBlob()
571
+
572
+ if (options.path) {
573
+ triggerDownload(blob, options.path)
574
+ }
575
+
576
+ return blobToDataUrl(blob)
577
+ }
578
+ }
579
+
580
+ // ---------------------------------------------------------------------------
581
+ // Global type augmentation so window.egakiSDK is typed everywhere
582
+ // ---------------------------------------------------------------------------
583
+
584
+ declare global {
585
+ interface Window {
586
+ egakiSDK: EgakiSDK
587
+ }
588
+ }
589
+
590
+ // ---------------------------------------------------------------------------
591
+ // Singleton — mounted on window for Playwriter access
592
+ // ---------------------------------------------------------------------------
593
+
594
+ export const egakiSDK = new EgakiSDK()
595
+
596
+ if (typeof window !== 'undefined') {
597
+ window.egakiSDK = egakiSDK
598
+ }