v2.3 — Character-Aussehen + Portrait-Fix
Features: - Character-Bogen: Aussehen-Sektion mit Stil, Geschlecht, Alter, Größe, Statur, Haarfarbe, Frisur, Augenfarbe, Haut, Kleidung, Merkmale - Stil-Optionen: Anime, Realistisch, Comic, Cartoon, Pixel Art - ComfyUI Prompt wird aus appearance-Objekt gebaut - Portrait-Button: Async mit Polling (kein Timeout mehr) - Portrait-Button: 5-Minuten Timeout als Fallback - Poll-Errors werden ignoriert (kein vorzeitiger Abbruch) - Mara.json aktualisiert mit appearance-Objekt
This commit is contained in:
+71
-7
@@ -1,5 +1,6 @@
|
||||
"""
|
||||
NeonChat ComfyUI Integration — Profilbild-Generierung für Characters
|
||||
Basiert auf dem Character-Aussehen (appearance)
|
||||
"""
|
||||
|
||||
import json, os, time, shutil
|
||||
@@ -8,7 +9,6 @@ import httpx
|
||||
|
||||
COMFYUI_URL = os.environ.get("COMFYUI_URL", "http://localhost:8188")
|
||||
COMFYUI_OUTPUT_DIR = Path(os.environ.get("COMFYUI_OUTPUT", str(Path.home() / "comfy" / "ComfyUI" / "output")))
|
||||
CHARACTERS_DIR = Path(__file__).parent / "characters"
|
||||
AVATARS_DIR = Path(__file__).parent / "static" / "avatars"
|
||||
|
||||
def is_comfyui_running() -> bool:
|
||||
@@ -19,6 +19,69 @@ def is_comfyui_running() -> bool:
|
||||
except:
|
||||
return False
|
||||
|
||||
def build_prompt_from_appearance(character: dict, context: str = "") -> str:
|
||||
"""Baut einen ComfyUI-Prompt aus dem Character-Aussehen."""
|
||||
app = character.get("appearance", {})
|
||||
|
||||
style = app.get("style", "anime")
|
||||
gender = app.get("gender", "female")
|
||||
age = app.get("age", "25")
|
||||
height = app.get("height", "")
|
||||
build = app.get("build", "")
|
||||
hair_color = app.get("hair_color", "")
|
||||
hair_style = app.get("hair_style", "")
|
||||
eye_color = app.get("eye_color", "")
|
||||
skin = app.get("skin", "")
|
||||
clothing = app.get("clothing", "")
|
||||
distinctive = app.get("distinctive", "")
|
||||
|
||||
# Style prefix
|
||||
style_map = {
|
||||
"anime": "anime style, anime art, ",
|
||||
"realistic": "photorealistic, realistic, ",
|
||||
"comic": "comic book style, western comic, ",
|
||||
"cartoon": "cartoon style, ",
|
||||
"pixel": "pixel art, ",
|
||||
}
|
||||
style_prefix = style_map.get(style, "anime style, ")
|
||||
|
||||
# Gender
|
||||
if gender == "female":
|
||||
gender_tag = "1girl, woman, "
|
||||
elif gender == "male":
|
||||
gender_tag = "1boy, man, "
|
||||
else:
|
||||
gender_tag = "person, "
|
||||
|
||||
# Build prompt
|
||||
parts = [
|
||||
f"portrait, headshot, {style_prefix}{gender_tag}",
|
||||
f"{age} years old",
|
||||
]
|
||||
if build:
|
||||
parts.append(build)
|
||||
if hair_color and hair_style:
|
||||
parts.append(f"{hair_color} hair, {hair_style}")
|
||||
elif hair_color:
|
||||
parts.append(f"{hair_color} hair")
|
||||
elif hair_style:
|
||||
parts.append(hair_style)
|
||||
if eye_color:
|
||||
parts.append(f"{eye_color} eyes")
|
||||
if skin:
|
||||
parts.append(f"{skin} skin")
|
||||
if clothing:
|
||||
parts.append(f"wearing {clothing}")
|
||||
if distinctive:
|
||||
parts.append(distinctive)
|
||||
if context:
|
||||
parts.append(context)
|
||||
|
||||
prompt = ", ".join(parts)
|
||||
prompt += ", detailed face, high quality, masterpiece, best quality, simple background"
|
||||
|
||||
return prompt
|
||||
|
||||
def build_portrait_workflow(prompt: str, negative: str = "", width: int = 512, height: int = 512) -> dict:
|
||||
return {
|
||||
"3": {
|
||||
@@ -47,14 +110,14 @@ def build_portrait_workflow(prompt: str, negative: str = "", width: int = 512, h
|
||||
"6": {
|
||||
"class_type": "CLIPTextEncode",
|
||||
"inputs": {
|
||||
"text": f"portrait, {prompt}, detailed face, high quality, masterpiece, best quality, 1girl, solo",
|
||||
"text": prompt,
|
||||
"clip": ["4", 1]
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"class_type": "CLIPTextEncode",
|
||||
"inputs": {
|
||||
"text": f"lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, {negative}",
|
||||
"text": f"lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, blurry, deformed, ugly, {negative}",
|
||||
"clip": ["4", 1]
|
||||
}
|
||||
},
|
||||
@@ -95,14 +158,15 @@ def wait_for_image(prompt_id: str, timeout: int = 180) -> str | None:
|
||||
time.sleep(3)
|
||||
return None
|
||||
|
||||
def generate_portrait(character_name: str, description: str) -> str | None:
|
||||
def generate_portrait(character_name: str, character: dict, context: str = "") -> str | None:
|
||||
"""Generiert ein Profilbild basierend auf dem Character-Aussehen."""
|
||||
if not is_comfyui_running():
|
||||
return None
|
||||
|
||||
AVATARS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
prompt = f"anime style, {description}, simple background, headshot"
|
||||
negative = "blurry, deformed, ugly, realistic"
|
||||
prompt = build_prompt_from_appearance(character, context)
|
||||
negative = "realistic, 3d, render"
|
||||
|
||||
workflow = build_portrait_workflow(prompt, negative, width=512, height=512)
|
||||
|
||||
@@ -126,4 +190,4 @@ def generate_portrait(character_name: str, description: str) -> str | None:
|
||||
return f"/static/avatars/{safe_name}.png"
|
||||
except Exception as e:
|
||||
print(f"ComfyUI error: {e}")
|
||||
return None
|
||||
return None
|
||||
Reference in New Issue
Block a user