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>
This commit is contained in:
Towsty
2026-09-11 21:12:24 -05:00
co-authored by Cursor
parent c6451b10ce
commit 65c69b779a
5 changed files with 144 additions and 4 deletions
+10
View File
@@ -2,6 +2,7 @@ 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 {
@@ -11,3 +12,12 @@ export async function fitStill(data: Buffer, width: number, height: number) {
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
}