@kheleshikhi/studio-agent 0.8.0 → 0.9.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.
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Long presentation renders as bounded daemon jobs.
3
+ *
4
+ * A 120 s / 30 fps render captures 3,600 frames at roughly 150 ms each: about
5
+ * nine minutes inside the daemon. A single loopback request that answers only
6
+ * after the last frame cannot carry that, because Node's fetch gives up on a
7
+ * response whose headers have not arrived after 300 s
8
+ * (`UND_ERR_HEADERS_TIMEOUT`), while the daemon kept rendering for nobody.
9
+ *
10
+ * So the CLI starts a job, polls its status and progress with short
11
+ * requests, and receives the result when the job completes. A job is
12
+ * bounded on both sides:
13
+ *
14
+ * - one job per render browser at a time (one by default; the daemon owns
15
+ * `NERDBRO_RENDER_WORKERS` browsers, each with its own page and profile);
16
+ * - a job nobody has polled for `abandonAfterMs` is cancelled (the CLI
17
+ * died, was killed, or lost the daemon), and so is a job that runs past
18
+ * `maxRuntimeMs`;
19
+ * - `DELETE` cancels it; the renderer stops at its next frame;
20
+ * - a cancelled or failed job runs its cleanup (the frames and audio it
21
+ * wrote) once the renderer has really stopped;
22
+ * - a finished job is forgotten `retainMs` after it ended.
23
+ *
24
+ * A failure keeps the stage the renderer last reported, so the caller can
25
+ * say where it failed.
26
+ */
27
+ import { randomBytes } from "node:crypto";
28
+ import { CliError } from "./types.js";
29
+ export const RENDER_JOB_LIMITS = {
30
+ /** No status poll for this long: the client is gone, so the job is cancelled. */
31
+ abandonAfterMs: 60_000,
32
+ /** The documented ceiling is ~9 minutes of capture; this is generous headroom. */
33
+ maxRuntimeMs: 45 * 60_000,
34
+ /** A finished job's result is kept this long for its client to collect. */
35
+ retainMs: 10 * 60_000,
36
+ };
37
+ /** The error a renderer throws when its job's signal is aborted. */
38
+ export function cancelledError(signal) {
39
+ const reason = typeof signal.reason === "string" ? signal.reason : "cancelled";
40
+ return new CliError(`The render was cancelled (${reason}).`, "VIDEO_RENDER_CANCELLED", 1, { reason });
41
+ }
42
+ /** Throw [cancelledError] when the job was cancelled; called between frames. */
43
+ export function throwIfCancelled(signal) {
44
+ if (signal?.aborted)
45
+ throw cancelledError(signal);
46
+ }
47
+ export class RenderJobs {
48
+ options;
49
+ jobs = new Map();
50
+ limits;
51
+ /** Jobs that may run at once: one per render browser the daemon owns. */
52
+ maxConcurrent;
53
+ constructor(options = {}) {
54
+ this.options = options;
55
+ this.maxConcurrent = Math.max(1, Math.floor(options.maxConcurrent ?? 1));
56
+ this.limits = { ...RENDER_JOB_LIMITS, ...options.limits };
57
+ }
58
+ now() { return (this.options.clock ?? Date.now)(); }
59
+ /** Start one job; refused while another is still running or stopping. */
60
+ start(kind, runner, cleanup) {
61
+ const active = [...this.jobs.values()].filter((job) => job.state === "running" || job.state === "cancelling");
62
+ if (active.length >= this.maxConcurrent) {
63
+ throw new CliError("The daemon is already rendering; wait for that render or cancel it.", "VIDEO_RENDER_BUSY", 3, { job: active[0].id, active: active.length, max_concurrent: this.maxConcurrent });
64
+ }
65
+ const now = this.now();
66
+ const controller = new AbortController();
67
+ const job = {
68
+ id: `render_${randomBytes(9).toString("base64url")}`,
69
+ kind, state: "running", progress: { stage: "starting" },
70
+ startedAt: now, lastSeenAt: now, controller,
71
+ };
72
+ const progress = (value) => { if (job.state === "running" || job.state === "cancelling")
73
+ job.progress = { ...value }; };
74
+ job.done = (async () => {
75
+ try {
76
+ const result = await runner({ signal: controller.signal, progress });
77
+ throwIfCancelled(controller.signal);
78
+ job.result = result;
79
+ job.state = "completed";
80
+ job.progress = { ...job.progress, stage: "completed" };
81
+ }
82
+ catch (error) {
83
+ const failure = error instanceof CliError ? error : new CliError(String(error), "VIDEO_RENDER_FAILED");
84
+ const details = failure.details && typeof failure.details === "object" ? failure.details : undefined;
85
+ const stage = typeof details?.stage === "string" ? details.stage : job.progress.stage;
86
+ if (controller.signal.aborted) {
87
+ const reason = typeof controller.signal.reason === "string" ? controller.signal.reason : "cancelled";
88
+ job.state = "cancelled";
89
+ job.error = { code: "VIDEO_RENDER_CANCELLED", message: `The render was cancelled (${reason}).`, stage, exitCode: 1, details: { reason } };
90
+ }
91
+ else {
92
+ job.state = "failed";
93
+ job.error = { code: failure.code, message: failure.message, stage, exitCode: failure.exitCode, details: failure.details };
94
+ }
95
+ if (cleanup)
96
+ await cleanup().catch(() => undefined);
97
+ }
98
+ finally {
99
+ job.finishedAt = this.now();
100
+ }
101
+ })();
102
+ this.jobs.set(job.id, job);
103
+ return this.view(job);
104
+ }
105
+ find(id) {
106
+ const job = this.jobs.get(id);
107
+ if (!job)
108
+ throw new CliError("No such render job (it finished long ago, or the daemon restarted).", "VIDEO_RENDER_JOB_UNKNOWN", 1, { job: id });
109
+ return job;
110
+ }
111
+ /** A client's poll: the job's state, and its result once it completed. Keeps the job alive. */
112
+ status(id) {
113
+ const job = this.find(id);
114
+ job.lastSeenAt = this.now();
115
+ return { job: this.view(job), ...(job.state === "completed" ? { result: job.result } : {}) };
116
+ }
117
+ /** Ask a running job to stop; it becomes `cancelled` once the renderer has. */
118
+ cancel(id, reason = "cancelled by client") {
119
+ const job = this.find(id);
120
+ this.abort(job, reason);
121
+ return this.view(job);
122
+ }
123
+ abort(job, reason) {
124
+ if (job.state !== "running")
125
+ return;
126
+ job.state = "cancelling";
127
+ job.cancelReason = reason;
128
+ job.controller.abort(reason);
129
+ }
130
+ /** Cancel abandoned or overlong jobs and forget old finished ones. Run periodically. */
131
+ sweep() {
132
+ const now = this.now();
133
+ for (const job of [...this.jobs.values()]) {
134
+ if (job.state === "running" && now - job.lastSeenAt > this.limits.abandonAfterMs)
135
+ this.abort(job, "abandoned: no client polled it");
136
+ else if (job.state === "running" && now - job.startedAt > this.limits.maxRuntimeMs)
137
+ this.abort(job, "timed out");
138
+ else if (job.finishedAt !== undefined && now - job.finishedAt > this.limits.retainMs)
139
+ this.jobs.delete(job.id);
140
+ }
141
+ }
142
+ /** Resolves once the job's renderer and cleanup have finished (tests and shutdown). */
143
+ async settled(id) {
144
+ const job = this.find(id);
145
+ await job.done;
146
+ return this.view(job);
147
+ }
148
+ /** Cancel everything and wait (daemon shutdown). */
149
+ async stopAll(reason = "daemon stopping") {
150
+ const pending = [...this.jobs.values()];
151
+ for (const job of pending)
152
+ this.abort(job, reason);
153
+ await Promise.all(pending.map((job) => job.done));
154
+ }
155
+ view(job) {
156
+ return {
157
+ id: job.id, kind: job.kind, state: job.state, progress: { ...job.progress },
158
+ elapsed_ms: (job.finishedAt ?? this.now()) - job.startedAt,
159
+ ...(job.cancelReason ? { cancel_reason: job.cancelReason } : {}),
160
+ ...(job.error ? { error: job.error } : {}),
161
+ };
162
+ }
163
+ }
164
+ /**
165
+ * The daemon's job routes: `POST /render-jobs` {kind, request} starts one,
166
+ * `GET /render-jobs/<id>` polls it, `DELETE /render-jobs/<id>` cancels it.
167
+ * Returns null for any other route.
168
+ */
169
+ export async function renderJobRoute(jobs, runners, cleanups, method, pathname, body) {
170
+ if (pathname === "/render-jobs" && method === "POST") {
171
+ const input = await body();
172
+ const kind = String(input.kind);
173
+ const runner = Object.hasOwn(runners, kind) ? runners[kind] : undefined;
174
+ if (!runner)
175
+ throw new CliError(`Unknown render job kind ${kind}.`, "VIDEO_RENDER_JOB_INVALID", 2);
176
+ const request = (input.request && typeof input.request === "object" ? input.request : {});
177
+ const cleanup = cleanups[kind];
178
+ const job = jobs.start(kind, (context) => runner(request, context), cleanup ? () => cleanup(request) : undefined);
179
+ return { status: 202, value: { ok: true, job } };
180
+ }
181
+ const match = /^\/render-jobs\/([A-Za-z0-9_-]{1,64})$/.exec(pathname);
182
+ if (!match)
183
+ return null;
184
+ if (method === "GET")
185
+ return { status: 200, value: { ok: true, ...jobs.status(match[1]) } };
186
+ if (method === "DELETE")
187
+ return { status: 200, value: { ok: true, job: jobs.cancel(match[1]) } };
188
+ return null;
189
+ }
190
+ function abortableSleep(milliseconds, signal) {
191
+ return new Promise((resolve) => {
192
+ if (signal?.aborted) {
193
+ resolve();
194
+ return;
195
+ }
196
+ const timer = setTimeout(done, milliseconds);
197
+ function done() { clearTimeout(timer); signal?.removeEventListener("abort", done); resolve(); }
198
+ signal?.addEventListener("abort", done, { once: true });
199
+ });
200
+ }
201
+ /**
202
+ * Run one render as a daemon job: start, poll until it ends, return its
203
+ * result. Each request is short, so no request comes near any client or
204
+ * server timeout however long the render is. A daemon that predates jobs
205
+ * (`AGENT_LOCAL_RPC_NOT_FOUND`) is served by its single long request.
206
+ */
207
+ export async function runRenderJob(rpc, kind, legacyRoute, request, options = {}) {
208
+ const sleep = options.sleep ?? abortableSleep;
209
+ let started;
210
+ try {
211
+ started = await rpc("/render-jobs", { method: "POST", body: JSON.stringify({ kind, request }) });
212
+ }
213
+ catch (error) {
214
+ if (error instanceof CliError && error.code === "AGENT_LOCAL_RPC_NOT_FOUND") {
215
+ return (await rpc(legacyRoute, { method: "POST", body: JSON.stringify(request) })).result;
216
+ }
217
+ throw error;
218
+ }
219
+ const id = started.job.id;
220
+ for (;;) {
221
+ if (options.signal?.aborted) {
222
+ await rpc(`/render-jobs/${id}`, { method: "DELETE" }).catch(() => undefined);
223
+ const reason = typeof options.signal.reason === "string" ? options.signal.reason : "interrupted";
224
+ throw new CliError(`The render was cancelled (${reason}).`, "VIDEO_RENDER_CANCELLED", 130, { reason, job: id, stage: started.job.progress.stage });
225
+ }
226
+ await sleep(options.pollMs ?? 1000, options.signal);
227
+ if (options.signal?.aborted)
228
+ continue;
229
+ const status = await rpc(`/render-jobs/${id}`, { method: "GET" });
230
+ started = status;
231
+ options.onProgress?.(status.job);
232
+ if (status.job.state === "completed")
233
+ return status.result;
234
+ if (status.job.state === "failed" || status.job.state === "cancelled") {
235
+ const error = status.job.error ?? { code: "VIDEO_RENDER_FAILED", message: "The render failed.", stage: status.job.progress.stage, exitCode: 1 };
236
+ const details = error.details && typeof error.details === "object" ? error.details : {};
237
+ throw new CliError(error.message, error.code, error.exitCode, { ...details, stage: error.stage, job: id });
238
+ }
239
+ }
240
+ }
241
+ //# sourceMappingURL=render-jobs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-jobs.js","sourceRoot":"","sources":["../src/render-jobs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ export type Api = <T>(method: string, route: string, body?: unknown) => Promise<T>;
2
+ export declare const DEPENDENCY_SCHEMA = "kheleshikhi.scene-dependencies.v1";
3
+ export type DependencyRecord = {
4
+ schema: string;
5
+ compilerVersion?: string;
6
+ componentHeader?: string;
7
+ components?: Array<{
8
+ id: string;
9
+ definitionHash: string | null;
10
+ }>;
11
+ behaviors?: Array<{
12
+ id: string;
13
+ definitionHash: string | null;
14
+ }>;
15
+ audio?: Array<{
16
+ id: string;
17
+ sha256: string | null;
18
+ }>;
19
+ digest: string;
20
+ };
21
+ export type SceneIdentity = {
22
+ /** Experience Scene id (manifest id) or "main" for a Studio Scene shot. */
23
+ scene: string;
24
+ kind: "studio_source" | "approved_revision" | "gallery_package";
25
+ asset?: string | null;
26
+ revision?: string | null;
27
+ source_hash?: string | null;
28
+ manifest_path?: string | null;
29
+ package?: string | null;
30
+ package_sha256?: string | null;
31
+ dependencies_digest: string | null;
32
+ dependencies?: DependencyRecord | null;
33
+ note?: string;
34
+ };
35
+ export type ExperienceProvenance = {
36
+ schema: "kheleshikhi.experience-scene-provenance.v1";
37
+ preview_source_hash: string | null;
38
+ scenes: Record<string, SceneIdentity>;
39
+ /** Audio stable id -> sha256 of its exact bytes. */
40
+ audio: Record<string, string>;
41
+ digest: string;
42
+ };
43
+ export declare function canonicalJson(value: unknown): string;
44
+ export declare function digestOf(value: unknown): string;
45
+ /** A dependency record from a compile response, or null when it has none. */
46
+ export declare function dependencyRecord(compileResult: unknown): DependencyRecord | null;
47
+ /** Compile a Studio source and return its dependency record. */
48
+ export declare function compileDependencies(api: Api, asset: string, source: unknown): Promise<{
49
+ compiled: boolean;
50
+ source_hash: string | null;
51
+ dependencies: DependencyRecord | null;
52
+ }>;
53
+ /**
54
+ * One approved Scene revision's identity: the asset's approved source (when
55
+ * it is the asset's current source, i.e. no newer draft hides it) compiled
56
+ * for its dependency record. A draft over the revision leaves the record
57
+ * unknown, which is said, never guessed.
58
+ */
59
+ export declare function approvedSceneIdentity(api: Api, scene: string, asset: string, revision: string, manifestPath: string | null): Promise<SceneIdentity>;
60
+ /**
61
+ * Every Scene and sound an Experience document resolves to right now, from
62
+ * the server's draft preview (the same resolution the player uses).
63
+ */
64
+ export declare function experienceProvenance(api: Api, document: unknown, options?: {
65
+ dependencies?: boolean;
66
+ }): Promise<ExperienceProvenance>;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * What a render was made from, exactly enough to key a cache on.
3
+ *
4
+ * A Scene render is made from one Scene Source (its source hash) compiled
5
+ * against exact Component, Behavior and Audio definitions: the server's
6
+ * compile response carries that record (`artifacts.dependencies`, schema
7
+ * `kheleshikhi.scene-dependencies.v1`, with its own digest). An Experience
8
+ * render is made from the Experience Source and, for each of its Scenes, the
9
+ * approved revision the draft preview resolved (asset, revision, stored
10
+ * manifest) plus that revision's dependency record when it can be read, and
11
+ * from the exact bytes of every sound it names.
12
+ *
13
+ * Nothing here decides validity on its own; the episode runner hashes these
14
+ * facts into a shot key. Library or project membership is never an input.
15
+ */
16
+ import { createHash } from "node:crypto";
17
+ export const DEPENDENCY_SCHEMA = "kheleshikhi.scene-dependencies.v1";
18
+ export function canonicalJson(value) {
19
+ if (value === null || typeof value !== "object")
20
+ return JSON.stringify(value);
21
+ if (Array.isArray(value))
22
+ return `[${value.map(canonicalJson).join(",")}]`;
23
+ const record = value;
24
+ return `{${Object.keys(record).filter((key) => record[key] !== undefined).sort()
25
+ .map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
26
+ }
27
+ export function digestOf(value) {
28
+ return createHash("sha256").update(canonicalJson(value)).digest("hex");
29
+ }
30
+ /** A dependency record from a compile response, or null when it has none. */
31
+ export function dependencyRecord(compileResult) {
32
+ const artifacts = compileResult?.artifacts;
33
+ const record = artifacts?.dependencies;
34
+ if (!record || record.schema !== DEPENDENCY_SCHEMA || typeof record.digest !== "string")
35
+ return null;
36
+ return record;
37
+ }
38
+ /** Compile a Studio source and return its dependency record. */
39
+ export async function compileDependencies(api, asset, source) {
40
+ const result = await api("POST", `/v1/agent/studio/${encodeURIComponent(asset)}/compile`, { source });
41
+ return {
42
+ compiled: result.compiled === true,
43
+ source_hash: typeof result.source_hash === "string" ? result.source_hash : null,
44
+ dependencies: dependencyRecord(result),
45
+ };
46
+ }
47
+ /**
48
+ * One approved Scene revision's identity: the asset's approved source (when
49
+ * it is the asset's current source, i.e. no newer draft hides it) compiled
50
+ * for its dependency record. A draft over the revision leaves the record
51
+ * unknown, which is said, never guessed.
52
+ */
53
+ export async function approvedSceneIdentity(api, scene, asset, revision, manifestPath) {
54
+ const identity = { scene, kind: "approved_revision", asset, revision, manifest_path: manifestPath, dependencies_digest: null };
55
+ try {
56
+ const loaded = await api("GET", `/v1/agent/studio/${encodeURIComponent(asset)}`);
57
+ if (loaded.source_origin === "approved" && loaded.base_revision_public_id === revision && loaded.source) {
58
+ const compiled = await compileDependencies(api, asset, loaded.source);
59
+ identity.source_hash = typeof loaded.source_hash === "string" ? loaded.source_hash : compiled.source_hash;
60
+ identity.dependencies = compiled.dependencies;
61
+ identity.dependencies_digest = compiled.dependencies?.digest ?? null;
62
+ }
63
+ else {
64
+ identity.note = "A newer draft or revision hides this revision's source; keyed by revision and stored manifest only.";
65
+ }
66
+ }
67
+ catch (error) {
68
+ identity.note = `Source not readable: ${error instanceof Error ? error.message : String(error)}`.slice(0, 300);
69
+ }
70
+ return identity;
71
+ }
72
+ /**
73
+ * Every Scene and sound an Experience document resolves to right now, from
74
+ * the server's draft preview (the same resolution the player uses).
75
+ */
76
+ export async function experienceProvenance(api, document, options = {}) {
77
+ const preview = await api("POST", "/v1/agent/authoring/experience/preview", { document });
78
+ const scenes = {};
79
+ for (const [scene, raw] of Object.entries((preview.scenes ?? {}))) {
80
+ if (raw.kind === "media") {
81
+ const origin = (raw.provenance ?? {});
82
+ const asset = typeof origin.asset === "string" ? origin.asset : null;
83
+ const revision = typeof origin.revision === "string" ? origin.revision : null;
84
+ const manifestPath = typeof raw.manifestPath === "string" ? raw.manifestPath : null;
85
+ if (asset && revision && options.dependencies !== false) {
86
+ scenes[scene] = await approvedSceneIdentity(api, scene, asset, revision, manifestPath);
87
+ }
88
+ else {
89
+ scenes[scene] = { scene, kind: "approved_revision", asset, revision, manifest_path: manifestPath,
90
+ source_hash: typeof origin.sourceHash === "string" ? origin.sourceHash : null, dependencies_digest: null };
91
+ }
92
+ }
93
+ else {
94
+ scenes[scene] = { scene, kind: "gallery_package", package: raw.package ?? null, package_sha256: raw.packageSha256 ?? null,
95
+ dependencies_digest: null, note: "An approved Gallery package: its sha256 is its identity." };
96
+ }
97
+ }
98
+ const audio = {};
99
+ for (const entry of Object.values((preview.audio ?? {}))) {
100
+ if (typeof entry.audio === "string" && typeof entry.sha256 === "string")
101
+ audio[entry.audio] = entry.sha256;
102
+ }
103
+ const body = { preview_source_hash: typeof preview.sourceHash === "string" ? preview.sourceHash : null, scenes, audio };
104
+ return {
105
+ schema: "kheleshikhi.experience-scene-provenance.v1",
106
+ ...body,
107
+ digest: digestOf({
108
+ preview_source_hash: body.preview_source_hash,
109
+ scenes: Object.fromEntries(Object.entries(scenes).map(([key, value]) => [key, { ...value, dependencies: undefined, note: undefined }])),
110
+ audio,
111
+ }),
112
+ };
113
+ }
114
+ //# sourceMappingURL=shot-provenance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shot-provenance.js","sourceRoot":"","sources":["../src/shot-provenance.ts"],"names":[],"mappings":"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"}
package/dist/state.js CHANGED
@@ -5,25 +5,29 @@ import path from "node:path";
5
5
  import { CliError } from "./types.js";
6
6
  export const DEFAULT_API_ORIGIN = "https://api.nerdbro.org";
7
7
  export function stateDirectory(env = process.env, platform = process.platform) {
8
+ // Paths follow the target platform's convention, wherever this runs.
9
+ const join = platform === "win32" ? path.win32.join : path.posix.join;
8
10
  if (env.NERDBRO_AGENT_STATE_DIR)
9
11
  return path.resolve(env.NERDBRO_AGENT_STATE_DIR);
10
12
  if (platform === "win32") {
11
- return path.join(env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "NerdBro", "Studio Agent");
13
+ return join(env.LOCALAPPDATA || join(os.homedir(), "AppData", "Local"), "NerdBro", "Studio Agent");
12
14
  }
13
15
  if (platform === "darwin") {
14
- return path.join(os.homedir(), "Library", "Application Support", "NerdBro", "Studio Agent");
16
+ return join(os.homedir(), "Library", "Application Support", "NerdBro", "Studio Agent");
15
17
  }
16
- return path.join(env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state"), "nerdbro-studio-agent");
18
+ return join(env.XDG_STATE_HOME || join(os.homedir(), ".local", "state"), "nerdbro-studio-agent");
17
19
  }
18
20
  export function cacheDirectory(env = process.env, platform = process.platform) {
21
+ // Paths follow the target platform's convention, wherever this runs.
22
+ const join = platform === "win32" ? path.win32.join : path.posix.join;
19
23
  if (env.NERDBRO_AGENT_CACHE_DIR)
20
24
  return path.resolve(env.NERDBRO_AGENT_CACHE_DIR);
21
25
  if (platform === "win32") {
22
- return path.join(env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "NerdBro", "Studio Agent", "Cache");
26
+ return join(env.LOCALAPPDATA || join(os.homedir(), "AppData", "Local"), "NerdBro", "Studio Agent", "Cache");
23
27
  }
24
28
  if (platform === "darwin")
25
- return path.join(os.homedir(), "Library", "Caches", "NerdBro Studio Agent");
26
- return path.join(env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache"), "nerdbro-studio-agent");
29
+ return join(os.homedir(), "Library", "Caches", "NerdBro Studio Agent");
30
+ return join(env.XDG_CACHE_HOME || join(os.homedir(), ".cache"), "nerdbro-studio-agent");
27
31
  }
28
32
  function daemonPath() {
29
33
  return path.join(stateDirectory(), "daemon.json");
package/dist/state.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
package/dist/video.d.ts CHANGED
@@ -87,6 +87,10 @@ export type Encoder = {
87
87
  * `NERDBRO_FFPROBE`) or `ffmpeg`/`ffprobe` on PATH. It is never downloaded.
88
88
  */
89
89
  export declare function findEncoder(environment?: NodeJS.ProcessEnv): Promise<Encoder>;
90
+ /** Audio is mixed and encoded at 48 kHz stereo. */
91
+ export declare const MIX_SAMPLE_RATE = 48000;
92
+ /** The exact audio length of `frames` video frames, in 48 kHz samples. */
93
+ export declare function sampleCount(frames: number, fps: number): number;
90
94
  /**
91
95
  * The FFmpeg arguments for one recipe: H.264 High, yuv420p, CFR, AAC audio,
92
96
  * faststart. Frames are already the output size (proved per PNG), so the
@@ -149,11 +153,18 @@ export declare function audioKey(event: {
149
153
  export declare function audioSegments(events: RenderAudioEvent[], endMs: number, startMs?: number): AudioSegment[];
150
154
  /**
151
155
  * FFmpeg arguments that mix [segments] from their canonical files into one
152
- * 48 kHz stereo PCM track exactly `durationMs` long. Each segment is its own
153
- * input (a looping sound is an endlessly looped input), trimmed from its
154
- * offset to its length, scaled by its volume and delayed to its start;
155
- * the segments are summed without normalisation, so a sound is as loud as
156
- * the runtime would play it. Returns null when nothing sounded.
156
+ * 48 kHz stereo PCM track of exactly `round(durationMs * 48)` samples. Each
157
+ * segment is its own input (a looping sound is an endlessly looped input),
158
+ * trimmed from its offset to its length, scaled by its volume and delayed
159
+ * to its start; the segments are summed without normalisation, so a sound
160
+ * is as loud as the runtime would play it. Returns null when nothing sounded.
161
+ *
162
+ * Everything is counted in samples, not timestamps: a looped AAC input
163
+ * leaves a 1,024-sample timestamp gap at every wrap (its priming is skipped
164
+ * again), so a timestamp trim came out one AAC frame short per loop. Each
165
+ * input is renumbered by decoded sample (`asetpts=N/SR/TB`), which makes a
166
+ * loop gapless at the asset's own decoded length, and the mix is padded and
167
+ * trimmed to the exact sample count.
157
168
  */
158
169
  export declare function mixArguments(segments: AudioSegment[], files: Map<string, string>, durationMs: number, output: string): string[] | null;
159
170
  export declare function mix(encoder: Encoder, segments: AudioSegment[], files: Map<string, string>, durationMs: number, output: string): Promise<string[] | null>;
@@ -162,3 +173,25 @@ export declare function audioLevel(encoder: Encoder, file: string): Promise<{
162
173
  mean_db: number | null;
163
174
  max_db: number | null;
164
175
  }>;
176
+ export type PhaseSummary = {
177
+ count: number;
178
+ total_ms: number;
179
+ per_frame_ms: number;
180
+ p50_ms: number;
181
+ p95_ms: number;
182
+ max_ms: number;
183
+ };
184
+ /**
185
+ * Where capture time goes, per phase, across a render's frames: the frame
186
+ * loop records each phase's wall time and render.json reports totals,
187
+ * per-frame means and percentiles. Diagnostic only; it never affects a frame.
188
+ */
189
+ export declare class PhaseTimes {
190
+ private readonly samples;
191
+ /** Only the frame loop records; setup and audio collection do not. */
192
+ active: boolean;
193
+ add(phase: string, milliseconds: number): void;
194
+ /** Time `body` as `phase`. */
195
+ time<T>(phase: string, body: () => Promise<T>): Promise<T>;
196
+ summary(frames: number): Record<string, PhaseSummary>;
197
+ }