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.
- package/CHANGELOG.md +145 -9
- 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.js → cli/cli.js} +626 -600
- 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/cli/credentials.d.ts.map +1 -0
- package/dist/{credentials.js → cli/credentials.js} +20 -0
- 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/cli/main.d.ts +3 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +7 -0
- package/dist/{model-catalog.d.ts → cli/model-catalog.d.ts} +6 -0
- package/dist/cli/model-catalog.d.ts.map +1 -0
- package/dist/{model-catalog.js → cli/model-catalog.js} +714 -4
- package/dist/{models.d.ts → cli/models.d.ts} +12 -5
- package/dist/cli/models.d.ts.map +1 -0
- package/dist/{models.js → cli/models.js} +58 -57
- 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.map +1 -0
- package/dist/cli/xai-auth.d.ts.map +1 -0
- package/dist/{xai-auth.js → cli/xai-auth.js} +1 -1
- 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 +539 -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 +101 -24
- 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.ts → cli/cli.ts} +836 -884
- package/src/cli/clone-generate.ts +202 -0
- package/src/{credentials.ts → cli/credentials.ts} +20 -0
- 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/cli/main.ts +8 -0
- package/src/{model-catalog.ts → cli/model-catalog.ts} +787 -58
- package/src/{models.ts → cli/models.ts} +65 -86
- 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/{xai-auth.ts → cli/xai-auth.ts} +1 -1
- 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 +585 -0
- package/src/vite/wave-gradient-shader.tsx +253 -0
- package/README.md +0 -370
- 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/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.map +0 -1
- package/dist/models.d.ts.map +0 -1
- package/dist/open-browser.d.ts.map +0 -1
- package/dist/subscription.d.ts.map +0 -1
- package/dist/video-model-catalog.d.ts.map +0 -1
- package/dist/xai-auth.d.ts.map +0 -1
- /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/{credentials.d.ts → cli/credentials.d.ts} +0 -0
- /package/dist/{login.d.ts → cli/login.d.ts} +0 -0
- /package/dist/{login.js → cli/login.js} +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/dist/{video-model-catalog.d.ts → cli/video-model-catalog.d.ts} +0 -0
- /package/dist/{video-model-catalog.js → cli/video-model-catalog.js} +0 -0
- /package/dist/{xai-auth.d.ts → cli/xai-auth.d.ts} +0 -0
- /package/src/{chatgpt-auth.ts → cli/chatgpt-auth.ts} +0 -0
- /package/src/{login.ts → cli/login.ts} +0 -0
- /package/src/{open-browser.ts → cli/open-browser.ts} +0 -0
- /package/src/{subscription.ts → cli/subscription.ts} +0 -0
- /package/src/{video-model-catalog.ts → cli/video-model-catalog.ts} +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Client wrappers for server-generated media components.
|
|
5
|
+
*
|
|
6
|
+
* Each wrapper receives a `srcPromise: Promise<string>` from the server
|
|
7
|
+
* component (streamed through RSC flight) and a `fallbackSrc` for the
|
|
8
|
+
* stale file to show while generation is in progress. React 19's `use()`
|
|
9
|
+
* suspends until the promise resolves, then renders the actual media
|
|
10
|
+
* component. While suspended, the fallback (stale generation) renders if
|
|
11
|
+
* available; otherwise nothing renders.
|
|
12
|
+
*
|
|
13
|
+
* Passthrough props are forwarded to the underlying media component so
|
|
14
|
+
* users can set style, className, trimBefore, trimAfter, playbackRate, etc.
|
|
15
|
+
*
|
|
16
|
+
* Generation tracking: handled server-side via the /api/generation-progress
|
|
17
|
+
* SSE stream. The server tracks all active generations across all scenes
|
|
18
|
+
* and the client fetches progress updates to show in the toolbar.
|
|
19
|
+
* The Suspense fallback only handles delayRender for export mode.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { Suspense, use, useLayoutEffect, type ComponentProps, type ReactNode } from 'react'
|
|
23
|
+
import { useDelayRender } from 'remotion'
|
|
24
|
+
import { Img, Audio, Video, useIsExporting } from './mdx-video.tsx'
|
|
25
|
+
import { useGenerationStatus, type GenerationStatus } from './store-hooks.ts'
|
|
26
|
+
|
|
27
|
+
export { useGenerationStatus, type GenerationStatus }
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Export-aware Suspense fallback — blocks Remotion frame capture while
|
|
31
|
+
// a generated media promise is still pending. Also registers/unregisters
|
|
32
|
+
// the generation in the tracker so the toolbar can show status.
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
/** Export-aware Suspense fallback — blocks Remotion frame capture while
|
|
36
|
+
* a generated media promise is still pending. Generation progress tracking
|
|
37
|
+
* is now handled server-side via the /api/generation-progress SSE stream,
|
|
38
|
+
* so this component only needs to handle delayRender for exports. */
|
|
39
|
+
function GeneratedMediaFallback({ children }: { children?: ReactNode }) {
|
|
40
|
+
const isExporting = useIsExporting()
|
|
41
|
+
const { delayRender, continueRender } = useDelayRender()
|
|
42
|
+
|
|
43
|
+
// useLayoutEffect prevents a first-frame capture race: the delay handle
|
|
44
|
+
// is registered before the browser paints, matching Remotion's own pattern.
|
|
45
|
+
useLayoutEffect(() => {
|
|
46
|
+
if (!isExporting) return
|
|
47
|
+
const handle = delayRender('Waiting for generated media', {
|
|
48
|
+
timeoutInMilliseconds: 10 * 60 * 1000,
|
|
49
|
+
})
|
|
50
|
+
return () => continueRender(handle)
|
|
51
|
+
}, [isExporting, delayRender, continueRender])
|
|
52
|
+
return <>{children}</>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Inner components that call use() — must be inside <Suspense>
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
function ResolvedImage({ srcPromise, ...rest }: { srcPromise: Promise<string> } & ComponentProps<typeof Img>) {
|
|
60
|
+
const src = use(srcPromise)
|
|
61
|
+
return <Img src={src} {...rest} />
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function ResolvedVideo({ srcPromise, ...rest }: { srcPromise: Promise<string> } & ComponentProps<typeof Video>) {
|
|
65
|
+
const src = use(srcPromise)
|
|
66
|
+
return <Video src={src} {...rest} />
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function ResolvedAudio({ srcPromise, ...rest }: { srcPromise: Promise<string> } & ComponentProps<typeof Audio>) {
|
|
70
|
+
const src = use(srcPromise)
|
|
71
|
+
return <Audio src={src} {...rest} />
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Exported client wrappers — render fallback while promise is pending
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
export function GeneratedImageClient({
|
|
79
|
+
srcPromise,
|
|
80
|
+
fallbackSrc,
|
|
81
|
+
...rest
|
|
82
|
+
}: {
|
|
83
|
+
srcPromise: Promise<string>
|
|
84
|
+
fallbackSrc?: string
|
|
85
|
+
} & ComponentProps<typeof Img>) {
|
|
86
|
+
return (
|
|
87
|
+
<Suspense fallback={<GeneratedMediaFallback>{fallbackSrc ? <Img src={fallbackSrc} {...rest} /> : null}</GeneratedMediaFallback>}>
|
|
88
|
+
<ResolvedImage srcPromise={srcPromise} {...rest} />
|
|
89
|
+
</Suspense>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function GeneratedVideoClient({
|
|
94
|
+
srcPromise,
|
|
95
|
+
fallbackSrc,
|
|
96
|
+
...rest
|
|
97
|
+
}: {
|
|
98
|
+
srcPromise: Promise<string>
|
|
99
|
+
fallbackSrc?: string
|
|
100
|
+
} & ComponentProps<typeof Video>) {
|
|
101
|
+
return (
|
|
102
|
+
<Suspense fallback={<GeneratedMediaFallback>{fallbackSrc ? <Video src={fallbackSrc} {...rest} /> : null}</GeneratedMediaFallback>}>
|
|
103
|
+
<ResolvedVideo srcPromise={srcPromise} {...rest} />
|
|
104
|
+
</Suspense>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function GeneratedSpeechClient({
|
|
109
|
+
srcPromise,
|
|
110
|
+
fallbackSrc,
|
|
111
|
+
...rest
|
|
112
|
+
}: {
|
|
113
|
+
srcPromise: Promise<string>
|
|
114
|
+
fallbackSrc?: string
|
|
115
|
+
} & ComponentProps<typeof Audio>) {
|
|
116
|
+
return (
|
|
117
|
+
<Suspense fallback={<GeneratedMediaFallback>{fallbackSrc ? <Audio src={fallbackSrc} {...rest} /> : null}</GeneratedMediaFallback>}>
|
|
118
|
+
<ResolvedAudio srcPromise={srcPromise} {...rest} />
|
|
119
|
+
</Suspense>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* keyframes() — evaluate a keyframed animation at a given frame.
|
|
3
|
+
*
|
|
4
|
+
* Accepts an array of typed keyframe descriptors with bezier easing, hold,
|
|
5
|
+
* and per-dimension control. Wraps Remotion's interpolate() + cubicBezier()
|
|
6
|
+
* so you get the full Lottie/After Effects easing model with clean parameters.
|
|
7
|
+
*
|
|
8
|
+
* Also includes fromLottieProperty() for converting raw Lottie animated
|
|
9
|
+
* properties to the Keyframe[] format, and extractLottieDimensionEasing()
|
|
10
|
+
* for per-dimension easing overrides.
|
|
11
|
+
*
|
|
12
|
+
* See docs/lottie-to-remotion.md for the Lottie field mapping.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { interpolate } from 'remotion'
|
|
16
|
+
import { cubicBezier } from './easing-curves.ts'
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Types
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Cubic bezier control points: [x1, y1, x2, y2].
|
|
24
|
+
* Same as CSS `cubic-bezier(x1, y1, x2, y2)`.
|
|
25
|
+
* x values must be in [0, 1]. y values can overshoot (< 0 or > 1).
|
|
26
|
+
*/
|
|
27
|
+
export type BezierCurve = [x1: number, y1: number, x2: number, y2: number]
|
|
28
|
+
|
|
29
|
+
/** A single keyframe in a `keyframes()` animation. */
|
|
30
|
+
export interface Keyframe<T extends number | number[] = number> {
|
|
31
|
+
/** Frame number where this keyframe occurs. */
|
|
32
|
+
time: number
|
|
33
|
+
/** Value at this keyframe. Scalar or vector (e.g. [x, y] for position). */
|
|
34
|
+
value: T
|
|
35
|
+
/**
|
|
36
|
+
* Bezier easing curve for the transition FROM this keyframe TO the next.
|
|
37
|
+
* Four control points [x1, y1, x2, y2], same as CSS cubic-bezier().
|
|
38
|
+
* Omit for linear interpolation. Ignored on the last keyframe.
|
|
39
|
+
*/
|
|
40
|
+
easing?: BezierCurve
|
|
41
|
+
/**
|
|
42
|
+
* If true, value holds constant until the next keyframe (step function).
|
|
43
|
+
* No interpolation occurs. Overrides `easing`.
|
|
44
|
+
*/
|
|
45
|
+
hold?: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Options for per-dimension easing on vector keyframes. */
|
|
49
|
+
export interface KeyframesDimensionOptions {
|
|
50
|
+
/**
|
|
51
|
+
* Per-dimension bezier easing overrides.
|
|
52
|
+
* Index matches the dimension index in the value array.
|
|
53
|
+
* When set, overrides the keyframe-level `easing` for that dimension.
|
|
54
|
+
* Each entry is a [x1, y1, x2, y2] bezier curve or undefined to use the keyframe's easing.
|
|
55
|
+
*/
|
|
56
|
+
dimensionEasing?: (BezierCurve | undefined)[]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Internal helpers
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
// Step easing: holds at 0 until t=1, then jumps to 1
|
|
64
|
+
function stepEasing(t: number): number {
|
|
65
|
+
return t < 1 ? 0 : 1
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function buildEasingFn(curve: BezierCurve | undefined): (t: number) => number {
|
|
69
|
+
if (!curve) return (t: number) => t // linear
|
|
70
|
+
return cubicBezier(curve[0], curve[1], curve[2], curve[3])
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// keyframes() — main API
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Evaluate a keyframed animation at the given frame.
|
|
79
|
+
*
|
|
80
|
+
* Scalar version: each keyframe has a numeric `value`, returns a number.
|
|
81
|
+
*
|
|
82
|
+
* ```ts
|
|
83
|
+
* const opacity = keyframes(frame, [
|
|
84
|
+
* { time: 0, value: 0, easing: [0.33, 0, 0.67, 1] },
|
|
85
|
+
* { time: 30, value: 1 },
|
|
86
|
+
* ])
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export function keyframes(
|
|
90
|
+
frame: number,
|
|
91
|
+
kfs: Keyframe<number>[],
|
|
92
|
+
options?: KeyframesDimensionOptions,
|
|
93
|
+
): number
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Vector version: each keyframe has an array `value`, returns an array
|
|
97
|
+
* of the same length. Supports per-dimension easing via options.
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* const [x, y] = keyframes(frame, [
|
|
101
|
+
* { time: 0, value: [0, 0], easing: [0.33, 0, 0.67, 1] },
|
|
102
|
+
* { time: 30, value: [200, 400] },
|
|
103
|
+
* ])
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export function keyframes<N extends number>(
|
|
107
|
+
frame: number,
|
|
108
|
+
kfs: Keyframe<number[] & { length: N }>[],
|
|
109
|
+
options?: KeyframesDimensionOptions,
|
|
110
|
+
): number[]
|
|
111
|
+
|
|
112
|
+
export function keyframes(
|
|
113
|
+
frame: number,
|
|
114
|
+
kfs: Keyframe<number | number[]>[],
|
|
115
|
+
options?: KeyframesDimensionOptions,
|
|
116
|
+
): number | number[] {
|
|
117
|
+
if (kfs.length === 0) {
|
|
118
|
+
throw new Error('keyframes() requires at least one keyframe')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (kfs.length === 1) {
|
|
122
|
+
return kfs[0]!.value
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const first = kfs[0]!.value
|
|
126
|
+
const isVector = Array.isArray(first)
|
|
127
|
+
|
|
128
|
+
if (isVector) {
|
|
129
|
+
return evaluateVector(frame, kfs as Keyframe<number[]>[], options)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return evaluateScalar(frame, kfs as Keyframe<number>[], options)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function evaluateScalar(
|
|
136
|
+
frame: number,
|
|
137
|
+
kfs: Keyframe<number>[],
|
|
138
|
+
options?: KeyframesDimensionOptions,
|
|
139
|
+
): number {
|
|
140
|
+
const inputRange = kfs.map((kf) => kf.time)
|
|
141
|
+
const outputRange = kfs.map((kf) => kf.value)
|
|
142
|
+
const dimEasing = options?.dimensionEasing?.[0]
|
|
143
|
+
|
|
144
|
+
const easings: ((t: number) => number)[] = []
|
|
145
|
+
for (let i = 0; i < kfs.length - 1; i++) {
|
|
146
|
+
const kf = kfs[i]!
|
|
147
|
+
if (kf.hold) {
|
|
148
|
+
easings.push(stepEasing)
|
|
149
|
+
} else {
|
|
150
|
+
easings.push(buildEasingFn(dimEasing ?? kf.easing))
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return interpolate(frame, inputRange, outputRange, {
|
|
155
|
+
easing: easings,
|
|
156
|
+
extrapolateLeft: 'clamp',
|
|
157
|
+
extrapolateRight: 'clamp',
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function evaluateVector(
|
|
162
|
+
frame: number,
|
|
163
|
+
kfs: Keyframe<number[]>[],
|
|
164
|
+
options?: KeyframesDimensionOptions,
|
|
165
|
+
): number[] {
|
|
166
|
+
const dimensions = kfs[0]!.value.length
|
|
167
|
+
const result: number[] = new Array(dimensions)
|
|
168
|
+
|
|
169
|
+
for (let dim = 0; dim < dimensions; dim++) {
|
|
170
|
+
const inputRange = kfs.map((kf) => kf.time)
|
|
171
|
+
const outputRange = kfs.map((kf) => kf.value[dim]!)
|
|
172
|
+
const dimEasing = options?.dimensionEasing?.[dim]
|
|
173
|
+
|
|
174
|
+
const easings: ((t: number) => number)[] = []
|
|
175
|
+
for (let i = 0; i < kfs.length - 1; i++) {
|
|
176
|
+
const kf = kfs[i]!
|
|
177
|
+
if (kf.hold) {
|
|
178
|
+
easings.push(stepEasing)
|
|
179
|
+
} else {
|
|
180
|
+
easings.push(buildEasingFn(dimEasing ?? kf.easing))
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
result[dim] = interpolate(frame, inputRange, outputRange as number[], {
|
|
185
|
+
easing: easings,
|
|
186
|
+
extrapolateLeft: 'clamp',
|
|
187
|
+
extrapolateRight: 'clamp',
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return result
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// Lottie conversion utilities
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
/** Raw Lottie easing handle shape. */
|
|
199
|
+
export interface LottieEasingHandle {
|
|
200
|
+
x: number | number[]
|
|
201
|
+
y: number | number[]
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Raw Lottie keyframe as found in a .json file. */
|
|
205
|
+
export interface LottieKeyframe {
|
|
206
|
+
/** Frame number */
|
|
207
|
+
t: number
|
|
208
|
+
/** Value at this keyframe */
|
|
209
|
+
s?: number[]
|
|
210
|
+
/** Hold flag */
|
|
211
|
+
h?: 0 | 1
|
|
212
|
+
/** In tangent (easing into next keyframe) */
|
|
213
|
+
i?: LottieEasingHandle
|
|
214
|
+
/** Out tangent (easing leaving this keyframe) */
|
|
215
|
+
o?: LottieEasingHandle
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Raw Lottie animated property: { a: 0|1, k: value | keyframes }. */
|
|
219
|
+
export interface LottieAnimatedProperty {
|
|
220
|
+
/** 1 if animated, 0 if static */
|
|
221
|
+
a: 0 | 1
|
|
222
|
+
/** Static value (when a=0) or array of keyframes (when a=1) */
|
|
223
|
+
k: number | number[] | LottieKeyframe[]
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Convert a raw Lottie animated property into a `Keyframe[]` array
|
|
228
|
+
* that can be passed directly to `keyframes()`.
|
|
229
|
+
*
|
|
230
|
+
* ```ts
|
|
231
|
+
* import lottieJson from './animation.json'
|
|
232
|
+
*
|
|
233
|
+
* // Get the opacity property from layer 0
|
|
234
|
+
* const opacityKfs = fromLottieProperty(lottieJson.layers[0].ks.o)
|
|
235
|
+
* const opacity = keyframes(frame, opacityKfs)
|
|
236
|
+
* ```
|
|
237
|
+
*
|
|
238
|
+
* For static properties (a=0), returns a single keyframe at time 0.
|
|
239
|
+
* For vector properties, values are preserved as arrays.
|
|
240
|
+
*/
|
|
241
|
+
export function fromLottieProperty(
|
|
242
|
+
property: LottieAnimatedProperty,
|
|
243
|
+
): Keyframe<number>[] | Keyframe<number[]>[] {
|
|
244
|
+
// Static property
|
|
245
|
+
if (!property.a || !Array.isArray(property.k) || property.k.length === 0) {
|
|
246
|
+
const val = property.k
|
|
247
|
+
if (Array.isArray(val)) {
|
|
248
|
+
return [{ time: 0, value: val }] as Keyframe<number[]>[]
|
|
249
|
+
}
|
|
250
|
+
return [{ time: 0, value: val as number }]
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const lottieKfs = property.k as LottieKeyframe[]
|
|
254
|
+
|
|
255
|
+
// Detect if this is a scalar or vector property from the first keyframe's value
|
|
256
|
+
const firstValue = lottieKfs[0]!.s
|
|
257
|
+
if (!firstValue || firstValue.length === 0) {
|
|
258
|
+
return [{ time: 0, value: 0 }]
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const isScalar = firstValue.length === 1
|
|
262
|
+
|
|
263
|
+
if (isScalar) {
|
|
264
|
+
return lottieKfs.map((lkf, i): Keyframe<number> => {
|
|
265
|
+
const kf: Keyframe<number> = {
|
|
266
|
+
time: lkf.t,
|
|
267
|
+
value: lkf.s?.[0] ?? 0,
|
|
268
|
+
}
|
|
269
|
+
if (lkf.h === 1) {
|
|
270
|
+
kf.hold = true
|
|
271
|
+
} else if (lkf.o && lkf.i && i < lottieKfs.length - 1) {
|
|
272
|
+
kf.easing = extractBezier(lkf.o, lkf.i, 0)
|
|
273
|
+
}
|
|
274
|
+
return kf
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Vector property
|
|
279
|
+
return lottieKfs.map((lkf, i): Keyframe<number[]> => {
|
|
280
|
+
const kf: Keyframe<number[]> = {
|
|
281
|
+
time: lkf.t,
|
|
282
|
+
value: lkf.s ? [...lkf.s] : [],
|
|
283
|
+
}
|
|
284
|
+
if (lkf.h === 1) {
|
|
285
|
+
kf.hold = true
|
|
286
|
+
} else if (lkf.o && lkf.i && i < lottieKfs.length - 1) {
|
|
287
|
+
// Use dimension 0 for the keyframe-level easing
|
|
288
|
+
kf.easing = extractBezier(lkf.o, lkf.i, 0)
|
|
289
|
+
}
|
|
290
|
+
return kf
|
|
291
|
+
})
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Extract per-dimension easing overrides from a Lottie keyframe sequence.
|
|
296
|
+
* Returns a `dimensionEasing` array suitable for the `keyframes()` options.
|
|
297
|
+
* Only needed when the Lottie property has different easing per dimension
|
|
298
|
+
* (e.g. position with independent X/Y curves).
|
|
299
|
+
*
|
|
300
|
+
* ```ts
|
|
301
|
+
* const posKfs = fromLottieProperty(lottieJson.layers[0].ks.p)
|
|
302
|
+
* const dimEasing = extractLottieDimensionEasing(lottieJson.layers[0].ks.p, segmentIndex)
|
|
303
|
+
* const [x, y] = keyframes(frame, posKfs, { dimensionEasing: dimEasing })
|
|
304
|
+
* ```
|
|
305
|
+
*/
|
|
306
|
+
export function extractLottieDimensionEasing(
|
|
307
|
+
property: LottieAnimatedProperty,
|
|
308
|
+
segmentIndex: number,
|
|
309
|
+
): (BezierCurve | undefined)[] | undefined {
|
|
310
|
+
if (!property.a || !Array.isArray(property.k)) return undefined
|
|
311
|
+
const lottieKfs = property.k as LottieKeyframe[]
|
|
312
|
+
const lkf = lottieKfs[segmentIndex]
|
|
313
|
+
if (!lkf?.o || !lkf?.i || !lkf.s) return undefined
|
|
314
|
+
|
|
315
|
+
const dimensions = lkf.s.length
|
|
316
|
+
if (dimensions <= 1) return undefined
|
|
317
|
+
|
|
318
|
+
// Check if all dimensions have the same easing — if so, no overrides needed
|
|
319
|
+
const ox = lkf.o.x
|
|
320
|
+
const oy = lkf.o.y
|
|
321
|
+
const ix = lkf.i.x
|
|
322
|
+
const iy = lkf.i.y
|
|
323
|
+
if (!Array.isArray(ox) || !Array.isArray(oy) || !Array.isArray(ix) || !Array.isArray(iy)) {
|
|
324
|
+
return undefined
|
|
325
|
+
}
|
|
326
|
+
if (ox.length <= 1 && oy.length <= 1 && ix.length <= 1 && iy.length <= 1) {
|
|
327
|
+
return undefined
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const result: (BezierCurve | undefined)[] = []
|
|
331
|
+
for (let dim = 0; dim < dimensions; dim++) {
|
|
332
|
+
result.push(extractBezier(lkf.o, lkf.i, dim))
|
|
333
|
+
}
|
|
334
|
+
return result
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function extractBezier(
|
|
338
|
+
out: LottieEasingHandle,
|
|
339
|
+
into: LottieEasingHandle,
|
|
340
|
+
dimension: number,
|
|
341
|
+
): BezierCurve {
|
|
342
|
+
const ox = Array.isArray(out.x) ? (out.x[dimension] ?? out.x[0]!) : out.x
|
|
343
|
+
const oy = Array.isArray(out.y) ? (out.y[dimension] ?? out.y[0]!) : out.y
|
|
344
|
+
const ix = Array.isArray(into.x) ? (into.x[dimension] ?? into.x[0]!) : into.x
|
|
345
|
+
const iy = Array.isArray(into.y) ? (into.y[dimension] ?? into.y[0]!) : into.y
|
|
346
|
+
return [ox, oy, ix, iy]
|
|
347
|
+
}
|