import os import json import uuid import spaces from gradio import Server from fastapi.responses import Response, FileResponse from fastapi.staticfiles import StaticFiles from huggingface_hub import hf_hub_download from transformers import Wav2Vec2Processor, Wav2Vec2ForCTC import torch import torchaudio from omnivoice import OmniVoice from llama_cpp import Llama import boto3 from typing import Generator from prompts import get_system_prompt from helpers import parse_dialogue_tags, create_episode_zip, postprocess_script, expected_schema, therapistCostumes, maleCostumes, femaleCostumes from forced_alignment import forced_align import json_repair import re # GLOBAL VARIABLE TO CHANGE TO TOGGLE OFF-GRID MODE # True will use Cloudflare R2, meant for the ZeroGPU space. # False will serve Unity build files from this Gradio Server, and store episodes here # We are not defaulting to serving game from HF space, because outbound network bandwidth is very slow. # As Unity build size is around 130 MB, and addressable assets are ~10MB per character (Each episode needs 3) online = True llm_model_path = hf_hub_download( repo_id="unsloth/gemma-4-26B-A4B-it-GGUF", filename="gemma-4-26B-A4B-it-UD-Q4_K_M.gguf" ) tts_model = OmniVoice.from_pretrained( "k2-fsa/OmniVoice", dtype=torch.float16, device_map="cuda:0" ) TTS_SAMPLE_RATE = 24000 align_processor = Wav2Vec2Processor.from_pretrained("facebook/mms-1b-all") align_model = Wav2Vec2ForCTC.from_pretrained("facebook/mms-1b-all", dtype=torch.float16).to("cuda:0") language = "hin" align_processor.tokenizer.set_target_lang(language) align_model.load_adapter(language) s3 = boto3.client( "s3", endpoint_url=os.getenv("R2_ENDPOINT_URL"), aws_access_key_id=os.getenv("R2_ACCESS_KEY_ID"), aws_secret_access_key=os.getenv("R2_SECRET_ACCESS_KEY"), ) EPISODES_DIR = "./episodes" os.makedirs(EPISODES_DIR, exist_ok=True) characters = {} ref_texts = { "emily": "Hey buddy! Ready to make something interesting? Let's go.", "priya": "Hey buddy! Ready to make something interesting? Let's go.", "mike": "I like horror stories, like a lot. And also romantic comedy.", "rahul": "I like horror stories, like a lot. And also romantic comedy.", "mia": "Hey there! I'm Hazel. What's on your mind? Let's create an awesome episode.", "neha": "Hey there! I'm Hazel. What's on your mind? Let's create an awesome episode.", } for name in ["emily", "mike", "mia", "priya", "rahul", "neha"]: waveform, sr = torchaudio.load(f"./characters/{name}.wav") characters[name] = {"ref_audio": (waveform, sr), "ref_text": ref_texts[name]} @spaces.GPU(duration=120) def warmup(): print("Warming up .gguf cache...") llm = Llama(model_path=llm_model_path, n_gpu_layers=-1, n_ctx=1024, flash_attn=True, verbose=False) llm.create_chat_completion(messages=[{"role": "user", "content": "Hey!"}], max_tokens=8) print("Success! .gguf cached!") @spaces.GPU() def run_episode_generation_pipeline(character_names: dict[str, str], problem: str, extras: str = "", language="English"): llm = Llama(model_path=llm_model_path, n_gpu_layers=-1, n_ctx=8192, flash_attn=True, verbose=False) user_prompt = f"Their problem is that {problem}" if extras != "": user_prompt += f"\nTry to integrate these into the therapy at some point: {extras}" yield "Writing Script..." print("Generating Script...") response = llm.create_chat_completion( messages=[ {"role": "system", "content": get_system_prompt(character_names, language)}, {"role": "user", "content": user_prompt} ], max_tokens=4096, ) script_raw = response['choices'][0]['message']['content'] print("RAW LLM OUTPUT") print(script_raw) # Episode JSON try: # Try fixing the script if broken script = json_repair.loads( script_raw, schema=expected_schema, schema_repair_mode="salvage" ) # Fix starting look_at tags for item in script.get("dialogues", []): original_text = item.get("dialogue", "") fixed_text = re.sub(r'(? Generator[str, None, None]: if language not in ["English", "Hindi"]: language = "English" if language == "English": character_names = { "therapist": "emily", "male": "mike", "female": "mia" } else: character_names = { "therapist": "priya", "male": "rahul", "female": "neha" } yield "Starting Generation..." pipeline_generator = run_episode_generation_pipeline(character_names, problem, language=language) script = None all_audio = None for result in pipeline_generator: if isinstance(result, str): yield result else: script, all_audio = result # Add costume to the script script["therapistCostume"] = therapistCostume if therapistCostume in therapistCostumes else "" script["maleCostume"] = maleCostume if maleCostume in maleCostumes else "" script["femaleCostume"] = femaleCostume if femaleCostume in femaleCostumes else "" zip_bytes = create_episode_zip(script, all_audio) unique_id = uuid.uuid4().hex[:8] filename = f"episode_{unique_id}.zip" yield "Uploading episode..." # Online version if online: print("Uploading episode...") # This takes time as # HF Space is throttling outbound traffic, even though each episode is ~6 MB # So using Cloudflare R2. # Subsequent episode sharings would be blazing fast s3_key = f"{R2_PREFIX}/{filename}" s3.put_object( Bucket=BUCKET, Key=s3_key, Body=zip_bytes, ContentType="application/zip" ) print(f"Successfully uploaded episode to R2: {BUCKET}/{s3_key}") yield f"[EPISODE]{filename}" # Off grid episode saving and access else: local_path = os.path.join(EPISODES_DIR, filename) with open(local_path, "wb") as f: f.write(zip_bytes) print(f"Successfully saved locally: {local_path}") yield f"[EPISODE]{filename}" @app.get("/online-status") async def get_online_status(): return {"online": online} if __name__ == "__main__": warmup() app.launch()