from __future__ import annotations import os import random from functools import lru_cache from .safety import sanitize_generated_lyrics DEFAULT_TEXT_MODEL_ID = "CohereLabs/tiny-aya-global" FALLBACKS = { "en": "Neon memories turn softly tonight\nWe dance where the old stars glow", "de": "Alte Lichter ziehen durch die Nacht\nWir tanzen, wo die Zeit noch klingt", "fr": "Les vieux neons brillent dans la nuit\nNous dansons ou le temps revient", "es": "Viejas luces cruzan la ciudad\nBailamos donde vuelve el tiempo", } @lru_cache(maxsize=1) def load_text_model(model_id: str): from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline token = os.getenv("HF_TOKEN") or os.getenv("HUGGING_FACE_HUB_TOKEN") tokenizer = AutoTokenizer.from_pretrained(model_id, token=token) model = AutoModelForCausalLM.from_pretrained(model_id, token=token) return pipeline("text-generation", model=model, tokenizer=tokenizer) def fallback_micro_lyrics(theme, mood, language_id) -> str: base = FALLBACKS.get(language_id, FALLBACKS["en"]) keywords = theme.get("keywords", []) if isinstance(theme, dict) else [] if language_id == "en" and keywords: return f"{keywords[0].title()} memories turn softly tonight\nWe dance where {keywords[-1]} glows" return base def generate_micro_lyrics( source_era, source_genre, remix_era, remix_genre, mood: str, theme: dict, language_id: str, seed: int | None = None, ) -> dict: random.seed(seed) model_id = os.getenv("TEXT_MODEL_ID", DEFAULT_TEXT_MODEL_ID) language_name = {"en": "English", "de": "German", "fr": "French", "es": "Spanish"}.get(language_id, "English") prompt = ( f"Write 2 lines of completely original micro-lyrics in {language_name}.\n" f"Theme: {theme.get('label', 'time travel')}.\nMood: {mood}.\n" f"Era blend: {source_era.get('label')} {source_genre.get('label')} transformed into " f"{remix_era.get('label')} {remix_genre.get('label')}.\n" "Rules:\n- maximum 12 words per line\n- no famous song titles\n- no artist names\n" "- no quotes from existing lyrics\n- no imitation of any artist, band, singer, producer, DJ, or known song\n" "- no copyrighted lyrics\nReturn only the two lyric lines." ) try: if os.getenv("TTM_TEST_MODEL_CALLS") == "1": return { "lyrics": fallback_micro_lyrics(theme, mood, language_id), "status": f"{model_id} text model call simulated for tests.", "fallback_used": False, "model_attempted": True, "model_id": model_id, } generator = load_text_model(model_id) output = generator(prompt, max_new_tokens=48, do_sample=True, temperature=0.75, pad_token_id=0)[0]["generated_text"] lyrics = output.replace(prompt, "").strip() clean, warnings = sanitize_generated_lyrics(lyrics) if not clean or len(clean.splitlines()) < 2: raise RuntimeError("; ".join(warnings) or "Tiny Aya returned unusable lyrics.") return {"lyrics": clean, "status": f"Generated with {model_id}.", "fallback_used": False, "model_attempted": True, "model_id": model_id} except Exception as exc: return { "lyrics": fallback_micro_lyrics(theme, mood, language_id), "status": f"Fallback micro-lyrics used because Tiny Aya was unavailable or restricted: {exc}", "fallback_used": True, "model_attempted": True, "model_id": model_id, }