@video-editor/editor-core 1.0.0-beta.1 → 1.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/dist/index.d.ts CHANGED
@@ -1,20 +1,144 @@
1
+ import { AssetReference } from '@video-editor/protocol';
2
+ import { AssetReferenceTarget } from '@video-editor/protocol';
1
3
  import { ComputedRef } from '@vue/reactivity';
2
4
  import { createVideoProtocolManager } from '@video-editor/protocol';
3
5
  import { DeepReadonly } from '@vue/reactivity';
6
+ import { IKeyframeEasing } from '@video-editor/shared';
7
+ import { IKeyframeProperty } from '@video-editor/shared';
4
8
  import { ITrackType } from '@video-editor/shared';
5
9
  import { IVideoProtocol } from '@video-editor/shared';
10
+ import { OperationLogMeta } from '@video-editor/protocol';
6
11
  import { SegmentUnion } from '@video-editor/shared';
7
12
  import { TrackUnion } from '@video-editor/shared';
8
13
 
9
14
  /** Result payload returned by addSegment. */
10
15
  export declare type AddSegmentResult = ReturnType<ProtocolManager['addSegment']>;
11
16
 
17
+ /** Input and result types for track structure commands. */
18
+ export declare type AddTrackOptions = Parameters<ProtocolManager['addTrack']>[0];
19
+
20
+ export declare type AddTrackResult = ReturnType<ProtocolManager['addTrack']>;
21
+
22
+ /**
23
+ * The single commands a batch is built from. Taken as a parameter rather than
24
+ * read off the finished command object so this module cannot accidentally reach
25
+ * for anything else.
26
+ */
27
+ declare interface BatchDeps {
28
+ transaction: EditorCoreCommands['transaction'];
29
+ moveSegment: EditorCoreCommands['moveSegment'];
30
+ removeSegment: EditorCoreCommands['removeSegment'];
31
+ duplicateSegment: EditorCoreCommands['duplicateSegment'];
32
+ updateSegment: EditorCoreCommands['updateSegment'];
33
+ getSegment: EditorCoreSelectors['getSegment'];
34
+ }
35
+
36
+ /**
37
+ * Outcome of a batch command.
38
+ *
39
+ * A batch is atomic: either every step landed and `success` is true, or nothing
40
+ * did and `error` says which step refused. The resulting protocol is read from
41
+ * `editor.state`, which is reactive — the batch reports what it operated on, not
42
+ * a snapshot that later steps in the same transaction may already have moved.
43
+ */
44
+ export declare interface BatchResult {
45
+ success: boolean;
46
+ /** Why the batch was rejected. Absent on success. */
47
+ error?: string;
48
+ /**
49
+ * The segments the batch operated on, in call order. For
50
+ * `duplicateSegments` these are the new copies, not the sources.
51
+ */
52
+ segmentIds: string[];
53
+ }
54
+
55
+ export declare function checkKeyframeCommand(check: KeyframeCommandCheck, getSegment: GetKeyframeSegment): CommandCheckResult;
56
+
57
+ /** The commands `canRun` can answer for, with the arguments each needs. */
58
+ export declare type CommandCheck = {
59
+ command: 'undo';
60
+ } | {
61
+ command: 'redo';
62
+ } | {
63
+ command: 'removeSegment';
64
+ segmentId: string;
65
+ } | {
66
+ command: 'duplicateSegment';
67
+ segmentId: string;
68
+ } | {
69
+ command: 'splitSegment';
70
+ segmentId: string;
71
+ timelineMs: number;
72
+ } | {
73
+ command: 'addTransition';
74
+ } | {
75
+ command: 'setCanvasSize';
76
+ width: number;
77
+ height: number;
78
+ } | {
79
+ command: 'setFps';
80
+ fps: number;
81
+ } | {
82
+ command: 'addTrack';
83
+ input: AddTrackOptions;
84
+ } | {
85
+ command: 'removeTrack';
86
+ trackId: string;
87
+ } | {
88
+ command: 'moveTrack';
89
+ trackId: string;
90
+ toIndex: number;
91
+ } | {
92
+ command: 'replaceSegmentAsset';
93
+ input: ReplaceSegmentAssetOptions;
94
+ } | KeyframeCommandCheck;
95
+
96
+ export declare interface CommandCheckResult {
97
+ ok: boolean;
98
+ /** Why the command would refuse. Absent when `ok`. */
99
+ reason?: string;
100
+ }
101
+
102
+ export declare function createBatchCommands(deps: BatchDeps): {
103
+ moveSegments(moves: readonly MoveSegmentOptions[]): BatchResult;
104
+ removeSegments(ids: readonly string[], options?: RemoveSegmentOptions): BatchResult;
105
+ updateSegments(ids: readonly string[], updater: (segment: SegmentUnion) => void): BatchResult;
106
+ duplicateSegments(ids: readonly string[]): BatchResult;
107
+ };
108
+
12
109
  export declare function createEditorCore(options: EditorCoreOptions): EditorCore;
13
110
 
111
+ export declare function createKeyframeCommands(deps: KeyframeCommandDeps): {
112
+ upsertKeyframe(input: UpsertKeyframeOptions): KeyframeCommandResult;
113
+ moveKeyframe(input: MoveKeyframeOptions): KeyframeCommandResult;
114
+ removeKeyframe(input: RemoveKeyframeOptions): KeyframeCommandResult;
115
+ setKeyframeEasing(input: SetKeyframeEasingOptions): KeyframeCommandResult;
116
+ };
117
+
14
118
  export declare function createPluginManager(ctx: EditorCoreContext): EditorCorePluginManager;
15
119
 
120
+ export declare function createProposalManager(deps: ProposalManagerDependencies): EditorProposalManager;
121
+
16
122
  export declare function createSegmentRegistry(): SegmentRegistry;
17
123
 
124
+ /**
125
+ * Read-only queries over the protocol.
126
+ *
127
+ * An agent has to understand the timeline before it can edit it. Without these
128
+ * it would have to parse the whole protocol itself, which means implementing
129
+ * the timeline rules a second time — and the two copies would drift.
130
+ */
131
+ export declare function createStructuralSelectors(deps: StructuralSelectorDeps): {
132
+ getSegmentsAt: (timeMs: number, options?: SegmentsAtOptions) => SegmentPlacement[];
133
+ getSegmentAt: (trackId: string, timeMs: number) => SegmentUnion | undefined;
134
+ getTrackGaps: (trackId: string) => TrackGap[];
135
+ getAdjacentSegments: (segmentId: string) => SegmentNeighbours;
136
+ getOverlaps: (trackId?: string) => SegmentOverlap[];
137
+ sampleProperty: (segmentId: string, property: IKeyframeProperty, timeMs: number) => SampledProperty | undefined;
138
+ getSelection: () => EditorSelection;
139
+ canRun: (check: CommandCheck) => CommandCheckResult;
140
+ };
141
+
18
142
  /** Result payload returned by duplicateSegment. */
19
143
  export declare type DuplicateSegmentResult = ReturnType<ProtocolManager['duplicateSegment']>;
20
144
 
@@ -28,6 +152,8 @@ export declare interface EditorCore {
28
152
  commands: EditorCoreCommands;
29
153
  /** Read-only selectors. */
30
154
  selectors: EditorCoreSelectors;
155
+ /** Isolated agent proposals waiting for review. */
156
+ proposals: EditorProposalManager;
31
157
  /** Plugin manager instance. */
32
158
  plugins: EditorCorePluginManager;
33
159
  /** Segment plugin registry. */
@@ -60,6 +186,8 @@ export declare interface EditorCoreCommands {
60
186
  moveSegment: ProtocolManager['moveSegment'];
61
187
  /** Resize a segment's time range. */
62
188
  resizeSegment: ProtocolManager['resizeSegment'];
189
+ /** Replace a media source with an explicit duration adaptation strategy. */
190
+ replaceSegmentAsset: ProtocolManager['replaceSegmentAsset'];
63
191
  /** Split a segment into two at a timeline position (single undo step). */
64
192
  splitSegment: ProtocolManager['splitSegment'];
65
193
  /** Add a transition at the current time or a specified time. */
@@ -70,12 +198,61 @@ export declare interface EditorCoreCommands {
70
198
  updateTransition: ProtocolManager['updateTransition'];
71
199
  /** Update a track's mutable presentation fields (hidden / muted / extra). */
72
200
  updateTrack: ProtocolManager['updateTrack'];
201
+ /** Add an empty track. */
202
+ addTrack: ProtocolManager['addTrack'];
203
+ /** Remove a track and all of its segments as one undo step. */
204
+ removeTrack: ProtocolManager['removeTrack'];
205
+ /** Move a track to its final zero-based position. */
206
+ moveTrack: ProtocolManager['moveTrack'];
73
207
  /** Resize the project canvas as a single undoable step. */
74
208
  setCanvasSize: ProtocolManager['setCanvasSize'];
209
+ /** Set the project frame rate as a single undoable step. */
210
+ setFps: ProtocolManager['setFps'];
211
+ /** Insert a keyframe, or update the value at the same property and time. */
212
+ upsertKeyframe: (input: UpsertKeyframeOptions) => KeyframeCommandResult;
213
+ /** Move one keyframe without replacing a frame already at the target time. */
214
+ moveKeyframe: (input: MoveKeyframeOptions) => KeyframeCommandResult;
215
+ /** Remove one keyframe and clean up its empty property track. */
216
+ removeKeyframe: (input: RemoveKeyframeOptions) => KeyframeCommandResult;
217
+ /** Change one keyframe's outgoing easing; omit easing to restore linear. */
218
+ setKeyframeEasing: (input: SetKeyframeEasingOptions) => KeyframeCommandResult;
75
219
  /** Replace a track id (useful for migrations). */
76
220
  replaceTrackId: ProtocolManager['replaceTrackId'];
77
221
  /** Replace a segment id (useful for migrations). */
78
222
  replaceSegmentId: ProtocolManager['replaceSegmentId'];
223
+ /**
224
+ * Move several segments as one undo step. Moves are applied in the order
225
+ * given; if any is refused the whole batch is rolled back.
226
+ */
227
+ moveSegments: (moves: readonly MoveSegmentOptions[]) => BatchResult;
228
+ /**
229
+ * Remove several segments as one undo step. Later segments are removed first,
230
+ * since a ripple delete shifts the ones after it left.
231
+ */
232
+ removeSegments: (ids: readonly string[], options?: RemoveSegmentOptions) => BatchResult;
233
+ /**
234
+ * Apply the same edit to several segments as one undo step — the entry point
235
+ * for adjusting a property across a multi-selection. An edit the protocol
236
+ * rejects fails the whole batch rather than being silently skipped.
237
+ */
238
+ updateSegments: (ids: readonly string[], updater: (segment: SegmentUnion) => void) => BatchResult;
239
+ /** Duplicate several segments as one undo step; reports the new ids. */
240
+ duplicateSegments: (ids: readonly string[]) => BatchResult;
241
+ /**
242
+ * Run a batch of commands as one atomic undo step.
243
+ *
244
+ * Every command inside still updates state immediately, so previews stay
245
+ * live, but only one history item is pushed when the batch commits. If the
246
+ * body throws, the protocol is restored and the error is rethrown; calling
247
+ * `tx.cancel()` discards the batch without touching the undo/redo stacks.
248
+ */
249
+ transaction: ProtocolManager['transaction'];
250
+ /**
251
+ * Open a transaction that spans multiple events, for continuous interactions
252
+ * such as a canvas or timeline drag. Commit on pointer up, cancel to restore
253
+ * the state captured at pointer down.
254
+ */
255
+ beginTransaction: ProtocolManager['beginTransaction'];
79
256
  /** Undo the last mutation. */
80
257
  undo: ProtocolManager['undo'];
81
258
  /** Redo the last undone mutation. */
@@ -103,10 +280,11 @@ export declare interface EditorCoreContext {
103
280
  export declare interface EditorCoreOptions {
104
281
  /** Initial protocol snapshot. */
105
282
  protocol: IVideoProtocol;
106
- /** Optional id generators for segments/tracks. */
283
+ /** Optional id generators for segments, tracks and proposals. */
107
284
  idFactory?: {
108
285
  segment?: () => string;
109
286
  track?: () => string;
287
+ proposal?: () => string;
110
288
  };
111
289
  /** Optional shared services (resource manager, renderer, etc). */
112
290
  services?: EditorCoreServices;
@@ -164,6 +342,37 @@ export declare interface EditorCoreSelectors {
164
342
  getTrackBySegmentId: (segmentId: string) => DeepReadonly<TrackUnion> | undefined;
165
343
  /** List tracks, optionally filtered by type. */
166
344
  getTracks: (trackType?: ITrackType) => DeepReadonly<TrackUnion>[];
345
+ /**
346
+ * Every segment playing at `timeMs`, in track order — the answer to "what is
347
+ * on screen right now". Segment ranges are half-open, so a segment ending at
348
+ * `timeMs` and the one starting there are never both returned.
349
+ */
350
+ getSegmentsAt: (timeMs: number, options?: SegmentsAtOptions) => SegmentPlacement[];
351
+ /** The segment playing on one track at `timeMs`. */
352
+ getSegmentAt: (trackId: string, timeMs: number) => SegmentUnion | undefined;
353
+ /**
354
+ * Bounded empty stretches on a track, in order. The open range after the last
355
+ * segment is not a gap — nothing bounds it.
356
+ */
357
+ getTrackGaps: (trackId: string) => TrackGap[];
358
+ /** The segments either side of one, on its own track. */
359
+ getAdjacentSegments: (segmentId: string) => SegmentNeighbours;
360
+ /** Segments of one track (or every track) that share time, which is invalid. */
361
+ getOverlaps: (trackId?: string) => SegmentOverlap[];
362
+ /**
363
+ * A property's effective value at a moment, and whether it came from a
364
+ * keyframe, an interpolation between two, the segment's static field, or the
365
+ * documented default. Uses the same pure sampler as the renderer and export.
366
+ */
367
+ sampleProperty: (segmentId: string, property: IKeyframeProperty, timeMs: number) => SampledProperty | undefined;
368
+ /** The current selection, resolved against the protocol. */
369
+ getSelection: () => EditorSelection;
370
+ /** Find every segment in this protocol that uses a library asset. */
371
+ getAssetReferences: (asset: AssetReferenceTarget) => AssetReference[];
372
+ /** Semantic history entries without internal Immer patches. */
373
+ getOperationLog: () => ProtocolManager['operationLog']['value'];
374
+ /** Whether a command would do anything right now, and why not when it would not. */
375
+ canRun: (check: CommandCheck) => CommandCheckResult;
167
376
  }
168
377
 
169
378
  /**
@@ -198,26 +407,195 @@ export declare interface EditorCoreState {
198
407
  undoCount: ProtocolManager['undoCount'];
199
408
  /** Redo stack size. */
200
409
  redoCount: ProtocolManager['redoCount'];
410
+ /** Semantic descriptions of the currently reachable history branch. */
411
+ operationLog: ProtocolManager['operationLog'];
412
+ /** Monotonic committed-state version used for proposal conflict checks. */
413
+ revision: ProtocolManager['revision'];
414
+ /** Whether a history transaction is currently open. */
415
+ isTransactionActive: ProtocolManager['isTransactionActive'];
416
+ /** Nesting depth of the open transaction; 0 when none is open. */
417
+ transactionDepth: ProtocolManager['transactionDepth'];
418
+ }
419
+
420
+ /** A valid protocol preview produced without changing the main editor. */
421
+ export declare interface EditorProposal {
422
+ id: string;
423
+ baseRevision: number;
424
+ previewProtocol: IVideoProtocol;
425
+ validation: {
426
+ valid: true;
427
+ };
428
+ operations: readonly OperationLogMeta[];
429
+ summary: ProposalChangeSummary;
430
+ }
431
+
432
+ export declare interface EditorProposalManager {
433
+ /** Run commands against an isolated copy and keep the resulting preview for review. */
434
+ create: (build: (editor: EditorCore) => void, options?: {
435
+ id?: string;
436
+ }) => ProposalActionResult;
437
+ /** Apply the whole proposal as one history item if its base revision is current. */
438
+ accept: (id: string) => ProposalActionResult;
439
+ /** Discard a proposal without touching protocol history. */
440
+ reject: (id: string) => ProposalActionResult;
441
+ get: (id: string) => EditorProposal | undefined;
442
+ list: () => EditorProposal[];
443
+ }
444
+
445
+ export declare interface EditorSelection {
446
+ segmentId?: string;
447
+ segment?: SegmentUnion;
448
+ trackId?: string;
449
+ }
450
+
451
+ declare type GetKeyframeSegment = (segmentId: string) => ReadableKeyframeSegment | undefined;
452
+
453
+ export declare type KeyframeCommandCheck = {
454
+ command: 'upsertKeyframe';
455
+ input: UpsertKeyframeOptions;
456
+ } | {
457
+ command: 'moveKeyframe';
458
+ input: MoveKeyframeOptions;
459
+ } | {
460
+ command: 'removeKeyframe';
461
+ input: RemoveKeyframeOptions;
462
+ } | {
463
+ command: 'setKeyframeEasing';
464
+ input: SetKeyframeEasingOptions;
465
+ };
466
+
467
+ declare interface KeyframeCommandDeps {
468
+ getSegment: GetKeyframeSegment;
469
+ updateSegment: EditorCoreCommands['updateSegment'];
470
+ transaction: EditorCoreCommands['transaction'];
471
+ }
472
+
473
+ export declare interface KeyframeCommandResult {
474
+ success: boolean;
475
+ error?: string;
476
+ }
477
+
478
+ export declare interface MoveKeyframeOptions {
479
+ segmentId: string;
480
+ property: IKeyframeProperty;
481
+ /** Current segment-relative timeline time in ms. */
482
+ timeMs: number;
483
+ /** New segment-relative timeline time in ms. */
484
+ toTimeMs: number;
201
485
  }
202
486
 
203
487
  /** Options for moving a segment between tracks or within a track. */
204
488
  export declare type MoveSegmentOptions = Parameters<ProtocolManager['moveSegment']>[0];
205
489
 
490
+ export declare interface ProposalActionResult {
491
+ success: boolean;
492
+ error?: string;
493
+ proposal?: EditorProposal;
494
+ }
495
+
496
+ export declare interface ProposalChangeSummary {
497
+ addedTrackIds: string[];
498
+ removedTrackIds: string[];
499
+ changedTrackIds: string[];
500
+ addedSegmentIds: string[];
501
+ removedSegmentIds: string[];
502
+ changedSegmentIds: string[];
503
+ projectFields: Array<'width' | 'height' | 'fps'>;
504
+ }
505
+
506
+ declare interface ProposalManagerDependencies {
507
+ getProtocol: () => IVideoProtocol;
508
+ getRevision: () => number;
509
+ isTransactionActive: () => boolean;
510
+ createSandbox: (protocol: IVideoProtocol) => EditorCore;
511
+ applySnapshot: (protocol: IVideoProtocol, proposal: EditorProposal) => 'committed' | 'empty' | 'cancelled' | 'invalid' | 'nested';
512
+ createId: () => string;
513
+ }
514
+
206
515
  /** Internal protocol manager type used to align editor-core signatures with protocol behavior. */
207
516
  declare type ProtocolManager = ReturnType<typeof createVideoProtocolManager>;
208
517
 
518
+ declare type ReadableEasing = Exclude<IKeyframeEasing, [number, number, number, number]> | readonly [number, number, number, number];
519
+
520
+ declare interface ReadableKeyframe {
521
+ readonly timeMs: number;
522
+ readonly value: number;
523
+ readonly easing?: ReadableEasing;
524
+ }
525
+
526
+ declare interface ReadableKeyframeSegment {
527
+ readonly segmentType: SegmentUnion['segmentType'];
528
+ readonly type?: string;
529
+ readonly startTime: number;
530
+ readonly endTime: number;
531
+ readonly keyframes?: readonly {
532
+ readonly property: IKeyframeProperty;
533
+ readonly frames: readonly ReadableKeyframe[];
534
+ }[];
535
+ }
536
+
537
+ export declare interface RemoveKeyframeOptions {
538
+ segmentId: string;
539
+ property: IKeyframeProperty;
540
+ /** Segment-relative timeline time in ms. */
541
+ timeMs: number;
542
+ }
543
+
209
544
  /** Optional flags accepted by removeSegment (e.g. ripple delete). */
210
545
  export declare type RemoveSegmentOptions = Parameters<ProtocolManager['removeSegment']>[1];
211
546
 
547
+ /** Input and result types for replacing a segment's media source. */
548
+ export declare type ReplaceSegmentAssetOptions = Parameters<ProtocolManager['replaceSegmentAsset']>[0];
549
+
550
+ export declare type ReplaceSegmentAssetResult = ReturnType<ProtocolManager['replaceSegmentAsset']>;
551
+
212
552
  /** Options for resizing a segment on a track. */
213
553
  export declare type ResizeSegmentOptions = Parameters<ProtocolManager['resizeSegment']>[0];
214
554
 
555
+ /**
556
+ * A property's effective value at a moment, and where it came from — a curve,
557
+ * the segment's own field, or the documented fallback. An agent reviewing a
558
+ * value needs to know which, since only an interpolated one is changing here.
559
+ *
560
+ * `keyframe` covers a value read straight off a frame, including one held at
561
+ * either end of the curve; `interpolated` means strictly between two frames.
562
+ */
563
+ export declare interface SampledProperty {
564
+ value: number;
565
+ source: 'keyframe' | 'interpolated' | 'static' | 'default';
566
+ /** False when the queried time lies outside the segment's own range. */
567
+ withinSegment: boolean;
568
+ }
569
+
215
570
  /** Input payload for adding a segment (id is optional). */
216
571
  export declare type SegmentInput = Parameters<ProtocolManager['addSegment']>[0];
217
572
 
218
573
  /** Result payload returned by segment mutation commands. */
219
574
  export declare type SegmentMutationResult = ReturnType<ProtocolManager['removeSegment']>;
220
575
 
576
+ export declare interface SegmentNeighbours {
577
+ trackId?: string;
578
+ previous?: SegmentUnion;
579
+ next?: SegmentUnion;
580
+ }
581
+
582
+ /** Two segments of one track sharing time, which the timeline rules forbid. */
583
+ export declare interface SegmentOverlap {
584
+ trackId: string;
585
+ a: SegmentUnion;
586
+ b: SegmentUnion;
587
+ /** The overlapping stretch itself. */
588
+ startTime: number;
589
+ endTime: number;
590
+ }
591
+
592
+ /** Where a segment sits: the segment itself plus the track carrying it. */
593
+ export declare interface SegmentPlacement {
594
+ segment: SegmentUnion;
595
+ trackId: string;
596
+ trackType: ITrackType;
597
+ }
598
+
221
599
  /**
222
600
  * Segment plugin bundle: ops + renderer adapter + optional UI bindings.
223
601
  */
@@ -264,10 +642,52 @@ export declare interface SegmentRegistry {
264
642
  list: () => SegmentPlugin[];
265
643
  }
266
644
 
645
+ export declare interface SegmentsAtOptions {
646
+ /** Only look at tracks of this type. */
647
+ trackType?: ITrackType;
648
+ /** Pass `false` to skip hidden tracks — what is actually on screen. */
649
+ includeHidden?: boolean;
650
+ }
651
+
267
652
  /** Result payload returned by setCanvasSize. */
268
653
  export declare type SetCanvasSizeResult = ReturnType<ProtocolManager['setCanvasSize']>;
269
654
 
655
+ /** Result payload returned by setFps. */
656
+ export declare type SetFpsResult = ReturnType<ProtocolManager['setFps']>;
657
+
658
+ export declare interface SetKeyframeEasingOptions extends RemoveKeyframeOptions {
659
+ /** Omit to restore linear easing. */
660
+ easing?: IKeyframeEasing;
661
+ }
662
+
663
+ declare interface StructuralSelectorDeps {
664
+ protocol: () => IVideoProtocol;
665
+ selectedSegmentId: () => string | undefined;
666
+ undoCount: () => number;
667
+ redoCount: () => number;
668
+ }
669
+
670
+ export declare function summarizeProposal(before: IVideoProtocol, after: IVideoProtocol): ProposalChangeSummary;
671
+
672
+ /** A bounded stretch of empty time on a track. */
673
+ export declare interface TrackGap {
674
+ startTime: number;
675
+ endTime: number;
676
+ }
677
+
270
678
  /** The mutable track fields exposed to an updateTrack updater. */
271
679
  export declare type TrackMutableFields = Parameters<Parameters<ProtocolManager['updateTrack']>[1]>[0];
272
680
 
681
+ export declare type TrackStructureResult = ReturnType<ProtocolManager['removeTrack']>;
682
+
683
+ export declare interface UpsertKeyframeOptions {
684
+ segmentId: string;
685
+ property: IKeyframeProperty;
686
+ /** Segment-relative timeline time in ms. */
687
+ timeMs: number;
688
+ value: number;
689
+ /** When omitted, an existing frame keeps its easing. */
690
+ easing?: IKeyframeEasing;
691
+ }
692
+
273
693
  export { }
package/dist/index.js CHANGED
@@ -1,74 +1,603 @@
1
- import { createVideoProtocolManager as v } from "@video-editor/protocol";
2
- import { computed as p } from "@vue/reactivity";
3
- function y(s) {
4
- const e = [], o = /* @__PURE__ */ new Map();
5
- let c = !1;
1
+ import { MIN_FPS as R, MIN_CANVAS_SIZE as L, MAX_CANVAS_SIZE as j, createVideoProtocolManager as Q, findAssetReferences as W } from "@video-editor/protocol";
2
+ import { computed as O } from "@vue/reactivity";
3
+ import { isVideoFramesSegment as ee, isAudioSegment as te, sampleKeyframes as re } from "@video-editor/shared";
4
+ function K(t) {
5
+ return { success: !0, segmentIds: t };
6
+ }
7
+ function _(t) {
8
+ return { success: !1, error: t, segmentIds: [] };
9
+ }
10
+ function P(t) {
11
+ return [...new Set(t)];
12
+ }
13
+ function ne(t) {
14
+ const { transaction: e, getSegment: n } = t;
15
+ function i(r, s, c) {
16
+ let p = _(`${r}: nothing ran`);
17
+ return e((v) => {
18
+ const f = c();
19
+ if (typeof f == "string") {
20
+ p = _(f), v.cancel();
21
+ return;
22
+ }
23
+ p = K(f);
24
+ }, { label: r, data: s }), p;
25
+ }
26
+ function a(r, s) {
27
+ const c = [];
28
+ for (const p of r) {
29
+ const v = n(p);
30
+ if (!v)
31
+ return `${s}: no segment with id ${p}`;
32
+ c.push({ id: v.id, startTime: v.startTime });
33
+ }
34
+ return c;
35
+ }
6
36
  return {
7
- register: async (t, n) => {
8
- const a = t(s), { name: i } = a;
9
- if (o.has(i)) {
10
- if (!n?.override)
11
- throw new Error(`Plugin ${i} has been registered`);
12
- await o.get(i)?.destroy?.();
13
- const u = e.findIndex((S) => S.name === i);
14
- u >= 0 && e.splice(u, 1), o.delete(i);
37
+ moveSegments(r) {
38
+ return r.length ? i("move-segments", { moves: r.map((s) => ({ ...s })) }, () => {
39
+ const s = [];
40
+ for (const c of r) {
41
+ if (!t.moveSegment(c).success)
42
+ return `move-segments: could not move ${c.segmentId}`;
43
+ s.push(c.segmentId);
44
+ }
45
+ return s;
46
+ }) : K([]);
47
+ },
48
+ removeSegments(r, s) {
49
+ const c = P(r);
50
+ return c.length ? i("remove-segments", { segmentIds: c, ripple: s?.ripple === !0 }, () => {
51
+ const p = a(c, "remove-segments");
52
+ if (typeof p == "string")
53
+ return p;
54
+ const v = p.map((f, k) => ({ segment: f, index: k })).sort((f, k) => k.segment.startTime - f.segment.startTime || k.index - f.index);
55
+ for (const { segment: f } of v)
56
+ if (!t.removeSegment(f.id, s).success)
57
+ return `remove-segments: could not remove ${f.id}`;
58
+ return c;
59
+ }) : K([]);
60
+ },
61
+ updateSegments(r, s) {
62
+ const c = P(r);
63
+ return c.length ? i("update-segments", { segmentIds: c }, () => {
64
+ const p = a(c, "update-segments");
65
+ if (typeof p == "string")
66
+ return p;
67
+ for (const v of c)
68
+ if (!t.updateSegment(s, v))
69
+ return `update-segments: rejected the edit to ${v}`;
70
+ return c;
71
+ }) : K([]);
72
+ },
73
+ duplicateSegments(r) {
74
+ const s = P(r);
75
+ return s.length ? i("duplicate-segments", { segmentIds: s }, () => {
76
+ const c = a(s, "duplicate-segments");
77
+ if (typeof c == "string")
78
+ return c;
79
+ const p = [];
80
+ for (const v of s) {
81
+ const f = t.duplicateSegment(v);
82
+ if (!f.success)
83
+ return `duplicate-segments: could not duplicate ${v}`;
84
+ p.push(f.id);
85
+ }
86
+ return p;
87
+ }) : K([]);
88
+ }
89
+ };
90
+ }
91
+ const se = /* @__PURE__ */ new Set(["linear", "easeIn", "easeOut", "easeInOut"]), oe = {
92
+ moveKeyframe: "move-keyframe",
93
+ removeKeyframe: "remove-keyframe",
94
+ setKeyframeEasing: "set-keyframe-easing",
95
+ upsertKeyframe: "upsert-keyframe"
96
+ };
97
+ function T(t) {
98
+ return { ok: !1, reason: t };
99
+ }
100
+ function q(t) {
101
+ return t === void 0 ? !0 : typeof t == "string" ? se.has(t) : t.length === 4 && t.every(Number.isFinite);
102
+ }
103
+ function z(t, e) {
104
+ return t === e ? !0 : !Array.isArray(t) || !Array.isArray(e) ? !1 : t.every((n, i) => n === e[i]);
105
+ }
106
+ function E(t) {
107
+ return Array.isArray(t) ? [...t] : t;
108
+ }
109
+ function ae(t, e) {
110
+ return e === "opacity" ? t.segmentType === "frames" || t.segmentType === "text" : e === "volume" ? t.segmentType === "audio" || t.segmentType === "frames" && t.type === "video" : e === "intensity" ? t.segmentType === "filter" : t.segmentType === "frames" || t.segmentType === "text" || t.segmentType === "sticker";
111
+ }
112
+ function D(t, e, n) {
113
+ return t.keyframes?.find((a) => a.property === e)?.frames.find((a) => a.timeMs === n);
114
+ }
115
+ function V(t, e) {
116
+ const n = t(e.segmentId);
117
+ if (!n)
118
+ return T(`no segment with id ${e.segmentId}`);
119
+ if (!ae(n, e.property))
120
+ return T(`${e.property} keyframes are not supported by this segment`);
121
+ if (!Number.isFinite(e.timeMs))
122
+ return T("keyframe time must be a finite number");
123
+ const i = n.endTime - n.startTime;
124
+ return e.timeMs < 0 || e.timeMs > i ? T(`keyframe time must be between 0 and ${i}`) : { ok: !0 };
125
+ }
126
+ function Y(t, e) {
127
+ const n = V(e, t.input);
128
+ if (!n.ok)
129
+ return n;
130
+ const i = e(t.input.segmentId), a = D(i, t.input.property, t.input.timeMs);
131
+ switch (t.command) {
132
+ case "upsertKeyframe": {
133
+ const { input: r } = t;
134
+ return Number.isFinite(r.value) ? q(r.easing) ? a && a.value === r.value && (r.easing === void 0 || z(a.easing, r.easing)) ? T("keyframe already has the requested value") : { ok: !0 } : T("keyframe easing is invalid") : T("keyframe value must be a finite number");
135
+ }
136
+ case "moveKeyframe": {
137
+ const { input: r } = t;
138
+ if (!a)
139
+ return T(`no ${r.property} keyframe at ${r.timeMs}`);
140
+ const s = V(e, { ...r, timeMs: r.toTimeMs });
141
+ return s.ok ? r.toTimeMs === r.timeMs ? T("keyframe is already at the requested time") : D(i, r.property, r.toTimeMs) ? T(`a ${r.property} keyframe already exists at ${r.toTimeMs}`) : { ok: !0 } : s;
142
+ }
143
+ case "removeKeyframe": {
144
+ const { input: r } = t;
145
+ return a ? { ok: !0 } : T(`no ${r.property} keyframe at ${r.timeMs}`);
146
+ }
147
+ case "setKeyframeEasing": {
148
+ const { input: r } = t;
149
+ return a ? q(r.easing) ? z(a.easing, r.easing) ? T("keyframe already has the requested easing") : { ok: !0 } : T("keyframe easing is invalid") : T(`no ${r.property} keyframe at ${r.timeMs}`);
150
+ }
151
+ }
152
+ }
153
+ function ie(t) {
154
+ function e(n, i) {
155
+ const a = Y(n, t.getSegment);
156
+ return a.ok ? t.transaction(() => t.updateSegment(i, n.input.segmentId) ? { success: !0 } : { success: !1, error: "the protocol rejected the keyframe edit" }, {
157
+ label: oe[n.command],
158
+ data: { ...n.input }
159
+ }).value : { success: !1, error: a.reason };
160
+ }
161
+ return {
162
+ upsertKeyframe(n) {
163
+ return e({ command: "upsertKeyframe", input: n }, (i) => {
164
+ const a = i.keyframes ?? (i.keyframes = []);
165
+ let r = a.find((c) => c.property === n.property);
166
+ r || (r = { property: n.property, frames: [] }, a.push(r));
167
+ const s = r.frames.find((c) => c.timeMs === n.timeMs);
168
+ s ? (s.value = n.value, n.easing !== void 0 && (s.easing = E(n.easing))) : (r.frames.push({
169
+ timeMs: n.timeMs,
170
+ value: n.value,
171
+ ...n.easing === void 0 ? {} : { easing: E(n.easing) }
172
+ }), r.frames.sort((c, p) => c.timeMs - p.timeMs));
173
+ });
174
+ },
175
+ moveKeyframe(n) {
176
+ return e({ command: "moveKeyframe", input: n }, (i) => {
177
+ const a = i.keyframes.find((s) => s.property === n.property), r = a.frames.find((s) => s.timeMs === n.timeMs);
178
+ r.timeMs = n.toTimeMs, a.frames.sort((s, c) => s.timeMs - c.timeMs);
179
+ });
180
+ },
181
+ removeKeyframe(n) {
182
+ return e({ command: "removeKeyframe", input: n }, (i) => {
183
+ const a = i.keyframes, r = a.findIndex((p) => p.property === n.property), s = a[r], c = s.frames.findIndex((p) => p.timeMs === n.timeMs);
184
+ s.frames.splice(c, 1), s.frames.length || a.splice(r, 1), a.length || delete i.keyframes;
185
+ });
186
+ },
187
+ setKeyframeEasing(n) {
188
+ return e({ command: "setKeyframeEasing", input: n }, (i) => {
189
+ const r = i.keyframes.find((s) => s.property === n.property).frames.find((s) => s.timeMs === n.timeMs);
190
+ n.easing === void 0 ? delete r.easing : r.easing = E(n.easing);
191
+ });
192
+ }
193
+ };
194
+ }
195
+ function ce(t) {
196
+ const e = [], n = /* @__PURE__ */ new Map();
197
+ let i = !1;
198
+ return {
199
+ register: async (f, k) => {
200
+ const w = f(t), { name: S } = w;
201
+ if (n.has(S)) {
202
+ if (!k?.override)
203
+ throw new Error(`Plugin ${S} has been registered`);
204
+ await n.get(S)?.destroy?.();
205
+ const g = e.findIndex((u) => u.name === S);
206
+ g >= 0 && e.splice(g, 1), n.delete(S);
15
207
  }
16
- e.push(a), o.set(i, a), (n?.autoInit || c) && await a.init?.();
208
+ e.push(w), n.set(S, w), (k?.autoInit || i) && await w.init?.();
17
209
  },
18
210
  init: async () => {
19
- for (const t of e)
20
- await t.init?.();
21
- c = !0;
211
+ for (const f of e)
212
+ await f.init?.();
213
+ i = !0;
22
214
  },
23
- get: (t) => o.get(t),
24
- has: (t) => o.has(t),
25
- remove: async (t) => {
26
- const n = e.findIndex((i) => i.name === t);
27
- return n === -1 ? !1 : (await e[n].destroy?.(), e.splice(n, 1), o.delete(t));
215
+ get: (f) => n.get(f),
216
+ has: (f) => n.has(f),
217
+ remove: async (f) => {
218
+ const k = e.findIndex((S) => S.name === f);
219
+ return k === -1 ? !1 : (await e[k].destroy?.(), e.splice(k, 1), n.delete(f));
28
220
  },
29
221
  destroy: async () => {
30
- for (const t of e)
31
- await t.destroy?.();
32
- e.length = 0, o.clear(), c = !1;
222
+ for (const f of e)
223
+ await f.destroy?.();
224
+ e.length = 0, n.clear(), i = !1;
33
225
  }
34
226
  };
35
227
  }
36
- function T() {
37
- const s = /* @__PURE__ */ new Map();
228
+ function Z(t, e) {
229
+ return JSON.stringify(t) === JSON.stringify(e);
230
+ }
231
+ function B(t) {
232
+ const e = /* @__PURE__ */ new Map();
233
+ for (const n of t.tracks)
234
+ for (const i of n.children)
235
+ e.set(i.id, i);
236
+ return e;
237
+ }
238
+ function U(t) {
239
+ return new Map(t.tracks.map((e) => [e.trackId, e]));
240
+ }
241
+ function G(t, e) {
242
+ const n = [], i = [], a = [];
243
+ for (const [r, s] of e) {
244
+ const c = t.get(r);
245
+ c === void 0 ? n.push(r) : Z(c, s) || a.push(r);
246
+ }
247
+ for (const r of t.keys())
248
+ e.has(r) || i.push(r);
249
+ return { added: n, removed: i, changed: a };
250
+ }
251
+ function me(t, e) {
252
+ const n = G(U(t), U(e)), i = G(B(t), B(e)), a = [];
253
+ for (const r of ["width", "height", "fps"])
254
+ t[r] !== e[r] && a.push(r);
255
+ return {
256
+ addedTrackIds: n.added,
257
+ removedTrackIds: n.removed,
258
+ changedTrackIds: n.changed,
259
+ addedSegmentIds: i.added,
260
+ removedSegmentIds: i.removed,
261
+ changedSegmentIds: i.changed,
262
+ projectFields: a
263
+ };
264
+ }
265
+ function A(t) {
266
+ return structuredClone(t);
267
+ }
268
+ function J(t) {
269
+ return {
270
+ success: !1,
271
+ error: t instanceof Error ? t.message : String(t)
272
+ };
273
+ }
274
+ function ue(t) {
275
+ const e = /* @__PURE__ */ new Map();
276
+ return {
277
+ create: (r, s) => {
278
+ const c = s?.id ?? t.createId();
279
+ if (!c)
280
+ return { success: !1, error: "proposal id must not be empty" };
281
+ if (e.has(c))
282
+ return { success: !1, error: `proposal id ${c} already exists` };
283
+ const p = t.getRevision(), v = structuredClone(t.getProtocol()), f = t.createSandbox(v);
284
+ try {
285
+ r(f);
286
+ const k = structuredClone(f.commands.exportProtocol());
287
+ if (Z(v, k))
288
+ return { success: !1, error: "proposal must change the protocol" };
289
+ const w = f.selectors.getOperationLog().filter((M) => M.status === "applied").flatMap((M) => M.operations.length ? M.operations : M.meta === void 0 ? [] : [M.meta]), S = {
290
+ id: c,
291
+ baseRevision: p,
292
+ previewProtocol: k,
293
+ validation: { valid: !0 },
294
+ operations: structuredClone(w),
295
+ summary: me(v, k)
296
+ };
297
+ return e.set(c, S), { success: !0, proposal: A(S) };
298
+ } catch (k) {
299
+ return J(k);
300
+ }
301
+ },
302
+ accept: (r) => {
303
+ const s = e.get(r);
304
+ if (!s)
305
+ return { success: !1, error: `no proposal with id ${r}` };
306
+ if (t.isTransactionActive())
307
+ return { success: !1, error: "cannot accept a proposal while a transaction is active" };
308
+ if (t.getRevision() !== s.baseRevision)
309
+ return {
310
+ success: !1,
311
+ error: `proposal ${r} conflicts with the current protocol revision`
312
+ };
313
+ try {
314
+ const c = t.applySnapshot(s.previewProtocol, s);
315
+ return c !== "committed" ? { success: !1, error: `proposal ${r} could not be committed: ${c}` } : (e.delete(r), { success: !0, proposal: A(s) });
316
+ } catch (c) {
317
+ return J(c);
318
+ }
319
+ },
320
+ reject: (r) => {
321
+ const s = e.get(r);
322
+ return s ? (e.delete(r), { success: !0, proposal: A(s) }) : { success: !1, error: `no proposal with id ${r}` };
323
+ },
324
+ get: (r) => {
325
+ const s = e.get(r);
326
+ return s === void 0 ? void 0 : A(s);
327
+ },
328
+ list: () => [...e.values()].map(A)
329
+ };
330
+ }
331
+ function de() {
332
+ const t = /* @__PURE__ */ new Map();
38
333
  return {
39
- register: (r, d) => {
40
- if (s.has(r.type) && !d?.override)
41
- throw new Error(`Segment plugin ${r.type} has been registered`);
42
- s.set(r.type, r);
334
+ register: (a, r) => {
335
+ if (t.has(a.type) && !r?.override)
336
+ throw new Error(`Segment plugin ${a.type} has been registered`);
337
+ t.set(a.type, a);
43
338
  },
44
- get: (r) => s.get(r),
45
- list: () => [...s.values()]
339
+ get: (a) => t.get(a),
340
+ list: () => [...t.values()]
46
341
  };
47
342
  }
48
- function k(s) {
343
+ const fe = {
344
+ opacity: 1,
345
+ "position.x": 0,
346
+ "position.y": 0,
347
+ scale: 1,
348
+ rotation: 0,
349
+ volume: 1,
350
+ intensity: 1
351
+ }, le = /* @__PURE__ */ new Set(["frames", "text", "sticker", "audio", "effect", "filter"]);
352
+ function ge(t, e) {
353
+ const n = t, i = n.transform;
354
+ switch (e) {
355
+ case "opacity":
356
+ return n.opacity;
357
+ case "volume":
358
+ return n.volume;
359
+ case "intensity":
360
+ return n.intensity;
361
+ case "position.x":
362
+ return i?.position?.[0];
363
+ case "position.y":
364
+ return i?.position?.[1];
365
+ case "scale":
366
+ return i?.scale?.[0];
367
+ case "rotation":
368
+ return i?.rotation?.[2];
369
+ }
370
+ }
371
+ function pe(t, e) {
372
+ return t.keyframes?.find((n) => n.property === e && n.frames.length > 0);
373
+ }
374
+ function F(t, e) {
375
+ return e >= t.startTime && e < t.endTime;
376
+ }
377
+ function N(t) {
378
+ return [...t.children].sort((e, n) => e.startTime - n.startTime);
379
+ }
380
+ function ye(t) {
381
+ const { protocol: e, selectedSegmentId: n, undoCount: i, redoCount: a } = t, r = (m) => e().tracks.find((o) => o.children.some((l) => l.id === m)), s = (m) => {
382
+ for (const o of e().tracks) {
383
+ const l = o.children.find((d) => d.id === m);
384
+ if (l)
385
+ return l;
386
+ }
387
+ }, c = () => e().tracks.find((m) => m.trackType === "frames" && m.isMain);
388
+ function p(m, o) {
389
+ const l = [];
390
+ for (const d of e().tracks)
391
+ if (!(o?.trackType && d.trackType !== o.trackType) && !(o?.includeHidden === !1 && d.hidden))
392
+ for (const y of d.children)
393
+ F(y, m) && l.push({ segment: y, trackId: d.trackId, trackType: d.trackType });
394
+ return l;
395
+ }
396
+ function v(m, o) {
397
+ return e().tracks.find((d) => d.trackId === m)?.children.find((d) => F(d, o));
398
+ }
399
+ function f(m) {
400
+ const o = e().tracks.find((y) => y.trackId === m);
401
+ if (!o)
402
+ return [];
403
+ const l = [];
404
+ let d = 0;
405
+ for (const y of N(o))
406
+ y.startTime > d && l.push({ startTime: d, endTime: y.startTime }), d = Math.max(d, y.endTime);
407
+ return l;
408
+ }
409
+ function k(m) {
410
+ const o = r(m);
411
+ if (!o)
412
+ return {};
413
+ const l = N(o), d = l.findIndex((y) => y.id === m);
414
+ return d < 0 ? {} : {
415
+ trackId: o.trackId,
416
+ previous: l[d - 1],
417
+ next: l[d + 1]
418
+ };
419
+ }
420
+ function w(m) {
421
+ const o = m ? e().tracks.filter((d) => d.trackId === m) : e().tracks, l = [];
422
+ for (const d of o) {
423
+ const y = N(d);
424
+ for (let h = 0; h < y.length - 1; h++) {
425
+ const I = y[h];
426
+ for (let b = h + 1; b < y.length; b++) {
427
+ const x = y[b];
428
+ if (x.startTime >= I.endTime)
429
+ break;
430
+ l.push({
431
+ trackId: d.trackId,
432
+ a: I,
433
+ b: x,
434
+ startTime: x.startTime,
435
+ endTime: Math.min(I.endTime, x.endTime)
436
+ });
437
+ }
438
+ }
439
+ }
440
+ return l;
441
+ }
442
+ function S(m, o, l) {
443
+ const d = s(m);
444
+ if (!d)
445
+ return;
446
+ const y = F(d, l), h = pe(d, o);
447
+ if (h) {
448
+ const b = Math.max(0, l - d.startTime), x = re(h, b);
449
+ if (Number.isFinite(x)) {
450
+ const $ = h.frames, H = $.length === 1 || b <= $[0].timeMs || b >= $[$.length - 1].timeMs || $.some((X) => X.timeMs === b);
451
+ return {
452
+ value: x,
453
+ source: H ? "keyframe" : "interpolated",
454
+ withinSegment: y
455
+ };
456
+ }
457
+ }
458
+ const I = ge(d, o);
459
+ return I !== void 0 && Number.isFinite(I) ? { value: I, source: "static", withinSegment: y } : { value: fe[o], source: "default", withinSegment: y };
460
+ }
461
+ function M() {
462
+ const m = n();
463
+ if (!m)
464
+ return {};
465
+ const o = s(m);
466
+ return o ? { segmentId: m, segment: o, trackId: r(m)?.trackId } : {};
467
+ }
468
+ const g = { ok: !0 }, u = (m) => ({ ok: !1, reason: m });
469
+ function C(m) {
470
+ switch (m.command) {
471
+ case "undo":
472
+ return i() > 0 ? g : u("nothing to undo");
473
+ case "redo":
474
+ return a() > 0 ? g : u("nothing to redo");
475
+ case "removeSegment":
476
+ case "duplicateSegment":
477
+ return s(m.segmentId) ? g : u(`no segment with id ${m.segmentId}`);
478
+ case "splitSegment": {
479
+ const o = s(m.segmentId);
480
+ return o ? Number.isFinite(m.timelineMs) ? m.timelineMs <= o.startTime || m.timelineMs >= o.endTime ? u("split time must fall strictly inside the segment") : g : u("split time must be a finite number") : u(`no segment with id ${m.segmentId}`);
481
+ }
482
+ case "addTransition": {
483
+ const o = c();
484
+ return o ? o.children.length < 2 ? u("a transition needs two adjacent segments") : g : u("the project has no main frames track");
485
+ }
486
+ case "setCanvasSize": {
487
+ for (const [o, l] of [["width", m.width], ["height", m.height]]) {
488
+ if (!Number.isInteger(l))
489
+ return u(`${o} must be a whole number of pixels`);
490
+ if (l < L || l > j)
491
+ return u(`${o} must be between ${L} and ${j}`);
492
+ }
493
+ return g;
494
+ }
495
+ case "setFps":
496
+ return Number.isFinite(m.fps) ? m.fps < R ? u(`fps must be at least ${R}`) : g : u("fps must be a finite number");
497
+ case "addTrack": {
498
+ const { input: o } = m;
499
+ if (!le.has(o.trackType))
500
+ return u(`unsupported track type ${String(o.trackType)}`);
501
+ if (o.trackId !== void 0 && (typeof o.trackId != "string" || o.trackId.length === 0))
502
+ return u("track id must not be empty");
503
+ if (o.trackId && e().tracks.some((d) => d.trackId === o.trackId))
504
+ return u(`track id ${o.trackId} already exists`);
505
+ const l = o.index ?? 0;
506
+ return !Number.isInteger(l) || l < 0 || l > e().tracks.length ? u(`track index must be between 0 and ${e().tracks.length}`) : g;
507
+ }
508
+ case "removeTrack":
509
+ return e().tracks.some((o) => o.trackId === m.trackId) ? g : u(`no track with id ${m.trackId}`);
510
+ case "moveTrack": {
511
+ const o = e().tracks;
512
+ return o.some((l) => l.trackId === m.trackId) ? !Number.isInteger(m.toIndex) || m.toIndex < 0 || m.toIndex >= o.length ? u(`track index must be between 0 and ${Math.max(0, o.length - 1)}`) : g : u(`no track with id ${m.trackId}`);
513
+ }
514
+ case "replaceSegmentAsset": {
515
+ const { asset: o, segmentId: l, strategy: d } = m.input, y = s(l);
516
+ if (!y)
517
+ return u(`no segment with id ${l}`);
518
+ const h = ee(y) ? "video" : te(y) ? "audio" : y.segmentType === "sticker" || y.segmentType === "frames" && y.type === "image" ? "image" : void 0;
519
+ if (!h)
520
+ return u("segment does not use a replaceable asset");
521
+ if (h !== o.kind)
522
+ return u(`cannot replace ${h} with ${o.kind}`);
523
+ if (o.id !== void 0 && !o.id)
524
+ return u("asset id must not be empty");
525
+ try {
526
+ if (!new URL(o.url).protocol)
527
+ return u("asset url must be an absolute URL");
528
+ } catch {
529
+ return u("asset url must be an absolute URL");
530
+ }
531
+ if (d !== "preserve" && d !== "fit")
532
+ return u(`unsupported replacement strategy ${String(d)}`);
533
+ if (h === "image")
534
+ return d === "fit" ? u("fit strategy requires a video or audio asset with a duration") : g;
535
+ if (typeof o.durationMs != "number" || !Number.isFinite(o.durationMs) || o.durationMs <= 0)
536
+ return u("asset durationMs must be a positive number for video and audio");
537
+ if (d === "preserve") {
538
+ const I = y, b = (I.fromTime ?? 0) + (I.endTime - I.startTime) * (I.playRate ?? 1);
539
+ if (b > o.durationMs)
540
+ return u(`current source window ends at ${b}ms, beyond the ${o.durationMs}ms asset`);
541
+ }
542
+ return g;
543
+ }
544
+ case "upsertKeyframe":
545
+ case "moveKeyframe":
546
+ case "removeKeyframe":
547
+ case "setKeyframeEasing":
548
+ return Y(m, s);
549
+ }
550
+ }
551
+ return {
552
+ getSegmentsAt: p,
553
+ getSegmentAt: v,
554
+ getTrackGaps: f,
555
+ getAdjacentSegments: k,
556
+ getOverlaps: w,
557
+ sampleProperty: S,
558
+ getSelection: M,
559
+ canRun: C
560
+ };
561
+ }
562
+ function ve(t) {
49
563
  let e = 0;
50
- for (const o of s)
51
- for (const c of o.children)
52
- c.endTime > e && (e = c.endTime);
564
+ for (const n of t)
565
+ for (const i of n.children)
566
+ i.endTime > e && (e = i.endTime);
53
567
  return e;
54
568
  }
55
- function x(s) {
56
- const e = v(s.protocol, {
57
- idFactory: s.idFactory
58
- }), o = p(() => e.selectedSegment.value?.id), c = p(() => k(e.protocol.value.tracks)), r = {
569
+ function ke(t) {
570
+ const e = Q(t.protocol, {
571
+ idFactory: t.idFactory
572
+ }), n = O(() => e.selectedSegment.value?.id), i = O(() => ve(e.protocol.value.tracks)), a = {
59
573
  protocol: e.protocol,
60
574
  videoBasicInfo: e.videoBasicInfo,
61
575
  currentTime: e.curTime,
62
576
  selectedSegment: e.selectedSegment,
63
- selectedSegmentId: o,
577
+ selectedSegmentId: n,
64
578
  trackMap: e.trackMap,
65
579
  segmentMap: e.segmentMap,
66
- duration: c,
580
+ duration: i,
67
581
  undoCount: e.undoCount,
68
- redoCount: e.redoCount
69
- }, d = {
70
- setCurrentTime: (n) => {
71
- e.curTime.value = n;
582
+ redoCount: e.redoCount,
583
+ operationLog: e.operationLog,
584
+ revision: e.revision,
585
+ isTransactionActive: e.isTransactionActive,
586
+ transactionDepth: e.transactionDepth
587
+ }, r = ne({
588
+ transaction: e.transaction,
589
+ moveSegment: e.moveSegment,
590
+ removeSegment: e.removeSegment,
591
+ duplicateSegment: e.duplicateSegment,
592
+ updateSegment: e.updateSegment,
593
+ getSegment: e.getSegment
594
+ }), s = ie({
595
+ getSegment: e.getSegment,
596
+ updateSegment: e.updateSegment,
597
+ transaction: e.transaction
598
+ }), c = {
599
+ setCurrentTime: (g) => {
600
+ e.curTime.value = g;
72
601
  },
73
602
  setSelectedSegment: e.setSelectedSegment,
74
603
  addSegment: e.addSegment,
@@ -77,45 +606,89 @@ function x(s) {
77
606
  updateSegment: e.updateSegment,
78
607
  moveSegment: e.moveSegment,
79
608
  resizeSegment: e.resizeSegment,
609
+ replaceSegmentAsset: e.replaceSegmentAsset,
80
610
  splitSegment: e.splitSegment,
81
611
  addTransition: e.addTransition,
82
612
  removeTransition: e.removeTransition,
83
613
  updateTransition: e.updateTransition,
614
+ addTrack: e.addTrack,
615
+ removeTrack: e.removeTrack,
616
+ moveTrack: e.moveTrack,
84
617
  updateTrack: e.updateTrack,
85
618
  setCanvasSize: e.setCanvasSize,
619
+ setFps: e.setFps,
620
+ upsertKeyframe: s.upsertKeyframe,
621
+ moveKeyframe: s.moveKeyframe,
622
+ removeKeyframe: s.removeKeyframe,
623
+ setKeyframeEasing: s.setKeyframeEasing,
86
624
  replaceTrackId: e.replaceTrackId,
87
625
  replaceSegmentId: e.replaceSegmentId,
626
+ moveSegments: r.moveSegments,
627
+ removeSegments: r.removeSegments,
628
+ updateSegments: r.updateSegments,
629
+ duplicateSegments: r.duplicateSegments,
630
+ transaction: e.transaction,
631
+ beginTransaction: e.beginTransaction,
88
632
  undo: e.undo,
89
633
  redo: e.redo,
90
634
  exportProtocol: e.exportProtocol
91
- }, g = {
635
+ }, v = {
636
+ ...ye({
637
+ protocol: () => e.exportProtocol(),
638
+ selectedSegmentId: () => n.value,
639
+ undoCount: () => e.undoCount.value,
640
+ redoCount: () => e.redoCount.value
641
+ }),
92
642
  getSegment: e.getSegment,
93
- getTrackById: (n) => r.protocol.value.tracks.find((a) => a.trackId === n),
94
- getTrackBySegmentId: (n) => r.protocol.value.tracks.find((a) => a.children.some((i) => i.id === n)),
95
- getTracks: (n) => n ? r.protocol.value.tracks.filter((a) => a.trackType === n) : r.protocol.value.tracks
96
- }, l = {
97
- segments: T()
98
- }, m = s.services ?? {}, t = y({
99
- state: r,
100
- commands: d,
101
- selectors: g,
102
- registry: l,
103
- services: m
643
+ getTrackById: (g) => a.protocol.value.tracks.find((u) => u.trackId === g),
644
+ getTrackBySegmentId: (g) => a.protocol.value.tracks.find((u) => u.children.some((C) => C.id === g)),
645
+ getTracks: (g) => g ? a.protocol.value.tracks.filter((u) => u.trackType === g) : a.protocol.value.tracks,
646
+ getOperationLog: () => structuredClone(a.operationLog.value),
647
+ getAssetReferences: (g) => W(e.exportProtocol(), g)
648
+ }, f = {
649
+ segments: de()
650
+ }, k = t.services ?? {}, S = ce({
651
+ state: a,
652
+ commands: c,
653
+ selectors: v,
654
+ registry: f,
655
+ services: k
656
+ }), M = ue({
657
+ getProtocol: () => e.exportProtocol(),
658
+ getRevision: () => e.revision.value,
659
+ isTransactionActive: () => e.isTransactionActive.value,
660
+ createSandbox: (g) => ke({
661
+ protocol: g,
662
+ idFactory: t.idFactory
663
+ }),
664
+ applySnapshot: (g, u) => e.applyProtocolSnapshot(g, {
665
+ label: "accept-proposal",
666
+ data: { proposalId: u.id, baseRevision: u.baseRevision },
667
+ operations: u.operations
668
+ }).status,
669
+ createId: t.idFactory?.proposal ?? (() => globalThis.crypto.randomUUID())
104
670
  });
105
671
  return {
106
- state: r,
107
- commands: d,
108
- selectors: g,
109
- plugins: t,
110
- registry: l,
111
- services: m,
672
+ state: a,
673
+ commands: c,
674
+ selectors: v,
675
+ proposals: M,
676
+ plugins: S,
677
+ registry: f,
678
+ services: k,
112
679
  destroy: async () => {
113
- await t.destroy();
680
+ await S.destroy();
114
681
  }
115
682
  };
116
683
  }
117
684
  export {
118
- x as createEditorCore,
119
- y as createPluginManager,
120
- T as createSegmentRegistry
685
+ Y as checkKeyframeCommand,
686
+ ne as createBatchCommands,
687
+ ke as createEditorCore,
688
+ ie as createKeyframeCommands,
689
+ ce as createPluginManager,
690
+ ue as createProposalManager,
691
+ de as createSegmentRegistry,
692
+ ye as createStructuralSelectors,
693
+ me as summarizeProposal
121
694
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@video-editor/editor-core",
3
3
  "type": "module",
4
- "version": "1.0.0-beta.1",
4
+ "version": "1.0.0-beta.2",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
@@ -19,14 +19,15 @@
19
19
  "@vue/reactivity": "^3.5.26"
20
20
  },
21
21
  "dependencies": {
22
- "@video-editor/protocol": "1.0.0-beta.1",
23
- "@video-editor/shared": "1.0.0-beta.1"
22
+ "@video-editor/protocol": "1.0.0-beta.2",
23
+ "@video-editor/shared": "1.0.0-beta.2"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@vue/reactivity": "^3.5.26"
27
27
  },
28
28
  "scripts": {
29
29
  "build": "vite build",
30
- "preview": "vite preview"
30
+ "preview": "vite preview",
31
+ "test": "vitest run --config ./vitest.config.ts"
31
32
  }
32
33
  }