Skip to content

Commit 849aee1

Browse files
committed
refactor: clarify output dynamic range contract
1 parent f6df7aa commit 849aee1

3 files changed

Lines changed: 112 additions & 30 deletions

File tree

packages/producer/src/server.hdrMode.test.ts renamed to packages/producer/src/server.outputDynamicRange.test.ts

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,25 @@ function createInternalStreamingApp(): Hono {
3838
return app;
3939
}
4040

41-
describe("POST /v1/render-stream — hdrMode", () => {
41+
function requestRender(overrides: Record<string, unknown>) {
42+
return createInternalStreamingApp().request("/v1/render-stream", {
43+
method: "POST",
44+
headers: { "content-type": "application/json" },
45+
body: JSON.stringify({ html: "<html><body></body></html>", ...overrides }),
46+
});
47+
}
48+
49+
describe("POST /v1/render-stream — outputDynamicRange", () => {
4250
beforeEach(() => capturedRenderConfigs.splice(0));
4351

44-
it.each(["auto", "force-hdr", "force-sdr"] as const)(
45-
"forwards %s through createRenderRequest into RenderConfig",
46-
async (hdrMode) => {
47-
const response = await createInternalStreamingApp().request("/v1/render-stream", {
48-
method: "POST",
49-
headers: { "content-type": "application/json" },
50-
body: JSON.stringify({ html: "<html><body></body></html>", hdrMode }),
51-
});
52+
it.each([
53+
["auto", "auto"],
54+
["hdr", "force-hdr"],
55+
["sdr", "force-sdr"],
56+
] as const)(
57+
"maps %s through createRenderRequest to internal hdrMode %s",
58+
async (outputDynamicRange, hdrMode) => {
59+
const response = await requestRender({ outputDynamicRange });
5260

5361
expect(response.status).toBe(200);
5462
expect(await response.text()).toContain('"type":"complete"');
@@ -58,16 +66,24 @@ describe("POST /v1/render-stream — hdrMode", () => {
5866
);
5967

6068
it("rejects an invalid mode before creating a render job", async () => {
61-
const response = await createInternalStreamingApp().request("/v1/render-stream", {
62-
method: "POST",
63-
headers: { "content-type": "application/json" },
64-
body: JSON.stringify({ html: "<html><body></body></html>", hdrMode: "hdr" }),
65-
});
69+
const response = await requestRender({ outputDynamicRange: "force-sdr" });
6670

6771
expect(response.status).toBe(200);
6872
expect(await response.text()).toContain(
69-
'hdrMode must be one of: \\"auto\\", \\"force-hdr\\", \\"force-sdr\\"',
73+
'outputDynamicRange must be one of: \\"auto\\", \\"hdr\\", \\"sdr\\"',
7074
);
7175
expect(capturedRenderConfigs).toHaveLength(0);
7276
});
77+
78+
it("accepts the matching legacy field during rolling deployment", async () => {
79+
const response = await requestRender({
80+
outputDynamicRange: "sdr",
81+
hdrMode: "force-sdr",
82+
});
83+
84+
expect(response.status).toBe(200);
85+
expect(await response.text()).toContain('"type":"complete"');
86+
expect(capturedRenderConfigs).toHaveLength(1);
87+
expect(capturedRenderConfigs[0]?.hdrMode).toBe("force-sdr");
88+
});
7389
});

packages/producer/src/server.test.ts

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,30 @@ describe("parseRenderOptions — render strictness", () => {
4646
});
4747
});
4848

49-
describe("parseRenderOptions — hdrMode", () => {
50-
it.each(["auto", "force-hdr", "force-sdr"] as const)("forwards %s", (hdrMode) => {
51-
expect(parseRenderOptions({ hdrMode }).hdrMode).toBe(hdrMode);
49+
describe("parseRenderOptions — outputDynamicRange", () => {
50+
it.each(["auto", "hdr", "sdr"] as const)("forwards %s", (outputDynamicRange) => {
51+
expect(parseRenderOptions({ outputDynamicRange }).outputDynamicRange).toBe(outputDynamicRange);
5252
});
5353

5454
it("drops invalid values from the lenient parser", () => {
55-
expect(parseRenderOptions({ hdrMode: "hdr" }).hdrMode).toBeUndefined();
56-
expect(parseRenderOptions({ hdrMode: true }).hdrMode).toBeUndefined();
55+
expect(
56+
parseRenderOptions({ outputDynamicRange: "force-sdr" }).outputDynamicRange,
57+
).toBeUndefined();
58+
expect(parseRenderOptions({ outputDynamicRange: true }).outputDynamicRange).toBeUndefined();
59+
});
60+
61+
it.each([
62+
["auto", "auto"],
63+
["force-hdr", "hdr"],
64+
["force-sdr", "sdr"],
65+
] as const)("maps legacy hdrMode %s to %s", (hdrMode, outputDynamicRange) => {
66+
expect(parseRenderOptions({ hdrMode }).outputDynamicRange).toBe(outputDynamicRange);
67+
});
68+
69+
it("prefers the canonical field when both equivalent fields are present", () => {
70+
expect(
71+
parseRenderOptions({ outputDynamicRange: "sdr", hdrMode: "force-sdr" }).outputDynamicRange,
72+
).toBe("sdr");
5773
});
5874
});
5975

@@ -78,11 +94,26 @@ describe("prepareRenderBody — validation", () => {
7894
expect((result as { error: string }).error).toContain("variables must be a JSON object");
7995
});
8096

81-
it("rejects an explicitly-supplied invalid hdrMode", async () => {
82-
const result = await prepareRenderBody({ hdrMode: "hdr", html: "<html></html>" });
97+
it("rejects an explicitly-supplied invalid outputDynamicRange", async () => {
98+
const result = await prepareRenderBody({
99+
outputDynamicRange: "force-sdr",
100+
html: "<html></html>",
101+
});
102+
expect(result).toHaveProperty("error");
103+
expect((result as { error: string }).error).toContain(
104+
'outputDynamicRange must be one of: "auto", "hdr", "sdr"',
105+
);
106+
});
107+
108+
it("rejects conflicting canonical and legacy policies", async () => {
109+
const result = await prepareRenderBody({
110+
outputDynamicRange: "sdr",
111+
hdrMode: "force-hdr",
112+
html: "<html></html>",
113+
});
83114
expect(result).toHaveProperty("error");
84115
expect((result as { error: string }).error).toContain(
85-
'hdrMode must be one of: "auto", "force-hdr", "force-sdr"',
116+
"outputDynamicRange and legacy hdrMode must describe the same output policy",
86117
);
87118
});
88119

packages/producer/src/server.ts

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ interface RenderInput {
8484
quality: "draft" | "standard" | "high";
8585
format?: "mp4" | "webm" | "mov";
8686
videoFrameFormat?: RenderConfig["videoFrameFormat"];
87-
hdrMode?: RenderConfig["hdrMode"];
87+
outputDynamicRange?: "auto" | "hdr" | "sdr";
8888
workers?: number;
8989
useGpu: boolean;
9090
debug: boolean;
@@ -159,10 +159,28 @@ function parseServerFormat(value: unknown): RenderInput["format"] {
159159
return value === "mp4" || value === "webm" || value === "mov" ? value : undefined;
160160
}
161161

162-
function parseServerHdrMode(value: unknown): RenderInput["hdrMode"] {
162+
function parseServerOutputDynamicRange(value: unknown): RenderInput["outputDynamicRange"] {
163+
return value === "auto" || value === "hdr" || value === "sdr" ? value : undefined;
164+
}
165+
166+
function parseLegacyServerHdrMode(value: unknown): RenderConfig["hdrMode"] {
163167
return value === "auto" || value === "force-hdr" || value === "force-sdr" ? value : undefined;
164168
}
165169

170+
function fromRenderHdrMode(hdrMode: RenderConfig["hdrMode"]): RenderInput["outputDynamicRange"] {
171+
if (hdrMode === "force-hdr") return "hdr";
172+
if (hdrMode === "force-sdr") return "sdr";
173+
return hdrMode;
174+
}
175+
176+
function toRenderHdrMode(
177+
outputDynamicRange: RenderInput["outputDynamicRange"],
178+
): RenderConfig["hdrMode"] {
179+
if (outputDynamicRange === "hdr") return "force-hdr";
180+
if (outputDynamicRange === "sdr") return "force-sdr";
181+
return outputDynamicRange;
182+
}
183+
166184
export function parseRenderOptions(body: Record<string, unknown>): Omit<RenderInput, "projectDir"> {
167185
// Accept either a JSON `number` (integer fps) or a JSON `string` (rational
168186
// like "30000/1001"). Falls back to 30 fps on parse failure to preserve the
@@ -181,7 +199,9 @@ export function parseRenderOptions(body: Record<string, unknown>): Omit<RenderIn
181199
const outputPath = parseOutputCandidate(body);
182200
const entryFile = nonEmptyString(body.entryFile);
183201
const format = parseServerFormat(body.format);
184-
const hdrMode = parseServerHdrMode(body.hdrMode);
202+
const outputDynamicRange =
203+
parseServerOutputDynamicRange(body.outputDynamicRange) ??
204+
fromRenderHdrMode(parseLegacyServerHdrMode(body.hdrMode));
185205
const videoFrameFormat = isVideoFrameFormat(body.videoFrameFormat)
186206
? body.videoFrameFormat
187207
: undefined;
@@ -199,7 +219,7 @@ export function parseRenderOptions(body: Record<string, unknown>): Omit<RenderIn
199219
strictness,
200220
entryFile,
201221
format,
202-
hdrMode,
222+
outputDynamicRange,
203223
variables,
204224
outputResolution,
205225
outputResolutionAspectAgnostic,
@@ -261,7 +281,7 @@ function buildRenderJobConfig(input: RenderInput, outputPath: string, log: Produ
261281
outputResolution: input.outputResolution,
262282
outputResolutionAspectAgnostic: input.outputResolutionAspectAgnostic,
263283
videoFrameFormat: input.videoFrameFormat,
264-
hdrMode: input.hdrMode,
284+
hdrMode: toRenderHdrMode(input.outputDynamicRange),
265285
},
266286
});
267287
return renderConfigFromRequest(request, { logger: log });
@@ -294,8 +314,23 @@ function validateRenderOverrides(body: Record<string, unknown>): string | undefi
294314
if (body.variables !== undefined && !isPlainObject(body.variables)) {
295315
return 'variables must be a JSON object keyed by variable id (e.g. {"title":"Hello"})';
296316
}
297-
if (body.hdrMode !== undefined && parseServerHdrMode(body.hdrMode) === undefined) {
298-
return 'hdrMode must be one of: "auto", "force-hdr", "force-sdr"';
317+
if (
318+
body.outputDynamicRange !== undefined &&
319+
parseServerOutputDynamicRange(body.outputDynamicRange) === undefined
320+
) {
321+
return 'outputDynamicRange must be one of: "auto", "hdr", "sdr"';
322+
}
323+
const legacyHdrMode = parseLegacyServerHdrMode(body.hdrMode);
324+
if (body.hdrMode !== undefined && legacyHdrMode === undefined) {
325+
return 'legacy hdrMode must be one of: "auto", "force-hdr", "force-sdr"';
326+
}
327+
const outputDynamicRange = parseServerOutputDynamicRange(body.outputDynamicRange);
328+
if (
329+
outputDynamicRange !== undefined &&
330+
legacyHdrMode !== undefined &&
331+
outputDynamicRange !== fromRenderHdrMode(legacyHdrMode)
332+
) {
333+
return "outputDynamicRange and legacy hdrMode must describe the same output policy";
299334
}
300335
return validateOutputResolutionOverride(body);
301336
}

0 commit comments

Comments
 (0)