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.
- package/CHANGELOG.md +188 -0
- package/dist/cli/cache-utils.d.ts +33 -0
- package/dist/cli/cache-utils.d.ts.map +1 -0
- package/dist/cli/cache-utils.js +157 -0
- package/dist/cli/cached-generate.d.ts +105 -0
- package/dist/cli/cached-generate.d.ts.map +1 -0
- package/dist/cli/cached-generate.js +208 -0
- package/dist/cli/cached-generate.test.d.ts +11 -0
- package/dist/cli/cached-generate.test.d.ts.map +1 -0
- package/dist/cli/cached-generate.test.js +431 -0
- package/dist/cli/cartesia-provider.d.ts +16 -0
- package/dist/cli/cartesia-provider.d.ts.map +1 -0
- package/dist/cli/cartesia-provider.js +184 -0
- package/dist/cli/chatgpt-auth.d.ts.map +1 -0
- package/dist/cli/cli.d.ts +4 -0
- package/dist/cli/cli.d.ts.map +1 -0
- package/dist/cli/cli.js +1329 -0
- package/dist/cli/clone-generate.d.ts +26 -0
- package/dist/cli/clone-generate.d.ts.map +1 -0
- package/dist/cli/clone-generate.js +148 -0
- package/dist/{credentials.d.ts → cli/credentials.d.ts} +11 -2
- package/dist/cli/credentials.d.ts.map +1 -0
- package/dist/{credentials.js → cli/credentials.js} +88 -5
- package/dist/cli/elevenlabs-speech-provider.d.ts +26 -0
- package/dist/cli/elevenlabs-speech-provider.d.ts.map +1 -0
- package/dist/cli/elevenlabs-speech-provider.js +199 -0
- package/dist/cli/generate.d.ts +140 -0
- package/dist/cli/generate.d.ts.map +1 -0
- package/dist/cli/generate.js +604 -0
- package/dist/cli/generate.test.d.ts +2 -0
- package/dist/cli/generate.test.d.ts.map +1 -0
- package/dist/cli/generate.test.js +131 -0
- package/dist/cli/login.d.ts.map +1 -0
- package/dist/{login.js → cli/login.js} +45 -4
- package/dist/cli/main.d.ts +3 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +7 -0
- package/dist/cli/model-catalog.d.ts +141 -0
- package/dist/cli/model-catalog.d.ts.map +1 -0
- package/dist/cli/model-catalog.js +2135 -0
- package/dist/{models.d.ts → cli/models.d.ts} +16 -9
- package/dist/cli/models.d.ts.map +1 -0
- package/dist/cli/models.js +313 -0
- package/dist/cli/open-browser.d.ts.map +1 -0
- package/dist/cli/openai-speech-provider.d.ts +3 -0
- package/dist/cli/openai-speech-provider.d.ts.map +1 -0
- package/dist/cli/openai-speech-provider.js +28 -0
- package/dist/cli/separation-generate.d.ts +34 -0
- package/dist/cli/separation-generate.d.ts.map +1 -0
- package/dist/cli/separation-generate.js +117 -0
- package/dist/cli/speech-catalog.d.ts +35 -0
- package/dist/cli/speech-catalog.d.ts.map +1 -0
- package/dist/cli/speech-catalog.js +195 -0
- package/dist/cli/speech-generate.d.ts +86 -0
- package/dist/cli/speech-generate.d.ts.map +1 -0
- package/dist/cli/speech-generate.js +156 -0
- package/dist/cli/speech-models.d.ts +8 -0
- package/dist/cli/speech-models.d.ts.map +1 -0
- package/dist/cli/speech-models.js +109 -0
- package/dist/cli/speech-provider.test.d.ts +2 -0
- package/dist/cli/speech-provider.test.d.ts.map +1 -0
- package/dist/cli/speech-provider.test.js +197 -0
- package/dist/cli/subscription.d.ts.map +1 -0
- package/dist/cli/transcription-catalog.d.ts +35 -0
- package/dist/cli/transcription-catalog.d.ts.map +1 -0
- package/dist/cli/transcription-catalog.js +192 -0
- package/dist/cli/transcription-generate.d.ts +62 -0
- package/dist/cli/transcription-generate.d.ts.map +1 -0
- package/dist/cli/transcription-generate.js +144 -0
- package/dist/cli/transcription-models.d.ts +9 -0
- package/dist/cli/transcription-models.d.ts.map +1 -0
- package/dist/cli/transcription-models.js +100 -0
- package/dist/cli/transcription-providers.d.ts +22 -0
- package/dist/cli/transcription-providers.d.ts.map +1 -0
- package/dist/cli/transcription-providers.js +290 -0
- package/dist/cli/video-model-catalog.d.ts +2 -0
- package/dist/cli/video-model-catalog.d.ts.map +1 -0
- package/dist/cli/video-model-catalog.js +5 -0
- package/dist/cli/xai-auth.d.ts +29 -0
- package/dist/cli/xai-auth.d.ts.map +1 -0
- package/dist/cli/xai-auth.js +325 -0
- package/dist/vite/angled-screen.d.ts +57 -0
- package/dist/vite/angled-screen.d.ts.map +1 -0
- package/dist/vite/angled-screen.js +115 -0
- package/dist/vite/app.d.ts +51 -0
- package/dist/vite/app.d.ts.map +1 -0
- package/dist/vite/app.js +150 -0
- package/dist/vite/bands-shader.d.ts +47 -0
- package/dist/vite/bands-shader.d.ts.map +1 -0
- package/dist/vite/bands-shader.js +267 -0
- package/dist/vite/code-block.d.ts +107 -0
- package/dist/vite/code-block.d.ts.map +1 -0
- package/dist/vite/code-block.js +641 -0
- package/dist/vite/components.d.ts +36 -0
- package/dist/vite/components.d.ts.map +1 -0
- package/dist/vite/components.js +113 -0
- package/dist/vite/dispersion-rings-shader.d.ts +44 -0
- package/dist/vite/dispersion-rings-shader.d.ts.map +1 -0
- package/dist/vite/dispersion-rings-shader.js +282 -0
- package/dist/vite/easing-curves.d.ts +129 -0
- package/dist/vite/easing-curves.d.ts.map +1 -0
- package/dist/vite/easing-curves.js +584 -0
- package/dist/vite/easing-curves.test.d.ts +2 -0
- package/dist/vite/easing-curves.test.d.ts.map +1 -0
- package/dist/vite/easing-curves.test.js +200 -0
- package/dist/vite/generated-media-client.d.ts +35 -0
- package/dist/vite/generated-media-client.d.ts.map +1 -0
- package/dist/vite/generated-media-client.js +76 -0
- package/dist/vite/keyframes.d.ts +129 -0
- package/dist/vite/keyframes.d.ts.map +1 -0
- package/dist/vite/keyframes.js +194 -0
- package/dist/vite/layout-editor.d.ts +45 -0
- package/dist/vite/layout-editor.d.ts.map +1 -0
- package/dist/vite/layout-editor.js +587 -0
- package/dist/vite/layout-transition.d.ts +136 -0
- package/dist/vite/layout-transition.d.ts.map +1 -0
- package/dist/vite/layout-transition.js +581 -0
- package/dist/vite/liquid-gradient-shader.d.ts +62 -0
- package/dist/vite/liquid-gradient-shader.d.ts.map +1 -0
- package/dist/vite/liquid-gradient-shader.js +413 -0
- package/dist/vite/mdx-client.d.ts +15 -0
- package/dist/vite/mdx-client.d.ts.map +1 -0
- package/dist/vite/mdx-client.js +265 -0
- package/dist/vite/mdx-parse.d.ts +102 -0
- package/dist/vite/mdx-parse.d.ts.map +1 -0
- package/dist/vite/mdx-parse.js +251 -0
- package/dist/vite/mdx-provided-components.d.ts +18 -0
- package/dist/vite/mdx-provided-components.d.ts.map +1 -0
- package/dist/vite/mdx-provided-components.js +14 -0
- package/dist/vite/mdx-video.d.ts +327 -0
- package/dist/vite/mdx-video.d.ts.map +1 -0
- package/dist/vite/mdx-video.js +596 -0
- package/dist/vite/mdx-video.test.d.ts +9 -0
- package/dist/vite/mdx-video.test.d.ts.map +1 -0
- package/dist/vite/mdx-video.test.js +1945 -0
- package/dist/vite/media-components.d.ts +45 -0
- package/dist/vite/media-components.d.ts.map +1 -0
- package/dist/vite/media-components.js +355 -0
- package/dist/vite/media-duration-store.d.ts +66 -0
- package/dist/vite/media-duration-store.d.ts.map +1 -0
- package/dist/vite/media-duration-store.js +204 -0
- package/dist/vite/player-page.d.ts +35 -0
- package/dist/vite/player-page.d.ts.map +1 -0
- package/dist/vite/player-page.js +720 -0
- package/dist/vite/render-client.d.ts +18 -0
- package/dist/vite/render-client.d.ts.map +1 -0
- package/dist/vite/render-client.js +67 -0
- package/dist/vite/sdk.d.ts +221 -0
- package/dist/vite/sdk.d.ts.map +1 -0
- package/dist/vite/sdk.js +355 -0
- package/dist/vite/server-components.d.ts +55 -0
- package/dist/vite/server-components.d.ts.map +1 -0
- package/dist/vite/server-components.js +156 -0
- package/dist/vite/server-mdx.d.ts +74 -0
- package/dist/vite/server-mdx.d.ts.map +1 -0
- package/dist/vite/server-mdx.js +259 -0
- package/dist/vite/shader-renderer.d.ts +48 -0
- package/dist/vite/shader-renderer.d.ts.map +1 -0
- package/dist/vite/shader-renderer.js +411 -0
- package/dist/vite/store-hooks.d.ts +12 -0
- package/dist/vite/store-hooks.d.ts.map +1 -0
- package/dist/vite/store-hooks.js +93 -0
- package/dist/vite/store.d.ts +75 -0
- package/dist/vite/store.d.ts.map +1 -0
- package/dist/vite/store.js +52 -0
- package/dist/vite/store.test.d.ts +6 -0
- package/dist/vite/store.test.d.ts.map +1 -0
- package/dist/vite/store.test.js +172 -0
- package/dist/vite/tweakpane-hook.d.ts +94 -0
- package/dist/vite/tweakpane-hook.d.ts.map +1 -0
- package/dist/vite/tweakpane-hook.js +450 -0
- package/dist/vite/vite-plugin.d.ts +28 -0
- package/dist/vite/vite-plugin.d.ts.map +1 -0
- package/dist/vite/vite-plugin.js +538 -0
- package/dist/vite/wave-gradient-shader.d.ts +41 -0
- package/dist/vite/wave-gradient-shader.d.ts.map +1 -0
- package/dist/vite/wave-gradient-shader.js +207 -0
- package/package.json +103 -25
- package/skills/egaki/SKILL.md +162 -1
- package/src/cli/cache-utils.ts +169 -0
- package/src/cli/cached-generate.test.ts +531 -0
- package/src/cli/cached-generate.ts +348 -0
- package/src/cli/cartesia-provider.ts +249 -0
- package/src/cli/cli.ts +1818 -0
- package/src/cli/clone-generate.ts +202 -0
- package/src/{credentials.ts → cli/credentials.ts} +90 -7
- package/src/cli/elevenlabs-speech-provider.ts +243 -0
- package/src/cli/generate.test.ts +166 -0
- package/src/cli/generate.ts +912 -0
- package/src/{login.ts → cli/login.ts} +48 -3
- package/src/cli/main.ts +8 -0
- package/src/cli/model-catalog.ts +2336 -0
- package/src/cli/models.ts +408 -0
- package/src/cli/openai-speech-provider.ts +34 -0
- package/src/cli/separation-generate.ts +187 -0
- package/src/cli/speech-catalog.ts +253 -0
- package/src/cli/speech-generate.ts +275 -0
- package/src/cli/speech-models.ts +146 -0
- package/src/cli/speech-provider.test.ts +226 -0
- package/src/cli/transcription-catalog.ts +250 -0
- package/src/cli/transcription-generate.ts +214 -0
- package/src/cli/transcription-models.ts +136 -0
- package/src/cli/transcription-providers.ts +427 -0
- package/src/cli/video-model-catalog.ts +14 -0
- package/src/cli/xai-auth.ts +396 -0
- package/src/vite/angled-screen.tsx +214 -0
- package/src/vite/app.tsx +197 -0
- package/src/vite/bands-shader.tsx +319 -0
- package/src/vite/code-block.tsx +939 -0
- package/src/vite/components.tsx +278 -0
- package/src/vite/dispersion-rings-shader.tsx +331 -0
- package/src/vite/easing-curves.test.ts +232 -0
- package/src/vite/easing-curves.ts +743 -0
- package/src/vite/generated-media-client.tsx +121 -0
- package/src/vite/keyframes.tsx +347 -0
- package/src/vite/layout-editor.tsx +709 -0
- package/src/vite/layout-transition.tsx +706 -0
- package/src/vite/liquid-gradient-shader.tsx +474 -0
- package/src/vite/mdx-client.tsx +420 -0
- package/src/vite/mdx-parse.ts +343 -0
- package/src/vite/mdx-provided-components.ts +19 -0
- package/src/vite/mdx-video.test.tsx +2219 -0
- package/src/vite/mdx-video.tsx +976 -0
- package/src/vite/media-components.tsx +473 -0
- package/src/vite/media-duration-store.ts +225 -0
- package/src/vite/motion-timing.ts +194 -0
- package/src/vite/player-page.tsx +1107 -0
- package/src/vite/render-client.ts +84 -0
- package/src/vite/sdk.ts +598 -0
- package/src/vite/server-components.tsx +241 -0
- package/src/vite/server-mdx.ts +285 -0
- package/src/vite/shader-renderer.tsx +535 -0
- package/src/vite/store-hooks.ts +117 -0
- package/src/vite/store.test.ts +202 -0
- package/src/vite/store.ts +115 -0
- package/src/vite/styles.css +21 -0
- package/src/vite/tweakpane-hook.tsx +593 -0
- package/src/vite/virtual-modules.d.ts +30 -0
- package/src/vite/vite-plugin.ts +584 -0
- package/src/vite/wave-gradient-shader.tsx +253 -0
- package/README.md +0 -266
- package/dist/antigravity-auth.d.ts +0 -15
- package/dist/antigravity-auth.d.ts.map +0 -1
- package/dist/antigravity-auth.js +0 -544
- package/dist/antigravity-image.d.ts +0 -11
- package/dist/antigravity-image.d.ts.map +0 -1
- package/dist/antigravity-image.js +0 -285
- package/dist/chatgpt-auth.d.ts.map +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -968
- package/dist/credentials.d.ts.map +0 -1
- package/dist/gateway-model-candidates.generated.d.ts +0 -11
- package/dist/gateway-model-candidates.generated.d.ts.map +0 -1
- package/dist/gateway-model-candidates.generated.js +0 -64
- package/dist/login.d.ts.map +0 -1
- package/dist/model-catalog.d.ts +0 -42
- package/dist/model-catalog.d.ts.map +0 -1
- package/dist/model-catalog.js +0 -1001
- package/dist/models.d.ts.map +0 -1
- package/dist/models.js +0 -246
- package/dist/open-browser.d.ts.map +0 -1
- package/dist/subscription.d.ts.map +0 -1
- package/dist/video-model-catalog.d.ts +0 -52
- package/dist/video-model-catalog.d.ts.map +0 -1
- package/dist/video-model-catalog.js +0 -306
- package/src/cli.ts +0 -1378
- package/src/model-catalog.ts +0 -1077
- package/src/models.ts +0 -309
- package/src/video-model-catalog.ts +0 -363
- /package/dist/{chatgpt-auth.d.ts → cli/chatgpt-auth.d.ts} +0 -0
- /package/dist/{chatgpt-auth.js → cli/chatgpt-auth.js} +0 -0
- /package/dist/{login.d.ts → cli/login.d.ts} +0 -0
- /package/dist/{open-browser.d.ts → cli/open-browser.d.ts} +0 -0
- /package/dist/{open-browser.js → cli/open-browser.js} +0 -0
- /package/dist/{subscription.d.ts → cli/subscription.d.ts} +0 -0
- /package/dist/{subscription.js → cli/subscription.js} +0 -0
- /package/src/{chatgpt-auth.ts → cli/chatgpt-auth.ts} +0 -0
- /package/src/{open-browser.ts → cli/open-browser.ts} +0 -0
- /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
|
+
}
|
package/src/vite/sdk.ts
ADDED
|
@@ -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
|
+
}
|