@shotstack/shotstack-studio 2.0.0-beta.8 → 2.0.0-rc.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.
@@ -0,0 +1,476 @@
1
+ import { components } from '@shotstack/schemas';
2
+
3
+ declare type Clip = components["schemas"]["Clip"];
4
+
5
+ declare type ClipLocation = {
6
+ trackIndex: number;
7
+ clipIndex: number;
8
+ };
9
+
10
+ /**
11
+ * Reference to a clip from the document (source of truth).
12
+ * Contains original timing values like "auto", "end", and alias references.
13
+ * Used in public SDK events so consumers see the document state.
14
+ */
15
+ declare type ClipReference = ClipLocation & {
16
+ clip: Clip;
17
+ };
18
+
19
+ declare type Destination = components["schemas"]["Destinations"];
20
+
21
+ export declare class Edit {
22
+ private static readonly MAX_HISTORY_SIZE;
23
+ private document;
24
+ private backgroundColor;
25
+ private tracks;
26
+ playbackTime: Seconds;
27
+ totalDuration: Seconds;
28
+ isPlaying: boolean;
29
+ private get clips();
30
+ private internalEvents;
31
+ events: ReadonlyEventEmitter<EditEventMap>;
32
+ private canvas;
33
+ private timingManager;
34
+ private lumaMaskController;
35
+ private playerReconciler;
36
+ private outputSettings;
37
+ private selectionManager;
38
+ private commandHistory;
39
+ private commandIndex;
40
+ private commandQueue;
41
+ private clipsToDispose;
42
+ private clipErrors;
43
+ private playerByClipId;
44
+ private lumaContentRelations;
45
+ private fontMetadata;
46
+ private isBatchingEvents;
47
+ private syncCorrectionCount;
48
+ private isExporting;
49
+ private lastResolved;
50
+ /**
51
+ * Create an Edit instance from a template configuration.
52
+ */
53
+ constructor(template: Edit_2);
54
+ /**
55
+ * Load the edit session.
56
+ */
57
+ load(): Promise<void>;
58
+ /**
59
+ * Initialize runtime from the document.
60
+ */
61
+ private initializeFromDocument;
62
+ play(): void;
63
+ pause(): void;
64
+ seek(target: Seconds): void;
65
+ stop(): void;
66
+ /**
67
+ * Reload the edit with a new configuration (hot-reload).
68
+ */
69
+ loadEdit(edit: Edit_2): Promise<void>;
70
+ private loadSoundtrack;
71
+ getEdit(): Edit_2;
72
+ addClip(trackIdx: number, clip: Clip): void | Promise<void>;
73
+ getClip(trackIdx: number, clipIdx: number): Clip | null;
74
+ /**
75
+ * Clear the error for a deleted clip and shift indices for remaining errors.
76
+ */
77
+ private clearClipErrorAndShift;
78
+ deleteClip(trackIdx: number, clipIdx: number): Promise<void>;
79
+ splitClip(trackIndex: number, clipIndex: number, splitTime: number): Promise<void>;
80
+ addTrack(trackIdx: number, track: Track): Promise<void>;
81
+ getTrack(trackIdx: number): Track | null;
82
+ deleteTrack(trackIdx: number): void;
83
+ undo(): Promise<void>;
84
+ redo(): Promise<void>;
85
+ /**
86
+ * Add a command to history without executing it.
87
+ */
88
+ private addCommandToHistory;
89
+ updateClip(trackIdx: number, clipIdx: number, updates: Partial<Clip>): Promise<void>;
90
+ /**
91
+ * Handle command result - only add to history if successful.
92
+ */
93
+ private handleCommandResult;
94
+ /**
95
+ * Detects merge field placeholders in the raw edit before substitution.
96
+ */
97
+ private detectMergeFieldBindings;
98
+ /**
99
+ * Recursively walks an object to find merge field placeholders.
100
+ */
101
+ private detectBindingsInObject;
102
+ /**
103
+ * Checks if edit has structural changes requiring full reload.
104
+ *
105
+ * TODO: Expand granular path to handle more cases:
106
+ * - Clip add/remove: Use existing addClip()/deleteClip() commands
107
+ * - Soundtrack changes: Add/remove AudioPlayer via commands
108
+ * - Font changes: Load new fonts incrementally
109
+ * - Merge field changes: Re-resolve affected clips
110
+ */
111
+ private hasStructuralChanges;
112
+ /**
113
+ * Transfers existing clip IDs from the current document to the new edit configuration.
114
+ */
115
+ private preserveClipIdsForGranularUpdate;
116
+ /**
117
+ * Applies granular changes without full reload.
118
+ * @param newEdit - The new edit configuration
119
+ * @param oldTracks - The old tracks (captured before document update)
120
+ * @param oldOutput - The old output settings (captured before document update)
121
+ */
122
+ private applyGranularChanges;
123
+ private queueDisposeClip;
124
+ /**
125
+ * Remove fonts from timeline.fonts that are no longer referenced by any clip.
126
+ */
127
+ private cleanupUnusedFonts;
128
+ /**
129
+ * Extract the filename (without extension) from a font URL.
130
+ */
131
+ private extractFilenameFromUrl;
132
+ private disposeClip;
133
+ private unloadClipAssets;
134
+ private movePlayerToTrackContainer;
135
+ private addPlayer;
136
+ setOutputSize(width: number, height: number): Promise<void>;
137
+ setOutputFps(fps: number): Promise<void>;
138
+ getOutputFps(): number;
139
+ setOutputFormat(format: string): Promise<void>;
140
+ getOutputFormat(): string;
141
+ setOutputDestinations(destinations: Destination[]): Promise<void>;
142
+ getOutputDestinations(): Destination[];
143
+ setOutputResolution(resolution: string): Promise<void>;
144
+ getOutputResolution(): string | undefined;
145
+ setOutputAspectRatio(aspectRatio: string): Promise<void>;
146
+ getOutputAspectRatio(): string | undefined;
147
+ setTimelineBackground(color: string): Promise<void>;
148
+ private setTimelineBackgroundInternal;
149
+ getTimelineBackground(): string;
150
+ /**
151
+ * Find the content clip that best matches a luma (by temporal overlap).
152
+ */
153
+ private findBestContentMatch;
154
+ private setupIntentListeners;
155
+ }
156
+
157
+ declare type Edit_2 = components["schemas"]["Edit"];
158
+
159
+ declare const EditEvent: {
160
+ readonly PlaybackPlay: "playback:play";
161
+ readonly PlaybackPause: "playback:pause";
162
+ readonly TimelineUpdated: "timeline:updated";
163
+ readonly TimelineBackgroundChanged: "timeline:backgroundChanged";
164
+ readonly ClipAdded: "clip:added";
165
+ readonly ClipSplit: "clip:split";
166
+ readonly ClipSelected: "clip:selected";
167
+ readonly ClipUpdated: "clip:updated";
168
+ readonly ClipDeleted: "clip:deleted";
169
+ readonly ClipRestored: "clip:restored";
170
+ readonly ClipCopied: "clip:copied";
171
+ readonly ClipLoadFailed: "clip:loadFailed";
172
+ readonly ClipUnresolved: "clip:unresolved";
173
+ readonly SelectionCleared: "selection:cleared";
174
+ readonly EditChanged: "edit:changed";
175
+ readonly EditUndo: "edit:undo";
176
+ readonly EditRedo: "edit:redo";
177
+ readonly TrackAdded: "track:added";
178
+ readonly TrackRemoved: "track:removed";
179
+ readonly DurationChanged: "duration:changed";
180
+ readonly OutputResized: "output:resized";
181
+ readonly OutputResolutionChanged: "output:resolutionChanged";
182
+ readonly OutputAspectRatioChanged: "output:aspectRatioChanged";
183
+ readonly OutputFpsChanged: "output:fpsChanged";
184
+ readonly OutputFormatChanged: "output:formatChanged";
185
+ readonly OutputDestinationsChanged: "output:destinationsChanged";
186
+ readonly MergeFieldChanged: "mergefield:changed";
187
+ readonly LumaAttached: "luma:attached";
188
+ readonly LumaDetached: "luma:detached";
189
+ };
190
+
191
+ declare type EditEventMap = {
192
+ [EditEvent.PlaybackPlay]: void;
193
+ [EditEvent.PlaybackPause]: void;
194
+ /** Contains the document (source of truth) with original timing values like "auto", "end" */
195
+ [EditEvent.TimelineUpdated]: {
196
+ current: Edit_2;
197
+ };
198
+ [EditEvent.TimelineBackgroundChanged]: {
199
+ color: string;
200
+ };
201
+ [EditEvent.ClipAdded]: ClipLocation;
202
+ [EditEvent.ClipSplit]: {
203
+ trackIndex: number;
204
+ originalClipIndex: number;
205
+ newClipIndex: number;
206
+ };
207
+ [EditEvent.ClipSelected]: ClipReference;
208
+ [EditEvent.ClipUpdated]: {
209
+ previous: ClipReference;
210
+ current: ClipReference;
211
+ };
212
+ [EditEvent.ClipDeleted]: ClipLocation;
213
+ [EditEvent.ClipRestored]: ClipLocation;
214
+ [EditEvent.ClipCopied]: ClipLocation;
215
+ [EditEvent.ClipLoadFailed]: ClipLocation & {
216
+ error: string;
217
+ assetType: string;
218
+ };
219
+ [EditEvent.ClipUnresolved]: ClipLocation & {
220
+ assetType: string;
221
+ clipId: string;
222
+ };
223
+ [EditEvent.SelectionCleared]: void;
224
+ [EditEvent.EditChanged]: {
225
+ source: string;
226
+ timestamp: number;
227
+ };
228
+ [EditEvent.EditUndo]: {
229
+ command: string;
230
+ };
231
+ [EditEvent.EditRedo]: {
232
+ command: string;
233
+ };
234
+ [EditEvent.TrackAdded]: {
235
+ trackIndex: number;
236
+ totalTracks: number;
237
+ };
238
+ [EditEvent.TrackRemoved]: {
239
+ trackIndex: number;
240
+ };
241
+ [EditEvent.DurationChanged]: {
242
+ duration: number;
243
+ };
244
+ [EditEvent.OutputResized]: {
245
+ width: number;
246
+ height: number;
247
+ };
248
+ [EditEvent.OutputResolutionChanged]: {
249
+ resolution: Output["resolution"];
250
+ };
251
+ [EditEvent.OutputAspectRatioChanged]: {
252
+ aspectRatio: Output["aspectRatio"];
253
+ };
254
+ [EditEvent.OutputFpsChanged]: {
255
+ fps: number;
256
+ };
257
+ [EditEvent.OutputFormatChanged]: {
258
+ format: Output["format"];
259
+ };
260
+ [EditEvent.OutputDestinationsChanged]: {
261
+ destinations: Destination[];
262
+ };
263
+ [EditEvent.MergeFieldChanged]: {
264
+ fields: MergeField[];
265
+ };
266
+ [EditEvent.LumaAttached]: ClipLocation & {
267
+ lumaSrc: string;
268
+ lumaClipIndex: number;
269
+ };
270
+ [EditEvent.LumaDetached]: ClipLocation;
271
+ };
272
+
273
+ declare class EventEmitter<TEventPayloadMap extends EventPayloadMap = EventPayloadMap> {
274
+ private readonly events;
275
+ constructor();
276
+ on<TEventName extends keyof TEventPayloadMap>(name: TEventName, listener: Listener<TEventPayloadMap[TEventName]>): () => void;
277
+ once<TEventName extends keyof TEventPayloadMap>(name: TEventName, listener: Listener<TEventPayloadMap[TEventName]>): () => void;
278
+ off<TEventName extends keyof TEventPayloadMap>(name: TEventName, listener: Listener<TEventPayloadMap[TEventName]>): void;
279
+ }
280
+
281
+ declare type EventPayloadMap<TPayload = any> = Record<string, TPayload>;
282
+
283
+ declare type Listener<TPayload = any> = (payload: TPayload) => void;
284
+
285
+ /**
286
+ * Merge field types for the Shotstack Studio SDK.
287
+ *
288
+ * Merge fields allow dynamic content substitution using {{ FIELD_NAME }} syntax.
289
+ * Values are replaced at render time, enabling template-based video generation.
290
+ */
291
+ /**
292
+ * A merge field definition used throughout the SDK.
293
+ */
294
+ export declare interface MergeField {
295
+ /** Field identifier (uppercase convention: MY_FIELD) */
296
+ name: string;
297
+ /** Default value used for preview when no runtime value is provided */
298
+ defaultValue: string;
299
+ /** Optional description for UI display */
300
+ description?: string;
301
+ }
302
+
303
+ export declare class MergeFieldService {
304
+ private fields;
305
+ private events;
306
+ constructor(events: EventEmitter<EditEventMap>);
307
+ /**
308
+ * Register or update a merge field.
309
+ * @param field The merge field to register
310
+ * @param options.silent If true, suppresses event emission (for command-based operations)
311
+ */
312
+ register(field: MergeField, options?: {
313
+ silent?: boolean;
314
+ }): void;
315
+ /**
316
+ * Remove a merge field by name.
317
+ * @param name The field name to remove
318
+ * @param options.silent If true, suppresses event emission (for command-based operations)
319
+ */
320
+ remove(name: string, options?: {
321
+ silent?: boolean;
322
+ }): boolean;
323
+ /** Get a merge field by name */
324
+ get(name: string): MergeField | undefined;
325
+ /** Get all registered merge fields */
326
+ getAll(): MergeField[];
327
+ /** Clear all merge fields */
328
+ clear(): void;
329
+ /**
330
+ * Apply merge field substitutions to a string.
331
+ * Replaces {{ FIELD_NAME }} patterns with their default values.
332
+ */
333
+ resolve(input: string): string;
334
+ /**
335
+ * Resolve a merge field template to a numeric value.
336
+ * Returns the number if successful, or null if:
337
+ * - Input is not a merge field template
338
+ * - Resolved value is not a valid number
339
+ */
340
+ resolveToNumber(input: string): number | null;
341
+ /**
342
+ * Check if a string contains unresolved merge fields.
343
+ * Returns true if any {{ FIELD_NAME }} patterns remain after resolution.
344
+ */
345
+ hasUnresolved(input: string): boolean;
346
+ /**
347
+ * Extract the first merge field name from a string.
348
+ * Returns null if no merge field pattern is found.
349
+ */
350
+ extractFieldName(input: string): string | null;
351
+ /** Check if a string is a merge field template (contains {{ FIELD }}) */
352
+ isMergeFieldTemplate(input: string): boolean;
353
+ /** Create a merge field template string from a field name */
354
+ createTemplate(fieldName: string): string;
355
+ /** Export fields in Shotstack API format ({ find, replace }) */
356
+ toSerializedArray(): SerializedMergeField[];
357
+ /** Import fields from Shotstack API format (does not emit event - called during loadEdit) */
358
+ loadFromSerialized(fields: SerializedMergeField[]): void;
359
+ /** Generate a unique field name with a given prefix (e.g., MEDIA_1, MEDIA_2) */
360
+ generateUniqueName(prefix: string): string;
361
+ }
362
+
363
+ declare type Output = components["schemas"]["Output"];
364
+
365
+ /**
366
+ * Read-only view of an EventEmitter that only exposes subscription methods.
367
+ * Used as the public `events` type on Edit to prevent consumers from emitting events.
368
+ */
369
+ declare interface ReadonlyEventEmitter<TEventPayloadMap extends EventPayloadMap> {
370
+ on<K extends keyof TEventPayloadMap>(name: K, listener: Listener<TEventPayloadMap[K]>): () => void;
371
+ once<K extends keyof TEventPayloadMap>(name: K, listener: Listener<TEventPayloadMap[K]>): () => void;
372
+ off<K extends keyof TEventPayloadMap>(name: K, listener: Listener<TEventPayloadMap[K]>): void;
373
+ }
374
+
375
+ /**
376
+ * A number representing time in seconds.
377
+ * Used at API/configuration boundaries where users specify timing.
378
+ */
379
+ declare type Seconds = number & {
380
+ readonly [SecondsSymbol]: typeof SecondsSymbol;
381
+ };
382
+
383
+ declare const SecondsSymbol: unique symbol;
384
+
385
+ /**
386
+ * Serialized format for JSON export (matches Shotstack API).
387
+ * The replace value can be any type - strings, numbers, booleans, objects.
388
+ */
389
+ declare interface SerializedMergeField {
390
+ find: string;
391
+ replace: unknown;
392
+ }
393
+
394
+ /**
395
+ * Extended Edit with Shotstack-specific capabilities.
396
+ *
397
+ * This class is for Shotstack products only.
398
+ * External SDK consumers should use the base `Edit` class.
399
+ */
400
+ export declare class ShotstackEdit extends Edit {
401
+ private isUpdatingMergeFields;
402
+ /**
403
+ * Merge field service for managing dynamic content placeholders.
404
+ * Use this to register, update, and query merge fields in templates.
405
+ */
406
+ get mergeFields(): MergeFieldService;
407
+ /**
408
+ * Apply a merge field to a clip property.
409
+ */
410
+ applyMergeField(clipId: string, propertyPath: string, fieldName: string, value: string, originalValue?: string): Promise<void>;
411
+ /**
412
+ * Remove a merge field from a clip property, restoring the original value.
413
+ */
414
+ removeMergeField(clipId: string, propertyPath: string, restoreValue: string): Promise<void>;
415
+ /**
416
+ * Get the merge field name for a clip property, if any.
417
+ */
418
+ getMergeFieldForProperty(clipId: string, propertyPath: string): string | null;
419
+ /**
420
+ * Update the placeholder value of a merge field.
421
+ */
422
+ updateMergeFieldValueLive(fieldName: string, newValue: string): void;
423
+ /**
424
+ * Check if a merge field is used for asset.src in any clip.
425
+ * Used by UI to determine if URL validation should be applied.
426
+ */
427
+ isSrcMergeField(fieldName: string): boolean;
428
+ /**
429
+ * Check if a value is type-compatible with all properties a merge field is bound to.
430
+ * Temporarily swaps the field value, resolves each bound clip, and validates
431
+ * against ClipSchema — the same Zod schema used at load time.
432
+ */
433
+ validateMergeFieldValue(fieldName: string, value: string): string | null;
434
+ /**
435
+ * Check if a value is compatible with a specific clip property via Zod schema validation.
436
+ * Used by the merge field label manager to filter compatible fields in dropdowns.
437
+ */
438
+ isValueCompatibleWithClipProperty(clipId: string, propertyPath: string, value: string): boolean;
439
+ /** Check if any binding in a clip references the given field name. */
440
+ private clipUsesField;
441
+ /**
442
+ * Remove a merge field globally from all clips and the registry.
443
+ */
444
+ deleteMergeFieldGlobally(fieldName: string): Promise<void>;
445
+ /**
446
+ * Convert all text assets and log the resulting template JSON to console.
447
+ */
448
+ convertAllTextAssets(): Promise<{
449
+ richText: number;
450
+ svg: number;
451
+ }>;
452
+ /**
453
+ * Map TextAsset vertical alignment to RichTextAsset vertical alignment.
454
+ * TextAsset uses "center", RichTextAsset uses "middle".
455
+ */
456
+ private mapVerticalAlign;
457
+ /**
458
+ * Convert a text clip to rich-text format (pure JSON transformation).
459
+ */
460
+ private convertTextClipToRichText;
461
+ /**
462
+ * Helper: Update merge field bindings and document clip data for a player.
463
+ * Must update both bindings and clip data because the resolver reads clip data directly.
464
+ */
465
+ private updateMergeFieldBindings;
466
+ /** Helper: Check if and how a clip uses a specific merge field */
467
+ private getMergeFieldUsage;
468
+ /**
469
+ * Helper: Find and restore merge field occurrences in a clip
470
+ */
471
+ private restoreMergeFieldInClip;
472
+ }
473
+
474
+ declare type Track = components["schemas"]["Track"];
475
+
476
+ export { }