aivee 0.1.0-alpha.0 → 0.1.0-dev.d34a6ba

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 (73) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +120 -118
  3. package/docs/architecture.md +401 -401
  4. package/docs/aspect-ratio-presets.md +72 -72
  5. package/docs/releasing.md +19 -19
  6. package/docs/usage.md +1193 -1193
  7. package/package.json +52 -52
  8. package/requirements-otio.txt +1 -1
  9. package/src/capabilities.mjs +307 -307
  10. package/src/cli.mjs +646 -646
  11. package/src/doctor.mjs +133 -133
  12. package/src/edits/analysis-to-edl.mjs +43 -43
  13. package/src/edits/detect-silence.mjs +51 -51
  14. package/src/edits/find-mistake-candidates.mjs +42 -42
  15. package/src/edits/plan-shot-placements.mjs +105 -105
  16. package/src/edits/plan-silence-cuts.mjs +49 -49
  17. package/src/edits/retime-transcript.mjs +105 -105
  18. package/src/files.mjs +80 -80
  19. package/src/index.mjs +44 -44
  20. package/src/interchange/otio.mjs +204 -204
  21. package/src/interchange/otio_bridge.py +521 -521
  22. package/src/media/analysis-provider.mjs +75 -75
  23. package/src/media/compatibility.mjs +45 -45
  24. package/src/media/ffmpeg-capabilities.mjs +129 -129
  25. package/src/media/frame-rate.mjs +58 -58
  26. package/src/media/frame-time.mjs +22 -22
  27. package/src/media/preview-assets.mjs +350 -350
  28. package/src/media/probe.mjs +149 -149
  29. package/src/media/render-cuts.mjs +90 -90
  30. package/src/media/scene-detection.mjs +107 -107
  31. package/src/media/scene-review.mjs +205 -205
  32. package/src/project/assets.mjs +1194 -1194
  33. package/src/project/create.mjs +100 -100
  34. package/src/project/presets.mjs +44 -44
  35. package/src/render/checkpoints.mjs +192 -192
  36. package/src/render/formats.mjs +163 -163
  37. package/src/render/hyperframes.mjs +590 -590
  38. package/src/render/process.mjs +111 -111
  39. package/src/render/progress.mjs +46 -46
  40. package/src/render/timeline-control.mjs +53 -53
  41. package/src/timeline/assemble.mjs +119 -119
  42. package/src/timeline/audio-effects.mjs +75 -75
  43. package/src/timeline/audio-mastering.mjs +236 -236
  44. package/src/timeline/audio-mix.mjs +105 -105
  45. package/src/timeline/audio-track-effects.mjs +139 -139
  46. package/src/timeline/audio-volume.mjs +63 -63
  47. package/src/timeline/background.mjs +8 -8
  48. package/src/timeline/blend-mode.mjs +34 -34
  49. package/src/timeline/caption-layouts.mjs +62 -61
  50. package/src/timeline/captions.mjs +80 -80
  51. package/src/timeline/edit.mjs +965 -965
  52. package/src/timeline/fingerprint.mjs +6 -6
  53. package/src/timeline/from-edl.mjs +31 -31
  54. package/src/timeline/history-store.mjs +281 -281
  55. package/src/timeline/history.mjs +69 -69
  56. package/src/timeline/inspect.mjs +117 -117
  57. package/src/timeline/keyframes.mjs +158 -158
  58. package/src/timeline/layers.mjs +18 -18
  59. package/src/timeline/mask.mjs +53 -53
  60. package/src/timeline/project.mjs +52 -52
  61. package/src/timeline/render.mjs +759 -759
  62. package/src/timeline/sidechain-render.mjs +209 -209
  63. package/src/timeline/source-fit.mjs +15 -15
  64. package/src/timeline/speech-bubble.mjs +26 -26
  65. package/src/timeline/speed-ramp.mjs +188 -188
  66. package/src/timeline/track-state.mjs +22 -22
  67. package/src/timeline/transition.mjs +34 -34
  68. package/src/timeline/validate.mjs +322 -322
  69. package/src/transcript/corrections.mjs +21 -21
  70. package/src/transcript/normalize.mjs +17 -17
  71. package/src/transcript/provider.mjs +42 -42
  72. package/src/transcript/subtitles.mjs +57 -57
  73. package/src/transcript/validate.mjs +39 -39
@@ -1,69 +1,69 @@
1
- import { editTimeline, editTimelineBatch } from './edit.mjs';
2
- import { validateTimeline } from './validate.mjs';
3
-
4
- const DEFAULT_HISTORY_LIMIT = 100;
5
-
6
- /** Create an isolated, bounded undo/redo history for immutable timeline edits. */
7
- export function createTimelineHistory(timeline, { limit = DEFAULT_HISTORY_LIMIT } = {}) {
8
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > 10_000) {
9
- throw new TypeError('Timeline history limit must be an integer from 1 to 10000.');
10
- }
11
-
12
- validateTimeline(timeline);
13
- let current = structuredClone(timeline);
14
- const undoStack = [];
15
- const redoStack = [];
16
-
17
- return Object.freeze({
18
- get current() {
19
- return structuredClone(current);
20
- },
21
- get canUndo() {
22
- return undoStack.length > 0;
23
- },
24
- get canRedo() {
25
- return redoStack.length > 0;
26
- },
27
- get undoDepth() {
28
- return undoStack.length;
29
- },
30
- get redoDepth() {
31
- return redoStack.length;
32
- },
33
- apply(operation) {
34
- const next = editTimeline(current, operation);
35
- undoStack.push(current);
36
- if (undoStack.length > limit) undoStack.shift();
37
- current = next;
38
- redoStack.length = 0;
39
- return structuredClone(current);
40
- },
41
- applyBatch(operations) {
42
- const next = editTimelineBatch(current, operations);
43
- undoStack.push(current);
44
- if (undoStack.length > limit) undoStack.shift();
45
- current = next;
46
- redoStack.length = 0;
47
- return structuredClone(current);
48
- },
49
- undo() {
50
- if (undoStack.length === 0) return structuredClone(current);
51
- redoStack.push(current);
52
- current = undoStack.pop();
53
- return structuredClone(current);
54
- },
55
- redo() {
56
- if (redoStack.length === 0) return structuredClone(current);
57
- undoStack.push(current);
58
- current = redoStack.pop();
59
- return structuredClone(current);
60
- },
61
- reset(nextTimeline) {
62
- validateTimeline(nextTimeline);
63
- current = structuredClone(nextTimeline);
64
- undoStack.length = 0;
65
- redoStack.length = 0;
66
- return structuredClone(current);
67
- },
68
- });
69
- }
1
+ import { editTimeline, editTimelineBatch } from './edit.mjs';
2
+ import { validateTimeline } from './validate.mjs';
3
+
4
+ const DEFAULT_HISTORY_LIMIT = 100;
5
+
6
+ /** Create an isolated, bounded undo/redo history for immutable timeline edits. */
7
+ export function createTimelineHistory(timeline, { limit = DEFAULT_HISTORY_LIMIT } = {}) {
8
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 10_000) {
9
+ throw new TypeError('Timeline history limit must be an integer from 1 to 10000.');
10
+ }
11
+
12
+ validateTimeline(timeline);
13
+ let current = structuredClone(timeline);
14
+ const undoStack = [];
15
+ const redoStack = [];
16
+
17
+ return Object.freeze({
18
+ get current() {
19
+ return structuredClone(current);
20
+ },
21
+ get canUndo() {
22
+ return undoStack.length > 0;
23
+ },
24
+ get canRedo() {
25
+ return redoStack.length > 0;
26
+ },
27
+ get undoDepth() {
28
+ return undoStack.length;
29
+ },
30
+ get redoDepth() {
31
+ return redoStack.length;
32
+ },
33
+ apply(operation) {
34
+ const next = editTimeline(current, operation);
35
+ undoStack.push(current);
36
+ if (undoStack.length > limit) undoStack.shift();
37
+ current = next;
38
+ redoStack.length = 0;
39
+ return structuredClone(current);
40
+ },
41
+ applyBatch(operations) {
42
+ const next = editTimelineBatch(current, operations);
43
+ undoStack.push(current);
44
+ if (undoStack.length > limit) undoStack.shift();
45
+ current = next;
46
+ redoStack.length = 0;
47
+ return structuredClone(current);
48
+ },
49
+ undo() {
50
+ if (undoStack.length === 0) return structuredClone(current);
51
+ redoStack.push(current);
52
+ current = undoStack.pop();
53
+ return structuredClone(current);
54
+ },
55
+ redo() {
56
+ if (redoStack.length === 0) return structuredClone(current);
57
+ undoStack.push(current);
58
+ current = redoStack.pop();
59
+ return structuredClone(current);
60
+ },
61
+ reset(nextTimeline) {
62
+ validateTimeline(nextTimeline);
63
+ current = structuredClone(nextTimeline);
64
+ undoStack.length = 0;
65
+ redoStack.length = 0;
66
+ return structuredClone(current);
67
+ },
68
+ });
69
+ }
@@ -1,117 +1,117 @@
1
- import { validateTimeline } from './validate.mjs';
2
-
3
- /** Return a compact, deterministic editing overview for agents and review tools. */
4
- export function inspectTimeline(timeline) {
5
- validateTimeline(timeline);
6
- const assets = new Map(timeline.assets.map((asset) => [asset.id, asset]));
7
- const tracks = [...timeline.tracks]
8
- .sort((left, right) => left.order - right.order)
9
- .map((track) => {
10
- const clips = [...track.clips]
11
- .sort((left, right) => left.timelineStart - right.timelineStart
12
- || (left.id < right.id ? -1 : left.id > right.id ? 1 : 0))
13
- .map((clip) => inspectClip(track, clip, assets.get(clip.assetId)));
14
- return {
15
- id: track.id,
16
- kind: track.kind,
17
- order: track.order,
18
- ...(track.name === undefined ? {} : { name: track.name }),
19
- ...(track.visible === undefined ? {} : { visible: track.visible }),
20
- ...(track.muted === undefined ? {} : { muted: track.muted }),
21
- ...(track.solo === undefined ? {} : { solo: track.solo }),
22
- ...(track.locked === undefined ? {} : { locked: track.locked }),
23
- ...(track.volume === undefined ? {} : { volume: track.volume }),
24
- ...(track.volumeKeyframes === undefined ? {} : { volumeKeyframes: structuredClone(track.volumeKeyframes) }),
25
- ...(track.audioEffects === undefined ? {} : { audioEffects: structuredClone(track.audioEffects) }),
26
- ...(track.allowOverlap === undefined ? {} : { allowOverlap: track.allowOverlap }),
27
- clipCount: clips.length,
28
- clips,
29
- };
30
- });
31
-
32
- const clipCounts = Object.fromEntries(['video', 'audio', 'overlay'].map((kind) => [
33
- kind,
34
- tracks.filter((track) => track.kind === kind).reduce((total, track) => total + track.clipCount, 0),
35
- ]));
36
- return {
37
- schemaVersion: 1,
38
- project: {
39
- id: timeline.project.id,
40
- width: timeline.project.width,
41
- height: timeline.project.height,
42
- fps: timeline.project.fps,
43
- durationSeconds: timeline.project.durationSeconds,
44
- ...(timeline.project.sourceFit === undefined ? {} : { sourceFit: timeline.project.sourceFit }),
45
- ...(timeline.project.backgroundColor === undefined ? {} : { backgroundColor: timeline.project.backgroundColor }),
46
- },
47
- ...(timeline.audioMix === undefined ? {} : { audioMix: structuredClone(timeline.audioMix) }),
48
- ...(timeline.audioMastering === undefined ? {} : { audioMastering: structuredClone(timeline.audioMastering) }),
49
- ...(timeline.lineage === undefined ? {} : { lineage: structuredClone(timeline.lineage) }),
50
- markers: [...(timeline.markers ?? [])]
51
- .sort((left, right) => left.timeSeconds - right.timeSeconds || (left.id < right.id ? -1 : left.id > right.id ? 1 : 0))
52
- .map((marker) => structuredClone(marker)),
53
- summary: {
54
- assetCount: timeline.assets.length,
55
- trackCount: tracks.length,
56
- clipCount: Object.values(clipCounts).reduce((total, count) => total + count, 0),
57
- clipCounts,
58
- markerCount: timeline.markers?.length ?? 0,
59
- },
60
- assets: timeline.assets.map((asset) => ({
61
- id: asset.id,
62
- path: asset.path,
63
- ...(asset.type === undefined ? {} : { type: asset.type }),
64
- ...(asset.durationSeconds === undefined ? {} : { durationSeconds: asset.durationSeconds }),
65
- ...(asset.width === undefined ? {} : { width: asset.width }),
66
- ...(asset.height === undefined ? {} : { height: asset.height }),
67
- ...(asset.frameRate === undefined ? {} : { frameRate: asset.frameRate }),
68
- ...(asset.hasAudio === undefined ? {} : { hasAudio: asset.hasAudio }),
69
- ...(asset.videoStreams === undefined ? {} : { videoStreams: structuredClone(asset.videoStreams) }),
70
- ...(asset.audioStreams === undefined ? {} : { audioStreams: structuredClone(asset.audioStreams) }),
71
- ...(asset.streamTiming === undefined ? {} : { streamTiming: structuredClone(asset.streamTiming) }),
72
- ...(asset.proxies === undefined ? {} : { proxies: structuredClone(asset.proxies) }),
73
- })),
74
- tracks,
75
- };
76
- }
77
-
78
- function inspectClip(track, clip, asset) {
79
- const result = {
80
- id: clip.id,
81
- assetId: clip.assetId ?? null,
82
- ...(clip.enabled === undefined ? {} : { enabled: clip.enabled }),
83
- mediaType: track.kind === 'overlay' ? (clip.text === undefined ? 'composition' : 'text') : asset?.type ?? track.kind,
84
- timeline: {
85
- startSeconds: clip.timelineStart,
86
- endSeconds: roundInspectionSeconds(clip.timelineStart + clip.durationSeconds),
87
- durationSeconds: clip.durationSeconds,
88
- },
89
- };
90
- if (track.kind !== 'overlay') {
91
- result.source = {
92
- inSeconds: clip.sourceIn,
93
- outSeconds: clip.sourceOut,
94
- ...(clip.playbackRate === undefined ? {} : { playbackRate: clip.playbackRate }),
95
- ...(clip.speedRamp === undefined ? {} : { speedRamp: clip.speedRamp }),
96
- ...(clip.reverse === undefined ? {} : { reverse: clip.reverse }),
97
- ...(clip.videoStreamIndex === undefined ? {} : { videoStreamIndex: clip.videoStreamIndex }),
98
- };
99
- }
100
- for (const field of [
101
- 'text', 'x', 'y', 'width', 'height', 'fontFamily', 'fontSize', 'fontWeight',
102
- 'lineHeight', 'verticalAlign', 'paddingPixels', 'color', 'backgroundColor',
103
- 'textAlign', 'textOutline', 'textShadow', 'wordTimings', 'wordHighlightColor',
104
- 'wordHighlightAnimation', 'volume', 'fadeInSeconds', 'fadeOutSeconds',
105
- 'bubble',
106
- 'audioEffects', 'volumeKeyframes',
107
- 'transitionIn', 'transitionOut', 'blendMode', 'keyframes', 'effects', 'mask',
108
- 'sourceFit',
109
- ]) {
110
- if (clip[field] !== undefined) result[field] = structuredClone(clip[field]);
111
- }
112
- return result;
113
- }
114
-
115
- function roundInspectionSeconds(seconds) {
116
- return Number(seconds.toFixed(6));
117
- }
1
+ import { validateTimeline } from './validate.mjs';
2
+
3
+ /** Return a compact, deterministic editing overview for agents and review tools. */
4
+ export function inspectTimeline(timeline) {
5
+ validateTimeline(timeline);
6
+ const assets = new Map(timeline.assets.map((asset) => [asset.id, asset]));
7
+ const tracks = [...timeline.tracks]
8
+ .sort((left, right) => left.order - right.order)
9
+ .map((track) => {
10
+ const clips = [...track.clips]
11
+ .sort((left, right) => left.timelineStart - right.timelineStart
12
+ || (left.id < right.id ? -1 : left.id > right.id ? 1 : 0))
13
+ .map((clip) => inspectClip(track, clip, assets.get(clip.assetId)));
14
+ return {
15
+ id: track.id,
16
+ kind: track.kind,
17
+ order: track.order,
18
+ ...(track.name === undefined ? {} : { name: track.name }),
19
+ ...(track.visible === undefined ? {} : { visible: track.visible }),
20
+ ...(track.muted === undefined ? {} : { muted: track.muted }),
21
+ ...(track.solo === undefined ? {} : { solo: track.solo }),
22
+ ...(track.locked === undefined ? {} : { locked: track.locked }),
23
+ ...(track.volume === undefined ? {} : { volume: track.volume }),
24
+ ...(track.volumeKeyframes === undefined ? {} : { volumeKeyframes: structuredClone(track.volumeKeyframes) }),
25
+ ...(track.audioEffects === undefined ? {} : { audioEffects: structuredClone(track.audioEffects) }),
26
+ ...(track.allowOverlap === undefined ? {} : { allowOverlap: track.allowOverlap }),
27
+ clipCount: clips.length,
28
+ clips,
29
+ };
30
+ });
31
+
32
+ const clipCounts = Object.fromEntries(['video', 'audio', 'overlay'].map((kind) => [
33
+ kind,
34
+ tracks.filter((track) => track.kind === kind).reduce((total, track) => total + track.clipCount, 0),
35
+ ]));
36
+ return {
37
+ schemaVersion: 1,
38
+ project: {
39
+ id: timeline.project.id,
40
+ width: timeline.project.width,
41
+ height: timeline.project.height,
42
+ fps: timeline.project.fps,
43
+ durationSeconds: timeline.project.durationSeconds,
44
+ ...(timeline.project.sourceFit === undefined ? {} : { sourceFit: timeline.project.sourceFit }),
45
+ ...(timeline.project.backgroundColor === undefined ? {} : { backgroundColor: timeline.project.backgroundColor }),
46
+ },
47
+ ...(timeline.audioMix === undefined ? {} : { audioMix: structuredClone(timeline.audioMix) }),
48
+ ...(timeline.audioMastering === undefined ? {} : { audioMastering: structuredClone(timeline.audioMastering) }),
49
+ ...(timeline.lineage === undefined ? {} : { lineage: structuredClone(timeline.lineage) }),
50
+ markers: [...(timeline.markers ?? [])]
51
+ .sort((left, right) => left.timeSeconds - right.timeSeconds || (left.id < right.id ? -1 : left.id > right.id ? 1 : 0))
52
+ .map((marker) => structuredClone(marker)),
53
+ summary: {
54
+ assetCount: timeline.assets.length,
55
+ trackCount: tracks.length,
56
+ clipCount: Object.values(clipCounts).reduce((total, count) => total + count, 0),
57
+ clipCounts,
58
+ markerCount: timeline.markers?.length ?? 0,
59
+ },
60
+ assets: timeline.assets.map((asset) => ({
61
+ id: asset.id,
62
+ path: asset.path,
63
+ ...(asset.type === undefined ? {} : { type: asset.type }),
64
+ ...(asset.durationSeconds === undefined ? {} : { durationSeconds: asset.durationSeconds }),
65
+ ...(asset.width === undefined ? {} : { width: asset.width }),
66
+ ...(asset.height === undefined ? {} : { height: asset.height }),
67
+ ...(asset.frameRate === undefined ? {} : { frameRate: asset.frameRate }),
68
+ ...(asset.hasAudio === undefined ? {} : { hasAudio: asset.hasAudio }),
69
+ ...(asset.videoStreams === undefined ? {} : { videoStreams: structuredClone(asset.videoStreams) }),
70
+ ...(asset.audioStreams === undefined ? {} : { audioStreams: structuredClone(asset.audioStreams) }),
71
+ ...(asset.streamTiming === undefined ? {} : { streamTiming: structuredClone(asset.streamTiming) }),
72
+ ...(asset.proxies === undefined ? {} : { proxies: structuredClone(asset.proxies) }),
73
+ })),
74
+ tracks,
75
+ };
76
+ }
77
+
78
+ function inspectClip(track, clip, asset) {
79
+ const result = {
80
+ id: clip.id,
81
+ assetId: clip.assetId ?? null,
82
+ ...(clip.enabled === undefined ? {} : { enabled: clip.enabled }),
83
+ mediaType: track.kind === 'overlay' ? (clip.text === undefined ? 'composition' : 'text') : asset?.type ?? track.kind,
84
+ timeline: {
85
+ startSeconds: clip.timelineStart,
86
+ endSeconds: roundInspectionSeconds(clip.timelineStart + clip.durationSeconds),
87
+ durationSeconds: clip.durationSeconds,
88
+ },
89
+ };
90
+ if (track.kind !== 'overlay') {
91
+ result.source = {
92
+ inSeconds: clip.sourceIn,
93
+ outSeconds: clip.sourceOut,
94
+ ...(clip.playbackRate === undefined ? {} : { playbackRate: clip.playbackRate }),
95
+ ...(clip.speedRamp === undefined ? {} : { speedRamp: clip.speedRamp }),
96
+ ...(clip.reverse === undefined ? {} : { reverse: clip.reverse }),
97
+ ...(clip.videoStreamIndex === undefined ? {} : { videoStreamIndex: clip.videoStreamIndex }),
98
+ };
99
+ }
100
+ for (const field of [
101
+ 'text', 'x', 'y', 'width', 'height', 'fontFamily', 'fontSize', 'fontWeight',
102
+ 'lineHeight', 'verticalAlign', 'paddingPixels', 'color', 'backgroundColor',
103
+ 'textAlign', 'textOutline', 'textShadow', 'wordTimings', 'wordHighlightColor',
104
+ 'wordHighlightAnimation', 'volume', 'fadeInSeconds', 'fadeOutSeconds',
105
+ 'bubble',
106
+ 'audioEffects', 'volumeKeyframes',
107
+ 'transitionIn', 'transitionOut', 'blendMode', 'keyframes', 'effects', 'mask',
108
+ 'sourceFit',
109
+ ]) {
110
+ if (clip[field] !== undefined) result[field] = structuredClone(clip[field]);
111
+ }
112
+ return result;
113
+ }
114
+
115
+ function roundInspectionSeconds(seconds) {
116
+ return Number(seconds.toFixed(6));
117
+ }