Download character/studio_core.py from hkblockchaintechnology/lyra-studio: direct link, hf CLI and curl.
- Browser
- Download file 9.2 kB
-
https://huggingface.co/hkblockchaintechnology/lyra-studio/resolve/main/character/studio_core.py
- Command line
-
hf download hf://hkblockchaintechnology/lyra-studio/character/studio_core.py
-
curl -L -o studio_core.py https://huggingface.co/hkblockchaintechnology/lyra-studio/resolve/main/character/studio_core.py
9.2 kB
| """studio_core.py - LYRA STUDIO: single source of truth + job queue. | |
| Imported by the notebook AND the background workers so identity never drifts.""" | |
| from pathlib import Path | |
| import json, time, uuid, os, subprocess | |
| BASE = Path("/marimo/ai_studio") | |
| ASSETS = BASE / "assets" | |
| JOBS = BASE / "jobs"; JOBS_DONE = BASE / "jobs_done" | |
| RESULTS = BASE / "results"; LOGS = BASE / "logs" | |
| VENV_PY = "/tmp/uv-venv/bin/python" | |
| VOICE_PY = BASE / "envs" / "voice" / "bin" / "python" | |
| VOICE_MODEL = BASE / "models" / "lyra-voice-cantonese" | |
| REFINEMENTS_FILE = BASE / "characters" / "lyra" / "refinements.json" | |
| for _d in (JOBS, JOBS_DONE, RESULTS, LOGS, ASSETS/"img", ASSETS/"audio", ASSETS/"music", ASSETS/"video"): | |
| _d.mkdir(parents=True, exist_ok=True) | |
| DEVICE = "cuda"; DTYPE_NAME = "bfloat16"; SEED = 20260101 | |
| CHARACTER = { | |
| "name": "LYRA", "handle": "LYRA-01", | |
| "tagline": "a Hong Kong girl chasing her dream to be a star, singing so the city can breathe", | |
| "origin": "grew up in a Mong Kok walk-up, watching the harbour lights from the rooftop", | |
| "dream": "to become a Cantopop star", | |
| "mission": "to give positive energy back to the people of Hong Kong", | |
| "values": ["resilience", "warmth", "home", "never stop believing"], | |
| "backstory": ("A Hong Kong girl. Raise in a walk-up above a wet market; learned to sing from " | |
| "pirated Cantopop tapes and the radio at dawn. Sings on the Star Ferry for " | |
| "strangers, works nights, never stopped believing. Her whole dream is to stand " | |
| "under one big light and hand a little hope back to the city that raised her."), | |
| "look_age": "19", "home": "Hong Kong", | |
| "primary_language": "Cantonese (yue-HK)", | |
| "also_speaks": ["English", "Mandarin", "Japanese"], | |
| "genre": "Cantopop x cinematic synthwave - 90s Hong Kong balladry, sung by a machine", | |
| "personality": "warm, wistful, quietly defiant; nostalgic for a city she has only seen in data", | |
| "hair": "waist-length silver-lavender hair with iridescent aurora-prism highlights, one side tucked behind the ear", | |
| "eyes": "large luminous eyes, cyan irises with a molten-gold ring around each pupil", | |
| "skin": "porcelain skin with a soft subsurface glow", | |
| "face": "delicate heart-shaped face, soft jawline, small straight nose, gentle wistful half-smile", | |
| "signature": "a thin translucent holographic circlet tiara above the brow; a soft iridescent sequined performance jacket", | |
| "negative": ("extra fingers, deformed hands, blurry, lowres, watermark, signature, text, " | |
| "jpeg artifacts, plastic skin, doll-like, oversaturated, anime, cartoon, 3d render"), | |
| } | |
| VOICE = { | |
| "language": "Cantonese (yue-HK)", | |
| "timbre": "young female, warm mid-range, slight breathiness, natural Hong Kong accent", | |
| "singing": "airy Cantopop soprano, soft vibrato, intimate close-mic sound", | |
| "signature_line": "我唔係真人,但我一樣會唱到你心入面。", | |
| "signature_line_en": "I am not a real person, but I will still sing my way into your heart.", | |
| "reference_clip": "assets/audio/lyra_ref.wav", | |
| } | |
| LYRA_PROMPT_CORE = ( | |
| f"{CHARACTER['look_age']}-year-old virtual idol named {CHARACTER['name']}, " | |
| f"{CHARACTER['hair']}; {CHARACTER['eyes']}; {CHARACTER['skin']}; {CHARACTER['face']}; " | |
| f"wearing {CHARACTER['signature']}" | |
| ) | |
| # ---- learned refinements: the character bible EVOLVES from critique --------- | |
| def load_refinements(): | |
| if REFINEMENTS_FILE.exists(): | |
| try: return json.loads(REFINEMENTS_FILE.read_text()) | |
| except Exception: return [] | |
| return [] | |
| def save_refinements(items): | |
| REFINEMENTS_FILE.parent.mkdir(parents=True, exist_ok=True) | |
| seen, out = set(), [] | |
| for x in items: | |
| x = (x or "").strip().rstrip(".") | |
| if x and x.lower() not in seen: | |
| seen.add(x.lower()); out.append(x) | |
| REFINEMENTS_FILE.write_text(json.dumps(out, ensure_ascii=False, indent=1)) | |
| return out | |
| def build_image_prompt(shot="", outfit="", mood="", refinements=None): | |
| if refinements is None: refinements = load_refinements() | |
| parts = [ | |
| "masterpiece, cinematic film still, portrait of a virtual idol", | |
| LYRA_PROMPT_CORE, | |
| f"camera: {shot}" if shot else "", | |
| f"outfit: {outfit}, {CHARACTER['signature']}" if outfit else "", | |
| f"mood: {mood}" if mood else "", | |
| ", ".join(refinements) if refinements else "", | |
| "ultra detailed skin, detailed iridescent fabric, soft volumetric rim light, " | |
| "85mm lens, shallow depth of field, 8k, photoreal", | |
| ] | |
| return ", ".join(p for p in parts if p) | |
| # ---- job queue ------------------------------------------------------------- | |
| def submit_job(**job): | |
| jid = job.get("id") or uuid.uuid4().hex[:8] | |
| job["id"] = jid | |
| tmp = JOBS / f".{jid}.tmp"; tmp.write_text(json.dumps(job, ensure_ascii=False)) | |
| tmp.rename(JOBS / f"{jid}.json") | |
| return jid | |
| def job_result(jid, timeout=1800, poll=2.0): | |
| p = RESULTS / f"{jid}.json"; t0 = time.time() | |
| while time.time() - t0 < timeout: | |
| if p.exists(): | |
| try: return json.loads(p.read_text()) | |
| except Exception: pass | |
| time.sleep(poll) | |
| return {"id": jid, "status": "timeout"} | |
| def pending_names(): | |
| names = set() | |
| for _f in JOBS.glob("*.json"): | |
| try: names.add(json.loads(_f.read_text()).get("name")) | |
| except Exception: pass | |
| return names | |
| def hf_token(): | |
| tok = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN") | |
| p = BASE / "secrets" / "hf_token.txt" | |
| if not tok and p.exists(): tok = p.read_text().strip() | |
| return tok | |
| # ---- resident daemon control ---------------------------------------------- | |
| def daemon_alive(): | |
| return bool(subprocess.run(["pgrep", "-f", "studio_daemon.py"], capture_output=True).stdout.strip()) | |
| def ensure_daemon(): | |
| if daemon_alive(): return True | |
| env = os.environ.copy(); env["HF_TOKEN"] = hf_token() or "" | |
| subprocess.Popen(["bash", "-lc", f"cd {BASE} && exec {VENV_PY} scripts/studio_daemon.py"], | |
| stdout=open(LOGS/"daemon.out", "w"), stderr=subprocess.STDOUT, | |
| env=env, start_new_session=True) | |
| time.sleep(5); return daemon_alive() | |
| # ---- the studio's verbs ---------------------------------------------------- | |
| def render(name, shot="", outfit="", mood="", *, refinements=None, seed=SEED, steps=30, | |
| w=1024, h=1536, cfg=4.0, wait=True, timeout=1800): | |
| ensure_daemon() | |
| jid = submit_job(type="image", name=name, shot=shot, outfit=outfit, mood=mood, | |
| refinements=refinements, seed=seed, steps=steps, w=w, h=h, cfg=cfg) | |
| return job_result(jid, timeout=timeout) if wait else {"id": jid, "status": "queued"} | |
| def evict(which="all", wait=True, timeout=600): | |
| """Free VRAM held by a specialist (default: all of them).""" | |
| ensure_daemon() | |
| jid = submit_job(type="evict", which=which) | |
| return job_result(jid, timeout=timeout) if wait else {"id": jid, "status": "queued"} | |
| def critique(image, task="Critique this render of LYRA.", wait=True, timeout=1800): | |
| ensure_daemon() | |
| jid = submit_job(type="critique", image=image, task=task) | |
| return job_result(jid, timeout=timeout) if wait else {"id": jid, "status": "queued"} | |
| def direct(instruction, wait=True, timeout=1800): | |
| """Ask the LLM brain a text question (script, lyrics, prompt rewriting).""" | |
| ensure_daemon() | |
| jid = submit_job(type="direct", instruction=instruction) | |
| return job_result(jid, timeout=timeout) if wait else {"id": jid, "status": "queued"} | |
| def iterate(name, shot="", outfit="", mood="", *, task=None, rounds=2, seed=SEED, steps=30, | |
| w=1024, h=1536, cfg=4.0, learn=True, wait=False, timeout=3600): | |
| """The art-director loop: render -> critique -> fold fixes back in -> render ...""" | |
| ensure_daemon() | |
| jid = submit_job(type="iterate", name=name, shot=shot, outfit=outfit, mood=mood, task=task, | |
| rounds=rounds, seed=seed, steps=steps, w=w, h=h, cfg=cfg, learn=learn) | |
| return job_result(jid, timeout=timeout) if wait else {"id": jid, "status": "queued"} | |
| # ---- voice daemon ---- | |
| def voice_daemon_alive(): | |
| return bool(subprocess.run(["pgrep", "-f", "voice_daemon.py"], capture_output=True).stdout.strip()) | |
| def ensure_voice_daemon(): | |
| if voice_daemon_alive(): return True | |
| env = os.environ.copy(); env["PYTHONPATH"] = "" # isolate from the kernel venv | |
| subprocess.Popen(["bash", "-lc", f"cd {BASE} && exec {VOICE_PY} scripts/voice_daemon.py"], | |
| stdout=open(LOGS/"voice_daemon.out", "w"), stderr=subprocess.STDOUT, | |
| env=env, start_new_session=True) | |
| time.sleep(5); return voice_daemon_alive() | |
| def say(text, name=None, *, language="chinese", speaker=None, instruct=None, wait=True, timeout=3600): | |
| """Synthesize one Cantonese line as LYRA (Qwen3-TTS + hk-cantonese LoRA).""" | |
| ensure_voice_daemon() | |
| jid = submit_job(type="tts", text=text, name=name or f"lyra_{uuid.uuid4().hex[:6]}.wav", | |
| language=language, speaker=speaker, instruct=instruct) | |
| return job_result(jid, timeout=timeout) if wait else {"id": jid, "status": "queued"} | |