comfyui-character-swap
Put your own character into any photo, keeping its framing, lighting and pose. Nano Banana Pro in ComfyUI on your own Google billing.
Nodes (3)
Put Your Character Into a Real Photo Without Rebuilding the Shot
Grade From One Photo, Light From Another, Your Character in the Middle
Turn Any Reference Photo Into a Cinematography Breakdown
ComfyUI Character Swap — Nano Banana Pro on your own Google API key
Put your own character into any photo, keeping that photo's framing, lighting, colour grade and pose. Runs on Nano Banana Pro (gemini-3-pro-image-preview) and other Google Gemini image models, billed to your own Google account.
| Node | What it does | |---|---| | Character Swap (Nano Banana Pro) | Replaces the person in a reference photo with your character, keeping the original shot intact | | Multi Reference Shot (Nano Banana Pro) | Builds a new shot from several references at once — grade from one, light from another, framing from a third | | Read the Shot | Writes an editable breakdown of a photo — framing, lens, lighting, pose. Cheap text call |
Why the swap holds up
Most setups send the model one prompt followed by a pile of images and hope the ordering survives. It usually doesn't. This node fixes that, and four other things that break these edits in practice.
Role labels are interleaved, not batched. Every image is sent immediately after a text label saying exactly what it is — SHOT REFERENCE, CHARACTER, WARDROBE. The model never infers roles from position.
This is what makes same-subject swaps work. Replacing a woman in a photo with a different woman fails almost everywhere else, because nothing distinguishes the two and the model keeps the one already embedded in the composition.
The original subject is removed explicitly. Every request states who to delete, not only who to add. Told only what to add, a model leaves the original in place.
Aspect ratio comes from the reference. A 1920×802 scope reference returns a 1584×672 frame. Forcing a wide reference into 16:9 re-blocks the entire composition, and it is the most common reason a swap "looks wrong" for no obvious reason.
Pose is stated, never assumed. Left unsaid, the model treats pose as free invention. When blocking_notes is empty the node instructs it to match the reference exactly, so empty means keep, not guess.
Diagnostics tell you what actually happened. finishReason, safety flags, whether the aspect was honoured. Google returns HTTP 200 even when it has quietly substituted a face, so without this a refusal is indistinguishable from a bad prompt.
Seeds are clamped to int32 as well — Vertex rejects ComfyUI's default 2^64 seeds outright.
Multi Reference Shot
Character Swap takes one photo and puts your person in it. Multi Reference Shot is for when no single photo is the shot — you want the grade from one image, the light from a second, the framing from a third, and your own character standing in the middle of it.
| Input | Its label authorises only |
|---|---|
| character | the person. Batch several angles here — more angles hold the likeness better than one portrait |
| character_2 | a second person, kept distinct from the first |
| look_reference | grade, palette, contrast, black level, grain |
| lighting_reference | key direction, hardness, fill ratio, rim, falloff |
| composition_reference | shot size, camera height, angle, lens, subject placement |
| wardrobe | the garment |
Every input is optional and an unconnected one sends nothing at all — no label, no tokens, no influence. Connect only character and it behaves as a text-to-image node.
Each label also says what the image is not for. LIGHTING REFERENCE ends with "take nothing else from it: not its framing, not its colour grade, not its people, not its set." Without that clause a lighting reference drags its whole scene into the result, which is the usual reason multi-reference prompts collapse into a copy of whichever image was strongest.
References that disagree are ranked, not averaged. A directive at the end of every request states the order: written notes, then the character's face, then composition, then lighting, then look. Unranked, the model splits the difference between contradictory references and you get neither.
Text goes in separate boxes — shot_description, camera_notes, lighting_notes, blocking_notes, character_notes, wardrobe_notes, do_not — rather than one field. Camera direction buried in the middle of a long paragraph competes with everything around it; its own box gives it its own weight.
On auto, the aspect ratio is read from composition_reference only, and falls back to 16:9. A look or lighting reference can be any shape at all, so neither is asked.
The workflow in workflows/ wires two loaders into a Batch Images node for the character and two more for the look reference, so multiple angles work out of the box. Copy a loader with Ctrl+C / Ctrl+V and wire it into the batch to add a third. Loaders you are not using start bypassed — Ctrl+B switches one on, and a bypassed loader sends nothing at all. character_2 is left unwired deliberately: connect your own loader there when a second person is in the shot.
Two backends
| Backend | Endpoint | Auth | Google Cloud $300 trial credit |
|---|---|---|---|
| AI Studio | generativelanguage.googleapis.com | API key | ❌ excluded by Google |
| Vertex AI | aiplatform.googleapis.com | gcloud login or service account | ✅ applies |
Since March 2026 Google excludes Gemini API in AI Studio from the $300 Cloud free trial. Most Gemini nodes only talk to AI Studio, so the trial credit cannot pay for them. Vertex is the route that works.
Sources: Free Cloud features · Gemini API billing
Install
cd ComfyUI/custom_nodes
git clone https://github.com/haristahir1/comfyui-character-swap
Nothing extra for AI Studio — it uses requests, torch, numpy and Pillow, which ComfyUI already has.
For Vertex:
python_embeded\python.exe -m pip install google-auth
Restart ComfyUI.
Setup — Vertex AI (recommended)
- Install the Google Cloud CLI
- Log in. Both commands are needed — the second is the one Python reads, and it is the step everyone misses:
gcloud auth login gcloud auth application-default login - Enable the API:
gcloud services enable aiplatform.googleapis.com --project=YOUR_PROJECT_ID - Copy
config.json.exampletoconfig.jsonand set your project id
Setup — AI Studio
Get a key at aistudio.google.com/app/apikey and put it in config.json:
{ "api_key": "AIza..." }
GEMINI_API_KEY and GOOGLE_API_KEY environment variables work too.
Using it
Load the photo to copy into SHOT REFERENCE, your person into CHARACTER, optionally a garment into WARDROBE, and run. A labelled workflow with a guide card on the canvas is in workflows/.
| Box | Use |
|---|---|
| shot_notes | Leave empty. Only fill it to change the scene. |
| remove_subject | Who to delete. Be specific — "the blonde woman" beats the default. |
| character_notes | Anything the portrait doesn't show. |
| wardrobe_notes | Garment, colour, fabric. Naming what to remove helps as much as what to add. |
| blocking_notes | Empty keeps the reference pose. Fill it to change the pose, always frame-relative. |
| do_not | Pre-filled with the clone and collage blockers. Add to it per shot. |
Good to know
Use one clear portrait as the character, not a contact sheet — a grid of poses makes the model produce several people.
To change a large garment, generate the image fresh rather than repainting an existing one. Small objects swap reliably; a coat filling half the frame usually won't.
Google's models decline to reproduce real public figures and will quietly substitute someone else. This affects every Gemini node, not just this one, and no prompt works around it.
Keeping your API key private
The most common way people leak a Gemini key is pasting it into the node's api_key widget. Don't.
Put the key in config.json, never in the node. Widget values are saved inside every workflow JSON you export, and they are embedded in ComfyUI's error reports — the ones people routinely paste into GitHub issues and Discord. A key typed into the node travels with all of it. config.json is gitignored.
Or use an environment variable — GEMINI_API_KEY or GOOGLE_API_KEY keep it out of every file.
Or use Vertex, where no key is sent at all. Authentication is your gcloud login, so there is nothing to leak.
The widget shows the key in plain text, so it is visible in any screenshot of that node. Clear the box if you have ever typed one in.
Before sharing a workflow or an error report, search it for AIza and AQ. — that catches both key formats.
Restrict the key at Google's end. In the Cloud console you can limit an API key to the Generative Language API only, so a leaked key is far less useful to whoever finds it.
If a key has ever appeared in a screenshot, a shared workflow, an error report or a commit, rotate it. Deleting the post does not un-leak it.
Set a budget alert. These keys spend real money, and a leaked key on a billing-enabled project spends yours.
If you fork this
Run git log -p | grep -iE "AIza|AQ\." before your first push. Gitignore only protects files that were never committed — a key that went in once stays in the history until it is rewritten. And set your commit email to your GitHub noreply address, or your real address is embedded permanently.
Troubleshooting
| Error | Cause |
|---|---|
| 429 prepayment credits depleted | AI Studio balance is zero — top up, or switch to Vertex |
| 403 SERVICE_DISABLED | Run the gcloud services enable step |
| 403 PERMISSION_DENIED | Service account missing the Vertex AI User role |
| 404 on a Vertex model | Try "location": "us-central1" instead of "global" |
| Vertex mode needs credentials | You ran gcloud auth login but not gcloud auth application-default login |
| IMAGE_SAFETY in diagnostics | Refused outright — change the reference or the prompt |
License
MIT — see LICENSE.