Make Studio 2 the home bench with YuE2 music, LTX, and per-preview hide.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -14,8 +14,6 @@ export default defineEventHandler(async (event) => {
|
||||
throw createError({ statusCode: e.statusCode || 400, statusMessage: e.message });
|
||||
}
|
||||
const video = ['video', 'extend'].includes(request.mode);
|
||||
if (video && request.engine !== 'minimax')
|
||||
throw createError({ statusCode: 400, statusMessage: 'Use the existing xAIGen studio for LTX. Studio 2 video currently supports MiniMax.' });
|
||||
request.settings.loraStack = parsePostedLoraStack(request.settings.loraStack, video ? 'video' : 'image');
|
||||
request.settings.loraStack = filterLoraStackForUse(request.settings.loraStack, request.engine, useRuntimeConfig().public.studio === 'xaigen');
|
||||
if (!video)
|
||||
|
||||
@@ -5,5 +5,5 @@ export default defineEventHandler(event => {
|
||||
const { owner } = assertLibraryOwner(event);
|
||||
const library = publicLibrary(event);
|
||||
const catalog=studio2LoraCatalog(owner);
|
||||
return {...library,loraAliases:catalog.aliases,loraTriggers:catalog.triggers, stills:library.stills.filter(s=>existsSync(stillPath(owner,s.id))), clips:library.clips.filter(c=>existsSync(clipVideoPath(owner,c.id)))};
|
||||
return {...library,loraAliases:catalog.aliases,loraTriggers:catalog.triggers, stills:library.stills.filter(s=>existsSync(stillPath(owner,s.id))), clips:library.clips.filter(c=>existsSync(clipVideoPath(owner,c.id))), tracks:library.tracks || []};
|
||||
});
|
||||
|
||||
@@ -50,10 +50,6 @@ export default defineEventHandler(async (event) => {
|
||||
payload.prompt = text(body.tags, 2000)
|
||||
row.prompt = payload.prompt
|
||||
}
|
||||
if (body.yueProfile != null && row.kind === 'music' && payload.musicEngine === 'yue') {
|
||||
if (body.yueProfile !== 1 && body.yueProfile !== 3) throw createError({ statusCode: 400, statusMessage: 'Choose YuEGP profile 1 or 3.' })
|
||||
payload.yueProfile = body.yueProfile
|
||||
}
|
||||
if (body.lyrics != null) payload.lyrics = text(body.lyrics)
|
||||
const instrumental = optionalBool(body.instrumental)
|
||||
if (instrumental != null) payload.instrumental = instrumental
|
||||
@@ -63,8 +59,8 @@ export default defineEventHandler(async (event) => {
|
||||
if (body.negative != null) payload.negative = text(body.negative, 2000)
|
||||
const duration = optionalNumber(body.duration)
|
||||
if (duration != null) {
|
||||
if (row.kind === 'music' && payload.musicEngine === 'yue') {
|
||||
if (!Number.isInteger(duration) || duration < 30 || duration > 150) throw createError({ statusCode: 400, statusMessage: 'YuEGP duration must be 30–150 seconds.' })
|
||||
if (row.kind === 'music') {
|
||||
if (!Number.isInteger(duration) || duration < 30 || duration > 150) throw createError({ statusCode: 400, statusMessage: 'YuE2 duration must be 30–150 seconds.' })
|
||||
payload.duration = duration
|
||||
} else payload.duration = Math.min(120, Math.max(0.5, duration))
|
||||
}
|
||||
|
||||
@@ -74,8 +74,6 @@ async function prepareGraph(r: any) {
|
||||
|
||||
let graph: any;
|
||||
if (['video', 'extend'].includes(q.mode)) {
|
||||
if (q.engine !== 'minimax')
|
||||
throw new Error('Studio 2 native identity video currently requires MiniMax. Use the existing xAIGen studio for LTX.');
|
||||
let start = a;
|
||||
if (q.startClipId) {
|
||||
const dir = join(studio2Root(), r.id);
|
||||
@@ -86,14 +84,46 @@ async function prepareGraph(r: any) {
|
||||
start = await upload(r, 'start', readFileSync(dest));
|
||||
unlinkSync(dest);
|
||||
}
|
||||
const end = await load(q.endStillId, 'end'), guides = [];
|
||||
for (const [i, g] of q.guides.entries())
|
||||
guides.push({ image: await load(g.stillId, `guide${i}`), frame: g.frame });
|
||||
graph = nativeVideoGraph(template, q, { hero, start, end, guides }, prefix);
|
||||
await ensureComfyLoraNames('video');
|
||||
resolveGraphLoraNames(graph, 'video');
|
||||
applyUserLoraToGraph(graph, s.loraStack);
|
||||
r.graphId = 'studio2_minimax_native.json';
|
||||
if (q.engine === 'ltx') {
|
||||
const { buildWorkflow } = await import('../workflow');
|
||||
const { frameLength } = await import('../videoChain');
|
||||
const { ltxWorkflowEnabled, LTX_DISABLED_MESSAGE } = await import('~/utils/videoModels');
|
||||
if (!ltxWorkflowEnabled()) throw new Error(LTX_DISABLED_MESSAGE);
|
||||
if (!start) throw new Error('Choose a start still for LTX video.');
|
||||
const fps = s.fps || 24;
|
||||
graph = buildWorkflow({
|
||||
workflow: 'ltx',
|
||||
imageName: start,
|
||||
prompt: q.compiledPrompt,
|
||||
width: s.width,
|
||||
height: s.height,
|
||||
length: frameLength(s.duration || 5, fps),
|
||||
fps,
|
||||
steps: s.steps,
|
||||
cfg: s.cfg,
|
||||
seed: s.seed,
|
||||
samplerName: 'euler',
|
||||
scheduler: 'simple',
|
||||
turbo: false,
|
||||
duration: s.duration || 5,
|
||||
filenamePrefix: prefix + '/video',
|
||||
loraStack: s.loraStack,
|
||||
useIdentityRefs: false
|
||||
});
|
||||
await ensureComfyLoraNames('video');
|
||||
r.graphId = 'workflow_ltx_video.json';
|
||||
} else {
|
||||
if (q.engine !== 'minimax')
|
||||
throw new Error('Studio 2 video supports MiniMax H3 and LTX (xAIGen).');
|
||||
const end = await load(q.endStillId, 'end'), guides = [];
|
||||
for (const [i, g] of q.guides.entries())
|
||||
guides.push({ image: await load(g.stillId, `guide${i}`), frame: g.frame });
|
||||
graph = nativeVideoGraph(template, q, { hero, start, end, guides }, prefix);
|
||||
await ensureComfyLoraNames('video');
|
||||
resolveGraphLoraNames(graph, 'video');
|
||||
applyUserLoraToGraph(graph, s.loraStack);
|
||||
r.graphId = 'studio2_minimax_native.json';
|
||||
}
|
||||
}
|
||||
else {
|
||||
const mode = identityPlan?.mode ?? (q.mode === 'iterate' ? (a ? 'edit' : 'generate') : q.mode);
|
||||
|
||||
Reference in New Issue
Block a user