Add clip extension from the last frame with FFmpeg stitching.
Let a finished player continue into a new MiniMax segment, then losslessly concat the parts and discard the intermediate files so the library keeps one extended MP4. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+25
-4
@@ -1,4 +1,4 @@
|
||||
import { NODE_LABELS, isEncodingNode } from '~/server/utils/workflow'
|
||||
import { existsSync } from 'node:fs'
|
||||
import type { Job } from '~/server/utils/jobs'
|
||||
|
||||
function classifyError(message: string) {
|
||||
@@ -42,6 +42,7 @@ export function watchComfyJob(job: Job): Promise<void> {
|
||||
job.error = classifyError(error)
|
||||
emitJob(job, { type: 'error', error: job.error, message: job.error })
|
||||
if (job.id) deletePendingJob(job.id)
|
||||
removeExtendTemp(job.library?.extendTmpDir)
|
||||
resolve()
|
||||
}
|
||||
|
||||
@@ -54,9 +55,24 @@ export function watchComfyJob(job: Job): Promise<void> {
|
||||
cleanup()
|
||||
try {
|
||||
job.video = video
|
||||
emitJob(job, { type: 'status', message: 'Saving to library...', progress: 98 })
|
||||
const stitching = Boolean(job.library?.extendPart1Path && job.library.extendTmpDir)
|
||||
emitJob(job, {
|
||||
type: 'status',
|
||||
message: stitching ? 'Extracting last frame & stitching extension...' : 'Saving to library...',
|
||||
progress: stitching ? 97 : 98
|
||||
})
|
||||
if (job.library) {
|
||||
const buffer = await downloadComfyVideo(video)
|
||||
let buffer = await downloadComfyVideo(video)
|
||||
if (stitching) {
|
||||
if (!existsSync(job.library.extendPart1Path!)) {
|
||||
throw new Error('Extension source clip was missing during stitch')
|
||||
}
|
||||
buffer = await stitchExtension({
|
||||
part1Path: job.library.extendPart1Path!,
|
||||
part2: buffer,
|
||||
tmpDir: job.library.extendTmpDir!
|
||||
})
|
||||
}
|
||||
const clip = await saveClip({
|
||||
ownerKey: job.library.ownerKey,
|
||||
folderId: job.library.folderId,
|
||||
@@ -87,11 +103,15 @@ export function watchComfyJob(job: Job): Promise<void> {
|
||||
imageSubfolder: job.library.imageSubfolder,
|
||||
promptId: job.promptId
|
||||
})
|
||||
removeExtendTemp(job.library.extendTmpDir)
|
||||
job.library.extendTmpDir = undefined
|
||||
}
|
||||
job.status = 'complete'
|
||||
emitJob(job, {
|
||||
type: 'complete',
|
||||
message: job.library?.folderLocked ? 'Saved to the locked folder. Unlock it to view.' : 'Video ready',
|
||||
message: job.library?.folderLocked
|
||||
? 'Saved to the locked folder. Unlock it to view.'
|
||||
: (job.library?.extendSourceClipId ? 'Extended video ready' : 'Video ready'),
|
||||
progress: 100,
|
||||
filename: job.library?.folderLocked ? undefined : video.filename,
|
||||
subfolder: job.library?.folderLocked ? undefined : video.subfolder,
|
||||
@@ -102,6 +122,7 @@ export function watchComfyJob(job: Job): Promise<void> {
|
||||
})
|
||||
deletePendingJob(job.id)
|
||||
} catch (saveError) {
|
||||
removeExtendTemp(job.library?.extendTmpDir)
|
||||
const message = saveError instanceof Error ? saveError.message : String(saveError)
|
||||
job.status = 'error'
|
||||
job.error = `Video generated but library save failed: ${message}`
|
||||
|
||||
Reference in New Issue
Block a user