ComfyUI Extension: comfyui-ageshift

Authored by aadebuger

Created

Updated

0 stars

Run ComfyUI workflows without the setup

No installs, no CUDA version roulette, no GPU sitting idle on your bill. Bring a workflow and run it in the browser.

ComfyUI nodes for face age detection and age-conditioned prompt building

README

ComfyUI-AgeShift

License: MIT Python ComfyUI

Detect a face's age, then generate identity-preserved portraits at any target age — from infant to 90+.

ComfyUI nodes for face age detection (ViT classifier) and age-conditioned prompt building. Pairs with ComfyUI-PuLID-Flux (or InstantID) to produce identity-preserving age-shifted portraits.

source face ──► AgeDetect ──► current age = 40
                                 │
target age (e.g. 65) ──► AgePromptBuilder ──► positive + negative prompt
                                 │
                                 ▼
                      PuLID-Flux + Flux 1 dev
                                 │
                                 ▼
                       aged portrait (65 y/o)

✨ Features

  • 🎂 Continuous age estimation — ViT classifier with weighted-mean decoding over bin midpoints; produces smooth integer ages (32, 47) rather than snapping to bin centers.
  • 🎂 Concrete-feature prompts — translates target_age=65 into "deep facial wrinkles, gray or white hair often thinning, sagging jowls, age spots", which Flux follows much more reliably than numeric age alone.
  • 🎂 Anti-drift negatives — auto-injects contextual negatives so a 40-year-old source doesn't leak adult features into the "age 5" generation.
  • 🎂 Identity-agnostic — works with PuLID-Flux, InstantID, IPAdapter FaceID, or any face-conditioning backbone. We don't reimplement ID injection; we just drive the prompt side.
  • 🎂 Batch CLI — generate an age progression sequence (5 → 15 → 25 → 45 → 65) in one command from your laptop, talking to a remote ComfyUI server.

📦 Installation

1. Clone the node package

cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/aadebuger/ComfyUI-AgeShift.git
cd ComfyUI-AgeShift

# in the ComfyUI venv:
source /path/to/ComfyUI/.venv/bin/activate
uv pip install -r requirements.txt

Dependencies are minimal (transformers, pillow, numpy) — almost always already in a ComfyUI venv.

2. (Optional but recommended) Companion packages

The age-detect side works standalone. For identity-preserving generation you also need PuLID-Flux + Flux:

cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/balazik/ComfyUI-PuLID-Flux.git
cd ComfyUI-PuLID-Flux
uv pip install -r requirements.txt

| Asset | Where | Size | Notes | |---|---|---|---| | Flux 1 dev fp8 | models/unet/flux1-dev-fp8.safetensors | ~12 GB | from Kijai/flux-fp8 (no license required) | | CLIP-L | models/clip/clip_l.safetensors | ~250 MB | standard Flux | | T5-XXL | models/clip/t5xxl_fp8_*.safetensors | ~5 GB | scaled or e4m3fn both work | | VAE | models/vae/ae.safetensors | ~330 MB | standard Flux | | PuLID-Flux weights | models/pulid/pulid_flux_v0.9.1.safetensors | ~1.1 GB | from guozinan/PuLID | | InsightFace antelopev2 | models/insightface/models/antelopev2/*.onnx | ~360 MB | manual placement required — see below | | EVA02-CLIP-L | auto-downloaded | ~430 MB | downloads on first run |

⚠️ InsightFace antelopev2 does NOT auto-download (unlike the default buffalo_l). You must place the 5 ONNX files manually at models/insightface/models/antelopev2/ (note the double models — that's how the InsightFace lib expects it). See INSTALL_NOTES.md for one-liner downloads.

3. Restart ComfyUI

Search the node palette for 🎂 — four nodes should appear.

Quick verification from the CLI:

curl -s http://127.0.0.1:8188/object_info | python3 -c "
import json,sys; d = json.load(sys.stdin)
for n in ['AgeDetectorLoader','AgeDetect','AgePromptBuilder','AgeRangeBatch',
         'PulidFluxModelLoader','ApplyPulidFlux']:
    print(f'  {n}:', '✓' if n in d else '✗')"

🚀 Quick start

Detect only (UI)

Open example_workflows/age_detect_only.json in ComfyUI, drop a portrait into the LoadImage node, queue. The summary PreviewAny shows:

Estimated age: 40 (adult) | top bin: 30-39 (46.8%) | top-3: 30-39=0.47, 40-49=0.43, 50-59=0.05

Full pipeline (UI)

Open example_workflows/age_shift_pulid_flux.json, set the target_age on the AgePromptBuilder node, queue.

CLI

# Detect only — runs in ~2 seconds on CPU
uv run --script cli/age_shift_cli.py face.jpg --detect-only --ip <server>

# Single target age
uv run --script cli/age_shift_cli.py face.jpg --target-age 65 --ip <server>

# Age progression sequence
uv run --script cli/age_shift_cli.py face.jpg \
    --target-ages 5,15,25,45,65 \
    --gender male --subject man \
    --pulid-weight 0.95 --guidance 3.0 --steps 28 --seed 42 \
    --flux-unet flux1-dev-fp8.safetensors \
    --flux-t5 t5xxl_fp8_scaled.safetensors \
    --ip <server> --out ./out

Output: ./out/age_005_*.png, ./out/age_015_*.png, ...

See cli/README.md for the full argument list.


🧠 Node reference

🎂 Age Detector Loader

Loads a HF ViT age-classification model, with in-process cache.

| Input | Type | Default | |---|---|---| | model_id | STRING | nateraw/vit-age-classifier | | device | enum | auto | | dtype | enum | fp32 | | force_reload | BOOL | False |

Output: AGE_DETECTOR.

🎂 Age Detect

| Input | Type | Default | |---|---|---| | detector | AGE_DETECTOR | — | | image | IMAGE | — | | method | enum | weighted_mean |

Outputs: age:INT, age_band:STRING, confidence:FLOAT, summary:STRING.

Note: this is whole-image classification. For non-portrait input, run a face-crop step upstream (e.g. via ComfyUI-Crystools or InsightFace).

🎂 Age Prompt Builder

| Input | Type | Default | |---|---|---| | target_age | INT | 30 | | subject | STRING | person | | gender | enum | unspecified | | style | STRING | photorealistic portrait... | | extra_positive | STRING | same identity... | | negative_base | STRING | cartoon, illustration... | | include_negative_age_drift | BOOL | True |

Outputs: positive:STRING, negative:STRING, summary:STRING, target_age:INT.

📖 See PROMPTS.md for the complete list of generated prompts across all 20 age ranges + examples at age 5/15/25/45/65/80.

🎂 Age Range Batch

CLI-oriented. Emits JSONL {age, band, prompt} for a comma-separated list of target ages.


🎯 Identity preservation tuning

The default settings work for most cases. If the generated faces look like different people, walk this checklist:

1. Lock the seed across ages

Different seeds = different sub-identities. Pass --seed 42 (or any fixed value) so all ages share the same noise schedule.

2. Bump PuLID weight

--pulid-weight 0.95     # default 0.9; up to 1.0

3. Lower the prompt guidance

--guidance 3.0          # default 3.5

The age prompt is a strong signal — lowering guidance leaves more room for the identity to assert itself.

4. More steps

--steps 28              # default 20

PuLID continues injecting identity throughout sampling, so more steps = more chances for ID to converge.

5. Crop the source to a clean portrait

PuLID extracts identity via InsightFace, which works best on a centered frontal face. If the input has background / side angle / multiple faces, crop first.

6. Strengthen identity anchors in the prompt

--extra-positive "exact same person, identical facial bone structure, same eye distance, same nose shape, same lip shape"

Direction-specific tips

| Direction | Tip | |---|---| | Aging up (40 → 70) | Default settings work well. Optionally --style "photorealistic portrait, soft natural lighting" (avoid sharp focus, which over-emphasizes wrinkles) | | Aging down (40 → 20) | Default settings work well. | | Going to child (adult → 5–12) | Drop --pulid-weight 0.75–0.80 — full strength preserves adult bone structure and produces unsettling "small adult" results. | | Going to infant (adult → 0–3) | Hard. Consider face-landmark ControlNet weighted on upper face only, or accept that the result is "what this person's son/daughter might look like" rather than a literal regression. |


🐞 Troubleshooting

PulidFluxModelLoader not found

PuLID-Flux either isn't installed or failed to import. Check:

grep -B2 -A8 -iE "pulid|IMPORT FAILED" ~/comfy_start.log | tail -40

Most often facexlib / onnxruntime-gpu / insightface is missing:

cd /path/to/ComfyUI/custom_nodes/ComfyUI-PuLID-Flux
uv pip install -r requirements.txt --no-cache-dir

Then restart ComfyUI.

assert 'detection' in self.models (AssertionError from InsightFace)

The antelopev2 model files aren't at the expected path. Required layout (note the double models directory):

/path/to/ComfyUI/models/insightface/models/antelopev2/
├── 1k3d68.onnx
├── 2d106det.onnx
├── genderage.onnx
├── glintr100.onnx
└── scrfd_10g_bnkps.onnx

Fix:

cd /path/to/ComfyUI/models/insightface
mkdir -p models && cd models
wget "https://github.com/deepinsight/insightface/releases/download/v0.7/antelopev2.zip"
unzip antelopev2.zip && rm antelopev2.zip

No module named 'websocket'

The CLI needs websocket-client (NOT websocket, which is a separate abandoned package):

uv pip install websocket-client

Or just use uv run --script cli/age_shift_cli.py ... — the script's PEP 723 header declares deps, uv handles them automatically.

Faces all look like different people

See the identity preservation tuning section. Most often: missing --seed, or --pulid-weight too low.

"child" generation produces creepy mini-adults

PuLID weight is too high for child direction. Try --pulid-weight 0.75 for child-direction runs; keep it at 0.9–0.95 for aging-up runs.

Hangs on first generation

PuLID's first run downloads EVA02-CLIP-L (~430 MB) and (if you skipped the manual antelopev2 step) tries to set up InsightFace. Watch the ComfyUI server log — once it prints apply_pulid done, generations become fast.


🧰 Project layout

ComfyUI-AgeShift/
├── __init__.py                    # NODE_CLASS_MAPPINGS
├── nodes/
│   ├── _types.py                  # age → features mapping (the brain)
│   ├── detector_loader.py         # _MODEL_CACHE pattern
│   ├── detect.py                  # ViT classifier execution
│   └── prompt_builder.py          # AgePromptBuilder + AgeRangeBatch
├── cli/
│   ├── README.md
│   └── age_shift_cli.py           # PEP 723 inline-deps script
├── example_workflows/
│   ├── age_detect_only.json       # minimal UI workflow
│   └── age_shift_pulid_flux.json  # full pipeline UI workflow
├── PROMPTS.md                     # complete prompt reference
├── INSTALL_NOTES.md               # server-side install record
├── CHANGELOG.md
├── CONTRIBUTING.md
├── pyproject.toml
└── requirements.txt

🛣️ Roadmap

  • [ ] Built-in face-crop node (so users don't need a separate package)
  • [ ] CLI flag --pulid-fusion {mean,concat} and --pulid-train-step for advanced ID tuning
  • [ ] InstantID + SDXL alternative workflow (for users without Flux)
  • [ ] ControlNet face-landmark integration for stronger child-direction results
  • [ ] Side-by-side grid output (concatenated PNG) for sequence runs

🤝 Contributing

PRs welcome. See CONTRIBUTING.md. The contribution most likely to help right now: testing different age-feature phrasings on Flux and submitting improvements to _types.py with before/after comparison images.


📜 License

MIT — see LICENSE.

The age classifier (nateraw/vit-age-classifier) is MIT-licensed. PuLID, Flux, and InsightFace weights have their own licenses — see those projects for details.


🙏 Acknowledgments

Run ComfyUI workflows without the setup

No installs, no CUDA version roulette, no GPU sitting idle on your bill. Bring a workflow and run it in the browser.

Learn more