Use the parent clip last frame and new-segment join for extensions, not the stitched file's first frame. Show this account's library storage in Settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-08-28 10:54:46 -05:00
co-authored by Cursor
parent 231c59a7a7
commit 0af3cddb14
16 changed files with 571 additions and 70 deletions
+29 -7
View File
@@ -20,7 +20,7 @@ import {
setQueueJob,
updateShotQueue
} from '~/server/utils/shotQueue'
import { composeShotPrompt, type PermanenceRef } from '~/utils/globalLocks'
import { composeShotPrompt, allowIdentityRefs, type PermanenceRef } from '~/utils/globalLocks'
export type ChainImage = { filename: string; data: Buffer; type?: string }
@@ -110,7 +110,12 @@ function paramsFromJob(job: Job): VideoChainParams {
extensions: library.extensions || [],
workflow,
duration,
useIdentityRefs: library.useIdentityRefs === true,
useIdentityRefs: allowIdentityRefs(
library.useIdentityRefs === true,
library.permanenceRefs,
library.globalLocks,
library.shotPermanenceRefs
),
referenceImages,
globalLocks: library.globalLocks,
permanenceRefs: library.permanenceRefs,
@@ -336,8 +341,11 @@ export async function continueQueuedExtensions(
}
copyFileSync(currentPath, part1Path)
const parentId = job.library.parentClipId
const parentVideo = parentId ? clipVideoPath(job.library.ownerKey, parentId) : ''
const extractSource = parentVideo && existsSync(parentVideo) ? parentVideo : part1Path
try {
await extractLastFrame(part1Path, framePath)
await extractLastFrame(extractSource, framePath)
} catch (error) {
const detail = error instanceof Error ? error.message : String(error)
throw new Error(detail.includes('last frame')
@@ -345,6 +353,9 @@ export async function continueQueuedExtensions(
: `Could not extract the last frame for the next extension: ${detail}`)
}
const frame = readFileSync(framePath)
if (!frame.length || frame.length < 64) {
throw new Error('Could not extract the last frame for the next extension: the frame file was empty')
}
job.library.extendPart1Path = part1Path
job.library.prompt = ext.prompt
if (live.permanenceRefs?.length) {
@@ -354,11 +365,17 @@ export async function continueQueuedExtensions(
}
const sound = await probeHasAudio(currentPath)
const seed = Math.floor(Math.random() * 2_147_483_647)
const identity = allowIdentityRefs(
params.useIdentityRefs,
job.library.permanenceRefs || params.permanenceRefs,
job.library.globalLocks || params.globalLocks,
job.library.shotPermanenceRefs || params.shotPermanenceRefs
)
await ensureComfyReady(ready)
await queueMiniMax(job, {
prompt: ext.prompt,
image: params.useIdentityRefs
image: identity
? params.image
: { filename: 'last_frame.png', data: frame, type: 'image/png' },
width: params.width,
@@ -375,8 +392,8 @@ export async function continueQueuedExtensions(
persist,
workflow: workflowForExtension(params.workflow),
duration: ext.duration,
useIdentityRefs: params.useIdentityRefs,
referenceImages: params.useIdentityRefs ? params.referenceImages : []
useIdentityRefs: identity,
referenceImages: identity ? params.referenceImages : []
})
shotsLeft -= 1
@@ -520,7 +537,12 @@ export async function startQueueBurst(owner: string, queueId: string, count: num
familyId: queue.familyId,
parentClipId: clipId,
workflow: queue.workflow,
useIdentityRefs: queue.useIdentityRefs,
useIdentityRefs: allowIdentityRefs(
queue.useIdentityRefs === true,
queue.permanenceRefs,
queue.globalLocks,
queue.segments.map(segment => segment.permanenceRefs || [])
),
queueId: queue.id,
queueAutoRun: count === 'all',
queueBudget: n,