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.
- package/LICENSE +21 -21
- package/README.md +120 -118
- package/docs/architecture.md +401 -401
- package/docs/aspect-ratio-presets.md +72 -72
- package/docs/releasing.md +19 -19
- package/docs/usage.md +1193 -1193
- package/package.json +52 -52
- package/requirements-otio.txt +1 -1
- package/src/capabilities.mjs +307 -307
- package/src/cli.mjs +646 -646
- package/src/doctor.mjs +133 -133
- package/src/edits/analysis-to-edl.mjs +43 -43
- package/src/edits/detect-silence.mjs +51 -51
- package/src/edits/find-mistake-candidates.mjs +42 -42
- package/src/edits/plan-shot-placements.mjs +105 -105
- package/src/edits/plan-silence-cuts.mjs +49 -49
- package/src/edits/retime-transcript.mjs +105 -105
- package/src/files.mjs +80 -80
- package/src/index.mjs +44 -44
- package/src/interchange/otio.mjs +204 -204
- package/src/interchange/otio_bridge.py +521 -521
- package/src/media/analysis-provider.mjs +75 -75
- package/src/media/compatibility.mjs +45 -45
- package/src/media/ffmpeg-capabilities.mjs +129 -129
- package/src/media/frame-rate.mjs +58 -58
- package/src/media/frame-time.mjs +22 -22
- package/src/media/preview-assets.mjs +350 -350
- package/src/media/probe.mjs +149 -149
- package/src/media/render-cuts.mjs +90 -90
- package/src/media/scene-detection.mjs +107 -107
- package/src/media/scene-review.mjs +205 -205
- package/src/project/assets.mjs +1194 -1194
- package/src/project/create.mjs +100 -100
- package/src/project/presets.mjs +44 -44
- package/src/render/checkpoints.mjs +192 -192
- package/src/render/formats.mjs +163 -163
- package/src/render/hyperframes.mjs +590 -590
- package/src/render/process.mjs +111 -111
- package/src/render/progress.mjs +46 -46
- package/src/render/timeline-control.mjs +53 -53
- package/src/timeline/assemble.mjs +119 -119
- package/src/timeline/audio-effects.mjs +75 -75
- package/src/timeline/audio-mastering.mjs +236 -236
- package/src/timeline/audio-mix.mjs +105 -105
- package/src/timeline/audio-track-effects.mjs +139 -139
- package/src/timeline/audio-volume.mjs +63 -63
- package/src/timeline/background.mjs +8 -8
- package/src/timeline/blend-mode.mjs +34 -34
- package/src/timeline/caption-layouts.mjs +62 -61
- package/src/timeline/captions.mjs +80 -80
- package/src/timeline/edit.mjs +965 -965
- package/src/timeline/fingerprint.mjs +6 -6
- package/src/timeline/from-edl.mjs +31 -31
- package/src/timeline/history-store.mjs +281 -281
- package/src/timeline/history.mjs +69 -69
- package/src/timeline/inspect.mjs +117 -117
- package/src/timeline/keyframes.mjs +158 -158
- package/src/timeline/layers.mjs +18 -18
- package/src/timeline/mask.mjs +53 -53
- package/src/timeline/project.mjs +52 -52
- package/src/timeline/render.mjs +759 -759
- package/src/timeline/sidechain-render.mjs +209 -209
- package/src/timeline/source-fit.mjs +15 -15
- package/src/timeline/speech-bubble.mjs +26 -26
- package/src/timeline/speed-ramp.mjs +188 -188
- package/src/timeline/track-state.mjs +22 -22
- package/src/timeline/transition.mjs +34 -34
- package/src/timeline/validate.mjs +322 -322
- package/src/transcript/corrections.mjs +21 -21
- package/src/transcript/normalize.mjs +17 -17
- package/src/transcript/provider.mjs +42 -42
- package/src/transcript/subtitles.mjs +57 -57
- package/src/transcript/validate.mjs +39 -39
package/src/project/create.mjs
CHANGED
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
import { copyFileSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { dirname, join, resolve } from 'node:path';
|
|
3
|
-
import { createRequire } from 'node:module';
|
|
4
|
-
import { resolveCanvasPreset } from './presets.mjs';
|
|
5
|
-
import { DEFAULT_SOURCE_FIT, validateSourceFit } from '../timeline/source-fit.mjs';
|
|
6
|
-
import { createProjectTimeline } from '../timeline/project.mjs';
|
|
7
|
-
import { normalizeFrameRate } from '../media/frame-rate.mjs';
|
|
8
|
-
import { DEFAULT_PROJECT_BACKGROUND_COLOR, validateProjectBackgroundColor } from '../timeline/background.mjs';
|
|
9
|
-
|
|
10
|
-
const require = createRequire(import.meta.url);
|
|
11
|
-
|
|
12
|
-
export function createVideoProject(root, slug, options = {}) {
|
|
13
|
-
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) {
|
|
14
|
-
throw new TypeError('Use a lowercase kebab-case slug, not a path.');
|
|
15
|
-
}
|
|
16
|
-
const { fps: requestedFps = 30, durationSeconds = 10, sourceFit = DEFAULT_SOURCE_FIT, backgroundColor = DEFAULT_PROJECT_BACKGROUND_COLOR } = options;
|
|
17
|
-
if (options.preset !== undefined && (options.width !== undefined || options.height !== undefined)) {
|
|
18
|
-
throw new TypeError('Choose a canvas preset or custom width and height, not both.');
|
|
19
|
-
}
|
|
20
|
-
const preset = options.preset === undefined ? undefined : resolveCanvasPreset(options.preset);
|
|
21
|
-
const width = preset?.width ?? options.width ?? 1920;
|
|
22
|
-
const height = preset?.height ?? options.height ?? 1080;
|
|
23
|
-
validateSourceFit(sourceFit, 'sourceFit');
|
|
24
|
-
validateProjectBackgroundColor(backgroundColor, 'backgroundColor');
|
|
25
|
-
if (![width, height].every((value) => Number.isSafeInteger(value) && value > 0)) {
|
|
26
|
-
throw new TypeError('Width and height must be positive integers.');
|
|
27
|
-
}
|
|
28
|
-
const fps = normalizeFrameRate(requestedFps);
|
|
29
|
-
if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) {
|
|
30
|
-
throw new TypeError('durationSeconds must be a positive number.');
|
|
31
|
-
}
|
|
32
|
-
const projectPath = resolve(root, 'video-projects', slug);
|
|
33
|
-
if (existsSync(projectPath)) throw new Error(`Project already exists: ${slug}`);
|
|
34
|
-
const projectsPath = join(root, 'video-projects');
|
|
35
|
-
|
|
36
|
-
const project = {
|
|
37
|
-
schemaVersion: 1,
|
|
38
|
-
id: slug,
|
|
39
|
-
name: slug,
|
|
40
|
-
width,
|
|
41
|
-
height,
|
|
42
|
-
fps,
|
|
43
|
-
durationSeconds,
|
|
44
|
-
sourceFit,
|
|
45
|
-
backgroundColor,
|
|
46
|
-
...(preset ? { canvasPreset: preset.id } : {}),
|
|
47
|
-
};
|
|
48
|
-
const directories = ['assets', 'transcripts', 'edits', 'compositions', 'renders'];
|
|
49
|
-
mkdirSync(projectsPath, { recursive: true });
|
|
50
|
-
mkdirSync(projectPath);
|
|
51
|
-
try {
|
|
52
|
-
for (const directory of directories) mkdirSync(join(projectPath, directory));
|
|
53
|
-
const gsapPath = join(dirname(require.resolve('gsap')), 'gsap.min.js');
|
|
54
|
-
copyFileSync(gsapPath, join(projectPath, 'assets', 'gsap.min.js'));
|
|
55
|
-
writeFileSync(join(projectPath, 'meta.json'), `${JSON.stringify(project, null, 2)}\n`, { flag: 'wx' });
|
|
56
|
-
writeFileSync(join(projectPath, 'hyperframes.json'), `${JSON.stringify({
|
|
57
|
-
'$schema': 'https://hyperframes.heygen.com/schema/hyperframes.json',
|
|
58
|
-
paths: { blocks: 'compositions', components: 'compositions/components', assets: 'assets' },
|
|
59
|
-
}, null, 2)}\n`, { flag: 'wx' });
|
|
60
|
-
writeFileSync(join(projectPath, 'index.html'), starterHtml(project), { flag: 'wx' });
|
|
61
|
-
writeFileSync(join(projectPath, 'timeline.json'), `${JSON.stringify(createProjectTimeline(projectPath), null, 2)}\n`, { flag: 'wx' });
|
|
62
|
-
} catch (error) {
|
|
63
|
-
rmSync(projectPath, { recursive: true, force: true });
|
|
64
|
-
throw error;
|
|
65
|
-
}
|
|
66
|
-
return projectPath;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function starterHtml({ name, width, height, durationSeconds, backgroundColor }) {
|
|
70
|
-
const background = backgroundColor ?? 'transparent';
|
|
71
|
-
return `<!doctype html>
|
|
72
|
-
<html lang="en">
|
|
73
|
-
<head>
|
|
74
|
-
<meta charset="utf-8">
|
|
75
|
-
<meta name="viewport" content="width=${width}, initial-scale=1">
|
|
76
|
-
<title>${escapeHtml(name)}</title>
|
|
77
|
-
<link rel="icon" href="data:,">
|
|
78
|
-
<style>html,body,#root{box-sizing:border-box;margin:0;width:${width}px;height:${height}px}body{background:${background};color:#f5f5f5;font:48px system-ui,sans-serif}#root{display:grid;place-items:center}</style>
|
|
79
|
-
<script src="assets/gsap.min.js"></script>
|
|
80
|
-
</head>
|
|
81
|
-
<body>
|
|
82
|
-
<div id="root" data-composition-id="root" data-start="0" data-duration="${durationSeconds}" data-width="${width}" data-height="${height}">
|
|
83
|
-
<div id="title" class="clip" data-start="0" data-duration="${durationSeconds}" data-track-index="0">${escapeHtml(name)}</div>
|
|
84
|
-
</div>
|
|
85
|
-
<script>
|
|
86
|
-
const timeline = gsap.timeline({ paused: true });
|
|
87
|
-
timeline.from('#title', { opacity: 0, y: 24, duration: 0.8, ease: 'power2.out' });
|
|
88
|
-
window.__timelines = window.__timelines || {};
|
|
89
|
-
window.__timelines.root = timeline;
|
|
90
|
-
</script>
|
|
91
|
-
</body>
|
|
92
|
-
</html>
|
|
93
|
-
`;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function escapeHtml(value) {
|
|
97
|
-
return value.replace(/[&<>"']/g, (character) => ({
|
|
98
|
-
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
|
99
|
-
})[character]);
|
|
100
|
-
}
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { resolveCanvasPreset } from './presets.mjs';
|
|
5
|
+
import { DEFAULT_SOURCE_FIT, validateSourceFit } from '../timeline/source-fit.mjs';
|
|
6
|
+
import { createProjectTimeline } from '../timeline/project.mjs';
|
|
7
|
+
import { normalizeFrameRate } from '../media/frame-rate.mjs';
|
|
8
|
+
import { DEFAULT_PROJECT_BACKGROUND_COLOR, validateProjectBackgroundColor } from '../timeline/background.mjs';
|
|
9
|
+
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
|
|
12
|
+
export function createVideoProject(root, slug, options = {}) {
|
|
13
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)) {
|
|
14
|
+
throw new TypeError('Use a lowercase kebab-case slug, not a path.');
|
|
15
|
+
}
|
|
16
|
+
const { fps: requestedFps = 30, durationSeconds = 10, sourceFit = DEFAULT_SOURCE_FIT, backgroundColor = DEFAULT_PROJECT_BACKGROUND_COLOR } = options;
|
|
17
|
+
if (options.preset !== undefined && (options.width !== undefined || options.height !== undefined)) {
|
|
18
|
+
throw new TypeError('Choose a canvas preset or custom width and height, not both.');
|
|
19
|
+
}
|
|
20
|
+
const preset = options.preset === undefined ? undefined : resolveCanvasPreset(options.preset);
|
|
21
|
+
const width = preset?.width ?? options.width ?? 1920;
|
|
22
|
+
const height = preset?.height ?? options.height ?? 1080;
|
|
23
|
+
validateSourceFit(sourceFit, 'sourceFit');
|
|
24
|
+
validateProjectBackgroundColor(backgroundColor, 'backgroundColor');
|
|
25
|
+
if (![width, height].every((value) => Number.isSafeInteger(value) && value > 0)) {
|
|
26
|
+
throw new TypeError('Width and height must be positive integers.');
|
|
27
|
+
}
|
|
28
|
+
const fps = normalizeFrameRate(requestedFps);
|
|
29
|
+
if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) {
|
|
30
|
+
throw new TypeError('durationSeconds must be a positive number.');
|
|
31
|
+
}
|
|
32
|
+
const projectPath = resolve(root, 'video-projects', slug);
|
|
33
|
+
if (existsSync(projectPath)) throw new Error(`Project already exists: ${slug}`);
|
|
34
|
+
const projectsPath = join(root, 'video-projects');
|
|
35
|
+
|
|
36
|
+
const project = {
|
|
37
|
+
schemaVersion: 1,
|
|
38
|
+
id: slug,
|
|
39
|
+
name: slug,
|
|
40
|
+
width,
|
|
41
|
+
height,
|
|
42
|
+
fps,
|
|
43
|
+
durationSeconds,
|
|
44
|
+
sourceFit,
|
|
45
|
+
backgroundColor,
|
|
46
|
+
...(preset ? { canvasPreset: preset.id } : {}),
|
|
47
|
+
};
|
|
48
|
+
const directories = ['assets', 'transcripts', 'edits', 'compositions', 'renders'];
|
|
49
|
+
mkdirSync(projectsPath, { recursive: true });
|
|
50
|
+
mkdirSync(projectPath);
|
|
51
|
+
try {
|
|
52
|
+
for (const directory of directories) mkdirSync(join(projectPath, directory));
|
|
53
|
+
const gsapPath = join(dirname(require.resolve('gsap')), 'gsap.min.js');
|
|
54
|
+
copyFileSync(gsapPath, join(projectPath, 'assets', 'gsap.min.js'));
|
|
55
|
+
writeFileSync(join(projectPath, 'meta.json'), `${JSON.stringify(project, null, 2)}\n`, { flag: 'wx' });
|
|
56
|
+
writeFileSync(join(projectPath, 'hyperframes.json'), `${JSON.stringify({
|
|
57
|
+
'$schema': 'https://hyperframes.heygen.com/schema/hyperframes.json',
|
|
58
|
+
paths: { blocks: 'compositions', components: 'compositions/components', assets: 'assets' },
|
|
59
|
+
}, null, 2)}\n`, { flag: 'wx' });
|
|
60
|
+
writeFileSync(join(projectPath, 'index.html'), starterHtml(project), { flag: 'wx' });
|
|
61
|
+
writeFileSync(join(projectPath, 'timeline.json'), `${JSON.stringify(createProjectTimeline(projectPath), null, 2)}\n`, { flag: 'wx' });
|
|
62
|
+
} catch (error) {
|
|
63
|
+
rmSync(projectPath, { recursive: true, force: true });
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
return projectPath;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function starterHtml({ name, width, height, durationSeconds, backgroundColor }) {
|
|
70
|
+
const background = backgroundColor ?? 'transparent';
|
|
71
|
+
return `<!doctype html>
|
|
72
|
+
<html lang="en">
|
|
73
|
+
<head>
|
|
74
|
+
<meta charset="utf-8">
|
|
75
|
+
<meta name="viewport" content="width=${width}, initial-scale=1">
|
|
76
|
+
<title>${escapeHtml(name)}</title>
|
|
77
|
+
<link rel="icon" href="data:,">
|
|
78
|
+
<style>html,body,#root{box-sizing:border-box;margin:0;width:${width}px;height:${height}px}body{background:${background};color:#f5f5f5;font:48px system-ui,sans-serif}#root{display:grid;place-items:center}</style>
|
|
79
|
+
<script src="assets/gsap.min.js"></script>
|
|
80
|
+
</head>
|
|
81
|
+
<body>
|
|
82
|
+
<div id="root" data-composition-id="root" data-start="0" data-duration="${durationSeconds}" data-width="${width}" data-height="${height}">
|
|
83
|
+
<div id="title" class="clip" data-start="0" data-duration="${durationSeconds}" data-track-index="0">${escapeHtml(name)}</div>
|
|
84
|
+
</div>
|
|
85
|
+
<script>
|
|
86
|
+
const timeline = gsap.timeline({ paused: true });
|
|
87
|
+
timeline.from('#title', { opacity: 0, y: 24, duration: 0.8, ease: 'power2.out' });
|
|
88
|
+
window.__timelines = window.__timelines || {};
|
|
89
|
+
window.__timelines.root = timeline;
|
|
90
|
+
</script>
|
|
91
|
+
</body>
|
|
92
|
+
</html>
|
|
93
|
+
`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function escapeHtml(value) {
|
|
97
|
+
return value.replace(/[&<>"']/g, (character) => ({
|
|
98
|
+
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
|
99
|
+
})[character]);
|
|
100
|
+
}
|
package/src/project/presets.mjs
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
const presets = [
|
|
2
|
-
{
|
|
3
|
-
id: 'vertical',
|
|
4
|
-
aspectRatio: '9:16',
|
|
5
|
-
width: 1080,
|
|
6
|
-
height: 1920,
|
|
7
|
-
platforms: ['TikTok', 'Instagram Reels and Stories', 'Facebook Reels and Stories', 'YouTube Shorts'],
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
id: 'portrait-feed',
|
|
11
|
-
aspectRatio: '4:5',
|
|
12
|
-
width: 1080,
|
|
13
|
-
height: 1350,
|
|
14
|
-
platforms: ['Instagram feed', 'LinkedIn feed'],
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
id: 'square',
|
|
18
|
-
aspectRatio: '1:1',
|
|
19
|
-
width: 1080,
|
|
20
|
-
height: 1080,
|
|
21
|
-
platforms: ['TikTok', 'Instagram feed', 'LinkedIn feed'],
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
id: 'landscape',
|
|
25
|
-
aspectRatio: '16:9',
|
|
26
|
-
width: 1920,
|
|
27
|
-
height: 1080,
|
|
28
|
-
platforms: ['YouTube', 'LinkedIn feed'],
|
|
29
|
-
},
|
|
30
|
-
].map((preset) => Object.freeze({ ...preset, platforms: Object.freeze(preset.platforms) }));
|
|
31
|
-
|
|
32
|
-
export const CANVAS_PRESETS = Object.freeze(presets);
|
|
33
|
-
|
|
34
|
-
export function listCanvasPresets() {
|
|
35
|
-
return CANVAS_PRESETS;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function resolveCanvasPreset(id) {
|
|
39
|
-
const preset = CANVAS_PRESETS.find((candidate) => candidate.id === id);
|
|
40
|
-
if (!preset) {
|
|
41
|
-
throw new TypeError(`Unknown canvas preset "${id}". Available presets: ${CANVAS_PRESETS.map(({ id: name }) => name).join(', ')}.`);
|
|
42
|
-
}
|
|
43
|
-
return preset;
|
|
44
|
-
}
|
|
1
|
+
const presets = [
|
|
2
|
+
{
|
|
3
|
+
id: 'vertical',
|
|
4
|
+
aspectRatio: '9:16',
|
|
5
|
+
width: 1080,
|
|
6
|
+
height: 1920,
|
|
7
|
+
platforms: ['TikTok', 'Instagram Reels and Stories', 'Facebook Reels and Stories', 'YouTube Shorts'],
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
id: 'portrait-feed',
|
|
11
|
+
aspectRatio: '4:5',
|
|
12
|
+
width: 1080,
|
|
13
|
+
height: 1350,
|
|
14
|
+
platforms: ['Instagram feed', 'LinkedIn feed'],
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'square',
|
|
18
|
+
aspectRatio: '1:1',
|
|
19
|
+
width: 1080,
|
|
20
|
+
height: 1080,
|
|
21
|
+
platforms: ['TikTok', 'Instagram feed', 'LinkedIn feed'],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'landscape',
|
|
25
|
+
aspectRatio: '16:9',
|
|
26
|
+
width: 1920,
|
|
27
|
+
height: 1080,
|
|
28
|
+
platforms: ['YouTube', 'LinkedIn feed'],
|
|
29
|
+
},
|
|
30
|
+
].map((preset) => Object.freeze({ ...preset, platforms: Object.freeze(preset.platforms) }));
|
|
31
|
+
|
|
32
|
+
export const CANVAS_PRESETS = Object.freeze(presets);
|
|
33
|
+
|
|
34
|
+
export function listCanvasPresets() {
|
|
35
|
+
return CANVAS_PRESETS;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveCanvasPreset(id) {
|
|
39
|
+
const preset = CANVAS_PRESETS.find((candidate) => candidate.id === id);
|
|
40
|
+
if (!preset) {
|
|
41
|
+
throw new TypeError(`Unknown canvas preset "${id}". Available presets: ${CANVAS_PRESETS.map(({ id: name }) => name).join(', ')}.`);
|
|
42
|
+
}
|
|
43
|
+
return preset;
|
|
44
|
+
}
|