(opts)
| 349 | rendersDir: () => join(projectDir, "renders"), |
| 350 | |
| 351 | startRender(opts): RenderJobState { |
| 352 | const state: RenderJobState = { |
| 353 | id: opts.jobId, |
| 354 | status: "rendering", |
| 355 | progress: 0, |
| 356 | outputPath: opts.outputPath, |
| 357 | }; |
| 358 | |
| 359 | // Run render asynchronously, mutating the state object |
| 360 | const startTime = Date.now(); |
| 361 | (async () => { |
| 362 | let renderJob: RenderJob | undefined; |
| 363 | try { |
| 364 | const { createRenderJob, executeRenderJob } = await import("@hyperframes/producer"); |
| 365 | const { ensureBrowser } = await import("../browser/manager.js"); |
| 366 | |
| 367 | try { |
| 368 | const browser = await ensureBrowser(); |
| 369 | if (browser.executablePath && !process.env.PRODUCER_HEADLESS_SHELL_PATH) { |
| 370 | process.env.PRODUCER_HEADLESS_SHELL_PATH = browser.executablePath; |
| 371 | } |
| 372 | } catch { |
| 373 | // Continue without — acquireBrowser will try its own resolution |
| 374 | } |
| 375 | |
| 376 | const manifestContent = readStudioManualEditManifestContent(opts.project.dir); |
| 377 | const manualEditsRenderScript = createStudioManualEditsRenderBodyScript(manifestContent); |
| 378 | const job = createRenderJob({ |
| 379 | // opts.fps is already an Fps rational — see vite-config-studio |
| 380 | // adapter for the same convention. |
| 381 | fps: opts.fps, |
| 382 | quality: opts.quality as "draft" | "standard" | "high", |
| 383 | format: opts.format, |
| 384 | outputResolution: opts.outputResolution, |
| 385 | ...(manualEditsRenderScript ? { renderBodyScripts: [manualEditsRenderScript] } : {}), |
| 386 | ...(opts.composition ? { entryFile: opts.composition } : {}), |
| 387 | }); |
| 388 | renderJob = job; |
| 389 | const onProgress = (j: { progress: number; currentStage?: string }) => { |
| 390 | state.progress = j.progress; |
| 391 | if (j.currentStage) state.stage = j.currentStage; |
| 392 | }; |
| 393 | await executeRenderJob(job, opts.project.dir, opts.outputPath, onProgress); |
| 394 | state.status = "complete"; |
| 395 | state.progress = 100; |
| 396 | const metaPath = opts.outputPath.replace(/\.(mp4|webm|mov)$/, ".meta.json"); |
| 397 | writeFileSync( |
| 398 | metaPath, |
| 399 | JSON.stringify({ status: "complete", durationMs: Date.now() - startTime }), |
| 400 | ); |
| 401 | emitStudioRenderComplete(opts, Date.now() - startTime, job.perfSummary); |
| 402 | } catch (err) { |
| 403 | state.status = "failed"; |
| 404 | state.error = err instanceof Error ? err.message : String(err); |
| 405 | // fallow-ignore-next-line code-duplication |
| 406 | emitStudioRenderError(opts, Date.now() - startTime, state.stage, err, renderJob); |
| 407 | try { |
| 408 | const metaPath = opts.outputPath.replace(/\.(mp4|webm|mov)$/, ".meta.json"); |
nothing calls this directly
no test coverage detected