varg.ai-sdk 0.1.0 → 0.4.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/.claude/settings.local.json +1 -1
  2. package/.env.example +3 -0
  3. package/.github/workflows/ci.yml +23 -0
  4. package/.husky/README.md +102 -0
  5. package/.husky/commit-msg +6 -0
  6. package/.husky/pre-commit +9 -0
  7. package/.husky/pre-push +6 -0
  8. package/.size-limit.json +8 -0
  9. package/.test-hooks.ts +5 -0
  10. package/CLAUDE.md +10 -3
  11. package/CONTRIBUTING.md +150 -0
  12. package/LICENSE.md +53 -0
  13. package/README.md +56 -209
  14. package/SKILLS.md +26 -10
  15. package/biome.json +7 -1
  16. package/bun.lock +1286 -0
  17. package/commitlint.config.js +22 -0
  18. package/docs/index.html +1130 -0
  19. package/docs/prompting.md +326 -0
  20. package/docs/react.md +834 -0
  21. package/docs/sdk.md +812 -0
  22. package/ffmpeg/CLAUDE.md +68 -0
  23. package/package.json +48 -8
  24. package/pipeline/cookbooks/scripts/animate-frames-parallel.ts +84 -0
  25. package/pipeline/cookbooks/scripts/combine-scenes.sh +53 -0
  26. package/pipeline/cookbooks/scripts/generate-frames-parallel.ts +99 -0
  27. package/pipeline/cookbooks/scripts/still-to-video.sh +37 -0
  28. package/pipeline/cookbooks/text-to-tiktok.md +669 -0
  29. package/pipeline/cookbooks/trendwatching.md +156 -0
  30. package/plan.md +281 -0
  31. package/scripts/.gitkeep +0 -0
  32. package/src/ai-sdk/cache.ts +142 -0
  33. package/src/ai-sdk/examples/cached-generation.ts +53 -0
  34. package/src/ai-sdk/examples/duet-scene-4.ts +53 -0
  35. package/src/ai-sdk/examples/duet-scene-5-audio.ts +32 -0
  36. package/src/ai-sdk/examples/duet-video.ts +56 -0
  37. package/src/ai-sdk/examples/editly-composition.ts +63 -0
  38. package/src/ai-sdk/examples/editly-test.ts +57 -0
  39. package/src/ai-sdk/examples/editly-video-test.ts +52 -0
  40. package/src/ai-sdk/examples/fal-lipsync.ts +43 -0
  41. package/src/ai-sdk/examples/higgsfield-image.ts +61 -0
  42. package/src/ai-sdk/examples/music-generation.ts +19 -0
  43. package/src/ai-sdk/examples/openai-sora.ts +34 -0
  44. package/src/ai-sdk/examples/replicate-bg-removal.ts +52 -0
  45. package/src/ai-sdk/examples/simpsons-scene.ts +61 -0
  46. package/src/ai-sdk/examples/talking-lion.ts +55 -0
  47. package/src/ai-sdk/examples/video-generation.ts +39 -0
  48. package/src/ai-sdk/examples/workflow-animated-girl.ts +104 -0
  49. package/src/ai-sdk/examples/workflow-before-after.ts +114 -0
  50. package/src/ai-sdk/examples/workflow-character-grid.ts +112 -0
  51. package/src/ai-sdk/examples/workflow-slideshow.ts +161 -0
  52. package/src/ai-sdk/file-cache.ts +112 -0
  53. package/src/ai-sdk/file.ts +238 -0
  54. package/src/ai-sdk/generate-element.ts +92 -0
  55. package/src/ai-sdk/generate-music.ts +46 -0
  56. package/src/ai-sdk/generate-video.ts +165 -0
  57. package/src/ai-sdk/index.ts +72 -0
  58. package/src/ai-sdk/music-model.ts +110 -0
  59. package/src/ai-sdk/providers/editly/editly.test.ts +1108 -0
  60. package/src/ai-sdk/providers/editly/ffmpeg.ts +60 -0
  61. package/src/ai-sdk/providers/editly/index.ts +817 -0
  62. package/src/ai-sdk/providers/editly/layers.ts +776 -0
  63. package/src/ai-sdk/providers/editly/plan.md +144 -0
  64. package/src/ai-sdk/providers/editly/types.ts +328 -0
  65. package/src/ai-sdk/providers/elevenlabs-provider.ts +255 -0
  66. package/src/ai-sdk/providers/fal-provider.ts +512 -0
  67. package/src/ai-sdk/providers/higgsfield.ts +379 -0
  68. package/src/ai-sdk/providers/openai.ts +251 -0
  69. package/src/ai-sdk/providers/replicate.ts +16 -0
  70. package/src/ai-sdk/video-model.ts +185 -0
  71. package/src/cli/commands/find.tsx +137 -0
  72. package/src/cli/commands/help.tsx +85 -0
  73. package/src/cli/commands/index.ts +6 -0
  74. package/src/cli/commands/list.tsx +238 -0
  75. package/src/cli/commands/render.tsx +71 -0
  76. package/src/cli/commands/run.tsx +511 -0
  77. package/src/cli/commands/which.tsx +253 -0
  78. package/src/cli/index.ts +114 -0
  79. package/src/cli/quiet.ts +44 -0
  80. package/src/cli/types.ts +32 -0
  81. package/src/cli/ui/components/Badge.tsx +29 -0
  82. package/src/cli/ui/components/DataTable.tsx +51 -0
  83. package/src/cli/ui/components/Header.tsx +23 -0
  84. package/src/cli/ui/components/HelpBlock.tsx +44 -0
  85. package/src/cli/ui/components/KeyValue.tsx +33 -0
  86. package/src/cli/ui/components/OptionRow.tsx +81 -0
  87. package/src/cli/ui/components/Separator.tsx +23 -0
  88. package/src/cli/ui/components/StatusBox.tsx +108 -0
  89. package/src/cli/ui/components/VargBox.tsx +51 -0
  90. package/src/cli/ui/components/VargProgress.tsx +36 -0
  91. package/src/cli/ui/components/VargSpinner.tsx +34 -0
  92. package/src/cli/ui/components/VargText.tsx +56 -0
  93. package/src/cli/ui/components/index.ts +19 -0
  94. package/src/cli/ui/index.ts +12 -0
  95. package/src/cli/ui/render.ts +35 -0
  96. package/src/cli/ui/theme.ts +63 -0
  97. package/src/cli/utils.ts +78 -0
  98. package/src/core/executor/executor.ts +201 -0
  99. package/src/core/executor/index.ts +13 -0
  100. package/src/core/executor/job.ts +214 -0
  101. package/src/core/executor/pipeline.ts +222 -0
  102. package/src/core/index.ts +11 -0
  103. package/src/core/registry/index.ts +9 -0
  104. package/src/core/registry/loader.ts +149 -0
  105. package/src/core/registry/registry.ts +221 -0
  106. package/src/core/registry/resolver.ts +206 -0
  107. package/src/core/schema/helpers.ts +134 -0
  108. package/src/core/schema/index.ts +8 -0
  109. package/src/core/schema/shared.ts +102 -0
  110. package/src/core/schema/types.ts +279 -0
  111. package/src/core/schema/validator.ts +92 -0
  112. package/src/definitions/actions/captions.ts +261 -0
  113. package/src/definitions/actions/edit.ts +298 -0
  114. package/src/definitions/actions/image.ts +125 -0
  115. package/src/definitions/actions/index.ts +114 -0
  116. package/src/definitions/actions/music.ts +205 -0
  117. package/src/definitions/actions/sync.ts +128 -0
  118. package/{action/transcribe/index.ts → src/definitions/actions/transcribe.ts} +63 -90
  119. package/src/definitions/actions/upload.ts +111 -0
  120. package/src/definitions/actions/video.ts +163 -0
  121. package/src/definitions/actions/voice.ts +119 -0
  122. package/src/definitions/index.ts +23 -0
  123. package/src/definitions/models/elevenlabs.ts +50 -0
  124. package/src/definitions/models/flux.ts +56 -0
  125. package/src/definitions/models/index.ts +36 -0
  126. package/src/definitions/models/kling.ts +56 -0
  127. package/src/definitions/models/llama.ts +54 -0
  128. package/src/definitions/models/nano-banana-pro.ts +102 -0
  129. package/src/definitions/models/sonauto.ts +68 -0
  130. package/src/definitions/models/soul.ts +65 -0
  131. package/src/definitions/models/wan.ts +54 -0
  132. package/src/definitions/models/whisper.ts +44 -0
  133. package/src/definitions/skills/index.ts +12 -0
  134. package/src/definitions/skills/talking-character.ts +87 -0
  135. package/src/definitions/skills/text-to-tiktok.ts +97 -0
  136. package/src/index.ts +118 -0
  137. package/src/providers/apify.ts +269 -0
  138. package/src/providers/base.ts +264 -0
  139. package/src/providers/elevenlabs.ts +217 -0
  140. package/src/providers/fal.ts +392 -0
  141. package/src/providers/ffmpeg.ts +544 -0
  142. package/src/providers/fireworks.ts +193 -0
  143. package/src/providers/groq.ts +149 -0
  144. package/src/providers/higgsfield.ts +145 -0
  145. package/src/providers/index.ts +143 -0
  146. package/src/providers/replicate.ts +147 -0
  147. package/src/providers/storage.ts +206 -0
  148. package/src/react/cli.ts +52 -0
  149. package/src/react/elements.ts +146 -0
  150. package/src/react/examples/branching.tsx +66 -0
  151. package/src/react/examples/captions-demo.tsx +37 -0
  152. package/src/react/examples/character-video.tsx +84 -0
  153. package/src/react/examples/grid.tsx +53 -0
  154. package/src/react/examples/layouts-demo.tsx +57 -0
  155. package/src/react/examples/madi.tsx +60 -0
  156. package/src/react/examples/music-test.tsx +35 -0
  157. package/src/react/examples/onlyfans-1m/workflow.tsx +88 -0
  158. package/src/react/examples/orange-portrait.tsx +41 -0
  159. package/src/react/examples/split-element-demo.tsx +60 -0
  160. package/src/react/examples/split-layout-demo.tsx +60 -0
  161. package/src/react/examples/split.tsx +41 -0
  162. package/src/react/examples/video-grid.tsx +46 -0
  163. package/src/react/index.ts +43 -0
  164. package/src/react/layouts/grid.tsx +28 -0
  165. package/src/react/layouts/index.ts +2 -0
  166. package/src/react/layouts/split.tsx +20 -0
  167. package/src/react/react.test.ts +309 -0
  168. package/src/react/render.ts +21 -0
  169. package/src/react/renderers/animate.ts +59 -0
  170. package/src/react/renderers/captions.ts +297 -0
  171. package/src/react/renderers/clip.ts +248 -0
  172. package/src/react/renderers/context.ts +17 -0
  173. package/src/react/renderers/image.ts +109 -0
  174. package/src/react/renderers/index.ts +22 -0
  175. package/src/react/renderers/music.ts +60 -0
  176. package/src/react/renderers/packshot.ts +84 -0
  177. package/src/react/renderers/progress.ts +173 -0
  178. package/src/react/renderers/render.ts +243 -0
  179. package/src/react/renderers/slider.ts +69 -0
  180. package/src/react/renderers/speech.ts +53 -0
  181. package/src/react/renderers/split.ts +91 -0
  182. package/src/react/renderers/subtitle.ts +16 -0
  183. package/src/react/renderers/swipe.ts +75 -0
  184. package/src/react/renderers/title.ts +17 -0
  185. package/src/react/renderers/utils.ts +124 -0
  186. package/src/react/renderers/video.ts +127 -0
  187. package/src/react/runtime/jsx-dev-runtime.ts +43 -0
  188. package/src/react/runtime/jsx-runtime.ts +35 -0
  189. package/src/react/types.ts +232 -0
  190. package/src/studio/index.ts +26 -0
  191. package/src/studio/scanner.ts +102 -0
  192. package/src/studio/server.ts +554 -0
  193. package/src/studio/stages.ts +251 -0
  194. package/src/studio/step-renderer.ts +279 -0
  195. package/src/studio/types.ts +60 -0
  196. package/src/studio/ui/cache.html +303 -0
  197. package/src/studio/ui/index.html +1820 -0
  198. package/src/tests/all.test.ts +509 -0
  199. package/src/tests/index.ts +33 -0
  200. package/src/tests/unit.test.ts +403 -0
  201. package/tsconfig.cli.json +8 -0
  202. package/tsconfig.json +21 -3
  203. package/TEST_RESULTS.md +0 -122
  204. package/action/captions/SKILL.md +0 -170
  205. package/action/captions/index.ts +0 -227
  206. package/action/edit/SKILL.md +0 -235
  207. package/action/edit/index.ts +0 -493
  208. package/action/image/SKILL.md +0 -140
  209. package/action/image/index.ts +0 -112
  210. package/action/sync/SKILL.md +0 -136
  211. package/action/sync/index.ts +0 -187
  212. package/action/transcribe/SKILL.md +0 -179
  213. package/action/video/SKILL.md +0 -116
  214. package/action/video/index.ts +0 -135
  215. package/action/voice/SKILL.md +0 -125
  216. package/action/voice/index.ts +0 -201
  217. package/index.ts +0 -38
  218. package/lib/README.md +0 -144
  219. package/lib/ai-sdk/fal.ts +0 -106
  220. package/lib/ai-sdk/replicate.ts +0 -107
  221. package/lib/elevenlabs.ts +0 -382
  222. package/lib/fal.ts +0 -478
  223. package/lib/ffmpeg.ts +0 -467
  224. package/lib/fireworks.ts +0 -235
  225. package/lib/groq.ts +0 -246
  226. package/lib/higgsfield.ts +0 -176
  227. package/lib/remotion/SKILL.md +0 -823
  228. package/lib/remotion/cli.ts +0 -115
  229. package/lib/remotion/functions.ts +0 -283
  230. package/lib/remotion/index.ts +0 -19
  231. package/lib/remotion/templates.ts +0 -73
  232. package/lib/replicate.ts +0 -304
  233. package/output.txt +0 -1
  234. package/test-import.ts +0 -7
  235. package/test-services.ts +0 -97
  236. package/utilities/s3.ts +0 -147
@@ -0,0 +1,232 @@
1
+ import type { ImageModelV3, SpeechModelV3 } from "@ai-sdk/provider";
2
+ import type { MusicModelV3 } from "../ai-sdk/music-model";
3
+ import type {
4
+ Position,
5
+ ResizeMode,
6
+ SizeValue,
7
+ TransitionOptions,
8
+ } from "../ai-sdk/providers/editly/types";
9
+ import type { VideoModelV3 } from "../ai-sdk/video-model";
10
+
11
+ export type VargElementType =
12
+ | "render"
13
+ | "clip"
14
+ | "overlay"
15
+ | "image"
16
+ | "video"
17
+ | "animate"
18
+ | "speech"
19
+ | "talking-head"
20
+ | "title"
21
+ | "subtitle"
22
+ | "music"
23
+ | "captions"
24
+ | "split"
25
+ | "slider"
26
+ | "swipe"
27
+ | "packshot";
28
+
29
+ export interface VargElement<T extends VargElementType = VargElementType> {
30
+ type: T;
31
+ props: Record<string, unknown>;
32
+ children: VargNode[];
33
+ }
34
+
35
+ export type VargNode =
36
+ | VargElement
37
+ | string
38
+ | number
39
+ | null
40
+ | undefined
41
+ | VargNode[];
42
+
43
+ export interface BaseProps {
44
+ key?: string | number;
45
+ }
46
+
47
+ export interface PositionProps {
48
+ left?: SizeValue;
49
+ top?: SizeValue;
50
+ width?: SizeValue;
51
+ height?: SizeValue;
52
+ }
53
+
54
+ export interface VolumeProps {
55
+ volume?: number;
56
+ }
57
+
58
+ export interface AudioProps extends VolumeProps {
59
+ keepAudio?: boolean;
60
+ }
61
+
62
+ export type TrimProps =
63
+ | { cutFrom?: number; cutTo?: number; duration?: never }
64
+ | { cutFrom?: number; cutTo?: never; duration?: number };
65
+
66
+ // Root container - sets dimensions, fps, contains clips
67
+ export interface RenderProps extends BaseProps {
68
+ width?: number;
69
+ height?: number;
70
+ fps?: number;
71
+ normalize?: boolean;
72
+ children?: VargNode;
73
+ }
74
+
75
+ export interface ClipProps extends BaseProps {
76
+ duration?: number | "auto";
77
+ transition?: TransitionOptions;
78
+ children?: VargNode;
79
+ }
80
+
81
+ export interface OverlayProps extends BaseProps, PositionProps, AudioProps {
82
+ children?: VargNode;
83
+ }
84
+
85
+ export type ImageInput = Uint8Array | string | VargElement<"image">;
86
+ export type ImagePrompt = string | { text?: string; images: ImageInput[] };
87
+
88
+ export interface ImageProps extends BaseProps, PositionProps {
89
+ prompt?: ImagePrompt;
90
+ src?: string;
91
+ model?: ImageModelV3;
92
+ aspectRatio?: `${number}:${number}`;
93
+ zoom?: "in" | "out" | "left" | "right";
94
+ resize?: ResizeMode;
95
+ position?: Position;
96
+ size?: { width: string; height: string };
97
+ removeBackground?: boolean;
98
+ }
99
+
100
+ export type VideoPrompt =
101
+ | string
102
+ | {
103
+ text?: string;
104
+ images?: ImageInput[];
105
+ audio?: Uint8Array | string;
106
+ video?: Uint8Array | string;
107
+ };
108
+
109
+ export type VideoProps = BaseProps &
110
+ PositionProps &
111
+ AudioProps &
112
+ TrimProps & {
113
+ prompt?: VideoPrompt;
114
+ src?: string;
115
+ model?: VideoModelV3;
116
+ resize?: ResizeMode;
117
+ };
118
+
119
+ // Image-to-video animation
120
+ export interface AnimateProps extends BaseProps, PositionProps {
121
+ image?: VargElement<"image">;
122
+ src?: string;
123
+ model?: VideoModelV3;
124
+ motion?: string;
125
+ duration?: number;
126
+ }
127
+
128
+ export interface SpeechProps extends BaseProps, VolumeProps {
129
+ voice?: string;
130
+ model?: SpeechModelV3;
131
+ id?: string;
132
+ children?: string;
133
+ }
134
+
135
+ export interface TalkingHeadProps extends BaseProps {
136
+ character?: string;
137
+ src?: string;
138
+ voice?: string;
139
+ model?: VideoModelV3;
140
+ lipsyncModel?: VideoModelV3;
141
+ position?:
142
+ | Position
143
+ | { left?: string; right?: string; top?: string; bottom?: string };
144
+ size?: { width: string; height: string };
145
+ children?: string;
146
+ }
147
+
148
+ export interface TitleProps extends BaseProps {
149
+ position?: Position;
150
+ color?: string;
151
+ start?: number;
152
+ end?: number;
153
+ children?: string;
154
+ }
155
+
156
+ export interface SubtitleProps extends BaseProps {
157
+ backgroundColor?: string;
158
+ children?: string;
159
+ }
160
+
161
+ export type MusicProps = BaseProps &
162
+ VolumeProps &
163
+ TrimProps & {
164
+ prompt?: string;
165
+ model?: MusicModelV3;
166
+ src?: string;
167
+ loop?: boolean;
168
+ ducking?: boolean;
169
+ };
170
+
171
+ export interface CaptionsProps extends BaseProps {
172
+ src?: string | VargElement<"speech">;
173
+ srt?: string;
174
+ style?: "tiktok" | "karaoke" | "bounce" | "typewriter";
175
+ color?: string;
176
+ activeColor?: string;
177
+ fontSize?: number;
178
+ }
179
+
180
+ export interface SplitProps extends BaseProps {
181
+ direction?: "horizontal" | "vertical";
182
+ children?: VargNode;
183
+ }
184
+
185
+ export interface SliderProps extends BaseProps {
186
+ direction?: "horizontal" | "vertical";
187
+ children?: VargNode;
188
+ }
189
+
190
+ export interface SwipeProps extends BaseProps {
191
+ direction?: "left" | "right" | "up" | "down";
192
+ interval?: number;
193
+ children?: VargNode;
194
+ }
195
+
196
+ export interface PackshotProps extends BaseProps {
197
+ background?: VargElement<"image"> | string;
198
+ logo?: string;
199
+ logoPosition?: Position;
200
+ logoSize?: SizeValue;
201
+ cta?: string;
202
+ ctaPosition?: Position;
203
+ ctaColor?: string;
204
+ ctaSize?: number;
205
+ blinkCta?: boolean;
206
+ duration?: number;
207
+ }
208
+
209
+ export interface RenderOptions {
210
+ output?: string;
211
+ cache?: string;
212
+ quiet?: boolean;
213
+ }
214
+
215
+ export interface ElementPropsMap {
216
+ render: RenderProps;
217
+ clip: ClipProps;
218
+ overlay: OverlayProps;
219
+ image: ImageProps;
220
+ video: VideoProps;
221
+ animate: AnimateProps;
222
+ speech: SpeechProps;
223
+ "talking-head": TalkingHeadProps;
224
+ title: TitleProps;
225
+ subtitle: SubtitleProps;
226
+ music: MusicProps;
227
+ captions: CaptionsProps;
228
+ split: SplitProps;
229
+ slider: SliderProps;
230
+ swipe: SwipeProps;
231
+ packshot: PackshotProps;
232
+ }
@@ -0,0 +1,26 @@
1
+ import { resolve } from "node:path";
2
+ import { createStudioServer } from "./server";
3
+
4
+ // Parse arguments: bun run studio [file.tsx] [--cache=.cache/ai]
5
+ let initialFile: string | undefined;
6
+ let cacheDir = ".cache/ai";
7
+
8
+ for (const arg of process.argv.slice(2)) {
9
+ if (arg.startsWith("--cache=")) {
10
+ cacheDir = arg.replace("--cache=", "");
11
+ } else if (arg.endsWith(".tsx") || arg.endsWith(".ts")) {
12
+ initialFile = resolve(arg);
13
+ }
14
+ }
15
+
16
+ console.log("varg studio starting...");
17
+ console.log(`cache folder: ${cacheDir}`);
18
+ if (initialFile) {
19
+ console.log(`initial file: ${initialFile}`);
20
+ }
21
+
22
+ const { port } = createStudioServer({ cacheDir, initialFile });
23
+
24
+ console.log(`\nopen http://localhost:${port}`);
25
+ console.log(" /editor - code editor");
26
+ console.log(" /cache - cache viewer");
@@ -0,0 +1,102 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import type { CacheEntry, CacheItem, ImageData, VideoData } from "./types";
4
+
5
+ function extractBase64(data: ImageData | VideoData): string | null {
6
+ if (
7
+ "uint8ArrayData" in data &&
8
+ data.uint8ArrayData?.__type === "Uint8Array"
9
+ ) {
10
+ return data.uint8ArrayData.data;
11
+ }
12
+ if ("_data" in data && data._data?.__type === "Uint8Array") {
13
+ return data._data.data;
14
+ }
15
+ if ("base64" in data && data.base64) {
16
+ return data.base64;
17
+ }
18
+ return null;
19
+ }
20
+
21
+ function detectMediaType(
22
+ filename: string,
23
+ value: unknown,
24
+ ): "image" | "video" | "unknown" {
25
+ if (filename.startsWith("generateImage")) return "image";
26
+ if (filename.startsWith("generateVideo")) return "video";
27
+
28
+ if (value && typeof value === "object") {
29
+ const v = value as Record<string, unknown>;
30
+ if ("images" in v) return "image";
31
+ if ("video" in v) return "video";
32
+ }
33
+
34
+ return "unknown";
35
+ }
36
+
37
+ export async function scanCacheFolder(cacheDir: string): Promise<CacheItem[]> {
38
+ const items: CacheItem[] = [];
39
+
40
+ try {
41
+ const files = await readdir(cacheDir);
42
+
43
+ for (const file of files) {
44
+ if (!file.endsWith(".json") || file === ".gitkeep") continue;
45
+
46
+ const filePath = join(cacheDir, file);
47
+ const fileStat = await stat(filePath);
48
+
49
+ try {
50
+ const content = await Bun.file(filePath).text();
51
+ const entry = JSON.parse(content) as CacheEntry;
52
+ const type = detectMediaType(file, entry.value);
53
+
54
+ items.push({
55
+ id: file.replace(".json", ""),
56
+ filename: file,
57
+ type,
58
+ size: fileStat.size,
59
+ createdAt: fileStat.mtime,
60
+ metadata: { expires: entry.expires },
61
+ });
62
+ } catch {}
63
+ }
64
+ } catch (err) {
65
+ console.error("failed to scan cache folder:", err);
66
+ }
67
+
68
+ return items.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
69
+ }
70
+
71
+ export async function getCacheItemMedia(
72
+ cacheDir: string,
73
+ id: string,
74
+ ): Promise<{ type: "image" | "video"; data: string; mimeType: string } | null> {
75
+ const filePath = join(cacheDir, `${id}.json`);
76
+
77
+ try {
78
+ const content = await Bun.file(filePath).text();
79
+ const entry = JSON.parse(content) as CacheEntry;
80
+ const value = entry.value as Record<string, unknown>;
81
+
82
+ if ("images" in value && Array.isArray(value.images)) {
83
+ const firstImage = value.images[0] as ImageData;
84
+ const base64 = extractBase64(firstImage);
85
+ if (base64) {
86
+ return { type: "image", data: base64, mimeType: "image/jpeg" };
87
+ }
88
+ }
89
+
90
+ if ("video" in value && typeof value.video === "object") {
91
+ const video = value.video as VideoData;
92
+ const base64 = extractBase64(video);
93
+ if (base64) {
94
+ return { type: "video", data: base64, mimeType: "video/mp4" };
95
+ }
96
+ }
97
+
98
+ return null;
99
+ } catch {
100
+ return null;
101
+ }
102
+ }