@vibeframe/cli 0.27.0 → 0.29.0

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.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/dist/agent/adapters/index.d.ts +1 -0
  3. package/dist/agent/adapters/index.d.ts.map +1 -1
  4. package/dist/agent/adapters/index.js +5 -0
  5. package/dist/agent/adapters/index.js.map +1 -1
  6. package/dist/agent/adapters/openrouter.d.ts +16 -0
  7. package/dist/agent/adapters/openrouter.d.ts.map +1 -0
  8. package/dist/agent/adapters/openrouter.js +100 -0
  9. package/dist/agent/adapters/openrouter.js.map +1 -0
  10. package/dist/agent/types.d.ts +1 -1
  11. package/dist/agent/types.d.ts.map +1 -1
  12. package/dist/commands/agent.d.ts.map +1 -1
  13. package/dist/commands/agent.js +3 -1
  14. package/dist/commands/agent.js.map +1 -1
  15. package/dist/commands/setup.js +5 -2
  16. package/dist/commands/setup.js.map +1 -1
  17. package/dist/config/schema.d.ts +2 -1
  18. package/dist/config/schema.d.ts.map +1 -1
  19. package/dist/config/schema.js +2 -0
  20. package/dist/config/schema.js.map +1 -1
  21. package/dist/index.js +0 -0
  22. package/package.json +16 -12
  23. package/.turbo/turbo-build.log +0 -4
  24. package/.turbo/turbo-lint.log +0 -21
  25. package/.turbo/turbo-test.log +0 -689
  26. package/src/agent/adapters/claude.ts +0 -143
  27. package/src/agent/adapters/gemini.ts +0 -159
  28. package/src/agent/adapters/index.ts +0 -61
  29. package/src/agent/adapters/ollama.ts +0 -231
  30. package/src/agent/adapters/openai.ts +0 -116
  31. package/src/agent/adapters/xai.ts +0 -119
  32. package/src/agent/index.ts +0 -251
  33. package/src/agent/memory/index.ts +0 -151
  34. package/src/agent/prompts/system.ts +0 -106
  35. package/src/agent/tools/ai-editing.ts +0 -845
  36. package/src/agent/tools/ai-generation.ts +0 -1073
  37. package/src/agent/tools/ai-pipeline.ts +0 -1055
  38. package/src/agent/tools/ai.ts +0 -21
  39. package/src/agent/tools/batch.ts +0 -429
  40. package/src/agent/tools/e2e.test.ts +0 -545
  41. package/src/agent/tools/export.ts +0 -184
  42. package/src/agent/tools/filesystem.ts +0 -237
  43. package/src/agent/tools/index.ts +0 -150
  44. package/src/agent/tools/integration.test.ts +0 -775
  45. package/src/agent/tools/media.ts +0 -697
  46. package/src/agent/tools/project.ts +0 -313
  47. package/src/agent/tools/timeline.ts +0 -951
  48. package/src/agent/types.ts +0 -68
  49. package/src/commands/agent.ts +0 -340
  50. package/src/commands/ai-analyze.ts +0 -429
  51. package/src/commands/ai-animated-caption.ts +0 -390
  52. package/src/commands/ai-audio.ts +0 -941
  53. package/src/commands/ai-broll.ts +0 -490
  54. package/src/commands/ai-edit-cli.ts +0 -658
  55. package/src/commands/ai-edit.ts +0 -1542
  56. package/src/commands/ai-fill-gaps.ts +0 -566
  57. package/src/commands/ai-helpers.ts +0 -65
  58. package/src/commands/ai-highlights.ts +0 -1303
  59. package/src/commands/ai-image.ts +0 -761
  60. package/src/commands/ai-motion.ts +0 -347
  61. package/src/commands/ai-narrate.ts +0 -451
  62. package/src/commands/ai-review.ts +0 -309
  63. package/src/commands/ai-script-pipeline-cli.ts +0 -1710
  64. package/src/commands/ai-script-pipeline.ts +0 -1365
  65. package/src/commands/ai-suggest-edit.ts +0 -264
  66. package/src/commands/ai-video-fx.ts +0 -445
  67. package/src/commands/ai-video.ts +0 -915
  68. package/src/commands/ai-viral.ts +0 -595
  69. package/src/commands/ai-visual-fx.ts +0 -601
  70. package/src/commands/ai.test.ts +0 -627
  71. package/src/commands/ai.ts +0 -307
  72. package/src/commands/analyze.ts +0 -282
  73. package/src/commands/audio.ts +0 -644
  74. package/src/commands/batch.test.ts +0 -279
  75. package/src/commands/batch.ts +0 -440
  76. package/src/commands/detect.ts +0 -329
  77. package/src/commands/doctor.ts +0 -237
  78. package/src/commands/edit-cmd.ts +0 -1014
  79. package/src/commands/export.ts +0 -918
  80. package/src/commands/generate.ts +0 -2146
  81. package/src/commands/media.ts +0 -177
  82. package/src/commands/output.ts +0 -142
  83. package/src/commands/pipeline.ts +0 -398
  84. package/src/commands/project.test.ts +0 -127
  85. package/src/commands/project.ts +0 -149
  86. package/src/commands/sanitize.ts +0 -60
  87. package/src/commands/schema.ts +0 -130
  88. package/src/commands/setup.ts +0 -509
  89. package/src/commands/timeline.test.ts +0 -499
  90. package/src/commands/timeline.ts +0 -529
  91. package/src/commands/validate.ts +0 -77
  92. package/src/config/config.test.ts +0 -197
  93. package/src/config/index.ts +0 -125
  94. package/src/config/schema.ts +0 -82
  95. package/src/engine/index.ts +0 -2
  96. package/src/engine/project.test.ts +0 -702
  97. package/src/engine/project.ts +0 -439
  98. package/src/index.ts +0 -146
  99. package/src/utils/api-key.test.ts +0 -41
  100. package/src/utils/api-key.ts +0 -247
  101. package/src/utils/audio.ts +0 -83
  102. package/src/utils/exec-safe.ts +0 -75
  103. package/src/utils/first-run.ts +0 -52
  104. package/src/utils/provider-resolver.ts +0 -56
  105. package/src/utils/remotion.ts +0 -951
  106. package/src/utils/subtitle.test.ts +0 -227
  107. package/src/utils/subtitle.ts +0 -169
  108. package/src/utils/tty.ts +0 -196
  109. package/tsconfig.json +0 -20
@@ -1,702 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "vitest";
2
- import { Project, generateId } from "./project";
3
-
4
- describe("generateId", () => {
5
- it("generates unique IDs", () => {
6
- const id1 = generateId();
7
- const id2 = generateId();
8
- expect(id1).not.toBe(id2);
9
- });
10
-
11
- it("generates IDs in expected format", () => {
12
- const id = generateId();
13
- expect(id).toMatch(/^\d+-[a-z0-9]+$/);
14
- });
15
- });
16
-
17
- describe("Project", () => {
18
- let project: Project;
19
-
20
- beforeEach(() => {
21
- project = new Project("Test Project");
22
- });
23
-
24
- describe("initialization", () => {
25
- it("creates project with given name", () => {
26
- expect(project.getMeta().name).toBe("Test Project");
27
- });
28
-
29
- it("creates project with default aspect ratio 16:9", () => {
30
- expect(project.getMeta().aspectRatio).toBe("16:9");
31
- });
32
-
33
- it("creates project with default frame rate 30", () => {
34
- expect(project.getMeta().frameRate).toBe(30);
35
- });
36
-
37
- it("creates project with two default tracks", () => {
38
- const tracks = project.getTracks();
39
- expect(tracks).toHaveLength(2);
40
- expect(tracks[0].type).toBe("video");
41
- expect(tracks[1].type).toBe("audio");
42
- });
43
-
44
- it("creates project with empty clips and sources", () => {
45
- expect(project.getClips()).toHaveLength(0);
46
- expect(project.getSources()).toHaveLength(0);
47
- });
48
-
49
- it("creates project with zero duration", () => {
50
- expect(project.getDuration()).toBe(0);
51
- });
52
- });
53
-
54
- describe("project settings", () => {
55
- it("sets project name", () => {
56
- project.setName("New Name");
57
- expect(project.getMeta().name).toBe("New Name");
58
- });
59
-
60
- it("sets aspect ratio", () => {
61
- project.setAspectRatio("9:16");
62
- expect(project.getMeta().aspectRatio).toBe("9:16");
63
- });
64
-
65
- it("sets frame rate", () => {
66
- project.setFrameRate(60);
67
- expect(project.getMeta().frameRate).toBe(60);
68
- });
69
- });
70
-
71
- describe("media sources", () => {
72
- it("adds a source", () => {
73
- const source = project.addSource({
74
- name: "test.mp4",
75
- type: "video",
76
- url: "/path/to/test.mp4",
77
- duration: 10,
78
- });
79
-
80
- expect(source.id).toBeDefined();
81
- expect(source.name).toBe("test.mp4");
82
- expect(project.getSources()).toHaveLength(1);
83
- });
84
-
85
- it("gets source by id", () => {
86
- const source = project.addSource({
87
- name: "test.mp4",
88
- type: "video",
89
- url: "/path/to/test.mp4",
90
- duration: 10,
91
- });
92
-
93
- const found = project.getSource(source.id);
94
- expect(found).toBeDefined();
95
- expect(found?.name).toBe("test.mp4");
96
- });
97
-
98
- it("removes a source", () => {
99
- const source = project.addSource({
100
- name: "test.mp4",
101
- type: "video",
102
- url: "/path/to/test.mp4",
103
- duration: 10,
104
- });
105
-
106
- const removed = project.removeSource(source.id);
107
- expect(removed).toBe(true);
108
- expect(project.getSources()).toHaveLength(0);
109
- });
110
-
111
- it("removes clips when source is removed", () => {
112
- const source = project.addSource({
113
- name: "test.mp4",
114
- type: "video",
115
- url: "/path/to/test.mp4",
116
- duration: 10,
117
- });
118
-
119
- project.addClip({
120
- sourceId: source.id,
121
- trackId: "video-track-1",
122
- startTime: 0,
123
- duration: 5,
124
- sourceStartOffset: 0,
125
- sourceEndOffset: 5,
126
- });
127
-
128
- expect(project.getClips()).toHaveLength(1);
129
- project.removeSource(source.id);
130
- expect(project.getClips()).toHaveLength(0);
131
- });
132
-
133
- it("returns false when removing non-existent source", () => {
134
- const removed = project.removeSource("non-existent");
135
- expect(removed).toBe(false);
136
- });
137
- });
138
-
139
- describe("tracks", () => {
140
- it("adds a track", () => {
141
- const track = project.addTrack({
142
- name: "Video 2",
143
- type: "video",
144
- order: 2,
145
- isMuted: false,
146
- isLocked: false,
147
- isVisible: true,
148
- });
149
-
150
- expect(track.id).toBeDefined();
151
- expect(track.name).toBe("Video 2");
152
- expect(project.getTracks()).toHaveLength(3);
153
- });
154
-
155
- it("gets track by id", () => {
156
- const track = project.getTrack("video-track-1");
157
- expect(track).toBeDefined();
158
- expect(track?.name).toBe("Video 1");
159
- });
160
-
161
- it("gets tracks by type", () => {
162
- const videoTracks = project.getTracksByType("video");
163
- const audioTracks = project.getTracksByType("audio");
164
-
165
- expect(videoTracks).toHaveLength(1);
166
- expect(audioTracks).toHaveLength(1);
167
- });
168
-
169
- it("updates a track", () => {
170
- const updated = project.updateTrack("video-track-1", { name: "Main Video" });
171
- expect(updated).toBe(true);
172
- expect(project.getTrack("video-track-1")?.name).toBe("Main Video");
173
- });
174
-
175
- it("removes a track", () => {
176
- const removed = project.removeTrack("video-track-1");
177
- expect(removed).toBe(true);
178
- expect(project.getTracks()).toHaveLength(1);
179
- });
180
-
181
- it("removes clips when track is removed", () => {
182
- const source = project.addSource({
183
- name: "test.mp4",
184
- type: "video",
185
- url: "/path/to/test.mp4",
186
- duration: 10,
187
- });
188
-
189
- project.addClip({
190
- sourceId: source.id,
191
- trackId: "video-track-1",
192
- startTime: 0,
193
- duration: 5,
194
- sourceStartOffset: 0,
195
- sourceEndOffset: 5,
196
- });
197
-
198
- expect(project.getClips()).toHaveLength(1);
199
- project.removeTrack("video-track-1");
200
- expect(project.getClips()).toHaveLength(0);
201
- });
202
- });
203
-
204
- describe("clips", () => {
205
- let sourceId: string;
206
-
207
- beforeEach(() => {
208
- const source = project.addSource({
209
- name: "test.mp4",
210
- type: "video",
211
- url: "/path/to/test.mp4",
212
- duration: 10,
213
- });
214
- sourceId = source.id;
215
- });
216
-
217
- it("adds a clip", () => {
218
- const clip = project.addClip({
219
- sourceId,
220
- trackId: "video-track-1",
221
- startTime: 0,
222
- duration: 5,
223
- sourceStartOffset: 0,
224
- sourceEndOffset: 5,
225
- });
226
-
227
- expect(clip.id).toBeDefined();
228
- expect(clip.effects).toEqual([]);
229
- expect(project.getClips()).toHaveLength(1);
230
- });
231
-
232
- it("gets clip by id", () => {
233
- const clip = project.addClip({
234
- sourceId,
235
- trackId: "video-track-1",
236
- startTime: 0,
237
- duration: 5,
238
- sourceStartOffset: 0,
239
- sourceEndOffset: 5,
240
- });
241
-
242
- const found = project.getClip(clip.id);
243
- expect(found).toBeDefined();
244
- expect(found?.sourceId).toBe(sourceId);
245
- });
246
-
247
- it("gets clips by track", () => {
248
- project.addClip({
249
- sourceId,
250
- trackId: "video-track-1",
251
- startTime: 0,
252
- duration: 5,
253
- sourceStartOffset: 0,
254
- sourceEndOffset: 5,
255
- });
256
-
257
- const videoClips = project.getClipsByTrack("video-track-1");
258
- const audioClips = project.getClipsByTrack("audio-track-1");
259
-
260
- expect(videoClips).toHaveLength(1);
261
- expect(audioClips).toHaveLength(0);
262
- });
263
-
264
- it("updates a clip", () => {
265
- const clip = project.addClip({
266
- sourceId,
267
- trackId: "video-track-1",
268
- startTime: 0,
269
- duration: 5,
270
- sourceStartOffset: 0,
271
- sourceEndOffset: 5,
272
- });
273
-
274
- const updated = project.updateClip(clip.id, { duration: 3 });
275
- expect(updated).toBe(true);
276
- expect(project.getClip(clip.id)?.duration).toBe(3);
277
- });
278
-
279
- it("moves a clip", () => {
280
- const clip = project.addClip({
281
- sourceId,
282
- trackId: "video-track-1",
283
- startTime: 0,
284
- duration: 5,
285
- sourceStartOffset: 0,
286
- sourceEndOffset: 5,
287
- });
288
-
289
- project.moveClip(clip.id, "video-track-1", 10);
290
- expect(project.getClip(clip.id)?.startTime).toBe(10);
291
- });
292
-
293
- it("prevents negative start time when moving", () => {
294
- const clip = project.addClip({
295
- sourceId,
296
- trackId: "video-track-1",
297
- startTime: 5,
298
- duration: 5,
299
- sourceStartOffset: 0,
300
- sourceEndOffset: 5,
301
- });
302
-
303
- project.moveClip(clip.id, "video-track-1", -10);
304
- expect(project.getClip(clip.id)?.startTime).toBe(0);
305
- });
306
-
307
- it("trims clip start", () => {
308
- const clip = project.addClip({
309
- sourceId,
310
- trackId: "video-track-1",
311
- startTime: 0,
312
- duration: 10,
313
- sourceStartOffset: 0,
314
- sourceEndOffset: 10,
315
- });
316
-
317
- project.trimClipStart(clip.id, 2);
318
- const updated = project.getClip(clip.id);
319
- expect(updated?.startTime).toBe(2);
320
- expect(updated?.sourceStartOffset).toBe(2);
321
- expect(updated?.duration).toBe(8);
322
- });
323
-
324
- it("trims clip end", () => {
325
- const clip = project.addClip({
326
- sourceId,
327
- trackId: "video-track-1",
328
- startTime: 0,
329
- duration: 10,
330
- sourceStartOffset: 0,
331
- sourceEndOffset: 10,
332
- });
333
-
334
- project.trimClipEnd(clip.id, 5);
335
- const updated = project.getClip(clip.id);
336
- expect(updated?.duration).toBe(5);
337
- expect(updated?.sourceEndOffset).toBe(5);
338
- });
339
-
340
- it("enforces minimum duration when trimming", () => {
341
- const clip = project.addClip({
342
- sourceId,
343
- trackId: "video-track-1",
344
- startTime: 0,
345
- duration: 10,
346
- sourceStartOffset: 0,
347
- sourceEndOffset: 10,
348
- });
349
-
350
- project.trimClipEnd(clip.id, 0);
351
- expect(project.getClip(clip.id)?.duration).toBe(0.1);
352
- });
353
-
354
- it("removes a clip", () => {
355
- const clip = project.addClip({
356
- sourceId,
357
- trackId: "video-track-1",
358
- startTime: 0,
359
- duration: 5,
360
- sourceStartOffset: 0,
361
- sourceEndOffset: 5,
362
- });
363
-
364
- const removed = project.removeClip(clip.id);
365
- expect(removed).toBe(true);
366
- expect(project.getClips()).toHaveLength(0);
367
- });
368
-
369
- it("calculates project duration from clips", () => {
370
- project.addClip({
371
- sourceId,
372
- trackId: "video-track-1",
373
- startTime: 0,
374
- duration: 5,
375
- sourceStartOffset: 0,
376
- sourceEndOffset: 5,
377
- });
378
-
379
- project.addClip({
380
- sourceId,
381
- trackId: "video-track-1",
382
- startTime: 10,
383
- duration: 5,
384
- sourceStartOffset: 0,
385
- sourceEndOffset: 5,
386
- });
387
-
388
- expect(project.getDuration()).toBe(15);
389
- });
390
-
391
- describe("splitClip", () => {
392
- it("splits a clip at given time", () => {
393
- const clip = project.addClip({
394
- sourceId,
395
- trackId: "video-track-1",
396
- startTime: 0,
397
- duration: 10,
398
- sourceStartOffset: 0,
399
- sourceEndOffset: 10,
400
- });
401
-
402
- const result = project.splitClip(clip.id, 4);
403
- expect(result).not.toBeNull();
404
-
405
- const [firstClip, secondClip] = result!;
406
- expect(firstClip.duration).toBe(4);
407
- expect(firstClip.sourceEndOffset).toBe(4);
408
- expect(secondClip.startTime).toBe(4);
409
- expect(secondClip.duration).toBe(6);
410
- expect(secondClip.sourceStartOffset).toBe(4);
411
- expect(project.getClips()).toHaveLength(2);
412
- });
413
-
414
- it("returns null for non-existent clip", () => {
415
- const result = project.splitClip("non-existent", 5);
416
- expect(result).toBeNull();
417
- });
418
-
419
- it("returns null when split time is at or before 0", () => {
420
- const clip = project.addClip({
421
- sourceId,
422
- trackId: "video-track-1",
423
- startTime: 0,
424
- duration: 10,
425
- sourceStartOffset: 0,
426
- sourceEndOffset: 10,
427
- });
428
-
429
- expect(project.splitClip(clip.id, 0)).toBeNull();
430
- expect(project.splitClip(clip.id, -1)).toBeNull();
431
- });
432
-
433
- it("returns null when split time is at or after duration", () => {
434
- const clip = project.addClip({
435
- sourceId,
436
- trackId: "video-track-1",
437
- startTime: 0,
438
- duration: 10,
439
- sourceStartOffset: 0,
440
- sourceEndOffset: 10,
441
- });
442
-
443
- expect(project.splitClip(clip.id, 10)).toBeNull();
444
- expect(project.splitClip(clip.id, 15)).toBeNull();
445
- });
446
- });
447
-
448
- describe("duplicateClip", () => {
449
- it("duplicates a clip after original", () => {
450
- const clip = project.addClip({
451
- sourceId,
452
- trackId: "video-track-1",
453
- startTime: 0,
454
- duration: 5,
455
- sourceStartOffset: 0,
456
- sourceEndOffset: 5,
457
- });
458
-
459
- const duplicate = project.duplicateClip(clip.id);
460
- expect(duplicate).not.toBeNull();
461
- expect(duplicate?.sourceId).toBe(sourceId);
462
- expect(duplicate?.trackId).toBe("video-track-1");
463
- expect(duplicate?.startTime).toBe(5); // After original
464
- expect(duplicate?.duration).toBe(5);
465
- expect(project.getClips()).toHaveLength(2);
466
- });
467
-
468
- it("duplicates a clip at specified time", () => {
469
- const clip = project.addClip({
470
- sourceId,
471
- trackId: "video-track-1",
472
- startTime: 0,
473
- duration: 5,
474
- sourceStartOffset: 0,
475
- sourceEndOffset: 5,
476
- });
477
-
478
- const duplicate = project.duplicateClip(clip.id, 20);
479
- expect(duplicate).not.toBeNull();
480
- expect(duplicate?.startTime).toBe(20);
481
- });
482
-
483
- it("duplicates clip effects with new IDs", () => {
484
- const clip = project.addClip({
485
- sourceId,
486
- trackId: "video-track-1",
487
- startTime: 0,
488
- duration: 5,
489
- sourceStartOffset: 0,
490
- sourceEndOffset: 5,
491
- });
492
-
493
- project.addEffect(clip.id, {
494
- type: "fadeIn",
495
- startTime: 0,
496
- duration: 1,
497
- params: { intensity: 1 },
498
- });
499
-
500
- const duplicate = project.duplicateClip(clip.id);
501
- expect(duplicate?.effects).toHaveLength(1);
502
- expect(duplicate?.effects[0].type).toBe("fadeIn");
503
- expect(duplicate?.effects[0].id).not.toBe(clip.effects?.[0]?.id);
504
- });
505
-
506
- it("returns null for non-existent clip", () => {
507
- const result = project.duplicateClip("non-existent");
508
- expect(result).toBeNull();
509
- });
510
- });
511
- });
512
-
513
- describe("effects", () => {
514
- let clipId: string;
515
-
516
- beforeEach(() => {
517
- const source = project.addSource({
518
- name: "test.mp4",
519
- type: "video",
520
- url: "/path/to/test.mp4",
521
- duration: 10,
522
- });
523
-
524
- const clip = project.addClip({
525
- sourceId: source.id,
526
- trackId: "video-track-1",
527
- startTime: 0,
528
- duration: 5,
529
- sourceStartOffset: 0,
530
- sourceEndOffset: 5,
531
- });
532
-
533
- clipId = clip.id;
534
- });
535
-
536
- it("adds an effect to a clip", () => {
537
- const effect = project.addEffect(clipId, {
538
- type: "fadeIn",
539
- startTime: 0,
540
- duration: 1,
541
- params: { intensity: 1 },
542
- });
543
-
544
- expect(effect).not.toBeNull();
545
- expect(effect?.type).toBe("fadeIn");
546
- expect(project.getClip(clipId)?.effects).toHaveLength(1);
547
- });
548
-
549
- it("returns null when adding effect to non-existent clip", () => {
550
- const effect = project.addEffect("non-existent", {
551
- type: "fadeIn",
552
- startTime: 0,
553
- duration: 1,
554
- params: {},
555
- });
556
-
557
- expect(effect).toBeNull();
558
- });
559
-
560
- it("removes an effect from a clip", () => {
561
- const effect = project.addEffect(clipId, {
562
- type: "fadeIn",
563
- startTime: 0,
564
- duration: 1,
565
- params: {},
566
- });
567
-
568
- const removed = project.removeEffect(clipId, effect!.id);
569
- expect(removed).toBe(true);
570
- expect(project.getClip(clipId)?.effects).toHaveLength(0);
571
- });
572
-
573
- it("updates an effect", () => {
574
- const effect = project.addEffect(clipId, {
575
- type: "fadeIn",
576
- startTime: 0,
577
- duration: 1,
578
- params: { intensity: 0.5 },
579
- });
580
-
581
- const updated = project.updateEffect(clipId, effect!.id, {
582
- params: { intensity: 1 },
583
- });
584
-
585
- expect(updated).toBe(true);
586
- expect(project.getClip(clipId)?.effects[0].params.intensity).toBe(1);
587
- });
588
- });
589
-
590
- describe("transitions", () => {
591
- it("adds a transition", () => {
592
- const transition = project.addTransition({
593
- type: "dissolve",
594
- duration: 1,
595
- fromClipId: "clip-1",
596
- toClipId: "clip-2",
597
- });
598
-
599
- expect(transition.id).toBeDefined();
600
- expect(project.getTransitions()).toHaveLength(1);
601
- });
602
-
603
- it("removes a transition", () => {
604
- const transition = project.addTransition({
605
- type: "dissolve",
606
- duration: 1,
607
- fromClipId: "clip-1",
608
- toClipId: "clip-2",
609
- });
610
-
611
- const removed = project.removeTransition(transition.id);
612
- expect(removed).toBe(true);
613
- expect(project.getTransitions()).toHaveLength(0);
614
- });
615
- });
616
-
617
- describe("serialization", () => {
618
- it("serializes to JSON", () => {
619
- const source = project.addSource({
620
- name: "test.mp4",
621
- type: "video",
622
- url: "/path/to/test.mp4",
623
- duration: 10,
624
- });
625
-
626
- project.addClip({
627
- sourceId: source.id,
628
- trackId: "video-track-1",
629
- startTime: 0,
630
- duration: 5,
631
- sourceStartOffset: 0,
632
- sourceEndOffset: 5,
633
- });
634
-
635
- const json = project.toJSON();
636
- expect(json.version).toBe("1.0.0");
637
- expect(json.state.project.name).toBe("Test Project");
638
- expect(json.state.sources).toHaveLength(1);
639
- expect(json.state.clips).toHaveLength(1);
640
- });
641
-
642
- it("deserializes from JSON", () => {
643
- const source = project.addSource({
644
- name: "test.mp4",
645
- type: "video",
646
- url: "/path/to/test.mp4",
647
- duration: 10,
648
- });
649
-
650
- project.addClip({
651
- sourceId: source.id,
652
- trackId: "video-track-1",
653
- startTime: 0,
654
- duration: 5,
655
- sourceStartOffset: 0,
656
- sourceEndOffset: 5,
657
- });
658
-
659
- const json = project.toJSON();
660
- const restored = Project.fromJSON(json);
661
-
662
- expect(restored.getMeta().name).toBe("Test Project");
663
- expect(restored.getSources()).toHaveLength(1);
664
- expect(restored.getClips()).toHaveLength(1);
665
- });
666
-
667
- it("returns immutable state copy", () => {
668
- const state1 = project.getState();
669
- const state2 = project.getState();
670
-
671
- expect(state1).not.toBe(state2);
672
- expect(state1).toEqual(state2);
673
- });
674
- });
675
-
676
- describe("summary", () => {
677
- it("returns project summary", () => {
678
- const source = project.addSource({
679
- name: "test.mp4",
680
- type: "video",
681
- url: "/path/to/test.mp4",
682
- duration: 10,
683
- });
684
-
685
- project.addClip({
686
- sourceId: source.id,
687
- trackId: "video-track-1",
688
- startTime: 0,
689
- duration: 5,
690
- sourceStartOffset: 0,
691
- sourceEndOffset: 5,
692
- });
693
-
694
- const summary = project.getSummary();
695
- expect(summary.name).toBe("Test Project");
696
- expect(summary.duration).toBe(5);
697
- expect(summary.trackCount).toBe(2);
698
- expect(summary.clipCount).toBe(1);
699
- expect(summary.sourceCount).toBe(1);
700
- });
701
- });
702
- });