Files
aigen/server/utils/resolution.ts
T
TowstyandCursor dbe45fd2ff Add generation quality controls and bind them to the MiniMax workflow.
Expose aspect, CFG, FPS, sampler, and scheduler in Generation settings, persist them on clips and held jobs, and inject those values into the Comfy graph. Keep the image picker stills-only and allow renaming library clips.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-25 22:55:06 -05:00

97 lines
3.1 KiB
TypeScript

const SHORT_EDGE = 768
const LONG_EDGE = 1344
export const ASPECT_PRESETS: Record<string, { width: number; height: number }> = {
'16:9': { width: 1344, height: 768 },
'9:16': { width: 768, height: 1344 },
'1:1': { width: 960, height: 960 }
}
export function snap32(value: number) {
return Math.max(32, Math.round(value / 32) * 32)
}
/** Fit the source still to H3's native 768 short-edge canvas, 32px grid, 1344 long-edge cap. */
export function autoResolution(srcWidth: number, srcHeight: number) {
const ratio = srcWidth / Math.max(srcHeight, 1)
let width: number
let height: number
if (ratio >= 1) {
height = SHORT_EDGE
width = Math.min(LONG_EDGE, height * ratio)
} else {
width = SHORT_EDGE
height = Math.min(LONG_EDGE, width / ratio)
}
width = snap32(width)
height = snap32(height)
if (width > LONG_EDGE) width = LONG_EDGE
if (height > LONG_EDGE) height = LONG_EDGE
return { width: Math.max(32, width), height: Math.max(32, height) }
}
export function imageDimensions(buf: Buffer) {
if (buf.length < 24) return null
if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) {
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) }
}
if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46) {
return { width: buf.readUInt16LE(6), height: buf.readUInt16LE(8) }
}
if (buf[0] === 0x42 && buf[1] === 0x4d && buf.length >= 26) {
return { width: buf.readInt32LE(18), height: Math.abs(buf.readInt32LE(22)) }
}
if (buf.toString('ascii', 0, 4) === 'RIFF' && buf.toString('ascii', 8, 12) === 'WEBP') {
return webpSize(buf)
}
if (buf[0] === 0xff && buf[1] === 0xd8) {
return jpegSize(buf)
}
return null
}
function webpSize(buf: Buffer) {
const kind = buf.toString('ascii', 12, 16)
if (kind === 'VP8X' && buf.length >= 30) {
const width = 1 + buf[24] + (buf[25] << 8) + (buf[26] << 16)
const height = 1 + buf[27] + (buf[28] << 8) + (buf[29] << 16)
return { width, height }
}
if (kind === 'VP8 ' && buf.length >= 30) {
return { width: buf.readUInt16LE(26) & 0x3fff, height: buf.readUInt16LE(28) & 0x3fff }
}
if (kind === 'VP8L' && buf.length >= 25) {
const bits = buf.readUInt32LE(21)
return { width: (bits & 0x3fff) + 1, height: ((bits >> 14) & 0x3fff) + 1 }
}
return null
}
function jpegSize(buf: Buffer) {
let offset = 2
while (offset + 9 < buf.length) {
if (buf[offset] !== 0xff) return null
const marker = buf[offset + 1]
if (marker === 0xd8 || marker === 0xd9) {
offset += 2
continue
}
const length = buf.readUInt16BE(offset + 2)
if (marker >= 0xc0 && marker <= 0xc3) {
return { width: buf.readUInt16BE(offset + 7), height: buf.readUInt16BE(offset + 5) }
}
offset += 2 + length
}
return null
}
export function resolveOutputSize(aspect: string | undefined, _image: Buffer, fallbackWidth?: number, fallbackHeight?: number) {
if (aspect && ASPECT_PRESETS[aspect]) {
return ASPECT_PRESETS[aspect]
}
if (Number(fallbackWidth) && Number(fallbackHeight)) {
return { width: snap32(Number(fallbackWidth)), height: snap32(Number(fallbackHeight)) }
}
return ASPECT_PRESETS['16:9']
}