@shotstack/shotstack-studio 2.0.0-beta.34 → 2.0.0-beta.35
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.d.ts +27 -1232
- package/dist/internal.d.ts +478 -0
- package/dist/internal.es.js +3953 -3884
- package/dist/internal.umd.js +17 -17
- package/dist/schema/index.cjs +1 -1
- package/dist/schema/index.d.ts +27 -1232
- package/dist/schema/index.mjs +397 -323
- package/dist/shotstack-studio.es.js +4428 -4561
- package/dist/shotstack-studio.umd.js +114 -140
- package/package.json +9 -4
- package/readme.md +152 -144
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"cpuccino",
|
|
6
6
|
"dazzatron"
|
|
7
7
|
],
|
|
8
|
-
"version": "2.0.0-beta.
|
|
8
|
+
"version": "2.0.0-beta.35",
|
|
9
9
|
"description": "A video editing library for creating and editing videos with Shotstack",
|
|
10
10
|
"type": "module",
|
|
11
11
|
"main": "dist/shotstack-studio.umd.js",
|
|
@@ -46,6 +46,9 @@
|
|
|
46
46
|
"ffmpeg"
|
|
47
47
|
],
|
|
48
48
|
"license": "PolyForm Shield License 1.0.0",
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=22 <23"
|
|
51
|
+
},
|
|
49
52
|
"repository": {
|
|
50
53
|
"type": "git",
|
|
51
54
|
"url": "https://github.com/shotstack/shotstack-studio-sdk"
|
|
@@ -56,7 +59,7 @@
|
|
|
56
59
|
"start": "npm run build && vite preview",
|
|
57
60
|
"build": "npm run build:main && npm run build:internal && npm run build:schema",
|
|
58
61
|
"build:main": "vite build",
|
|
59
|
-
"build:internal": "
|
|
62
|
+
"build:internal": "node scripts/build-internal.mjs",
|
|
60
63
|
"build:schema": "vite build --config vite.config.schema.ts",
|
|
61
64
|
"test": "jest",
|
|
62
65
|
"test:watch": "jest --watch",
|
|
@@ -65,7 +68,9 @@
|
|
|
65
68
|
"lint": "eslint --ignore-path .gitignore .",
|
|
66
69
|
"lint:fix": "eslint --ignore-path .gitignore --fix .",
|
|
67
70
|
"format": "prettier --ignore-path .gitignore --write .",
|
|
68
|
-
"
|
|
71
|
+
"verify:ci": "npm run lint && npm run typecheck && npm run test && npm run build && npm run test:package",
|
|
72
|
+
"release:check": "npm run verify:ci && npm pack --dry-run --json",
|
|
73
|
+
"prepublishOnly": "npm run release:check",
|
|
69
74
|
"generate:fonts": "npx tsx scripts/fetch-google-fonts.ts"
|
|
70
75
|
},
|
|
71
76
|
"devDependencies": {
|
|
@@ -91,7 +96,7 @@
|
|
|
91
96
|
},
|
|
92
97
|
"dependencies": {
|
|
93
98
|
"@huggingface/transformers": "^3.0.0",
|
|
94
|
-
"@shotstack/schemas": "1.5.
|
|
99
|
+
"@shotstack/schemas": "1.5.7",
|
|
95
100
|
"@shotstack/shotstack-canvas": "^1.9.6",
|
|
96
101
|
"fast-deep-equal": "^3.1.3",
|
|
97
102
|
"howler": "^2.2.4",
|
package/readme.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://polyformproject.org/licenses/shield/1.0.0/)
|
|
5
5
|
[](https://www.typescriptlang.org/)
|
|
6
6
|
|
|
7
|
-
A JavaScript
|
|
7
|
+
A JavaScript SDK for browser-based video editing with timeline, canvas preview, and export.
|
|
8
8
|
|
|
9
9
|
## Interactive Examples
|
|
10
10
|
|
|
@@ -18,11 +18,11 @@ Try Shotstack Studio in your preferred framework:
|
|
|
18
18
|
|
|
19
19
|
## Features
|
|
20
20
|
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
21
|
+
- Template-driven editing with undo/redo command model
|
|
22
|
+
- Canvas preview rendering
|
|
23
|
+
- Visual timeline with drag, resize, selection, and snapping
|
|
24
|
+
- Extensible UI via `UIController` button API
|
|
25
|
+
- Browser export pipeline via `VideoExporter`
|
|
26
26
|
|
|
27
27
|
## Installation
|
|
28
28
|
|
|
@@ -37,33 +37,57 @@ yarn add @shotstack/shotstack-studio
|
|
|
37
37
|
## Quick Start
|
|
38
38
|
|
|
39
39
|
```typescript
|
|
40
|
-
import { Edit, Canvas, Controls, Timeline } from "@shotstack/shotstack-studio";
|
|
40
|
+
import { Edit, Canvas, Controls, Timeline, UIController } from "@shotstack/shotstack-studio";
|
|
41
41
|
|
|
42
|
-
// 1
|
|
42
|
+
// 1) Load a template
|
|
43
43
|
const response = await fetch("https://shotstack-assets.s3.amazonaws.com/templates/hello-world/hello.json");
|
|
44
44
|
const template = await response.json();
|
|
45
45
|
|
|
46
|
-
// 2
|
|
46
|
+
// 2) Create core components
|
|
47
47
|
const edit = new Edit(template);
|
|
48
|
+
const canvas = new Canvas(edit);
|
|
49
|
+
const ui = UIController.create(edit, canvas);
|
|
50
|
+
|
|
51
|
+
// 3) Load runtime
|
|
52
|
+
await canvas.load();
|
|
48
53
|
await edit.load();
|
|
49
54
|
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
// 4) Add one custom UI button
|
|
56
|
+
ui.registerButton({
|
|
57
|
+
id: "text",
|
|
58
|
+
icon: `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3H13"/><path d="M8 3V13"/><path d="M5 13H11"/></svg>`,
|
|
59
|
+
tooltip: "Add Text"
|
|
60
|
+
});
|
|
53
61
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
ui.on("button:text", ({ position }) => {
|
|
63
|
+
edit.addTrack(0, {
|
|
64
|
+
clips: [
|
|
65
|
+
{
|
|
66
|
+
asset: {
|
|
67
|
+
type: "rich-text",
|
|
68
|
+
text: "Title",
|
|
69
|
+
font: { family: "Work Sans", size: 72, weight: 600, color: "#ffffff", opacity: 1 },
|
|
70
|
+
align: { horizontal: "center", vertical: "middle" }
|
|
71
|
+
},
|
|
72
|
+
start: position,
|
|
73
|
+
length: 5,
|
|
74
|
+
width: 500,
|
|
75
|
+
height: 200
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
});
|
|
58
79
|
});
|
|
80
|
+
|
|
81
|
+
// 5) Timeline + controls
|
|
82
|
+
const timelineContainer = document.querySelector("[data-shotstack-timeline]") as HTMLElement;
|
|
83
|
+
const timeline = new Timeline(edit, timelineContainer);
|
|
59
84
|
await timeline.load();
|
|
60
85
|
|
|
61
|
-
// 5. Add keyboard controls
|
|
62
86
|
const controls = new Controls(edit);
|
|
63
87
|
await controls.load();
|
|
64
88
|
```
|
|
65
89
|
|
|
66
|
-
Your HTML
|
|
90
|
+
Your HTML must include both containers:
|
|
67
91
|
|
|
68
92
|
```html
|
|
69
93
|
<div data-shotstack-studio></div>
|
|
@@ -74,185 +98,160 @@ Your HTML should include containers for both the canvas and timeline:
|
|
|
74
98
|
|
|
75
99
|
### Edit
|
|
76
100
|
|
|
77
|
-
|
|
101
|
+
`Edit` is the runtime editing session and source of truth for document mutations.
|
|
78
102
|
|
|
79
103
|
```typescript
|
|
80
104
|
import { Edit } from "@shotstack/shotstack-studio";
|
|
81
105
|
|
|
82
|
-
// Create an edit from a template
|
|
83
106
|
const edit = new Edit(templateJson);
|
|
84
107
|
await edit.load();
|
|
85
108
|
|
|
86
|
-
|
|
87
|
-
await edit.loadEdit(newTemplate);
|
|
109
|
+
await edit.loadEdit(nextTemplateJson);
|
|
88
110
|
|
|
89
|
-
// Playback
|
|
111
|
+
// Playback (seconds)
|
|
90
112
|
edit.play();
|
|
91
113
|
edit.pause();
|
|
92
|
-
edit.seek(
|
|
93
|
-
edit.stop();
|
|
94
|
-
|
|
95
|
-
//
|
|
96
|
-
edit.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
src: "https://example.com/image.jpg"
|
|
100
|
-
},
|
|
114
|
+
edit.seek(2);
|
|
115
|
+
edit.stop();
|
|
116
|
+
|
|
117
|
+
// Mutations
|
|
118
|
+
await edit.addTrack(0, { clips: [] });
|
|
119
|
+
await edit.addClip(0, {
|
|
120
|
+
asset: { type: "image", src: "https://example.com/image.jpg" },
|
|
101
121
|
start: 0,
|
|
102
122
|
length: 5
|
|
103
123
|
});
|
|
124
|
+
await edit.updateClip(0, 0, { length: 6 });
|
|
125
|
+
await edit.deleteClip(0, 0);
|
|
104
126
|
|
|
105
|
-
|
|
106
|
-
edit.
|
|
107
|
-
edit.
|
|
108
|
-
|
|
109
|
-
// Undo/Redo
|
|
110
|
-
edit.undo();
|
|
111
|
-
edit.redo();
|
|
112
|
-
edit.canUndo(); // Check if undo is available (useful for UI)
|
|
113
|
-
edit.canRedo(); // Check if redo is available
|
|
127
|
+
// History
|
|
128
|
+
await edit.undo();
|
|
129
|
+
await edit.redo();
|
|
114
130
|
|
|
115
|
-
//
|
|
131
|
+
// Read state
|
|
116
132
|
const clip = edit.getClip(0, 0);
|
|
117
133
|
const track = edit.getTrack(0);
|
|
118
|
-
const
|
|
119
|
-
const
|
|
134
|
+
const snapshot = edit.getEdit();
|
|
135
|
+
const durationSeconds = edit.totalDuration;
|
|
120
136
|
```
|
|
121
137
|
|
|
122
138
|
#### Events
|
|
123
139
|
|
|
124
|
-
|
|
140
|
+
Listen using string event names:
|
|
125
141
|
|
|
126
142
|
```typescript
|
|
127
|
-
|
|
143
|
+
const unsubscribeClipSelected = edit.events.on("clip:selected", data => {
|
|
144
|
+
console.log("Selected clip", data.trackIndex, data.clipIndex);
|
|
145
|
+
});
|
|
128
146
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
console.log("Clip selected:", data.clip);
|
|
132
|
-
console.log("Track index:", data.trackIndex);
|
|
133
|
-
console.log("Clip index:", data.clipIndex);
|
|
147
|
+
edit.events.on("clip:updated", data => {
|
|
148
|
+
console.log("Updated from", data.previous, "to", data.current);
|
|
134
149
|
});
|
|
135
150
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
console.log("Previous state:", data.previous);
|
|
139
|
-
console.log("Current state:", data.current);
|
|
151
|
+
edit.events.on("playback:play", () => {
|
|
152
|
+
console.log("Playback started");
|
|
140
153
|
});
|
|
141
154
|
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
edit.events.on(EditEvent.PlaybackPause, () => console.log("Paused"));
|
|
155
|
+
// Unsubscribe when no longer needed
|
|
156
|
+
unsubscribeClipSelected();
|
|
145
157
|
```
|
|
146
158
|
|
|
147
|
-
Available
|
|
148
|
-
|
|
149
|
-
**Playback:** `PlaybackPlay`, `PlaybackPause`
|
|
150
|
-
|
|
151
|
-
**Clips:** `ClipAdded`, `ClipDeleted`, `ClipSelected`, `ClipUpdated`, `ClipCopied`, `ClipSplit`, `ClipRestored`
|
|
152
|
-
|
|
153
|
-
**Selection:** `SelectionCleared`
|
|
154
|
-
|
|
155
|
-
**Edit State:** `EditChanged`, `EditUndo`, `EditRedo`
|
|
159
|
+
Available event names:
|
|
156
160
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
161
|
+
- Playback: `playback:play`, `playback:pause`
|
|
162
|
+
- Timeline: `timeline:updated`, `timeline:backgroundChanged`
|
|
163
|
+
- Clip lifecycle: `clip:added`, `clip:split`, `clip:selected`, `clip:updated`, `clip:deleted`, `clip:restored`, `clip:copied`, `clip:loadFailed`, `clip:unresolved`
|
|
164
|
+
- Selection: `selection:cleared`
|
|
165
|
+
- Edit state: `edit:changed`, `edit:undo`, `edit:redo`
|
|
166
|
+
- Track: `track:added`, `track:removed`
|
|
167
|
+
- Duration: `duration:changed`
|
|
168
|
+
- Output: `output:resized`, `output:resolutionChanged`, `output:aspectRatioChanged`, `output:fpsChanged`, `output:formatChanged`, `output:destinationsChanged`
|
|
169
|
+
- Merge fields: `mergefield:changed`
|
|
170
|
+
- Transcription: `transcription:progress`, `transcription:completed`, `transcription:failed`
|
|
171
|
+
- Luma masking: `luma:attached`, `luma:detached`
|
|
166
172
|
|
|
167
173
|
### Canvas
|
|
168
174
|
|
|
169
|
-
|
|
175
|
+
`Canvas` renders the current edit.
|
|
170
176
|
|
|
171
177
|
```typescript
|
|
172
|
-
|
|
178
|
+
import { Canvas } from "@shotstack/shotstack-studio";
|
|
179
|
+
|
|
173
180
|
const canvas = new Canvas(edit);
|
|
174
181
|
await canvas.load();
|
|
175
182
|
|
|
176
|
-
// Zoom and positioning
|
|
177
183
|
canvas.centerEdit();
|
|
178
184
|
canvas.zoomToFit();
|
|
179
|
-
canvas.setZoom(1.
|
|
180
|
-
canvas.
|
|
185
|
+
canvas.setZoom(1.25);
|
|
186
|
+
canvas.resize();
|
|
187
|
+
canvas.dispose();
|
|
181
188
|
```
|
|
182
189
|
|
|
183
|
-
###
|
|
190
|
+
### UIController
|
|
184
191
|
|
|
185
|
-
|
|
192
|
+
`UIController` manages built-in UI wiring and extensible button events.
|
|
186
193
|
|
|
187
194
|
```typescript
|
|
188
|
-
|
|
189
|
-
|
|
195
|
+
import { UIController } from "@shotstack/shotstack-studio";
|
|
196
|
+
|
|
197
|
+
const ui = UIController.create(edit, canvas, { mergeFields: true });
|
|
198
|
+
|
|
199
|
+
ui.registerButton({
|
|
200
|
+
id: "add-title",
|
|
201
|
+
icon: `<svg viewBox="0 0 16 16">...</svg>`,
|
|
202
|
+
tooltip: "Add Title"
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const unsubscribe = ui.on("button:add-title", ({ position }) => {
|
|
206
|
+
console.log("Button clicked at", position, "seconds");
|
|
207
|
+
});
|
|
190
208
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
// K - Pause
|
|
195
|
-
// L - Play
|
|
196
|
-
// Left Arrow - Seek backward
|
|
197
|
-
// Right Arrow - Seek forward
|
|
198
|
-
// Shift + Arrow - Seek larger amount
|
|
199
|
-
// Comma - Step backward one frame
|
|
200
|
-
// Period - Step forward one frame
|
|
201
|
-
// Cmd/Ctrl + Z - Undo
|
|
202
|
-
// Cmd/Ctrl + Shift + Z - Redo
|
|
203
|
-
// Cmd/Ctrl + E - Export/download video
|
|
209
|
+
ui.unregisterButton("add-title");
|
|
210
|
+
unsubscribe();
|
|
211
|
+
ui.dispose();
|
|
204
212
|
```
|
|
205
213
|
|
|
206
214
|
### Timeline
|
|
207
215
|
|
|
208
|
-
|
|
216
|
+
`Timeline` provides visual clip editing.
|
|
209
217
|
|
|
210
218
|
```typescript
|
|
211
219
|
import { Timeline } from "@shotstack/shotstack-studio";
|
|
212
220
|
|
|
213
|
-
const container = document.querySelector("[data-shotstack-timeline]");
|
|
214
|
-
const timeline = new Timeline(edit, container
|
|
215
|
-
|
|
216
|
-
toolbar: true, // Playback controls and editing buttons
|
|
217
|
-
ruler: true, // Time ruler with markers
|
|
218
|
-
playhead: true, // Draggable playhead
|
|
219
|
-
snap: true, // Snap clips to grid and other clips
|
|
220
|
-
badges: true // Asset type badges on clips
|
|
221
|
-
}
|
|
222
|
-
});
|
|
221
|
+
const container = document.querySelector("[data-shotstack-timeline]") as HTMLElement;
|
|
222
|
+
const timeline = new Timeline(edit, container);
|
|
223
|
+
|
|
223
224
|
await timeline.load();
|
|
225
|
+
timeline.zoomIn();
|
|
226
|
+
timeline.zoomOut();
|
|
227
|
+
timeline.dispose();
|
|
224
228
|
```
|
|
225
229
|
|
|
226
|
-
###
|
|
230
|
+
### Controls
|
|
227
231
|
|
|
228
|
-
|
|
232
|
+
`Controls` enables keyboard playback/edit shortcuts.
|
|
229
233
|
|
|
230
234
|
```typescript
|
|
231
|
-
|
|
232
|
-
|
|
235
|
+
import { Controls } from "@shotstack/shotstack-studio";
|
|
236
|
+
|
|
237
|
+
const controls = new Controls(edit);
|
|
238
|
+
await controls.load();
|
|
233
239
|
```
|
|
234
240
|
|
|
235
|
-
|
|
241
|
+
### VideoExporter
|
|
236
242
|
|
|
237
|
-
|
|
243
|
+
`VideoExporter` exports a timeline render from the browser runtime.
|
|
238
244
|
|
|
239
245
|
```typescript
|
|
240
|
-
import {
|
|
246
|
+
import { VideoExporter } from "@shotstack/shotstack-studio";
|
|
241
247
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
edit.setMergeField("SUBTITLE", "A great subtitle");
|
|
245
|
-
|
|
246
|
-
// Get all registered merge fields
|
|
247
|
-
const fields = edit.getMergeFields();
|
|
248
|
-
|
|
249
|
-
// Listen for merge field changes
|
|
250
|
-
edit.events.on(EditEvent.MergeFieldUpdated, ({ field }) => {
|
|
251
|
-
console.log(`Field ${field.name} updated to:`, field.value);
|
|
252
|
-
});
|
|
248
|
+
const exporter = new VideoExporter(edit, canvas);
|
|
249
|
+
await exporter.export("my-video.mp4", 25);
|
|
253
250
|
```
|
|
254
251
|
|
|
255
|
-
|
|
252
|
+
## Merge Fields
|
|
253
|
+
|
|
254
|
+
Merge fields are template placeholders, typically in the form `{{ FIELD_NAME }}`.
|
|
256
255
|
|
|
257
256
|
```json
|
|
258
257
|
{
|
|
@@ -263,34 +262,43 @@ In templates, use placeholders that will be replaced with merge field values:
|
|
|
263
262
|
}
|
|
264
263
|
```
|
|
265
264
|
|
|
266
|
-
|
|
265
|
+
When merge-field-aware UI is required, enable it via `UIController` options:
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
const ui = UIController.create(edit, canvas, { mergeFields: true });
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
You can also subscribe to merge field events when integrations update merge data:
|
|
267
272
|
|
|
268
|
-
|
|
273
|
+
```typescript
|
|
274
|
+
edit.events.on("mergefield:changed", ({ fields }) => {
|
|
275
|
+
console.log("Merge fields updated:", fields.length);
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Custom UI Buttons
|
|
280
|
+
|
|
281
|
+
Use `UIController` to register and handle custom button actions.
|
|
269
282
|
|
|
270
283
|
```typescript
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
id: "add-text",
|
|
284
|
+
ui.registerButton({
|
|
285
|
+
id: "text",
|
|
274
286
|
icon: `<svg viewBox="0 0 16 16">...</svg>`,
|
|
275
287
|
tooltip: "Add Text",
|
|
276
|
-
|
|
277
|
-
dividerBefore: true // Optional: add a divider before this button
|
|
288
|
+
dividerBefore: true
|
|
278
289
|
});
|
|
279
290
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
edit.addClip(0, {
|
|
284
|
-
asset: { type: "text", text: "New Text" },
|
|
285
|
-
start: position / 1000,
|
|
286
|
-
length: 5
|
|
287
|
-
});
|
|
291
|
+
ui.on("button:text", ({ position, selectedClip }) => {
|
|
292
|
+
console.log("Current time (seconds):", position);
|
|
293
|
+
console.log("Current selection:", selectedClip);
|
|
288
294
|
});
|
|
295
|
+
|
|
296
|
+
ui.unregisterButton("text");
|
|
289
297
|
```
|
|
290
298
|
|
|
291
299
|
## API Reference
|
|
292
300
|
|
|
293
|
-
For
|
|
301
|
+
For schema-level details and type definitions, see the [Shotstack API Reference](https://shotstack.io/docs/api/#tocs_edit).
|
|
294
302
|
|
|
295
303
|
## License
|
|
296
304
|
|