frameyard 0.5.0 → 0.6.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 +55 -0
- package/GLOSSARY.md +152 -24
- package/README.md +94 -29
- package/dist/cli.js +111 -14
- package/dist/cli.js.map +1 -1
- package/dist/contracts.d.ts +12 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.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/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/preview/main.js +89 -11
- package/dist/preview/main.js.map +1 -1
- package/dist/preview/timeline-data.d.ts +85 -0
- package/dist/preview/timeline-data.d.ts.map +1 -0
- package/dist/preview/timeline-data.js +2 -0
- package/dist/preview/timeline-data.js.map +1 -0
- package/dist/preview/timeline-host.d.ts +19 -0
- package/dist/preview/timeline-host.d.ts.map +1 -0
- package/dist/preview/timeline-host.js +297 -0
- package/dist/preview/timeline-host.js.map +1 -0
- package/dist/preview/timeline-main.d.ts +17 -0
- package/dist/preview/timeline-main.d.ts.map +1 -0
- package/dist/preview/timeline-main.js +364 -0
- package/dist/preview/timeline-main.js.map +1 -0
- package/dist/project.d.ts +16 -1
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +81 -26
- package/dist/project.js.map +1 -1
- package/dist/renderer/browser.d.ts +2 -0
- package/dist/renderer/browser.d.ts.map +1 -1
- package/dist/renderer/browser.js +6 -1
- package/dist/renderer/browser.js.map +1 -1
- package/dist/renderer/ffmpeg.d.ts +61 -0
- package/dist/renderer/ffmpeg.d.ts.map +1 -1
- package/dist/renderer/ffmpeg.js +198 -0
- package/dist/renderer/ffmpeg.js.map +1 -1
- package/dist/renderer/timeline.d.ts +109 -0
- package/dist/renderer/timeline.d.ts.map +1 -0
- package/dist/renderer/timeline.js +984 -0
- package/dist/renderer/timeline.js.map +1 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +89 -24
- package/dist/scaffold.js.map +1 -1
- package/dist/server/preview-html.d.ts.map +1 -1
- package/dist/server/preview-html.js +15 -1
- package/dist/server/preview-html.js.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +39 -1
- package/dist/server/server.js.map +1 -1
- package/dist/server/timeline-preview-data.d.ts +4 -0
- package/dist/server/timeline-preview-data.d.ts.map +1 -0
- package/dist/server/timeline-preview-data.js +97 -0
- package/dist/server/timeline-preview-data.js.map +1 -0
- package/dist/server/timeline-preview-html.d.ts +4 -0
- package/dist/server/timeline-preview-html.d.ts.map +1 -0
- package/dist/server/timeline-preview-html.js +227 -0
- package/dist/server/timeline-preview-html.js.map +1 -0
- package/dist/timeline/plan.d.ts +32 -0
- package/dist/timeline/plan.d.ts.map +1 -0
- package/dist/timeline/plan.js +68 -0
- package/dist/timeline/plan.js.map +1 -0
- package/dist/timeline/resolve.d.ts +60 -0
- package/dist/timeline/resolve.d.ts.map +1 -0
- package/dist/timeline/resolve.js +523 -0
- package/dist/timeline/resolve.js.map +1 -0
- package/dist/timeline/schema.d.ts +71 -0
- package/dist/timeline/schema.d.ts.map +1 -0
- package/dist/timeline/schema.js +425 -0
- package/dist/timeline/schema.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/adr/0002-make-frame-and-props-the-only-animation-state.md +2 -0
- package/docs/adr/0005-keep-parallel-rendering-opt-in-and-frame-ordered.md +2 -0
- package/docs/adr/0007-make-html-documents-the-composition-unit.md +1 -1
- package/docs/adr/0009-composite-production-frames-in-software.md +1 -1
- package/docs/adr/0010-make-timelines-the-render-unit.md +24 -0
- package/docs/adr/0011-compose-timeline-frames-in-a-live-browser-host.md +22 -0
- package/docs/adr/0012-render-timelines-with-segmented-lossless-clip-intermediates.md +31 -0
- package/docs/v0.1-design.md +135 -86
- package/examples/demo/README.md +10 -3
- package/examples/demo/timelines/demo.timeline.json +12 -0
- package/examples/demo/video.config.js +4 -0
- package/examples/video/README.md +50 -6
- package/examples/video/compositions/video-overlay.html +350 -0
- package/examples/video/props/video-timeline.json +10 -0
- package/examples/video/timelines/video-timeline-mp4.timeline.json +44 -0
- package/examples/video/timelines/video-timeline.timeline.json +44 -0
- package/examples/video/video.config.js +6 -0
- package/package.json +1 -1
- package/skills/frameyard/SKILL.md +17 -13
- package/skills/frameyard/references/timelines.md +141 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { assertJsonSerializable, isPlainObject, } from "../contracts.js";
|
|
2
|
+
const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
3
|
+
const SCHEME = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
|
|
4
|
+
function isNonEmptyString(value) {
|
|
5
|
+
return typeof value === "string" && value.length > 0;
|
|
6
|
+
}
|
|
7
|
+
function isLocalPath(value) {
|
|
8
|
+
return (isNonEmptyString(value) &&
|
|
9
|
+
!SCHEME.test(value) &&
|
|
10
|
+
!value.startsWith("/") &&
|
|
11
|
+
!value.startsWith("\\") &&
|
|
12
|
+
!value.split(/[\\/]/).includes(".."));
|
|
13
|
+
}
|
|
14
|
+
function isFiniteNumber(value) {
|
|
15
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
16
|
+
}
|
|
17
|
+
function isInteger(value) {
|
|
18
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
19
|
+
}
|
|
20
|
+
function unknownKeys(value, allowed) {
|
|
21
|
+
return Object.keys(value).filter((key) => !allowed.includes(key));
|
|
22
|
+
}
|
|
23
|
+
function jsonPath(base, key) {
|
|
24
|
+
return typeof key === "number" ? `${base}[${key}]` : `${base}.${key}`;
|
|
25
|
+
}
|
|
26
|
+
function makeDiagnostic(context, severity, code, message, path, details = {}) {
|
|
27
|
+
const merged = {
|
|
28
|
+
...(context.path !== undefined ? { path: context.path } : {}),
|
|
29
|
+
...(context.timeline !== undefined ? { timeline: context.timeline } : {}),
|
|
30
|
+
jsonPath: path,
|
|
31
|
+
...details,
|
|
32
|
+
};
|
|
33
|
+
return { severity, code, message, details: merged };
|
|
34
|
+
}
|
|
35
|
+
function parseTransform(value, path, add) {
|
|
36
|
+
const defaults = { fit: "contain", x: 0, y: 0, scale: 1, rotation: 0, opacity: 1 };
|
|
37
|
+
if (!isPlainObject(value)) {
|
|
38
|
+
add("TIMELINE_CLIP_INVALID", "Clip transform must be an object.", path);
|
|
39
|
+
return defaults;
|
|
40
|
+
}
|
|
41
|
+
for (const key of unknownKeys(value, ["fit", "x", "y", "width", "height", "scale", "rotation", "opacity"])) {
|
|
42
|
+
add("TIMELINE_CLIP_INVALID", `Unknown clip transform field "${key}".`, jsonPath(path, key));
|
|
43
|
+
}
|
|
44
|
+
const transform = { ...defaults };
|
|
45
|
+
if (value.fit !== undefined) {
|
|
46
|
+
if (value.fit !== "contain" && value.fit !== "cover" && value.fit !== "stretch") {
|
|
47
|
+
add("TIMELINE_CLIP_INVALID", 'Clip transform.fit must be "contain", "cover", or "stretch".', jsonPath(path, "fit"));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
transform.fit = value.fit;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const width = value.width;
|
|
54
|
+
const height = value.height;
|
|
55
|
+
if ((width === undefined) !== (height === undefined)) {
|
|
56
|
+
add("TIMELINE_CLIP_INVALID", "Clip transform.width and transform.height must be provided together.", jsonPath(path, width === undefined ? "width" : "height"));
|
|
57
|
+
}
|
|
58
|
+
for (const [key, field] of [
|
|
59
|
+
["width", width],
|
|
60
|
+
["height", height],
|
|
61
|
+
]) {
|
|
62
|
+
if (field === undefined) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (!isInteger(field) || field <= 0) {
|
|
66
|
+
add("TIMELINE_CLIP_INVALID", `Clip transform.${key} must be a positive integer.`, jsonPath(path, key));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (width !== undefined && height !== undefined && value.scale !== undefined) {
|
|
70
|
+
add("TIMELINE_CLIP_INVALID", "Clip transform.scale cannot be combined with transform.width and transform.height.", jsonPath(path, "scale"));
|
|
71
|
+
}
|
|
72
|
+
if (isInteger(width) && width > 0 && isInteger(height) && height > 0) {
|
|
73
|
+
transform.width = width;
|
|
74
|
+
transform.height = height;
|
|
75
|
+
}
|
|
76
|
+
for (const key of ["x", "y", "scale", "rotation", "opacity"]) {
|
|
77
|
+
const field = value[key];
|
|
78
|
+
if (field === undefined) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (!isFiniteNumber(field)) {
|
|
82
|
+
add("TIMELINE_CLIP_INVALID", `Clip transform.${key} must be a finite number.`, jsonPath(path, key));
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (key === "scale" && field < 0) {
|
|
86
|
+
add("TIMELINE_CLIP_INVALID", "Clip transform.scale must be zero or greater.", jsonPath(path, key));
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (key === "opacity" && (field < 0 || field > 1)) {
|
|
90
|
+
add("TIMELINE_CLIP_INVALID", "Clip transform.opacity must be between 0 and 1.", jsonPath(path, key));
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
transform[key] = field;
|
|
94
|
+
}
|
|
95
|
+
return transform;
|
|
96
|
+
}
|
|
97
|
+
function parseAudio(value, path, add) {
|
|
98
|
+
const defaults = { muted: false, gain: 1 };
|
|
99
|
+
if (!isPlainObject(value)) {
|
|
100
|
+
add("TIMELINE_CLIP_INVALID", "Clip audio must be an object.", path);
|
|
101
|
+
return defaults;
|
|
102
|
+
}
|
|
103
|
+
for (const key of unknownKeys(value, ["muted", "gain"])) {
|
|
104
|
+
add("TIMELINE_CLIP_INVALID", `Unknown clip audio field "${key}".`, jsonPath(path, key));
|
|
105
|
+
}
|
|
106
|
+
if (value.muted !== undefined) {
|
|
107
|
+
if (typeof value.muted !== "boolean") {
|
|
108
|
+
add("TIMELINE_CLIP_INVALID", "Clip audio.muted must be a boolean.", jsonPath(path, "muted"));
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
defaults.muted = value.muted;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (value.gain !== undefined) {
|
|
115
|
+
if (!isFiniteNumber(value.gain) || value.gain < 0 || value.gain > 1) {
|
|
116
|
+
add("TIMELINE_CLIP_INVALID", "Clip audio.gain must be between 0 and 1.", jsonPath(path, "gain"));
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
defaults.gain = value.gain;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return defaults;
|
|
123
|
+
}
|
|
124
|
+
function parseSource(value, path, add) {
|
|
125
|
+
if (!isPlainObject(value)) {
|
|
126
|
+
add("TIMELINE_CLIP_INVALID", "Clip source must be an object.", path);
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const kind = value.kind;
|
|
130
|
+
if (kind === "composition" || kind === "timeline") {
|
|
131
|
+
for (const key of unknownKeys(value, ["kind", "id"])) {
|
|
132
|
+
add("TIMELINE_CLIP_INVALID", `Unknown ${kind} source field "${key}".`, jsonPath(path, key));
|
|
133
|
+
}
|
|
134
|
+
if (!isNonEmptyString(value.id) || !ID_PATTERN.test(value.id)) {
|
|
135
|
+
add("TIMELINE_CLIP_INVALID", `Clip ${kind} source.id must be a valid project id.`, jsonPath(path, "id"));
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
return kind === "composition" ? { kind, id: value.id } : { kind, id: value.id };
|
|
139
|
+
}
|
|
140
|
+
if (kind === "video" || kind === "image" || kind === "audio") {
|
|
141
|
+
for (const key of unknownKeys(value, ["kind", "src"])) {
|
|
142
|
+
add("TIMELINE_CLIP_INVALID", `Unknown ${kind} source field "${key}".`, jsonPath(path, key));
|
|
143
|
+
}
|
|
144
|
+
if (!isLocalPath(value.src)) {
|
|
145
|
+
add("TIMELINE_CLIP_INVALID", `Clip ${kind} source.src must be a local project-relative path.`, jsonPath(path, "src"));
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
return { kind, src: value.src };
|
|
149
|
+
}
|
|
150
|
+
add("TIMELINE_CLIP_INVALID", 'Clip source.kind must be "composition", "timeline", "video", "image", or "audio".', jsonPath(path, "kind"));
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
function isVisualSource(source) {
|
|
154
|
+
return (source.kind === "composition" || source.kind === "timeline" || source.kind === "video" || source.kind === "image");
|
|
155
|
+
}
|
|
156
|
+
function isAudioBearingSource(source) {
|
|
157
|
+
return source.kind !== "image";
|
|
158
|
+
}
|
|
159
|
+
/** Parses and normalizes a timeline JSON document without touching the filesystem. */
|
|
160
|
+
export function parseTimelineDocument(value, context = {}) {
|
|
161
|
+
const diagnostics = [];
|
|
162
|
+
const add = (code, message, path, details = {}) => {
|
|
163
|
+
diagnostics.push(makeDiagnostic(context, "error", code, message, path, details));
|
|
164
|
+
};
|
|
165
|
+
if (!isPlainObject(value)) {
|
|
166
|
+
add("TIMELINE_INVALID", "Timeline document must be a JSON object.", "$");
|
|
167
|
+
return { timeline: null, diagnostics };
|
|
168
|
+
}
|
|
169
|
+
for (const key of unknownKeys(value, ["version", "name", "durationInFrames", "background", "tracks", "clips"])) {
|
|
170
|
+
add("TIMELINE_INVALID", `Unknown timeline field "${key}".`, jsonPath("$", key));
|
|
171
|
+
}
|
|
172
|
+
if (value.version !== 1) {
|
|
173
|
+
add("TIMELINE_INVALID", "Timeline version must be 1.", "$.version");
|
|
174
|
+
}
|
|
175
|
+
if (value.name !== undefined && !isNonEmptyString(value.name)) {
|
|
176
|
+
add("TIMELINE_INVALID", "Timeline name must be a non-empty string.", "$.name");
|
|
177
|
+
}
|
|
178
|
+
if (value.background !== undefined && !isNonEmptyString(value.background)) {
|
|
179
|
+
add("TIMELINE_INVALID", "Timeline background must be a non-empty string.", "$.background");
|
|
180
|
+
}
|
|
181
|
+
if (value.durationInFrames !== undefined) {
|
|
182
|
+
if (!isInteger(value.durationInFrames) || value.durationInFrames <= 0) {
|
|
183
|
+
add("TIMELINE_DURATION_INVALID", "Timeline durationInFrames must be a positive integer.", "$.durationInFrames");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (!Array.isArray(value.tracks) || value.tracks.length === 0) {
|
|
187
|
+
add("TIMELINE_INVALID", "Timeline tracks must be a non-empty array.", "$.tracks");
|
|
188
|
+
return { timeline: null, diagnostics };
|
|
189
|
+
}
|
|
190
|
+
if (!Array.isArray(value.clips) || value.clips.length === 0) {
|
|
191
|
+
add("TIMELINE_INVALID", "Timeline clips must be a non-empty array.", "$.clips");
|
|
192
|
+
return { timeline: null, diagnostics };
|
|
193
|
+
}
|
|
194
|
+
const tracks = [];
|
|
195
|
+
const trackById = new Map();
|
|
196
|
+
for (const [index, rawTrack] of value.tracks.entries()) {
|
|
197
|
+
const path = `$.tracks[${index}]`;
|
|
198
|
+
if (!isPlainObject(rawTrack)) {
|
|
199
|
+
add("TIMELINE_TRACK_INVALID", "Track must be an object.", path);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
for (const key of unknownKeys(rawTrack, ["id", "kind", "name", "gain", "muted"])) {
|
|
203
|
+
add("TIMELINE_TRACK_INVALID", `Unknown track field "${key}".`, jsonPath(path, key));
|
|
204
|
+
}
|
|
205
|
+
if (!isNonEmptyString(rawTrack.id) || !ID_PATTERN.test(rawTrack.id)) {
|
|
206
|
+
add("TIMELINE_TRACK_INVALID", "Track id must be a valid project id.", jsonPath(path, "id"));
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (trackById.has(rawTrack.id)) {
|
|
210
|
+
add("TIMELINE_TRACK_INVALID", `Duplicate track id "${rawTrack.id}".`, jsonPath(path, "id"), {
|
|
211
|
+
track: rawTrack.id,
|
|
212
|
+
});
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (rawTrack.kind !== "video" && rawTrack.kind !== "audio") {
|
|
216
|
+
add("TIMELINE_TRACK_INVALID", 'Track kind must be "video" or "audio".', jsonPath(path, "kind"), {
|
|
217
|
+
track: rawTrack.id,
|
|
218
|
+
});
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (rawTrack.name !== undefined && !isNonEmptyString(rawTrack.name)) {
|
|
222
|
+
add("TIMELINE_TRACK_INVALID", "Track name must be a non-empty string.", jsonPath(path, "name"), {
|
|
223
|
+
track: rawTrack.id,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
if (rawTrack.kind === "video" && (rawTrack.gain !== undefined || rawTrack.muted !== undefined)) {
|
|
227
|
+
add("TIMELINE_TRACK_INVALID", "Video tracks do not support gain or muted.", path, { track: rawTrack.id });
|
|
228
|
+
}
|
|
229
|
+
if (rawTrack.gain !== undefined && (!isFiniteNumber(rawTrack.gain) || rawTrack.gain < 0 || rawTrack.gain > 1)) {
|
|
230
|
+
add("TIMELINE_TRACK_INVALID", "Audio track gain must be between 0 and 1.", jsonPath(path, "gain"), {
|
|
231
|
+
track: rawTrack.id,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
if (rawTrack.muted !== undefined && typeof rawTrack.muted !== "boolean") {
|
|
235
|
+
add("TIMELINE_TRACK_INVALID", "Audio track muted must be a boolean.", jsonPath(path, "muted"), {
|
|
236
|
+
track: rawTrack.id,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const trackId = rawTrack.id;
|
|
240
|
+
const trackKind = rawTrack.kind;
|
|
241
|
+
const trackName = isNonEmptyString(rawTrack.name) ? rawTrack.name : undefined;
|
|
242
|
+
const trackGain = trackKind === "audio" && isFiniteNumber(rawTrack.gain) ? rawTrack.gain : undefined;
|
|
243
|
+
const trackMuted = trackKind === "audio" && typeof rawTrack.muted === "boolean" ? rawTrack.muted : undefined;
|
|
244
|
+
const track = {
|
|
245
|
+
id: trackId,
|
|
246
|
+
kind: trackKind,
|
|
247
|
+
...(trackName !== undefined ? { name: trackName } : {}),
|
|
248
|
+
...(trackKind === "audio" ? { gain: trackGain ?? 1 } : {}),
|
|
249
|
+
...(trackKind === "audio" ? { muted: trackMuted ?? false } : {}),
|
|
250
|
+
};
|
|
251
|
+
tracks.push(track);
|
|
252
|
+
trackById.set(track.id, track);
|
|
253
|
+
}
|
|
254
|
+
const clips = [];
|
|
255
|
+
const clipIds = new Set();
|
|
256
|
+
for (const [index, rawClip] of value.clips.entries()) {
|
|
257
|
+
const path = `$.clips[${index}]`;
|
|
258
|
+
if (!isPlainObject(rawClip)) {
|
|
259
|
+
add("TIMELINE_CLIP_INVALID", "Clip must be an object.", path);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const clipId = isNonEmptyString(rawClip.id) ? rawClip.id : null;
|
|
263
|
+
const details = clipId ? { clip: clipId } : {};
|
|
264
|
+
for (const key of unknownKeys(rawClip, [
|
|
265
|
+
"id",
|
|
266
|
+
"track",
|
|
267
|
+
"source",
|
|
268
|
+
"start",
|
|
269
|
+
"duration",
|
|
270
|
+
"sourceIn",
|
|
271
|
+
"props",
|
|
272
|
+
"transform",
|
|
273
|
+
"audio",
|
|
274
|
+
])) {
|
|
275
|
+
add("TIMELINE_CLIP_INVALID", `Unknown clip field "${key}".`, jsonPath(path, key), details);
|
|
276
|
+
}
|
|
277
|
+
if (!clipId || !ID_PATTERN.test(clipId)) {
|
|
278
|
+
add("TIMELINE_CLIP_INVALID", "Clip id must be a valid project id.", jsonPath(path, "id"), details);
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (clipIds.has(clipId)) {
|
|
282
|
+
add("TIMELINE_CLIP_INVALID", `Duplicate clip id "${clipId}".`, jsonPath(path, "id"), details);
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
clipIds.add(clipId);
|
|
286
|
+
const trackId = isNonEmptyString(rawClip.track) ? rawClip.track : null;
|
|
287
|
+
if (!trackId || !trackById.has(trackId)) {
|
|
288
|
+
add("TIMELINE_CLIP_INVALID", `Clip track must reference a declared track.`, jsonPath(path, "track"), details);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const track = trackById.get(trackId);
|
|
292
|
+
const clipDetails = { ...details, track: trackId };
|
|
293
|
+
const source = parseSource(rawClip.source, jsonPath(path, "source"), (code, message, errorPath, errorDetails) => {
|
|
294
|
+
add(code, message, errorPath, { ...clipDetails, ...errorDetails });
|
|
295
|
+
});
|
|
296
|
+
if (!source) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (!isInteger(rawClip.start) || rawClip.start < 0) {
|
|
300
|
+
add("TIMELINE_CLIP_INVALID", "Clip start must be a non-negative integer.", jsonPath(path, "start"), clipDetails);
|
|
301
|
+
}
|
|
302
|
+
const start = isInteger(rawClip.start) && rawClip.start >= 0 ? rawClip.start : 0;
|
|
303
|
+
if (rawClip.duration !== undefined && (!isInteger(rawClip.duration) || rawClip.duration <= 0)) {
|
|
304
|
+
add("TIMELINE_CLIP_INVALID", "Clip duration must be a positive integer.", jsonPath(path, "duration"), clipDetails);
|
|
305
|
+
}
|
|
306
|
+
if (source.kind === "image" && rawClip.duration === undefined) {
|
|
307
|
+
add("TIMELINE_CLIP_INVALID", "Image clips must declare a duration.", jsonPath(path, "duration"), clipDetails);
|
|
308
|
+
}
|
|
309
|
+
const duration = rawClip.duration === undefined
|
|
310
|
+
? undefined
|
|
311
|
+
: isInteger(rawClip.duration) && rawClip.duration > 0
|
|
312
|
+
? rawClip.duration
|
|
313
|
+
: undefined;
|
|
314
|
+
if (rawClip.sourceIn !== undefined && (!isInteger(rawClip.sourceIn) || rawClip.sourceIn < 0)) {
|
|
315
|
+
add("TIMELINE_CLIP_INVALID", "Clip sourceIn must be a non-negative integer.", jsonPath(path, "sourceIn"), clipDetails);
|
|
316
|
+
}
|
|
317
|
+
const sourceIn = isInteger(rawClip.sourceIn) && rawClip.sourceIn >= 0 ? rawClip.sourceIn : 0;
|
|
318
|
+
if (source.kind === "image" && sourceIn !== 0) {
|
|
319
|
+
add("TIMELINE_CLIP_INVALID", "Image clips must use sourceIn 0.", jsonPath(path, "sourceIn"), clipDetails);
|
|
320
|
+
}
|
|
321
|
+
let props;
|
|
322
|
+
if (rawClip.props !== undefined) {
|
|
323
|
+
if (source.kind !== "composition") {
|
|
324
|
+
add("TIMELINE_CLIP_INVALID", "props is only valid on composition clips.", jsonPath(path, "props"), clipDetails);
|
|
325
|
+
}
|
|
326
|
+
else if (!isPlainObject(rawClip.props)) {
|
|
327
|
+
add("TIMELINE_CLIP_INVALID", "Clip props must be a plain JSON object.", jsonPath(path, "props"), clipDetails);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
try {
|
|
331
|
+
assertJsonSerializable(rawClip.props, "props");
|
|
332
|
+
props = rawClip.props;
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
add("TIMELINE_CLIP_INVALID", `Clip props are not JSON-serializable: ${error instanceof Error ? error.message : String(error)}`, jsonPath(path, "props"), clipDetails);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const visual = isVisualSource(source);
|
|
340
|
+
const audioBearing = isAudioBearingSource(source);
|
|
341
|
+
let transform;
|
|
342
|
+
if (rawClip.transform !== undefined) {
|
|
343
|
+
if (!visual) {
|
|
344
|
+
add("TIMELINE_CLIP_INVALID", "transform is only valid on visual clips.", jsonPath(path, "transform"), clipDetails);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
transform = parseTransform(rawClip.transform, jsonPath(path, "transform"), (code, message, errorPath, errorDetails) => {
|
|
348
|
+
add(code, message, errorPath, { ...clipDetails, ...errorDetails });
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
else if (visual) {
|
|
353
|
+
transform = { fit: "contain", x: 0, y: 0, scale: 1, rotation: 0, opacity: 1 };
|
|
354
|
+
}
|
|
355
|
+
let audio;
|
|
356
|
+
if (rawClip.audio !== undefined) {
|
|
357
|
+
if (!audioBearing) {
|
|
358
|
+
add("TIMELINE_CLIP_INVALID", "audio is only valid on clips with audio.", jsonPath(path, "audio"), clipDetails);
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
audio = parseAudio(rawClip.audio, jsonPath(path, "audio"), (code, message, errorPath, errorDetails) => {
|
|
362
|
+
add(code, message, errorPath, { ...clipDetails, ...errorDetails });
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
else if (audioBearing) {
|
|
367
|
+
audio = { muted: false, gain: 1 };
|
|
368
|
+
}
|
|
369
|
+
const expectedKind = source.kind === "audio" ? "audio" : "video";
|
|
370
|
+
if (track.kind !== expectedKind) {
|
|
371
|
+
add("TIMELINE_CLIP_INVALID", `Clip source ${source.kind} requires a ${expectedKind} track; "${trackId}" is ${track.kind}.`, jsonPath(path, "track"), clipDetails);
|
|
372
|
+
}
|
|
373
|
+
clips.push({
|
|
374
|
+
id: clipId,
|
|
375
|
+
track: trackId,
|
|
376
|
+
source,
|
|
377
|
+
start,
|
|
378
|
+
...(duration !== undefined ? { duration } : {}),
|
|
379
|
+
sourceIn,
|
|
380
|
+
...(props !== undefined ? { props } : {}),
|
|
381
|
+
...(transform !== undefined ? { transform } : {}),
|
|
382
|
+
...(audio !== undefined ? { audio } : {}),
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
for (const track of tracks) {
|
|
386
|
+
const trackClips = clips
|
|
387
|
+
.filter((clip) => clip.track === track.id && clip.duration !== undefined)
|
|
388
|
+
.sort((left, right) => left.start - right.start);
|
|
389
|
+
for (let index = 1; index < trackClips.length; index += 1) {
|
|
390
|
+
const previous = trackClips[index - 1];
|
|
391
|
+
const current = trackClips[index];
|
|
392
|
+
if (current.start < previous.start + previous.duration) {
|
|
393
|
+
add("TIMELINE_CLIP_OVERLAP", `Clip "${current.id}" overlaps "${previous.id}" on track "${track.id}".`, `$.clips[${clips.indexOf(current)}]`, { track: track.id, clip: current.id, suggestion: "No two clips on one track may overlap in 0.6." });
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const explicitEnd = clips.reduce((end, clip) => {
|
|
398
|
+
if (clip.duration === undefined) {
|
|
399
|
+
return end;
|
|
400
|
+
}
|
|
401
|
+
return Math.max(end, clip.start + clip.duration);
|
|
402
|
+
}, 0);
|
|
403
|
+
if (typeof value.durationInFrames === "number" &&
|
|
404
|
+
Number.isInteger(value.durationInFrames) &&
|
|
405
|
+
value.durationInFrames > 0) {
|
|
406
|
+
if (value.durationInFrames < explicitEnd) {
|
|
407
|
+
add("TIMELINE_DURATION_INVALID", `Timeline durationInFrames ${value.durationInFrames} is shorter than the explicit clip end ${explicitEnd}.`, "$.durationInFrames");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
411
|
+
return { timeline: null, diagnostics };
|
|
412
|
+
}
|
|
413
|
+
return {
|
|
414
|
+
timeline: {
|
|
415
|
+
version: 1,
|
|
416
|
+
...(typeof value.name === "string" ? { name: value.name } : {}),
|
|
417
|
+
...(typeof value.durationInFrames === "number" ? { durationInFrames: value.durationInFrames } : {}),
|
|
418
|
+
...(typeof value.background === "string" ? { background: value.background } : {}),
|
|
419
|
+
tracks,
|
|
420
|
+
clips,
|
|
421
|
+
},
|
|
422
|
+
diagnostics,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/timeline/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.6.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = "0.
|
|
1
|
+
export const VERSION = "0.6.0";
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
# Make frame and props the only animation state
|
|
2
2
|
|
|
3
|
+
**Amended by [ADR-0010](./0010-make-timelines-the-render-unit.md).** Timeline frames are the master clock; each active clip is seeked to its own composition frame, and each composition instance has its own immutable props.
|
|
4
|
+
|
|
3
5
|
Every composition frame must be seek-safe and derivable from its integer frame number and immutable props, with asynchronous work completed before frame production. Frameyard rejects wall-clock animation and history-dependent updates because deterministic seeking is what makes reliable scrubbing, independent frame rendering, and future parallelism possible.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
# Keep parallel rendering opt-in and frame-ordered
|
|
2
2
|
|
|
3
|
+
**Amended by [ADR-0010](./0010-make-timelines-the-render-unit.md).** Parallel capture remains composition-only in 0.6; timeline rendering is sequential until the live timeline host proves safe to duplicate.
|
|
4
|
+
|
|
3
5
|
Frameyard will offer parallel frame capture as an explicit `render --concurrency <n>` option, defaulting to one browser session and limiting `n` to 16 to bound browser processes and buffered PNG memory. Each worker loads the same composition, props, and preview server in an independent session; workers seek and capture assigned frames while one ordered writer feeds a single FFmpeg encoder. Bounded lookahead limits buffered PNGs, failures stop further frame work and abort publication, and no partial output is published. This keeps sequential rendering as the predictable default and preserves the determinism contract at the capture boundary: concurrency changes scheduling only, never frame order or visual state. The cost is additional CPU and memory proportional to the requested session count.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Make HTML documents the composition unit
|
|
2
2
|
|
|
3
|
-
**Status:** Accepted
|
|
3
|
+
**Status:** Accepted. Amended by [ADR-0010](./0010-make-timelines-the-render-unit.md): HTML documents remain the composition unit, but compositions become clip sources inside JSON timelines.
|
|
4
4
|
**Date:** 2026-10-30
|
|
5
5
|
|
|
6
6
|
Frameyard compositions will be declarative HTML documents rather than JavaScript modules. A composition declares its metadata, default props, bindings, animation schedule, audio, and transcripts in HTML and CSS, while an optional inline or external controller handles Canvas 2D, WebGL, karaoke, and per-frame computation the declarative layer cannot express. This replaces the imperative `mount → update → dispose` authoring contract with an HTML-first model that is easier for humans and agents to read, edit, and inspect, while preserving frame-and-props determinism and preview/render parity through the same injected host runtime.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Composite production frames in software
|
|
2
2
|
|
|
3
|
-
**Status:** Accepted
|
|
3
|
+
**Status:** Accepted. Extended by [ADR-0011](./0011-compose-timeline-frames-in-a-live-browser-host.md): timeline frames use the same software-composited capture path.
|
|
4
4
|
**Date:** 2026-10-30
|
|
5
5
|
|
|
6
6
|
Production Chromium sessions keep ANGLE pinned to SwiftShader for WebGL but add
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Make timelines the render unit and compositions clip sources
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted
|
|
4
|
+
**Date:** 2026-10-31
|
|
5
|
+
|
|
6
|
+
Frameyard will render timelines as its primary output unit. A timeline is a project-local JSON document with a project-fixed width, height, and frame rate, ordered video and audio tracks, and clips sourced from HTML compositions, local media, or other timelines. A composition remains the reusable HTML authoring unit and becomes a clip source whose source range and props are fixed by each clip. Standalone composition renders remain supported on the existing direct path.
|
|
7
|
+
|
|
8
|
+
## Considered Options
|
|
9
|
+
|
|
10
|
+
- **Keep compositions as the render unit and add a playlist.** Rejected because a playlist produces separate videos, not one composited output, and cannot express layers, trims, or nested timelines.
|
|
11
|
+
- **Make every composition render an implicit one-clip timeline.** Rejected as the internal path because wrapping the existing composition capture in iframes and a stage would risk pixel and result-schema changes for existing projects. Composition renders keep the direct path; the CLI exposes both targets under one ID space.
|
|
12
|
+
- **Make the timeline a composition.** Rejected because it would conflate a reusable, prop-parameterized source with an edit that has tracks, clips, and a project format, and would invite ambiguous recursion. Timelines nest through clips, not through the composition contract.
|
|
13
|
+
- **Allow per-timeline formats.** Rejected because one project format removes frame-rate conversion from clip mapping and makes nested timelines and media composition straightforward. Standalone compositions keep their own metadata.
|
|
14
|
+
- **Allow overlapping clips on one track.** Rejected for 0.6 because transitions are deferred; overlap would create ambiguous audio and visual precedence. Crossfades can use separate tracks until transitions exist.
|
|
15
|
+
|
|
16
|
+
## Consequences
|
|
17
|
+
|
|
18
|
+
- `video.config.js` gains an optional project `format` and an explicit `timelines` map. IDs are unique across compositions and timelines so `render <id>`, `check <id>`, and `still <id>` stay unambiguous.
|
|
19
|
+
- A composition used in a timeline must share the project frame rate exactly; a different size is fitted and transformed by the clip.
|
|
20
|
+
- Timeline duration is derived from the last clip end, with an optional override. A timeline with no clips is invalid; an audio-only timeline is valid and renders black video with audio.
|
|
21
|
+
- Nested timelines are visual and audio sources, are cycle-checked, and do not accept prop overrides for their internal compositions in 0.6.
|
|
22
|
+
- Timeline clips use static fit, position, scale, rotation, and opacity. Transitions, keyframes, effects, track solo/hidden, and audio fades are deferred.
|
|
23
|
+
- Composition rendering keeps its existing result schema; timeline rendering returns a new timeline result schema.
|
|
24
|
+
- The design baseline, glossary, agent skill, scaffold, examples, and tests gain timeline language; existing composition projects remain valid without migration.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Compose timeline frames in a live browser host
|
|
2
|
+
|
|
3
|
+
**Status:** Superseded by [ADR-0012](./0012-render-timelines-with-segmented-lossless-clip-intermediates.md). Rejected for production in 0.6; retained as the preview model.
|
|
4
|
+
**Date:** 2026-10-31
|
|
5
|
+
|
|
6
|
+
> **Spike result (2026-10-31):** the host was deterministic (pixel-identical across seeks and sessions) and a one-clip host was pixel-identical to a direct composition capture, but a 1920×1080 30 fps timeline with four active layers averaged 138.8 ms/frame versus 33.6 ms/frame for a direct composition capture (4.1×). The project prioritizes long-timeline rendering throughput, so the live host is kept only as a preview candidate, not as the production compositor.
|
|
7
|
+
|
|
8
|
+
Timeline frames will be produced by a browser timeline host that loads every clip source as a layer and captures the composed stage. Composition and nested-timeline clips are same-origin iframes, media clips are video and image elements, and static clip transforms and opacity are CSS. For each timeline frame the host seeks only the active clips, waits for their settle, and the renderer captures the stage through CDP. Preview and production drive the same host, so the timeline output has one compositing path.
|
|
9
|
+
|
|
10
|
+
## Considered Options
|
|
11
|
+
|
|
12
|
+
- **Pre-render each clip and assemble with FFmpeg.** Rejected as the primary path because preview must composite live, so preview and render would diverge at scaling, opacity, and blending; intermediates also add generation loss or very large lossless files.
|
|
13
|
+
- **Capture each clip separately and composite PNG frames in Node.** Rejected because it is substantially slower per timeline frame and cannot use browser-native compositing; preview would still need a separate browser compositor.
|
|
14
|
+
- **Composite with CSS in the preview but pre-render for production.** Rejected for the same parity reason; the project's value is that preview and production execute the same behavior.
|
|
15
|
+
|
|
16
|
+
## Consequences
|
|
17
|
+
|
|
18
|
+
- Composition and nested-timeline clips are isolated iframe surfaces. CSS blend modes and backdrop filters cannot blend across clip boundaries; 0.6 has neither.
|
|
19
|
+
- The timeline host preloads all clip sources, then network is blocked for frame production. Memory and WebGL context use grow with the number of loaded clips; the spike must measure a representative timeline before this decision is accepted.
|
|
20
|
+
- Timeline rendering is sequential in 0.6. Composition rendering keeps opt-in concurrency.
|
|
21
|
+
- Audio is not played by the host; preview mixes timeline audio in the parent and production mixes it offline with FFmpeg.
|
|
22
|
+
- If the spike fails its cost budget, this ADR is revised before implementation; the timeline document contract does not depend on the compositor choice.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Render timelines with segmented lossless clip intermediates
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted
|
|
4
|
+
**Date:** 2026-10-31
|
|
5
|
+
|
|
6
|
+
Timeline rendering will be segmented and parallel. For each segment, Chromium renders the active composition and nested-timeline clips to lossless FFV1 intermediates; FFmpeg decodes media, applies the clip's fit and static transform, composites the segment, encodes H.264, and the ordered segments are concatenated without re-encoding. Audio is mixed once for the whole timeline and muxed at the end. Preview remains a live Chromium timeline host with a bounded pool of active clips; parity is guaranteed at clip capture, not at the final composite.
|
|
7
|
+
|
|
8
|
+
The production target is a representative 60-minute 1920×1080 30 fps timeline with up to three active layers, a roughly 50/50 composition/media split by layer-seconds, 60% one layer, 30% two layers, and 10% three layers. The measured 10-second segment (two compositions, video, image) captured in 10.7 s, produced 35.2 MB of FFV1, and composited plus encoded in 6.1 s. A continuous three-layer composition-heavy 60-second gate took 52.4 s, and a continuous 50/50 full-frame video-overlay gate took 58.5 s; both extrapolate to about 1× real time and are documented as worst-case behavior, not the target workload. The representative 60-second gate — 50/50 layer-seconds, 60/30/10 occupancy, eight segments, four workers, and a 1080p video fast path — took 29.39 s and produced exactly 1800 frames at 30 fps, extrapolating to about 29.4 minutes for 60 minutes. The full 60-minute gate (2026-11-01) rendered a composition-only stress timeline of 108,000 frames, 120 composition clips, 360 segments, and four workers in 2,120.9 s (35.35 minutes). The output was exactly 108,000 frames / 3,600.000 s at 1920×1080, 30 fps, with scratch cleaned. That gate is stricter than the 50/50 target mix because every second is an HTML composition capture; it exceeds 30 minutes by 18%. The target is therefore met for the representative 50/50 workload, with little margin, while composition-heavy timelines run at roughly 1.7× real time.
|
|
9
|
+
|
|
10
|
+
The 1.8 TB USB HDD provides enough scratch capacity; a raw-frame cache would be roughly 670–900 GB, which does not fit, which is why the design uses compressed lossless FFV1 rather than raw frames or PNG sequences.
|
|
11
|
+
|
|
12
|
+
## Considered Options
|
|
13
|
+
|
|
14
|
+
- **Live browser host (ADR 0011).** Rejected: deterministic and pixel-exact, but 138.8 ms per 1920×1080 frame with four layers versus 33.6 ms for a direct composition capture. It does not meet the long-timeline target.
|
|
15
|
+
- **Skia + FFmpeg streaming compositor.** Rejected for 0.6: it avoids intermediates and preserves capture pixels, but adds a large compositor dependency and a custom frame pipeline. The FFmpeg + FFV1 path already meets the target without it. Skia remains an option if future effects or blend modes outgrow FFmpeg filters.
|
|
16
|
+
- **FFmpeg streaming through pipes.** Rejected: avoids intermediates and dependencies, but requires a fragile cross-platform multi-pipe/filter-graph coordination for a variable set of active layers.
|
|
17
|
+
- **Pre-render every clip for the whole timeline.** Rejected: for 60 minutes the intermediates can be tens to hundreds of gigabytes and the write/read time alone can exceed the render budget. Segmentation bounds peak scratch and lets workers run in parallel.
|
|
18
|
+
- **Lossy intermediates.** Rejected as the default: an extra lossy encode breaks pixel parity at clip capture. FFV1 keeps the captured frames exact; only the final H.264 encode is lossy, as it already is for composition renders.
|
|
19
|
+
|
|
20
|
+
## Consequences
|
|
21
|
+
|
|
22
|
+
- Composition and nested-timeline clips are captured with Chromium; media clips are decoded directly by FFmpeg. The timeline compositor is FFmpeg, not the browser.
|
|
23
|
+
- Composition clips that are transparent must be captured with a transparent browser background (`Emulation.setDefaultBackgroundColorOverride` with alpha 0); otherwise the top-level canvas produces a white background. FFV1 uses a pixel format that preserves alpha.
|
|
24
|
+
- Segments are bounded by active-layer changes or a maximum duration (10 s in the benchmark) so each segment has a fixed filter graph; segments are encoded with identical settings and concatenated with `-c copy`.
|
|
25
|
+
- Full-frame video clips with no transform or opacity that already match the project format and whose segment boundaries fall on source keyframes may be stream-copied instead of re-encoded. Other media is scaled/transformed/re-encoded. The representative gate's 29.4-minute projection includes this fast path and excludes any source-transcode time.
|
|
26
|
+
- Timeline workers are bounded (8 by default on the reference machine) and scratch is configurable and cleaned on success and failure. A 60-minute render should not depend on the system `/tmp` tmpfs.
|
|
27
|
+
- Nested timelines are rendered recursively to segment intermediates and treated as one layer, so parent opacity and transform apply to the grouped result. Cycles are rejected statically.
|
|
28
|
+
- Audio is mixed globally rather than per segment, avoiding boundary clicks and keeping clip trims/gains exact.
|
|
29
|
+
- `check` and `still` use the clip capture + timeline composite path for the requested frame; the long-form render path is not used to produce a single still.
|
|
30
|
+
- Preview uses a live Chromium timeline host with a small active-clip pool. Scaling, alpha, and video color conversion may differ from the final FFmpeg composite; the reproducibility guarantee is at clip capture.
|
|
31
|
+
- The 60-second, three-layer, audio-enabled benchmark is the gate before production implementation. A continuous three-layer composition-heavy run measured 52.4 s for 60 s, and a continuous 50/50 full-frame video-overlay run measured 58.5 s; both are about 1× real time and are documented as worst-case. The representative 50/50, 60/30/10 occupancy gate measured 29.39 s for 60 s with four workers and a 1080p media fast path, extrapolating to about 29.4 minutes for 60 minutes with exact 1800-frame output. The full 60-minute, composition-only gate completed in 35.35 minutes with exact 108,000-frame output; composition-heavy performance remains above the 30-minute target. If stricter composition-heavy performance is required, the next levers are a faster lossless intermediate codec, persistent capture sessions, and encoder preset/thread tuning.
|