@webpacked-timeline/core 1.0.0-beta.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.
- package/CHANGELOG.md +36 -0
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/dist/chunk-27XCNVPR.js +5969 -0
- package/dist/chunk-6PDBJDHM.js +2263 -0
- package/dist/chunk-BWPS6NQT.js +7465 -0
- package/dist/chunk-FBOYSUYV.js +1280 -0
- package/dist/chunk-FR632TZX.js +1870 -0
- package/dist/chunk-HW4Z7YLJ.js +1242 -0
- package/dist/chunk-HWW62IFH.js +5424 -0
- package/dist/chunk-I2GZXRH4.js +4790 -0
- package/dist/chunk-JQZE3OK4.js +1255 -0
- package/dist/chunk-KF7JNK2F.js +1864 -0
- package/dist/chunk-KR3P2DYK.js +5655 -0
- package/dist/chunk-MO5DSFSW.js +2214 -0
- package/dist/chunk-MQAW33RJ.js +5530 -0
- package/dist/chunk-N4WUWZZX.js +2833 -0
- package/dist/chunk-NRJV7I4C.js +1331 -0
- package/dist/chunk-NXG52532.js +2230 -0
- package/dist/chunk-PVXF67CN.js +1278 -0
- package/dist/chunk-QSB6DHIF.js +5429 -0
- package/dist/chunk-QYWJT7HR.js +5837 -0
- package/dist/chunk-SWBRCMW7.js +7466 -0
- package/dist/chunk-TAT3ULSV.js +2214 -0
- package/dist/chunk-TTDP5JUM.js +2228 -0
- package/dist/chunk-UAGP4VPG.js +1739 -0
- package/dist/chunk-WIG6SY7A.js +1183 -0
- package/dist/chunk-YJ2K5N2R.js +6187 -0
- package/dist/index-3Lr_vKBd.d.cts +2810 -0
- package/dist/index-3Lr_vKBd.d.ts +2810 -0
- package/dist/index-7IPJn1yM.d.cts +1146 -0
- package/dist/index-7IPJn1yM.d.ts +1146 -0
- package/dist/index-B0xOv0V0.d.cts +3259 -0
- package/dist/index-B0xOv0V0.d.ts +3259 -0
- package/dist/index-B2m3zwg7.d.cts +1381 -0
- package/dist/index-B2m3zwg7.d.ts +1381 -0
- package/dist/index-B3sUrU_X.d.cts +1249 -0
- package/dist/index-B3sUrU_X.d.ts +1249 -0
- package/dist/index-B6wla7ZJ.d.cts +2751 -0
- package/dist/index-B6wla7ZJ.d.ts +2751 -0
- package/dist/index-BIv8RWWT.d.cts +1574 -0
- package/dist/index-BIv8RWWT.d.ts +1574 -0
- package/dist/index-BJv6hDHL.d.cts +3255 -0
- package/dist/index-BJv6hDHL.d.ts +3255 -0
- package/dist/index-BUCimS2e.d.cts +1393 -0
- package/dist/index-BUCimS2e.d.ts +1393 -0
- package/dist/index-Bw_nvNcG.d.cts +1275 -0
- package/dist/index-Bw_nvNcG.d.ts +1275 -0
- package/dist/index-ByG0gOtd.d.cts +1167 -0
- package/dist/index-ByG0gOtd.d.ts +1167 -0
- package/dist/index-CDGd2XXv.d.cts +2492 -0
- package/dist/index-CDGd2XXv.d.ts +2492 -0
- package/dist/index-CznAVeJ6.d.cts +1145 -0
- package/dist/index-CznAVeJ6.d.ts +1145 -0
- package/dist/index-DQD9IMh7.d.cts +2534 -0
- package/dist/index-DQD9IMh7.d.ts +2534 -0
- package/dist/index-Dl3qtJEI.d.cts +2178 -0
- package/dist/index-Dl3qtJEI.d.ts +2178 -0
- package/dist/index-DnE2A-Nz.d.cts +2603 -0
- package/dist/index-DnE2A-Nz.d.ts +2603 -0
- package/dist/index-DrOA6QmW.d.cts +2492 -0
- package/dist/index-DrOA6QmW.d.ts +2492 -0
- package/dist/index-Vpa3rPEM.d.cts +1402 -0
- package/dist/index-Vpa3rPEM.d.ts +1402 -0
- package/dist/index-jP6BomSd.d.cts +2640 -0
- package/dist/index-jP6BomSd.d.ts +2640 -0
- package/dist/index-wiGRwVyY.d.cts +3259 -0
- package/dist/index-wiGRwVyY.d.ts +3259 -0
- package/dist/index.cjs +7386 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +263 -0
- package/dist/internal.cjs +7721 -0
- package/dist/internal.d.cts +704 -0
- package/dist/internal.d.ts +704 -0
- package/dist/internal.js +405 -0
- package/package.json +58 -0
|
@@ -0,0 +1,1275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FRAME-BASED TIME REPRESENTATION
|
|
3
|
+
*
|
|
4
|
+
* This file defines the foundational time system for the entire timeline engine.
|
|
5
|
+
*
|
|
6
|
+
* WHY FRAMES INSTEAD OF MILLISECONDS?
|
|
7
|
+
* - Frames are discrete, integer values (no floating-point drift)
|
|
8
|
+
* - Frames are deterministic (same input always produces same output)
|
|
9
|
+
* - Frames match how video editors actually work (frame-accurate editing)
|
|
10
|
+
* - Frames prevent rounding errors that accumulate over time
|
|
11
|
+
*
|
|
12
|
+
* CRITICAL RULE:
|
|
13
|
+
* All time values in the timeline state MUST be stored as frames.
|
|
14
|
+
* Never store time as seconds or milliseconds in state.
|
|
15
|
+
*
|
|
16
|
+
* USAGE:
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const fps = 30 as FrameRate;
|
|
19
|
+
* const frame = 150 as Frame; // 5 seconds at 30fps
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Frame - A discrete point in time, measured in frames
|
|
24
|
+
*
|
|
25
|
+
* This is a "branded type" - it's just a number at runtime,
|
|
26
|
+
* but TypeScript treats it as a distinct type to prevent mixing
|
|
27
|
+
* frames with regular numbers accidentally.
|
|
28
|
+
*
|
|
29
|
+
* INVARIANT: Frame values must be non-negative integers
|
|
30
|
+
*/
|
|
31
|
+
type Frame = number & {
|
|
32
|
+
readonly __brand: 'Frame';
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* FrameRate - Frames per second (FPS)
|
|
36
|
+
*
|
|
37
|
+
* Common values: 24, 25, 30, 60
|
|
38
|
+
*
|
|
39
|
+
* INVARIANT: FrameRate must be a positive number
|
|
40
|
+
*/
|
|
41
|
+
type FrameRate = number & {
|
|
42
|
+
readonly __brand: 'FrameRate';
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Create a Frame value from a number
|
|
46
|
+
*
|
|
47
|
+
* This function validates and rounds the input to ensure it's a valid frame number.
|
|
48
|
+
*
|
|
49
|
+
* @param value - The frame number (will be rounded to nearest integer)
|
|
50
|
+
* @returns A valid Frame value
|
|
51
|
+
* @throws Error if value is negative
|
|
52
|
+
*/
|
|
53
|
+
declare function frame(value: number): Frame;
|
|
54
|
+
/**
|
|
55
|
+
* Create a FrameRate value from a number
|
|
56
|
+
*
|
|
57
|
+
* @param value - The frames per second
|
|
58
|
+
* @returns A valid FrameRate value
|
|
59
|
+
* @throws Error if value is not positive
|
|
60
|
+
*/
|
|
61
|
+
declare function frameRate(value: number): FrameRate;
|
|
62
|
+
/**
|
|
63
|
+
* Check if a value is a valid frame number
|
|
64
|
+
*
|
|
65
|
+
* @param value - The value to check
|
|
66
|
+
* @returns true if the value is a non-negative integer
|
|
67
|
+
*/
|
|
68
|
+
declare function isValidFrame(value: number): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Check if a value is a valid frame rate
|
|
71
|
+
*
|
|
72
|
+
* @param value - The value to check
|
|
73
|
+
* @returns true if the value is positive
|
|
74
|
+
*/
|
|
75
|
+
declare function isValidFrameRate(value: number): boolean;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* CLIP MODEL
|
|
79
|
+
*
|
|
80
|
+
* A Clip represents a time-bound reference to an Asset on a Track.
|
|
81
|
+
*
|
|
82
|
+
* WHAT IS A CLIP?
|
|
83
|
+
* - A piece of media placed at a specific time on the timeline
|
|
84
|
+
* - References an Asset (the source media)
|
|
85
|
+
* - Defines WHEN it appears (timeline bounds)
|
|
86
|
+
* - Defines WHAT portion of the asset to play (media bounds)
|
|
87
|
+
*
|
|
88
|
+
* KEY CONCEPTS:
|
|
89
|
+
*
|
|
90
|
+
* 1. TIMELINE BOUNDS:
|
|
91
|
+
* - timelineStart: When the clip appears on the timeline
|
|
92
|
+
* - timelineEnd: When the clip ends on the timeline
|
|
93
|
+
* - Duration = timelineEnd - timelineStart
|
|
94
|
+
*
|
|
95
|
+
* 2. MEDIA BOUNDS:
|
|
96
|
+
* - mediaIn: Start frame in the source asset
|
|
97
|
+
* - mediaOut: End frame in the source asset
|
|
98
|
+
* - Defines which portion of the asset to play
|
|
99
|
+
*
|
|
100
|
+
* EXAMPLE:
|
|
101
|
+
* ```typescript
|
|
102
|
+
* // A 10-second video asset
|
|
103
|
+
* const asset = { id: 'asset_1', duration: frame(300) }; // 300 frames at 30fps
|
|
104
|
+
*
|
|
105
|
+
* // A clip that shows 3 seconds of the video (frames 60-150)
|
|
106
|
+
* // starting at 5 seconds on the timeline
|
|
107
|
+
* const clip: Clip = {
|
|
108
|
+
* id: 'clip_1',
|
|
109
|
+
* assetId: 'asset_1',
|
|
110
|
+
* trackId: 'track_1',
|
|
111
|
+
* timelineStart: frame(150), // 5 seconds * 30fps
|
|
112
|
+
* timelineEnd: frame(240), // 8 seconds * 30fps
|
|
113
|
+
* mediaIn: frame(60), // Start at 2 seconds into the asset
|
|
114
|
+
* mediaOut: frame(150), // End at 5 seconds into the asset
|
|
115
|
+
* };
|
|
116
|
+
* ```
|
|
117
|
+
*
|
|
118
|
+
* INVARIANTS (Phase 1 - No Speed Remapping):
|
|
119
|
+
* - timelineEnd > timelineStart
|
|
120
|
+
* - mediaOut > mediaIn
|
|
121
|
+
* - timelineEnd - timelineStart === mediaOut - mediaIn (same duration)
|
|
122
|
+
* - mediaOut <= asset.duration (can't exceed asset bounds)
|
|
123
|
+
* - All frame values must be non-negative
|
|
124
|
+
*
|
|
125
|
+
* FUTURE: When speed remapping is added, the duration constraint will change.
|
|
126
|
+
*/
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Clip - A time-bound reference to an Asset
|
|
130
|
+
*/
|
|
131
|
+
interface Clip {
|
|
132
|
+
/** Unique identifier */
|
|
133
|
+
id: string;
|
|
134
|
+
/** Reference to the asset this clip uses */
|
|
135
|
+
assetId: string;
|
|
136
|
+
/** Reference to the track this clip belongs to */
|
|
137
|
+
trackId: string;
|
|
138
|
+
/** Start frame on the timeline */
|
|
139
|
+
timelineStart: Frame;
|
|
140
|
+
/** End frame on the timeline */
|
|
141
|
+
timelineEnd: Frame;
|
|
142
|
+
/** Start frame in the source asset */
|
|
143
|
+
mediaIn: Frame;
|
|
144
|
+
/** End frame in the source asset */
|
|
145
|
+
mediaOut: Frame;
|
|
146
|
+
/** Optional label for the clip */
|
|
147
|
+
label?: string;
|
|
148
|
+
/** Optional metadata for custom use cases */
|
|
149
|
+
metadata?: Record<string, unknown>;
|
|
150
|
+
/** Optional link group ID - clips in same group move/delete together */
|
|
151
|
+
linkGroupId?: string;
|
|
152
|
+
/** Optional group ID - for visual organization */
|
|
153
|
+
groupId?: string;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Create a new clip
|
|
157
|
+
*
|
|
158
|
+
* @param params - Clip parameters
|
|
159
|
+
* @returns A new Clip object
|
|
160
|
+
*/
|
|
161
|
+
declare function createClip(params: {
|
|
162
|
+
id: string;
|
|
163
|
+
assetId: string;
|
|
164
|
+
trackId: string;
|
|
165
|
+
timelineStart: Frame;
|
|
166
|
+
timelineEnd: Frame;
|
|
167
|
+
mediaIn: Frame;
|
|
168
|
+
mediaOut: Frame;
|
|
169
|
+
label?: string;
|
|
170
|
+
metadata?: Record<string, unknown>;
|
|
171
|
+
}): Clip;
|
|
172
|
+
/**
|
|
173
|
+
* Get the timeline duration of a clip
|
|
174
|
+
*
|
|
175
|
+
* @param clip - The clip
|
|
176
|
+
* @returns Duration in frames
|
|
177
|
+
*/
|
|
178
|
+
declare function getClipDuration(clip: Clip): Frame;
|
|
179
|
+
/**
|
|
180
|
+
* Get the media duration of a clip
|
|
181
|
+
*
|
|
182
|
+
* @param clip - The clip
|
|
183
|
+
* @returns Duration in frames
|
|
184
|
+
*/
|
|
185
|
+
declare function getClipMediaDuration(clip: Clip): Frame;
|
|
186
|
+
/**
|
|
187
|
+
* Check if a clip contains a specific frame on the timeline
|
|
188
|
+
*
|
|
189
|
+
* @param clip - The clip
|
|
190
|
+
* @param frame - The frame to check
|
|
191
|
+
* @returns true if the frame is within the clip's timeline bounds
|
|
192
|
+
*/
|
|
193
|
+
declare function clipContainsFrame(clip: Clip, frame: Frame): boolean;
|
|
194
|
+
/**
|
|
195
|
+
* Check if two clips overlap on the timeline
|
|
196
|
+
*
|
|
197
|
+
* @param clip1 - First clip
|
|
198
|
+
* @param clip2 - Second clip
|
|
199
|
+
* @returns true if the clips overlap
|
|
200
|
+
*/
|
|
201
|
+
declare function clipsOverlap(clip1: Clip, clip2: Clip): boolean;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* TRACK MODEL
|
|
205
|
+
*
|
|
206
|
+
* A Track is a horizontal container for Clips.
|
|
207
|
+
*
|
|
208
|
+
* WHAT IS A TRACK?
|
|
209
|
+
* - A layer that holds clips (like a layer in Photoshop)
|
|
210
|
+
* - Provides organization and isolation for clips
|
|
211
|
+
* - Has a type (video or audio) that clips must match
|
|
212
|
+
*
|
|
213
|
+
* WHY TRACKS?
|
|
214
|
+
* - Organize clips into layers
|
|
215
|
+
* - Enable stacking and compositing (track order matters)
|
|
216
|
+
* - Provide track-level controls (mute, lock)
|
|
217
|
+
* - Isolate editing operations
|
|
218
|
+
*
|
|
219
|
+
* TRACK ORDER:
|
|
220
|
+
* Tracks are rendered bottom-to-top:
|
|
221
|
+
* - tracks[0] = bottom layer (rendered first)
|
|
222
|
+
* - tracks[n] = top layer (rendered last, appears on top)
|
|
223
|
+
*
|
|
224
|
+
* EXAMPLE:
|
|
225
|
+
* ```typescript
|
|
226
|
+
* const track: Track = {
|
|
227
|
+
* id: 'track_1',
|
|
228
|
+
* name: 'Video Track 1',
|
|
229
|
+
* type: 'video',
|
|
230
|
+
* clips: [],
|
|
231
|
+
* locked: false,
|
|
232
|
+
* muted: false,
|
|
233
|
+
* };
|
|
234
|
+
* ```
|
|
235
|
+
*
|
|
236
|
+
* INVARIANTS:
|
|
237
|
+
* - Clips on a track must not overlap
|
|
238
|
+
* - All clips must match the track type
|
|
239
|
+
* - Clips array should be sorted by timelineStart (for performance)
|
|
240
|
+
*/
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* TrackType - The kind of content this track holds
|
|
244
|
+
*/
|
|
245
|
+
type TrackType = 'video' | 'audio';
|
|
246
|
+
/**
|
|
247
|
+
* Track - A container for clips
|
|
248
|
+
*/
|
|
249
|
+
interface Track {
|
|
250
|
+
/** Unique identifier */
|
|
251
|
+
id: string;
|
|
252
|
+
/** Human-readable name */
|
|
253
|
+
name: string;
|
|
254
|
+
/** Type of content this track holds */
|
|
255
|
+
type: TrackType;
|
|
256
|
+
/** Clips on this track (should be sorted by timelineStart) */
|
|
257
|
+
clips: Clip[];
|
|
258
|
+
/** Whether the track is locked (prevents editing) */
|
|
259
|
+
locked: boolean;
|
|
260
|
+
/** Whether the track is muted (affects playback) */
|
|
261
|
+
muted: boolean;
|
|
262
|
+
/** Whether the track is soloed (mutes all other tracks) */
|
|
263
|
+
solo: boolean;
|
|
264
|
+
/** Track height in pixels (for UI rendering) */
|
|
265
|
+
height: number;
|
|
266
|
+
/** Optional metadata for custom use cases */
|
|
267
|
+
metadata?: Record<string, unknown>;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Create a new track
|
|
271
|
+
*
|
|
272
|
+
* @param params - Track parameters
|
|
273
|
+
* @returns A new Track object
|
|
274
|
+
*/
|
|
275
|
+
declare function createTrack(params: {
|
|
276
|
+
id: string;
|
|
277
|
+
name: string;
|
|
278
|
+
type: TrackType;
|
|
279
|
+
clips?: Clip[];
|
|
280
|
+
locked?: boolean;
|
|
281
|
+
muted?: boolean;
|
|
282
|
+
solo?: boolean;
|
|
283
|
+
height?: number;
|
|
284
|
+
metadata?: Record<string, unknown>;
|
|
285
|
+
}): Track;
|
|
286
|
+
/**
|
|
287
|
+
* Sort clips on a track by timeline start frame
|
|
288
|
+
*
|
|
289
|
+
* This is useful for maintaining clip order and improving
|
|
290
|
+
* query performance.
|
|
291
|
+
*
|
|
292
|
+
* @param track - The track to sort
|
|
293
|
+
* @returns A new track with sorted clips
|
|
294
|
+
*/
|
|
295
|
+
declare function sortTrackClips(track: Track): Track;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* TIMELINE MODEL
|
|
299
|
+
*
|
|
300
|
+
* The Timeline is the root container for the entire editing project.
|
|
301
|
+
*
|
|
302
|
+
* WHAT IS A TIMELINE?
|
|
303
|
+
* - The top-level data structure for a project
|
|
304
|
+
* - Contains all tracks (which contain all clips)
|
|
305
|
+
* - Defines the frame rate (FPS) for the entire project
|
|
306
|
+
* - Defines the total duration of the project
|
|
307
|
+
*
|
|
308
|
+
* WHY A TIMELINE?
|
|
309
|
+
* - Single source of truth for all timeline data
|
|
310
|
+
* - Defines the temporal bounds of the project
|
|
311
|
+
* - Provides a consistent frame rate for all time calculations
|
|
312
|
+
*
|
|
313
|
+
* EXAMPLE:
|
|
314
|
+
* ```typescript
|
|
315
|
+
* const timeline: Timeline = {
|
|
316
|
+
* id: 'timeline_1',
|
|
317
|
+
* name: 'My Project',
|
|
318
|
+
* fps: frameRate(30),
|
|
319
|
+
* duration: frame(9000), // 5 minutes at 30fps
|
|
320
|
+
* tracks: [],
|
|
321
|
+
* };
|
|
322
|
+
* ```
|
|
323
|
+
*
|
|
324
|
+
* INVARIANTS:
|
|
325
|
+
* - FPS is immutable after timeline creation
|
|
326
|
+
* - Duration must be positive
|
|
327
|
+
* - All tracks must have unique IDs
|
|
328
|
+
*/
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Timeline - The root container for a timeline project
|
|
332
|
+
*/
|
|
333
|
+
interface Timeline {
|
|
334
|
+
/** Unique identifier */
|
|
335
|
+
id: string;
|
|
336
|
+
/** Human-readable name */
|
|
337
|
+
name: string;
|
|
338
|
+
/** Frames per second (immutable after creation) */
|
|
339
|
+
fps: FrameRate;
|
|
340
|
+
/** Total duration of the timeline in frames */
|
|
341
|
+
duration: Frame;
|
|
342
|
+
/** Tracks in the timeline (ordered bottom-to-top) */
|
|
343
|
+
tracks: Track[];
|
|
344
|
+
/** Optional metadata for custom use cases */
|
|
345
|
+
metadata?: Record<string, unknown>;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Create a new timeline
|
|
349
|
+
*
|
|
350
|
+
* @param params - Timeline parameters
|
|
351
|
+
* @returns A new Timeline object
|
|
352
|
+
*/
|
|
353
|
+
declare function createTimeline(params: {
|
|
354
|
+
id: string;
|
|
355
|
+
name: string;
|
|
356
|
+
fps: FrameRate;
|
|
357
|
+
duration: Frame;
|
|
358
|
+
tracks?: Track[];
|
|
359
|
+
metadata?: Record<string, unknown>;
|
|
360
|
+
}): Timeline;
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* ASSET MODEL
|
|
364
|
+
*
|
|
365
|
+
* An Asset represents immutable metadata about a media file.
|
|
366
|
+
*
|
|
367
|
+
* WHAT IS AN ASSET?
|
|
368
|
+
* - A reference to source media (video, audio, image)
|
|
369
|
+
* - Contains metadata like duration and type
|
|
370
|
+
* - Immutable once registered (duration never changes)
|
|
371
|
+
*
|
|
372
|
+
* WHY SEPARATE ASSETS FROM CLIPS?
|
|
373
|
+
* - Multiple clips can reference the same asset
|
|
374
|
+
* - Asset duration is the source of truth
|
|
375
|
+
* - Clips can trim/slice the asset without modifying it
|
|
376
|
+
*
|
|
377
|
+
* EXAMPLE:
|
|
378
|
+
* ```typescript
|
|
379
|
+
* const asset: Asset = {
|
|
380
|
+
* id: 'asset_1',
|
|
381
|
+
* type: 'video',
|
|
382
|
+
* duration: frame(3600), // 2 minutes at 30fps
|
|
383
|
+
* sourceUrl: 'https://example.com/video.mp4',
|
|
384
|
+
* };
|
|
385
|
+
* ```
|
|
386
|
+
*
|
|
387
|
+
* INVARIANTS:
|
|
388
|
+
* - Asset ID must be unique
|
|
389
|
+
* - Duration must be positive
|
|
390
|
+
* - Duration is immutable after registration
|
|
391
|
+
*/
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* AssetType - The kind of media this asset represents
|
|
395
|
+
*/
|
|
396
|
+
type AssetType = 'video' | 'audio' | 'image';
|
|
397
|
+
/**
|
|
398
|
+
* Asset - Immutable metadata about a media file
|
|
399
|
+
*/
|
|
400
|
+
interface Asset {
|
|
401
|
+
/** Unique identifier */
|
|
402
|
+
id: string;
|
|
403
|
+
/** Type of media */
|
|
404
|
+
type: AssetType;
|
|
405
|
+
/** Total duration of the asset in frames */
|
|
406
|
+
duration: Frame;
|
|
407
|
+
/** Source URL or file path */
|
|
408
|
+
sourceUrl: string;
|
|
409
|
+
/** Optional metadata for custom use cases */
|
|
410
|
+
metadata?: Record<string, unknown>;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Create a new asset
|
|
414
|
+
*
|
|
415
|
+
* @param params - Asset parameters
|
|
416
|
+
* @returns A new Asset object
|
|
417
|
+
*/
|
|
418
|
+
declare function createAsset(params: {
|
|
419
|
+
id: string;
|
|
420
|
+
type: AssetType;
|
|
421
|
+
duration: Frame;
|
|
422
|
+
sourceUrl: string;
|
|
423
|
+
metadata?: Record<string, unknown>;
|
|
424
|
+
}): Asset;
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* LINKING TYPES
|
|
428
|
+
*
|
|
429
|
+
* Link groups synchronize edits across multiple clips.
|
|
430
|
+
* When one clip in a link group is moved/deleted, all linked clips are affected.
|
|
431
|
+
*/
|
|
432
|
+
/**
|
|
433
|
+
* Link group - relates clips for synchronized editing
|
|
434
|
+
*/
|
|
435
|
+
interface LinkGroup {
|
|
436
|
+
id: string;
|
|
437
|
+
clipIds: string[];
|
|
438
|
+
createdAt?: number;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* GROUPING TYPES
|
|
443
|
+
*
|
|
444
|
+
* Groups organize clips visually without affecting edit behavior.
|
|
445
|
+
* Unlike link groups, groups are for organization only.
|
|
446
|
+
*/
|
|
447
|
+
/**
|
|
448
|
+
* Group - organizes clips visually
|
|
449
|
+
*/
|
|
450
|
+
interface Group {
|
|
451
|
+
id: string;
|
|
452
|
+
name: string;
|
|
453
|
+
clipIds: string[];
|
|
454
|
+
parentGroupId?: string;
|
|
455
|
+
color?: string;
|
|
456
|
+
collapsed?: boolean;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* MARKER TYPES
|
|
461
|
+
*
|
|
462
|
+
* Pure metadata for timeline navigation and organization.
|
|
463
|
+
* Markers do not affect clip timing or validation.
|
|
464
|
+
*/
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Timeline marker - marks a specific frame on the timeline
|
|
468
|
+
*/
|
|
469
|
+
interface TimelineMarker {
|
|
470
|
+
id: string;
|
|
471
|
+
type: 'timeline';
|
|
472
|
+
frame: Frame;
|
|
473
|
+
label: string;
|
|
474
|
+
color?: string;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Clip marker - marks a frame relative to clip start
|
|
478
|
+
*/
|
|
479
|
+
interface ClipMarker {
|
|
480
|
+
id: string;
|
|
481
|
+
type: 'clip';
|
|
482
|
+
clipId: string;
|
|
483
|
+
frame: Frame;
|
|
484
|
+
label: string;
|
|
485
|
+
color?: string;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Region marker - marks a frame range
|
|
489
|
+
*/
|
|
490
|
+
interface RegionMarker {
|
|
491
|
+
id: string;
|
|
492
|
+
type: 'region';
|
|
493
|
+
startFrame: Frame;
|
|
494
|
+
endFrame: Frame;
|
|
495
|
+
label: string;
|
|
496
|
+
color?: string;
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Work area - defines the active editing region
|
|
500
|
+
*/
|
|
501
|
+
interface WorkArea {
|
|
502
|
+
startFrame: Frame;
|
|
503
|
+
endFrame: Frame;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Union type for all marker types
|
|
507
|
+
*/
|
|
508
|
+
type Marker = TimelineMarker | ClipMarker | RegionMarker;
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* TIMELINE STATE
|
|
512
|
+
*
|
|
513
|
+
* This is the complete state shape for the timeline engine.
|
|
514
|
+
*
|
|
515
|
+
* WHAT IS TIMELINE STATE?
|
|
516
|
+
* - The root state object that contains everything
|
|
517
|
+
* - Timeline (tracks and clips)
|
|
518
|
+
* - Assets (media metadata)
|
|
519
|
+
*
|
|
520
|
+
* WHY SEPARATE STATE?
|
|
521
|
+
* - Clear separation between timeline structure and asset registry
|
|
522
|
+
* - Assets can be shared across multiple clips
|
|
523
|
+
* - State is the single source of truth
|
|
524
|
+
*
|
|
525
|
+
* EXAMPLE:
|
|
526
|
+
* ```typescript
|
|
527
|
+
* const state: TimelineState = {
|
|
528
|
+
* timeline: {
|
|
529
|
+
* id: 'timeline_1',
|
|
530
|
+
* name: 'My Project',
|
|
531
|
+
* fps: frameRate(30),
|
|
532
|
+
* duration: frame(9000),
|
|
533
|
+
* tracks: [],
|
|
534
|
+
* },
|
|
535
|
+
* assets: new Map([
|
|
536
|
+
* ['asset_1', { id: 'asset_1', type: 'video', duration: frame(3600), sourceUrl: '...' }],
|
|
537
|
+
* ]),
|
|
538
|
+
* };
|
|
539
|
+
* ```
|
|
540
|
+
*
|
|
541
|
+
* IMMUTABILITY:
|
|
542
|
+
* All operations on TimelineState must return a NEW state object.
|
|
543
|
+
* Never mutate the existing state.
|
|
544
|
+
*/
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* TimelineState - The complete state for the timeline engine
|
|
548
|
+
*
|
|
549
|
+
* Phase 2 additions:
|
|
550
|
+
* - linkGroups: Synchronized editing groups
|
|
551
|
+
* - groups: Visual organization groups
|
|
552
|
+
* - markers: Timeline/clip/region markers
|
|
553
|
+
* - workArea: Active editing region
|
|
554
|
+
*/
|
|
555
|
+
interface TimelineState {
|
|
556
|
+
/** The timeline (tracks and clips) */
|
|
557
|
+
timeline: Timeline;
|
|
558
|
+
/** Asset registry (media metadata) */
|
|
559
|
+
assets: Map<string, Asset>;
|
|
560
|
+
/** Link groups for synchronized editing */
|
|
561
|
+
linkGroups: Map<string, LinkGroup>;
|
|
562
|
+
/** Groups for visual organization */
|
|
563
|
+
groups: Map<string, Group>;
|
|
564
|
+
/** Markers for navigation and annotation */
|
|
565
|
+
markers: {
|
|
566
|
+
timeline: TimelineMarker[];
|
|
567
|
+
clips: ClipMarker[];
|
|
568
|
+
regions: RegionMarker[];
|
|
569
|
+
};
|
|
570
|
+
/** Optional work area definition */
|
|
571
|
+
workArea?: WorkArea;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Create a new timeline state
|
|
575
|
+
*
|
|
576
|
+
* @param params - State parameters
|
|
577
|
+
* @returns A new TimelineState object
|
|
578
|
+
*/
|
|
579
|
+
declare function createTimelineState(params: {
|
|
580
|
+
timeline: Timeline;
|
|
581
|
+
assets?: Map<string, Asset>;
|
|
582
|
+
linkGroups?: Map<string, LinkGroup>;
|
|
583
|
+
groups?: Map<string, Group>;
|
|
584
|
+
markers?: {
|
|
585
|
+
timeline: TimelineMarker[];
|
|
586
|
+
clips: ClipMarker[];
|
|
587
|
+
regions: RegionMarker[];
|
|
588
|
+
};
|
|
589
|
+
workArea?: WorkArea;
|
|
590
|
+
}): TimelineState;
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* HISTORY ENGINE
|
|
594
|
+
*
|
|
595
|
+
* Snapshot-based undo/redo system for timeline state.
|
|
596
|
+
*
|
|
597
|
+
* WHAT IS THE HISTORY ENGINE?
|
|
598
|
+
* - Stores immutable snapshots of timeline state
|
|
599
|
+
* - Provides undo/redo functionality
|
|
600
|
+
* - Prevents state corruption
|
|
601
|
+
*
|
|
602
|
+
* HOW IT WORKS:
|
|
603
|
+
* - past: Array of previous states
|
|
604
|
+
* - present: Current state
|
|
605
|
+
* - future: Array of states that can be redone
|
|
606
|
+
*
|
|
607
|
+
* WHY SNAPSHOTS?
|
|
608
|
+
* - Simple and reliable (no complex diffing)
|
|
609
|
+
* - Guaranteed to restore exact state
|
|
610
|
+
* - No risk of partial corruption
|
|
611
|
+
* - Easy to implement and test
|
|
612
|
+
*
|
|
613
|
+
* USAGE:
|
|
614
|
+
* ```typescript
|
|
615
|
+
* let history = createHistory(initialState);
|
|
616
|
+
* history = pushHistory(history, newState);
|
|
617
|
+
* history = undo(history);
|
|
618
|
+
* history = redo(history);
|
|
619
|
+
* ```
|
|
620
|
+
*
|
|
621
|
+
* ALL FUNCTIONS ARE PURE:
|
|
622
|
+
* - Take history as input
|
|
623
|
+
* - Return new history as output
|
|
624
|
+
* - Never mutate input
|
|
625
|
+
*/
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* HistoryState - The history container
|
|
629
|
+
*
|
|
630
|
+
* Contains:
|
|
631
|
+
* - past: Array of previous states (oldest first)
|
|
632
|
+
* - present: Current state
|
|
633
|
+
* - future: Array of states that can be redone (newest first)
|
|
634
|
+
* - limit: Maximum number of past states to keep
|
|
635
|
+
*/
|
|
636
|
+
interface HistoryState {
|
|
637
|
+
past: TimelineState[];
|
|
638
|
+
present: TimelineState;
|
|
639
|
+
future: TimelineState[];
|
|
640
|
+
limit: number;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* VALIDATION SYSTEM TYPES
|
|
645
|
+
*
|
|
646
|
+
* This file defines the structure for validation results throughout the engine.
|
|
647
|
+
*
|
|
648
|
+
* WHY STRUCTURED VALIDATION?
|
|
649
|
+
* - Operations can fail gracefully (no exceptions thrown)
|
|
650
|
+
* - Errors are descriptive and actionable
|
|
651
|
+
* - Multiple errors can be collected and reported together
|
|
652
|
+
* - Validation logic is separated from business logic
|
|
653
|
+
*
|
|
654
|
+
* USAGE:
|
|
655
|
+
* ```typescript
|
|
656
|
+
* const result = validateClip(state, clip);
|
|
657
|
+
* if (!result.valid) {
|
|
658
|
+
* console.error('Validation failed:', result.errors);
|
|
659
|
+
* return;
|
|
660
|
+
* }
|
|
661
|
+
* ```
|
|
662
|
+
*/
|
|
663
|
+
/**
|
|
664
|
+
* ValidationError - A single validation error
|
|
665
|
+
*
|
|
666
|
+
* Contains:
|
|
667
|
+
* - code: Machine-readable error code (e.g., "CLIP_OVERLAP")
|
|
668
|
+
* - message: Human-readable error message
|
|
669
|
+
* - context: Optional additional data about the error
|
|
670
|
+
*/
|
|
671
|
+
interface ValidationError {
|
|
672
|
+
/** Machine-readable error code */
|
|
673
|
+
code: string;
|
|
674
|
+
/** Human-readable error message */
|
|
675
|
+
message: string;
|
|
676
|
+
/** Optional context data for debugging */
|
|
677
|
+
context?: Record<string, unknown>;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* ValidationResult - The result of a validation operation
|
|
681
|
+
*
|
|
682
|
+
* Contains:
|
|
683
|
+
* - valid: Whether the validation passed
|
|
684
|
+
* - errors: Array of validation errors (empty if valid)
|
|
685
|
+
*/
|
|
686
|
+
interface ValidationResult {
|
|
687
|
+
/** Whether the validation passed */
|
|
688
|
+
valid: boolean;
|
|
689
|
+
/** Array of validation errors (empty if valid) */
|
|
690
|
+
errors: ValidationError[];
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Create a successful validation result
|
|
694
|
+
*
|
|
695
|
+
* @returns A ValidationResult indicating success
|
|
696
|
+
*/
|
|
697
|
+
declare function validResult(): ValidationResult;
|
|
698
|
+
/**
|
|
699
|
+
* Create a failed validation result with a single error
|
|
700
|
+
*
|
|
701
|
+
* @param code - Error code
|
|
702
|
+
* @param message - Error message
|
|
703
|
+
* @param context - Optional context data
|
|
704
|
+
* @returns A ValidationResult indicating failure
|
|
705
|
+
*/
|
|
706
|
+
declare function invalidResult(code: string, message: string, context?: Record<string, unknown>): ValidationResult;
|
|
707
|
+
/**
|
|
708
|
+
* Create a failed validation result with multiple errors
|
|
709
|
+
*
|
|
710
|
+
* @param errors - Array of validation errors
|
|
711
|
+
* @returns A ValidationResult indicating failure
|
|
712
|
+
*/
|
|
713
|
+
declare function invalidResults(errors: ValidationError[]): ValidationResult;
|
|
714
|
+
/**
|
|
715
|
+
* Combine multiple validation results
|
|
716
|
+
*
|
|
717
|
+
* If any result is invalid, the combined result is invalid.
|
|
718
|
+
* All errors are collected together.
|
|
719
|
+
*
|
|
720
|
+
* @param results - Array of validation results to combine
|
|
721
|
+
* @returns A combined ValidationResult
|
|
722
|
+
*/
|
|
723
|
+
declare function combineResults(...results: ValidationResult[]): ValidationResult;
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* DISPATCH ORCHESTRATION
|
|
727
|
+
*
|
|
728
|
+
* Coordinates operation execution, validation, and history management.
|
|
729
|
+
*
|
|
730
|
+
* WHAT IS THE DISPATCHER?
|
|
731
|
+
* - Executes operations on state
|
|
732
|
+
* - Validates the resulting state
|
|
733
|
+
* - Commits valid states to history
|
|
734
|
+
* - Rejects invalid states with errors
|
|
735
|
+
*
|
|
736
|
+
* WHY A DISPATCHER?
|
|
737
|
+
* - Enforces validation before mutation
|
|
738
|
+
* - Prevents invalid states from entering history
|
|
739
|
+
* - Provides consistent error handling
|
|
740
|
+
* - Separates concerns (operations vs validation vs history)
|
|
741
|
+
*
|
|
742
|
+
* CRITICAL FLOW:
|
|
743
|
+
* 1. Execute operation (pure function)
|
|
744
|
+
* 2. Validate resulting state
|
|
745
|
+
* 3. If valid: push to history, return success
|
|
746
|
+
* 4. If invalid: return errors, state unchanged
|
|
747
|
+
*
|
|
748
|
+
* USAGE:
|
|
749
|
+
* ```typescript
|
|
750
|
+
* const result = dispatch(history, (state) => addClip(state, trackId, clip));
|
|
751
|
+
* if (result.success) {
|
|
752
|
+
* history = result.history;
|
|
753
|
+
* } else {
|
|
754
|
+
* console.error('Operation failed:', result.errors);
|
|
755
|
+
* }
|
|
756
|
+
* ```
|
|
757
|
+
*/
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* DispatchResult - The result of a dispatch operation
|
|
761
|
+
*
|
|
762
|
+
* Contains:
|
|
763
|
+
* - success: Whether the operation succeeded
|
|
764
|
+
* - history: Updated history (if success) or unchanged (if failure)
|
|
765
|
+
* - errors: Validation errors (if failure)
|
|
766
|
+
*/
|
|
767
|
+
interface DispatchResult {
|
|
768
|
+
/** Whether the operation succeeded */
|
|
769
|
+
success: boolean;
|
|
770
|
+
/** Updated history state */
|
|
771
|
+
history: HistoryState;
|
|
772
|
+
/** Validation errors (if operation failed) */
|
|
773
|
+
errors?: ValidationError[];
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* TIMELINE ENGINE
|
|
778
|
+
*
|
|
779
|
+
* The main public API for the timeline editing kernel.
|
|
780
|
+
*
|
|
781
|
+
* WHAT IS THE TIMELINE ENGINE?
|
|
782
|
+
* - A thin wrapper around the history and dispatch systems
|
|
783
|
+
* - Provides a convenient, object-oriented API
|
|
784
|
+
* - Manages internal state
|
|
785
|
+
* - Coordinates operations, validation, and history
|
|
786
|
+
*
|
|
787
|
+
* WHY A CLASS?
|
|
788
|
+
* - Encapsulates state management
|
|
789
|
+
* - Provides a clean API for users
|
|
790
|
+
* - Hides complexity of history and dispatch
|
|
791
|
+
* - Familiar OOP interface for most developers
|
|
792
|
+
*
|
|
793
|
+
* USAGE:
|
|
794
|
+
* ```typescript
|
|
795
|
+
* const engine = new TimelineEngine(initialState);
|
|
796
|
+
*
|
|
797
|
+
* // Add a clip
|
|
798
|
+
* const result = engine.addClip(trackId, clip);
|
|
799
|
+
* if (!result.success) {
|
|
800
|
+
* console.error('Failed to add clip:', result.errors);
|
|
801
|
+
* }
|
|
802
|
+
*
|
|
803
|
+
* // Undo/redo
|
|
804
|
+
* engine.undo();
|
|
805
|
+
* engine.redo();
|
|
806
|
+
*
|
|
807
|
+
* // Query state
|
|
808
|
+
* const clip = engine.findClipById('clip_1');
|
|
809
|
+
* const state = engine.getState();
|
|
810
|
+
* ```
|
|
811
|
+
*
|
|
812
|
+
* DESIGN PHILOSOPHY:
|
|
813
|
+
* - Business logic lives in pure modules (operations, validation, etc.)
|
|
814
|
+
* - Engine is just a thin orchestration layer
|
|
815
|
+
* - Easy to test (can test pure functions independently)
|
|
816
|
+
*/
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* TimelineEngine - The main timeline editing engine
|
|
820
|
+
*
|
|
821
|
+
* Provides a high-level API for timeline editing with built-in
|
|
822
|
+
* undo/redo, validation, and state management.
|
|
823
|
+
*/
|
|
824
|
+
declare class TimelineEngine {
|
|
825
|
+
private history;
|
|
826
|
+
private listeners;
|
|
827
|
+
/**
|
|
828
|
+
* Create a new timeline engine
|
|
829
|
+
*
|
|
830
|
+
* @param initialState - Initial timeline state
|
|
831
|
+
* @param historyLimit - Maximum number of undo steps (default: 50)
|
|
832
|
+
*/
|
|
833
|
+
constructor(initialState: TimelineState, historyLimit?: number);
|
|
834
|
+
/**
|
|
835
|
+
* Subscribe to state changes
|
|
836
|
+
*
|
|
837
|
+
* The listener will be called whenever the timeline state changes,
|
|
838
|
+
* with the new state passed as an argument.
|
|
839
|
+
* This is used by framework adapters (e.g., React) to trigger re-renders.
|
|
840
|
+
*
|
|
841
|
+
* @param listener - Function to call on state changes, receives new state
|
|
842
|
+
* @returns Unsubscribe function
|
|
843
|
+
*
|
|
844
|
+
* @example
|
|
845
|
+
* ```typescript
|
|
846
|
+
* const unsubscribe = engine.subscribe((state) => {
|
|
847
|
+
* console.log('State changed:', state);
|
|
848
|
+
* });
|
|
849
|
+
*
|
|
850
|
+
* // Later...
|
|
851
|
+
* unsubscribe();
|
|
852
|
+
* ```
|
|
853
|
+
*/
|
|
854
|
+
subscribe(listener: (state: TimelineState) => void): () => void;
|
|
855
|
+
/**
|
|
856
|
+
* Notify all subscribers of a state change
|
|
857
|
+
*
|
|
858
|
+
* This is called internally after any operation that modifies state.
|
|
859
|
+
* Framework adapters use this to trigger re-renders.
|
|
860
|
+
*/
|
|
861
|
+
private notify;
|
|
862
|
+
/**
|
|
863
|
+
* Get the current timeline state
|
|
864
|
+
*
|
|
865
|
+
* @returns Current timeline state
|
|
866
|
+
*/
|
|
867
|
+
getState(): TimelineState;
|
|
868
|
+
/**
|
|
869
|
+
* Register an asset
|
|
870
|
+
*
|
|
871
|
+
* @param asset - Asset to register
|
|
872
|
+
* @returns Dispatch result
|
|
873
|
+
*/
|
|
874
|
+
registerAsset(asset: Asset): DispatchResult;
|
|
875
|
+
/**
|
|
876
|
+
* Get an asset by ID
|
|
877
|
+
*
|
|
878
|
+
* @param assetId - Asset ID
|
|
879
|
+
* @returns The asset, or undefined if not found
|
|
880
|
+
*/
|
|
881
|
+
getAsset(assetId: string): Asset | undefined;
|
|
882
|
+
/**
|
|
883
|
+
* Add a clip to a track
|
|
884
|
+
*
|
|
885
|
+
* @param trackId - ID of the track to add to
|
|
886
|
+
* @param clip - Clip to add
|
|
887
|
+
* @returns Dispatch result
|
|
888
|
+
*/
|
|
889
|
+
addClip(trackId: string, clip: Clip): DispatchResult;
|
|
890
|
+
/**
|
|
891
|
+
* Remove a clip
|
|
892
|
+
*
|
|
893
|
+
* @param clipId - ID of the clip to remove
|
|
894
|
+
* @returns Dispatch result
|
|
895
|
+
*/
|
|
896
|
+
removeClip(clipId: string): DispatchResult;
|
|
897
|
+
/**
|
|
898
|
+
* Move a clip to a new timeline position
|
|
899
|
+
*
|
|
900
|
+
* @param clipId - ID of the clip to move
|
|
901
|
+
* @param newStart - New timeline start frame
|
|
902
|
+
* @returns Dispatch result
|
|
903
|
+
*/
|
|
904
|
+
moveClip(clipId: string, newStart: Frame): DispatchResult;
|
|
905
|
+
/**
|
|
906
|
+
* Resize a clip
|
|
907
|
+
*
|
|
908
|
+
* @param clipId - ID of the clip to resize
|
|
909
|
+
* @param newStart - New timeline start frame
|
|
910
|
+
* @param newEnd - New timeline end frame
|
|
911
|
+
* @returns Dispatch result
|
|
912
|
+
*/
|
|
913
|
+
resizeClip(clipId: string, newStart: Frame, newEnd: Frame): DispatchResult;
|
|
914
|
+
/**
|
|
915
|
+
* Trim a clip (change media bounds)
|
|
916
|
+
*
|
|
917
|
+
* @param clipId - ID of the clip to trim
|
|
918
|
+
* @param newMediaIn - New media in frame
|
|
919
|
+
* @param newMediaOut - New media out frame
|
|
920
|
+
* @returns Dispatch result
|
|
921
|
+
*/
|
|
922
|
+
trimClip(clipId: string, newMediaIn: Frame, newMediaOut: Frame): DispatchResult;
|
|
923
|
+
/**
|
|
924
|
+
* Move a clip to a different track
|
|
925
|
+
*
|
|
926
|
+
* @param clipId - ID of the clip to move
|
|
927
|
+
* @param targetTrackId - ID of the target track
|
|
928
|
+
* @returns Dispatch result
|
|
929
|
+
*/
|
|
930
|
+
moveClipToTrack(clipId: string, targetTrackId: string): DispatchResult;
|
|
931
|
+
/**
|
|
932
|
+
* Add a track
|
|
933
|
+
*
|
|
934
|
+
* @param track - Track to add
|
|
935
|
+
* @returns Dispatch result
|
|
936
|
+
*/
|
|
937
|
+
addTrack(track: Track): DispatchResult;
|
|
938
|
+
/**
|
|
939
|
+
* Remove a track
|
|
940
|
+
*
|
|
941
|
+
* @param trackId - ID of the track to remove
|
|
942
|
+
* @returns Dispatch result
|
|
943
|
+
*/
|
|
944
|
+
removeTrack(trackId: string): DispatchResult;
|
|
945
|
+
/**
|
|
946
|
+
* Move a track to a new position
|
|
947
|
+
*
|
|
948
|
+
* @param trackId - ID of the track to move
|
|
949
|
+
* @param newIndex - New index position
|
|
950
|
+
* @returns Dispatch result
|
|
951
|
+
*/
|
|
952
|
+
moveTrack(trackId: string, newIndex: number): DispatchResult;
|
|
953
|
+
/**
|
|
954
|
+
* Toggle track mute
|
|
955
|
+
*
|
|
956
|
+
* @param trackId - ID of the track
|
|
957
|
+
* @returns Dispatch result
|
|
958
|
+
*/
|
|
959
|
+
toggleTrackMute(trackId: string): DispatchResult;
|
|
960
|
+
/**
|
|
961
|
+
* Toggle track lock
|
|
962
|
+
*
|
|
963
|
+
* @param trackId - ID of the track
|
|
964
|
+
* @returns Dispatch result
|
|
965
|
+
*/
|
|
966
|
+
toggleTrackLock(trackId: string): DispatchResult;
|
|
967
|
+
/**
|
|
968
|
+
* Toggle track solo
|
|
969
|
+
*
|
|
970
|
+
* @param trackId - ID of the track
|
|
971
|
+
* @returns Dispatch result
|
|
972
|
+
*/
|
|
973
|
+
toggleTrackSolo(trackId: string): DispatchResult;
|
|
974
|
+
/**
|
|
975
|
+
* Set track height
|
|
976
|
+
*
|
|
977
|
+
* @param trackId - ID of the track
|
|
978
|
+
* @param height - New height in pixels
|
|
979
|
+
* @returns Dispatch result
|
|
980
|
+
*/
|
|
981
|
+
setTrackHeight(trackId: string, height: number): DispatchResult;
|
|
982
|
+
/**
|
|
983
|
+
* Set timeline duration
|
|
984
|
+
*
|
|
985
|
+
* @param duration - New duration in frames
|
|
986
|
+
* @returns Dispatch result
|
|
987
|
+
*/
|
|
988
|
+
setTimelineDuration(duration: Frame): DispatchResult;
|
|
989
|
+
/**
|
|
990
|
+
* Set timeline name
|
|
991
|
+
*
|
|
992
|
+
* @param name - New timeline name
|
|
993
|
+
* @returns Dispatch result
|
|
994
|
+
*/
|
|
995
|
+
setTimelineName(name: string): DispatchResult;
|
|
996
|
+
/**
|
|
997
|
+
* Undo the last action
|
|
998
|
+
*
|
|
999
|
+
* @returns true if undo was performed
|
|
1000
|
+
*/
|
|
1001
|
+
undo(): boolean;
|
|
1002
|
+
/**
|
|
1003
|
+
* Redo the last undone action
|
|
1004
|
+
*
|
|
1005
|
+
* @returns true if redo was performed
|
|
1006
|
+
*/
|
|
1007
|
+
redo(): boolean;
|
|
1008
|
+
/**
|
|
1009
|
+
* Check if undo is available
|
|
1010
|
+
*
|
|
1011
|
+
* @returns true if undo is available
|
|
1012
|
+
*/
|
|
1013
|
+
canUndo(): boolean;
|
|
1014
|
+
/**
|
|
1015
|
+
* Check if redo is available
|
|
1016
|
+
*
|
|
1017
|
+
* @returns true if redo is available
|
|
1018
|
+
*/
|
|
1019
|
+
canRedo(): boolean;
|
|
1020
|
+
/**
|
|
1021
|
+
* Find a clip by ID
|
|
1022
|
+
*
|
|
1023
|
+
* @param clipId - Clip ID
|
|
1024
|
+
* @returns The clip, or undefined if not found
|
|
1025
|
+
*/
|
|
1026
|
+
findClipById(clipId: string): Clip | undefined;
|
|
1027
|
+
/**
|
|
1028
|
+
* Find a track by ID
|
|
1029
|
+
*
|
|
1030
|
+
* @param trackId - Track ID
|
|
1031
|
+
* @returns The track, or undefined if not found
|
|
1032
|
+
*/
|
|
1033
|
+
findTrackById(trackId: string): Track | undefined;
|
|
1034
|
+
/**
|
|
1035
|
+
* Get all clips on a track
|
|
1036
|
+
*
|
|
1037
|
+
* @param trackId - Track ID
|
|
1038
|
+
* @returns Array of clips on the track
|
|
1039
|
+
*/
|
|
1040
|
+
getClipsOnTrack(trackId: string): Clip[];
|
|
1041
|
+
/**
|
|
1042
|
+
* Get all clips at a specific frame
|
|
1043
|
+
*
|
|
1044
|
+
* @param frame - Frame to check
|
|
1045
|
+
* @returns Array of clips at that frame
|
|
1046
|
+
*/
|
|
1047
|
+
getClipsAtFrame(frame: Frame): Clip[];
|
|
1048
|
+
/**
|
|
1049
|
+
* Get all clips in a frame range
|
|
1050
|
+
*
|
|
1051
|
+
* @param start - Start frame
|
|
1052
|
+
* @param end - End frame
|
|
1053
|
+
* @returns Array of clips in the range
|
|
1054
|
+
*/
|
|
1055
|
+
getClipsInRange(start: Frame, end: Frame): Clip[];
|
|
1056
|
+
/**
|
|
1057
|
+
* Get all clips in the timeline
|
|
1058
|
+
*
|
|
1059
|
+
* @returns Array of all clips
|
|
1060
|
+
*/
|
|
1061
|
+
getAllClips(): Clip[];
|
|
1062
|
+
/**
|
|
1063
|
+
* Get all tracks in the timeline
|
|
1064
|
+
*
|
|
1065
|
+
* @returns Array of all tracks
|
|
1066
|
+
*/
|
|
1067
|
+
getAllTracks(): Track[];
|
|
1068
|
+
/**
|
|
1069
|
+
* Ripple delete - delete clip and shift subsequent clips left
|
|
1070
|
+
*
|
|
1071
|
+
* @param clipId - ID of the clip to delete
|
|
1072
|
+
* @returns Dispatch result
|
|
1073
|
+
*/
|
|
1074
|
+
rippleDelete(clipId: string): DispatchResult;
|
|
1075
|
+
/**
|
|
1076
|
+
* Ripple trim - trim clip end and shift subsequent clips
|
|
1077
|
+
*
|
|
1078
|
+
* @param clipId - ID of the clip to trim
|
|
1079
|
+
* @param newEnd - New end frame for the clip
|
|
1080
|
+
* @returns Dispatch result
|
|
1081
|
+
*/
|
|
1082
|
+
rippleTrim(clipId: string, newEnd: Frame): DispatchResult;
|
|
1083
|
+
/**
|
|
1084
|
+
* Insert edit - insert clip and shift subsequent clips right
|
|
1085
|
+
*
|
|
1086
|
+
* @param trackId - ID of the track to insert into
|
|
1087
|
+
* @param clip - Clip to insert
|
|
1088
|
+
* @param atFrame - Frame to insert at
|
|
1089
|
+
* @returns Dispatch result
|
|
1090
|
+
*/
|
|
1091
|
+
insertEdit(trackId: string, clip: Clip, atFrame: Frame): DispatchResult;
|
|
1092
|
+
/**
|
|
1093
|
+
* Ripple move - move clip and shift surrounding clips to accommodate
|
|
1094
|
+
*
|
|
1095
|
+
* This moves a clip to a new position while maintaining timeline continuity:
|
|
1096
|
+
* - Closes the gap at the source position
|
|
1097
|
+
* - Makes space at the destination position
|
|
1098
|
+
* - All operations are atomic (single undo entry)
|
|
1099
|
+
*
|
|
1100
|
+
* @param clipId - ID of the clip to move
|
|
1101
|
+
* @param newStart - New start frame for the clip
|
|
1102
|
+
* @returns Dispatch result
|
|
1103
|
+
*/
|
|
1104
|
+
rippleMove(clipId: string, newStart: Frame): DispatchResult;
|
|
1105
|
+
/**
|
|
1106
|
+
* Insert move - move clip and shift destination clips right
|
|
1107
|
+
*
|
|
1108
|
+
* This moves a clip to a new position without closing the gap at source:
|
|
1109
|
+
* - Leaves gap at the source position
|
|
1110
|
+
* - Pushes all clips at destination right to make space
|
|
1111
|
+
* - All operations are atomic (single undo entry)
|
|
1112
|
+
*
|
|
1113
|
+
* @param clipId - ID of the clip to move
|
|
1114
|
+
* @param newStart - New start frame for the clip
|
|
1115
|
+
* @returns Dispatch result
|
|
1116
|
+
*/
|
|
1117
|
+
insertMove(clipId: string, newStart: Frame): DispatchResult;
|
|
1118
|
+
/**
|
|
1119
|
+
* Add a timeline marker
|
|
1120
|
+
*
|
|
1121
|
+
* @param marker - Timeline marker to add
|
|
1122
|
+
* @returns Dispatch result
|
|
1123
|
+
*/
|
|
1124
|
+
addTimelineMarker(marker: TimelineMarker): DispatchResult;
|
|
1125
|
+
/**
|
|
1126
|
+
* Add a clip marker
|
|
1127
|
+
*
|
|
1128
|
+
* @param marker - Clip marker to add
|
|
1129
|
+
* @returns Dispatch result
|
|
1130
|
+
*/
|
|
1131
|
+
addClipMarker(marker: ClipMarker): DispatchResult;
|
|
1132
|
+
/**
|
|
1133
|
+
* Add a region marker
|
|
1134
|
+
*
|
|
1135
|
+
* @param marker - Region marker to add
|
|
1136
|
+
* @returns Dispatch result
|
|
1137
|
+
*/
|
|
1138
|
+
addRegionMarker(marker: RegionMarker): DispatchResult;
|
|
1139
|
+
/**
|
|
1140
|
+
* Remove a marker by ID
|
|
1141
|
+
*
|
|
1142
|
+
* @param markerId - ID of the marker to remove
|
|
1143
|
+
* @returns Dispatch result
|
|
1144
|
+
*/
|
|
1145
|
+
removeMarker(markerId: string): DispatchResult;
|
|
1146
|
+
/**
|
|
1147
|
+
* Update a timeline marker
|
|
1148
|
+
*
|
|
1149
|
+
* @param markerId - ID of the marker to update
|
|
1150
|
+
* @param updates - Partial marker updates
|
|
1151
|
+
* @returns Dispatch result
|
|
1152
|
+
*/
|
|
1153
|
+
updateTimelineMarker(markerId: string, updates: Partial<Omit<TimelineMarker, 'id' | 'type'>>): DispatchResult;
|
|
1154
|
+
/**
|
|
1155
|
+
* Update a region marker
|
|
1156
|
+
*
|
|
1157
|
+
* @param markerId - ID of the marker to update
|
|
1158
|
+
* @param updates - Partial marker updates
|
|
1159
|
+
* @returns Dispatch result
|
|
1160
|
+
*/
|
|
1161
|
+
updateRegionMarker(markerId: string, updates: Partial<Omit<RegionMarker, 'id' | 'type'>>): DispatchResult;
|
|
1162
|
+
/**
|
|
1163
|
+
* Set work area
|
|
1164
|
+
*
|
|
1165
|
+
* @param start - Start frame
|
|
1166
|
+
* @param end - End frame
|
|
1167
|
+
* @returns Dispatch result
|
|
1168
|
+
*/
|
|
1169
|
+
setWorkArea(start: Frame, end: Frame): DispatchResult;
|
|
1170
|
+
/**
|
|
1171
|
+
* Clear work area
|
|
1172
|
+
*
|
|
1173
|
+
* @returns Dispatch result
|
|
1174
|
+
*/
|
|
1175
|
+
clearWorkArea(): DispatchResult;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
/**
|
|
1179
|
+
* FRAME UTILITIES
|
|
1180
|
+
*
|
|
1181
|
+
* Pure functions for working with frame-based time values.
|
|
1182
|
+
*
|
|
1183
|
+
* These utilities handle:
|
|
1184
|
+
* - Converting between frames and seconds
|
|
1185
|
+
* - Formatting frames as timecode (HH:MM:SS:FF)
|
|
1186
|
+
* - Frame arithmetic (clamping, rounding)
|
|
1187
|
+
*
|
|
1188
|
+
* CRITICAL RULES:
|
|
1189
|
+
* - All conversions must quantize to whole frames
|
|
1190
|
+
* - No floating-point frame values allowed
|
|
1191
|
+
* - Always round/floor/ceil explicitly
|
|
1192
|
+
*
|
|
1193
|
+
* USAGE:
|
|
1194
|
+
* ```typescript
|
|
1195
|
+
* const fps = frameRate(30);
|
|
1196
|
+
* const frames = secondsToFrames(5.5, fps); // 165 frames
|
|
1197
|
+
* const seconds = framesToSeconds(frames, fps); // 5.5 seconds
|
|
1198
|
+
* const timecode = framesToTimecode(frames, fps); // "00:00:05:15"
|
|
1199
|
+
* ```
|
|
1200
|
+
*/
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* Convert frames to seconds
|
|
1204
|
+
*
|
|
1205
|
+
* @param frames - Frame number
|
|
1206
|
+
* @param fps - Frames per second
|
|
1207
|
+
* @returns Time in seconds (may be fractional)
|
|
1208
|
+
*/
|
|
1209
|
+
declare function framesToSeconds(frames: Frame, fps: FrameRate): number;
|
|
1210
|
+
/**
|
|
1211
|
+
* Convert seconds to frames
|
|
1212
|
+
*
|
|
1213
|
+
* IMPORTANT: This rounds to the nearest frame.
|
|
1214
|
+
* If you need different rounding behavior, use Math.floor or Math.ceil explicitly.
|
|
1215
|
+
*
|
|
1216
|
+
* @param seconds - Time in seconds
|
|
1217
|
+
* @param fps - Frames per second
|
|
1218
|
+
* @returns Frame number (rounded to nearest frame)
|
|
1219
|
+
*/
|
|
1220
|
+
declare function secondsToFrames(seconds: number, fps: FrameRate): Frame;
|
|
1221
|
+
/**
|
|
1222
|
+
* Convert frames to timecode format (HH:MM:SS:FF)
|
|
1223
|
+
*
|
|
1224
|
+
* Example: 3825 frames at 30fps = "00:02:07:15"
|
|
1225
|
+
*
|
|
1226
|
+
* @param frames - Frame number
|
|
1227
|
+
* @param fps - Frames per second
|
|
1228
|
+
* @returns Timecode string
|
|
1229
|
+
*/
|
|
1230
|
+
declare function framesToTimecode(frames: Frame, fps: FrameRate): string;
|
|
1231
|
+
/**
|
|
1232
|
+
* Convert frames to simple MM:SS format
|
|
1233
|
+
*
|
|
1234
|
+
* Example: 3825 frames at 30fps = "02:07"
|
|
1235
|
+
*
|
|
1236
|
+
* @param frames - Frame number
|
|
1237
|
+
* @param fps - Frames per second
|
|
1238
|
+
* @returns Time string in MM:SS format
|
|
1239
|
+
*/
|
|
1240
|
+
declare function framesToMinutesSeconds(frames: Frame, fps: FrameRate): string;
|
|
1241
|
+
/**
|
|
1242
|
+
* Clamp a frame value between min and max
|
|
1243
|
+
*
|
|
1244
|
+
* @param value - Frame to clamp
|
|
1245
|
+
* @param min - Minimum frame (inclusive)
|
|
1246
|
+
* @param max - Maximum frame (inclusive)
|
|
1247
|
+
* @returns Clamped frame value
|
|
1248
|
+
*/
|
|
1249
|
+
declare function clampFrame(value: Frame, min: Frame, max: Frame): Frame;
|
|
1250
|
+
/**
|
|
1251
|
+
* Add two frame values
|
|
1252
|
+
*
|
|
1253
|
+
* @param a - First frame
|
|
1254
|
+
* @param b - Second frame
|
|
1255
|
+
* @returns Sum of frames
|
|
1256
|
+
*/
|
|
1257
|
+
declare function addFrames(a: Frame, b: Frame): Frame;
|
|
1258
|
+
/**
|
|
1259
|
+
* Subtract two frame values
|
|
1260
|
+
*
|
|
1261
|
+
* @param a - First frame
|
|
1262
|
+
* @param b - Second frame (subtracted from a)
|
|
1263
|
+
* @returns Difference of frames (clamped to 0 if negative)
|
|
1264
|
+
*/
|
|
1265
|
+
declare function subtractFrames(a: Frame, b: Frame): Frame;
|
|
1266
|
+
/**
|
|
1267
|
+
* Calculate duration between two frames
|
|
1268
|
+
*
|
|
1269
|
+
* @param start - Start frame
|
|
1270
|
+
* @param end - End frame
|
|
1271
|
+
* @returns Duration in frames (end - start)
|
|
1272
|
+
*/
|
|
1273
|
+
declare function frameDuration(start: Frame, end: Frame): Frame;
|
|
1274
|
+
|
|
1275
|
+
export { type Asset as A, getClipMediaDuration as B, type Clip as C, type DispatchResult as D, invalidResult as E, type Frame as F, type Group as G, invalidResults as H, isValidFrame as I, isValidFrameRate as J, secondsToFrames as K, type LinkGroup as L, type Marker as M, sortTrackClips as N, subtractFrames as O, validResult as P, type RegionMarker as R, type TimelineState as T, type ValidationResult as V, type WorkArea as W, type Track as a, type ClipMarker as b, type TimelineMarker as c, type AssetType as d, type FrameRate as e, type Timeline as f, TimelineEngine as g, type TrackType as h, type ValidationError as i, addFrames as j, clampFrame as k, clipContainsFrame as l, clipsOverlap as m, combineResults as n, createAsset as o, createClip as p, createTimeline as q, createTimelineState as r, createTrack as s, frame as t, frameDuration as u, frameRate as v, framesToMinutesSeconds as w, framesToSeconds as x, framesToTimecode as y, getClipDuration as z };
|