PNGTuber Generated Mouth Sprite Applier
Swap in model-generated mouth sprites when the video extraction was garbage
- closed_image
- open_image
- half_image
- e_image
- u_image
- bundle_manifest
- mouth_sprite_atlas
- mouth_open
- mouth_half
- mouth_e
- mouth_u
Here's the situation this node exists for. You ran PNGTuber Video Upload to Mouth Bundle on a video where the character barely moves their mouth, and the bundle came back flagged articulation.requiresModelGeneration: true. Forced extraction gave you blurry, half-wrong mouth sprites, and the closed-mouth shape especially looks like a smear. The honest fix isn't more OpenCV - it's generating clean mouth shapes with a local image-edit model, then swapping them into the bundle. That's exactly what this applier does.
It's the "edit" half of the pack's generated-mouth path, and it's deliberately independent: you give it an existing bundle_manifest.json plus optional generated mouth images, queue just this node, and it rewrites the bundle in place. The companion workflow examples/workflows/pngtuber_generated_mouth_applier.json is a one-node graph for exactly that.
How it works
The node extracts the mouth component from each edited face crop you feed it, writes it as a transparent sprite, rotates it across the angle atlas bins (angle_p00 through the ±45° sets), and updates bundle_manifest.json, mouth_sprite_atlas.json, and summary.json with modelGeneration metadata so the player knows these sprites are generated, not extracted.
It replaces only the shapes you actually pass in. If your closed extraction was the disaster, feed just a closed_image; if the whole set is bad, feed all five. The pack's pngtuber_qwen_mouth_generation.json example shows the full loop: it loads the reference_face_mouth_crop.png the builder left in mouth_generation_inputs/, runs local Qwen Image Edit, and decodes straight into this node.
Inputs and outputs
- bundle_manifest_path - point at the
bundle_manifest.json(or the bundle directory) from the builder. - sprite_size - 512 default; match what the bundle was built at if you can.
- overwrite_existing -
truereplaces on-disk sprites;falsekeeps anything already there. - closed_image / open_image / half_image / e_image / u_image - optional ComfyUI
IMAGEinputs. At least one is required, or the node errors out.
Outputs are the updated manifest, the atlas, and the four sprite paths it wrote (mouth_open, mouth_half, mouth_e, mouth_u). One small gotcha: there's no mouth_closed output slot even though the closed sprite gets written to disk and recorded in the manifest - don't go hunting for a missing wire, the file is there.
Install and what it needs
Same pack install as everything else here - ComfyUI Manager search, or:
cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/hobi2k/ComfyUI-PromptMaker-PNGTuber.git
cd /path/to/ComfyUI
python -m pip install -r custom_nodes/ComfyUI-PromptMaker-PNGTuber/requirements.txt
Then restart ComfyUI - the README calls out that you need to restart after updating so this class shows up in the registry. The node itself adds no new dependencies, but the generation side does: it assumes you have a local ComfyUI Qwen Image Edit stack installed - the Qwen image-edit model, the Qwen text encoder and VAE, and the node pack that provides TextEncodeQwenImageEditPlus. Those are multi-gigabyte downloads, and they're the same stack every "eigen" path in this pack uses. If you feed the applier images you generated elsewhere, none of that matters.
Troubleshooting
- "Provide at least one generated mouth image" - the IMAGE inputs were all left empty. That's the one hard requirement.
- "Bundle manifest not found" - you gave it a path that doesn't exist; a directory path is fine, it appends
bundle_manifest.jsonfor you. - Red LoadImage nodes in the example workflow - placeholder filenames; drop your real generated mouth images into ComfyUI's input folder and re-upload.
- Blurry or identity-drifted generated mouths aren't this node's fault - that's Qwen Image Edit doing the same thing it does everywhere (character-consistency pain is the standard complaint). Feed it the full-source edits and let the mask confine the change.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| bundle_manifest_path | STRING | — | |
| sprite_size | INT | 512128–2048 | — |
| overwrite_existing | BOOLEAN | true | — |
| closed_imageopt | IMAGE | — | |
| open_imageopt | IMAGE | — | |
| half_imageopt | IMAGE | — | |
| e_imageopt | IMAGE | — | |
| u_imageopt | IMAGE | — |
Outputs (6)
| Name | Type | Description |
|---|---|---|
| bundle_manifest | STRING | — |
| mouth_sprite_atlas | STRING | — |
| mouth_open | STRING | — |
| mouth_half | STRING | — |
| mouth_e | STRING | — |
| mouth_u | STRING | — |