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,265 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Client-side MDX application.
|
|
5
|
+
*
|
|
6
|
+
* All MDX processing happens in the browser: parsing, section splitting,
|
|
7
|
+
* user module resolution, and safe-mdx rendering. Because rendering runs on
|
|
8
|
+
* the client there is no RSC serialization boundary between MDX content and
|
|
9
|
+
* the components — expression props can be functions (easing={x => x}),
|
|
10
|
+
* imported values can be anything, and user components don't need a
|
|
11
|
+
* 'use client' directive.
|
|
12
|
+
*
|
|
13
|
+
* The server (app.tsx) only passes the raw MDX source string through the
|
|
14
|
+
* RSC flight payload. Entry MDX edits flow server → client via rsc:update;
|
|
15
|
+
* user .tsx/.ts/.mdx edits flow through the client module graph: this
|
|
16
|
+
* module accepts HMR updates of virtual:egaki-modules directly via
|
|
17
|
+
* import.meta.hot.accept(dep, cb) and pushes the fresh map into React
|
|
18
|
+
* through useSyncExternalStore.
|
|
19
|
+
*/
|
|
20
|
+
import { createContext, useContext, useEffect, useMemo } from 'react';
|
|
21
|
+
import { SafeMdxRenderer } from 'safe-mdx';
|
|
22
|
+
import { mdxParse, extractImports, resolveModulePath } from 'safe-mdx/parse';
|
|
23
|
+
import { eagerModules as initialModules } from 'virtual:egaki-modules';
|
|
24
|
+
import { splitIntoSections, calculateTotalDuration, resolveAutoDurations, parseFrontmatter, buildMdxScope } from "./mdx-parse.js";
|
|
25
|
+
import { filterImportNodesToModules } from "./server-mdx.js";
|
|
26
|
+
import { PlayerPage } from "./player-page.js";
|
|
27
|
+
import { MDX_BUILTIN_COMPONENTS, Img, ServerSlotsContext } from "./mdx-video.js";
|
|
28
|
+
import { MdxCodeBlockWrapper } from "./code-block.js";
|
|
29
|
+
import { egakiStore } from "./store.js";
|
|
30
|
+
import { useModules, useMediaDurations } from "./store-hooks.js";
|
|
31
|
+
import { resetSectionDurations } from "./media-duration-store.js";
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// MDX components map
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
const FONT_SANS = '-apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", sans-serif';
|
|
36
|
+
const FONT_MONO = '"SF Mono", ui-monospace, SFMono-Regular, "Cascadia Code", monospace';
|
|
37
|
+
function buildVideoMdxComponents() {
|
|
38
|
+
return {
|
|
39
|
+
...MDX_BUILTIN_COMPONENTS,
|
|
40
|
+
// Reserved: server component slot marker. The server renders the
|
|
41
|
+
// original children; this client component splices the slot in by
|
|
42
|
+
// matching its data-markdown-line against the slot keys.
|
|
43
|
+
Server,
|
|
44
|
+
// Standard element overrides
|
|
45
|
+
p: ({ children }) => (_jsx("div", { style: {
|
|
46
|
+
fontSize: 'clamp(1.5rem, 2.5vw, 3rem)', fontWeight: 400,
|
|
47
|
+
color: '#a1a1aa', fontFamily: FONT_SANS, textAlign: 'center',
|
|
48
|
+
letterSpacing: '-0.02em', lineHeight: 1.4, maxWidth: '80%',
|
|
49
|
+
}, children: children })),
|
|
50
|
+
strong: ({ children }) => (_jsx("span", { style: { color: '#fafafa', fontWeight: 600 }, children: children })),
|
|
51
|
+
em: ({ children }) => (_jsx("span", { style: { fontStyle: 'italic' }, children: children })),
|
|
52
|
+
a: ({ children }) => (_jsx("span", { style: { color: '#818cf8', textDecoration: 'underline' }, children: children })),
|
|
53
|
+
h1: () => null, h2: () => null, h3: () => null,
|
|
54
|
+
h4: () => null, h5: () => null, h6: () => null,
|
|
55
|
+
blockquote: () => null,
|
|
56
|
+
pre: MdxCodeBlockWrapper,
|
|
57
|
+
inlineCode: ({ children }) => (_jsx("span", { style: {
|
|
58
|
+
fontFamily: FONT_MONO, fontSize: '0.875em', color: '#e4e4e7',
|
|
59
|
+
background: 'rgba(255, 255, 255, 0.06)', borderRadius: '0.25em',
|
|
60
|
+
padding: '0.1em 0.4em',
|
|
61
|
+
}, children: children })),
|
|
62
|
+
ul: ({ children }) => (_jsx("div", { style: {
|
|
63
|
+
fontSize: 'clamp(1.25rem, 2vw, 2rem)', color: '#a1a1aa',
|
|
64
|
+
fontFamily: FONT_SANS, textAlign: 'left', display: 'flex',
|
|
65
|
+
flexDirection: 'column', gap: '0.4em',
|
|
66
|
+
}, children: children })),
|
|
67
|
+
ol: ({ children }) => (_jsx("div", { style: {
|
|
68
|
+
fontSize: 'clamp(1.25rem, 2vw, 2rem)', color: '#a1a1aa',
|
|
69
|
+
fontFamily: FONT_SANS, textAlign: 'left', display: 'flex',
|
|
70
|
+
flexDirection: 'column', gap: '0.4em',
|
|
71
|
+
}, children: children })),
|
|
72
|
+
li: ({ children }) => (_jsxs("div", { style: { display: 'flex', gap: '0.5em' }, children: [_jsx("span", { style: { color: '#52525b' }, children: "\u2022" }), _jsx("span", { children: children })] })),
|
|
73
|
+
img: ({ src, alt }) => (_jsx(Img, { src: src, alt: alt || '', style: {
|
|
74
|
+
maxWidth: '80%', maxHeight: '70%', objectFit: 'contain', borderRadius: '0.5em',
|
|
75
|
+
} })),
|
|
76
|
+
hr: () => (_jsx("div", { style: { width: '40%', height: 1, background: 'rgba(255, 255, 255, 0.1)' } })),
|
|
77
|
+
table: ({ children }) => (_jsx("div", { style: { fontSize: 'clamp(0.875rem, 1.2vw, 1.125rem)', fontFamily: FONT_SANS, color: '#a1a1aa' }, children: children })),
|
|
78
|
+
thead: ({ children }) => _jsx("div", { children: children }),
|
|
79
|
+
tbody: ({ children }) => _jsx("div", { children: children }),
|
|
80
|
+
tr: ({ children }) => (_jsx("div", { style: { display: 'flex', gap: '1em', borderBottom: '1px solid rgba(255,255,255,0.06)', padding: '0.5em 0' }, children: children })),
|
|
81
|
+
td: ({ children }) => _jsx("div", { style: { flex: 1 }, children: children }),
|
|
82
|
+
th: ({ children }) => (_jsx("div", { style: { flex: 1, fontWeight: 600, color: '#e4e4e7' }, children: children })),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// Built once — the map is static.
|
|
86
|
+
const mdxComponents = buildVideoMdxComponents();
|
|
87
|
+
// Enable function expressions in MDX attribute props. safe-mdx evaluates
|
|
88
|
+
// them with a safe AST interpreter (no eval), so `easing={x => x}` works.
|
|
89
|
+
const evaluateOptions = { functions: true };
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// User modules — initial value from static import, HMR updates via store
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
egakiStore.setState({ modules: initialModules });
|
|
94
|
+
if (import.meta.hot) {
|
|
95
|
+
import.meta.hot.accept('virtual:egaki-modules', (next) => {
|
|
96
|
+
if (next?.eagerModules) {
|
|
97
|
+
egakiStore.setState({ modules: next.eagerModules });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Composition building (parse → sections → JSX)
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Slots travel via React context (provided by MdxClientApp around
|
|
105
|
+
// PlayerPage) rather than a safe-mdx renderNode hook: safe-mdx only calls
|
|
106
|
+
// renderNode in its top-level mdast traversal, while JSX elements nested
|
|
107
|
+
// inside other JSX elements go through jsxTransformer which resolves the
|
|
108
|
+
// components map directly. A real `Server` component in the map works at
|
|
109
|
+
// any nesting depth; it matches its slot via the data-markdown-line prop
|
|
110
|
+
// that safe-mdx injects (line numbers are identical on server and client
|
|
111
|
+
// because blankServerContents preserves line positions).
|
|
112
|
+
function Server(props) {
|
|
113
|
+
const slots = useContext(ServerSlotsContext);
|
|
114
|
+
const key = String(props['data-markdown-line']);
|
|
115
|
+
if (key in slots)
|
|
116
|
+
return slots[key];
|
|
117
|
+
// No slot: <Server> inside an imported .mdx file (not scanned, v1
|
|
118
|
+
// limitation) or a stale flight payload.
|
|
119
|
+
console.warn(`[egaki] <Server> at line ${key} has no server-rendered slot; rendering nothing`);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
let lastGoodComposition = null;
|
|
123
|
+
function makeErrorComposition(error) {
|
|
124
|
+
// If we have a cached good result, reuse its frontmatter and show the
|
|
125
|
+
// error as a single-section overlay so the player stays alive.
|
|
126
|
+
const frontmatter = lastGoodComposition?.frontmatter ?? { fps: 30, bpm: 120, width: 1920, height: 1080, scale: 1 };
|
|
127
|
+
return {
|
|
128
|
+
sections: [{
|
|
129
|
+
heading: 'Parse Error',
|
|
130
|
+
durationInFrames: frontmatter.fps * 5,
|
|
131
|
+
jsx: (_jsxs("div", { style: {
|
|
132
|
+
display: 'flex', flexDirection: 'column', alignItems: 'center',
|
|
133
|
+
justifyContent: 'center', width: '100%', height: '100%',
|
|
134
|
+
padding: '2rem', fontFamily: 'ui-monospace, SFMono-Regular, "Cascadia Code", monospace',
|
|
135
|
+
}, children: [_jsx("div", { style: { color: '#ef4444', fontSize: 'clamp(1rem, 2vw, 1.5rem)', fontWeight: 600, marginBottom: '1rem' }, children: "MDX Parse Error" }), _jsx("div", { style: {
|
|
136
|
+
color: '#fca5a5', fontSize: 'clamp(0.75rem, 1.5vw, 1rem)',
|
|
137
|
+
background: 'rgba(239, 68, 68, 0.1)', borderRadius: '0.5rem',
|
|
138
|
+
padding: '1rem 1.5rem', maxWidth: '80%', whiteSpace: 'pre-wrap',
|
|
139
|
+
lineHeight: 1.6, border: '1px solid rgba(239, 68, 68, 0.2)',
|
|
140
|
+
}, children: error.message }), _jsx("div", { style: { color: '#71717a', fontSize: 'clamp(0.625rem, 1vw, 0.875rem)', marginTop: '1rem' }, children: "Fix the syntax error and save. The video will recover automatically." })] })),
|
|
141
|
+
}],
|
|
142
|
+
preamble: lastGoodComposition?.preamble,
|
|
143
|
+
frontmatter,
|
|
144
|
+
parseError: error.message,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function buildComposition(mdxSource, modules) {
|
|
148
|
+
let ast;
|
|
149
|
+
try {
|
|
150
|
+
ast = mdxParse(mdxSource);
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
console.warn('[egaki] MDX parse error:', e.message);
|
|
154
|
+
return makeErrorComposition(e);
|
|
155
|
+
}
|
|
156
|
+
// Compute FPS and BEAT from frontmatter early so they're available as
|
|
157
|
+
// scope variables for all SafeMdxRenderer calls (including imported MDX).
|
|
158
|
+
const { fps, bpm } = parseFrontmatter(ast);
|
|
159
|
+
const mdxScope = buildMdxScope(fps, bpm);
|
|
160
|
+
// Render imported .mdx/.md files into React components so safe-mdx can
|
|
161
|
+
// resolve `import Intro from './intro.mdx'` and render `<Intro />` via
|
|
162
|
+
// React composition. Each imported MDX gets its own SafeMdxRenderer pass
|
|
163
|
+
// with the same components map.
|
|
164
|
+
const moduleKeys = Object.keys(modules);
|
|
165
|
+
const mergedModules = { ...modules };
|
|
166
|
+
const imports = extractImports(ast);
|
|
167
|
+
for (const imp of imports) {
|
|
168
|
+
if (!/\.mdx?$/.test(imp.source))
|
|
169
|
+
continue;
|
|
170
|
+
const key = resolveModulePath(imp.source, './', moduleKeys);
|
|
171
|
+
if (!key || !mergedModules[key])
|
|
172
|
+
continue;
|
|
173
|
+
const rawContent = mergedModules[key].default;
|
|
174
|
+
if (typeof rawContent !== 'string')
|
|
175
|
+
continue;
|
|
176
|
+
let importedAst;
|
|
177
|
+
try {
|
|
178
|
+
importedAst = mdxParse(rawContent);
|
|
179
|
+
}
|
|
180
|
+
catch (e) {
|
|
181
|
+
// Imported MDX has a syntax error. Skip it instead of crashing the
|
|
182
|
+
// whole composition; the main file's content still renders.
|
|
183
|
+
console.warn(`[egaki] parse error in imported MDX (${imp.source}):`, e.message);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const renderedJsx = (_jsx(SafeMdxRenderer, { markdown: rawContent, mdast: importedAst, components: mdxComponents, modules: mergedModules, baseUrl: "./", scope: mdxScope, evaluateOptions: evaluateOptions, onError: (e) => console.warn('[egaki] imported MDX:', e.message) }));
|
|
187
|
+
// Replace the raw string module with a component that returns the
|
|
188
|
+
// pre-rendered JSX. safe-mdx reads mod.default for default imports.
|
|
189
|
+
mergedModules[key] = { default: () => renderedJsx };
|
|
190
|
+
}
|
|
191
|
+
const result = splitIntoSections(ast);
|
|
192
|
+
// Extract import nodes (mdxjsEsm) from the full mdast. Section splitting
|
|
193
|
+
// drops them, but SafeMdxRenderer needs them to resolve imported components
|
|
194
|
+
// from the modules map. Prepend to every section's nodes.
|
|
195
|
+
//
|
|
196
|
+
// Imports not resolvable in the client modules map are stripped: those
|
|
197
|
+
// are server-only files (used exclusively inside <Server> blocks, which
|
|
198
|
+
// the server already rendered into slots) excluded from the client map
|
|
199
|
+
// by the vite plugin's inference.
|
|
200
|
+
const importNodes = filterImportNodesToModules(ast.children.filter((node) => node.type === 'mdxjsEsm'), Object.keys(mergedModules));
|
|
201
|
+
const renderNodes = (nodes) => (_jsx(SafeMdxRenderer, { markdown: mdxSource, mdast: { type: 'root', children: [...importNodes, ...nodes] }, components: mdxComponents, modules: mergedModules, baseUrl: "./", scope: mdxScope, addMarkdownLineNumbers: true, evaluateOptions: evaluateOptions, onError: (e) => console.warn('[egaki] MDX:', e.message) }));
|
|
202
|
+
const sections = result.sections.map((section) => ({
|
|
203
|
+
heading: section.heading,
|
|
204
|
+
durationInFrames: section.durationInFrames,
|
|
205
|
+
jsx: renderNodes(section.nodes),
|
|
206
|
+
}));
|
|
207
|
+
// Preamble: content before the first heading, rendered at composition
|
|
208
|
+
// level (outside Series) so it spans the full video duration.
|
|
209
|
+
const preamble = result.preamble.length > 0
|
|
210
|
+
? renderNodes(result.preamble)
|
|
211
|
+
: undefined;
|
|
212
|
+
const composition = { sections, preamble, frontmatter: result.frontmatter };
|
|
213
|
+
// Cache successful result for parse error recovery
|
|
214
|
+
lastGoodComposition = composition;
|
|
215
|
+
return composition;
|
|
216
|
+
}
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// App component
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
const EMPTY_SLOTS = {};
|
|
221
|
+
export function MdxClientApp({ mdx, serverSlots = EMPTY_SLOTS, entryPath, availableEntries = [], currentRoute = '', }) {
|
|
222
|
+
const modules = useModules();
|
|
223
|
+
// Subscribe to media duration reports. When Audio/Video components report
|
|
224
|
+
// their durations (via mediabunny metadata fetch), this re-renders and
|
|
225
|
+
// sections without explicit duration= get auto-sized to their media content.
|
|
226
|
+
// The map is keyed by section index (as string) → max media duration in
|
|
227
|
+
// seconds, derived from the src-keyed persistent cache.
|
|
228
|
+
const sectionDurations = useMediaDurations();
|
|
229
|
+
// Split into two memos to avoid an infinite loop:
|
|
230
|
+
// 1. buildComposition creates JSX (depends on mdx + modules only).
|
|
231
|
+
// If this re-ran on sectionDurations changes, it would create new JSX
|
|
232
|
+
// identity → React unmounts Audio/Video → clearSectionDuration fires →
|
|
233
|
+
// snapshot changes → sectionDurations changes → infinite loop.
|
|
234
|
+
// 2. resolveAutoDurations fills in null durations from the section store.
|
|
235
|
+
// This memo is cheap and can re-run on every sectionDurations change
|
|
236
|
+
// without touching JSX identity.
|
|
237
|
+
const composed = useMemo(() => buildComposition(mdx, modules), [mdx, modules]);
|
|
238
|
+
// Reset section duration reports when the composition changes (HMR,
|
|
239
|
+
// MDX edit, module update). Media components will re-report on mount.
|
|
240
|
+
// Does NOT clear the raw src cache so cached durations resolve instantly.
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
resetSectionDurations();
|
|
243
|
+
}, [composed]);
|
|
244
|
+
const { sections, totalDuration, preamble, hasUnresolvedDurations } = useMemo(() => {
|
|
245
|
+
const { fps, bpm } = composed.frontmatter;
|
|
246
|
+
// MDX files without headings (e.g. imported partials rendered standalone)
|
|
247
|
+
// have 0 sections. Synthesize one from the preamble with null duration
|
|
248
|
+
// BEFORE resolving auto-durations, so media auto-sizing still works.
|
|
249
|
+
let sourceSections = composed.sections;
|
|
250
|
+
let finalPreamble = composed.preamble;
|
|
251
|
+
if (sourceSections.length === 0 && finalPreamble) {
|
|
252
|
+
sourceSections = [{ heading: null, durationInFrames: null, jsx: finalPreamble }];
|
|
253
|
+
finalPreamble = undefined;
|
|
254
|
+
}
|
|
255
|
+
const resolved = resolveAutoDurations(sourceSections, fps, bpm, sectionDurations);
|
|
256
|
+
const hasUnresolved = sourceSections.some((s, i) => s.durationInFrames === null && sectionDurations[String(i)] === undefined);
|
|
257
|
+
return {
|
|
258
|
+
sections: resolved,
|
|
259
|
+
totalDuration: Math.max(1, calculateTotalDuration(resolved)),
|
|
260
|
+
preamble: finalPreamble,
|
|
261
|
+
hasUnresolvedDurations: hasUnresolved,
|
|
262
|
+
};
|
|
263
|
+
}, [composed, sectionDurations]);
|
|
264
|
+
return (_jsx(ServerSlotsContext.Provider, { value: serverSlots, children: _jsx(PlayerPage, { sections: sections, totalDuration: totalDuration, preamble: preamble, entryPath: entryPath, hasUnresolvedDurations: hasUnresolvedDurations, frontmatter: composed.frontmatter, availableEntries: availableEntries, currentRoute: currentRoute }) }));
|
|
265
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-safe MDX parsing utilities for the video framework.
|
|
3
|
+
*
|
|
4
|
+
* This module contains ONLY parsing logic (frontmatter, section splitting,
|
|
5
|
+
* duration calculation). It does NOT import remotion or any client-only
|
|
6
|
+
* modules, so it can safely run in the RSC server environment.
|
|
7
|
+
*
|
|
8
|
+
* The rendering counterpart is mdx-video.tsx which re-exports these
|
|
9
|
+
* functions alongside the Remotion components.
|
|
10
|
+
*/
|
|
11
|
+
type RootContent = any;
|
|
12
|
+
type Root = {
|
|
13
|
+
type: 'root';
|
|
14
|
+
children: RootContent[];
|
|
15
|
+
};
|
|
16
|
+
export interface VideoFrontmatter {
|
|
17
|
+
fps: number;
|
|
18
|
+
bpm: number;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
/** Pixel density / scale multiplier for rendering. Default 1.
|
|
22
|
+
* Setting scale=2 renders at 2x resolution (e.g. 3840×2160 for a 1920×1080 comp). */
|
|
23
|
+
scale: number;
|
|
24
|
+
}
|
|
25
|
+
/** Scope variables injected into MDX expressions via safe-mdx's `scope` prop.
|
|
26
|
+
* Both server (app.tsx) and client (mdx-client.tsx) must pass the same shape. */
|
|
27
|
+
export interface MdxScope {
|
|
28
|
+
FPS: number;
|
|
29
|
+
BEAT: number;
|
|
30
|
+
Math: typeof Math;
|
|
31
|
+
Number: typeof Number;
|
|
32
|
+
parseInt: typeof parseInt;
|
|
33
|
+
parseFloat: typeof parseFloat;
|
|
34
|
+
Infinity: typeof Infinity;
|
|
35
|
+
NaN: typeof NaN;
|
|
36
|
+
Boolean: typeof Boolean;
|
|
37
|
+
String: typeof String;
|
|
38
|
+
Array: typeof Array;
|
|
39
|
+
Object: typeof Object;
|
|
40
|
+
JSON: typeof JSON;
|
|
41
|
+
}
|
|
42
|
+
/** Build the MDX scope from frontmatter values.
|
|
43
|
+
* Includes JS globals so MDX expressions can use Math.round(), parseInt(), etc. */
|
|
44
|
+
export declare function buildMdxScope(fps: number, bpm: number): MdxScope;
|
|
45
|
+
/** Parse YAML frontmatter from mdast. Extracts fps, bpm, width, height, scale. */
|
|
46
|
+
export declare function parseFrontmatter(mdast: Root): VideoFrontmatter;
|
|
47
|
+
/** Compute the closest aspect ratio string from pixel dimensions.
|
|
48
|
+
* When `allowedRatios` is provided (e.g. from a model's supported list),
|
|
49
|
+
* picks the best match from that list. Otherwise uses STANDARD_RATIOS.
|
|
50
|
+
* Reduces to the exact ratio first (e.g. 1920×1080 → 16:9). If the exact
|
|
51
|
+
* ratio doesn't match, picks the closest by comparing decimal values.
|
|
52
|
+
* Returns '16:9' for invalid inputs. */
|
|
53
|
+
export declare function aspectRatioFromDimensions(width: number, height: number, allowedRatios?: string[]): string;
|
|
54
|
+
export interface MdxSection {
|
|
55
|
+
heading: string | null;
|
|
56
|
+
nodes: RootContent[];
|
|
57
|
+
/** Duration in frames. `null` means "auto-infer from media content". */
|
|
58
|
+
durationInFrames: number | null;
|
|
59
|
+
}
|
|
60
|
+
export interface SplitResult {
|
|
61
|
+
sections: MdxSection[];
|
|
62
|
+
frontmatter: VideoFrontmatter;
|
|
63
|
+
/** ESM import nodes from the document, needed by SafeMdxRenderer to resolve modules */
|
|
64
|
+
imports: RootContent[];
|
|
65
|
+
/** Content nodes before the first heading. Rendered at composition level,
|
|
66
|
+
* outside the Series, spanning the full video duration. Use for soundtracks,
|
|
67
|
+
* ambient background videos, or any component that should persist across
|
|
68
|
+
* all sections. */
|
|
69
|
+
preamble: RootContent[];
|
|
70
|
+
}
|
|
71
|
+
export declare function splitIntoSections(mdast: Root): SplitResult;
|
|
72
|
+
/** Calculate total composition duration.
|
|
73
|
+
* All sections must have resolved (non-null) durations. */
|
|
74
|
+
export declare function calculateTotalDuration(sections: {
|
|
75
|
+
durationInFrames: number;
|
|
76
|
+
}[]): number;
|
|
77
|
+
/** MdxSection with a guaranteed non-null durationInFrames. */
|
|
78
|
+
export type ResolvedMdxSection = MdxSection & {
|
|
79
|
+
durationInFrames: number;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Resolve null (auto) durations using per-section media durations.
|
|
83
|
+
*
|
|
84
|
+
* `sectionDurations` is keyed by section index (as string) → max media
|
|
85
|
+
* duration in seconds. Audio/Video components populate this at runtime
|
|
86
|
+
* via the media duration store after fetching metadata with mediabunny.
|
|
87
|
+
*
|
|
88
|
+
* For each section with `durationInFrames === null`:
|
|
89
|
+
* 1. Look up the section's index in `sectionDurations`.
|
|
90
|
+
* 2. If found, set duration = Math.round(seconds * fps).
|
|
91
|
+
* 3. Otherwise fall back to DEFAULT_SECTION_BEATS * framesPerBeat.
|
|
92
|
+
*
|
|
93
|
+
* Sections with explicit durations are returned unchanged.
|
|
94
|
+
* Generic so extra fields (like `jsx`) are preserved in the return type.
|
|
95
|
+
*/
|
|
96
|
+
export declare function resolveAutoDurations<T extends {
|
|
97
|
+
durationInFrames: number | null;
|
|
98
|
+
}>(sections: T[], fps: number, bpm: number, sectionDurations?: Record<string, number>): (T & {
|
|
99
|
+
durationInFrames: number;
|
|
100
|
+
})[];
|
|
101
|
+
export {};
|
|
102
|
+
//# sourceMappingURL=mdx-parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mdx-parse.d.ts","sourceRoot":"","sources":["../../src/vite/mdx-parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,KAAK,WAAW,GAAG,GAAG,CAAA;AACtB,KAAK,IAAI,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,WAAW,EAAE,CAAA;CAAE,CAAA;AAiBrD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd;0FACsF;IACtF,KAAK,EAAE,MAAM,CAAA;CACd;AAED;kFACkF;AAClF,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IAEZ,IAAI,EAAE,OAAO,IAAI,CAAA;IACjB,MAAM,EAAE,OAAO,MAAM,CAAA;IACrB,QAAQ,EAAE,OAAO,QAAQ,CAAA;IACzB,UAAU,EAAE,OAAO,UAAU,CAAA;IAC7B,QAAQ,EAAE,OAAO,QAAQ,CAAA;IACzB,GAAG,EAAE,OAAO,GAAG,CAAA;IACf,OAAO,EAAE,OAAO,OAAO,CAAA;IACvB,MAAM,EAAE,OAAO,MAAM,CAAA;IACrB,KAAK,EAAE,OAAO,KAAK,CAAA;IACnB,MAAM,EAAE,OAAO,MAAM,CAAA;IACrB,IAAI,EAAE,OAAO,IAAI,CAAA;CAClB;AAED;oFACoF;AACpF,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAgBhE;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,IAAI,GAAG,gBAAgB,CA4B9D;AAqBD;;;;;yCAKyC;AACzC,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CA4BzG;AAiDD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,wEAAwE;IACxE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,UAAU,EAAE,CAAA;IACtB,WAAW,EAAE,gBAAgB,CAAA;IAC7B,uFAAuF;IACvF,OAAO,EAAE,WAAW,EAAE,CAAA;IACtB;;;wBAGoB;IACpB,QAAQ,EAAE,WAAW,EAAE,CAAA;CACxB;AAWD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,GAAG,WAAW,CA6C1D;AAED;4DAC4D;AAC5D,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE;IAAE,gBAAgB,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,MAAM,CAMvF;AAMD,8DAA8D;AAC9D,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG;IAAE,gBAAgB,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1E;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS;IAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAChF,QAAQ,EAAE,CAAC,EAAE,EACb,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,gBAAgB,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAC5C,CAAC,CAAC,GAAG;IAAE,gBAAgB,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,CAgBtC"}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-safe MDX parsing utilities for the video framework.
|
|
3
|
+
*
|
|
4
|
+
* This module contains ONLY parsing logic (frontmatter, section splitting,
|
|
5
|
+
* duration calculation). It does NOT import remotion or any client-only
|
|
6
|
+
* modules, so it can safely run in the RSC server environment.
|
|
7
|
+
*
|
|
8
|
+
* The rendering counterpart is mdx-video.tsx which re-exports these
|
|
9
|
+
* functions alongside the Remotion components.
|
|
10
|
+
*/
|
|
11
|
+
import YAML from 'yaml';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Constants
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const DEFAULT_FPS = 30;
|
|
16
|
+
const DEFAULT_BPM = 120;
|
|
17
|
+
const DEFAULT_SECTION_BEATS = 10;
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Frontmatter parsing
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
const DEFAULT_WIDTH = 1920;
|
|
22
|
+
const DEFAULT_HEIGHT = 1080;
|
|
23
|
+
/** Build the MDX scope from frontmatter values.
|
|
24
|
+
* Includes JS globals so MDX expressions can use Math.round(), parseInt(), etc. */
|
|
25
|
+
export function buildMdxScope(fps, bpm) {
|
|
26
|
+
return {
|
|
27
|
+
FPS: fps,
|
|
28
|
+
BEAT: fps / (bpm / 60),
|
|
29
|
+
Math,
|
|
30
|
+
Number,
|
|
31
|
+
parseInt,
|
|
32
|
+
parseFloat,
|
|
33
|
+
Infinity,
|
|
34
|
+
NaN,
|
|
35
|
+
Boolean,
|
|
36
|
+
String,
|
|
37
|
+
Array,
|
|
38
|
+
Object,
|
|
39
|
+
JSON,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Parse YAML frontmatter from mdast. Extracts fps, bpm, width, height, scale. */
|
|
43
|
+
export function parseFrontmatter(mdast) {
|
|
44
|
+
const result = {
|
|
45
|
+
fps: DEFAULT_FPS,
|
|
46
|
+
bpm: DEFAULT_BPM,
|
|
47
|
+
width: DEFAULT_WIDTH,
|
|
48
|
+
height: DEFAULT_HEIGHT,
|
|
49
|
+
scale: 1,
|
|
50
|
+
};
|
|
51
|
+
for (const node of mdast.children) {
|
|
52
|
+
if (node.type !== 'yaml')
|
|
53
|
+
continue;
|
|
54
|
+
const text = node.value;
|
|
55
|
+
if (!text)
|
|
56
|
+
continue;
|
|
57
|
+
const parsed = YAML.parse(text);
|
|
58
|
+
if (parsed && typeof parsed === 'object') {
|
|
59
|
+
if (typeof parsed.fps === 'number' && parsed.fps > 0)
|
|
60
|
+
result.fps = parsed.fps;
|
|
61
|
+
if (typeof parsed.bpm === 'number' && parsed.bpm > 0)
|
|
62
|
+
result.bpm = parsed.bpm;
|
|
63
|
+
if (typeof parsed.width === 'number') {
|
|
64
|
+
const w = Math.round(parsed.width);
|
|
65
|
+
if (w > 0)
|
|
66
|
+
result.width = w;
|
|
67
|
+
}
|
|
68
|
+
if (typeof parsed.height === 'number') {
|
|
69
|
+
const h = Math.round(parsed.height);
|
|
70
|
+
if (h > 0)
|
|
71
|
+
result.height = h;
|
|
72
|
+
}
|
|
73
|
+
if (typeof parsed.scale === 'number' && parsed.scale > 0)
|
|
74
|
+
result.scale = parsed.scale;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Aspect ratio from composition dimensions
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
const STANDARD_RATIOS = [
|
|
83
|
+
[1, 1], [3, 4], [4, 3], [9, 16], [16, 9],
|
|
84
|
+
[2, 3], [3, 2], [4, 5], [5, 4], [9, 21], [21, 9],
|
|
85
|
+
];
|
|
86
|
+
function gcd(a, b) {
|
|
87
|
+
while (b) {
|
|
88
|
+
[a, b] = [b, a % b];
|
|
89
|
+
}
|
|
90
|
+
return a;
|
|
91
|
+
}
|
|
92
|
+
function parseRatio(s) {
|
|
93
|
+
const [a, b] = s.split(':').map(Number);
|
|
94
|
+
if (a && b && a > 0 && b > 0)
|
|
95
|
+
return [a, b];
|
|
96
|
+
}
|
|
97
|
+
/** Compute the closest aspect ratio string from pixel dimensions.
|
|
98
|
+
* When `allowedRatios` is provided (e.g. from a model's supported list),
|
|
99
|
+
* picks the best match from that list. Otherwise uses STANDARD_RATIOS.
|
|
100
|
+
* Reduces to the exact ratio first (e.g. 1920×1080 → 16:9). If the exact
|
|
101
|
+
* ratio doesn't match, picks the closest by comparing decimal values.
|
|
102
|
+
* Returns '16:9' for invalid inputs. */
|
|
103
|
+
export function aspectRatioFromDimensions(width, height, allowedRatios) {
|
|
104
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
105
|
+
return '16:9';
|
|
106
|
+
}
|
|
107
|
+
const candidates = allowedRatios?.length
|
|
108
|
+
? allowedRatios.map(parseRatio).filter((r) => r !== undefined)
|
|
109
|
+
: STANDARD_RATIOS;
|
|
110
|
+
if (candidates.length === 0)
|
|
111
|
+
return '16:9';
|
|
112
|
+
const d = gcd(width, height);
|
|
113
|
+
const rw = width / d;
|
|
114
|
+
const rh = height / d;
|
|
115
|
+
// Check exact match first
|
|
116
|
+
if (candidates.some(([w, h]) => w === rw && h === rh)) {
|
|
117
|
+
return `${rw}:${rh}`;
|
|
118
|
+
}
|
|
119
|
+
// Find closest by decimal value
|
|
120
|
+
const target = width / height;
|
|
121
|
+
let best = candidates[0];
|
|
122
|
+
let bestDiff = Infinity;
|
|
123
|
+
for (const ratio of candidates) {
|
|
124
|
+
const diff = Math.abs(ratio[0] / ratio[1] - target);
|
|
125
|
+
if (diff < bestDiff) {
|
|
126
|
+
bestDiff = diff;
|
|
127
|
+
best = ratio;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return `${best[0]}:${best[1]}`;
|
|
131
|
+
}
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Duration parsing from heading text
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
const HEADING_PROP_RE = /\s+(duration)=(\d+(?:\.\d+)?)(s|fps|frames?|f|beats?)?/gi;
|
|
136
|
+
function parseHeadingProps(rawText, fps, bpm) {
|
|
137
|
+
let label = rawText;
|
|
138
|
+
let durationInFrames = null;
|
|
139
|
+
const framesPerBeat = fps / (bpm / 60);
|
|
140
|
+
// Strip all key=value props from the heading text
|
|
141
|
+
label = label.replace(HEADING_PROP_RE, (_match, key, value, unit) => {
|
|
142
|
+
const v = Number(value);
|
|
143
|
+
const u = (unit || '').toLowerCase();
|
|
144
|
+
let frames;
|
|
145
|
+
if (u === 's') {
|
|
146
|
+
frames = Math.round(v * fps);
|
|
147
|
+
}
|
|
148
|
+
else if (u === 'beat' || u === 'beats') {
|
|
149
|
+
frames = Math.round(v * framesPerBeat);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// bare number, fps, f, frame, frames — all mean raw frames
|
|
153
|
+
frames = Math.round(v);
|
|
154
|
+
}
|
|
155
|
+
if (key.toLowerCase() === 'duration') {
|
|
156
|
+
durationInFrames = frames;
|
|
157
|
+
}
|
|
158
|
+
return '';
|
|
159
|
+
}).trim();
|
|
160
|
+
return { label: label || 'Untitled', durationInFrames };
|
|
161
|
+
}
|
|
162
|
+
function extractHeadingText(node) {
|
|
163
|
+
if (node.type !== 'heading')
|
|
164
|
+
return '';
|
|
165
|
+
const parts = [];
|
|
166
|
+
for (const child of node.children || []) {
|
|
167
|
+
if (child.type === 'text')
|
|
168
|
+
parts.push(child.value);
|
|
169
|
+
}
|
|
170
|
+
return parts.join('') || 'Untitled';
|
|
171
|
+
}
|
|
172
|
+
export function splitIntoSections(mdast) {
|
|
173
|
+
const frontmatter = parseFrontmatter(mdast);
|
|
174
|
+
const { fps, bpm } = frontmatter;
|
|
175
|
+
const framesPerBeat = fps / (bpm / 60);
|
|
176
|
+
const defaultDuration = Math.round(DEFAULT_SECTION_BEATS * framesPerBeat);
|
|
177
|
+
const sections = [];
|
|
178
|
+
const imports = [];
|
|
179
|
+
const preamble = [];
|
|
180
|
+
let current = null;
|
|
181
|
+
let beforeFirstHeading = true;
|
|
182
|
+
for (const node of mdast.children) {
|
|
183
|
+
if (node.type === 'yaml' || node.type === 'toml') {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (node.type === 'mdxjsEsm') {
|
|
187
|
+
imports.push(node);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (node.type === 'heading') {
|
|
191
|
+
beforeFirstHeading = false;
|
|
192
|
+
const rawText = extractHeadingText(node);
|
|
193
|
+
const parsed = parseHeadingProps(rawText, fps, bpm);
|
|
194
|
+
current = {
|
|
195
|
+
heading: parsed.label,
|
|
196
|
+
nodes: [],
|
|
197
|
+
durationInFrames: parsed.durationInFrames ?? null,
|
|
198
|
+
};
|
|
199
|
+
sections.push(current);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
// Content before the first heading goes into the preamble, which is
|
|
203
|
+
// rendered at composition level (outside Series) so it spans the
|
|
204
|
+
// full video duration.
|
|
205
|
+
if (beforeFirstHeading) {
|
|
206
|
+
preamble.push(node);
|
|
207
|
+
}
|
|
208
|
+
else if (current) {
|
|
209
|
+
current.nodes.push(node);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return { sections, frontmatter, imports, preamble };
|
|
213
|
+
}
|
|
214
|
+
/** Calculate total composition duration.
|
|
215
|
+
* All sections must have resolved (non-null) durations. */
|
|
216
|
+
export function calculateTotalDuration(sections) {
|
|
217
|
+
let total = 0;
|
|
218
|
+
for (const s of sections) {
|
|
219
|
+
total += s.durationInFrames;
|
|
220
|
+
}
|
|
221
|
+
return total;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Resolve null (auto) durations using per-section media durations.
|
|
225
|
+
*
|
|
226
|
+
* `sectionDurations` is keyed by section index (as string) → max media
|
|
227
|
+
* duration in seconds. Audio/Video components populate this at runtime
|
|
228
|
+
* via the media duration store after fetching metadata with mediabunny.
|
|
229
|
+
*
|
|
230
|
+
* For each section with `durationInFrames === null`:
|
|
231
|
+
* 1. Look up the section's index in `sectionDurations`.
|
|
232
|
+
* 2. If found, set duration = Math.round(seconds * fps).
|
|
233
|
+
* 3. Otherwise fall back to DEFAULT_SECTION_BEATS * framesPerBeat.
|
|
234
|
+
*
|
|
235
|
+
* Sections with explicit durations are returned unchanged.
|
|
236
|
+
* Generic so extra fields (like `jsx`) are preserved in the return type.
|
|
237
|
+
*/
|
|
238
|
+
export function resolveAutoDurations(sections, fps, bpm, sectionDurations = {}) {
|
|
239
|
+
const framesPerBeat = fps / (bpm / 60);
|
|
240
|
+
const defaultDuration = Math.round(DEFAULT_SECTION_BEATS * framesPerBeat);
|
|
241
|
+
return sections.map((section, i) => {
|
|
242
|
+
if (section.durationInFrames !== null) {
|
|
243
|
+
return section;
|
|
244
|
+
}
|
|
245
|
+
const seconds = sectionDurations[String(i)];
|
|
246
|
+
const durationInFrames = seconds !== undefined && seconds > 0
|
|
247
|
+
? Math.round(seconds * fps)
|
|
248
|
+
: defaultDuration;
|
|
249
|
+
return { ...section, durationInFrames };
|
|
250
|
+
});
|
|
251
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global MDXProvidedComponents type for MDX LSP support.
|
|
3
|
+
*
|
|
4
|
+
* The MDX analyzer (mdx-analyzer / vscode-mdx) uses the special global type
|
|
5
|
+
* `MDXProvidedComponents` to know which components are available in MDX files
|
|
6
|
+
* without explicit imports. This file declares that type from the same
|
|
7
|
+
* MDX_BUILTIN_COMPONENTS map that the runtime uses, keeping them in sync.
|
|
8
|
+
*
|
|
9
|
+
* Projects opt in by having an `egaki-env.d.ts` with:
|
|
10
|
+
* /// <reference types="egaki/mdx-components" />
|
|
11
|
+
*
|
|
12
|
+
* The egaki vite plugin auto-generates this file on first run.
|
|
13
|
+
*/
|
|
14
|
+
import { MDX_BUILTIN_COMPONENTS } from './mdx-video.tsx';
|
|
15
|
+
declare global {
|
|
16
|
+
type MDXProvidedComponents = typeof MDX_BUILTIN_COMPONENTS;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=mdx-provided-components.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mdx-provided-components.d.ts","sourceRoot":"","sources":["../../src/vite/mdx-provided-components.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAExD,OAAO,CAAC,MAAM,CAAC;IACb,KAAK,qBAAqB,GAAG,OAAO,sBAAsB,CAAA;CAC3D"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global MDXProvidedComponents type for MDX LSP support.
|
|
3
|
+
*
|
|
4
|
+
* The MDX analyzer (mdx-analyzer / vscode-mdx) uses the special global type
|
|
5
|
+
* `MDXProvidedComponents` to know which components are available in MDX files
|
|
6
|
+
* without explicit imports. This file declares that type from the same
|
|
7
|
+
* MDX_BUILTIN_COMPONENTS map that the runtime uses, keeping them in sync.
|
|
8
|
+
*
|
|
9
|
+
* Projects opt in by having an `egaki-env.d.ts` with:
|
|
10
|
+
* /// <reference types="egaki/mdx-components" />
|
|
11
|
+
*
|
|
12
|
+
* The egaki vite plugin auto-generates this file on first run.
|
|
13
|
+
*/
|
|
14
|
+
import { MDX_BUILTIN_COMPONENTS } from "./mdx-video.js";
|