389 lines
19 KiB
Python
389 lines
19 KiB
Python
"""动态风格 / 配色推导(领域知识,可插拔)。
|
||
|
||
关键:art_style 与 color_palette 不再按国家写死,而是根据「热点词本身语义」动态推导:
|
||
① 该国专属 extra 规则(prompts/<country>/aesthetics.yaml,最具体优先)
|
||
② 全局关键词规则 STYLE_PALETTE_RULES(覆盖更全、配色更丰富、风格更具体新颖)
|
||
③ 确定性风格微调 STYLE_TWISTS(按主题哈希选,同词稳定、跨词不同,打破千篇一律)
|
||
④ 按 classify 类别兜底
|
||
⑤ 国家基调最末兜底(极少走到)
|
||
|
||
关键词匹配用「单词边界」,避免 eco 误中 cottagecore、kit 误中 jacket 等子串歧义。
|
||
具体规则排在通用 retro/vintage 之前,避免 retro games / retro trainers 被笼统归为复古风。
|
||
"""
|
||
import hashlib
|
||
import re
|
||
from typing import Dict, List, Optional, Tuple
|
||
|
||
# 规则按「优先级」排列:越靠前越具体,命中任一关键词即采用该条(取第一个命中)。
|
||
# 关键词匹配为单词边界。配色统一 5 色,艺术风格尽量具体+新颖。
|
||
STYLE_PALETTE_RULES: List[Tuple[tuple, str, str]] = [
|
||
(("skull", "death", "gothic", "horror", "zombie", "vampire", "spooky", "occult"),
|
||
"dark gothic engraving with ornate filigree",
|
||
"oxblood red, charcoal black, antique silver, deep purple, bone white"),
|
||
(("metal", "rock", "band", "grunge", "punk", "emo", "anarchist"),
|
||
"gritty risograph grunge zine",
|
||
"muted olive, rust orange, dirty cream, faded black, safety-pin silver"),
|
||
(("kawaii", "cute", "chibi", "sanrio"),
|
||
"kawaii chibi sticker with thick outlines",
|
||
"pastel pink, baby blue, mint, butter yellow, cream"),
|
||
(("cat", "kitten"),
|
||
"cute cat cartoon with bold outlines",
|
||
"warm cream, soft pink, charcoal, peach, sky blue"),
|
||
(("dog", "puppy"),
|
||
"playful dog cartoon with wagging tail",
|
||
"warm brown, cream, navy, tan, rust"),
|
||
(("coffee", "cafe", "tea", "latte", "brew"),
|
||
"cozy drink line illustration with steam curls",
|
||
"espresso brown, cream, caramel, sage, terracotta"),
|
||
(("mountain", "nature", "forest", "camping", "hiking", "outdoor", "wilderness", "woodland"),
|
||
"minimal outdoor landscape with layered ridgelines",
|
||
"forest green, slate gray, sand, cream, pine"),
|
||
(("beach", "surf", "ocean", "summer", "tropical", "sun", "seaside"),
|
||
"bright tropical screen-print",
|
||
"sky blue, coral, sand, sun-bleached white, turquoise"),
|
||
(("synthwave", "vaporwave", "retrowave"),
|
||
"synthwave neon with grid horizon",
|
||
"neon purple, magenta, cyan, deep navy, hot pink"),
|
||
(("anime", "manga", "otaku", "waifu"),
|
||
"anime-inspired flat cel with speed lines",
|
||
"vivid cyan, magenta, white, ink black, lemon"),
|
||
(("space", "galaxy", "star", "astro", "cosmic", "moon", "universe", "nebula"),
|
||
"cosmic vector with nebula glow",
|
||
"deep navy, violet, silver, starlight white, magenta"),
|
||
(("heart", "love", "valentine", "romance", "couple"),
|
||
"romantic badge with hand-lettered flourish",
|
||
"rose red, blush pink, cream, gold, burgundy"),
|
||
(("book", "reading", "library", "bookish", "novel"),
|
||
"cozy bookish line art with marginalia",
|
||
"warm brown, cream, forest green, oxblood, gold"),
|
||
(("music", "song", "concert", "festival", "dj", "gig"),
|
||
"dynamic gig poster with spotlight beams",
|
||
"electric purple, magenta, black, neon lime, silver"),
|
||
(("food", "pizza", "burger", "baking", "donut", "taco"),
|
||
"appetizing flat food illustration",
|
||
"warm red, cheese yellow, leaf green, cream, tomato"),
|
||
(("cyber", "cyberpunk", "neon", "tech", "robot", "mecha", "glitch"),
|
||
"neon digital with glitch grid",
|
||
"neon magenta, cyan, electric purple, black, lime"),
|
||
(("steam", "steampunk", "gear", "cog", "airship", "mechanical"),
|
||
"detailed mechanical engraving with brass",
|
||
"brass, copper, aged brown, sepia, gunmetal"),
|
||
(("solar", "eco", "green", "sustainable", "earth", "climate"),
|
||
"hopeful solarpunk eco with clean lines",
|
||
"leaf green, solar gold, sky blue, terracotta, cream"),
|
||
(("cottage", "cottagecore", "pastoral", "farm", "rustic"),
|
||
"soft storybook watercolor with wildflowers",
|
||
"sage green, butter yellow, dusty rose, cream, moss"),
|
||
(("car", "automotive", "vehicle", "classic", "racer"),
|
||
"retro automotive poster with chrome sheen",
|
||
"cherry red, cream, chrome silver, navy, tan"),
|
||
(("game", "gaming", "arcade", "pixel", "8-bit"),
|
||
"8-bit pixel-art with scanlines",
|
||
"neon green, magenta, cyan, black, yellow"),
|
||
(("shoe", "sneaker", "trainer", "footwear", "boots"),
|
||
"retro product sneaker illustration",
|
||
"white, red, navy, gum-sole tan, court grey"),
|
||
(("watch", "clock", "timepiece", "chronograph"),
|
||
"elegant engraving with roman numerals",
|
||
"antique gold, navy, cream, burgundy, slate"),
|
||
(("lion", "shield", "crest", "heraldic", "crown", "queen", "king", "royal"),
|
||
"heraldic emblem with original rampant beast",
|
||
"royal blue, crimson, gold, cream, navy"),
|
||
(("bee", "animal", "wildlife", "bird", "fox", "bear", "rabbit"),
|
||
"charming zoological illustration",
|
||
"honey gold, charcoal, leaf green, cream, rust"),
|
||
(("map", "city", "london", "travel", "trip", "skyline", "landmark"),
|
||
"mid-century travel poster with skyline",
|
||
"royal red, teal, navy, cream, mustard"),
|
||
(("rain", "weather", "cloud", "moody", "storm", "fog"),
|
||
"moody rain illustration with droplets",
|
||
"slate blue, pewter, cream, oxblood, charcoal"),
|
||
(("witch", "magic", "halloween", "wizard", "spell"),
|
||
"whimsical witchy illustration with moon",
|
||
"deep purple, black, moss green, gold, amethyst"),
|
||
(("christmas", "xmas", "santa", "snowflake", "festive"),
|
||
"festive christmas with needle-felt texture",
|
||
"pine green, berry red, cream, gold, ice blue"),
|
||
(("sport", "gym", "football", "soccer", "baseball", "workout", "jersey", "kit", "athletic", "england"),
|
||
"bold athletic emblem with motion streaks",
|
||
"navy, white, athletic red, silver, volt green"),
|
||
(("patriotic", "flag", "america", "freedom", "usa"),
|
||
"bold patriotic emblem with stars",
|
||
"navy, red, cream, gold, slate"),
|
||
(("retro", "80s", "90s", "y2k", "memphis"),
|
||
"1980s Memphis pop with geometric confetti",
|
||
"faded navy, cream, burnt orange, hot pink, teal"),
|
||
(("vintage", "antique", "distressed", "classic", "aged"),
|
||
"aged letterpress vintage with halftone",
|
||
"faded sepia, cream, muted teal, oxblood, distressed black"),
|
||
]
|
||
|
||
# 预编译单词边界正则,避免 eco 误中 cottagecore、kit 误中 jacket 等子串歧义。
|
||
# 每个关键词附加可选复数 (?:s)?,覆盖 games/cats 等复数形态。
|
||
def _pattern_from(kws: List[str]) -> "re.Pattern":
|
||
return re.compile(r"\b(?:" + "|".join(re.escape(k) + "(?:s)?" for k in kws) + r")\b")
|
||
|
||
|
||
_RULE_PATTERNS = [
|
||
(_pattern_from(list(kws)), art, pal)
|
||
for kws, art, pal in STYLE_PALETTE_RULES
|
||
]
|
||
|
||
|
||
def _kw_pattern(kws: List[str]) -> "re.Pattern":
|
||
return _pattern_from(kws)
|
||
|
||
|
||
# 风格微调池:确定性地给每条设计追加一个“技法/质感”修饰,提升新颖度与差异度。
|
||
# 选择基于主题的稳定哈希,保证「同词稳定、跨词不同」(不依赖进程随机种子)。
|
||
STYLE_TWISTS: List[str] = [
|
||
"with subtle risograph grain and slight misregistration",
|
||
"with bold halftone dot shading",
|
||
"with hand-drawn imperfect ink edges",
|
||
"with limited-palette screen-print separation",
|
||
"with fine stipple and engraving texture",
|
||
"with paper-cut layered depth",
|
||
"with art-deco geometric framing",
|
||
"with Memphis-style confetti shapes",
|
||
"with soft watercolor bleed at the edges",
|
||
"with iridescent foil accent",
|
||
]
|
||
|
||
|
||
def _stable_hash(text: str) -> int:
|
||
"""跨进程稳定的字符串哈希(不依赖 PYTHONHASHSEED)。"""
|
||
return int(hashlib.md5(text.encode("utf-8")).hexdigest(), 16)
|
||
|
||
|
||
# 类别兜底:关键词都没命中时,按 classify 类别给一个合理风格/配色(含 5 色)
|
||
STYLE_BY_CATEGORY: Dict[str, str] = {
|
||
"Event": "festive badge illustration",
|
||
"Meme": "bold comic meme illustration",
|
||
"Style": "trendy flat vector illustration",
|
||
"Niche": "clean modern vector illustration",
|
||
"Pattern": "seamless pattern tile illustration",
|
||
"Quote": "bold typographic illustration",
|
||
}
|
||
PALETTE_BY_CATEGORY: Dict[str, str] = {
|
||
"Event": "festive multi-color: red, gold, forest green, cream, berry",
|
||
"Meme": "bold high-contrast: black, white, pop yellow, magenta, cyan",
|
||
"Style": "trendy balanced modern: navy, coral, cream, sage, slate",
|
||
"Niche": "versatile balanced: teal, sand, charcoal, blush, white",
|
||
"Pattern": "harmonious repeating: terracotta, olive, cream, rust, gold",
|
||
"Quote": "high-contrast typographic: ink black, off-white, accent red, gold",
|
||
}
|
||
|
||
# 国家基调最末兜底(代码内置;真正按国家定制走 prompts/<country>/aesthetics.yaml)
|
||
COUNTRY_AESTHETICS: Dict[str, Dict[str, str]] = {
|
||
"US": {
|
||
"label": "美国",
|
||
"style_hint": "Bold vintage / retro Americana, humorous and punchy, high-contrast poster style.",
|
||
"art_style": "bold vintage retro Americana poster, clean vector",
|
||
"palette": "muted retro Americana palette: faded navy, cream, burnt orange, distressed black, mustard",
|
||
},
|
||
"GB": {
|
||
"label": "英国",
|
||
"style_hint": "Witty, self-deprecating British humor; punk / bold lettering; tea-and-rain mood.",
|
||
"art_style": "witty punk zine illustration, bold hand-lettered",
|
||
"palette": "punk-zine palette: high-contrast black, off-white, safety-orange, oxblood red, slate",
|
||
},
|
||
"JP": {
|
||
"label": "日本",
|
||
"style_hint": "Kawaii / minimalist / anime-inspired; clean lines, Tokyo street edge, original kanji accents.",
|
||
"art_style": "kawaii minimalist flat illustration, clean lines",
|
||
"palette": "soft kawaii palette: pastel pink, mint, butter yellow, soft lavender, cream",
|
||
},
|
||
"AU": {
|
||
"label": "澳大利亚",
|
||
"style_hint": "Sunny, laid-back coastal vibe; surf / beach / BBQ; relaxed and warm.",
|
||
"art_style": "sunny laid-back coastal illustration, relaxed",
|
||
"palette": "sunny coastal palette: sky blue, sand beige, coral, sun-bleached white, turquoise",
|
||
},
|
||
"MX": {
|
||
"label": "墨西哥",
|
||
"style_hint": "Vibrant Mexican folk art; Day of the Dead / calavera / Loteria / Aztec / Talavera motifs; fiesta colors.",
|
||
"art_style": "vibrant mexican folk art illustration, festive",
|
||
"palette": "fiesta palette: marigold orange, magenta, deep purple, black, gold",
|
||
},
|
||
"DE": {
|
||
"label": "德国",
|
||
"style_hint": "Bavarian-Alpine folk charm, Berlin street-art edge, Bauhaus minimalism, cozy beer-garden and Christmas-market mood.",
|
||
"art_style": "bavarian folk or bauhaus minimal illustration, clean",
|
||
"palette": "bavarian palette: bavarian blue, white, golden yellow, alpine green, red",
|
||
},
|
||
"BR": {
|
||
"label": "巴西",
|
||
"style_hint": "Vibrant tropical carnival energy, samba rhythm, football passion, bold beach and street-art colors.",
|
||
"art_style": "vibrant tropical carnival illustration, bold",
|
||
"palette": "tropical carnival palette: hot pink, turquoise, gold, lime green, purple",
|
||
},
|
||
"SA": {
|
||
"label": "沙特阿拉伯",
|
||
"style_hint": "Elegant desert minimalism, arabian geometric patterns, falconry and starry-night calm, refined and conservative.",
|
||
"art_style": "elegant desert geometric illustration, refined",
|
||
"palette": "desert palette: warm sand, terracotta, deep amber, teal, cream",
|
||
},
|
||
"PL": {
|
||
"label": "波兰",
|
||
"style_hint": "Highland folk embroidery charm, wycinanki paper-cut patterns, slavic forest mystique, retro polish poster mood.",
|
||
"art_style": "highland folk or retro polish poster illustration",
|
||
"palette": "folk palette: rust red, forest green, cream, amber gold, black",
|
||
},
|
||
"ES": {
|
||
"label": "西班牙",
|
||
"style_hint": "Flamenco passion, andalusian tile patterns, mediterranean sunshine, festive fiesta energy.",
|
||
"art_style": "flamenco or andalusian tile illustration, vibrant",
|
||
"palette": "flamenco palette: flamenco red, black, gold, cobalt blue, cream",
|
||
},
|
||
"IT": {
|
||
"label": "意大利",
|
||
"style_hint": "Renaissance elegance, roman heritage, tuscan countryside warmth, la dolce vita retro charm.",
|
||
"art_style": "renaissance or tuscan countryside illustration, elegant",
|
||
"palette": "tuscan palette: terracotta, olive green, golden wheat, cream, gold",
|
||
},
|
||
"CA": {
|
||
"label": "加拿大",
|
||
"style_hint": "Maple-leaf pride, northern-lights wonder, rocky-mountain outdoors, cozy cottage and hockey spirit.",
|
||
"art_style": "maple or rocky-mountain outdoor illustration, clean",
|
||
"palette": "canadian palette: maple red, aurora green, pine green, snow white, lake blue",
|
||
},
|
||
}
|
||
|
||
|
||
def derive_style_palette(
|
||
topic: str,
|
||
country: str,
|
||
extra_rules: Optional[List[Dict[str, str]]] = None,
|
||
category: Optional[str] = None,
|
||
apply_twist: bool = True,
|
||
) -> Tuple[str, str]:
|
||
"""根据热点词语义确定性地推导 (art_style, color_palette)。
|
||
|
||
优先级:① 国家 extra 规则(最具体)→ ② 全局关键词规则 → ③ 类别兜底 → ④ 国家基调最末兜底。
|
||
结果与具体词一一对应,跨词不同、同词稳定。apply_twist=True 时追加确定性风格微调。
|
||
"""
|
||
from .classify import classify
|
||
|
||
tl = (topic or "").lower()
|
||
cat = category or classify(topic)
|
||
|
||
# ① 国家专属 extra 规则(来自 prompts/<country>/aesthetics.yaml)
|
||
for rule in (extra_rules or []):
|
||
kws = [k.lower() for k in (rule.get("keywords") or [])]
|
||
if kws and _kw_pattern(kws).search(tl):
|
||
art = rule.get("art_style", "clean vector illustration")
|
||
pal = rule.get("color_palette", "balanced modern palette")
|
||
if apply_twist:
|
||
art = _apply_twist(art, topic)
|
||
return art, pal
|
||
|
||
# ② 全局关键词规则(单词边界匹配,具体规则优先于通用 retro/vintage)
|
||
for pat, art, pal in _RULE_PATTERNS:
|
||
if pat.search(tl):
|
||
if apply_twist:
|
||
art = _apply_twist(art, topic)
|
||
return art, pal
|
||
|
||
# ③ 类别兜底
|
||
art = STYLE_BY_CATEGORY.get(cat, "clean vector illustration")
|
||
pal = PALETTE_BY_CATEGORY.get(cat, "balanced modern color palette")
|
||
|
||
# ④ 国家基调最末兜底(极少走到)
|
||
if art == "clean vector illustration":
|
||
a = COUNTRY_AESTHETICS.get(country, {})
|
||
art = a.get("art_style", art)
|
||
pal = a.get("palette", pal)
|
||
|
||
if apply_twist:
|
||
art = _apply_twist(art, topic)
|
||
return art, pal
|
||
|
||
|
||
def _apply_twist(art: str, topic: str) -> str:
|
||
"""确定性地给 art_style 追加一个技法微调(同词稳定、跨词不同)。"""
|
||
twist = STYLE_TWISTS[_stable_hash(topic) % len(STYLE_TWISTS)]
|
||
return f"{art}, {twist}"
|
||
|
||
|
||
# 构图变体池:与风格 twist 同理,确定性地为每条设计选一个差异化构图,打破千篇一律。
|
||
COMPOSITION_VARIANTS: List[str] = [
|
||
"centered circular emblem with balanced negative space",
|
||
"all-over repeat pattern with a centered focal badge",
|
||
"side-profile hero with motion lines",
|
||
"symmetrical mandala emblem, centered",
|
||
"scattered botanical border framing a centered wreath",
|
||
"bold central emblem with spray texture",
|
||
"layered landscape with a centered focal subject",
|
||
"centered badge inside a decorative ring",
|
||
"dynamic diagonal composition with speed streaks",
|
||
"tiled geometric grid with a centered motif",
|
||
"vertical stack emblem with a banner ribbon",
|
||
"framed portrait window with ornate border",
|
||
]
|
||
|
||
|
||
def derive_composition(topic: str, category: Optional[str] = None) -> str:
|
||
"""确定性地推导构图(同词稳定、跨词不同),避免所有设计共用同一句构图。"""
|
||
return COMPOSITION_VARIANTS[_stable_hash(topic) % len(COMPOSITION_VARIANTS)]
|
||
|
||
|
||
# 图像生成策略敏感词 → 安全等效描述(生成提示词前清洗,降低内容政策拦截)
|
||
_IMG_RISKY_SWAP = {
|
||
"skull": "smiley mascot", "skeleton": "cute mascot", "blood": "red accents",
|
||
"gore": "bold shapes", "gun": "star", "weapon": "tool", "bomb": "firework",
|
||
"drug": "confetti", "demon": "cute monster", "devil": "mischievous imp",
|
||
"occult": "mystic pattern", "satanic": "dark pattern", "nazi": "retro emblem",
|
||
"hitler": "retro emblem", "zombie": "friendly ghoul", "horror": "spooky-cute",
|
||
"vampire": "night owl", "politics": "abstract shapes", "political": "abstract",
|
||
"president": "captain", "army": "team", "police": "officer",
|
||
}
|
||
|
||
|
||
def sanitize_image_prompt(prompt: str) -> str:
|
||
"""清洗生图提示词:
|
||
1) 删除一切背景描述(官方要求:透明背景由 background="transparent" 参数控制,
|
||
提示词中不得提到背景,否则无法正常生成透明背景);
|
||
2) 敏感词替换为安全等效描述(避免内容政策拦截)。
|
||
"""
|
||
import re
|
||
out = prompt or ""
|
||
# 1) 删除背景短语(中英文都处理)
|
||
for pat in (r",\s*isolated on (transparent|pure white|white) background\b",
|
||
r"\s+isolated on (transparent|pure white|white) background\b",
|
||
r",\s*(transparent|white) background\b",
|
||
r"\s+on a (transparent|white|pure white) background\b",
|
||
r",\s*no background scene\b", r",\s*no background\b", r",\s*plain (transparent|white) background\b"):
|
||
out = re.sub(pat, "", out, flags=re.IGNORECASE)
|
||
# 1.5) 删除内容策略触发段(旧模板残留的安全规则说明:no politics/religion/hate/violence/sexual 等
|
||
# 一旦出现在生图提示词中,图像 API 直接 content_policy_violation)
|
||
out = re.sub(r';?\s*any text must be safe[^;]*?(?:no gibberish|gibberish|\.[^,;]*)', '', out, flags=re.IGNORECASE)
|
||
out = re.sub(r'no (politics|religion|hate|violence|sexual|nude|nudity|bikini|nsfw|racist|profanity|swearing|adult content)s?,?', '', out, flags=re.IGNORECASE)
|
||
out = re.sub(r'(politics|religion|hate|violence|sexual content|nudity|nsfw)', '', out, flags=re.IGNORECASE)
|
||
# 2) 敏感词替换
|
||
low = out.lower()
|
||
for k, v in _IMG_RISKY_SWAP.items():
|
||
if k in low:
|
||
out = re.sub(rf"\b{re.escape(k)}\b", v, out, flags=re.IGNORECASE)
|
||
low = out.lower()
|
||
# 3) 清理多余空格/逗号
|
||
out = re.sub(r",\s*,+", ",", out)
|
||
out = re.sub(r"\s{2,}", " ", out).strip(" ,")
|
||
return out
|
||
|
||
|
||
# review(疑似商标/受保护主题)简报的「原创化魔改」引导:只做风格参考,禁止复刻商标/品牌/角色
|
||
REVIEW_REBRAND_HINT = (
|
||
" IMPORTANT: this theme is ONLY a loose stylistic reference. "
|
||
"Do NOT reproduce any brand logo, trademark, character, mascot, copyrighted artwork or real person. "
|
||
"Create a fully ORIGINAL design with a different name and distinct visual details and colors — "
|
||
"a generic, non-infringing homage in the same mood, clearly distinct from the original."
|
||
)
|
||
|
||
|
||
def ensure_rebrand_hint(brief: dict, prompt: str) -> str:
|
||
"""review 简报生成设计时兜底追加原创化魔改引导(旧缓存简报未注入时补上)。"""
|
||
if str(brief.get("risk_level", "")).strip().lower() == "review" \
|
||
and "IMPORTANT: this theme is ONLY" not in (prompt or ""):
|
||
return (prompt or "") + REVIEW_REBRAND_HINT
|
||
return prompt or ""
|