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
@@ -1,823 +0,0 @@
1
- # remotion skill
2
-
3
- ## overview
4
- programmatic video creation with react components using remotion
5
-
6
- ## quick start
7
- ```bash
8
- # 1. create composition with template files
9
- bun run lib/remotion/index.ts create MyVideo
10
- # creates: lib/remotion/compositions/MyVideo.tsx (composition component)
11
- # lib/remotion/compositions/MyVideo.root.tsx (root with registerRoot)
12
-
13
- # 2. copy media files to public directory
14
- mkdir -p lib/remotion/public
15
- cp media/video.mp4 media/audio.mp3 lib/remotion/public/
16
-
17
- # 3. customize the generated composition files
18
- # - edit MyVideo.tsx to add your video/image/audio content
19
- # - edit MyVideo.root.tsx to set fps, duration, width, height
20
-
21
- # 4. render
22
- bun run lib/remotion/index.ts render lib/remotion/compositions/MyVideo.root.tsx MyVideo output.mp4
23
- ```
24
-
25
- **important**: always use `staticFile("filename.ext")` for media paths, never absolute paths
26
-
27
- ## what you can use remotion for
28
-
29
- ### 1. video editing
30
- - trim videos to specific frame ranges
31
- - adjust playback speed (slow motion, time-lapse)
32
- - apply filters and color grading with CSS
33
- - overlay graphics and text
34
- - create picture-in-picture effects
35
-
36
- ### 2. zooming and panning
37
- - smooth zoom in/out effects with `interpolate()`
38
- - ken burns effect on static images
39
- - dynamic camera movements
40
- - focus on specific areas frame-by-frame
41
-
42
- ### 3. combining multiple videos
43
- - concatenate videos sequentially (one after another)
44
- - play videos side-by-side or in grid layouts
45
- - layer videos with opacity/blend modes
46
- - transition between scenes with crossfades
47
-
48
- ### 4. audio mixing
49
- - combine multiple audio tracks
50
- - sync audio with video
51
- - adjust volume levels with `interpolate()`
52
- - add background music and sound effects
53
- - fade in/out audio
54
-
55
- ### 5. beautiful subtitles
56
- - word-by-word animated captions
57
- - styled text with custom fonts and colors
58
- - background boxes for readability
59
- - position captions anywhere on screen
60
- - karaoke-style highlighting
61
- - emoji support and rich formatting
62
-
63
- ### 6. thumbnail generation
64
- - render specific frames as stills (using remotion's renderStill)
65
- - create custom thumbnail compositions with text/graphics
66
- - generate multiple preview frames at different timestamps
67
- - design animated thumbnail previews
68
-
69
- ### 7. advanced effects
70
- - motion graphics and animations
71
- - data visualizations synchronized with narration
72
- - dynamic text reveals
73
- - progress bars and timers
74
- - responsive layouts that adapt to content
75
-
76
- ## capabilities
77
-
78
- ### composition creation
79
- - create composition structure with `bun run lib/remotion/index.ts create <name>`
80
- - automatically generates template files:
81
- - `<name>.tsx` - composition component with all necessary imports
82
- - `<name>.root.tsx` - root file with registerRoot() already configured
83
- - files are ready to customize with your content
84
- - media files go in `lib/remotion/public/`
85
-
86
- ### composition editing
87
- - write react components to create video scenes
88
- - use remotion's `<OffthreadVideo>`, `<Audio>`, `<Img>` components
89
- - reference media with `staticFile("filename.mp4")` helper
90
- - add animations with `useCurrentFrame()` and `interpolate()`
91
- - parse and display subtitles/captions
92
- - combine multiple videos sequentially or in parallel
93
-
94
- ### root file setup
95
- - must use `registerRoot()` function (not export)
96
- - register compositions with `<Composition>` component
97
- - specify id, component, durationInFrames, fps, width, height
98
-
99
- ### rendering
100
- - bundle project with webpack automatically
101
- - render compositions to mp4 video with h264 codec
102
- - render single frames as images (thumbnails)
103
- - track rendering progress in real-time
104
-
105
- ## common patterns
106
-
107
- ### 1. create video with captions
108
- ```typescript
109
- import { createProject, render } from "lib/remotion";
110
-
111
- // create project
112
- const project = await createProject();
113
-
114
- // edit composition (add to src/MyComp.tsx)
115
- // - add Video component with staticFile("video.mp4")
116
- // - parse SRT file and display captions
117
- // - use useCurrentFrame() to sync captions with video
118
-
119
- // render
120
- await render({
121
- entryPoint: project.entryPoint,
122
- compositionId: "MyComp",
123
- outputPath: "output.mp4"
124
- });
125
- ```
126
-
127
- ### 2. concatenate videos
128
- ```typescript
129
- // in composition:
130
- const frame = useCurrentFrame();
131
- const video1Duration = 1430; // frames
132
-
133
- {frame < video1Duration ? (
134
- <Video src={staticFile("video1.mp4")} />
135
- ) : (
136
- <Video
137
- src={staticFile("video2.mp4")}
138
- startFrom={frame - video1Duration}
139
- />
140
- )}
141
- ```
142
-
143
- ### 3. add styled captions
144
- ```typescript
145
- interface Subtitle {
146
- startTime: number;
147
- endTime: number;
148
- text: string;
149
- }
150
-
151
- const currentTime = frame / fps;
152
- const subtitle = subtitles.find(
153
- s => currentTime >= s.startTime && currentTime <= s.endTime
154
- );
155
-
156
- {subtitle && (
157
- <div style={{
158
- fontSize: 48,
159
- fontWeight: "bold",
160
- color: "white",
161
- backgroundColor: "rgba(0,0,0,0.7)",
162
- padding: "20px 40px",
163
- borderRadius: 12
164
- }}>
165
- {subtitle.text}
166
- </div>
167
- )}
168
- ```
169
-
170
- ## important notes
171
-
172
- ### audio/voiceover duration
173
- - always probe audio files before setting composition duration
174
- - voiceovers are often much longer than you think (can be 30s, 60s, or more)
175
- - never assume voiceover duration - always check with ffmpeg probe
176
- - common mistake: setting durationInFrames too short, cutting off audio
177
- - workflow:
178
- 1. probe voiceover: `bun run lib/ffmpeg.ts probe media/voiceover.mp3`
179
- 2. note the duration (e.g., 45.2 seconds)
180
- 3. calculate frames: `45.2 * 30fps = 1356 frames`
181
- 4. set composition `durationInFrames` to match or exceed this
182
- - if video is shorter than audio, you need to either:
183
- - extend video with images/broll to match audio length
184
- - trim the audio to match video length
185
- - verify before rendering: check that composition duration >= audio duration
186
-
187
- ### image dimensions and aspect ratios
188
- - be mindful of image aspect ratios vs composition dimensions
189
- - images may not fill the frame properly (leaving black bars or getting cropped)
190
- - common solutions:
191
- - `objectFit: "cover"` - fills frame but crops image
192
- - `objectFit: "contain"` - fits full image but may leave black bars
193
- - **blurred background technique** - best of both worlds:
194
- 1. layer 1 (background): same image scaled to fill, with blur filter
195
- 2. layer 2 (foreground): full image fitted with `objectFit: "contain"`
196
- 3. result: no black bars, full image visible, aesthetic blurred background
197
- - always check how images look in final composition, especially portrait images in landscape frames
198
-
199
- ### media file paths
200
- - copy media to `public/` directory in project
201
- - use `staticFile("filename.mp4")` to reference
202
- - absolute paths won't work in remotion
203
- - **CRITICAL**: staticFile() caches based on filename
204
- - if you overwrite files (e.g., `before.jpg`, `after.jpg`) between renders, Remotion will cache the LAST version for ALL renders
205
- - solution: use unique filenames for each variation (e.g., `woman-01-before.jpg`, `woman-02-before.jpg`)
206
- - for variations: pass unique identifiers as props and use template strings: `staticFile(\`image-${id}.jpg\`)`
207
-
208
- ### frame-based timing
209
- - everything in remotion is frame-based
210
- - calculate duration: `frames = seconds * fps`
211
- - get current time: `currentTime = frame / fps`
212
-
213
- ### video concatenation
214
- - calculate end frame of first video
215
- - start second video at that frame
216
- - adjust `startFrom` prop for proper timing
217
-
218
- ### composition registration
219
- - register compositions in `src/Root.tsx`
220
- - specify id, width, height, fps, durationInFrames
221
- - use unique composition ids
222
- - for multiple variations: use `Array.from()` to generate compositions programmatically
223
- - example: `Array.from({ length: 15 }, (_, i) => { ... })`
224
- - pass unique props via `defaultProps: { variationId: "01" }`
225
- - each composition can render different content based on props
226
-
227
- ## typical workflow
228
-
229
- 1. **probe all media files** (especially audio/voiceover - they're often very long)
230
- ```bash
231
- # probe video to get duration, fps, resolution
232
- bun run lib/ffmpeg.ts probe media/video.mp4
233
-
234
- # probe voiceover/audio to get true duration
235
- bun run lib/ffmpeg.ts probe media/voiceover.mp3
236
- # voiceovers can be 30s, 60s, or more - never assume
237
- ```
238
-
239
- 2. **create composition with templates**
240
- ```bash
241
- bun run lib/remotion/index.ts create MyVideo
242
- ```
243
- this automatically creates:
244
- - `lib/remotion/compositions/MyVideo.tsx` (composition component)
245
- - `lib/remotion/compositions/MyVideo.root.tsx` (root file with registerRoot)
246
-
247
- 3. **copy media to public directory**
248
- ```bash
249
- mkdir -p lib/remotion/public
250
- cp media/video.mp4 media/audio.mp3 media/*.png lib/remotion/public/
251
- ```
252
-
253
- 4. **customize composition** (lib/remotion/compositions/MyVideo.tsx)
254
- - template already has all imports: `OffthreadVideo`, `Audio`, `Img`, `staticFile`
255
- - replace placeholder content with your media
256
- - use `staticFile("filename.mp4")` for all media references
257
- - add animations with `useCurrentFrame()` and `interpolate()`
258
-
259
- ```tsx
260
- // example customization
261
- const video = staticFile("video.mp4");
262
- const audio = staticFile("audio.mp3");
263
-
264
- return (
265
- <AbsoluteFill>
266
- <OffthreadVideo src={video} />
267
- <Audio src={audio} />
268
- </AbsoluteFill>
269
- );
270
- ```
271
-
272
- 5. **configure settings** (lib/remotion/compositions/MyVideo.root.tsx)
273
- - template already uses `registerRoot()` correctly
274
- - update fps, durationInFrames, width, height as needed
275
- ```tsx
276
- const fps = 30;
277
- const durationInFrames = 150; // 5 seconds
278
- const width = 1920;
279
- const height = 1080;
280
- ```
281
-
282
- 6. **render**
283
- ```bash
284
- bun run lib/remotion/index.ts render lib/remotion/compositions/MyVideo.root.tsx MyVideo output.mp4
285
- ```
286
-
287
- ## tools available
288
-
289
- ### lib/remotion/index.ts
290
- ```bash
291
- # setup composition directory
292
- bun run lib/remotion/index.ts create <name>
293
-
294
- # list compositions
295
- bun run lib/remotion/index.ts compositions <root-file.tsx>
296
-
297
- # render video
298
- bun run lib/remotion/index.ts render <root-file.tsx> <comp-id> <output.mp4>
299
-
300
- # render still frame
301
- bun run lib/remotion/index.ts still <root-file.tsx> <comp-id> <frame> <out.png>
302
- ```
303
-
304
- ### lib/ffmpeg.ts
305
- ```bash
306
- # get video metadata
307
- bun run lib/ffmpeg.ts probe <input.mp4>
308
- ```
309
-
310
- ## examples
311
-
312
- ### complete workflow: video + images montage with audio
313
-
314
- ```bash
315
- # 1. probe video to get metadata
316
- bun run lib/ffmpeg.ts probe media/video.mp4
317
- # output: 1920x1080 @ 24fps, 5.041667s
318
-
319
- # 2. create composition structure
320
- bun run lib/remotion/index.ts create MediaMontage
321
- # creates: lib/remotion/compositions/MediaMontage.tsx
322
- # lib/remotion/compositions/MediaMontage.root.tsx
323
-
324
- # 3. copy specific media files to public directory
325
- mkdir -p lib/remotion/public
326
- cp media/video.mp4 media/audio.ogg media/image1.png media/image2.png media/image3.png media/image4.png lib/remotion/public/
327
-
328
- # 4. create composition file (MediaMontage.tsx)
329
- cat > lib/remotion/compositions/MediaMontage.tsx << 'EOF'
330
- import React from "react";
331
- import { AbsoluteFill, OffthreadVideo, Audio, Img, useCurrentFrame, useVideoConfig, interpolate, staticFile } from "remotion";
332
-
333
- export const MediaMontage: React.FC = () => {
334
- const frame = useCurrentFrame();
335
- const { fps } = useVideoConfig();
336
-
337
- const imageDisplayTime = 3;
338
- const imageFrames = imageDisplayTime * fps;
339
- const videoFrames = Math.floor(5.041667 * fps);
340
-
341
- const videoPath = staticFile("video.mp4");
342
- const audioPath = staticFile("audio.ogg");
343
- const images = [
344
- staticFile("image1.png"),
345
- staticFile("image2.png"),
346
- ];
347
-
348
- const videoEnd = videoFrames;
349
- let content: React.ReactNode = null;
350
-
351
- if (frame < videoEnd) {
352
- content = <OffthreadVideo src={videoPath} />;
353
- } else {
354
- const imageFrame = frame - videoEnd;
355
- const imageIndex = Math.floor(imageFrame / imageFrames);
356
-
357
- if (imageIndex < images.length) {
358
- const localFrame = imageFrame % imageFrames;
359
- const scale = interpolate(localFrame, [0, imageFrames], [1, 1.15], { extrapolateRight: "clamp" });
360
- content = (
361
- <div style={{ width: "100%", height: "100%", transform: `scale(${scale})` }}>
362
- <Img src={images[imageIndex] as string} style={{ width: "100%", height: "100%", objectFit: "cover" }} />
363
- </div>
364
- );
365
- }
366
- }
367
-
368
- return (
369
- <AbsoluteFill style={{ backgroundColor: "black" }}>
370
- {content}
371
- <Audio src={audioPath} />
372
- </AbsoluteFill>
373
- );
374
- };
375
- EOF
376
-
377
- # 5. create root file (MediaMontage.root.tsx)
378
- cat > lib/remotion/compositions/MediaMontage.root.tsx << 'EOF'
379
- import React from "react";
380
- import { Composition, registerRoot } from "remotion";
381
- import { MediaMontage } from "./MediaMontage";
382
-
383
- const fps = 30;
384
- const videoFrames = Math.floor(5.041667 * fps);
385
- const imageFrames = 4 * 3 * fps; // 4 images, 3 seconds each
386
- const totalFrames = videoFrames + imageFrames;
387
-
388
- registerRoot(() => {
389
- return (
390
- <>
391
- <Composition
392
- id="MediaMontage"
393
- component={MediaMontage}
394
- durationInFrames={totalFrames}
395
- fps={fps}
396
- width={1920}
397
- height={1080}
398
- />
399
- </>
400
- );
401
- });
402
- EOF
403
-
404
- # 6. render composition
405
- bun run lib/remotion/index.ts render lib/remotion/compositions/MediaMontage.root.tsx MediaMontage media/output.mp4
406
-
407
- # 7. verify output
408
- bun run lib/ffmpeg.ts probe media/output.mp4
409
- # output: 1920x1080 @ 30fps, 17.033s
410
- ```
411
-
412
- ### render specific frame as thumbnail
413
- ```bash
414
- # render frame 100 as image (useful for video preview)
415
- bun run lib/remotion.ts still /path/to/project/src/index.ts MyVideo 100 thumbnail.png
416
- ```
417
-
418
- ```typescript
419
- // create custom thumbnail composition with graphics
420
- export const Thumbnail: React.FC = () => {
421
- return (
422
- <AbsoluteFill>
423
- <Video src={staticFile("video.mp4")} />
424
- {/* add title overlay */}
425
- <div style={{
426
- position: "absolute",
427
- bottom: 50,
428
- fontSize: 60,
429
- fontWeight: "bold",
430
- color: "white",
431
- }}>
432
- My Video Title
433
- </div>
434
- </AbsoluteFill>
435
- );
436
- };
437
-
438
- // then render frame 0 of this composition
439
- ```
440
-
441
- ### zoom in effect
442
- ```typescript
443
- import { interpolate } from "remotion";
444
-
445
- const frame = useCurrentFrame();
446
-
447
- // zoom from 1x to 2x over 60 frames
448
- const scale = interpolate(frame, [0, 60], [1, 2], {
449
- extrapolateRight: "clamp"
450
- });
451
-
452
- return (
453
- <AbsoluteFill>
454
- <div style={{
455
- transform: `scale(${scale})`,
456
- transformOrigin: "center center"
457
- }}>
458
- <Video src={staticFile("video.mp4")} />
459
- </div>
460
- </AbsoluteFill>
461
- );
462
- ```
463
-
464
- ### ken burns effect (pan + zoom)
465
- ```typescript
466
- const scale = interpolate(frame, [0, 150], [1, 1.3]);
467
- const translateX = interpolate(frame, [0, 150], [0, -100]);
468
- const translateY = interpolate(frame, [0, 150], [0, -50]);
469
-
470
- return (
471
- <div style={{
472
- transform: `scale(${scale}) translate(${translateX}px, ${translateY}px)`,
473
- }}>
474
- <Img src={staticFile("image.jpg")} />
475
- </div>
476
- );
477
- ```
478
-
479
- ### image with blurred background (aspect ratio fix)
480
- ```typescript
481
- // fits any image into frame without black bars
482
- // works great for portrait images in landscape compositions
483
- const imageSrc = staticFile("portrait-image.jpg");
484
-
485
- return (
486
- <AbsoluteFill>
487
- {/* blurred background layer - fills entire frame */}
488
- <AbsoluteFill>
489
- <Img
490
- src={imageSrc}
491
- style={{
492
- width: "100%",
493
- height: "100%",
494
- objectFit: "cover",
495
- filter: "blur(40px)",
496
- opacity: 0.6
497
- }}
498
- />
499
- </AbsoluteFill>
500
-
501
- {/* foreground layer - full image fitted */}
502
- <AbsoluteFill style={{
503
- display: "flex",
504
- justifyContent: "center",
505
- alignItems: "center"
506
- }}>
507
- <Img
508
- src={imageSrc}
509
- style={{
510
- maxWidth: "100%",
511
- maxHeight: "100%",
512
- objectFit: "contain"
513
- }}
514
- />
515
- </AbsoluteFill>
516
- </AbsoluteFill>
517
- );
518
- ```
519
-
520
- ### combine multiple audio tracks
521
- ```typescript
522
- import { Audio } from "remotion";
523
-
524
- return (
525
- <AbsoluteFill>
526
- <Video src={staticFile("video.mp4")} />
527
- {/* background music at 30% volume */}
528
- <Audio src={staticFile("music.mp3")} volume={0.3} />
529
- {/* voiceover at full volume */}
530
- <Audio src={staticFile("narration.mp3")} volume={1} />
531
- </AbsoluteFill>
532
- );
533
- ```
534
-
535
- ### audio fade in/out
536
- ```typescript
537
- const audioVolume = interpolate(
538
- frame,
539
- [0, 30, 270, 300], // fade in first 30 frames, out last 30
540
- [0, 1, 1, 0],
541
- { extrapolateLeft: "clamp", extrapolateRight: "clamp" }
542
- );
543
-
544
- <Audio src={staticFile("music.mp3")} volume={audioVolume} />
545
- ```
546
-
547
- ### side-by-side videos
548
- ```typescript
549
- const { width, height } = useVideoConfig();
550
-
551
- return (
552
- <AbsoluteFill>
553
- {/* left video */}
554
- <AbsoluteFill style={{ width: width / 2, left: 0 }}>
555
- <Video src={staticFile("video1.mp4")} />
556
- </AbsoluteFill>
557
-
558
- {/* right video */}
559
- <AbsoluteFill style={{ width: width / 2, left: width / 2 }}>
560
- <Video src={staticFile("video2.mp4")} />
561
- </AbsoluteFill>
562
- </AbsoluteFill>
563
- );
564
- ```
565
-
566
- ### grid layout (4 videos)
567
- ```typescript
568
- return (
569
- <AbsoluteFill style={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
570
- <Video src={staticFile("video1.mp4")} />
571
- <Video src={staticFile("video2.mp4")} />
572
- <Video src={staticFile("video3.mp4")} />
573
- <Video src={staticFile("video4.mp4")} />
574
- </AbsoluteFill>
575
- );
576
- ```
577
-
578
- ### video with word-by-word captions
579
- ```typescript
580
- // parse SRT
581
- const subtitles = parseSRT(srtContent);
582
-
583
- // in component
584
- const frame = useCurrentFrame();
585
- const { fps } = useVideoConfig();
586
- const currentTime = frame / fps;
587
-
588
- const currentSubtitle = subtitles.find(
589
- sub => currentTime >= sub.startTime && currentTime <= sub.endTime
590
- );
591
-
592
- return (
593
- <AbsoluteFill>
594
- <OffthreadVideo src={staticFile("video.mp4")} />
595
- {currentSubtitle && (
596
- <div className="caption">{currentSubtitle.text}</div>
597
- )}
598
- </AbsoluteFill>
599
- );
600
- ```
601
-
602
- ### sequential video concatenation
603
- ```typescript
604
- const fitnessEnd = 1430; // 47.67s * 30fps
605
- const kangarooStart = fitnessEnd;
606
-
607
- return (
608
- <AbsoluteFill>
609
- {frame < fitnessEnd ? (
610
- <OffthreadVideo src={staticFile("fitness.mp4")} />
611
- ) : (
612
- <OffthreadVideo
613
- src={staticFile("kangaroo.mp4")}
614
- startFrom={Math.floor((frame - kangarooStart) * (24/30))}
615
- />
616
- )}
617
- </AbsoluteFill>
618
- );
619
- ```
620
-
621
- ### crossfade transition between videos
622
- ```typescript
623
- const transitionStart = 140;
624
- const transitionDuration = 20;
625
-
626
- const opacity1 = interpolate(
627
- frame,
628
- [transitionStart, transitionStart + transitionDuration],
629
- [1, 0],
630
- { extrapolateRight: "clamp" }
631
- );
632
-
633
- const opacity2 = interpolate(
634
- frame,
635
- [transitionStart, transitionStart + transitionDuration],
636
- [0, 1],
637
- { extrapolateRight: "clamp" }
638
- );
639
-
640
- return (
641
- <AbsoluteFill>
642
- <AbsoluteFill style={{ opacity: opacity1 }}>
643
- <Video src={staticFile("video1.mp4")} />
644
- </AbsoluteFill>
645
- <AbsoluteFill style={{ opacity: opacity2 }}>
646
- <Video src={staticFile("video2.mp4")} />
647
- </AbsoluteFill>
648
- </AbsoluteFill>
649
- );
650
- ```
651
-
652
- ### beautiful animated captions
653
- ```typescript
654
- // word appears from bottom with bounce
655
- const captionY = interpolate(
656
- frame - subtitle.startFrame,
657
- [0, 10],
658
- [50, 0],
659
- { extrapolateRight: "clamp", easing: Easing.bounce }
660
- );
661
-
662
- const captionOpacity = interpolate(
663
- frame - subtitle.startFrame,
664
- [0, 5],
665
- [0, 1],
666
- { extrapolateRight: "clamp" }
667
- );
668
-
669
- {currentSubtitle && (
670
- <div style={{
671
- fontFamily: "Inter",
672
- fontSize: 60,
673
- fontWeight: "900",
674
- color: "#FFD700",
675
- textAlign: "center",
676
- textShadow: "4px 4px 8px rgba(0,0,0,0.8)",
677
- background: "linear-gradient(135deg, rgba(0,0,0,0.9), rgba(20,20,50,0.9))",
678
- padding: "30px 50px",
679
- borderRadius: 20,
680
- border: "3px solid #FFD700",
681
- transform: `translateY(${captionY}px)`,
682
- opacity: captionOpacity,
683
- }}>
684
- {currentSubtitle.text.toUpperCase()}
685
- </div>
686
- )}
687
- ```
688
-
689
- ## troubleshooting
690
-
691
- ### "registerRoot" error when rendering
692
- - **error**: `This file does not contain "registerRoot"`
693
- - **cause**: root file exports component instead of calling registerRoot()
694
- - **fix**: use `registerRoot(() => { return (<>...</>) })` instead of `export const RemotionRoot`
695
- - **example**:
696
- ```tsx
697
- // ❌ wrong
698
- export const RemotionRoot: React.FC = () => { return (<>...</>) };
699
-
700
- // ✅ correct
701
- import { registerRoot } from "remotion";
702
- registerRoot(() => { return (<>...</>) });
703
- ```
704
-
705
- ### video not loading (404 error)
706
- - **error**: `Received a status code of 404 while downloading file`
707
- - **cause**: using absolute file paths instead of staticFile()
708
- - **fix**: copy media to `lib/remotion/public/` and use `staticFile()`
709
- - **example**:
710
- ```tsx
711
- // ❌ wrong
712
- const video = "/Users/aleks/project/media/video.mp4";
713
-
714
- // ✅ correct - copy file first
715
- // cp media/video.mp4 lib/remotion/public/
716
- import { staticFile } from "remotion";
717
- const video = staticFile("video.mp4");
718
- ```
719
-
720
- ### deprecated components warnings
721
- - **warning**: `Video` and `Audio` are deprecated
722
- - **fix**: use `OffthreadVideo` instead of `Video`, `Audio` is still usable but may change
723
- - **example**:
724
- ```tsx
725
- // ❌ deprecated
726
- import { Video } from "remotion";
727
- <Video src={staticFile("video.mp4")} />
728
-
729
- // ✅ recommended
730
- import { OffthreadVideo } from "remotion";
731
- <OffthreadVideo src={staticFile("video.mp4")} />
732
- ```
733
-
734
- ### type errors with array indexing
735
- - **error**: `Type 'string | undefined' is not assignable to type 'string'`
736
- - **cause**: typescript doesn't know array index is valid
737
- - **fix**: use type assertion `as string` or check bounds
738
- - **example**:
739
- ```tsx
740
- // ❌ type error
741
- <Img src={images[index]} />
742
-
743
- // ✅ with type assertion
744
- <Img src={images[index] as string} />
745
-
746
- // ✅ with bounds check
747
- {index < images.length && <Img src={images[index]} />}
748
- ```
749
-
750
- ### composition not found
751
- - verify composition is registered with registerRoot()
752
- - check composition id matches exactly
753
- - run `compositions` command to list available
754
-
755
- ### audio/voiceover gets cut off
756
- - error: rendered video ends before audio finishes
757
- - cause: composition `durationInFrames` is shorter than audio duration
758
- - fix: probe audio first, calculate correct frame count
759
- - example:
760
- ```bash
761
- # probe the voiceover
762
- bun run lib/ffmpeg.ts probe media/voiceover.mp3
763
- # output: duration: 47.2s
764
-
765
- # calculate frames for 30fps
766
- # 47.2 * 30 = 1416 frames
767
-
768
- # in root file, set durationInFrames to at least 1416
769
- const durationInFrames = 1416; // or higher if adding more content
770
- ```
771
- - prevention: always probe audio files before setting composition duration
772
- - common mistake: assuming voiceover is only 5-10 seconds when it's actually 30-60+ seconds
773
-
774
- ### wrong video duration
775
- - probe video to get exact duration and fps
776
- - calculate frames: `durationInFrames = duration * fps`
777
- - account for fps differences when concatenating
778
-
779
- ### captions not syncing
780
- - verify SRT timestamps are in seconds
781
- - convert to frame-based timing: `frame / fps`
782
- - check start/end time comparisons
783
-
784
- ### all renders showing same content (caching issue)
785
- - **error**: batch rendering multiple variations but all videos show the same content
786
- - **cause**: overwriting files in `public/` folder between renders causes staticFile() to cache the last version
787
- - **symptoms**:
788
- - renders complete successfully
789
- - all videos have correct file size/duration
790
- - but all videos show identical content (usually the last variation)
791
- - **fix**: use unique filenames for each variation instead of overwriting
792
- - pass variation ID as prop: `defaultProps: { variationId: "01" }`
793
- - use template strings in staticFile: `staticFile(\`woman-${variationId}-before.jpg\`)`
794
- - ensure all unique files exist in `public/` before rendering
795
- - **example**:
796
- ```tsx
797
- // ❌ wrong - overwrites same file
798
- // render loop: copy woman1 → before.jpg, render, copy woman2 → before.jpg, render...
799
- const beforeImg = staticFile("before.jpg"); // caches last file!
800
-
801
- // ✅ correct - unique filenames
802
- interface Props { variationId?: string }
803
- const MyComp: React.FC<Props> = ({ variationId = "01" }) => {
804
- const beforeImg = staticFile(\`woman-${variationId}-before.jpg\`);
805
- // each render uses different file, no caching issues
806
- }
807
- ```
808
-
809
- ## best practices
810
-
811
- 1. **always probe videos first** - get accurate duration/fps using `bun run lib/ffmpeg.ts probe`
812
- 2. **probe audio files too** - voiceovers/narration are often 30s, 60s, or longer - never guess duration
813
- 3. **verify composition duration vs audio** - make sure `durationInFrames` is >= audio duration, or audio will be cut off
814
- 4. **copy media to public/** - copy all media files to `lib/remotion/public/` before rendering
815
- 5. **use staticFile() for all media** - never use absolute paths in compositions
816
- 6. **use unique filenames for variations** - never overwrite files in `public/` between renders (staticFile caches by filename)
817
- 7. **use registerRoot()** - root files must call `registerRoot()`, not export a component
818
- 8. **use OffthreadVideo** - prefer `OffthreadVideo` over deprecated `Video` component
819
- 9. **calculate frames correctly** - `durationInFrames = duration * fps`
820
- 10. **test compositions** - run `compositions` command to verify before rendering
821
- 11. **handle fps differences** - adjust startFrom when concatenating videos with different fps
822
- 12. **use descriptive ids** - make composition names clear and unique
823
- 13. **batch render with props** - for multiple variations, register multiple compositions with unique defaultProps instead of file overwriting