(body: Record<string, unknown>)
| 101 | } |
| 102 | |
| 103 | export function parseRenderOptions(body: Record<string, unknown>): Omit<RenderInput, "projectDir"> { |
| 104 | // Accept either a JSON `number` (integer fps) or a JSON `string` (rational |
| 105 | // like "30000/1001"). Falls back to 30 fps on parse failure to preserve the |
| 106 | // forgiving behaviour the original whitelist had — the producer surfaces a |
| 107 | // clearer downstream error if the value is genuinely unusable. |
| 108 | const fpsRaw = body.fps; |
| 109 | const fpsParse = |
| 110 | typeof fpsRaw === "number" || typeof fpsRaw === "string" ? parseFps(fpsRaw) : null; |
| 111 | const fps = fpsParse && fpsParse.ok ? fpsParse.value : ({ num: 30, den: 1 } as const); |
| 112 | const quality = ( |
| 113 | ["draft", "standard", "high"].includes(body.quality as string) ? body.quality : "high" |
| 114 | ) as "draft" | "standard" | "high"; |
| 115 | const workers = typeof body.workers === "number" ? body.workers : undefined; |
| 116 | const useGpu = body.gpu === true; |
| 117 | const debug = body.debug === true; |
| 118 | const outputPath = |
| 119 | typeof body.outputPath === "string" && body.outputPath.trim().length > 0 |
| 120 | ? body.outputPath |
| 121 | : typeof body.output === "string" && body.output.trim().length > 0 |
| 122 | ? body.output |
| 123 | : null; |
| 124 | |
| 125 | const entryFile = |
| 126 | typeof body.entryFile === "string" && body.entryFile.trim().length > 0 |
| 127 | ? body.entryFile.trim() |
| 128 | : undefined; |
| 129 | |
| 130 | const format = ( |
| 131 | ["mp4", "webm", "mov"].includes(body.format as string) ? body.format : undefined |
| 132 | ) as RenderInput["format"]; |
| 133 | const videoFrameFormat = isVideoFrameFormat(body.videoFrameFormat) |
| 134 | ? body.videoFrameFormat |
| 135 | : undefined; |
| 136 | |
| 137 | const { variables, outputResolution } = parseRenderOverrides(body); |
| 138 | |
| 139 | return { |
| 140 | outputPath, |
| 141 | fps, |
| 142 | quality, |
| 143 | workers, |
| 144 | useGpu, |
| 145 | debug, |
| 146 | entryFile, |
| 147 | format, |
| 148 | variables, |
| 149 | outputResolution, |
| 150 | videoFrameFormat, |
| 151 | }; |
| 152 | } |
| 153 | |
| 154 | function isPlainObject(value: unknown): value is Record<string, unknown> { |
| 155 | return typeof value === "object" && value !== null && !Array.isArray(value); |
no test coverage detected