@vibeframe/cli 0.27.0 → 0.30.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 (118) 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/ai-edit-cli.d.ts.map +1 -1
  16. package/dist/commands/ai-edit-cli.js +18 -0
  17. package/dist/commands/ai-edit-cli.js.map +1 -1
  18. package/dist/commands/generate.js +14 -0
  19. package/dist/commands/generate.js.map +1 -1
  20. package/dist/commands/schema.d.ts +1 -0
  21. package/dist/commands/schema.d.ts.map +1 -1
  22. package/dist/commands/schema.js +122 -21
  23. package/dist/commands/schema.js.map +1 -1
  24. package/dist/commands/setup.js +5 -2
  25. package/dist/commands/setup.js.map +1 -1
  26. package/dist/config/schema.d.ts +2 -1
  27. package/dist/config/schema.d.ts.map +1 -1
  28. package/dist/config/schema.js +2 -0
  29. package/dist/config/schema.js.map +1 -1
  30. package/dist/index.js +0 -0
  31. package/package.json +16 -12
  32. package/.turbo/turbo-build.log +0 -4
  33. package/.turbo/turbo-lint.log +0 -21
  34. package/.turbo/turbo-test.log +0 -689
  35. package/src/agent/adapters/claude.ts +0 -143
  36. package/src/agent/adapters/gemini.ts +0 -159
  37. package/src/agent/adapters/index.ts +0 -61
  38. package/src/agent/adapters/ollama.ts +0 -231
  39. package/src/agent/adapters/openai.ts +0 -116
  40. package/src/agent/adapters/xai.ts +0 -119
  41. package/src/agent/index.ts +0 -251
  42. package/src/agent/memory/index.ts +0 -151
  43. package/src/agent/prompts/system.ts +0 -106
  44. package/src/agent/tools/ai-editing.ts +0 -845
  45. package/src/agent/tools/ai-generation.ts +0 -1073
  46. package/src/agent/tools/ai-pipeline.ts +0 -1055
  47. package/src/agent/tools/ai.ts +0 -21
  48. package/src/agent/tools/batch.ts +0 -429
  49. package/src/agent/tools/e2e.test.ts +0 -545
  50. package/src/agent/tools/export.ts +0 -184
  51. package/src/agent/tools/filesystem.ts +0 -237
  52. package/src/agent/tools/index.ts +0 -150
  53. package/src/agent/tools/integration.test.ts +0 -775
  54. package/src/agent/tools/media.ts +0 -697
  55. package/src/agent/tools/project.ts +0 -313
  56. package/src/agent/tools/timeline.ts +0 -951
  57. package/src/agent/types.ts +0 -68
  58. package/src/commands/agent.ts +0 -340
  59. package/src/commands/ai-analyze.ts +0 -429
  60. package/src/commands/ai-animated-caption.ts +0 -390
  61. package/src/commands/ai-audio.ts +0 -941
  62. package/src/commands/ai-broll.ts +0 -490
  63. package/src/commands/ai-edit-cli.ts +0 -658
  64. package/src/commands/ai-edit.ts +0 -1542
  65. package/src/commands/ai-fill-gaps.ts +0 -566
  66. package/src/commands/ai-helpers.ts +0 -65
  67. package/src/commands/ai-highlights.ts +0 -1303
  68. package/src/commands/ai-image.ts +0 -761
  69. package/src/commands/ai-motion.ts +0 -347
  70. package/src/commands/ai-narrate.ts +0 -451
  71. package/src/commands/ai-review.ts +0 -309
  72. package/src/commands/ai-script-pipeline-cli.ts +0 -1710
  73. package/src/commands/ai-script-pipeline.ts +0 -1365
  74. package/src/commands/ai-suggest-edit.ts +0 -264
  75. package/src/commands/ai-video-fx.ts +0 -445
  76. package/src/commands/ai-video.ts +0 -915
  77. package/src/commands/ai-viral.ts +0 -595
  78. package/src/commands/ai-visual-fx.ts +0 -601
  79. package/src/commands/ai.test.ts +0 -627
  80. package/src/commands/ai.ts +0 -307
  81. package/src/commands/analyze.ts +0 -282
  82. package/src/commands/audio.ts +0 -644
  83. package/src/commands/batch.test.ts +0 -279
  84. package/src/commands/batch.ts +0 -440
  85. package/src/commands/detect.ts +0 -329
  86. package/src/commands/doctor.ts +0 -237
  87. package/src/commands/edit-cmd.ts +0 -1014
  88. package/src/commands/export.ts +0 -918
  89. package/src/commands/generate.ts +0 -2146
  90. package/src/commands/media.ts +0 -177
  91. package/src/commands/output.ts +0 -142
  92. package/src/commands/pipeline.ts +0 -398
  93. package/src/commands/project.test.ts +0 -127
  94. package/src/commands/project.ts +0 -149
  95. package/src/commands/sanitize.ts +0 -60
  96. package/src/commands/schema.ts +0 -130
  97. package/src/commands/setup.ts +0 -509
  98. package/src/commands/timeline.test.ts +0 -499
  99. package/src/commands/timeline.ts +0 -529
  100. package/src/commands/validate.ts +0 -77
  101. package/src/config/config.test.ts +0 -197
  102. package/src/config/index.ts +0 -125
  103. package/src/config/schema.ts +0 -82
  104. package/src/engine/index.ts +0 -2
  105. package/src/engine/project.test.ts +0 -702
  106. package/src/engine/project.ts +0 -439
  107. package/src/index.ts +0 -146
  108. package/src/utils/api-key.test.ts +0 -41
  109. package/src/utils/api-key.ts +0 -247
  110. package/src/utils/audio.ts +0 -83
  111. package/src/utils/exec-safe.ts +0 -75
  112. package/src/utils/first-run.ts +0 -52
  113. package/src/utils/provider-resolver.ts +0 -56
  114. package/src/utils/remotion.ts +0 -951
  115. package/src/utils/subtitle.test.ts +0 -227
  116. package/src/utils/subtitle.ts +0 -169
  117. package/src/utils/tty.ts +0 -196
  118. 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
- });