Files

72 lines
3.9 KiB
JavaScript

export function nativeVideoGraph(template, request, files, prefix) {
const appearance = request.lockFace !== false || request.lockOutfit !== false
if (appearance && !files.hero) throw new Error('Identity still is required; never substitute the start frame.')
const g=structuredClone(template), s=request.settings
const textToVideo = request.mode === 'video' && !appearance && !files.start
if (appearance) { g.hero.inputs.image=files.hero; g['155'].inputs['ref_images.ref_image_0']=['hero',0] }
else { delete g.hero; delete g['155'].inputs['ref_images.ref_image_0'] }
Object.assign(g['155'].inputs,{prompt:request.compiledPrompt,width:s.width,height:s.height,length:Math.ceil((s.duration*(s.fps || 24)-5)/17)*17+5,ref_image_size:'max'})
if (textToVideo) {
// Studio 1 T2V: MiniMaxH3ImageToVideo with first_frame disconnected (no empty placeholder still).
g['155'].class_type = 'MiniMaxH3ImageToVideo'
g['155']._meta = { title: 'MiniMax H3 Text to Video' }
delete g['155'].inputs['ref_images.ref_image_0']
delete g['155'].inputs.ref_image_size
delete g['155'].inputs.audio_vae
delete g['155'].inputs.first_frame
delete g['155'].inputs.last_frame
}
let conditioning=['155',0]
for (const [name,image,frame] of [['start',files.start,0],['end',files.end,-1],...(files.guides||[]).map((f,i)=>[`guide${i}`,f.image,f.frame])]) {
if (!image) continue
g[`load_${name}`]={class_type:'LoadImage',inputs:{image}}
g[name]={class_type:'MiniMaxH3AddGuide',inputs:{positive:conditioning,latent:['155',1],vae:['161',0],image:[`load_${name}`,0],frame_idx:frame}}
conditioning=[name,0]
}
g.guidance.inputs={conditioning,guidance:s.cfg}
g['171'].inputs.noise_seed=s.seed
g['172'].inputs.fps=s.fps || 24
g['179'].inputs.value=s.steps;g['180'].inputs.value=s.steps;g['181'].inputs.value=s.turbo
g['92'].inputs.filename_prefix=prefix+'/video'
g.anchor_frame={class_type:'ImageFromBatch',inputs:{image:['164',0],batch_index:g['155'].inputs.length-1,length:1}}
g.anchor_save={class_type:'SaveImage',inputs:{images:['anchor_frame',0],filename_prefix:prefix+'/anchor'}}
return g
}
export function attachHeroReference(g, image) {
if (!image) return g
const guider = g['18']; if (!guider) throw new Error('Klein reference conditioning is missing.')
g.hero_load={class_type:'LoadImage',inputs:{image}}
g.hero_scale={class_type:'ImageScaleToTotalPixels',inputs:{image:['hero_load',0],upscale_method:'lanczos',megapixels:1,resolution_steps:1}}
g.hero_encode={class_type:'VAEEncode',inputs:{pixels:['hero_scale',0],vae:['6',0]}}
for (const name of ['positive','negative']) {
if (!guider.inputs[name]) continue
g[`hero_${name}`]={class_type:'ReferenceLatent',inputs:{conditioning:guider.inputs[name],latent:['hero_encode',0]}}
guider.inputs[name]=[`hero_${name}`,0]
}
return g
}
export function sampleKleinSource(graph, denoise = .65) {
if (!graph['11'] || !graph['19'] || !graph['17']) throw new Error('Klein source sampling nodes are missing.')
graph['19'].inputs.latent_image=['11',0]
delete graph['14']
const steps=graph['17'].inputs.steps
graph['17'].inputs.steps=Math.ceil(steps/denoise)
graph.source_sigmas={class_type:'SplitSigmasDenoise',inputs:{sigmas:['17',0],denoise}}
graph['19'].inputs.sigmas=['source_sigmas',1]
return graph
}
/** The shared image builders have their own defaults and megapixel scalers.
* Studio 2 must send its already resolved canvas size through those nodes. */
export function applyResolvedImageSize(graph, {width,height}) {
for (const node of Object.values(graph)) {
if (node.class_type === 'ImageScaleToTotalPixels') {
node.class_type='ImageScale'
node.inputs={image:node.inputs.image,upscale_method:'lanczos',width,height,crop:'disabled'}
} else {
if (typeof node.inputs?.width === 'number') node.inputs.width=width
if (typeof node.inputs?.height === 'number') node.inputs.height=height
}
}
return graph
}