Character Photo
Character Photo for Old-Time Radio
- photos_in
- character_photos
The face problem shows up the moment your radio play gets pictures. ComfyUI-OldTimeRadio writes, casts, voices and scores a whole episode locally, then draws a still per beat - and without help, the lead in beat three is a different person from the lead in beat one. The pack's answer is a reference photo, not a trained LoRA.
OTR_CharacterPhoto is the node that hands that photo over. It's small on purpose: a name, a picture, a chain socket.
What it actually does
You give it a photo of a face and the name of a character. The image phase paints that character's portrait from the photo, in whatever visual style the episode rolled or pinned, and every still of them - plus anyone sharing their frame - follows the portrait.
Which means your photo never appears in the episode. It isn't composited, it isn't a face swap, it isn't fed forward as a still. It's identity conditioning: the portrait is a generated image in the episode's style, and the photo only steers it. If you wanted a real photo of a real person in the frame, this isn't that, and no pack node is.
How the chain works
One node holds one photo, which sounds annoying until you see why: ComfyUI only gives the first required image picker an upload button, and an optional picker silently defaults to whatever sorts first in your input folder. Hence the chain.
photos_in takes the previous node's output; the last one wires into character_photos_in on OTR_ImageDirector. Under the hood it's a plain dict - {"version": 1, "photos": {"Jeffrey": ["headshot.png"]}} - accumulating as it travels. Repeat a name across nodes and you're handing the model up to three pictures of one person to average into a portrait. Three is the ceiling; the rest is dropped and reported.
The three inputs worth knowing
name is the character's name as the story uses it. Matching folds whitespace and case - "jeffrey" finds "Jeffrey" - but it doesn't guess. A name nobody in the cast has gets reported and the photo goes unused, and so does a name two characters share. Two doctors in one cast? This node can't pick for you.
image is the picker: choose a file already in ComfyUI's input folder, or upload/paste one into the widget (pastes land in pasted/). A clear face beats a gorgeous photo of someone turned three-quarters away.
photos_in is the chain socket described above. One output, character_photos, which only ever goes to Image Director.
One behaviour worth internalising: blank name plus blank picture is a pass-through. The canonical workflow ships one of these wired and empty, so the socket exists on every machine and a shared graph never names a file your disk lacks. Give it only a name, or only a picture, and validation refuses the run before it starts.
Installing it
cd ComfyUI/custom_nodes
git clone https://github.com/jbrick2070/ComfyUI-OldTimeRadio
python -m pip install -r ComfyUI-OldTimeRadio/requirements.txt
Or in ComfyUI: Extensions → Node Manager, search old time radio (registry id comfyui-old-time-radio), install, restart. The Windows portable ships without Manager, so the clone route is the honest one there.
Two install traps, both from the README: run that pip install with ComfyUI's interpreter, not a system Python - the failure mode is nodes that silently refuse to load much later, not an error. And Python 3.10–3.13 only; 3.14 has no Kokoro voice build.
This node needs nothing heavy itself, but its purpose does: your still engine must accept a reference image, and here only flux3_klein_ref does - FLUX 3 Klein with references always on, ~10 GB if you don't have those weights. The 16 GB reference workflows (otr_16gb_foley_flux3_ref and its open-weights twin) select it; a random model roll never will.
Where people get burned
The silent one: photos are reported, not honoured, on any other still engine. Leave the character still model on the canonical's z_image_turbo and the run finishes happily with the photos you handed it completely ignored. The log says so, but it's easy to miss.
The rest are literalness, not bugs. A file outside ComfyUI's input folder is "not a picture in ComfyUI's folders." A name that doesn't match the cast is a typo to hunt down. And if you also typed Name = file lines into Image Director's own character_photos widget - which the app view writes - those typed lines win for any name both give.
One honest caveat: reference-based consistency is strong, not absolute - these approaches drift on identity across many beats where a trained LoRA wouldn't. So this buys continuity, not a guarantee. Over dozens of stills that must match exactly, you're in LoRA territory. For a four-minute play where faces just have to hold between scenes, it's the cheaper answer, and it's what this pack ships.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| name | STRING | The character's name as the story uses it, e.g. Jeffrey. Empty (with no picture) passes the chain through. | |
| image | COMBO | Their photo: pick, upload or paste one. A clear face works best. | |
| photos_inopt | OTR_CHARACTER_PHOTOS | The previous Character Photo node, to chain them. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| character_photos | OTR_CHARACTER_PHOTOS | — |