aivee 0.0.0-stage → 0.1.0-dev.59872e1
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/CHANGELOG.md +34 -0
- package/LICENSE +21 -0
- package/README.md +119 -2
- package/docs/architecture.md +401 -0
- package/docs/aspect-ratio-presets.md +72 -0
- package/docs/releasing.md +19 -0
- package/docs/usage.md +1193 -0
- package/package.json +50 -4
- package/requirements-otio.txt +1 -0
- package/src/capabilities.mjs +307 -0
- package/src/cli.mjs +646 -0
- package/src/doctor.mjs +133 -0
- package/src/edits/analysis-to-edl.mjs +43 -0
- package/src/edits/detect-silence.mjs +51 -0
- package/src/edits/find-mistake-candidates.mjs +42 -0
- package/src/edits/plan-shot-placements.mjs +105 -0
- package/src/edits/plan-silence-cuts.mjs +49 -0
- package/src/edits/retime-transcript.mjs +105 -0
- package/src/files.mjs +80 -0
- package/src/index.mjs +44 -0
- package/src/interchange/otio.mjs +204 -0
- package/src/interchange/otio_bridge.py +521 -0
- package/src/media/analysis-provider.mjs +75 -0
- package/src/media/compatibility.mjs +45 -0
- package/src/media/ffmpeg-capabilities.mjs +129 -0
- package/src/media/frame-rate.mjs +58 -0
- package/src/media/frame-time.mjs +22 -0
- package/src/media/preview-assets.mjs +350 -0
- package/src/media/probe.mjs +149 -0
- package/src/media/render-cuts.mjs +90 -0
- package/src/media/scene-detection.mjs +107 -0
- package/src/media/scene-review.mjs +205 -0
- package/src/project/assets.mjs +1194 -0
- package/src/project/create.mjs +100 -0
- package/src/project/presets.mjs +44 -0
- package/src/render/checkpoints.mjs +192 -0
- package/src/render/formats.mjs +163 -0
- package/src/render/hyperframes.mjs +590 -0
- package/src/render/process.mjs +111 -0
- package/src/render/progress.mjs +46 -0
- package/src/render/timeline-control.mjs +53 -0
- package/src/timeline/assemble.mjs +119 -0
- package/src/timeline/audio-effects.mjs +75 -0
- package/src/timeline/audio-mastering.mjs +236 -0
- package/src/timeline/audio-mix.mjs +105 -0
- package/src/timeline/audio-track-effects.mjs +139 -0
- package/src/timeline/audio-volume.mjs +63 -0
- package/src/timeline/background.mjs +8 -0
- package/src/timeline/blend-mode.mjs +34 -0
- package/src/timeline/caption-layouts.mjs +62 -0
- package/src/timeline/captions.mjs +80 -0
- package/src/timeline/edit.mjs +965 -0
- package/src/timeline/fingerprint.mjs +6 -0
- package/src/timeline/from-edl.mjs +31 -0
- package/src/timeline/history-store.mjs +281 -0
- package/src/timeline/history.mjs +69 -0
- package/src/timeline/inspect.mjs +117 -0
- package/src/timeline/keyframes.mjs +158 -0
- package/src/timeline/layers.mjs +18 -0
- package/src/timeline/mask.mjs +53 -0
- package/src/timeline/project.mjs +52 -0
- package/src/timeline/render.mjs +759 -0
- package/src/timeline/sidechain-render.mjs +209 -0
- package/src/timeline/source-fit.mjs +15 -0
- package/src/timeline/speech-bubble.mjs +26 -0
- package/src/timeline/speed-ramp.mjs +188 -0
- package/src/timeline/track-state.mjs +22 -0
- package/src/timeline/transition.mjs +34 -0
- package/src/timeline/validate.mjs +322 -0
- package/src/transcript/corrections.mjs +21 -0
- package/src/transcript/normalize.mjs +17 -0
- package/src/transcript/provider.mjs +42 -0
- package/src/transcript/subtitles.mjs +57 -0
- package/src/transcript/validate.mjs +39 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Assign renderer layer indices while preserving track order and clip overlap. */
|
|
2
|
+
export function assignRenderTrackIndices(timeline) {
|
|
3
|
+
const orderedTracks = [...timeline.tracks].sort((a, b) => a.order - b.order);
|
|
4
|
+
const indices = new Map();
|
|
5
|
+
let baseIndex = 0;
|
|
6
|
+
for (const track of orderedTracks) {
|
|
7
|
+
const laneEnds = [];
|
|
8
|
+
const clips = [...track.clips].sort((a, b) => a.timelineStart - b.timelineStart || a.id.localeCompare(b.id));
|
|
9
|
+
for (const clip of clips) {
|
|
10
|
+
let lane = laneEnds.findIndex((end) => end <= clip.timelineStart + 1e-6);
|
|
11
|
+
if (lane < 0) lane = laneEnds.length;
|
|
12
|
+
laneEnds[lane] = clip.timelineStart + clip.durationSeconds;
|
|
13
|
+
indices.set(`${track.id}/${clip.id}`, baseIndex + lane);
|
|
14
|
+
}
|
|
15
|
+
baseIndex += Math.max(1, laneEnds.length);
|
|
16
|
+
}
|
|
17
|
+
return indices;
|
|
18
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const SHAPES = new Set(['circle', 'ellipse', 'rect']);
|
|
2
|
+
|
|
3
|
+
export function validateVisualMask(mask, label = 'Visual mask') {
|
|
4
|
+
if (!mask || typeof mask !== 'object' || Array.isArray(mask) || !SHAPES.has(mask.type)) {
|
|
5
|
+
throw new TypeError(`${label} type must be circle, ellipse, or rect.`);
|
|
6
|
+
}
|
|
7
|
+
const fields = mask.type === 'circle'
|
|
8
|
+
? ['type', 'centerX', 'centerY', 'radius']
|
|
9
|
+
: mask.type === 'ellipse'
|
|
10
|
+
? ['type', 'centerX', 'centerY', 'radiusX', 'radiusY']
|
|
11
|
+
: ['type', 'x', 'y', 'width', 'height'];
|
|
12
|
+
if (Object.keys(mask).some((field) => !fields.includes(field))) {
|
|
13
|
+
throw new TypeError(`${label} contains unsupported fields for ${mask.type}.`);
|
|
14
|
+
}
|
|
15
|
+
const values = fields.filter((field) => field !== 'type').map((field) => mask[field]);
|
|
16
|
+
if (!values.every((value) => Number.isFinite(value) && value >= 0 && value <= 1)) {
|
|
17
|
+
throw new TypeError(`${label} values must be finite normalized numbers from 0 to 1.`);
|
|
18
|
+
}
|
|
19
|
+
if (mask.type === 'circle' && mask.radius <= 0) throw new TypeError(`${label} radius must be greater than 0.`);
|
|
20
|
+
if (mask.type === 'ellipse' && (mask.radiusX <= 0 || mask.radiusY <= 0)) throw new TypeError(`${label} radii must be greater than 0.`);
|
|
21
|
+
if (mask.type === 'rect' && (mask.width <= 0 || mask.height <= 0 || mask.x + mask.width > 1 || mask.y + mask.height > 1)) {
|
|
22
|
+
throw new TypeError(`${label} rectangle must have positive size within normalized bounds.`);
|
|
23
|
+
}
|
|
24
|
+
return mask;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function visualMaskClipPath(mask, animated = false) {
|
|
28
|
+
validateVisualMask(mask);
|
|
29
|
+
if (mask.type === 'circle') return `circle(${animated ? scaleLength(mask.radius) : percent(mask.radius)} at ${percent(mask.centerX)} ${percent(mask.centerY)})`;
|
|
30
|
+
if (mask.type === 'ellipse') return `ellipse(${animated ? scaleLength(mask.radiusX) : percent(mask.radiusX)} ${animated ? scaleLength(mask.radiusY) : percent(mask.radiusY)} at ${percent(mask.centerX)} ${percent(mask.centerY)})`;
|
|
31
|
+
if (animated) {
|
|
32
|
+
const x1 = scaleFromCenter(mask.x), y1 = scaleFromCenter(mask.y);
|
|
33
|
+
const x2 = scaleFromCenter(mask.x + mask.width), y2 = scaleFromCenter(mask.y + mask.height);
|
|
34
|
+
return `polygon(${x1} ${y1}, ${x2} ${y1}, ${x2} ${y2}, ${x1} ${y2})`;
|
|
35
|
+
}
|
|
36
|
+
const top = percent(mask.y);
|
|
37
|
+
const right = percent(1 - mask.x - mask.width);
|
|
38
|
+
const bottom = percent(1 - mask.y - mask.height);
|
|
39
|
+
const left = percent(mask.x);
|
|
40
|
+
return `inset(${top} ${right} ${bottom} ${left})`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function scaleLength(value) {
|
|
44
|
+
return `calc(${percent(value)} * var(--engine-mask-progress, 1))`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function scaleFromCenter(value) {
|
|
48
|
+
return `calc(50% + (${percent(value)} - 50%) * var(--engine-mask-progress, 1))`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function percent(value) {
|
|
52
|
+
return `${Number((value * 100).toFixed(6))}%`;
|
|
53
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { existsSync, lstatSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { listProjectAssets } from '../project/assets.mjs';
|
|
4
|
+
import { validateTimeline } from './validate.mjs';
|
|
5
|
+
|
|
6
|
+
export function createProjectTimeline(projectPath, options = {}) {
|
|
7
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) throw new TypeError('Timeline creation options must be an object.');
|
|
8
|
+
for (const key of Object.keys(options)) if (key !== 'durationSeconds') throw new TypeError(`Unsupported timeline creation option: ${key}.`);
|
|
9
|
+
const root = resolve(projectPath);
|
|
10
|
+
const metaPath = join(root, 'meta.json');
|
|
11
|
+
if (!existsSync(metaPath) || lstatSync(metaPath).isSymbolicLink() || !lstatSync(metaPath).isFile()) {
|
|
12
|
+
throw new Error(`Not an Engine project: ${root}`);
|
|
13
|
+
}
|
|
14
|
+
const meta = JSON.parse(readFileSync(metaPath, 'utf8'));
|
|
15
|
+
const durationSeconds = options.durationSeconds ?? meta.durationSeconds;
|
|
16
|
+
if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) throw new TypeError('Timeline durationSeconds must be positive.');
|
|
17
|
+
const catalog = listProjectAssets(root);
|
|
18
|
+
const timeline = {
|
|
19
|
+
schemaVersion: 1,
|
|
20
|
+
project: {
|
|
21
|
+
id: meta.id,
|
|
22
|
+
width: meta.width,
|
|
23
|
+
height: meta.height,
|
|
24
|
+
fps: meta.fps,
|
|
25
|
+
durationSeconds,
|
|
26
|
+
...(meta.sourceFit === undefined ? {} : { sourceFit: meta.sourceFit }),
|
|
27
|
+
...(meta.backgroundColor === undefined ? {} : { backgroundColor: meta.backgroundColor }),
|
|
28
|
+
},
|
|
29
|
+
assets: catalog.assets.map((asset) => ({
|
|
30
|
+
id: asset.id,
|
|
31
|
+
path: asset.path,
|
|
32
|
+
type: asset.type,
|
|
33
|
+
...(asset.durationSeconds === undefined ? {} : { durationSeconds: asset.durationSeconds }),
|
|
34
|
+
...(asset.width === undefined ? {} : { width: asset.width }),
|
|
35
|
+
...(asset.height === undefined ? {} : { height: asset.height }),
|
|
36
|
+
...(asset.frameRate === undefined ? {} : { frameRate: asset.frameRate }),
|
|
37
|
+
...(asset.nominalFrameRate === undefined ? {} : { nominalFrameRate: asset.nominalFrameRate }),
|
|
38
|
+
...(asset.hasAudio === undefined ? {} : { hasAudio: asset.hasAudio }),
|
|
39
|
+
...(asset.videoStreams === undefined ? {} : { videoStreams: structuredClone(asset.videoStreams) }),
|
|
40
|
+
...(asset.audioStreams === undefined ? {} : { audioStreams: structuredClone(asset.audioStreams) }),
|
|
41
|
+
...(asset.streamTiming === undefined ? {} : { streamTiming: structuredClone(asset.streamTiming) }),
|
|
42
|
+
sha256: asset.sha256,
|
|
43
|
+
})),
|
|
44
|
+
markers: [],
|
|
45
|
+
tracks: [
|
|
46
|
+
{ id: 'video-main', kind: 'video', order: 0, clips: [] },
|
|
47
|
+
{ id: 'audio-main', kind: 'audio', order: 1, clips: [] },
|
|
48
|
+
{ id: 'overlay-main', kind: 'overlay', order: 2, clips: [] },
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
return validateTimeline(timeline);
|
|
52
|
+
}
|