frameyard 0.6.0 → 0.8.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.
- package/CHANGELOG.md +106 -0
- package/GLOSSARY.md +40 -16
- package/README.md +64 -55
- package/dist/assets/process.js.map +1 -1
- package/dist/assets/provenance.js.map +1 -1
- package/dist/cli.js +255 -59
- package/dist/cli.js.map +1 -1
- package/dist/composition/attributes.d.ts.map +1 -1
- package/dist/composition/descriptor.d.ts +17 -3
- package/dist/composition/descriptor.d.ts.map +1 -1
- package/dist/composition/descriptor.js +51 -28
- package/dist/composition/descriptor.js.map +1 -1
- package/dist/composition/diagnostics.js.map +1 -1
- package/dist/composition/format.d.ts +12 -0
- package/dist/composition/format.d.ts.map +1 -0
- package/dist/composition/format.js +28 -0
- package/dist/composition/format.js.map +1 -0
- package/dist/composition/index.d.ts +2 -1
- package/dist/composition/index.d.ts.map +1 -1
- package/dist/composition/index.js +1 -0
- package/dist/composition/index.js.map +1 -1
- package/dist/contracts.d.ts +55 -6
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +3 -0
- package/dist/contracts.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +79 -0
- package/dist/doctor.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/html/document-adapter.d.ts +3 -3
- package/dist/html/document-adapter.d.ts.map +1 -1
- package/dist/html/document-adapter.js +1 -1
- package/dist/html/document-adapter.js.map +1 -1
- package/dist/html/index.d.ts +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/parse.d.ts +10 -2
- package/dist/html/parse.d.ts.map +1 -1
- package/dist/html/parse.js +14 -5
- package/dist/html/parse.js.map +1 -1
- package/dist/html/validate.d.ts +12 -1
- package/dist/html/validate.d.ts.map +1 -1
- package/dist/html/validate.js +233 -61
- package/dist/html/validate.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/preview/main.d.ts +2 -2
- package/dist/preview/main.d.ts.map +1 -1
- package/dist/preview/main.js +47 -19
- package/dist/preview/main.js.map +1 -1
- package/dist/preview/timeline-data.d.ts +5 -1
- package/dist/preview/timeline-data.d.ts.map +1 -1
- package/dist/preview/timeline-host.d.ts +2 -0
- package/dist/preview/timeline-host.d.ts.map +1 -1
- package/dist/preview/timeline-host.js +31 -10
- package/dist/preview/timeline-host.js.map +1 -1
- package/dist/preview/timeline-main.js +12 -7
- package/dist/preview/timeline-main.js.map +1 -1
- package/dist/project.d.ts +3 -5
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js.map +1 -1
- package/dist/renderer/browser.d.ts +2 -2
- package/dist/renderer/browser.d.ts.map +1 -1
- package/dist/renderer/browser.js +10 -4
- package/dist/renderer/browser.js.map +1 -1
- package/dist/renderer/ffmpeg.d.ts +69 -2
- package/dist/renderer/ffmpeg.d.ts.map +1 -1
- package/dist/renderer/ffmpeg.js +381 -69
- package/dist/renderer/ffmpeg.js.map +1 -1
- package/dist/renderer/index.d.ts +66 -5
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +226 -51
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/timeline.d.ts +41 -4
- package/dist/renderer/timeline.d.ts.map +1 -1
- package/dist/renderer/timeline.js +242 -39
- package/dist/renderer/timeline.js.map +1 -1
- package/dist/runtime/host.d.ts.map +1 -1
- package/dist/runtime/host.js +155 -31
- package/dist/runtime/host.js.map +1 -1
- package/dist/runtime/media.d.ts.map +1 -1
- package/dist/runtime/media.js +3 -6
- package/dist/runtime/media.js.map +1 -1
- package/dist/runtime/video.d.ts +13 -0
- package/dist/runtime/video.d.ts.map +1 -0
- package/dist/runtime/video.js +41 -0
- package/dist/runtime/video.js.map +1 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +107 -103
- package/dist/scaffold.js.map +1 -1
- package/dist/server/composition-html.d.ts +13 -7
- package/dist/server/composition-html.d.ts.map +1 -1
- package/dist/server/composition-html.js +65 -16
- package/dist/server/composition-html.js.map +1 -1
- package/dist/server/preview-html.js +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +26 -6
- package/dist/server/server.js.map +1 -1
- package/dist/server/timeline-preview-data.d.ts.map +1 -1
- package/dist/server/timeline-preview-data.js +11 -0
- package/dist/server/timeline-preview-data.js.map +1 -1
- package/dist/server/timeline-preview-html.d.ts.map +1 -1
- package/dist/server/timeline-preview-html.js +2 -1
- package/dist/server/timeline-preview-html.js.map +1 -1
- package/dist/timeline/plan.js.map +1 -1
- package/dist/timeline/resolve.d.ts +4 -3
- package/dist/timeline/resolve.d.ts.map +1 -1
- package/dist/timeline/resolve.js +14 -4
- package/dist/timeline/resolve.js.map +1 -1
- package/dist/timeline/schema.d.ts +7 -0
- package/dist/timeline/schema.d.ts.map +1 -1
- package/dist/timeline/schema.js +50 -0
- package/dist/timeline/schema.js.map +1 -1
- package/dist/transcript.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/adr/0007-make-html-documents-the-composition-unit.md +1 -1
- package/docs/adr/0008-share-the-composition-document-grammar.md +1 -1
- package/docs/adr/0011-compose-timeline-frames-in-a-live-browser-host.md +1 -1
- package/docs/adr/0012-render-timelines-with-segmented-lossless-clip-intermediates.md +1 -1
- package/docs/adr/0013-capture-bounded-multi-frame-stills-for-inspection.md +33 -0
- package/docs/adr/0014-compose-timeline-clips-with-css-blend-modes.md +37 -0
- package/docs/adr/0015-inherit-composition-format-from-the-project.md +28 -0
- package/docs/adr/0016-author-compositions-as-html-fragments.md +32 -0
- package/docs/adr/0017-render-timeless-compositions-as-stills.md +99 -0
- package/docs/migration-0.4.md +3 -1
- package/docs/migration-0.8.md +163 -0
- package/docs/preview-testing.md +60 -0
- package/docs/v0.1-design.md +52 -42
- package/examples/blend/README.md +106 -0
- package/examples/blend/assets/ink.mp4 +0 -0
- package/examples/blend/compositions/aurora.html +55 -0
- package/examples/blend/compositions/backdrop.html +74 -0
- package/examples/blend/compositions/labels.html +149 -0
- package/examples/blend/scripts/make-ink.sh +26 -0
- package/examples/blend/timelines/blend.timeline.json +155 -0
- package/examples/blend/video.config.js +11 -0
- package/examples/demo/compositions/demo.html +243 -251
- package/examples/static/README.md +17 -0
- package/examples/static/compositions/still.html +37 -0
- package/examples/static/timelines/still.timeline.json +13 -0
- package/examples/static/video.config.js +9 -0
- package/examples/video/compositions/video-mp4.html +349 -357
- package/examples/video/compositions/video-overlay.html +337 -345
- package/examples/video/compositions/video.html +349 -357
- package/examples/webgl/compositions/webgl.html +205 -213
- package/package.json +6 -3
- package/skills/frameyard/README.md +7 -5
- package/skills/frameyard/SKILL.md +17 -13
- package/skills/frameyard/references/common-mistakes.md +30 -6
- package/skills/frameyard/references/controllers.md +5 -4
- package/skills/frameyard/references/deterministic-authoring.md +42 -36
- package/skills/frameyard/references/diagnostics.md +11 -3
- package/skills/frameyard/references/failure-recovery.md +28 -7
- package/skills/frameyard/references/json-results.md +58 -7
- package/skills/frameyard/references/subtitles-karaoke.md +2 -2
- package/skills/frameyard/references/timelines.md +21 -15
- package/skills/frameyard/scripts/frameyard.mjs +20 -3
package/dist/renderer/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CompositionDiagnostic, type CompositionMetadata, type StaticCompositionMetadata } from "../contracts.js";
|
|
2
2
|
import type { Project } from "../project.js";
|
|
3
3
|
import { type AudioInspection } from "./audio.js";
|
|
4
|
+
import { type VideoCodec, type VideoContainer, type VideoEncoderChoice, type VideoEncoderName } from "./ffmpeg.js";
|
|
4
5
|
import { type TranscriptInspection } from "./transcripts.js";
|
|
5
6
|
export interface ProgressEvent {
|
|
6
7
|
frame: number;
|
|
@@ -14,7 +15,8 @@ interface SessionOptions {
|
|
|
14
15
|
signal?: AbortSignal;
|
|
15
16
|
onProgress?: (event: ProgressEvent) => void;
|
|
16
17
|
}
|
|
17
|
-
export interface
|
|
18
|
+
export interface AnimatedCheckResult {
|
|
19
|
+
kind: "animated";
|
|
18
20
|
composition: string;
|
|
19
21
|
entry: string;
|
|
20
22
|
controller: {
|
|
@@ -36,7 +38,8 @@ export interface CheckResult {
|
|
|
36
38
|
diagnostics: CompositionDiagnostic[];
|
|
37
39
|
elapsedMs: number;
|
|
38
40
|
}
|
|
39
|
-
export interface
|
|
41
|
+
export interface StaticCheckResult {
|
|
42
|
+
kind: "static";
|
|
40
43
|
composition: string;
|
|
41
44
|
entry: string;
|
|
42
45
|
controller: {
|
|
@@ -45,14 +48,37 @@ export interface StillResult {
|
|
|
45
48
|
kind: "external";
|
|
46
49
|
src: string;
|
|
47
50
|
} | null;
|
|
51
|
+
metadata: StaticCompositionMetadata;
|
|
52
|
+
framesChecked: 1;
|
|
53
|
+
diagnostics: CompositionDiagnostic[];
|
|
54
|
+
elapsedMs: number;
|
|
55
|
+
}
|
|
56
|
+
export type CheckResult = AnimatedCheckResult | StaticCheckResult;
|
|
57
|
+
export interface StillFrameResult {
|
|
48
58
|
frame: number;
|
|
49
59
|
path: string;
|
|
60
|
+
}
|
|
61
|
+
export interface StillResult {
|
|
62
|
+
composition: string;
|
|
63
|
+
entry: string;
|
|
64
|
+
controller: {
|
|
65
|
+
kind: "inline";
|
|
66
|
+
} | {
|
|
67
|
+
kind: "external";
|
|
68
|
+
src: string;
|
|
69
|
+
} | null;
|
|
70
|
+
/** Present only when exactly one frame was captured. */
|
|
71
|
+
frame?: number;
|
|
72
|
+
/** Present only when exactly one frame was captured. */
|
|
73
|
+
path?: string;
|
|
74
|
+
frames: StillFrameResult[];
|
|
50
75
|
width: number;
|
|
51
76
|
height: number;
|
|
52
77
|
diagnostics: CompositionDiagnostic[];
|
|
53
78
|
elapsedMs: number;
|
|
54
79
|
}
|
|
55
|
-
export interface
|
|
80
|
+
export interface VideoRenderResult {
|
|
81
|
+
kind: "video";
|
|
56
82
|
composition: string;
|
|
57
83
|
entry: string;
|
|
58
84
|
controller: {
|
|
@@ -69,6 +95,12 @@ export interface RenderResult {
|
|
|
69
95
|
durationSeconds: number;
|
|
70
96
|
framesRendered: number;
|
|
71
97
|
concurrency: number;
|
|
98
|
+
/** Resolved delivery codec. */
|
|
99
|
+
codec: VideoCodec;
|
|
100
|
+
/** Resolved delivery container. */
|
|
101
|
+
container: VideoContainer;
|
|
102
|
+
/** Resolved encoder used for the output. */
|
|
103
|
+
encoder: VideoEncoderName;
|
|
72
104
|
bytes: number;
|
|
73
105
|
audio: {
|
|
74
106
|
clips: number;
|
|
@@ -79,19 +111,48 @@ export interface RenderResult {
|
|
|
79
111
|
diagnostics: CompositionDiagnostic[];
|
|
80
112
|
elapsedMs: number;
|
|
81
113
|
}
|
|
114
|
+
export interface ImageRenderResult {
|
|
115
|
+
kind: "image";
|
|
116
|
+
composition: string;
|
|
117
|
+
entry: string;
|
|
118
|
+
controller: {
|
|
119
|
+
kind: "inline";
|
|
120
|
+
} | {
|
|
121
|
+
kind: "external";
|
|
122
|
+
src: string;
|
|
123
|
+
} | null;
|
|
124
|
+
path: string;
|
|
125
|
+
width: number;
|
|
126
|
+
height: number;
|
|
127
|
+
bytes: number;
|
|
128
|
+
diagnostics: CompositionDiagnostic[];
|
|
129
|
+
elapsedMs: number;
|
|
130
|
+
}
|
|
131
|
+
export type RenderResult = VideoRenderResult | ImageRenderResult;
|
|
82
132
|
export declare const MAX_RENDER_CONCURRENCY = 16;
|
|
83
133
|
/** Validates the render concurrency flag and returns its normalized value. */
|
|
84
134
|
export declare function validateRenderConcurrency(value: number | undefined): number;
|
|
85
135
|
export declare function checkComposition(options: SessionOptions): Promise<CheckResult>;
|
|
136
|
+
export declare function captureStills(options: SessionOptions & {
|
|
137
|
+
frames: number[];
|
|
138
|
+
outPaths: string[];
|
|
139
|
+
overwrite?: boolean;
|
|
140
|
+
}): Promise<StillResult>;
|
|
86
141
|
export declare function captureStill(options: SessionOptions & {
|
|
87
142
|
frame: number;
|
|
88
143
|
outPath: string;
|
|
89
144
|
overwrite?: boolean;
|
|
90
145
|
}): Promise<StillResult>;
|
|
91
146
|
export declare function renderComposition(options: SessionOptions & {
|
|
92
|
-
outPath
|
|
147
|
+
outPath?: string;
|
|
93
148
|
overwrite?: boolean;
|
|
94
149
|
concurrency?: number;
|
|
150
|
+
/** Encoder choice; defaults to the codec's software encoder. */
|
|
151
|
+
encoder?: VideoEncoderChoice;
|
|
152
|
+
/** Delivery codec; defaults to `h264`. */
|
|
153
|
+
codec?: VideoCodec;
|
|
154
|
+
/** Delivery container; defaults to `mp4`. */
|
|
155
|
+
container?: VideoContainer;
|
|
95
156
|
}): Promise<RenderResult>;
|
|
96
157
|
export {};
|
|
97
158
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/renderer/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/renderer/index.ts"],"names":[],"mappings":"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"}
|
package/dist/renderer/index.js
CHANGED
|
@@ -2,12 +2,14 @@ import { link, mkdir, mkdtemp, rename, rm, stat, writeFile } from "node:fs/promi
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { COMPOSITION_FORMAT_UNRESOLVED_MESSAGE } from "../composition/index.js";
|
|
6
|
+
import { isPlainObject, } from "../contracts.js";
|
|
5
7
|
import { FrameyardError, isFrameyardError } from "../errors.js";
|
|
6
8
|
import { validateHtmlComposition } from "../html/validate.js";
|
|
7
9
|
import { startPreviewServer } from "../server/server.js";
|
|
8
10
|
import { inspectTimelineAudio } from "./audio.js";
|
|
9
11
|
import { openCompositionSession } from "./browser.js";
|
|
10
|
-
import { assertFfmpegCapabilities, mixAudioTimeline, startVideoEncoder } from "./ffmpeg.js";
|
|
12
|
+
import { assertContainerCodec, assertFfmpegCapabilities, CONTAINER_EXTENSION, defaultCodecForContainer, mixAudioTimeline, resolveVideoEncoder, startVideoEncoder, } from "./ffmpeg.js";
|
|
11
13
|
import { inspectTranscriptClips } from "./transcripts.js";
|
|
12
14
|
export const MAX_RENDER_CONCURRENCY = 16;
|
|
13
15
|
/** Validates the render concurrency flag and returns its normalized value. */
|
|
@@ -55,6 +57,21 @@ function mergeDiagnostics(...groups) {
|
|
|
55
57
|
}
|
|
56
58
|
return result;
|
|
57
59
|
}
|
|
60
|
+
function withBatchContext(error, frames, failedFrame) {
|
|
61
|
+
if (!isFrameyardError(error)) {
|
|
62
|
+
return error;
|
|
63
|
+
}
|
|
64
|
+
const details = error.details === undefined
|
|
65
|
+
? {}
|
|
66
|
+
: isPlainObject(error.details)
|
|
67
|
+
? { ...error.details }
|
|
68
|
+
: { originalDetails: error.details };
|
|
69
|
+
return new FrameyardError(error.code, error.message, {
|
|
70
|
+
cause: error,
|
|
71
|
+
details: { ...details, frames: [...frames], failedFrame },
|
|
72
|
+
diagnostics: error.diagnostics,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
58
75
|
async function readSessionDiagnostics(sessions) {
|
|
59
76
|
const groups = await Promise.all(sessions.map((session) => session.readDiagnostics()));
|
|
60
77
|
return mergeDiagnostics(...groups);
|
|
@@ -79,6 +96,25 @@ async function ensureOutputAvailable(outPath, overwrite) {
|
|
|
79
96
|
}
|
|
80
97
|
await mkdir(path.dirname(outPath), { recursive: true });
|
|
81
98
|
}
|
|
99
|
+
async function assertOutputsAvailable(outPaths, overwrite) {
|
|
100
|
+
if (overwrite) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const conflicts = [];
|
|
104
|
+
for (const outPath of outPaths) {
|
|
105
|
+
if (await pathExists(outPath)) {
|
|
106
|
+
conflicts.push(outPath);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (conflicts.length === 0) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const first = conflicts[0];
|
|
113
|
+
const message = conflicts.length === 1
|
|
114
|
+
? `Output already exists: ${first}. Pass --overwrite to replace it.`
|
|
115
|
+
: `Output already exists: ${first} (and ${conflicts.length - 1} more). Pass --overwrite to replace them.`;
|
|
116
|
+
throw new FrameyardError("OUTPUT_EXISTS", message, { details: { conflicts } });
|
|
117
|
+
}
|
|
82
118
|
async function publishFile(tempPath, outPath, overwrite) {
|
|
83
119
|
if (overwrite) {
|
|
84
120
|
await rename(tempPath, outPath);
|
|
@@ -100,21 +136,28 @@ async function publishFile(tempPath, outPath, overwrite) {
|
|
|
100
136
|
await rm(tempPath, { force: true });
|
|
101
137
|
}
|
|
102
138
|
}
|
|
103
|
-
async function
|
|
139
|
+
async function prepareComposition(options) {
|
|
104
140
|
throwIfAborted(options.signal);
|
|
105
141
|
const composition = compositionFor(options.project, options.compositionId);
|
|
106
142
|
const validation = await validateHtmlComposition({
|
|
107
143
|
projectRoot: options.project.root,
|
|
108
144
|
entry: composition.entry,
|
|
109
145
|
props: options.props,
|
|
146
|
+
...(options.project.format ? { format: options.project.format } : {}),
|
|
110
147
|
});
|
|
111
148
|
const staticDiagnostics = validation.diagnostics;
|
|
112
149
|
const staticErrors = staticDiagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
113
150
|
if (!validation.info || staticErrors.length > 0) {
|
|
114
151
|
const first = staticErrors[0];
|
|
152
|
+
if (!first && validation.formatUnresolved) {
|
|
153
|
+
throw new FrameyardError("COMPOSITION_FORMAT_UNRESOLVED", `Composition "${options.compositionId}": ${COMPOSITION_FORMAT_UNRESOLVED_MESSAGE}`, { diagnostics: validation.diagnostics });
|
|
154
|
+
}
|
|
115
155
|
throw new FrameyardError(first?.code ?? "COMPOSITION_INVALID", `Composition "${options.compositionId}": ${first?.message ?? "static validation failed."}`, { diagnostics: validation.diagnostics });
|
|
116
156
|
}
|
|
117
|
-
|
|
157
|
+
return { composition, staticInfo: validation.info, staticDiagnostics };
|
|
158
|
+
}
|
|
159
|
+
async function withPreparedSessions(options, prepared, resolveSessionCount, callback) {
|
|
160
|
+
const { composition, staticInfo, staticDiagnostics } = prepared;
|
|
118
161
|
const server = await startPreviewServer({ projectRoot: options.project.root, port: 0 });
|
|
119
162
|
const sessions = [];
|
|
120
163
|
try {
|
|
@@ -176,9 +219,6 @@ async function withSessions(options, resolveSessionCount, callback) {
|
|
|
176
219
|
}
|
|
177
220
|
}
|
|
178
221
|
}
|
|
179
|
-
async function withSession(options, callback) {
|
|
180
|
-
return withSessions(options, () => 1, async (sessions) => callback(sessions[0]));
|
|
181
|
-
}
|
|
182
222
|
function reportProgress(options, frame, total, startedAt) {
|
|
183
223
|
options.onProgress?.({ frame, total, elapsedMs: performance.now() - startedAt });
|
|
184
224
|
}
|
|
@@ -264,12 +304,31 @@ async function captureFramesInOrder(options) {
|
|
|
264
304
|
}
|
|
265
305
|
export async function checkComposition(options) {
|
|
266
306
|
const startedAt = performance.now();
|
|
267
|
-
|
|
268
|
-
|
|
307
|
+
const prepared = await prepareComposition(options);
|
|
308
|
+
return withPreparedSessions(options, prepared, () => 1, async (sessions) => {
|
|
309
|
+
const session = sessions[0];
|
|
310
|
+
const metadata = session.metadata;
|
|
311
|
+
if (metadata.static === true) {
|
|
312
|
+
throwIfAborted(options.signal);
|
|
313
|
+
await session.seek(0);
|
|
314
|
+
reportProgress(options, 1, 1, startedAt);
|
|
315
|
+
const diagnostics = mergeDiagnostics(prepared.staticDiagnostics, await session.readDiagnostics());
|
|
316
|
+
return {
|
|
317
|
+
kind: "static",
|
|
318
|
+
composition: options.compositionId,
|
|
319
|
+
entry: session.entry,
|
|
320
|
+
controller: session.controller,
|
|
321
|
+
metadata,
|
|
322
|
+
framesChecked: 1,
|
|
323
|
+
diagnostics,
|
|
324
|
+
elapsedMs: performance.now() - startedAt,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
const total = metadata.durationInFrames;
|
|
269
328
|
const audio = await inspectTimelineAudio({
|
|
270
329
|
projectRoot: options.project.root,
|
|
271
330
|
clips: timelineAudioClips(session),
|
|
272
|
-
fps:
|
|
331
|
+
fps: metadata.fps,
|
|
273
332
|
durationInFrames: total,
|
|
274
333
|
signal: options.signal,
|
|
275
334
|
});
|
|
@@ -285,10 +344,11 @@ export async function checkComposition(options) {
|
|
|
285
344
|
}
|
|
286
345
|
const diagnostics = mergeDiagnostics(await session.readDiagnostics(), audioDiagnostics(audio.warnings), transcriptDiagnostics(transcripts.warnings));
|
|
287
346
|
return {
|
|
347
|
+
kind: "animated",
|
|
288
348
|
composition: options.compositionId,
|
|
289
349
|
entry: session.entry,
|
|
290
350
|
controller: session.controller,
|
|
291
|
-
metadata
|
|
351
|
+
metadata,
|
|
292
352
|
framesChecked: total,
|
|
293
353
|
audio: { clips: audio.clips.length, details: audio.clips },
|
|
294
354
|
transcripts: {
|
|
@@ -300,63 +360,171 @@ export async function checkComposition(options) {
|
|
|
300
360
|
};
|
|
301
361
|
});
|
|
302
362
|
}
|
|
303
|
-
export async function
|
|
304
|
-
if (
|
|
305
|
-
throw new FrameyardError("
|
|
363
|
+
export async function captureStills(options) {
|
|
364
|
+
if (options.frames.length === 0) {
|
|
365
|
+
throw new FrameyardError("USAGE", "Still capture requires at least one frame.");
|
|
366
|
+
}
|
|
367
|
+
if (options.frames.length !== options.outPaths.length) {
|
|
368
|
+
throw new FrameyardError("USAGE", "Still frames and output paths must have the same length.");
|
|
306
369
|
}
|
|
307
|
-
|
|
308
|
-
|
|
370
|
+
for (const frame of options.frames) {
|
|
371
|
+
if (!Number.isInteger(frame) || frame < 0) {
|
|
372
|
+
throw new FrameyardError("FRAME_OUT_OF_RANGE", `--frame must be a non-negative integer; received ${frame}.`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (new Set(options.frames).size !== options.frames.length) {
|
|
376
|
+
throw new FrameyardError("USAGE", "Still frames must be unique.");
|
|
377
|
+
}
|
|
378
|
+
if (new Set(options.outPaths).size !== options.outPaths.length) {
|
|
379
|
+
throw new FrameyardError("USAGE", "Still output paths must be unique.");
|
|
380
|
+
}
|
|
381
|
+
for (const outPath of options.outPaths) {
|
|
382
|
+
if (path.extname(outPath).toLowerCase() !== ".png") {
|
|
383
|
+
throw new FrameyardError("USAGE", `Still output must use a .png extension: ${outPath}`);
|
|
384
|
+
}
|
|
309
385
|
}
|
|
310
386
|
const overwrite = options.overwrite ?? false;
|
|
311
|
-
await ensureOutputAvailable(options.outPath, overwrite);
|
|
312
387
|
const startedAt = performance.now();
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
388
|
+
const prepared = await prepareComposition(options);
|
|
389
|
+
const metadata = prepared.staticInfo.metadata;
|
|
390
|
+
const total = metadata.static === true ? 1 : metadata.durationInFrames;
|
|
391
|
+
const invalidFrames = options.frames.filter((frame) => frame >= total);
|
|
392
|
+
if (invalidFrames.length > 0) {
|
|
393
|
+
const first = invalidFrames[0];
|
|
394
|
+
throw new FrameyardError("FRAME_OUT_OF_RANGE", `Frame ${first} is outside the valid range 0..${total - 1}.`, {
|
|
395
|
+
details: { invalidFrames, validRange: { min: 0, max: total - 1 } },
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
await assertOutputsAvailable(options.outPaths, overwrite);
|
|
399
|
+
for (const outPath of options.outPaths) {
|
|
400
|
+
await mkdir(path.dirname(outPath), { recursive: true });
|
|
401
|
+
}
|
|
402
|
+
return withPreparedSessions(options, prepared, () => 1, async (sessions) => {
|
|
403
|
+
const session = sessions[0];
|
|
318
404
|
const transcripts = await inspectTranscriptClips({
|
|
319
405
|
projectRoot: options.project.root,
|
|
320
406
|
clips: session.transcriptClips,
|
|
321
407
|
signal: options.signal,
|
|
322
408
|
});
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
409
|
+
const tempPaths = options.outPaths.map((outPath) => path.join(path.dirname(outPath), `.${path.basename(outPath)}.frameyard-${process.pid}-${randomUUID()}.tmp`));
|
|
410
|
+
try {
|
|
411
|
+
for (const [index, frame] of options.frames.entries()) {
|
|
412
|
+
throwIfAborted(options.signal);
|
|
413
|
+
try {
|
|
414
|
+
await session.seek(frame);
|
|
415
|
+
const png = await session.capture();
|
|
416
|
+
await writeFile(tempPaths[index], png);
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
throw withBatchContext(error, options.frames, frame);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
await assertOutputsAvailable(options.outPaths, overwrite);
|
|
423
|
+
const diagnostics = mergeDiagnostics(metadata.static === true ? prepared.staticDiagnostics : [], await session.readDiagnostics(), transcriptDiagnostics(transcripts.warnings));
|
|
424
|
+
for (const [index, outPath] of options.outPaths.entries()) {
|
|
425
|
+
await publishFile(tempPaths[index], outPath, overwrite);
|
|
426
|
+
}
|
|
427
|
+
const frames = options.frames.map((frame, index) => ({
|
|
428
|
+
frame,
|
|
429
|
+
path: options.outPaths[index],
|
|
430
|
+
}));
|
|
431
|
+
const first = frames[0];
|
|
432
|
+
return {
|
|
433
|
+
composition: options.compositionId,
|
|
434
|
+
entry: session.entry,
|
|
435
|
+
controller: session.controller,
|
|
436
|
+
...(frames.length === 1 && first !== undefined ? { frame: first.frame, path: first.path } : {}),
|
|
437
|
+
frames,
|
|
438
|
+
width: session.metadata.width,
|
|
439
|
+
height: session.metadata.height,
|
|
440
|
+
diagnostics,
|
|
441
|
+
elapsedMs: performance.now() - startedAt,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
finally {
|
|
445
|
+
await Promise.all(tempPaths.map((tempPath) => rm(tempPath, { force: true })));
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
export async function captureStill(options) {
|
|
450
|
+
const { frame, outPath, ...rest } = options;
|
|
451
|
+
return captureStills({
|
|
452
|
+
...rest,
|
|
453
|
+
frames: [frame],
|
|
454
|
+
outPaths: [outPath],
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
async function renderStaticComposition(options, prepared, startedAt) {
|
|
458
|
+
if (options.container !== undefined ||
|
|
459
|
+
options.codec !== undefined ||
|
|
460
|
+
options.encoder !== undefined ||
|
|
461
|
+
options.concurrency !== undefined) {
|
|
462
|
+
throw new FrameyardError("USAGE", "Static compositions render to PNG; --container, --codec, --encoder, and --concurrency are not valid.");
|
|
463
|
+
}
|
|
464
|
+
const metadata = prepared.staticInfo.metadata;
|
|
465
|
+
if (metadata.static !== true) {
|
|
466
|
+
throw new FrameyardError("INTERNAL", "renderStaticComposition received an animated composition.");
|
|
467
|
+
}
|
|
468
|
+
const outPath = options.outPath ?? path.join(options.project.root, "out", `${options.compositionId}.png`);
|
|
469
|
+
if (path.extname(outPath).toLowerCase() !== ".png") {
|
|
470
|
+
throw new FrameyardError("USAGE", `Static compositions render to a .png file: ${outPath}`);
|
|
471
|
+
}
|
|
472
|
+
const overwrite = options.overwrite ?? false;
|
|
473
|
+
await ensureOutputAvailable(outPath, overwrite);
|
|
474
|
+
return withPreparedSessions(options, prepared, () => 1, async (sessions) => {
|
|
475
|
+
const session = sessions[0];
|
|
476
|
+
const tempPath = path.join(path.dirname(outPath), `.${path.basename(outPath)}.frameyard-${process.pid}-${randomUUID()}.tmp`);
|
|
328
477
|
try {
|
|
478
|
+
throwIfAborted(options.signal);
|
|
479
|
+
await session.seek(0);
|
|
480
|
+
const png = await session.capture();
|
|
329
481
|
await writeFile(tempPath, png);
|
|
330
|
-
await
|
|
331
|
-
await publishFile(tempPath,
|
|
482
|
+
await assertOutputsAvailable([outPath], overwrite);
|
|
483
|
+
await publishFile(tempPath, outPath, overwrite);
|
|
484
|
+
reportProgress(options, 1, 1, startedAt);
|
|
485
|
+
const diagnostics = mergeDiagnostics(prepared.staticDiagnostics, await session.readDiagnostics());
|
|
486
|
+
return {
|
|
487
|
+
kind: "image",
|
|
488
|
+
composition: options.compositionId,
|
|
489
|
+
entry: session.entry,
|
|
490
|
+
controller: session.controller,
|
|
491
|
+
path: outPath,
|
|
492
|
+
width: metadata.width,
|
|
493
|
+
height: metadata.height,
|
|
494
|
+
bytes: (await stat(outPath)).size,
|
|
495
|
+
diagnostics,
|
|
496
|
+
elapsedMs: performance.now() - startedAt,
|
|
497
|
+
};
|
|
332
498
|
}
|
|
333
499
|
finally {
|
|
334
500
|
await rm(tempPath, { force: true });
|
|
335
501
|
}
|
|
336
|
-
return {
|
|
337
|
-
composition: options.compositionId,
|
|
338
|
-
entry: session.entry,
|
|
339
|
-
controller: session.controller,
|
|
340
|
-
frame: options.frame,
|
|
341
|
-
path: options.outPath,
|
|
342
|
-
width: session.metadata.width,
|
|
343
|
-
height: session.metadata.height,
|
|
344
|
-
diagnostics,
|
|
345
|
-
elapsedMs: performance.now() - startedAt,
|
|
346
|
-
};
|
|
347
502
|
});
|
|
348
503
|
}
|
|
349
504
|
export async function renderComposition(options) {
|
|
350
|
-
|
|
351
|
-
|
|
505
|
+
const startedAt = performance.now();
|
|
506
|
+
const prepared = await prepareComposition(options);
|
|
507
|
+
if (prepared.staticInfo.metadata.static === true) {
|
|
508
|
+
return renderStaticComposition(options, prepared, startedAt);
|
|
509
|
+
}
|
|
510
|
+
const container = options.container ?? "mp4";
|
|
511
|
+
const codec = options.codec ?? defaultCodecForContainer(container);
|
|
512
|
+
assertContainerCodec(container, codec);
|
|
513
|
+
const extension = `.${CONTAINER_EXTENSION[container]}`;
|
|
514
|
+
const outPath = options.outPath ?? path.join(options.project.root, "out", `${options.compositionId}${extension}`);
|
|
515
|
+
if (path.extname(outPath).toLowerCase() !== extension) {
|
|
516
|
+
throw new FrameyardError("USAGE", `Render output for --container ${container} must use a ${extension} extension: ${outPath}`);
|
|
352
517
|
}
|
|
353
518
|
const requestedConcurrency = validateRenderConcurrency(options.concurrency);
|
|
354
519
|
const overwrite = options.overwrite ?? false;
|
|
355
|
-
await ensureOutputAvailable(
|
|
356
|
-
await assertFfmpegCapabilities(options.signal);
|
|
357
|
-
const
|
|
358
|
-
return
|
|
520
|
+
await ensureOutputAvailable(outPath, overwrite);
|
|
521
|
+
await assertFfmpegCapabilities(options.signal, codec);
|
|
522
|
+
const encoderName = await resolveVideoEncoder(codec, options.encoder, options.signal);
|
|
523
|
+
return withPreparedSessions(options, prepared, (first) => (first.metadata.static === true ? 1 : Math.min(requestedConcurrency, first.metadata.durationInFrames)), async (sessions) => {
|
|
359
524
|
const session = sessions[0];
|
|
525
|
+
if (session.metadata.static === true) {
|
|
526
|
+
throw new FrameyardError("INTERNAL", "An animated render opened a static composition session.");
|
|
527
|
+
}
|
|
360
528
|
const { width, height, fps, durationInFrames } = session.metadata;
|
|
361
529
|
const concurrency = sessions.length;
|
|
362
530
|
if (width % 2 !== 0 || height % 2 !== 0) {
|
|
@@ -377,7 +545,7 @@ export async function renderComposition(options) {
|
|
|
377
545
|
signal: options.signal,
|
|
378
546
|
});
|
|
379
547
|
const scratchDir = await mkdtemp(path.join(tmpdir(), "frameyard-render-"));
|
|
380
|
-
const tempOut = path.join(path.dirname(
|
|
548
|
+
const tempOut = path.join(path.dirname(outPath), `.${path.basename(outPath)}.frameyard-${process.pid}-${randomUUID()}.mp4`);
|
|
381
549
|
let encoder;
|
|
382
550
|
let completed = false;
|
|
383
551
|
try {
|
|
@@ -403,6 +571,9 @@ export async function renderComposition(options) {
|
|
|
403
571
|
fps,
|
|
404
572
|
frames: total,
|
|
405
573
|
durationSeconds,
|
|
574
|
+
encoder: encoderName,
|
|
575
|
+
codec,
|
|
576
|
+
container,
|
|
406
577
|
...(audioPath !== undefined ? { audioPath } : {}),
|
|
407
578
|
signal: options.signal,
|
|
408
579
|
});
|
|
@@ -428,17 +599,18 @@ export async function renderComposition(options) {
|
|
|
428
599
|
}
|
|
429
600
|
await encoder.finish();
|
|
430
601
|
encoder = undefined;
|
|
431
|
-
if (!overwrite && (await pathExists(
|
|
432
|
-
throw new FrameyardError("OUTPUT_EXISTS", `Output already exists: ${
|
|
602
|
+
if (!overwrite && (await pathExists(outPath))) {
|
|
603
|
+
throw new FrameyardError("OUTPUT_EXISTS", `Output already exists: ${outPath}. Pass --overwrite to replace it.`);
|
|
433
604
|
}
|
|
434
|
-
await publishFile(tempOut,
|
|
605
|
+
await publishFile(tempOut, outPath, overwrite);
|
|
435
606
|
completed = true;
|
|
436
607
|
const diagnostics = mergeDiagnostics(await readSessionDiagnostics(sessions), audioDiagnostics(audio.warnings), transcriptDiagnostics(transcripts.warnings));
|
|
437
608
|
return {
|
|
609
|
+
kind: "video",
|
|
438
610
|
composition: options.compositionId,
|
|
439
611
|
entry: session.entry,
|
|
440
612
|
controller: session.controller,
|
|
441
|
-
path:
|
|
613
|
+
path: outPath,
|
|
442
614
|
width,
|
|
443
615
|
height,
|
|
444
616
|
fps,
|
|
@@ -446,7 +618,10 @@ export async function renderComposition(options) {
|
|
|
446
618
|
durationSeconds,
|
|
447
619
|
framesRendered: total,
|
|
448
620
|
concurrency,
|
|
449
|
-
|
|
621
|
+
codec,
|
|
622
|
+
container,
|
|
623
|
+
encoder: encoderName,
|
|
624
|
+
bytes: (await stat(outPath)).size,
|
|
450
625
|
audio: { clips: audio.clips.length },
|
|
451
626
|
transcripts: {
|
|
452
627
|
clips: transcripts.clips.length,
|