mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 12:54:29 +00:00
Merge pull request #735 from heygen-com/05-12-refactor_producer_document_executerenderjob_as_a_thin_sequencer
refactor(producer): document executeRenderJob as a thin sequencer
This commit is contained in:
@@ -17,7 +17,6 @@
|
||||
|
||||
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 {
|
||||
@@ -25,7 +24,6 @@ export interface AudioStageInput {
|
||||
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`. */
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
@@ -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;
|
||||
@@ -165,7 +164,6 @@ export async function runCaptureStreamingStage(
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
const streamStart = Date.now();
|
||||
const currentEncoder: StreamingEncoder = streamingEncoder;
|
||||
|
||||
try {
|
||||
@@ -280,7 +278,6 @@ export async function runCaptureStreamingStage(
|
||||
|
||||
return {
|
||||
success: true,
|
||||
captureDurationMs: Date.now() - streamStart,
|
||||
encodeMs: encodeResult.durationMs,
|
||||
probeSession,
|
||||
lastBrowserConsole,
|
||||
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
encodeFramesFromDir,
|
||||
getEncoderPreset,
|
||||
} from "@hyperframes/engine";
|
||||
import type { Fps } from "@hyperframes/core";
|
||||
import type { ProducerLogger } from "../../../logger.js";
|
||||
import {
|
||||
updateJobStatus,
|
||||
@@ -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. */
|
||||
@@ -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;
|
||||
@@ -89,7 +86,6 @@ export async function runEncodeStage(input: EncodeStageInput): Promise<EncodeSta
|
||||
videoOnlyPath,
|
||||
width,
|
||||
height,
|
||||
fps,
|
||||
needsAlpha,
|
||||
hasAudio,
|
||||
audioOutputPath,
|
||||
@@ -97,7 +93,6 @@ export async function runEncodeStage(input: EncodeStageInput): Promise<EncodeSta
|
||||
preset,
|
||||
effectiveQuality,
|
||||
effectiveBitrate,
|
||||
useGpu,
|
||||
enableChunkedEncode,
|
||||
chunkedEncodeSize,
|
||||
abortSignal,
|
||||
@@ -143,7 +138,7 @@ 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,
|
||||
@@ -151,7 +146,7 @@ export async function runEncodeStage(input: EncodeStageInput): Promise<EncodeSta
|
||||
quality: effectiveQuality,
|
||||
bitrate: effectiveBitrate,
|
||||
pixelFormat: preset.pixelFormat,
|
||||
useGpu,
|
||||
useGpu: job.config.useGpu,
|
||||
hdr: preset.hdr,
|
||||
};
|
||||
const encodeResult = enableChunkedEncode
|
||||
|
||||
@@ -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 {
|
||||
@@ -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,
|
||||
@@ -2069,7 +2096,6 @@ export async function executeRenderJob(
|
||||
projectDir,
|
||||
workDir,
|
||||
compiledDir,
|
||||
job,
|
||||
duration: job.duration,
|
||||
audios: composition.audios,
|
||||
abortSignal,
|
||||
@@ -2442,7 +2468,6 @@ export async function executeRenderJob(
|
||||
videoOnlyPath,
|
||||
width,
|
||||
height,
|
||||
fps: job.config.fps,
|
||||
needsAlpha,
|
||||
hasAudio,
|
||||
audioOutputPath,
|
||||
@@ -2450,7 +2475,6 @@ export async function executeRenderJob(
|
||||
preset,
|
||||
effectiveQuality,
|
||||
effectiveBitrate,
|
||||
useGpu: job.config.useGpu,
|
||||
enableChunkedEncode,
|
||||
chunkedEncodeSize,
|
||||
abortSignal,
|
||||
|
||||
Reference in New Issue
Block a user