feat(cli): add --output-resolution to lambda render

Allows authored-at-1080p compositions to render at 4K/2K via Chrome
deviceScaleFactor supersampling without re-laying-out the composition.
Plain --width 3840 silently lays out at 1920×1080 because data-width/
data-height attrs override Config.width — this flag is the supported
way to ask the renderer to supersample.

Accepts canonical CanvasResolution names (landscape, landscape-4k,
portrait, portrait-4k, square, square-4k) and aliases (1080p, 4k, uhd,
hd, 1080p-portrait, 4k-portrait, 1080p-square, 4k-square). Wired
through render + render-batch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
James
2026-05-22 15:08:30 -04:00
committed by James Russo
co-authored by Claude Opus 4.7
parent 154359d95d
commit 6d1236a0cc
4 changed files with 65 additions and 2 deletions
+26
View File
@@ -10,6 +10,11 @@
import { defineCommand } from "citty";
import type { DistributedFormat } from "@hyperframes/aws-lambda/sdk";
import {
type CanvasResolution,
VALID_CANVAS_RESOLUTIONS,
normalizeResolutionFlag,
} from "@hyperframes/core";
import type { Example } from "./_examples.js";
import { c } from "../ui/colors.js";
@@ -23,6 +28,10 @@ export const examples: Example[] = [
"Render and stream progress until done",
"hyperframes lambda render ./my-project --width 1920 --height 1080 --wait",
],
[
"Supersample a 1080p composition to 4K via Chrome deviceScaleFactor",
"hyperframes lambda render ./my-project --width 1920 --height 1080 --output-resolution 4k --wait",
],
[
"Render with composition variables (personalised template)",
'hyperframes lambda render ./my-template --site-id abc1234deadbeef0 --width 1920 --height 1080 --variables \'{"title":"Hello Alice","accent":"#ff0000"}\'',
@@ -113,6 +122,11 @@ export default defineCommand({
"site-id": { type: "string", description: "Explicit site id (overrides content hash)" },
width: { type: "string", description: "Render width in pixels" },
height: { type: "string", description: "Render height in pixels" },
"output-resolution": {
type: "string",
description:
"Output resolution preset that engages Chrome deviceScaleFactor supersampling. Accepts canonical names (landscape, landscape-4k, portrait, portrait-4k, square, square-4k) and aliases (1080p, 4k, uhd, hd). When set, the composition's authored data-width/data-height is supersampled to the target preset without changing the layout.",
},
fps: { type: "string", description: "Render fps (24 | 30 | 60)" },
format: { type: "string", description: "mp4 | mov | png-sequence | webm (default: mp4)" },
codec: { type: "string", description: "h264 | h265 (mp4 only)" },
@@ -291,6 +305,7 @@ export default defineCommand({
fps: fpsRaw,
width,
height,
outputResolution: parseOutputResolution(args["output-resolution"]),
format: parseFormat(args.format),
codec: parseCodec(args.codec),
quality: parseQuality(args.quality),
@@ -342,6 +357,7 @@ export default defineCommand({
fps: fpsRaw,
width,
height,
outputResolution: parseOutputResolution(args["output-resolution"]),
format: parseFormat(args.format),
codec: parseCodec(args.codec),
quality: parseQuality(args.quality),
@@ -450,3 +466,13 @@ const parseQuality = (raw: unknown): (typeof QUALITIES)[number] | undefined =>
parseEnum(raw, QUALITIES, "[lambda render] --quality", undefined);
const parseChromeSource = (raw: unknown): (typeof CHROME_SOURCES)[number] =>
parseEnum(raw, CHROME_SOURCES, "[lambda deploy] --chrome-source", "sparticuz")!;
function parseOutputResolution(raw: unknown): CanvasResolution | undefined {
if (raw == null || raw === "") return undefined;
const normalized = normalizeResolutionFlag(String(raw));
if (normalized) return normalized;
throw new Error(
`[lambda render] --output-resolution must be one of ${VALID_CANVAS_RESOLUTIONS.join("|")} ` +
`(or an alias: 1080p, 4k, uhd, hd, 1080p-portrait, portrait-1080p, 4k-portrait, 1080p-square, square-1080p, 4k-square); got ${String(raw)}`,
);
}