mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-04 07:19:52 +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 { join } from "node:path";
|
||||||
import { processCompositionAudio } from "@hyperframes/engine";
|
import { processCompositionAudio } from "@hyperframes/engine";
|
||||||
import type { RenderJob } from "../../renderOrchestrator.js";
|
|
||||||
import type { CompositionMetadata } from "../shared.js";
|
import type { CompositionMetadata } from "../shared.js";
|
||||||
|
|
||||||
export interface AudioStageInput {
|
export interface AudioStageInput {
|
||||||
@@ -25,7 +24,6 @@ export interface AudioStageInput {
|
|||||||
workDir: string;
|
workDir: string;
|
||||||
/** `join(workDir, "compiled")`; passed through to the audio mixer for asset resolution. */
|
/** `join(workDir, "compiled")`; passed through to the audio mixer for asset resolution. */
|
||||||
compiledDir: string;
|
compiledDir: string;
|
||||||
job: RenderJob;
|
|
||||||
/** Composition duration (post-probe). Must be > 0 — probeStage guarantees this. */
|
/** Composition duration (post-probe). Must be > 0 — probeStage guarantees this. */
|
||||||
duration: number;
|
duration: number;
|
||||||
/** Read-only view of `composition.audios`. */
|
/** Read-only view of `composition.audios`. */
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* captureHdrStage — Z-ordered HDR / shader-transition layered composite.
|
* captureHdrStage — Z-ordered HDR / shader-transition layered composite.
|
||||||
*
|
*
|
||||||
* Lifted verbatim from `executeRenderJob`'s `if (useLayeredComposite)`
|
* The most complex capture path:
|
||||||
* branch. The most complex capture path:
|
|
||||||
* - Spawns a dedicated `domSession` for transparent-background screenshots.
|
* - Spawns a dedicated `domSession` for transparent-background screenshots.
|
||||||
* - Spawns an `hdrEncoder` (`spawnStreamingEncoder` with
|
* - Spawns an `hdrEncoder` (`spawnStreamingEncoder` with
|
||||||
* `rawInputFormat: "rgb48le"`) accepting pre-composited HDR frames.
|
* `rawInputFormat: "rgb48le"`) accepting pre-composited HDR frames.
|
||||||
|
|||||||
@@ -34,9 +34,10 @@
|
|||||||
* `streamingEncoderClosed` so it's idempotent.
|
* `streamingEncoderClosed` so it's idempotent.
|
||||||
*
|
*
|
||||||
* Known follow-up (same as captureStage): this stage imports
|
* Known follow-up (same as captureStage): this stage imports
|
||||||
* `updateJobStatus` from `renderOrchestrator.ts`, re-introducing the
|
* `updateJobStatus` from `renderOrchestrator.ts`, forming a runtime
|
||||||
* cycle PR 1.3.5 broke. A subsequent PR will consolidate capture
|
* cycle with the orchestrator's import of `runCaptureStreamingStage`.
|
||||||
* helpers into a shared module.
|
* Safe at runtime; a subsequent change will move the capture helpers
|
||||||
|
* into a shared module so the stages can import without reaching back.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -102,8 +103,6 @@ export type CaptureStreamingStageResult =
|
|||||||
| {
|
| {
|
||||||
/** Streaming path ran successfully — sequencer should skip the disk path AND Stage 5 encode. */
|
/** Streaming path ran successfully — sequencer should skip the disk path AND Stage 5 encode. */
|
||||||
success: true;
|
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). */
|
/** Wall-clock ms for the encode phase (overlapped with capture; from the encoder's own report). */
|
||||||
encodeMs: number;
|
encodeMs: number;
|
||||||
probeSession: CaptureSession | null;
|
probeSession: CaptureSession | null;
|
||||||
@@ -165,7 +164,6 @@ export async function runCaptureStreamingStage(
|
|||||||
return { success: false };
|
return { success: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
const streamStart = Date.now();
|
|
||||||
const currentEncoder: StreamingEncoder = streamingEncoder;
|
const currentEncoder: StreamingEncoder = streamingEncoder;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -280,7 +278,6 @@ export async function runCaptureStreamingStage(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
captureDurationMs: Date.now() - streamStart,
|
|
||||||
encodeMs: encodeResult.durationMs,
|
encodeMs: encodeResult.durationMs,
|
||||||
probeSession,
|
probeSession,
|
||||||
lastBrowserConsole,
|
lastBrowserConsole,
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import {
|
|||||||
encodeFramesFromDir,
|
encodeFramesFromDir,
|
||||||
getEncoderPreset,
|
getEncoderPreset,
|
||||||
} from "@hyperframes/engine";
|
} from "@hyperframes/engine";
|
||||||
import type { Fps } from "@hyperframes/core";
|
|
||||||
import type { ProducerLogger } from "../../../logger.js";
|
import type { ProducerLogger } from "../../../logger.js";
|
||||||
import {
|
import {
|
||||||
updateJobStatus,
|
updateJobStatus,
|
||||||
@@ -53,7 +52,6 @@ export interface EncodeStageInput {
|
|||||||
/** Output dimensions (post-deviceScaleFactor). */
|
/** Output dimensions (post-deviceScaleFactor). */
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
fps: Fps;
|
|
||||||
/** True when the output format requires an alpha channel; selects frame extension. */
|
/** True when the output format requires an alpha channel; selects frame extension. */
|
||||||
needsAlpha: boolean;
|
needsAlpha: boolean;
|
||||||
/** True iff the composition has audio. Drives the sidecar copy. */
|
/** True iff the composition has audio. Drives the sidecar copy. */
|
||||||
@@ -66,7 +64,6 @@ export interface EncodeStageInput {
|
|||||||
preset: ReturnType<typeof getEncoderPreset>;
|
preset: ReturnType<typeof getEncoderPreset>;
|
||||||
effectiveQuality: number;
|
effectiveQuality: number;
|
||||||
effectiveBitrate: string | undefined;
|
effectiveBitrate: string | undefined;
|
||||||
useGpu: boolean | undefined;
|
|
||||||
/** Producer config — enables the chunked-concat encoder when on. */
|
/** Producer config — enables the chunked-concat encoder when on. */
|
||||||
enableChunkedEncode: boolean;
|
enableChunkedEncode: boolean;
|
||||||
chunkedEncodeSize: number;
|
chunkedEncodeSize: number;
|
||||||
@@ -89,7 +86,6 @@ export async function runEncodeStage(input: EncodeStageInput): Promise<EncodeSta
|
|||||||
videoOnlyPath,
|
videoOnlyPath,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
fps,
|
|
||||||
needsAlpha,
|
needsAlpha,
|
||||||
hasAudio,
|
hasAudio,
|
||||||
audioOutputPath,
|
audioOutputPath,
|
||||||
@@ -97,7 +93,6 @@ export async function runEncodeStage(input: EncodeStageInput): Promise<EncodeSta
|
|||||||
preset,
|
preset,
|
||||||
effectiveQuality,
|
effectiveQuality,
|
||||||
effectiveBitrate,
|
effectiveBitrate,
|
||||||
useGpu,
|
|
||||||
enableChunkedEncode,
|
enableChunkedEncode,
|
||||||
chunkedEncodeSize,
|
chunkedEncodeSize,
|
||||||
abortSignal,
|
abortSignal,
|
||||||
@@ -143,7 +138,7 @@ export async function runEncodeStage(input: EncodeStageInput): Promise<EncodeSta
|
|||||||
const frameExt = needsAlpha ? "png" : "jpg";
|
const frameExt = needsAlpha ? "png" : "jpg";
|
||||||
const framePattern = `frame_%06d.${frameExt}`;
|
const framePattern = `frame_%06d.${frameExt}`;
|
||||||
const encoderOpts = {
|
const encoderOpts = {
|
||||||
fps,
|
fps: job.config.fps,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
codec: preset.codec,
|
codec: preset.codec,
|
||||||
@@ -151,7 +146,7 @@ export async function runEncodeStage(input: EncodeStageInput): Promise<EncodeSta
|
|||||||
quality: effectiveQuality,
|
quality: effectiveQuality,
|
||||||
bitrate: effectiveBitrate,
|
bitrate: effectiveBitrate,
|
||||||
pixelFormat: preset.pixelFormat,
|
pixelFormat: preset.pixelFormat,
|
||||||
useGpu,
|
useGpu: job.config.useGpu,
|
||||||
hdr: preset.hdr,
|
hdr: preset.hdr,
|
||||||
};
|
};
|
||||||
const encodeResult = enableChunkedEncode
|
const encodeResult = enableChunkedEncode
|
||||||
|
|||||||
@@ -1,16 +1,33 @@
|
|||||||
/**
|
/**
|
||||||
* Render Orchestrator Service
|
* Render Orchestrator Service
|
||||||
*
|
*
|
||||||
* Coordinates the entire video rendering pipeline:
|
* `executeRenderJob` is the in-process entry point that composes the
|
||||||
* 1. Parse composition metadata
|
* pipeline's six stages. Each stage lives in its own module under
|
||||||
* 2. Pre-extract video frames
|
* `./render/stages/` so the pure-function primitives can be reused by
|
||||||
* 3. Pre-process audio tracks
|
* the distributed render path without dragging the orchestrator's
|
||||||
* 4. Parallel frame capture
|
* cleanup and observability scaffolding with them.
|
||||||
* 5. Video encoding
|
|
||||||
* 6. Final assembly (audio mux + faststart)
|
|
||||||
*
|
*
|
||||||
* Heavy observability: every stage logs timing, errors include
|
* Stage 1 compile → services/render/stages/compileStage.ts
|
||||||
* full context, and failures produce a diagnostic summary.
|
* 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 {
|
import {
|
||||||
@@ -1812,6 +1829,16 @@ export function extractStandaloneEntryFromIndex(
|
|||||||
return document.toString();
|
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(
|
export async function executeRenderJob(
|
||||||
job: RenderJob,
|
job: RenderJob,
|
||||||
projectDir: string,
|
projectDir: string,
|
||||||
@@ -2069,7 +2096,6 @@ export async function executeRenderJob(
|
|||||||
projectDir,
|
projectDir,
|
||||||
workDir,
|
workDir,
|
||||||
compiledDir,
|
compiledDir,
|
||||||
job,
|
|
||||||
duration: job.duration,
|
duration: job.duration,
|
||||||
audios: composition.audios,
|
audios: composition.audios,
|
||||||
abortSignal,
|
abortSignal,
|
||||||
@@ -2442,7 +2468,6 @@ export async function executeRenderJob(
|
|||||||
videoOnlyPath,
|
videoOnlyPath,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
fps: job.config.fps,
|
|
||||||
needsAlpha,
|
needsAlpha,
|
||||||
hasAudio,
|
hasAudio,
|
||||||
audioOutputPath,
|
audioOutputPath,
|
||||||
@@ -2450,7 +2475,6 @@ export async function executeRenderJob(
|
|||||||
preset,
|
preset,
|
||||||
effectiveQuality,
|
effectiveQuality,
|
||||||
effectiveBitrate,
|
effectiveBitrate,
|
||||||
useGpu: job.config.useGpu,
|
|
||||||
enableChunkedEncode,
|
enableChunkedEncode,
|
||||||
chunkedEncodeSize,
|
chunkedEncodeSize,
|
||||||
abortSignal,
|
abortSignal,
|
||||||
|
|||||||
Reference in New Issue
Block a user