@vargai/sdk 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +24 -0
- package/CLAUDE.md +118 -0
- package/HIGGSFIELD_REWRITE_SUMMARY.md +300 -0
- package/README.md +231 -0
- package/SKILLS.md +157 -0
- package/STRUCTURE.md +92 -0
- package/TEST_RESULTS.md +122 -0
- package/action/captions/SKILL.md +170 -0
- package/action/captions/index.ts +169 -0
- package/action/edit/SKILL.md +235 -0
- package/action/edit/index.ts +437 -0
- package/action/image/SKILL.md +140 -0
- package/action/image/index.ts +105 -0
- package/action/sync/SKILL.md +136 -0
- package/action/sync/index.ts +145 -0
- package/action/transcribe/SKILL.md +179 -0
- package/action/transcribe/index.ts +210 -0
- package/action/video/SKILL.md +116 -0
- package/action/video/index.ts +125 -0
- package/action/voice/SKILL.md +125 -0
- package/action/voice/index.ts +136 -0
- package/biome.json +33 -0
- package/bun.lock +842 -0
- package/cli/commands/find.ts +58 -0
- package/cli/commands/help.ts +70 -0
- package/cli/commands/list.ts +49 -0
- package/cli/commands/run.ts +237 -0
- package/cli/commands/which.ts +66 -0
- package/cli/discover.ts +66 -0
- package/cli/index.ts +33 -0
- package/cli/runner.ts +65 -0
- package/cli/types.ts +49 -0
- package/cli/ui.ts +185 -0
- package/index.ts +75 -0
- package/lib/README.md +144 -0
- package/lib/ai-sdk/fal.ts +106 -0
- package/lib/ai-sdk/replicate.ts +107 -0
- package/lib/elevenlabs.ts +382 -0
- package/lib/fal.ts +467 -0
- package/lib/ffmpeg.ts +467 -0
- package/lib/fireworks.ts +235 -0
- package/lib/groq.ts +246 -0
- package/lib/higgsfield/MIGRATION.md +308 -0
- package/lib/higgsfield/README.md +273 -0
- package/lib/higgsfield/example.ts +228 -0
- package/lib/higgsfield/index.ts +241 -0
- package/lib/higgsfield/soul.ts +262 -0
- package/lib/higgsfield.ts +176 -0
- package/lib/remotion/SKILL.md +823 -0
- package/lib/remotion/cli.ts +115 -0
- package/lib/remotion/functions.ts +283 -0
- package/lib/remotion/index.ts +19 -0
- package/lib/remotion/templates.ts +73 -0
- package/lib/replicate.ts +304 -0
- package/output.txt +1 -0
- package/package.json +42 -0
- package/pipeline/cookbooks/SKILL.md +285 -0
- package/pipeline/cookbooks/remotion-video.md +585 -0
- package/pipeline/cookbooks/round-video-character.md +337 -0
- package/pipeline/cookbooks/talking-character.md +59 -0
- package/scripts/produce-menopause-campaign.sh +202 -0
- package/service/music/SKILL.md +229 -0
- package/service/music/index.ts +296 -0
- package/test-import.ts +7 -0
- package/test-services.ts +97 -0
- package/tsconfig.json +29 -0
- package/utilities/s3.ts +147 -0
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
# remotion video creation pipeline
|
|
2
|
+
|
|
3
|
+
## overview
|
|
4
|
+
create programmatic videos using react components with remotion
|
|
5
|
+
|
|
6
|
+
## what remotion can do
|
|
7
|
+
|
|
8
|
+
### video editing & effects
|
|
9
|
+
- **trim & cut**: extract specific frame ranges from videos
|
|
10
|
+
- **zoom & pan**: smooth ken burns effects, dynamic camera movements
|
|
11
|
+
- **speed control**: slow motion, time-lapse, variable playback speed
|
|
12
|
+
- **filters**: apply CSS filters (brightness, contrast, blur, grayscale)
|
|
13
|
+
- **transitions**: crossfades, wipes, custom transitions between clips
|
|
14
|
+
|
|
15
|
+
### combining content
|
|
16
|
+
- **concatenate videos**: join multiple videos sequentially
|
|
17
|
+
- **multi-track**: side-by-side, grid layouts, picture-in-picture
|
|
18
|
+
- **audio mixing**: combine background music, voiceover, sound effects
|
|
19
|
+
- **volume control**: fade in/out, dynamic volume adjustment
|
|
20
|
+
- **layering**: overlay graphics, text, images on video
|
|
21
|
+
|
|
22
|
+
### beautiful subtitles
|
|
23
|
+
- **word-by-word captions**: sync text with audio timing
|
|
24
|
+
- **custom styling**: fonts, colors, backgrounds, borders
|
|
25
|
+
- **animations**: fade in/out, slide up, bounce effects
|
|
26
|
+
- **positioning**: center, bottom, top, custom placement
|
|
27
|
+
- **karaoke mode**: highlight current word
|
|
28
|
+
- **rich formatting**: bold, italic, emoji, multi-line
|
|
29
|
+
|
|
30
|
+
### thumbnail generation
|
|
31
|
+
- **specific frame render**: capture any frame as still image using renderStill
|
|
32
|
+
- **custom thumbnails**: create compositions with overlaid text/graphics
|
|
33
|
+
- **multiple previews**: render several frames at different timestamps
|
|
34
|
+
- **branded thumbnails**: add logos, titles, watermarks to frame captures
|
|
35
|
+
|
|
36
|
+
### advanced capabilities
|
|
37
|
+
- **motion graphics**: animated text, shapes, data visualizations
|
|
38
|
+
- **responsive layouts**: adapt to different aspect ratios
|
|
39
|
+
- **programmatic content**: generate videos from data/templates
|
|
40
|
+
- **frame-perfect timing**: precise control down to single frame
|
|
41
|
+
- **react ecosystem**: use any react library (charts, animations, etc)
|
|
42
|
+
|
|
43
|
+
## when to use
|
|
44
|
+
- need precise control over video composition
|
|
45
|
+
- want to add dynamic captions/subtitles
|
|
46
|
+
- combining multiple videos with effects
|
|
47
|
+
- creating videos from templates
|
|
48
|
+
- need frame-perfect synchronization
|
|
49
|
+
- editing videos programmatically
|
|
50
|
+
- generating thumbnails and previews
|
|
51
|
+
- creating social media content at scale
|
|
52
|
+
|
|
53
|
+
## prerequisites
|
|
54
|
+
```bash
|
|
55
|
+
bun install remotion @remotion/cli
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## pipeline steps
|
|
59
|
+
|
|
60
|
+
### step 1: analyze source media
|
|
61
|
+
probe all input videos to get metadata
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
bun run lib/ffmpeg.ts probe media/video1.mp4
|
|
65
|
+
bun run lib/ffmpeg.ts probe media/video2.mp4
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**capture:**
|
|
69
|
+
- duration (seconds)
|
|
70
|
+
- fps (frames per second)
|
|
71
|
+
- resolution (width x height)
|
|
72
|
+
- codec
|
|
73
|
+
|
|
74
|
+
**calculate:**
|
|
75
|
+
- total frames = duration * fps
|
|
76
|
+
- end frame for concatenation
|
|
77
|
+
|
|
78
|
+
### step 2: setup composition
|
|
79
|
+
```bash
|
|
80
|
+
bun run lib/remotion/index.ts create MyVideo
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**output:**
|
|
84
|
+
- compositionPath: lib/remotion/compositions/MyVideo.tsx
|
|
85
|
+
- rootPath: lib/remotion/compositions/MyVideo.root.tsx
|
|
86
|
+
- compositionsDir: lib/remotion/compositions/
|
|
87
|
+
|
|
88
|
+
### step 3: create composition component
|
|
89
|
+
edit generated file `lib/remotion/compositions/MyVideo.tsx`
|
|
90
|
+
|
|
91
|
+
**media files:**
|
|
92
|
+
- copy media to `lib/remotion/public/` directory
|
|
93
|
+
- use `staticFile("filename.ext")` to reference files
|
|
94
|
+
- never use absolute paths in compositions
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
mkdir -p lib/remotion/public
|
|
98
|
+
cp media/video.mp4 media/audio.mp3 lib/remotion/public/
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
if using subtitles, copy SRT and read:
|
|
102
|
+
```bash
|
|
103
|
+
cp media/subtitles.srt lib/remotion/public/
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { staticFile } from "remotion";
|
|
108
|
+
const srtContent = await Bun.file(staticFile("subtitles.srt")).text();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**basic structure:**
|
|
112
|
+
```typescript
|
|
113
|
+
import { AbsoluteFill, OffthreadVideo, useCurrentFrame, useVideoConfig, staticFile } from "remotion";
|
|
114
|
+
|
|
115
|
+
export const MyComposition: React.FC = () => {
|
|
116
|
+
const frame = useCurrentFrame();
|
|
117
|
+
const { fps } = useVideoConfig();
|
|
118
|
+
|
|
119
|
+
const video = staticFile("video1.mp4");
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<AbsoluteFill>
|
|
123
|
+
<OffthreadVideo src={video} />
|
|
124
|
+
</AbsoluteFill>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**for captions:**
|
|
130
|
+
```typescript
|
|
131
|
+
// parse SRT at top of file
|
|
132
|
+
const subtitles = parseSRT(`1
|
|
133
|
+
00:00:00,231 --> 00:00:00,491
|
|
134
|
+
Hello
|
|
135
|
+
|
|
136
|
+
2
|
|
137
|
+
00:00:00,491 --> 00:00:00,651
|
|
138
|
+
World
|
|
139
|
+
`);
|
|
140
|
+
|
|
141
|
+
// in component
|
|
142
|
+
const currentTime = frame / fps;
|
|
143
|
+
const currentSubtitle = subtitles.find(
|
|
144
|
+
sub => currentTime >= sub.startTime && currentTime <= sub.endTime
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
{currentSubtitle && (
|
|
148
|
+
<div style={{
|
|
149
|
+
fontFamily: "Inter",
|
|
150
|
+
fontSize: 48,
|
|
151
|
+
fontWeight: "bold",
|
|
152
|
+
color: "white",
|
|
153
|
+
backgroundColor: "rgba(0,0,0,0.7)",
|
|
154
|
+
padding: "20px 40px",
|
|
155
|
+
borderRadius: 12,
|
|
156
|
+
}}>
|
|
157
|
+
{currentSubtitle.text}
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**for concatenation:**
|
|
163
|
+
```typescript
|
|
164
|
+
const video1 = staticFile("video1.mp4");
|
|
165
|
+
const video2 = staticFile("video2.mp4");
|
|
166
|
+
|
|
167
|
+
const video1EndFrame = 1430; // calculated from probe
|
|
168
|
+
const video2StartFrame = video1EndFrame;
|
|
169
|
+
|
|
170
|
+
{frame < video1EndFrame ? (
|
|
171
|
+
<OffthreadVideo src={video1} />
|
|
172
|
+
) : (
|
|
173
|
+
<OffthreadVideo
|
|
174
|
+
src={video2}
|
|
175
|
+
startFrom={Math.floor((frame - video2StartFrame) * (24/30))}
|
|
176
|
+
/>
|
|
177
|
+
)}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### step 4: register composition
|
|
181
|
+
edit generated file `lib/remotion/compositions/MyVideo.root.tsx`
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import React from "react";
|
|
185
|
+
import { Composition, registerRoot } from "remotion";
|
|
186
|
+
import { MyComposition } from "./MyVideo";
|
|
187
|
+
|
|
188
|
+
const fps = 30;
|
|
189
|
+
const durationInFrames = 1582; // total frames
|
|
190
|
+
const width = 1920;
|
|
191
|
+
const height = 1080;
|
|
192
|
+
|
|
193
|
+
registerRoot(() => {
|
|
194
|
+
return (
|
|
195
|
+
<>
|
|
196
|
+
<Composition
|
|
197
|
+
id="MyVideo"
|
|
198
|
+
component={MyComposition}
|
|
199
|
+
durationInFrames={durationInFrames}
|
|
200
|
+
fps={fps}
|
|
201
|
+
width={width}
|
|
202
|
+
height={height}
|
|
203
|
+
/>
|
|
204
|
+
</>
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**calculate durationInFrames:**
|
|
210
|
+
- single video: `duration * fps`
|
|
211
|
+
- concatenated: `video1Frames + video2Frames`
|
|
212
|
+
- use fps from probe results
|
|
213
|
+
|
|
214
|
+
### step 5: verify compositions
|
|
215
|
+
```bash
|
|
216
|
+
bun run lib/remotion/index.ts compositions lib/remotion/compositions/MyVideo.root.tsx
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**check output:**
|
|
220
|
+
- composition id matches
|
|
221
|
+
- dimensions are correct
|
|
222
|
+
- fps is correct
|
|
223
|
+
- durationInFrames is correct
|
|
224
|
+
|
|
225
|
+
### step 6: render video
|
|
226
|
+
```bash
|
|
227
|
+
bun run lib/remotion/index.ts render lib/remotion/compositions/MyVideo.root.tsx MyVideo media/output.mp4
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**rendering process:**
|
|
231
|
+
1. bundles project with webpack
|
|
232
|
+
2. launches chrome headless
|
|
233
|
+
3. renders each frame
|
|
234
|
+
4. encodes with ffmpeg (h264 codec)
|
|
235
|
+
5. saves to output.mp4
|
|
236
|
+
|
|
237
|
+
**monitor:**
|
|
238
|
+
- progress percentage
|
|
239
|
+
- frames rendered
|
|
240
|
+
- frames encoded
|
|
241
|
+
|
|
242
|
+
### step 7: verify output
|
|
243
|
+
```bash
|
|
244
|
+
bun run lib/ffmpeg.ts probe media/output.mp4
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
check:
|
|
248
|
+
- duration matches expected
|
|
249
|
+
- fps is correct
|
|
250
|
+
- resolution is correct
|
|
251
|
+
|
|
252
|
+
## common workflows
|
|
253
|
+
|
|
254
|
+
### workflow 1: video + captions
|
|
255
|
+
```
|
|
256
|
+
1. probe video → get duration, fps
|
|
257
|
+
2. setup composition
|
|
258
|
+
3. create composition with Video + captions
|
|
259
|
+
4. parse SRT file into subtitle array
|
|
260
|
+
5. sync captions with useCurrentFrame()
|
|
261
|
+
6. register composition in root
|
|
262
|
+
7. render
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### workflow 2: concatenate videos
|
|
266
|
+
```
|
|
267
|
+
1. probe all videos → get durations, fps
|
|
268
|
+
2. calculate frame boundaries
|
|
269
|
+
3. setup composition
|
|
270
|
+
4. create composition with frame-based switching
|
|
271
|
+
5. use absolute paths to media files
|
|
272
|
+
6. handle fps conversions if needed
|
|
273
|
+
7. register composition with total duration
|
|
274
|
+
8. render
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### workflow 3: video with overlay graphics
|
|
278
|
+
```
|
|
279
|
+
1. probe video
|
|
280
|
+
2. setup composition
|
|
281
|
+
3. copy video + images to lib/remotion/public/
|
|
282
|
+
4. create composition with layers using staticFile()
|
|
283
|
+
5. use interpolate() for animations
|
|
284
|
+
6. position overlays with AbsoluteFill
|
|
285
|
+
7. register composition with registerRoot()
|
|
286
|
+
8. render
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### workflow 4: render custom thumbnail
|
|
290
|
+
```
|
|
291
|
+
1. setup composition
|
|
292
|
+
2. copy video to lib/remotion/public/
|
|
293
|
+
3. create thumbnail composition with OffthreadVideo + overlays
|
|
294
|
+
4. use staticFile() for video path
|
|
295
|
+
5. add text, logos, graphics on top
|
|
296
|
+
6. register composition with registerRoot()
|
|
297
|
+
7. render specific frame as png/jpg using renderStill
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### workflow 5: zoom & pan effect
|
|
301
|
+
```
|
|
302
|
+
1. probe video or image
|
|
303
|
+
2. setup composition
|
|
304
|
+
3. copy media to lib/remotion/public/
|
|
305
|
+
4. create composition with transform animations
|
|
306
|
+
5. use staticFile() for media paths
|
|
307
|
+
6. use interpolate() for scale and translate
|
|
308
|
+
7. set easing for smooth motion
|
|
309
|
+
8. register composition with registerRoot()
|
|
310
|
+
9. render
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### workflow 6: multi-track audio/video
|
|
314
|
+
```
|
|
315
|
+
1. probe all media files
|
|
316
|
+
2. setup composition
|
|
317
|
+
3. copy videos + audio files to lib/remotion/public/
|
|
318
|
+
4. create composition with multiple OffthreadVideo/Audio components
|
|
319
|
+
5. use staticFile() for all media paths
|
|
320
|
+
6. adjust volume levels with interpolate()
|
|
321
|
+
7. sync timing using frame calculations
|
|
322
|
+
8. register composition with registerRoot()
|
|
323
|
+
9. render
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## calculation formulas
|
|
327
|
+
|
|
328
|
+
### frames to seconds
|
|
329
|
+
```typescript
|
|
330
|
+
const seconds = frames / fps;
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### seconds to frames
|
|
334
|
+
```typescript
|
|
335
|
+
const frames = Math.floor(seconds * fps);
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### fps conversion
|
|
339
|
+
```typescript
|
|
340
|
+
// video is 24fps, composition is 30fps
|
|
341
|
+
const adjustedFrame = Math.floor(frame * (24 / 30));
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### srt time to seconds
|
|
345
|
+
```typescript
|
|
346
|
+
function parseTime(time: string) {
|
|
347
|
+
const [hours, minutes, rest] = time.split(":");
|
|
348
|
+
const [seconds, ms] = rest.split(",");
|
|
349
|
+
return (
|
|
350
|
+
parseInt(hours) * 3600 +
|
|
351
|
+
parseInt(minutes) * 60 +
|
|
352
|
+
parseInt(seconds) +
|
|
353
|
+
parseInt(ms) / 1000
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## tips & tricks
|
|
359
|
+
|
|
360
|
+
### caption styling
|
|
361
|
+
```typescript
|
|
362
|
+
// word-by-word bold captions
|
|
363
|
+
{
|
|
364
|
+
fontFamily: "Inter",
|
|
365
|
+
fontSize: 48,
|
|
366
|
+
fontWeight: "bold",
|
|
367
|
+
color: "white",
|
|
368
|
+
textAlign: "center",
|
|
369
|
+
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
|
370
|
+
padding: "20px 40px",
|
|
371
|
+
borderRadius: 12,
|
|
372
|
+
textTransform: "uppercase",
|
|
373
|
+
letterSpacing: 2,
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### smooth transitions
|
|
378
|
+
```typescript
|
|
379
|
+
import { interpolate } from "remotion";
|
|
380
|
+
|
|
381
|
+
const opacity = interpolate(
|
|
382
|
+
frame,
|
|
383
|
+
[0, 30, 60],
|
|
384
|
+
[0, 1, 0],
|
|
385
|
+
{ extrapolateRight: "clamp" }
|
|
386
|
+
);
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### loading fonts
|
|
390
|
+
```typescript
|
|
391
|
+
import { loadFont } from "@remotion/google-fonts/Inter";
|
|
392
|
+
const { fontFamily } = loadFont();
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### responsive layouts
|
|
396
|
+
```typescript
|
|
397
|
+
const { width, height } = useVideoConfig();
|
|
398
|
+
const scale = Math.min(width / 1920, height / 1080);
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## troubleshooting
|
|
402
|
+
|
|
403
|
+
### issue: video not loading
|
|
404
|
+
**solution:**
|
|
405
|
+
- copy media files to `lib/remotion/public/`
|
|
406
|
+
- use `staticFile("filename.ext")` not absolute paths
|
|
407
|
+
- verify file exists in public directory
|
|
408
|
+
- check file permissions
|
|
409
|
+
|
|
410
|
+
### issue: captions out of sync
|
|
411
|
+
**solution:**
|
|
412
|
+
- verify SRT timestamps
|
|
413
|
+
- check fps matches video fps
|
|
414
|
+
- log currentTime to debug: `console.log(frame / fps)`
|
|
415
|
+
|
|
416
|
+
### issue: wrong video duration
|
|
417
|
+
**solution:**
|
|
418
|
+
- probe video to get exact duration
|
|
419
|
+
- calculate frames correctly: `duration * fps`
|
|
420
|
+
- round to nearest frame
|
|
421
|
+
|
|
422
|
+
### issue: concatenation glitch
|
|
423
|
+
**solution:**
|
|
424
|
+
- calculate exact end frame of first video
|
|
425
|
+
- adjust `startFrom` for fps differences
|
|
426
|
+
- use `Math.floor()` for frame calculations
|
|
427
|
+
|
|
428
|
+
### issue: render fails
|
|
429
|
+
**solution:**
|
|
430
|
+
- verify all media files exist in `lib/remotion/public/`
|
|
431
|
+
- check composition is registered with `registerRoot()`
|
|
432
|
+
- ensure durationInFrames is sufficient
|
|
433
|
+
- verify all media paths use `staticFile()`
|
|
434
|
+
- check console for webpack errors
|
|
435
|
+
|
|
436
|
+
## performance optimization
|
|
437
|
+
|
|
438
|
+
### use OffthreadVideo
|
|
439
|
+
for better performance when concatenating:
|
|
440
|
+
```typescript
|
|
441
|
+
<OffthreadVideo src={staticFile("video.mp4")} />
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### limit font requests
|
|
445
|
+
```typescript
|
|
446
|
+
loadFont({
|
|
447
|
+
weights: ["400", "700"], // only needed weights
|
|
448
|
+
subsets: ["latin"], // only needed subsets
|
|
449
|
+
});
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### optimize media files
|
|
453
|
+
```bash
|
|
454
|
+
# compress video before using
|
|
455
|
+
bun run lib/ffmpeg.ts convert input.mp4 output.mp4
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
## example workflows
|
|
459
|
+
|
|
460
|
+
### example 1: video with captions and concatenation
|
|
461
|
+
```bash
|
|
462
|
+
# 1. probe videos
|
|
463
|
+
bun run lib/ffmpeg.ts probe media/fitness-demo.mp4
|
|
464
|
+
# output: 360x640 @ 30fps, 47.67s
|
|
465
|
+
|
|
466
|
+
bun run lib/ffmpeg.ts probe media/kangaroo-scene.mp4
|
|
467
|
+
# output: 1920x1080 @ 24fps, 5.04s
|
|
468
|
+
|
|
469
|
+
# 2. setup composition
|
|
470
|
+
bun run lib/remotion/index.ts create Demo
|
|
471
|
+
|
|
472
|
+
# 3. create composition files
|
|
473
|
+
# edit lib/remotion/compositions/Demo.tsx
|
|
474
|
+
# edit lib/remotion/compositions/Demo.root.tsx
|
|
475
|
+
|
|
476
|
+
# 4. verify
|
|
477
|
+
bun run lib/remotion/index.ts compositions lib/remotion/compositions/Demo.root.tsx
|
|
478
|
+
# Demo: 360x640 @ 30fps (1582 frames)
|
|
479
|
+
|
|
480
|
+
# 5. render
|
|
481
|
+
bun run lib/remotion/index.ts render lib/remotion/compositions/Demo.root.tsx Demo media/output.mp4
|
|
482
|
+
# [remotion] progress: 100.0% | rendered: 1582 | encoded: 1582
|
|
483
|
+
# [remotion] saved to media/output.mp4
|
|
484
|
+
|
|
485
|
+
# 6. verify output
|
|
486
|
+
bun run lib/ffmpeg.ts probe media/output.mp4
|
|
487
|
+
# 360x640 @ 30fps, 52.73s
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### example 2: custom thumbnail with overlay
|
|
491
|
+
```bash
|
|
492
|
+
# 1. setup composition
|
|
493
|
+
bun run lib/remotion/index.ts create Thumbnail
|
|
494
|
+
|
|
495
|
+
# 2. copy media
|
|
496
|
+
cp media/video.mp4 lib/remotion/public/
|
|
497
|
+
|
|
498
|
+
# 3. edit thumbnail composition lib/remotion/compositions/Thumbnail.tsx
|
|
499
|
+
# import { AbsoluteFill, OffthreadVideo, staticFile } from "remotion";
|
|
500
|
+
# export const Thumbnail = () => {
|
|
501
|
+
# const video = staticFile("video.mp4");
|
|
502
|
+
# return (
|
|
503
|
+
# <AbsoluteFill>
|
|
504
|
+
# <OffthreadVideo src={video} />
|
|
505
|
+
# <div style={{
|
|
506
|
+
# position: "absolute",
|
|
507
|
+
# bottom: 50,
|
|
508
|
+
# left: 50,
|
|
509
|
+
# fontSize: 72,
|
|
510
|
+
# fontWeight: "bold",
|
|
511
|
+
# color: "white",
|
|
512
|
+
# textShadow: "4px 4px 8px black"
|
|
513
|
+
# }}>
|
|
514
|
+
# MY VIDEO TITLE
|
|
515
|
+
# </div>
|
|
516
|
+
# </AbsoluteFill>
|
|
517
|
+
# );
|
|
518
|
+
# };
|
|
519
|
+
|
|
520
|
+
# 4. edit root with registerRoot() lib/remotion/compositions/Thumbnail.root.tsx
|
|
521
|
+
|
|
522
|
+
# 5. render frame 90 (3 seconds in @ 30fps)
|
|
523
|
+
bun run lib/remotion/index.ts still lib/remotion/compositions/Thumbnail.root.tsx Thumbnail 90 media/thumbnail.png
|
|
524
|
+
# [remotion] saved to media/thumbnail.png
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### example 3: zoom effect with audio
|
|
528
|
+
```bash
|
|
529
|
+
# 1. setup composition
|
|
530
|
+
bun run lib/remotion/index.ts create Zoom
|
|
531
|
+
|
|
532
|
+
# 2. copy media
|
|
533
|
+
cp media/image.jpg media/music.mp3 lib/remotion/public/
|
|
534
|
+
|
|
535
|
+
# 3. edit composition lib/remotion/compositions/Zoom.tsx
|
|
536
|
+
# import { Img, Audio, staticFile, interpolate, useCurrentFrame } from "remotion";
|
|
537
|
+
# const frame = useCurrentFrame();
|
|
538
|
+
# const image = staticFile("image.jpg");
|
|
539
|
+
# const audio = staticFile("music.mp3");
|
|
540
|
+
# const scale = interpolate(frame, [0, 150], [1, 1.5]);
|
|
541
|
+
# <div style={{ transform: `scale(${scale})` }}>
|
|
542
|
+
# <Img src={image} />
|
|
543
|
+
# </div>
|
|
544
|
+
# <Audio src={audio} />
|
|
545
|
+
|
|
546
|
+
# 4. edit root with registerRoot() and durationInFrames: 150 (5 seconds @ 30fps)
|
|
547
|
+
|
|
548
|
+
# 5. render
|
|
549
|
+
bun run lib/remotion/index.ts render lib/remotion/compositions/Zoom.root.tsx Zoom media/zoomed.mp4
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### example 4: side-by-side comparison
|
|
553
|
+
```bash
|
|
554
|
+
# 1. probe videos
|
|
555
|
+
bun run lib/ffmpeg.ts probe media/before.mp4
|
|
556
|
+
bun run lib/ffmpeg.ts probe media/after.mp4
|
|
557
|
+
|
|
558
|
+
# 2. setup composition
|
|
559
|
+
bun run lib/remotion/index.ts create Comparison
|
|
560
|
+
|
|
561
|
+
# 3. copy media
|
|
562
|
+
cp media/before.mp4 media/after.mp4 lib/remotion/public/
|
|
563
|
+
|
|
564
|
+
# 4. edit composition lib/remotion/compositions/Comparison.tsx
|
|
565
|
+
# import { AbsoluteFill, OffthreadVideo, staticFile } from "remotion";
|
|
566
|
+
# const before = staticFile("before.mp4");
|
|
567
|
+
# const after = staticFile("after.mp4");
|
|
568
|
+
# <AbsoluteFill style={{ width: "50%", left: 0 }}>
|
|
569
|
+
# <OffthreadVideo src={before} />
|
|
570
|
+
# </AbsoluteFill>
|
|
571
|
+
# <AbsoluteFill style={{ width: "50%", left: "50%" }}>
|
|
572
|
+
# <OffthreadVideo src={after} />
|
|
573
|
+
# </AbsoluteFill>
|
|
574
|
+
|
|
575
|
+
# 5. edit root with registerRoot()
|
|
576
|
+
|
|
577
|
+
# 6. render
|
|
578
|
+
bun run lib/remotion/index.ts render lib/remotion/compositions/Comparison.root.tsx Comparison media/comparison.mp4
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
## see also
|
|
582
|
+
- lib/remotion/SKILL.md - detailed remotion skill reference
|
|
583
|
+
- lib/ffmpeg.ts - video probing and editing
|
|
584
|
+
- service/captions/ - automated caption generation
|
|
585
|
+
- service/edit/ - video editing workflows
|