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:
Towsty
2026-08-27 19:38:53 -05:00
co-authored by Cursor
parent 2bebeea526
commit 40cd17f35c
15 changed files with 676 additions and 120 deletions
+24 -10
View File
@@ -1,3 +1,5 @@
import { LTX_NEGATIVE } from '~/utils/videoModels'
let comfyHostOverride = ''
export function setComfyHostOverride(url: string) {
@@ -188,7 +190,12 @@ export function extractPromptFromHistory(entry: unknown) {
const text = String(node.inputs?.prompt || node.inputs?.text || '').trim()
if (!text) continue
if (node?.class_type === 'MiniMaxH3ReferenceToVideo') return text
if (node?.class_type === 'MiniMaxH3ImageToVideo') imageToVideo = text
if (node?.class_type === 'MiniMaxH3ImageToVideo' || node?.class_type === 'MiniMaxH3TextToVideo') imageToVideo = text
if (node?.class_type === 'CLIPTextEncode') {
const title = String((node as { _meta?: { title?: string } })._meta?.title || '')
if (/negative/i.test(title) || text === LTX_NEGATIVE) continue
if (!imageToVideo) imageToVideo = text
}
}
return imageToVideo
}
@@ -200,14 +207,18 @@ export function extractClipMetaFromHistory(entry: unknown) {
if (!graph || typeof graph !== 'object') return meta
for (const node of Object.values(graph as Record<string, { class_type?: string; inputs?: Record<string, unknown> }>)) {
const inputs = node?.inputs || {}
if (node?.class_type === 'MiniMaxH3ImageToVideo') {
if (node?.class_type === 'MiniMaxH3ImageToVideo' || node?.class_type === 'MiniMaxH3TextToVideo' || node?.class_type === 'EmptyLTXVLatentVideo') {
meta.width = Number(inputs.width || meta.width)
meta.height = Number(inputs.height || meta.height)
}
if (node?.class_type === 'BasicScheduler' || node?.class_type === 'PrimitiveInt') {
if (node?.class_type === 'BasicScheduler' || node?.class_type === 'PrimitiveInt' || node?.class_type === 'LTXVScheduler') {
const steps = Number(inputs.steps || 0)
if (steps > 0) meta.steps = steps
}
if (node?.class_type === 'ImageResize+' && typeof inputs.width === 'number' && typeof inputs.height === 'number') {
meta.width = Number(inputs.width)
meta.height = Number(inputs.height)
}
if (node?.class_type === 'RandomNoise' || node?.class_type === 'SamplerMiniMax' || node?.class_type === 'KSampler') {
const seed = Number(inputs.noise_seed ?? inputs.seed ?? 0)
if (seed) meta.seed = seed
@@ -276,13 +287,16 @@ export function comfyFilenamePrefix() {
}
export function isOurComfyVideo(video: { filename: string; subfolder: string }) {
const prefix = comfyFilenamePrefix().replace(/\/$/, '')
const parts = prefix.split('/')
const namePrefix = parts[parts.length - 1]
const sub = parts.length > 1 ? parts.slice(0, -1).join('/') : 'video'
const nameOk = video.filename.startsWith(namePrefix)
const subOk = !video.subfolder || video.subfolder === sub
return nameOk && subOk
const prefixes = [comfyFilenamePrefix(), 'video/LTX23']
return prefixes.some((raw) => {
const prefix = raw.replace(/\/$/, '')
const parts = prefix.split('/')
const namePrefix = parts[parts.length - 1]
const sub = parts.length > 1 ? parts.slice(0, -1).join('/') : 'video'
const nameOk = video.filename.startsWith(namePrefix) || video.filename.startsWith('LTX23')
const subOk = !video.subfolder || video.subfolder === sub
return nameOk && subOk
})
}
export function purgeComfyEnabled() {