Always prepend the Comfy identity prefix and keep refs on every shot.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+3
-3
@@ -109,7 +109,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<p v-if="shotScriptMode && studioMode === 'video'" class="mb-2 text-xs text-zinc-500">
|
<p v-if="shotScriptMode && studioMode === 'video'" class="mb-2 text-xs text-zinc-500">
|
||||||
Paste a block. Text before the first marker is shot 1. Put <span class="font-medium text-zinc-300">shot 2</span>, <span class="font-medium text-zinc-300">shot 3</span> on their own lines to queue extensions — a line like <span class="font-medium text-zinc-300">Shot Type</span> will not start a new shot. Every shot uses the duration slider.
|
Paste a block. Text before the first marker is shot 1. Put <span class="font-medium text-zinc-300">shot 2</span>, <span class="font-medium text-zinc-300">shot 3</span> on their own lines to queue extensions — a line like <span class="font-medium text-zinc-300">Shot Type</span> will not start a new shot. Every shot uses the duration slider.
|
||||||
<span v-if="identityPrompting"> Identity boilerplate is prepended to the first shot only, then concatenated into Reference to Video.</span>
|
<span v-if="identityPrompting"> The Comfy identity prefix is prepended to every shot, and the same Picture 1 / extra stills are sent with each shot.</span>
|
||||||
</p>
|
</p>
|
||||||
<p v-else-if="identityPrompting" class="mb-2 text-xs text-zinc-500">
|
<p v-else-if="identityPrompting" class="mb-2 text-xs text-zinc-500">
|
||||||
Write camera, action, and audio. That is the dynamic action prompt. We concatenate the <span class="font-medium text-zinc-300"><Picture 1></span> identity prefix in front of it and send the result to <span class="font-medium text-zinc-300">Reference to Video</span>, not Image to Video.
|
Write camera, action, and audio. That is the dynamic action prompt. We concatenate the <span class="font-medium text-zinc-300"><Picture 1></span> identity prefix in front of it and send the result to <span class="font-medium text-zinc-300">Reference to Video</span>, not Image to Video.
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
{{ index === 0 ? 'Initial' : `Extension ${index}` }}
|
{{ index === 0 ? 'Initial' : `Extension ${index}` }}
|
||||||
<span class="font-normal text-zinc-500"> · shot {{ shot.n }} · {{ formatClipDuration(duration) }}</span>
|
<span class="font-normal text-zinc-500"> · shot {{ shot.n }} · {{ formatClipDuration(duration) }}</span>
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-1 whitespace-pre-wrap text-xs text-zinc-300">{{ shot.prompt }}</p>
|
<p class="mt-1 whitespace-pre-wrap text-xs text-zinc-300">{{ identityPrompting ? buildIdentityPrompt(shot.prompt, extraIdentityPictures) : shot.prompt }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -321,7 +321,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="useIdentityRefs" class="text-xs text-zinc-500">Picture 1 is the start still. Extra stills map to Picture 2–5. Empty extra slots reuse the start still so Comfy does not error. Identity refs are not reused on queued extensions.</p>
|
<p v-if="useIdentityRefs" class="text-xs text-zinc-500">Picture 1 is the start still and stays Picture 1 on every shot, including queued extensions. Extra stills map to Picture 2–5. Empty extra slots reuse the start still.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+12
-10
@@ -309,17 +309,17 @@ async function queueMiniMax(
|
|||||||
|
|
||||||
const done = watchComfyJob(job, { persist: params.persist })
|
const done = watchComfyJob(job, { persist: params.persist })
|
||||||
job.status = 'uploading'
|
job.status = 'uploading'
|
||||||
const uploading = (job.library?.chainIndex || 0) > 0
|
const chainIndex = job.library?.chainIndex || 0
|
||||||
? 'Uploading last frame to ComfyUI...'
|
const uploading = params.useIdentityRefs
|
||||||
: 'Uploading image to ComfyUI...'
|
? (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
|
const queueing = (job.library?.chainIndex || 0) > 0
|
||||||
? 'Queueing extension on MiniMax H3...'
|
? 'Queueing extension on MiniMax H3...'
|
||||||
: 'Queueing MiniMax H3 job...'
|
: 'Queueing MiniMax H3 job...'
|
||||||
emitChainJob(job, { type: 'status', message: uploading, progress: 4 })
|
emitChainJob(job, { type: 'status', message: uploading, progress: 4 })
|
||||||
const uploaded = await uploadImage(params.image, job.id)
|
const uploaded = await uploadImage(params.image, job.id)
|
||||||
const referenceNames: string[] = ['', '', '', '']
|
const referenceNames: string[] = ['', '', '', '']
|
||||||
const chainIndex = job.library?.chainIndex || 0
|
if (params.useIdentityRefs) {
|
||||||
if (chainIndex === 0) {
|
|
||||||
for (const [index, ref] of (params.referenceImages || []).entries()) {
|
for (const [index, ref] of (params.referenceImages || []).entries()) {
|
||||||
if (!ref?.data?.length) continue
|
if (!ref?.data?.length) continue
|
||||||
const next = await uploadImage({
|
const next = await uploadImage({
|
||||||
@@ -353,8 +353,8 @@ async function queueMiniMax(
|
|||||||
sound: params.sound,
|
sound: params.sound,
|
||||||
workflow: params.workflow,
|
workflow: params.workflow,
|
||||||
duration: params.duration,
|
duration: params.duration,
|
||||||
useIdentityRefs: chainIndex === 0 && params.useIdentityRefs,
|
useIdentityRefs: params.useIdentityRefs,
|
||||||
referenceImageNames: chainIndex === 0 ? referenceNames : []
|
referenceImageNames: params.useIdentityRefs ? referenceNames : []
|
||||||
})
|
})
|
||||||
|
|
||||||
const queued = await queuePrompt(graph, job.clientId)
|
const queued = await queuePrompt(graph, job.clientId)
|
||||||
@@ -466,7 +466,9 @@ async function runGeneration(
|
|||||||
await ensureComfyReady(ready)
|
await ensureComfyReady(ready)
|
||||||
await queueMiniMax(job, {
|
await queueMiniMax(job, {
|
||||||
prompt: ext.prompt,
|
prompt: ext.prompt,
|
||||||
image: { filename: 'last_frame.png', data: frame, type: 'image/png' },
|
image: params.useIdentityRefs
|
||||||
|
? params.image
|
||||||
|
: { filename: 'last_frame.png', data: frame, type: 'image/png' },
|
||||||
width: params.width,
|
width: params.width,
|
||||||
height: params.height,
|
height: params.height,
|
||||||
steps: params.steps,
|
steps: params.steps,
|
||||||
@@ -481,8 +483,8 @@ async function runGeneration(
|
|||||||
persist: isLast,
|
persist: isLast,
|
||||||
workflow: params.workflow,
|
workflow: params.workflow,
|
||||||
duration: ext.duration,
|
duration: ext.duration,
|
||||||
useIdentityRefs: false,
|
useIdentityRefs: params.useIdentityRefs,
|
||||||
referenceImages: []
|
referenceImages: params.useIdentityRefs ? params.referenceImages : []
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!isLast) {
|
if (!isLast) {
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ export function identityBoilerplate(extraRefs: number | number[] = 0) {
|
|||||||
: extras.length === 1
|
: extras.length === 1
|
||||||
? ` <Picture ${extras[0]}> is a secondary reference angle for profile details.`
|
? ` <Picture ${extras[0]}> is a secondary reference angle for profile details.`
|
||||||
: ` ${extras.map(n => `<Picture ${n}>`).join(' and ')} are secondary reference angles for profile features only.`
|
: ` ${extras.map(n => `<Picture ${n}>`).join(' and ')} are secondary reference angles for profile features only.`
|
||||||
return `<Picture 1> is the exact facial identity, hairstyle, body proportions and clothing of the main character. Preserve this identity with high fidelity even when the subject temporarily leaves the frame and returns.${secondary}`
|
return `<Picture 1> is the primary identity anchor defining exact facial geometry, hairstyle, and body proportions.${secondary} Strictly maintain the structural proportions of <Picture 1> with high fidelity when moving.`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function looksLikeIdentityPrompt(text: string) {
|
export function looksLikeIdentityPrompt(text: string) {
|
||||||
return /primary identity anchor|exact facial identity/i.test(String(text || ''))
|
return /^<Picture 1> is the (primary identity anchor|exact facial identity)/i.test(String(text || '').trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildIdentityPrompt(actionPrompt: string, extraRefs: number | number[] = 0) {
|
export function buildIdentityPrompt(actionPrompt: string, extraRefs: number | number[] = 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user