Add MiniMax and PinkCherry LTX as pickable video engines, with still or text start.
Shot 1 can run text-to-video; later shots still extend from the last frame. Recommend output is also normalized into shot-script cards. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+24
-15
@@ -5,6 +5,7 @@ import { pendingFromJob, remainingAfterCurrentShot, writePendingJob } from '~/se
|
||||
import { emitChainJob, waitForComfySocket, watchComfyJob } from '~/server/utils/watch'
|
||||
import { comfyFilenamePrefix, queuePrompt, uploadImage } from '~/server/utils/comfy'
|
||||
import { buildWorkflow } from '~/server/utils/workflow'
|
||||
import { isLtxWorkflow, isTextToVideo, parseVideoWorkflow, workflowForExtension, type VideoWorkflowId } from '~/utils/videoModels'
|
||||
import { clipVideoPath, clipTitle, deleteRetryDraft, extendTempDir, getClip, nextClipPartName, removeExtendTemp, stillPath } from '~/server/utils/library'
|
||||
import { extractLastFrame, probeHasAudio } from '~/server/utils/ffmpeg'
|
||||
import { ensureComfyReady } from '~/server/utils/comfyLifecycle'
|
||||
@@ -23,7 +24,7 @@ export type ChainImage = { filename: string; data: Buffer; type?: string }
|
||||
|
||||
type VideoChainParams = {
|
||||
prompt: string
|
||||
image: ChainImage
|
||||
image?: ChainImage | null
|
||||
width: number
|
||||
height: number
|
||||
steps: number
|
||||
@@ -36,7 +37,7 @@ type VideoChainParams = {
|
||||
samplerName: string
|
||||
scheduler: string
|
||||
extensions: { prompt: string; duration: number }[]
|
||||
workflow: 'v1' | 'v2'
|
||||
workflow: VideoWorkflowId
|
||||
duration: number
|
||||
useIdentityRefs: boolean
|
||||
referenceImages: Array<ChainImage | null>
|
||||
@@ -76,7 +77,8 @@ function paramsFromJob(job: Job): VideoChainParams {
|
||||
throw new Error('Cannot continue the shot chain: job metadata is missing')
|
||||
}
|
||||
const image = loadStillImage(library.ownerKey, library.stillId, library.stillFilename)
|
||||
if (!image?.data.length) {
|
||||
const workflow = parseVideoWorkflow(library.workflow)
|
||||
if (!image?.data.length && !isTextToVideo(workflow)) {
|
||||
throw new Error('Cannot continue the shot chain: the start still is missing from the library')
|
||||
}
|
||||
const referenceImages: Array<ChainImage | null> = [null, null, null, null]
|
||||
@@ -101,7 +103,7 @@ function paramsFromJob(job: Job): VideoChainParams {
|
||||
samplerName: library.samplerName || 'res_multistep',
|
||||
scheduler: library.scheduler || 'simple',
|
||||
extensions: library.extensions || [],
|
||||
workflow: library.workflow || 'v1',
|
||||
workflow,
|
||||
duration,
|
||||
useIdentityRefs: library.useIdentityRefs === true,
|
||||
referenceImages
|
||||
@@ -121,14 +123,21 @@ export async function queueMiniMax(
|
||||
const done = watchComfyJob(job, { persist: params.persist })
|
||||
job.status = 'uploading'
|
||||
const chainIndex = job.library?.chainIndex || 0
|
||||
const uploading = params.useIdentityRefs
|
||||
? (chainIndex > 0 ? 'Uploading identity stills for next shot...' : 'Uploading image to ComfyUI...')
|
||||
: (chainIndex > 0 ? 'Uploading last frame to ComfyUI...' : 'Uploading image to ComfyUI...')
|
||||
const queueing = (job.library?.chainIndex || 0) > 0
|
||||
? 'Queueing extension on MiniMax H3...'
|
||||
: 'Queueing MiniMax H3 job...'
|
||||
const graphId = chainIndex > 0 ? workflowForExtension(params.workflow) : params.workflow
|
||||
const engineName = isLtxWorkflow(graphId) ? 'LTX-2.3' : 'MiniMax H3'
|
||||
const hasImage = Boolean(params.image?.data?.length)
|
||||
const uploading = !hasImage
|
||||
? `Queueing ${engineName} text-to-video…`
|
||||
: params.useIdentityRefs
|
||||
? (chainIndex > 0 ? 'Uploading identity stills for next shot...' : 'Uploading image to ComfyUI...')
|
||||
: (chainIndex > 0 ? 'Uploading last frame to ComfyUI...' : 'Uploading image to ComfyUI...')
|
||||
const queueing = chainIndex > 0
|
||||
? `Queueing extension on ${engineName}...`
|
||||
: `Queueing ${engineName} job...`
|
||||
emitChainJob(job, { type: 'status', message: uploading, progress: 4 })
|
||||
const uploaded = await uploadImage(params.image, job.id)
|
||||
const uploaded = hasImage && params.image
|
||||
? await uploadImage(params.image, job.id)
|
||||
: { name: '', subfolder: '' }
|
||||
const referenceNames: string[] = ['', '', '', '']
|
||||
if (params.useIdentityRefs) {
|
||||
for (const [index, ref] of (params.referenceImages || []).entries()) {
|
||||
@@ -161,9 +170,9 @@ export async function queueMiniMax(
|
||||
fps: params.fps,
|
||||
samplerName: params.samplerName,
|
||||
scheduler: params.scheduler,
|
||||
filenamePrefix: comfyFilenamePrefix(),
|
||||
sound: params.sound,
|
||||
workflow: params.workflow,
|
||||
filenamePrefix: isLtxWorkflow(graphId) ? 'video/LTX23' : comfyFilenamePrefix(),
|
||||
sound: params.sound && !isLtxWorkflow(graphId),
|
||||
workflow: graphId,
|
||||
duration: params.duration,
|
||||
useIdentityRefs: params.useIdentityRefs,
|
||||
referenceImageNames: params.useIdentityRefs ? referenceNames : []
|
||||
@@ -317,7 +326,7 @@ export async function continueQueuedExtensions(
|
||||
samplerName: params.samplerName,
|
||||
scheduler: params.scheduler,
|
||||
persist,
|
||||
workflow: params.workflow,
|
||||
workflow: workflowForExtension(params.workflow),
|
||||
duration: ext.duration,
|
||||
useIdentityRefs: params.useIdentityRefs,
|
||||
referenceImages: params.useIdentityRefs ? params.referenceImages : []
|
||||
|
||||
Reference in New Issue
Block a user