138 lines
4.8 KiB
Python
138 lines
4.8 KiB
Python
import re
|
|||
|
|
from ai.character_memory import build_consistency_tokens
|
||
|
|
|
||
|
|
STYLES = {
|
||
|
|
"lineart": {
|
||
|
|
"positive": (
|
||
|
|
"anime illustration, manga style, black and white, monochrome, "
|
||
|
|
"clean ink linework, expressive characters, dynamic composition, "
|
||
|
|
"professional manga art, single scene, full image"
|
||
|
|
),
|
||
|
|
"negative": (
|
||
|
|
"color, colorful, coloured, vibrant, saturated, "
|
||
|
|
"multiple panels, panel borders, panel grid, split panels, "
|
||
|
|
"collage, triptych, diptych, "
|
||
|
|
"realistic, photorealistic, photograph, 3d render, "
|
||
|
|
"ugly, blurry, watermark, text, signature, lowres, "
|
||
|
|
"bad anatomy, deformed, extra limbs"
|
||
|
|
),
|
||
|
|
"cfg": 7.0,
|
||
|
|
},
|
||
|
|
"manga": {
|
||
|
|
"positive": (
|
||
|
|
"single full-page manga illustration, full-bleed scene, "
|
||
|
|
"black and white ink, screentone shading, "
|
||
|
|
"expressive faces, detailed backgrounds, "
|
||
|
|
"professional manga art, one continuous scene"
|
||
|
|
),
|
||
|
|
"negative": (
|
||
|
|
"multiple panels, panel borders, panel grid, comic layout, split panels, "
|
||
|
|
"panel dividers, gutters, multi-panel page, page layout, comic book grid, "
|
||
|
|
"collage, triptych, diptych, "
|
||
|
|
"lowres, bad anatomy, blurry, watermark, text, ugly, deformed, "
|
||
|
|
"color, coloured, western comic style"
|
||
|
|
),
|
||
|
|
"cfg": 7.5,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
MOOD_MAP = {
|
||
|
|
"tense": "tense dramatic scene, characters look worried or scared",
|
||
|
|
"sad": "sad melancholy scene, characters look downcast",
|
||
|
|
"happy": "happy cheerful scene, characters smiling",
|
||
|
|
"angry": "angry confrontational scene, characters look furious",
|
||
|
|
"mysterious": "mysterious eerie scene, shadowy atmosphere",
|
||
|
|
"neutral": "calm everyday scene",
|
||
|
|
"romantic": "romantic gentle scene, soft atmosphere",
|
||
|
|
"action": "action dynamic scene, characters in motion",
|
||
|
|
"excited": "excited energetic scene, characters enthusiastic",
|
||
|
|
"fearful": "fearful tense scene, characters look afraid",
|
||
|
|
}
|
||
|
|
|
||
|
|
JUNK_NAMES = {
|
||
|
|
"he", "she", "they", "him", "her", "them", "his", "hers", "their",
|
||
|
|
"i", "me", "we", "us", "it", "you", "location", "unknown", "none",
|
||
|
|
"character", "person", "man", "woman", "boy", "girl",
|
||
|
|
"the man", "the woman", "the boy", "the girl", "the person",
|
||
|
|
"a man", "a woman", "old man", "young man", "young woman",
|
||
|
|
"his secretary", "his secretarys", "her secretary",
|
||
|
|
"narrator", "voice", "someone", "anyone", "everyone",
|
||
|
|
}
|
||
|
|
|
||
|
|
PLACEHOLDER_SETTINGS = {"location", "unknown", "none", "'location'", '"location"', ""}
|
||
|
|
|
||
|
|
POSSESSIVE_RE = re.compile(r"'s$|s'$", re.IGNORECASE)
|
||
|
|
|
||
|
|
def clean_text(s):
|
||
|
|
s = s.replace("\\u0022", "").replace("\\u0027", "")
|
||
|
|
s = s.replace('\\"', "").replace("\\'", "")
|
||
|
|
s = re.sub(r'[\"\'`]', "", s)
|
||
|
|
s = re.sub(r"\s+", " ", s).strip()
|
||
|
|
return s
|
||
|
|
|
||
|
|
def is_valid_name(name):
|
||
|
|
n = name.strip().lower()
|
||
|
|
if not n or len(n) < 2:
|
||
|
|
return False
|
||
|
|
if n in JUNK_NAMES:
|
||
|
|
return False
|
||
|
|
if POSSESSIVE_RE.search(n):
|
||
|
|
return False
|
||
|
|
return True
|
||
|
|
|
||
|
|
def build_page_prompt(parsed_scenes, style="lineart"):
|
||
|
|
style_def = STYLES.get(style, STYLES["lineart"])
|
||
|
|
|
||
|
|
if not parsed_scenes:
|
||
|
|
return style_def["positive"], style_def["negative"]
|
||
|
|
|
||
|
|
all_characters = []
|
||
|
|
visual_parts = []
|
||
|
|
settings = []
|
||
|
|
moods = []
|
||
|
|
|
||
|
|
for scene in parsed_scenes:
|
||
|
|
if isinstance(scene, str):
|
||
|
|
visual_parts.append(scene)
|
||
|
|
continue
|
||
|
|
for char in scene.get("characters", []):
|
||
|
|
char = clean_text(char)
|
||
|
|
if is_valid_name(char):
|
||
|
|
all_characters.append(char)
|
||
|
|
visual = clean_text(scene.get("visual_scene", ""))
|
||
|
|
if visual:
|
||
|
|
visual_parts.append(visual)
|
||
|
|
setting = clean_text(scene.get("setting", ""))
|
||
|
|
if setting.lower() not in PLACEHOLDER_SETTINGS:
|
||
|
|
settings.append(setting)
|
||
|
|
mood = scene.get("mood", "neutral").strip().lower()
|
||
|
|
if mood:
|
||
|
|
moods.append(mood)
|
||
|
|
|
||
|
|
unique_chars = list(dict.fromkeys(all_characters))
|
||
|
|
char_tokens = build_consistency_tokens(unique_chars)
|
||
|
|
|
||
|
|
dominant_mood = moods[0] if moods else "neutral"
|
||
|
|
mood_desc = MOOD_MAP.get(dominant_mood, MOOD_MAP["neutral"])
|
||
|
|
|
||
|
|
setting_str = settings[0][:60] if settings else ""
|
||
|
|
scene_str = visual_parts[0][:80] if visual_parts else ""
|
||
|
|
|
||
|
|
style_prefix = (
|
||
|
|
"(anime style:1.4), (manga illustration:1.3), "
|
||
|
|
"(black and white:1.3), (monochrome:1.2), "
|
||
|
|
) if style == "lineart" else ""
|
||
|
|
|
||
|
|
parts = [style_prefix + style_def["positive"], mood_desc]
|
||
|
|
if setting_str:
|
||
|
|
parts.append(setting_str)
|
||
|
|
if char_tokens:
|
||
|
|
parts.append(char_tokens)
|
||
|
|
if scene_str:
|
||
|
|
parts.append(scene_str)
|
||
|
|
|
||
|
|
return ", ".join(parts), style_def["negative"]
|
||
|
|
|
||
|
|
def get_cfg(style="lineart"):
|
||
|
|
return STYLES.get(style, STYLES["lineart"])["cfg"]
|