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:
@@ -1,3 +1,5 @@
|
||||
import { parseShotScript } from './parseShots'
|
||||
|
||||
export function parseEditPasses(text: string): string[] | null {
|
||||
const src = String(text || '').replace(/\r\n/g, '\n')
|
||||
const marker = /^[ \t]*---pass\s+(\d+)---[ \t]*$/gim
|
||||
@@ -21,3 +23,40 @@ export function parseEditPasses(text: string): string[] | null {
|
||||
export function looksLikeShotScript(text: string) {
|
||||
return /^[ \t]*shot\s+[2-9]\d*\b/im.test(String(text || ''))
|
||||
}
|
||||
|
||||
function stripFences(text: string) {
|
||||
return String(text || '')
|
||||
.replace(/^\s*```(?:[\w-]+)?\s*\n?/, '')
|
||||
.replace(/\n?```\s*$/, '')
|
||||
.trim()
|
||||
}
|
||||
|
||||
function splitInlineShotMarkers(text: string) {
|
||||
return String(text || '')
|
||||
.replace(/\r\n/g, '\n')
|
||||
.replace(/(^|[^A-Za-z])shot\s+(\d+)\s*[:.)\-]?\s*/gi, '$1\n\nshot $2\n')
|
||||
.replace(/\n{3,}/g, '\n\n')
|
||||
.trim()
|
||||
}
|
||||
|
||||
function formatShotBody(body: string) {
|
||||
let text = String(body || '').trim()
|
||||
text = text.replace(/^\s*[:.\-–)]\s*/, '')
|
||||
text = text.replace(/\s*\[SHOT CONFIGURATION\]\s*/gi, '\n\n[SHOT CONFIGURATION]\n')
|
||||
text = text.replace(/\s*\[SUBJECT DIRECTION(?:\s*&\s*ACTION)?\]\s*/gi, '\n\n[SUBJECT DIRECTION & ACTION]\n')
|
||||
text = text.replace(/\s*\[AUDIO CUES\]\s*/gi, '\n\n[AUDIO CUES]\n')
|
||||
text = text.replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').trim()
|
||||
return text
|
||||
}
|
||||
|
||||
export function normalizeShotScript(text: string) {
|
||||
const src = splitInlineShotMarkers(stripFences(text))
|
||||
const shots = parseShotScript(src)
|
||||
if (!shots.length) return src
|
||||
if (shots.length === 1 && !/^[ \t]*shot\s+\d+\b/im.test(src)) {
|
||||
return formatShotBody(shots[0].prompt)
|
||||
}
|
||||
return shots
|
||||
.map(shot => `shot ${shot.n}\n${formatShotBody(shot.prompt)}`.trim())
|
||||
.join('\n\n')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
export type VideoEngineId = 'minimax' | 'ltx'
|
||||
export type VideoStartId = 'still' | 'text'
|
||||
export type MiniMaxGraphId = 'v1' | 'v2'
|
||||
export type VideoWorkflowId = 'v1' | 'v2' | 't2v' | 'ltx' | 'ltx-t2v'
|
||||
|
||||
export function parseVideoWorkflow(raw: unknown): VideoWorkflowId {
|
||||
const value = String(raw || '').trim()
|
||||
if (value === 'v2') return 'v2'
|
||||
if (value === 't2v') return 't2v'
|
||||
if (value === 'ltx' || value === 'ltx-i2v') return 'ltx'
|
||||
if (value === 'ltx-t2v') return 'ltx-t2v'
|
||||
return 'v1'
|
||||
}
|
||||
|
||||
export function videoEngineOf(id: VideoWorkflowId): VideoEngineId {
|
||||
return id.startsWith('ltx') ? 'ltx' : 'minimax'
|
||||
}
|
||||
|
||||
export function videoStartOf(id: VideoWorkflowId): VideoStartId {
|
||||
return id === 't2v' || id === 'ltx-t2v' ? 'text' : 'still'
|
||||
}
|
||||
|
||||
export function isLtxWorkflow(id: VideoWorkflowId) {
|
||||
return videoEngineOf(id) === 'ltx'
|
||||
}
|
||||
|
||||
export function isTextToVideo(id: VideoWorkflowId) {
|
||||
return videoStartOf(id) === 'text'
|
||||
}
|
||||
|
||||
export function minimaxGraphOf(id: VideoWorkflowId): MiniMaxGraphId {
|
||||
return id === 'v2' ? 'v2' : 'v1'
|
||||
}
|
||||
|
||||
export function workflowForExtension(id: VideoWorkflowId): VideoWorkflowId {
|
||||
if (id === 't2v') return 'v1'
|
||||
if (id === 'ltx-t2v') return 'ltx'
|
||||
return id
|
||||
}
|
||||
|
||||
export function composeVideoWorkflow(engine: VideoEngineId, start: VideoStartId, graph: MiniMaxGraphId = 'v1'): VideoWorkflowId {
|
||||
if (engine === 'ltx') return start === 'text' ? 'ltx-t2v' : 'ltx'
|
||||
if (start === 'text') return 't2v'
|
||||
return graph === 'v2' ? 'v2' : 'v1'
|
||||
}
|
||||
|
||||
export function defaultVideoSteps(turbo: boolean, workflow: string = 'v1') {
|
||||
const id = parseVideoWorkflow(workflow)
|
||||
if (!turbo) return isLtxWorkflow(id) ? 12 : 20
|
||||
if (isLtxWorkflow(id)) return 8
|
||||
return id === 'v2' ? 6 : 8
|
||||
}
|
||||
|
||||
export const LTX_UNET = 'PinkCherry_FineTune_int8_v1_8_LTX23.safetensors'
|
||||
export const LTX_DISTILLED_LORA = 'ltx-2.3-22b-distilled-lora-384-1.1.safetensors'
|
||||
export const LTX_NEGATIVE = 'blurry, low quality, jitter, deformed, static frame, freeze, watermark, bad anatomy, text, subtitles'
|
||||
@@ -1,4 +0,0 @@
|
||||
export function defaultVideoSteps(turbo: boolean, workflow = 'v1') {
|
||||
if (!turbo) return 20
|
||||
return workflow === 'v2' ? 6 : 8
|
||||
}
|
||||
Reference in New Issue
Block a user