llm-cinema / characters.py
ConductorAILabs's picture
Upload folder using huggingface_hub
e93d4ef verified
Raw
History Blame Contribute Delete
3.51 kB
"""
Saved character library — name a character once, reuse it in any movie.
Custom characters live in characters.json and resolve everywhere draw.draw_sprite
is used; a saved name takes precedence over the built-in library. Add them from
ASCII art you provide, or let the model draw one from a description.
python characters.py list
python characters.py add "fox king" --desc "a fox wearing a tiny crown" --color orange
python characters.py add robo --art-file robo.txt --color gray
python characters.py show "fox king"
python characters.py rm robo
A leaf module: it imports nothing from the package at module load (draw.py imports
THIS), so there is no cycle; add() imports draw lazily only when it must draw.
"""
import argparse
import json
import os
STORE = os.path.join(os.path.dirname(__file__), "characters.json")
def _load() -> dict:
if not os.path.exists(STORE):
return {}
try:
return json.load(open(STORE))
except (OSError, json.JSONDecodeError):
return {}
def _save(d: dict) -> None:
json.dump(d, open(STORE, "w"), ensure_ascii=False, indent=2)
def library() -> dict:
return _load()
def get(name: str | None) -> list[str] | None:
e = _load().get((name or "").strip().lower())
return e["art"] if e else None
def color(name: str | None) -> str | None:
e = _load().get((name or "").strip().lower())
return e.get("color") if e else None
def save(name: str, art: list[str], color: str | None = None, desc: str = "") -> str:
key = name.strip().lower()
d = _load()
d[key] = {"art": art, "color": color, "desc": desc}
_save(d)
return key
def add(name: str, desc: str = "", art: list[str] | None = None, color: str | None = None) -> str:
"""Add a character: use provided `art`, else draw it from `desc`/name."""
import draw # lazy: draw imports characters, so import it only when drawing
art = draw._normalize(art) if art is not None else draw.draw_sprite(name, desc)
return save(name, art, color, desc)
def remove(name: str) -> None:
d = _load()
d.pop(name.strip().lower(), None)
_save(d)
def main():
ap = argparse.ArgumentParser(description="Manage the saved character library.")
sub = ap.add_subparsers(dest="cmd", required=True)
sub.add_parser("list")
a = sub.add_parser("add")
a.add_argument("name")
a.add_argument("--desc", default="")
a.add_argument("--art-file", help="a text file of ASCII art (<=7 rows, <=14 cols)")
a.add_argument("--color", help="a palette colour name (e.g. orange, cyan, red)")
s = sub.add_parser("show"); s.add_argument("name")
r = sub.add_parser("rm"); r.add_argument("name")
args = ap.parse_args()
if args.cmd == "list":
lib = library()
if not lib:
print("(no saved characters yet)")
for k, e in sorted(lib.items()):
print(f" {k} [{e.get('color') or 'auto'}] {e.get('desc','')}")
elif args.cmd == "add":
art = open(args.art_file, encoding="utf-8").read().splitlines() if args.art_file else None
key = add(args.name, desc=args.desc, art=art, color=args.color)
print(f"saved '{key}':")
print("\n".join(get(key)))
elif args.cmd == "show":
art = get(args.name)
print("\n".join(art) if art else f"(no character named '{args.name}')")
elif args.cmd == "rm":
remove(args.name)
print(f"removed '{args.name}'")
if __name__ == "__main__":
main()