Files
aigen/server/utils/studio2/media.ts
T
TowstyandCursor 65c69b779a Stop resampling the extend start frame every hop
upload() ran fitStill (ffmpeg scale+pad) on every image except hero,
including the start/handoff still. Four chained extends resampled the
anchor frame four times and destroyed identity even with the hero
reference and lock text intact.

- upload(): only fit when the still's aspect truly differs from the
  job canvas (needsFit); never fit hero or start regardless.
- Track handoffSource (anchor|extract) and whether the start frame was
  fitted; surface both in job metadata and Details.
- extendSelected(): re-binding an already-selected clip on task switch
  must not stomp the user's overridden locks/settings back to the
  clip's saved values (regression introduced by the last hotfix push).
- Add tests/studio2-extend-fit.test.mjs: two extends at identical
  width/height reach Comfy with the anchor PNG byte-identical, no
  ffmpeg fit.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-11 21:12:24 -05:00

24 lines
1.2 KiB
TypeScript

import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs'
import { join } from 'node:path'
import { tmpdir } from 'node:os'
import { runFfmpeg } from '../ffmpeg'
import { imageDimensions } from '../resolution'
export async function fitStill(data: Buffer, width: number, height: number) {
const dir=mkdtempSync(join(tmpdir(),'studio2-fit-'))
try {
const input=join(dir,'source'),output=join(dir,'fitted.png')
writeFileSync(input,data)
await runFfmpeg(['-y','-i',input,'-vf',`scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1`,'-frames:v','1',output])
return readFileSync(output)
} finally { rmSync(dir,{recursive:true,force:true}) }
}
/** Only scale/pad when the still's aspect truly differs from the job canvas.
* A matching frame (typical extend handoff) must reach Comfy byte-identical. */
export function needsFit(data: Buffer, width: number, height: number) {
const size = imageDimensions(data)
if (!size?.width || !size?.height) return true
if (size.width === width && size.height === height) return false
const target = width / height, actual = size.width / size.height
return Math.abs(target - actual) > 0.01
}