@shotstack/shotstack-studio 1.10.1 → 2.0.0-beta.2

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": "1.10.1",
8
+ "version": "2.0.0-beta.2",
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",
@@ -48,14 +48,15 @@
48
48
  "build": "npm run build:main && npm run build:schema",
49
49
  "build:main": "vite build",
50
50
  "build:schema": "vite build --config vite.config.schema.ts",
51
- "test": "npm run build && jest",
51
+ "test": "jest",
52
52
  "test:watch": "jest --watch",
53
53
  "test:package": "node test-package.js",
54
54
  "typecheck": "tsc --noEmit && tsc --project tsconfig.test.json --noEmit",
55
55
  "lint": "eslint --ignore-path .gitignore .",
56
56
  "lint:fix": "eslint --ignore-path .gitignore --fix .",
57
57
  "format": "prettier --ignore-path .gitignore --write .",
58
- "prepublishOnly": "npm run build && npm run test && npm run test:package"
58
+ "prepublishOnly": "npm run build && npm run test && npm run test:package",
59
+ "generate:fonts": "npx tsx scripts/fetch-google-fonts.ts"
59
60
  },
60
61
  "devDependencies": {
61
62
  "@jest/globals": "^30.2.0",
@@ -71,6 +72,7 @@
71
72
  "eslint-config-prettier": "^9.1.0",
72
73
  "eslint-plugin-import": "^2.31.0",
73
74
  "jest": "^30.2.0",
75
+ "jest-environment-jsdom": "^30.2.0",
74
76
  "prettier": "^3.6.2",
75
77
  "ts-jest": "^29.4.5",
76
78
  "typescript": "^5.6.2",
@@ -78,10 +80,12 @@
78
80
  "vite-plugin-dts": "^4.5.4"
79
81
  },
80
82
  "dependencies": {
81
- "@shotstack/shotstack-canvas": "^1.5.3",
83
+ "@huggingface/transformers": "^3.0.0",
84
+ "@shotstack/shotstack-canvas": "^1.6.5",
82
85
  "fast-deep-equal": "^3.1.3",
83
86
  "howler": "^2.2.4",
84
87
  "mediabunny": "^1.11.2",
88
+ "opentype": "^0.1.2",
85
89
  "opentype.js": "^1.3.4",
86
90
  "pixi-filters": "^6.0.5",
87
91
  "pixi.js": "^8.5.2",
package/readme.md CHANGED
@@ -20,7 +20,6 @@ Try Shotstack Studio in your preferred framework:
20
20
 
21
21
  - Create video compositions with multiple tracks and clips
22
22
  - Visual timeline interface
23
- - WYSIWYG text editing
24
23
  - Multi-track, drag-and-drop clip manipulation with snap-to-grid
25
24
  - Use in conjunction with the [Shotstack Edit API](https://shotstack.io/docs/guide/getting-started/hello-world-using-curl/) to render video
26
25
  - Export to video via the browser
@@ -44,22 +43,22 @@ import { Edit, Canvas, Controls, Timeline } from "@shotstack/shotstack-studio";
44
43
  const response = await fetch("https://shotstack-assets.s3.amazonaws.com/templates/hello-world/hello.json");
45
44
  const template = await response.json();
46
45
 
47
- // 2. Initialize the edit
48
- const edit = new Edit(template.output.size, template.timeline.background);
46
+ // 2. Create Edit from template and load it
47
+ const edit = new Edit(template);
49
48
  await edit.load();
50
49
 
51
50
  // 3. Create a canvas to display the edit
52
- const canvas = new Canvas(template.output.size, edit);
51
+ const canvas = new Canvas(edit);
53
52
  await canvas.load(); // Renders to [data-shotstack-studio] element
54
53
 
55
- // 4. Load the template
56
- await edit.loadEdit(template);
57
-
58
- // 5. Initialize the Timeline
59
- const timeline = new Timeline(edit, { width: 1280, height: 300 });
60
- await timeline.load(); // Renders to [data-shotstack-timeline] element
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 }
58
+ });
59
+ await timeline.load();
61
60
 
62
- // 6. Add keyboard controls
61
+ // 5. Add keyboard controls
63
62
  const controls = new Controls(edit);
64
63
  await controls.load();
65
64
  ```
@@ -80,14 +79,12 @@ The Edit class represents a video project with its timeline, clips, and properti
80
79
  ```typescript
81
80
  import { Edit } from "@shotstack/shotstack-studio";
82
81
 
83
- // For schema validation only (e.g., in tests):
84
- import { EditSchema, ClipSchema } from "@shotstack/shotstack-studio/schema";
85
- // Create an edit with dimensions and background
86
- const edit = new Edit({ width: 1280, height: 720 }, "#000000");
82
+ // Create an edit from a template
83
+ const edit = new Edit(templateJson);
87
84
  await edit.load();
88
85
 
89
- // Load from template
90
- await edit.loadEdit(templateJson);
86
+ // Or reload a different template later
87
+ await edit.loadEdit(newTemplate);
91
88
 
92
89
  // Playback controls
93
90
  edit.play();
@@ -112,6 +109,8 @@ edit.deleteTrack(1);
112
109
  // Undo/Redo
113
110
  edit.undo();
114
111
  edit.redo();
112
+ edit.canUndo(); // Check if undo is available (useful for UI)
113
+ edit.canRedo(); // Check if redo is available
115
114
 
116
115
  // Get edit information
117
116
  const clip = edit.getClip(0, 0);
@@ -122,27 +121,48 @@ const duration = edit.totalDuration; // in milliseconds
122
121
 
123
122
  #### Events
124
123
 
125
- The Edit class provides an event system to listen for specific actions:
124
+ The Edit class provides a typed event system to listen for specific actions:
126
125
 
127
126
  ```typescript
127
+ import { Edit, EditEvent } from "@shotstack/shotstack-studio";
128
+
128
129
  // Listen for clip selection events
129
- edit.events.on("clip:selected", data => {
130
+ edit.events.on(EditEvent.ClipSelected, data => {
130
131
  console.log("Clip selected:", data.clip);
131
132
  console.log("Track index:", data.trackIndex);
132
133
  console.log("Clip index:", data.clipIndex);
133
134
  });
134
135
 
135
136
  // Listen for clip update events
136
- edit.events.on("clip:updated", data => {
137
- console.log("Previous state:", data.previous); // { clip, trackIndex, clipIndex }
138
- console.log("Current state:", data.current); // { clip, trackIndex, clipIndex }
137
+ edit.events.on(EditEvent.ClipUpdated, data => {
138
+ console.log("Previous state:", data.previous);
139
+ console.log("Current state:", data.current);
139
140
  });
141
+
142
+ // Listen for playback events
143
+ edit.events.on(EditEvent.PlaybackPlay, () => console.log("Playing"));
144
+ edit.events.on(EditEvent.PlaybackPause, () => console.log("Paused"));
140
145
  ```
141
146
 
142
147
  Available events:
143
148
 
144
- - `clip:selected` - Emitted when a clip is initially selected, providing data about the clip, its track index, and clip index.
145
- - `clip:updated` - Emitted when a clip's properties are modified, providing both previous and current states.
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`
156
+
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`
146
166
 
147
167
  ### Canvas
148
168
 
@@ -150,7 +170,7 @@ The Canvas class provides the visual rendering of the edit.
150
170
 
151
171
  ```typescript
152
172
  // Create and load the canvas
153
- const canvas = new Canvas(edit.size, edit);
173
+ const canvas = new Canvas(edit);
154
174
  await canvas.load();
155
175
 
156
176
  // Zoom and positioning
@@ -190,16 +210,17 @@ The Timeline class provides a visual timeline interface for editing.
190
210
  ```typescript
191
211
  import { Timeline } from "@shotstack/shotstack-studio";
192
212
 
193
- const timeline = new Timeline(edit, { width: 1280, height: 300 });
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
+ });
194
223
  await timeline.load();
195
-
196
- // Timeline features:
197
- // - Visual track and clip representation
198
- // - Drag-and-drop clip manipulation
199
- // - Clip resizing with edge detection
200
- // - Playhead control for navigation
201
- // - Snap-to-grid functionality
202
- // - Zoom and scroll controls
203
224
  ```
204
225
 
205
226
  ### VideoExporter
@@ -211,286 +232,66 @@ const exporter = new VideoExporter(edit, canvas);
211
232
  await exporter.export("my-video.mp4", 25); // filename, fps
212
233
  ```
213
234
 
214
- ## Theming
215
-
216
- Shotstack Studio supports theming for visual components. Currently, theming is available for the Timeline component, with Canvas theming coming in a future releases.
235
+ ## Merge Fields
217
236
 
218
- ### Built-in Themes
219
-
220
- The library includes pre-designed themes that you can use immediately:
237
+ Merge fields allow dynamic content substitution using `{{ FIELD_NAME }}` syntax in your templates.
221
238
 
222
239
  ```typescript
223
- import { Timeline } from "@shotstack/shotstack-studio";
224
- import darkTheme from "@shotstack/shotstack-studio/themes/dark.json";
225
- import minimalTheme from "@shotstack/shotstack-studio/themes/minimal.json";
240
+ import { Edit, EditEvent } from "@shotstack/shotstack-studio";
226
241
 
227
- // Apply a theme when creating the timeline
228
- const timeline = new Timeline(edit, { width: 1280, height: 300 }, { theme: darkTheme });
229
- ```
242
+ // Set a merge field value
243
+ edit.setMergeField("TITLE", "My Video Title");
244
+ edit.setMergeField("SUBTITLE", "A great subtitle");
230
245
 
231
- ### Custom Themes
246
+ // Get all registered merge fields
247
+ const fields = edit.getMergeFields();
232
248
 
233
- Create your own theme by defining colors and dimensions for each component:
234
-
235
- ```typescript
236
- const customTheme = {
237
- timeline: {
238
- // Main timeline colors
239
- background: "#1e1e1e",
240
- divider: "#1a1a1a",
241
- playhead: "#ff4444",
242
- snapGuide: "#888888",
243
- dropZone: "#00ff00",
244
- trackInsertion: "#00ff00",
245
-
246
- // Toolbar styling
247
- toolbar: {
248
- background: "#1a1a1a",
249
- surface: "#2a2a2a", // Button backgrounds
250
- hover: "#3a3a3a", // Button hover state
251
- active: "#007acc", // Button active state
252
- divider: "#3a3a3a", // Separator lines
253
- icon: "#888888", // Icon colors
254
- text: "#ffffff", // Text color
255
- height: 36 // Toolbar height in pixels
256
- },
257
-
258
- // Ruler styling
259
- ruler: {
260
- background: "#404040",
261
- text: "#ffffff", // Time labels
262
- markers: "#666666", // Time marker dots
263
- height: 40 // Ruler height in pixels
264
- },
265
-
266
- // Track styling
267
- tracks: {
268
- surface: "#2d2d2d", // Primary track color
269
- surfaceAlt: "#252525", // Alternating track color
270
- border: "#3a3a3a", // Track borders
271
- height: 60 // Track height in pixels
272
- },
273
-
274
- // Clip colors by asset type
275
- clips: {
276
- video: "#4a9eff",
277
- audio: "#00d4aa",
278
- image: "#f5a623",
279
- text: "#d0021b",
280
- shape: "#9013fe",
281
- html: "#50e3c2",
282
- luma: "#b8e986",
283
- default: "#8e8e93", // Unknown asset types
284
- selected: "#007acc", // Selection border
285
- radius: 4 // Corner radius in pixels
286
- }
287
- }
288
- // Canvas theming will be available in future releases
289
- // canvas: { ... }
290
- };
291
-
292
- const timeline = new Timeline(edit, { width: 1280, height: 300 }, { theme: customTheme });
249
+ // Listen for merge field changes
250
+ edit.events.on(EditEvent.MergeFieldUpdated, ({ field }) => {
251
+ console.log(`Field ${field.name} updated to:`, field.value);
252
+ });
293
253
  ```
294
254
 
295
- ### Theme Structure
296
-
297
- Themes are organized by component, making it intuitive to customize specific parts of the interface:
255
+ In templates, use placeholders that will be replaced with merge field values:
298
256
 
299
- - **Timeline**: Controls the appearance of the timeline interface
300
- - `toolbar`: Playback controls and buttons
301
- - `ruler`: Time markers and labels
302
- - `tracks`: Track backgrounds and borders
303
- - `clips`: Asset-specific colors and selection states
304
- - Global timeline properties (background, playhead, etc.)
305
-
306
- - **Canvas** (coming soon): Will control the appearance of the video preview area
307
-
308
- ## Template Format
309
-
310
- Templates use a JSON format with the following structure:
311
-
312
- ```typescript
257
+ ```json
313
258
  {
314
- timeline: {
315
- background: "#000000",
316
- fonts: [
317
- { src: "https://example.com/font.ttf" }
318
- ],
319
- tracks: [
320
- {
321
- clips: [
322
- {
323
- asset: {
324
- type: "image", // image, video, text, shape, audio
325
- src: "https://example.com/image.jpg",
326
- // Other asset properties depend on type
327
- },
328
- start: 0, // Start time in seconds
329
- length: 5, // Duration in seconds
330
- transition: { // Optional transitions
331
- in: "fade",
332
- out: "fade"
333
- },
334
- position: "center", // Positioning
335
- scale: 1, // Scale factor
336
- offset: {
337
- x: 0.1, // X-axis offset relative to position
338
- y: 0 // Y-axis offset relative to position
339
- }
340
- }
341
- ]
342
- }
343
- ]
344
- },
345
- output: {
346
- format: "mp4",
347
- size: {
348
- width: 1280,
349
- height: 720
350
- }
259
+ "asset": {
260
+ "type": "text",
261
+ "text": "{{ TITLE }}"
351
262
  }
352
263
  }
353
264
  ```
354
265
 
355
- ## License
356
-
357
- PolyForm Shield License 1.0.0
358
-
359
- ## API Reference
360
-
361
- ### Edit
266
+ ## Custom Toolbar Buttons
362
267
 
363
- The `Edit` class represents a video editing project with its timeline, clips, and properties.
268
+ Register custom toolbar buttons to extend the canvas toolbar with your own actions:
364
269
 
365
270
  ```typescript
366
- import { Edit } from "@shotstack/shotstack-studio";
367
- import { EditSchema } from "@shotstack/shotstack-studio";
368
- ```
369
-
370
- #### Constructor
371
-
372
- ```typescript
373
- constructor(size: Size, backgroundColor: string = "#ffffff")
374
- ```
375
-
376
- Creates a new Edit instance with the specified dimensions and background color.
377
-
378
- #### Properties
379
-
380
- - `assetLoader` - Asset loader instance for managing media assets
381
- - `events` - Event emitter for handling events
382
- - `playbackTime` - Current playback position in milliseconds
383
- - `totalDuration` - Total duration of the edit in milliseconds
384
-
385
- #### Methods
386
-
387
- - `async load()` - Initialize and prepare the edit for rendering
388
- - `async loadEdit(edit: EditType)` - Load an edit from a JSON template
389
- - `play()` - Start playback
390
- - `pause()` - Pause playback
391
- - `seek(target: number)` - Seek to a specific time in milliseconds
392
- - `stop()` - Stop playback and return to beginning
393
- - `addClip(trackIdx: number, clip: ClipType)` - Add a clip to a specific track
394
- - `deleteClip(trackIdx: number, clipIdx: number)` - Delete a clip
395
- - `getClip(trackIdx: number, clipIdx: number)` - Get a clip by track and index
396
- - `addTrack(trackIdx: number, track: TrackType)` - Add a new track
397
- - `getTrack(trackIdx: number)` - Get a track by index
398
- - `deleteTrack(trackIdx: number)` - Delete a track
399
- - `getEdit()` - Get the full edit configuration as a JSON object
400
- - `undo()` - Undo the last editing operation
401
- - `redo()` - Redo the last undone operation
402
-
403
- #### Events
404
-
405
- - `clip:selected` - Triggered when a clip is selected
406
- - `clip:updated` - Triggered when a clip is modified
407
- - `edit:undo` - Triggered when an undo operation is performed
408
- - `edit:redo` - Triggered when a redo operation is performed
409
-
410
- ### Canvas
411
-
412
- The `Canvas` class provides the visual rendering of the edit.
413
-
414
- ```typescript
415
- import { Canvas } from "@shotstack/shotstack-studio";
416
- import type { Size } from "@shotstack/shotstack-studio";
417
- ```
418
-
419
- #### Constructor
420
-
421
- ```typescript
422
- constructor(size: Size, edit: Edit)
423
- ```
424
-
425
- Creates a new canvas with specified dimensions for rendering the edit.
426
-
427
- #### Methods
428
-
429
- - `async load()` - Initialize the canvas and add it to the DOM
430
- - `centerEdit()` - Center the edit in the canvas
431
- - `zoomToFit()` - Zoom to fit the entire edit
432
- - `setZoom(zoom: number)` - Set zoom level
433
- - `dispose()` - Clean up resources and remove the canvas from the DOM
434
-
435
- ### Controls
436
-
437
- The `Controls` class adds keyboard controls for playback.
438
-
439
- ```typescript
440
- import { Controls } from "@shotstack/shotstack-studio";
441
- ```
442
-
443
- #### Constructor
444
-
445
- ```typescript
446
- constructor(edit: Edit)
447
- ```
448
-
449
- Creates a new controls instance for the provided Edit.
450
-
451
- #### Methods
452
-
453
- - `async load()` - Set up event listeners for keyboard controls
454
-
455
- ### Timeline
456
-
457
- The `Timeline` class provides a visual timeline interface for video editing.
458
-
459
- ```typescript
460
- import { Timeline } from "@shotstack/shotstack-studio";
461
- ```
462
-
463
- #### Constructor
464
-
465
- ```typescript
466
- constructor(edit: Edit, size: { width: number, height: number }, themeOptions?: TimelineThemeOptions)
467
- ```
468
-
469
- Creates a new timeline interface for the provided Edit.
470
-
471
- #### Methods
472
-
473
- - `async load()` - Initialize the timeline and add it to the DOM
474
- - `setTheme(themeOptions: TimelineThemeOptions)` - Change the timeline theme
475
- - `setOptions(options: Partial<TimelineOptions>)` - Update timeline options
476
- - `dispose()` - Clean up resources and remove from DOM
477
-
478
- ### VideoExporter
479
-
480
- The `VideoExporter` class handles exporting the edit to MP4.
271
+ // Register a custom button
272
+ edit.registerToolbarButton({
273
+ id: "add-text",
274
+ icon: `<svg viewBox="0 0 16 16">...</svg>`,
275
+ tooltip: "Add Text",
276
+ event: "text:requested",
277
+ dividerBefore: true // Optional: add a divider before this button
278
+ });
481
279
 
482
- ```typescript
483
- import { VideoExporter } from "@shotstack/shotstack-studio";
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
+ });
288
+ });
484
289
  ```
485
290
 
486
- #### Constructor
487
-
488
- ```typescript
489
- constructor(edit: Edit, canvas: Canvas)
490
- ```
291
+ ## API Reference
491
292
 
492
- Creates a new exporter for the provided Edit and Canvas.
293
+ For complete schema and type definitions, see the [Shotstack API Reference](https://shotstack.io/docs/api/#tocs_edit).
493
294
 
494
- #### Methods
295
+ ## License
495
296
 
496
- - `async export(filename: string = "shotstack-export.mp4", fps: number = 25)` - Export the edit to an MP4 video file
297
+ PolyForm Shield License 1.0.0