diff --git a/packages/producer/src/services/render/hdrMode.test.ts b/packages/producer/src/services/render/hdrMode.test.ts index b8ab196be4..257f7640f2 100644 --- a/packages/producer/src/services/render/hdrMode.test.ts +++ b/packages/producer/src/services/render/hdrMode.test.ts @@ -7,7 +7,7 @@ import { describe, expect, it, vi } from "vitest"; import type { ExtractionResult, VideoColorSpace } from "@hyperframes/engine"; -import { resolveEffectiveHdrMode } from "./hdrMode.js"; +import { resolveEffectiveHdrMode, resolveHdrEncodingMode } from "./hdrMode.js"; function makeLog() { return { error: vi.fn(), warn: vi.fn(), info: vi.fn(), debug: vi.fn() }; @@ -145,3 +145,12 @@ describe("resolveEffectiveHdrMode", () => { ); }); }); + +describe("resolveHdrEncodingMode", () => { + it("keeps forced HDR encoding enabled for an SDR-only composition", () => { + expect(resolveHdrEncodingMode({ transfer: "hlg" }, 0)).toEqual({ + hasNativeHdrContent: false, + encoderHdr: { transfer: "hlg" }, + }); + }); +}); diff --git a/packages/producer/src/services/render/hdrMode.ts b/packages/producer/src/services/render/hdrMode.ts index 4c74381a4f..67be443516 100644 --- a/packages/producer/src/services/render/hdrMode.ts +++ b/packages/producer/src/services/render/hdrMode.ts @@ -14,6 +14,19 @@ import type { ExtractionResult, HdrTransfer, VideoColorSpace } from "@hyperframe import type { ProducerLogger } from "../../logger.js"; import type { RenderConfig } from "../renderOrchestrator.js"; +export function resolveHdrEncodingMode( + effectiveHdr: { transfer: HdrTransfer } | undefined, + nativeHdrResourceCount: number, +): { + hasNativeHdrContent: boolean; + encoderHdr: { transfer: HdrTransfer } | undefined; +} { + return { + hasNativeHdrContent: Boolean(effectiveHdr && nativeHdrResourceCount > 0), + encoderHdr: effectiveHdr, + }; +} + export function resolveEffectiveHdrMode(input: { hdrMode: RenderConfig["hdrMode"]; outputFormat: NonNullable; diff --git a/packages/producer/src/services/renderOrchestrator.ts b/packages/producer/src/services/renderOrchestrator.ts index c5d2528d3c..1903da6d71 100644 --- a/packages/producer/src/services/renderOrchestrator.ts +++ b/packages/producer/src/services/renderOrchestrator.ts @@ -97,7 +97,7 @@ import { } from "./render/renderEventPublisher.js"; import { normalizeErrorMessage } from "../utils/errorMessage.js"; import { formatCaptureFrameName } from "../utils/paths.js"; -import { resolveEffectiveHdrMode } from "./render/hdrMode.js"; +import { resolveEffectiveHdrMode, resolveHdrEncodingMode } from "./render/hdrMode.js"; import { buildRenderPerfSummary, pushWorkerDedupPerfs, @@ -2094,7 +2094,10 @@ export async function executeRenderJob( // worker resolution, because the DE inversion below must not fire for // comps that route to the layered/HDR paths.) const nativeHdrIds = new Set([...nativeHdrVideoIds, ...nativeHdrImageIds]); - const hasHdrContent = Boolean(effectiveHdr && nativeHdrIds.size > 0); + const { hasNativeHdrContent: hasHdrContent, encoderHdr } = resolveHdrEncodingMode( + effectiveHdr, + nativeHdrIds.size, + ); // DE priority inversion eligibility — evaluated BEFORE capture calibration // because when every multi-worker resolution would be inverted to 1 anyway, // the calibration stage (a throwaway Chrome launch + timeline-spread sample @@ -2532,7 +2535,6 @@ export async function executeRenderJob( hasShaderTransitions: compiled.hasShaderTransitions, isPngSequence, }); - const encoderHdr = hasHdrContent ? effectiveHdr : undefined; // png-sequence has no encoder, but the rest of the orchestrator still // reads `preset.quality` for `effectiveQuality` and `preset.codec` for // unrelated bookkeeping. Fall back to the mp4 preset shape — its values