Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions packages/producer/src/services/render/stages/audioStage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@

import { join } from "node:path";
import { processCompositionAudio } from "@hyperframes/engine";
import type { RenderJob } from "../../renderOrchestrator.js";
import type { CompositionMetadata } from "../shared.js";

export interface AudioStageInput {
projectDir: string;
workDir: string;
/** `join(workDir, "compiled")`; passed through to the audio mixer for asset resolution. */
compiledDir: string;
job: RenderJob;
/** Composition duration (post-probe). Must be > 0 — probeStage guarantees this. */
duration: number;
/** Read-only view of `composition.audios`. */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/**
* captureHdrStage — Z-ordered HDR / shader-transition layered composite.
*
* Lifted verbatim from `executeRenderJob`'s `if (useLayeredComposite)`
* branch. The most complex capture path:
* The most complex capture path:
* - Spawns a dedicated `domSession` for transparent-background screenshots.
* - Spawns an `hdrEncoder` (`spawnStreamingEncoder` with
* `rawInputFormat: "rgb48le"`) accepting pre-composited HDR frames.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@
* `streamingEncoderClosed` so it's idempotent.
*
* Known follow-up (same as captureStage): this stage imports
* `updateJobStatus` from `renderOrchestrator.ts`, re-introducing the
* cycle PR 1.3.5 broke. A subsequent PR will consolidate capture
* helpers into a shared module.
* `updateJobStatus` from `renderOrchestrator.ts`, forming a runtime
* cycle with the orchestrator's import of `runCaptureStreamingStage`.
* Safe at runtime; a subsequent change will move the capture helpers
* into a shared module so the stages can import without reaching back.
*/

import {
Expand Down Expand Up @@ -102,8 +103,6 @@ export type CaptureStreamingStageResult =
| {
/** Streaming path ran successfully — sequencer should skip the disk path AND Stage 5 encode. */
success: true;
/** Wall-clock ms for the capture phase (`Date.now() - stage4Start` is the sequencer's job). */
captureDurationMs: number;
/** Wall-clock ms for the encode phase (overlapped with capture; from the encoder's own report). */
encodeMs: number;
probeSession: CaptureSession | null;
Expand Down Expand Up @@ -165,7 +164,6 @@ export async function runCaptureStreamingStage(
return { success: false };
}

const streamStart = Date.now();
const currentEncoder: StreamingEncoder = streamingEncoder;

try {
Expand Down Expand Up @@ -280,7 +278,6 @@ export async function runCaptureStreamingStage(

return {
success: true,
captureDurationMs: Date.now() - streamStart,
encodeMs: encodeResult.durationMs,
probeSession,
lastBrowserConsole,
Expand Down
9 changes: 2 additions & 7 deletions packages/producer/src/services/render/stages/encodeStage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import {
encodeFramesFromDir,
getEncoderPreset,
} from "@hyperframes/engine";
import type { Fps } from "@hyperframes/core";
import type { ProducerLogger } from "../../../logger.js";
import {
updateJobStatus,
Expand All @@ -53,7 +52,6 @@ export interface EncodeStageInput {
/** Output dimensions (post-deviceScaleFactor). */
width: number;
height: number;
fps: Fps;
/** True when the output format requires an alpha channel; selects frame extension. */
needsAlpha: boolean;
/** True iff the composition has audio. Drives the sidecar copy. */
Expand All @@ -66,7 +64,6 @@ export interface EncodeStageInput {
preset: ReturnType<typeof getEncoderPreset>;
effectiveQuality: number;
effectiveBitrate: string | undefined;
useGpu: boolean | undefined;
/** Producer config — enables the chunked-concat encoder when on. */
enableChunkedEncode: boolean;
chunkedEncodeSize: number;
Expand All @@ -89,15 +86,13 @@ export async function runEncodeStage(input: EncodeStageInput): Promise<EncodeSta
videoOnlyPath,
width,
height,
fps,
needsAlpha,
hasAudio,
audioOutputPath,
isPngSequence,
preset,
effectiveQuality,
effectiveBitrate,
useGpu,
enableChunkedEncode,
chunkedEncodeSize,
abortSignal,
Expand Down Expand Up @@ -143,15 +138,15 @@ export async function runEncodeStage(input: EncodeStageInput): Promise<EncodeSta
const frameExt = needsAlpha ? "png" : "jpg";
const framePattern = `frame_%06d.${frameExt}`;
const encoderOpts = {
fps,
fps: job.config.fps,
width,
height,
codec: preset.codec,
preset: preset.preset,
quality: effectiveQuality,
bitrate: effectiveBitrate,
pixelFormat: preset.pixelFormat,
useGpu,
useGpu: job.config.useGpu,
hdr: preset.hdr,
};
const encodeResult = enableChunkedEncode
Expand Down
48 changes: 36 additions & 12 deletions packages/producer/src/services/renderOrchestrator.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
/**
* Render Orchestrator Service
*
* Coordinates the entire video rendering pipeline:
* 1. Parse composition metadata
* 2. Pre-extract video frames
* 3. Pre-process audio tracks
* 4. Parallel frame capture
* 5. Video encoding
* 6. Final assembly (audio mux + faststart)
* `executeRenderJob` is the in-process entry point that composes the
* pipeline's six stages. Each stage lives in its own module under
* `./render/stages/` so the pure-function primitives can be reused by
* the distributed render path without dragging the orchestrator's
* cleanup and observability scaffolding with them.
*
* Heavy observability: every stage logs timing, errors include
* full context, and failures produce a diagnostic summary.
* Stage 1 compile → services/render/stages/compileStage.ts
* Stage 1b probe → services/render/stages/probeStage.ts
* (browser-driven duration discovery + media reconciliation;
* grouped with Stage 1 in the perf summary)
* Stage 2 extract videos → services/render/stages/extractVideosStage.ts
* Stage 3 audio → services/render/stages/audioStage.ts
* Stage 4 capture → services/render/stages/captureStage.ts
* services/render/stages/captureStreamingStage.ts
* services/render/stages/captureHdrStage.ts
* Stage 5 encode → services/render/stages/encodeStage.ts
* Stage 6 assemble → services/render/stages/assembleStage.ts
*
* Resources spawned by stages (file server, capture sessions, streaming
* encoders, raw HDR frame files) are tracked in the orchestrator's
* `try/finally` so a stage throwing mid-pipeline doesn't leak Chrome
* processes or ffmpeg subprocesses.
*
* Heavy observability: every stage records timing into `perfStages`,
* errors carry full context, and failures produce a diagnostic summary
* (browser console tail, memory peaks, capture attempts, HDR
* diagnostics).
*/

import {
Expand Down Expand Up @@ -1812,6 +1829,16 @@ export function extractStandaloneEntryFromIndex(
return document.toString();
}

/**
* Render a `RenderJob` end-to-end: compile → probe → extract videos →
* audio → capture → encode → assemble. The function body is a thin
* sequencer over the eight stage modules in `./render/stages/`; the
* orchestrator owns shared resources (work dir, file server, probe
* session, browser console buffer, perf counters, peak-memory sampler)
* and the `try/finally` cleanup. Returns once the final output exists at
* `outputPath`; throws on cancellation, encoder failure, or a stage
* error (with a diagnostic summary written to `perf-summary.json`).
*/
export async function executeRenderJob(
job: RenderJob,
projectDir: string,
Expand Down Expand Up @@ -2069,7 +2096,6 @@ export async function executeRenderJob(
projectDir,
workDir,
compiledDir,
job,
duration: job.duration,
audios: composition.audios,
abortSignal,
Expand Down Expand Up @@ -2442,15 +2468,13 @@ export async function executeRenderJob(
videoOnlyPath,
width,
height,
fps: job.config.fps,
needsAlpha,
hasAudio,
audioOutputPath,
isPngSequence,
preset,
effectiveQuality,
effectiveBitrate,
useGpu: job.config.useGpu,
enableChunkedEncode,
chunkedEncodeSize,
abortSignal,
Expand Down
Loading