@shotstack/shotstack-studio 2.0.0-beta.34 → 2.0.0-beta.36

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/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "cpuccino",
6
6
  "dazzatron"
7
7
  ],
8
- "version": "2.0.0-beta.34",
8
+ "version": "2.0.0-beta.36",
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": "vite build --config vite.config.internal.ts",
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
- "prepublishOnly": "npm run build && npm run test && npm run test:package",
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.6",
99
+ "@shotstack/schemas": "1.6.0",
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
  [![License](https://img.shields.io/badge/license-PolyForm_Shield-blue.svg)](https://polyformproject.org/licenses/shield/1.0.0/)
5
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.6-blue.svg)](https://www.typescriptlang.org/)
6
6
 
7
- A JavaScript library for creating and editing videos in the browser.
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
- - Create video compositions with multiple tracks and clips
22
- - Visual timeline interface
23
- - Multi-track, drag-and-drop clip manipulation with snap-to-grid
24
- - Use in conjunction with the [Shotstack Edit API](https://shotstack.io/docs/guide/getting-started/hello-world-using-curl/) to render video
25
- - Export to video via the browser
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. Load a template
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. Create Edit from template and load it
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
- // 3. Create a canvas to display the edit
51
- const canvas = new Canvas(edit);
52
- await canvas.load(); // Renders to [data-shotstack-studio] element
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
- // 4. Initialize the Timeline
55
- const container = document.querySelector("[data-shotstack-timeline]");
56
- const timeline = new Timeline(edit, container, {
57
- features: { toolbar: true, ruler: true, playhead: true, snap: true }
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 should include containers for both the canvas and timeline:
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
- The Edit class represents a video project with its timeline, clips, and properties.
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
- // Or reload a different template later
87
- await edit.loadEdit(newTemplate);
109
+ await edit.loadEdit(nextTemplateJson);
88
110
 
89
- // Playback controls
111
+ // Playback (seconds)
90
112
  edit.play();
91
113
  edit.pause();
92
- edit.seek(2000); // Seek to 2 seconds (in milliseconds)
93
- edit.stop(); // Stop and return to beginning
94
-
95
- // Editing functions
96
- edit.addClip(0, {
97
- asset: {
98
- type: "image",
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
- edit.addTrack(1, { clips: [] });
106
- edit.deleteClip(0, 0);
107
- edit.deleteTrack(1);
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
- // Get edit information
131
+ // Read state
116
132
  const clip = edit.getClip(0, 0);
117
133
  const track = edit.getTrack(0);
118
- const editJson = edit.getEdit();
119
- const duration = edit.totalDuration; // in milliseconds
134
+ const snapshot = edit.getEdit();
135
+ const durationSeconds = edit.totalDuration;
120
136
  ```
121
137
 
122
138
  #### Events
123
139
 
124
- The Edit class provides a typed event system to listen for specific actions:
140
+ Listen using string event names:
125
141
 
126
142
  ```typescript
127
- import { Edit, EditEvent } from "@shotstack/shotstack-studio";
143
+ const unsubscribeClipSelected = edit.events.on("clip:selected", data => {
144
+ console.log("Selected clip", data.trackIndex, data.clipIndex);
145
+ });
128
146
 
129
- // Listen for clip selection events
130
- edit.events.on(EditEvent.ClipSelected, data => {
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
- // Listen for clip update events
137
- edit.events.on(EditEvent.ClipUpdated, data => {
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
- // Listen for playback events
143
- edit.events.on(EditEvent.PlaybackPlay, () => console.log("Playing"));
144
- edit.events.on(EditEvent.PlaybackPause, () => console.log("Paused"));
155
+ // Unsubscribe when no longer needed
156
+ unsubscribeClipSelected();
145
157
  ```
146
158
 
147
- Available events:
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
- **Tracks:** `TrackAdded`, `TrackRemoved`
158
-
159
- **Duration:** `DurationChanged`
160
-
161
- **Output:** `OutputResized`, `OutputFpsChanged`, `OutputFormatChanged`
162
-
163
- **Merge Fields:** `MergeFieldRegistered`, `MergeFieldUpdated`, `MergeFieldRemoved`, `MergeFieldChanged`
164
-
165
- **Transcription:** `TranscriptionProgress`, `TranscriptionCompleted`, `TranscriptionFailed`
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
- The Canvas class provides the visual rendering of the edit.
175
+ `Canvas` renders the current edit.
170
176
 
171
177
  ```typescript
172
- // Create and load the canvas
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.5); // 1.0 is 100%, 0.5 is 50%, etc.
180
- canvas.dispose(); // Clean up resources when done
185
+ canvas.setZoom(1.25);
186
+ canvas.resize();
187
+ canvas.dispose();
181
188
  ```
182
189
 
183
- ### Controls
190
+ ### UIController
184
191
 
185
- The Controls class adds keyboard controls for playback.
192
+ `UIController` manages built-in UI wiring and extensible button events.
186
193
 
187
194
  ```typescript
188
- const controls = new Controls(edit);
189
- await controls.load();
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
- // Available keyboard controls:
192
- // Space - Play/Pause
193
- // J - Stop
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
- The Timeline class provides a visual timeline interface for editing.
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
- features: {
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
- ### VideoExporter
230
+ ### Controls
227
231
 
228
- The VideoExporter class exports the Edit to a MP4 video file encoded in h264 and AAC.
232
+ `Controls` enables keyboard playback/edit shortcuts.
229
233
 
230
234
  ```typescript
231
- const exporter = new VideoExporter(edit, canvas);
232
- await exporter.export("my-video.mp4", 25); // filename, fps
235
+ import { Controls } from "@shotstack/shotstack-studio";
236
+
237
+ const controls = new Controls(edit);
238
+ await controls.load();
233
239
  ```
234
240
 
235
- ## Merge Fields
241
+ ### VideoExporter
236
242
 
237
- Merge fields allow dynamic content substitution using `{{ FIELD_NAME }}` syntax in your templates.
243
+ `VideoExporter` exports a timeline render from the browser runtime.
238
244
 
239
245
  ```typescript
240
- import { Edit, EditEvent } from "@shotstack/shotstack-studio";
246
+ import { VideoExporter } from "@shotstack/shotstack-studio";
241
247
 
242
- // Set a merge field value
243
- edit.setMergeField("TITLE", "My Video Title");
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
- In templates, use placeholders that will be replaced with merge field values:
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
- ## Custom Toolbar Buttons
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
- Register custom toolbar buttons to extend the canvas toolbar with your own actions:
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
- // Register a custom button
272
- edit.registerToolbarButton({
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
- event: "text:requested",
277
- dividerBefore: true // Optional: add a divider before this button
288
+ dividerBefore: true
278
289
  });
279
290
 
280
- // Handle the custom event
281
- edit.events.on("text:requested", ({ position }) => {
282
- // position is the current playhead position in milliseconds
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 complete schema and type definitions, see the [Shotstack API Reference](https://shotstack.io/docs/api/#tocs_edit).
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