mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 02:36:10 +00:00
75 lines
2.8 KiB
TypeScript
75 lines
2.8 KiB
TypeScript
import { cpus } from "node:os";
|
|
import { readFileSync } from "node:fs";
|
|
import { fpsToFfmpegArg } from "@hyperframes/core";
|
|
import { c } from "../../ui/colors.js";
|
|
import type { RenderPlan } from "./plan.js";
|
|
|
|
/** Present warnings and the human render plan. JSON batch output stays silent. */
|
|
// This phase intentionally owns every mutually exclusive human-output branch.
|
|
// fallow-ignore-next-line complexity
|
|
export async function presentRenderPlan(plan: RenderPlan): Promise<void> {
|
|
await presentRenderWarnings(plan);
|
|
if (plan.effectiveQuiet || plan.batchPath) return;
|
|
presentRenderSummary(plan);
|
|
}
|
|
|
|
async function presentRenderWarnings(plan: RenderPlan): Promise<void> {
|
|
if (plan.invalidAuthoringSkill) {
|
|
process.stderr.write(
|
|
`hyperframes: ignoring --skill="${plan.invalidAuthoringSkill}" — not a valid slug ` +
|
|
"(lowercase letters/digits/hyphens, max 64); this render will be unattributed.\n",
|
|
);
|
|
}
|
|
if (!plan.effectiveQuiet && plan.gifFpsCapped) {
|
|
console.log(c.warn(" GIF output is capped at 30fps. Use --fps 15 for smaller files."));
|
|
}
|
|
if (!plan.effectiveQuiet) await warnForSlideshow(plan);
|
|
}
|
|
|
|
function presentRenderSummary(plan: RenderPlan): void {
|
|
const workerLabel =
|
|
plan.workers != null
|
|
? `${plan.workers} workers`
|
|
: `auto workers (${cpus().length} cores detected)`;
|
|
const nameLabel = plan.entryFile ? `${plan.project.name}/${plan.entryFile}` : plan.project.name;
|
|
console.log("");
|
|
console.log(
|
|
c.accent("\u25C6") + " Rendering " + c.accent(nameLabel) + c.dim(" \u2192 " + plan.outputPath),
|
|
);
|
|
console.log(
|
|
c.dim(` ${fpsToFfmpegArg(plan.fps)}fps \u00B7 ${plan.quality} \u00B7 ${workerLabel}`),
|
|
);
|
|
if (plan.outputResolution) {
|
|
console.log(c.dim(" Output resolution: " + plan.outputResolution));
|
|
}
|
|
if (plan.useGpu || plan.browserGpuMode !== "software") {
|
|
const gpuModes = [
|
|
plan.useGpu ? "encoder GPU" : null,
|
|
plan.browserGpuMode === "hardware"
|
|
? "browser GPU (forced)"
|
|
: plan.browserGpuMode === "auto"
|
|
? "browser GPU (auto-detect)"
|
|
: null,
|
|
].filter(Boolean);
|
|
console.log(c.dim(" GPU: " + gpuModes.join(" + ")));
|
|
}
|
|
console.log("");
|
|
}
|
|
|
|
async function warnForSlideshow(plan: RenderPlan): Promise<void> {
|
|
try {
|
|
const { slideshowIslandRegex } = await import("@hyperframes/core/slideshow");
|
|
if (!slideshowIslandRegex("i").test(readFileSync(plan.renderTarget, "utf8"))) return;
|
|
console.log(
|
|
c.warn("⚠") +
|
|
" This composition carries a slideshow island — `render` captures only the first" +
|
|
" scene, so the MP4 will be truncated to slide 1. Use " +
|
|
c.accent("hyperframes present") +
|
|
" for the deck; a linear main-line MP4 export is not yet available.",
|
|
);
|
|
console.log("");
|
|
} catch {
|
|
// Best-effort only; the execution pipeline owns real file failures.
|
|
}
|
|
}
|