@editframe/create 0.44.0 → 0.45.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/dist/index.js +16 -28
- package/dist/index.js.map +1 -1
- package/dist/skills/editframe-brand-video-generator/README.md +155 -0
- package/dist/skills/editframe-brand-video-generator/SKILL.md +207 -0
- package/dist/skills/editframe-brand-video-generator/references/brand-examples.md +178 -0
- package/dist/skills/editframe-brand-video-generator/references/color-psychology.md +227 -0
- package/dist/skills/editframe-brand-video-generator/references/composition-patterns.md +383 -0
- package/dist/skills/editframe-brand-video-generator/references/editing.md +66 -0
- package/dist/skills/editframe-brand-video-generator/references/emotional-arcs.md +496 -0
- package/dist/skills/editframe-brand-video-generator/references/genre-selection.md +135 -0
- package/dist/skills/editframe-brand-video-generator/references/transition-styles.md +611 -0
- package/dist/skills/editframe-brand-video-generator/references/typography-personalities.md +326 -0
- package/dist/skills/editframe-brand-video-generator/references/video-archetypes.md +86 -0
- package/dist/skills/editframe-brand-video-generator/references/video-fundamentals.md +169 -0
- package/dist/skills/editframe-brand-video-generator/references/visual-metaphors.md +50 -0
- package/dist/skills/editframe-composition/SKILL.md +169 -0
- package/dist/skills/editframe-composition/references/audio.md +483 -0
- package/dist/skills/editframe-composition/references/captions.md +844 -0
- package/dist/skills/editframe-composition/references/composition-model.md +73 -0
- package/dist/skills/editframe-composition/references/configuration.md +403 -0
- package/dist/skills/editframe-composition/references/css-parts.md +105 -0
- package/dist/skills/editframe-composition/references/css-variables.md +640 -0
- package/dist/skills/editframe-composition/references/entry-points.md +810 -0
- package/dist/skills/editframe-composition/references/events.md +499 -0
- package/dist/skills/editframe-composition/references/getting-started.md +259 -0
- package/dist/skills/editframe-composition/references/hooks.md +234 -0
- package/dist/skills/editframe-composition/references/image.md +241 -0
- package/dist/skills/editframe-composition/references/r3f.md +580 -0
- package/dist/skills/editframe-composition/references/render-api.md +484 -0
- package/dist/skills/editframe-composition/references/render-strategies.md +119 -0
- package/dist/skills/editframe-composition/references/render-to-video.md +1101 -0
- package/dist/skills/editframe-composition/references/scripting.md +606 -0
- package/dist/skills/editframe-composition/references/sequencing.md +116 -0
- package/dist/skills/editframe-composition/references/server-rendering.md +753 -0
- package/dist/skills/editframe-composition/references/surface.md +329 -0
- package/dist/skills/editframe-composition/references/text.md +627 -0
- package/dist/skills/editframe-composition/references/time-model.md +99 -0
- package/dist/skills/editframe-composition/references/timegroup-modes.md +102 -0
- package/dist/skills/editframe-composition/references/timegroup.md +457 -0
- package/dist/skills/editframe-composition/references/timeline-root.md +398 -0
- package/dist/skills/editframe-composition/references/transcription.md +47 -0
- package/dist/skills/editframe-composition/references/transitions.md +608 -0
- package/dist/skills/editframe-composition/references/use-media-info.md +357 -0
- package/dist/skills/editframe-composition/references/video.md +506 -0
- package/dist/skills/editframe-composition/references/waveform.md +327 -0
- package/dist/skills/editframe-editor-gui/SKILL.md +152 -0
- package/dist/skills/editframe-editor-gui/references/active-root-temporal.md +657 -0
- package/dist/skills/editframe-editor-gui/references/canvas.md +947 -0
- package/dist/skills/editframe-editor-gui/references/controls.md +366 -0
- package/dist/skills/editframe-editor-gui/references/dial.md +756 -0
- package/dist/skills/editframe-editor-gui/references/editor-toolkit.md +587 -0
- package/dist/skills/editframe-editor-gui/references/filmstrip.md +460 -0
- package/dist/skills/editframe-editor-gui/references/fit-scale.md +772 -0
- package/dist/skills/editframe-editor-gui/references/focus-overlay.md +561 -0
- package/dist/skills/editframe-editor-gui/references/hierarchy.md +544 -0
- package/dist/skills/editframe-editor-gui/references/overlay-item.md +634 -0
- package/dist/skills/editframe-editor-gui/references/overlay-layer.md +429 -0
- package/dist/skills/editframe-editor-gui/references/pan-zoom.md +568 -0
- package/dist/skills/editframe-editor-gui/references/pause.md +397 -0
- package/dist/skills/editframe-editor-gui/references/play.md +370 -0
- package/dist/skills/editframe-editor-gui/references/preview.md +391 -0
- package/dist/skills/editframe-editor-gui/references/resizable-box.md +749 -0
- package/dist/skills/editframe-editor-gui/references/scrubber.md +588 -0
- package/dist/skills/editframe-editor-gui/references/thumbnail-strip.md +566 -0
- package/dist/skills/editframe-editor-gui/references/time-display.md +492 -0
- package/dist/skills/editframe-editor-gui/references/timeline-ruler.md +489 -0
- package/dist/skills/editframe-editor-gui/references/timeline.md +604 -0
- package/dist/skills/editframe-editor-gui/references/toggle-loop.md +618 -0
- package/dist/skills/editframe-editor-gui/references/toggle-play.md +526 -0
- package/dist/skills/editframe-editor-gui/references/transform-handles.md +924 -0
- package/dist/skills/editframe-editor-gui/references/trim-handles.md +725 -0
- package/dist/skills/editframe-editor-gui/references/workbench.md +453 -0
- package/dist/skills/editframe-motion-design/SKILL.md +101 -0
- package/dist/skills/editframe-motion-design/references/0-editframe.md +299 -0
- package/dist/skills/editframe-motion-design/references/1-intent.md +201 -0
- package/dist/skills/editframe-motion-design/references/2-physics-model.md +405 -0
- package/dist/skills/editframe-motion-design/references/3-attention.md +350 -0
- package/dist/skills/editframe-motion-design/references/4-process.md +418 -0
- package/dist/skills/editframe-vite-plugin/SKILL.md +75 -0
- package/dist/skills/editframe-vite-plugin/references/file-api.md +111 -0
- package/dist/skills/editframe-vite-plugin/references/getting-started.md +96 -0
- package/dist/skills/editframe-vite-plugin/references/jit-transcoding.md +91 -0
- package/dist/skills/editframe-vite-plugin/references/local-assets.md +75 -0
- package/dist/skills/editframe-vite-plugin/references/visual-testing.md +136 -0
- package/dist/skills/editframe-webhooks/SKILL.md +126 -0
- package/dist/skills/editframe-webhooks/references/events.md +382 -0
- package/dist/skills/editframe-webhooks/references/getting-started.md +232 -0
- package/dist/skills/editframe-webhooks/references/security.md +418 -0
- package/dist/skills/editframe-webhooks/references/testing.md +409 -0
- package/dist/skills/editframe-webhooks/references/troubleshooting.md +457 -0
- package/dist/templates/html/AGENTS.md +13 -0
- package/dist/templates/react/AGENTS.md +13 -0
- package/dist/utils.js +15 -16
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
- package/tsdown.config.ts +4 -0
- package/dist/detectAgent.js +0 -89
- package/dist/detectAgent.js.map +0 -1
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Render API
|
|
3
|
+
description: Export compositions to video in the browser or via CLI with full control over resolution, bitrate, and encoding options.
|
|
4
|
+
type: reference
|
|
5
|
+
nav:
|
|
6
|
+
parent: "Rendering"
|
|
7
|
+
priority: 11
|
|
8
|
+
api:
|
|
9
|
+
methods:
|
|
10
|
+
- name: renderToVideo()
|
|
11
|
+
signature: renderToVideo(options?)
|
|
12
|
+
description: Export timegroup to MP4 video with WebCodecs
|
|
13
|
+
returns: Promise<Uint8Array | undefined>
|
|
14
|
+
- name: createRenderClone()
|
|
15
|
+
signature: createRenderClone()
|
|
16
|
+
description: Create off-DOM clone for rendering without affecting preview
|
|
17
|
+
returns: Promise<RenderCloneResult>
|
|
18
|
+
- name: getRenderData()
|
|
19
|
+
signature: getRenderData<T>()
|
|
20
|
+
description: Access custom data passed from CLI at render time
|
|
21
|
+
returns: T | undefined
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# Render API
|
|
25
|
+
|
|
26
|
+
Export compositions to MP4 video using browser-based WebCodecs or server-side rendering via CLI.
|
|
27
|
+
|
|
28
|
+
## Browser Export with renderToVideo()
|
|
29
|
+
|
|
30
|
+
Export videos directly in the browser using the WebCodecs API. Best for client-side applications and interactive exports.
|
|
31
|
+
|
|
32
|
+
### Basic Usage
|
|
33
|
+
|
|
34
|
+
```html live
|
|
35
|
+
<ef-timegroup mode="fixed" duration="3s" class="w-[720px] h-[480px] bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center">
|
|
36
|
+
<ef-text duration="3s" class="text-white text-6xl font-bold">Hello Video!</ef-text>
|
|
37
|
+
</ef-timegroup>
|
|
38
|
+
|
|
39
|
+
<button id="exportBtn" class="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
|
|
40
|
+
Export Video
|
|
41
|
+
</button>
|
|
42
|
+
|
|
43
|
+
<div id="progress" class="mt-4 hidden">
|
|
44
|
+
<div class="text-sm text-gray-300 mb-2">
|
|
45
|
+
<span id="progressText">Rendering...</span>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="w-full bg-gray-700 rounded-full h-2">
|
|
48
|
+
<div id="progressBar" class="bg-blue-600 h-2 rounded-full transition-all" style="width: 0%"></div>
|
|
49
|
+
</div>
|
|
50
|
+
<canvas id="preview" class="mt-4 border border-gray-600" style="width: 360px; height: 240px;"></canvas>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<script type="module">
|
|
54
|
+
const timegroup = document.querySelector('ef-timegroup');
|
|
55
|
+
const exportBtn = document.getElementById('exportBtn');
|
|
56
|
+
const progressDiv = document.getElementById('progress');
|
|
57
|
+
const progressBar = document.getElementById('progressBar');
|
|
58
|
+
const progressText = document.getElementById('progressText');
|
|
59
|
+
const previewCanvas = document.getElementById('preview');
|
|
60
|
+
|
|
61
|
+
exportBtn.addEventListener('click', async () => {
|
|
62
|
+
exportBtn.disabled = true;
|
|
63
|
+
progressDiv.classList.remove('hidden');
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
await timegroup.renderToVideo({
|
|
67
|
+
fps: 30,
|
|
68
|
+
codec: 'avc',
|
|
69
|
+
filename: 'my-video.mp4',
|
|
70
|
+
onProgress: (progress) => {
|
|
71
|
+
const percent = Math.round(progress.progress * 100);
|
|
72
|
+
progressBar.style.width = `${percent}%`;
|
|
73
|
+
progressText.textContent = `Rendering frame ${progress.currentFrame}/${progress.totalFrames} (${percent}%)`;
|
|
74
|
+
|
|
75
|
+
// Show live preview of current frame
|
|
76
|
+
if (progress.framePreviewCanvas) {
|
|
77
|
+
const ctx = previewCanvas.getContext('2d');
|
|
78
|
+
previewCanvas.width = progress.framePreviewCanvas.width;
|
|
79
|
+
previewCanvas.height = progress.framePreviewCanvas.height;
|
|
80
|
+
ctx.drawImage(progress.framePreviewCanvas, 0, 0);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
progressText.textContent = 'Export complete! Video downloaded.';
|
|
86
|
+
exportBtn.disabled = false;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
progressText.textContent = `Export failed: ${error.message}`;
|
|
89
|
+
exportBtn.disabled = false;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
</script>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### RenderToVideoOptions
|
|
96
|
+
|
|
97
|
+
All options for `renderToVideo()`:
|
|
98
|
+
|
|
99
|
+
| Option | Type | Default | Description |
|
|
100
|
+
|--------|------|---------|-------------|
|
|
101
|
+
| `fps` | `number` | `30` | Frame rate (frames per second) |
|
|
102
|
+
| `codec` | `string` | `"avc"` | Video codec: `"avc"`, `"hevc"`, `"vp9"`, `"av1"`, `"vp8"` |
|
|
103
|
+
| `bitrate` | `number` | `5_000_000` | Video bitrate in bits per second |
|
|
104
|
+
| `filename` | `string` | `"video.mp4"` | Download filename |
|
|
105
|
+
| `scale` | `number` | `1` | Rendering scale multiplier (2 = 2x resolution) |
|
|
106
|
+
| `keyFrameInterval` | `number` | `150` | Frames between keyframes |
|
|
107
|
+
| `fromMs` | `number` | `0` | Start time in milliseconds |
|
|
108
|
+
| `toMs` | `number` | `duration` | End time in milliseconds |
|
|
109
|
+
| `onProgress` | `function` | - | Progress callback (see below) |
|
|
110
|
+
| `streaming` | `boolean` | `false` | Stream output using File System Access API |
|
|
111
|
+
| `signal` | `AbortSignal` | - | AbortSignal to cancel render |
|
|
112
|
+
| `includeAudio` | `boolean` | `true` | Include audio tracks in output |
|
|
113
|
+
| `audioBitrate` | `number` | `128_000` | Audio bitrate in bits per second |
|
|
114
|
+
| `contentReadyMode` | `string` | `"immediate"` | `"immediate"` or `"blocking"` for video readiness |
|
|
115
|
+
| `blockingTimeoutMs` | `number` | `5000` | Timeout for blocking video loads |
|
|
116
|
+
| `returnBuffer` | `boolean` | `false` | Return Uint8Array instead of downloading |
|
|
117
|
+
| `preferredAudioCodecs` | `array` | `["opus", "aac"]` | Preferred audio codecs in order |
|
|
118
|
+
| `benchmarkMode` | `boolean` | `false` | Skip encoding for performance testing |
|
|
119
|
+
| `customWritableStream` | `WritableStream` | - | Custom output stream for programmatic control |
|
|
120
|
+
| `progressPreviewInterval` | `number` | `10` | Frames between preview updates |
|
|
121
|
+
| `canvasMode` | `string` | `"foreignObject"` | `"native"` or `"foreignObject"` rendering |
|
|
122
|
+
|
|
123
|
+
### Progress Callback
|
|
124
|
+
|
|
125
|
+
The `onProgress` callback receives a `RenderProgress` object with detailed information:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
interface RenderProgress {
|
|
129
|
+
progress: number; // 0.0 to 1.0
|
|
130
|
+
currentFrame: number; // Current frame index
|
|
131
|
+
totalFrames: number; // Total frames to render
|
|
132
|
+
renderedMs: number; // Milliseconds rendered so far
|
|
133
|
+
totalDurationMs: number; // Total video duration
|
|
134
|
+
elapsedMs: number; // Real time elapsed
|
|
135
|
+
estimatedRemainingMs: number; // Estimated time remaining
|
|
136
|
+
speedMultiplier: number; // Render speed (2.0 = 2x real-time)
|
|
137
|
+
framePreviewCanvas?: HTMLCanvasElement; // Preview of current frame
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Codec Support Matrix
|
|
142
|
+
|
|
143
|
+
Browser support varies by codec. Check availability before rendering:
|
|
144
|
+
|
|
145
|
+
| Codec | Chrome | Safari | Firefox | Notes |
|
|
146
|
+
|-------|--------|--------|---------|-------|
|
|
147
|
+
| `avc` (H.264) | ✅ | ✅ | ✅ | Best compatibility, widely supported |
|
|
148
|
+
| `hevc` (H.265) | ⚠️ | ✅ | ❌ | macOS/iOS only, better compression |
|
|
149
|
+
| `vp9` | ✅ | ❌ | ✅ | Open codec, good compression |
|
|
150
|
+
| `av1` | ✅ | ⚠️ | ✅ | Modern, best compression, slower encoding |
|
|
151
|
+
| `vp8` | ✅ | ❌ | ✅ | Legacy WebM codec |
|
|
152
|
+
|
|
153
|
+
**Recommendation:** Use `avc` for maximum compatibility or `av1` for best quality/size.
|
|
154
|
+
|
|
155
|
+
### Audio Inclusion
|
|
156
|
+
|
|
157
|
+
Audio from `ef-video` and `ef-audio` elements is automatically mixed and included:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
await timegroup.renderToVideo({
|
|
161
|
+
includeAudio: true, // Include audio tracks
|
|
162
|
+
audioBitrate: 192_000, // Higher quality audio (192 kbps)
|
|
163
|
+
preferredAudioCodecs: ['opus', 'aac'] // Codec preference order
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Audio codecs available: `opus` (best quality), `aac` (most compatible), `mp3` (legacy).
|
|
168
|
+
|
|
169
|
+
### Streaming Output
|
|
170
|
+
|
|
171
|
+
Stream large videos to disk without loading entire file into memory:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
await timegroup.renderToVideo({
|
|
175
|
+
streaming: true, // Uses File System Access API
|
|
176
|
+
filename: 'large-video.mp4' // User picks save location
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Requires browser support for File System Access API (Chrome 86+, Edge 86+).
|
|
181
|
+
|
|
182
|
+
### Aborting Renders
|
|
183
|
+
|
|
184
|
+
Cancel long-running exports with AbortController:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
const controller = new AbortController();
|
|
188
|
+
|
|
189
|
+
// Start render
|
|
190
|
+
const renderPromise = timegroup.renderToVideo({
|
|
191
|
+
signal: controller.signal,
|
|
192
|
+
onProgress: (progress) => {
|
|
193
|
+
console.log(`${Math.round(progress.progress * 100)}%`);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Cancel after 5 seconds
|
|
198
|
+
setTimeout(() => controller.abort(), 5000);
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
await renderPromise;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (error.name === 'RenderCancelledError') {
|
|
204
|
+
console.log('Render was cancelled');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Partial Exports
|
|
210
|
+
|
|
211
|
+
Export specific time ranges without modifying the composition:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// Export only seconds 5-15
|
|
215
|
+
await timegroup.renderToVideo({
|
|
216
|
+
fromMs: 5000,
|
|
217
|
+
toMs: 15000,
|
|
218
|
+
filename: 'clip.mp4'
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### High-Resolution Export
|
|
223
|
+
|
|
224
|
+
Render at higher resolutions using the `scale` option:
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// Render at 2x resolution (1440x960 from 720x480 composition)
|
|
228
|
+
await timegroup.renderToVideo({
|
|
229
|
+
scale: 2,
|
|
230
|
+
bitrate: 10_000_000 // Increase bitrate for higher resolution
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Programmatic Buffer Access
|
|
235
|
+
|
|
236
|
+
Get video data as Uint8Array instead of downloading:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
const videoBuffer = await timegroup.renderToVideo({
|
|
240
|
+
returnBuffer: true,
|
|
241
|
+
filename: 'video.mp4'
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Upload to server
|
|
245
|
+
const formData = new FormData();
|
|
246
|
+
formData.append('video', new Blob([videoBuffer], { type: 'video/mp4' }));
|
|
247
|
+
await fetch('/api/upload', { method: 'POST', body: formData });
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Off-DOM Rendering with createRenderClone()
|
|
251
|
+
|
|
252
|
+
Create an independent clone of a timegroup for off-screen rendering. This enables rendering without affecting the user's preview position and allows concurrent renders.
|
|
253
|
+
|
|
254
|
+
### Why Use Render Clones?
|
|
255
|
+
|
|
256
|
+
- **Non-disruptive:** Render in background without affecting preview playback
|
|
257
|
+
- **Concurrent:** Run multiple renders simultaneously with different clones
|
|
258
|
+
- **Isolated state:** Each clone has independent time position and state
|
|
259
|
+
- **JavaScript re-execution:** Initializer functions run on each clone
|
|
260
|
+
|
|
261
|
+
### Basic Usage
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// Create a render clone
|
|
265
|
+
const { clone, container, cleanup } = await timegroup.createRenderClone();
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
// Clone is fully functional and independent
|
|
269
|
+
await clone.seekForRender(5000); // Seek to 5 seconds
|
|
270
|
+
|
|
271
|
+
// Render single frame to canvas
|
|
272
|
+
const canvas = await renderToImageNative(clone, 1920, 1080);
|
|
273
|
+
|
|
274
|
+
// Use canvas data...
|
|
275
|
+
const dataUrl = canvas.toDataURL('image/png');
|
|
276
|
+
|
|
277
|
+
} finally {
|
|
278
|
+
// Always clean up when done
|
|
279
|
+
cleanup();
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Automatic Clone Management
|
|
284
|
+
|
|
285
|
+
`renderToVideo()` automatically manages clones internally. You typically don't need to use `createRenderClone()` directly unless you're building custom rendering logic.
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
// This internally creates and manages a render clone
|
|
289
|
+
await timegroup.renderToVideo({ fps: 30 });
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Clone Factory Pattern
|
|
293
|
+
|
|
294
|
+
For compositions with JavaScript behavior, provide an initializer function that runs on both the prime timeline and all clones:
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
<ef-timegroup id="myComp" mode="sequence"></ef-timegroup>
|
|
298
|
+
|
|
299
|
+
<script type="module">
|
|
300
|
+
const timegroup = document.getElementById('myComp');
|
|
301
|
+
|
|
302
|
+
// This function runs on the original AND on all render clones
|
|
303
|
+
timegroup.initializer = (tg) => {
|
|
304
|
+
// Set up reactive state, register callbacks, etc.
|
|
305
|
+
tg.addEventListener('frame-task', (e) => {
|
|
306
|
+
// Update canvas, modify DOM, etc.
|
|
307
|
+
console.log('Frame:', e.detail.currentTimeMs);
|
|
308
|
+
});
|
|
309
|
+
};
|
|
310
|
+
</script>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
The initializer:
|
|
314
|
+
- **Must be synchronous** (no async/await, no Promise return)
|
|
315
|
+
- **Must complete quickly** (<10ms warning, <100ms error)
|
|
316
|
+
- Runs once per instance (original + each clone)
|
|
317
|
+
- Enables JavaScript-driven animations in renders
|
|
318
|
+
|
|
319
|
+
## CLI Rendering
|
|
320
|
+
|
|
321
|
+
For server-side rendering, use the Editframe CLI. See the `editframe-cli` skill for full documentation.
|
|
322
|
+
|
|
323
|
+
### Quick Render
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
npx editframe render -o output.mp4
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Custom Render Data
|
|
330
|
+
|
|
331
|
+
Pass dynamic data into compositions at render time:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
npx editframe render --data '{"userName":"John","theme":"dark"}' -o video.mp4
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Read the data in your composition with `getRenderData()`:
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
import { getRenderData } from "@editframe/elements";
|
|
341
|
+
|
|
342
|
+
interface MyRenderData {
|
|
343
|
+
userName: string;
|
|
344
|
+
theme: "light" | "dark";
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const data = getRenderData<MyRenderData>();
|
|
348
|
+
if (data) {
|
|
349
|
+
console.log(data.userName); // "John"
|
|
350
|
+
console.log(data.theme); // "dark"
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### When to Use CLI vs Browser
|
|
355
|
+
|
|
356
|
+
**Use CLI rendering when:**
|
|
357
|
+
- Running on a server or CI/CD pipeline
|
|
358
|
+
- Need consistent encoding across platforms
|
|
359
|
+
- Processing videos in batch
|
|
360
|
+
- Require specific encoder settings not available in browsers
|
|
361
|
+
|
|
362
|
+
**Use browser rendering when:**
|
|
363
|
+
- Building interactive client-side applications
|
|
364
|
+
- Want instant preview and export without server
|
|
365
|
+
- Need real-time progress feedback
|
|
366
|
+
- Exporting user-generated content
|
|
367
|
+
|
|
368
|
+
## Advanced: Custom Writable Streams
|
|
369
|
+
|
|
370
|
+
For fine-grained control over output, provide a custom WritableStream:
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
class VideoUploadStream extends WritableStream<Uint8Array> {
|
|
374
|
+
constructor() {
|
|
375
|
+
super({
|
|
376
|
+
async write(chunk) {
|
|
377
|
+
// Stream chunks directly to server
|
|
378
|
+
await fetch('/api/upload/chunk', {
|
|
379
|
+
method: 'POST',
|
|
380
|
+
body: chunk
|
|
381
|
+
});
|
|
382
|
+
},
|
|
383
|
+
async close() {
|
|
384
|
+
// Finalize upload
|
|
385
|
+
await fetch('/api/upload/complete', { method: 'POST' });
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
await timegroup.renderToVideo({
|
|
392
|
+
customWritableStream: new VideoUploadStream(),
|
|
393
|
+
returnBuffer: false
|
|
394
|
+
});
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Performance Tips
|
|
398
|
+
|
|
399
|
+
1. **Use appropriate codecs:** `avc` encodes fastest, `av1` encodes slowest but smallest
|
|
400
|
+
2. **Reduce resolution:** Lower resolution renders much faster
|
|
401
|
+
3. **Limit audio bitrate:** High audio bitrates don't improve quality much
|
|
402
|
+
4. **Use contentReadyMode: "immediate":** Skip waiting for videos to fully load
|
|
403
|
+
5. **Disable progress previews:** Set high `progressPreviewInterval` or omit callback
|
|
404
|
+
6. **Test codec support:** Not all codecs are hardware-accelerated on all devices
|
|
405
|
+
|
|
406
|
+
## Browser Requirements
|
|
407
|
+
|
|
408
|
+
- **WebCodecs API** (Chrome 94+, Edge 94+, Safari 16.4+)
|
|
409
|
+
- **File System Access API** for streaming (Chrome 86+, Edge 86+)
|
|
410
|
+
- Requires HTTPS or localhost (secure context)
|
|
411
|
+
|
|
412
|
+
Check support:
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
const hasWebCodecs = 'VideoEncoder' in window && 'VideoDecoder' in window;
|
|
416
|
+
const hasFileSystemAccess = 'showSaveFilePicker' in window;
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## Error Handling
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
try {
|
|
423
|
+
await timegroup.renderToVideo({ fps: 60, codec: 'av1' });
|
|
424
|
+
} catch (error) {
|
|
425
|
+
if (error.name === 'RenderCancelledError') {
|
|
426
|
+
console.log('User cancelled export');
|
|
427
|
+
} else if (error.name === 'NoSupportedAudioCodecError') {
|
|
428
|
+
console.log('No compatible audio codec available');
|
|
429
|
+
// Retry without audio
|
|
430
|
+
await timegroup.renderToVideo({ includeAudio: false });
|
|
431
|
+
} else {
|
|
432
|
+
console.error('Render failed:', error);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Examples
|
|
438
|
+
|
|
439
|
+
### Export with Custom Settings
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
await timegroup.renderToVideo({
|
|
443
|
+
fps: 60, // Smooth 60fps
|
|
444
|
+
codec: 'avc', // H.264 for compatibility
|
|
445
|
+
bitrate: 8_000_000, // 8 Mbps
|
|
446
|
+
scale: 1.5, // 1.5x resolution
|
|
447
|
+
includeAudio: true,
|
|
448
|
+
audioBitrate: 256_000, // High quality audio
|
|
449
|
+
filename: 'high-quality.mp4'
|
|
450
|
+
});
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Progress Bar with Time Estimates
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
await timegroup.renderToVideo({
|
|
457
|
+
onProgress: ({ progress, elapsedMs, estimatedRemainingMs, speedMultiplier }) => {
|
|
458
|
+
const percent = Math.round(progress * 100);
|
|
459
|
+
const elapsed = Math.round(elapsedMs / 1000);
|
|
460
|
+
const remaining = Math.round(estimatedRemainingMs / 1000);
|
|
461
|
+
|
|
462
|
+
console.log(
|
|
463
|
+
`${percent}% complete | ` +
|
|
464
|
+
`Elapsed: ${elapsed}s | ` +
|
|
465
|
+
`Remaining: ${remaining}s | ` +
|
|
466
|
+
`Speed: ${speedMultiplier.toFixed(1)}x`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Export Multiple Clips
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
const clips = [
|
|
476
|
+
{ fromMs: 0, toMs: 5000, filename: 'intro.mp4' },
|
|
477
|
+
{ fromMs: 5000, toMs: 15000, filename: 'main.mp4' },
|
|
478
|
+
{ fromMs: 15000, toMs: 20000, filename: 'outro.mp4' },
|
|
479
|
+
];
|
|
480
|
+
|
|
481
|
+
for (const clip of clips) {
|
|
482
|
+
await timegroup.renderToVideo(clip);
|
|
483
|
+
}
|
|
484
|
+
```
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Render Strategies
|
|
3
|
+
description: Three approaches to rendering Editframe compositions — CLI, browser-side, and cloud — with tradeoffs and when to use each.
|
|
4
|
+
type: explanation
|
|
5
|
+
nav:
|
|
6
|
+
parent: "Concepts"
|
|
7
|
+
priority: 13
|
|
8
|
+
related: ["render-api", "render-to-video"]
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Render Strategies
|
|
12
|
+
|
|
13
|
+
Editframe compositions can be rendered through three paths. Each targets a different environment and set of constraints. The composition itself is identical across all three — only the rendering infrastructure differs.
|
|
14
|
+
|
|
15
|
+
## CLI Render (Playwright)
|
|
16
|
+
|
|
17
|
+
The CLI spawns a local Vite dev server and a Playwright browser instance. The browser loads the composition, and frames are captured server-side using Playwright's screenshot API.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx editframe render -o output.mp4
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The CLI handles encoding, audio muxing, and output assembly. Dynamic data can be injected at render time:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx editframe render --data '{"title":"Q4 Report","theme":"dark"}' -o output.mp4
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Access the data inside the composition with `getRenderData()`:
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<script type="module">
|
|
33
|
+
import { getRenderData } from "@editframe/elements";
|
|
34
|
+
|
|
35
|
+
const data = getRenderData();
|
|
36
|
+
if (data) {
|
|
37
|
+
document.querySelector('ef-text').textContent = data.title;
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The CLI path is suited for local development, automation scripts, and CI/CD pipelines where no browser UI is needed.
|
|
43
|
+
|
|
44
|
+
## Browser Render (WebCodecs)
|
|
45
|
+
|
|
46
|
+
The browser path uses the WebCodecs API for video encoding and FFmpeg.wasm for muxing. The entire render runs client-side — no server is required.
|
|
47
|
+
|
|
48
|
+
```html
|
|
49
|
+
<script type="module">
|
|
50
|
+
const timegroup = document.querySelector('ef-timegroup');
|
|
51
|
+
|
|
52
|
+
await timegroup.renderToVideo({
|
|
53
|
+
fps: 30,
|
|
54
|
+
codec: 'avc',
|
|
55
|
+
filename: 'export.mp4',
|
|
56
|
+
onProgress: (progress) => {
|
|
57
|
+
console.log(`${Math.round(progress.progress * 100)}%`);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
</script>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Browser support: Chrome 94+, Edge 94+, Safari 16.4+. Check availability at runtime:
|
|
64
|
+
|
|
65
|
+
```html
|
|
66
|
+
<script type="module">
|
|
67
|
+
const supported = 'VideoEncoder' in window && 'VideoDecoder' in window;
|
|
68
|
+
</script>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The browser path is suited for user-facing export features, interactive applications, and cases where you want to avoid running a backend.
|
|
72
|
+
|
|
73
|
+
## Cloud Render (API)
|
|
74
|
+
|
|
75
|
+
The cloud path uploads the composition bundle to a server, which processes it with Playwright in a controlled environment. The server handles encoding, asset resolution, and output delivery.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# POST composition bundle to render API
|
|
79
|
+
curl -X POST https://api.editframe.com/v1/renders \
|
|
80
|
+
-H "Authorization: Bearer $API_KEY" \
|
|
81
|
+
-F "bundle=@composition.tgz"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Results are delivered via webhook or polling. The cloud path is suited for production workflows, scalable rendering, and environments where no browser is available.
|
|
85
|
+
|
|
86
|
+
## Comparison
|
|
87
|
+
|
|
88
|
+
| Aspect | CLI (Playwright) | Browser (WebCodecs) | Cloud (API) |
|
|
89
|
+
|--------|-------------------|---------------------|-------------|
|
|
90
|
+
| Runs in | Local machine | User's browser | Remote server |
|
|
91
|
+
| Encoding | Server-side FFmpeg | WebCodecs + FFmpeg.wasm | Server-side FFmpeg |
|
|
92
|
+
| Audio muxing | Automatic | Automatic | Automatic |
|
|
93
|
+
| Dynamic data | `--data` flag | JavaScript state | Request payload |
|
|
94
|
+
| Progress feedback | Terminal output | `onProgress` callback | Webhook / polling |
|
|
95
|
+
| Browser required | Playwright (headless) | User's browser | None (server-side) |
|
|
96
|
+
| Codec control | Full FFmpeg options | Browser-supported codecs | Full FFmpeg options |
|
|
97
|
+
| Concurrent renders | Limited by local CPU | One per tab | Scales with infrastructure |
|
|
98
|
+
|
|
99
|
+
## Architecture Differences
|
|
100
|
+
|
|
101
|
+
All three paths share the same composition format. The divergence is in how frames are captured and encoded.
|
|
102
|
+
|
|
103
|
+
**CLI and cloud** both use Playwright to load the composition in a headless browser. Playwright captures each frame as a screenshot, and an FFmpeg process encodes the image sequence into the output container. Audio is decoded and muxed separately.
|
|
104
|
+
|
|
105
|
+
**Browser render** creates an off-DOM clone of the composition, seeks it frame-by-frame, and rasterizes each frame to a canvas. The canvas frames are fed to a `VideoEncoder` instance (WebCodecs API), and the encoded chunks are muxed with FFmpeg.wasm into the final MP4.
|
|
106
|
+
|
|
107
|
+
## Performance Characteristics
|
|
108
|
+
|
|
109
|
+
**CLI render** speed depends on the local machine. Frame capture through Playwright adds per-frame overhead, but encoding benefits from native FFmpeg and hardware acceleration where available.
|
|
110
|
+
|
|
111
|
+
**Browser render** speed depends on the user's device and browser. WebCodecs can leverage hardware encoding (GPU-accelerated H.264/H.265 on supported hardware), but the JavaScript overhead of canvas rasterization is the typical bottleneck.
|
|
112
|
+
|
|
113
|
+
**Cloud render** offloads all computation to the server. Latency comes from uploading the bundle and waiting for the job to complete, but the render itself benefits from dedicated infrastructure.
|
|
114
|
+
|
|
115
|
+
## See Also
|
|
116
|
+
|
|
117
|
+
- [render-api.md](references/render-api.md) — full `renderToVideo()` options reference
|
|
118
|
+
- [render-to-video.md](references/render-to-video.md) — step-by-step browser export tutorial
|
|
119
|
+
- [server-rendering.md](references/server-rendering.md) — SSR-safe imports for server environments
|