Skip to content

Commit e55a8d0

Browse files
committed
feat(producer): add services/distributed/renderChunk.ts
1 parent 34f5fc0 commit e55a8d0

9 files changed

Lines changed: 1252 additions & 93 deletions

File tree

packages/producer/src/services/distributed/plan.ts

Lines changed: 18 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,11 @@
2424
* never have to handle them.
2525
*/
2626

27-
import { execFile as execFileCallback } from "node:child_process";
28-
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync } from "node:fs";
29-
import { dirname, join } from "node:path";
30-
import { fileURLToPath } from "node:url";
31-
import { promisify } from "node:util";
32-
import { type CanvasResolution, type Fps } from "@hyperframes/core";
27+
import { existsSync, mkdirSync, renameSync, rmSync } from "node:fs";
28+
import { join } from "node:path";
29+
import { type CanvasResolution } from "@hyperframes/core";
3330
import { type EngineConfig, resolveConfig } from "@hyperframes/engine";
3431
import { defaultLogger, type ProducerLogger } from "../../logger.js";
35-
import { type RenderConfig, type RenderJob, createRenderJob } from "../renderOrchestrator.js";
3632
import { runAudioStage } from "../render/stages/audioStage.js";
3733
import { runCompileStage } from "../render/stages/compileStage.js";
3834
import { runExtractVideosStage } from "../render/stages/extractVideosStage.js";
@@ -50,8 +46,7 @@ import {
5046
} from "../render/stages/planHash.js";
5147
import { validateNoGpuEncode, validateNoSystemFonts } from "../render/planValidation.js";
5248
import { snapshotRuntimeEnv } from "../render/runtimeEnvSnapshot.js";
53-
54-
const execFile = promisify(execFileCallback);
49+
import { buildSyntheticRenderJob, readFfmpegVersion, readProducerVersion } from "./shared.js";
5550

5651
/**
5752
* Caller-supplied configuration for a distributed render. `fps`, `width`,
@@ -189,81 +184,6 @@ export function buildChunkSlices(
189184
return slices;
190185
}
191186

192-
/**
193-
* Map a `DistributedRenderConfig` onto the in-process `RenderConfig` shape
194-
* the stage functions consume. Distributed plan() is the first caller of
195-
* the staged renderer that operates without a full RenderJob — we synthesize
196-
* one from the distributed config so the existing stage interfaces don't
197-
* need a parallel "distributed mode" overload.
198-
*/
199-
function buildSyntheticRenderJob(config: DistributedRenderConfig): RenderJob {
200-
const renderConfig: RenderConfig = {
201-
fps: { num: config.fps, den: 1 } satisfies Fps,
202-
quality: config.quality ?? "standard",
203-
format: config.format,
204-
crf: config.crf,
205-
videoBitrate: config.bitrate,
206-
outputResolution: config.outputResolution,
207-
// Distributed mode hard-pins to software GPU. The plan-time validator
208-
// (see validateNoGpuEncode) refuses to fan out otherwise.
209-
useGpu: false,
210-
debug: false,
211-
entryFile: config.entryFile ?? "index.html",
212-
logger: config.logger ?? defaultLogger,
213-
// HDR is banned in distributed mode. force-sdr keeps the
214-
// extract / encoder paths off the HDR branches entirely.
215-
hdrMode: config.hdrMode ?? "force-sdr",
216-
producerConfig: config.producerConfig,
217-
};
218-
return createRenderJob(renderConfig);
219-
}
220-
221-
/**
222-
* Resolve the producer package version by walking up from the calling module
223-
* until a `package.json` whose `name === "@hyperframes/producer"` is found.
224-
* Works for both the bundled `dist/index.js` (1 level up) and the unbundled
225-
* source tree (`src/services/distributed/plan.ts` → 4 levels up).
226-
*/
227-
function readProducerVersion(): string {
228-
const startDir = dirname(fileURLToPath(import.meta.url));
229-
let current = startDir;
230-
for (let i = 0; i < 10; i++) {
231-
const candidate = join(current, "package.json");
232-
if (existsSync(candidate)) {
233-
try {
234-
const pkg = JSON.parse(readFileSync(candidate, "utf-8")) as {
235-
name?: string;
236-
version?: string;
237-
};
238-
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
239-
return pkg.version;
240-
}
241-
} catch {
242-
// Fall through to the next ancestor.
243-
}
244-
}
245-
const parent = dirname(current);
246-
if (parent === current) break;
247-
current = parent;
248-
}
249-
return "0.0.0-unknown";
250-
}
251-
252-
/**
253-
* Spawn `ffmpeg -version` and return the first line (e.g. `"ffmpeg version 6.1.1"`).
254-
* The string is opaque — `planHash` mixes it in verbatim, so any drift across
255-
* worker hosts trips a `FFMPEG_VERSION_MISMATCH` rather than producing pixels
256-
* that subtly disagree with the plan's baked-in encoder args.
257-
*/
258-
async function readFfmpegVersion(): Promise<string> {
259-
const { stdout } = await execFile("ffmpeg", ["-version"], { maxBuffer: 1024 * 1024 });
260-
const firstLine = stdout.split(/\r?\n/)[0]?.trim() ?? "";
261-
if (!firstLine) {
262-
throw new Error("[plan] ffmpeg -version returned empty output");
263-
}
264-
return firstLine;
265-
}
266-
267187
/**
268188
* Hash the deterministic-font bundle that ships inside `@hyperframes/producer`.
269189
* The compiled HTML already inlines per-family `@font-face` data URIs, so the
@@ -391,7 +311,20 @@ export async function plan(
391311
forceScreenshot: false,
392312
};
393313

394-
const job = buildSyntheticRenderJob(config);
314+
const job = buildSyntheticRenderJob({
315+
fps: { num: config.fps, den: 1 },
316+
quality: config.quality ?? "standard",
317+
format: config.format,
318+
crf: config.crf,
319+
bitrate: config.bitrate,
320+
outputResolution: config.outputResolution,
321+
// HDR is banned in distributed mode. force-sdr keeps the
322+
// extract / encoder paths off the HDR branches entirely.
323+
hdrMode: config.hdrMode ?? "force-sdr",
324+
entryFile: config.entryFile ?? "index.html",
325+
logger: config.logger,
326+
producerConfig: config.producerConfig,
327+
});
395328
const entryFile = config.entryFile ?? "index.html";
396329
const htmlPath = join(projectDir, entryFile);
397330
if (!existsSync(htmlPath)) {

0 commit comments

Comments
 (0)