cameo creature pfps to chardesigns

This commit is contained in:
2026-04-04 23:29:06 -06:00
parent f07c8f7430
commit 3ad2cebeb6
16 changed files with 166 additions and 4 deletions
+57 -3
View File
@@ -8,6 +8,7 @@ Usage:
Defaults:
chat-root: MIXER_TWITCH_CHAT env, else Windows Synology path used in this project.
Writes JPEGs under Story/pfp/<slug>/ and inserts a marked section into each roster .md.
Also fills Story/Cameo-Creatures.md from CAMEO_ROSTER (see tools/pfp_from_chat.py).
"""
from __future__ import annotations
@@ -53,6 +54,21 @@ MD_FILES: dict[str, str] = {
"raincloud": "RaincloudTheDragon.md",
}
# Grouped cameo handles (see Story/Cameo-Creatures.md). Slug = folder under Story/pfp/<slug>/.
CAMEO_ROSTER: dict[str, list[str]] = {
"cameo-branndon": ["branndongames", "experimenta1ic3"],
"cameo-noncritical": ["noncriticalmother", "noncriticalgamingttv"],
"cameo-pirate": ["pirate_protogen"],
"cameo-foxy": ["foxy_fnaf5_ucn"],
"cameo-gymrat": ["basedgymrat"],
"cameo-miclbero": ["miclbero"],
"cameo-queen": ["cameoqueen86"],
"cameo-rayne": ["rayne8856"],
"cameo-bd": ["bd_cum_lube"],
}
CAMEO_MD = "Cameo-Creatures.md"
DEFAULT_CHAT_ROOT = os.environ.get(
"MIXER_TWITCH_CHAT",
r"C:\Users\Nathan\SynologyDrive\YouTube\Streams\MixerTwitch",
@@ -113,12 +129,14 @@ class SeenAvatar:
url: str
def collect_avatars(chat_root: Path) -> dict[tuple[str, str], dict[str, SeenAvatar]]:
def collect_avatars(
chat_root: Path, roster: dict[str, list[str]]
) -> dict[tuple[str, str], dict[str, SeenAvatar]]:
"""
Returns map (slug, login) -> fingerprint -> SeenAvatar (earliest message wins per fp).
"""
login_to_slug: dict[str, str] = {}
for slug, logins in ROSTER.items():
for slug, logins in roster.items():
for lg in logins:
login_to_slug[lg.lower()] = slug
@@ -236,7 +254,8 @@ def main() -> int:
print("Set MIXER_TWITCH_CHAT or pass --chat-root.", file=sys.stderr)
return 1
acc = collect_avatars(args.chat_root)
full_roster: dict[str, list[str]] = {**ROSTER, **CAMEO_ROSTER}
acc = collect_avatars(args.chat_root, full_roster)
pfp_root = args.story_root / "pfp"
# Flatten per slug for multi-login: still per-login files
@@ -294,6 +313,41 @@ def main() -> int:
md_path.write_text(inject_section(text, section_md), encoding="utf-8")
print(f"updated {md_path}")
# Cameo creatures: one combined section in Cameo-Creatures.md
cameo_images: list[tuple[str, str]] = []
for slug in CAMEO_ROSTER:
logins = CAMEO_ROSTER[slug]
story_rel = f"pfp/{slug}"
for login in logins:
key = (slug, login)
avs = acc.get(key, {})
ordered = sorted(avs.values(), key=lambda x: x.first_at)
if not ordered:
print(f"No avatars in exports for cameo `{login}` ({slug})", file=sys.stderr)
continue
for i, av in enumerate(ordered):
fn = f"{login}_{i}.jpg"
rel = f"{story_rel}/{fn}"
dest = pfp_root / slug / fn
if not download_as_jpeg(av.url, dest):
continue
cap = f"`{login}`"
if len(ordered) > 1:
cap = f"{cap}{i + 1}"
cameo_images.append((cap, rel))
if cameo_images:
cameo_md = build_section_md(cameo_images)
cameo_path = args.story_root / CAMEO_MD
if not args.no_write_md and cameo_path.is_file():
cameo_path.write_text(
inject_section(cameo_path.read_text(encoding="utf-8"), cameo_md),
encoding="utf-8",
)
print(f"updated {cameo_path}")
elif not args.no_write_md:
print("No cameo avatars downloaded (check exports and logins).", file=sys.stderr)
return 0