turntable-time-machine / src /prompt_builder.py
Codex
Replace DJ intro TTS with MMS models
7b926ae
Raw
History Blame Contribute Delete
7.43 kB
from __future__ import annotations
import html
import random
from .safety import copyright_safety_note
LANG_TEMPLATES = {
"de": "Erzeuge einen originalen Musikclip. Ausgangspunkt: {source_era} {source_genre}: {source_style}. Verwandle ihn in {remix_era} {remix_genre}: {remix_style}. Stimmung: {mood}. Textur: {texture_terms}. BPM: {bpm}. Dauer: {duration} Sekunden.",
"fr": "Cree un clip musical original. Point de depart: {source_era} {source_genre}: {source_style}. Transforme-le en {remix_era} {remix_genre}: {remix_style}. Ambiance: {mood}. Texture: {texture_terms}. BPM: {bpm}. Duree: {duration} secondes.",
"es": "Crea un clip musical original. Punto de partida: {source_era} {source_genre}: {source_style}. Transformalo en {remix_era} {remix_genre}: {remix_style}. Ambiente: {mood}. Textura: {texture_terms}. BPM: {bpm}. Duracion: {duration} segundos.",
}
INTRO_MOOD_LINES = {
"en": "The mood is {mood}, and the route is entirely original.",
"de": "Die Stimmung ist {mood}, und diese Route ist vollstaendig original.",
"fr": "L'ambiance est {mood}, et cette route est entierement originale.",
"es": "El ambiente es {mood}, y esta ruta es completamente original.",
}
INTRO_TEMPLATES = {
"de": "Guten Abend, Zeitreisende. Wir schicken {source_era} {source_genre} durch {remix_era} {remix_genre}.",
"fr": "Bonsoir, voyageurs du temps. Nous envoyons {source_era} {source_genre} vers {remix_era} {remix_genre}.",
"es": "Buenas noches, viajeros del tiempo. Enviamos {source_era} {source_genre} hacia {remix_era} {remix_genre}.",
}
def choose_bpm(remix_genre, source_genre=None, mood=None, seed=None) -> int:
rng = random.Random(seed)
lo, hi = (remix_genre or {}).get("bpm_range") or (source_genre or {}).get("bpm_range") or [90, 125]
if mood == "relaxed and intimate":
hi = max(lo, int((lo + hi) / 2))
elif mood == "energetic and club-ready":
lo = min(hi, int((lo + hi) / 2))
return rng.randint(int(lo), int(hi))
def _english_prompt(source_era, source_genre, remix_era, remix_genre, texture, mood, duration, bpm) -> str:
texture_terms = ", ".join(texture.get("prompt_terms", [])) or texture.get("label", "clean digital master")
return (
f"Create an original music clip. Start from a {source_era.get('label')} {source_genre.get('label')} "
f"musical vocabulary: {source_genre.get('safe_prompt_style')}. Transform it into a {remix_era.get('label')} "
f"{remix_genre.get('label')} production: {remix_genre.get('safe_prompt_style')}. Mood: {mood}. "
f"Texture: {texture_terms}. BPM: {bpm}. Duration: {duration} seconds."
)
def _localized_prompt(language_id, source_era, source_genre, remix_era, remix_genre, texture, mood, duration, bpm) -> tuple[str, str]:
if language_id == "en":
return _english_prompt(source_era, source_genre, remix_era, remix_genre, texture, mood, duration, bpm), "English prompt."
template = LANG_TEMPLATES.get(language_id)
if not template:
return _english_prompt(source_era, source_genre, remix_era, remix_genre, texture, mood, duration, bpm), "Selected language template missing; English prompt used."
return template.format(
source_era=source_era.get("label"),
source_genre=source_genre.get("label"),
source_style=source_genre.get("safe_prompt_style"),
remix_era=remix_era.get("label"),
remix_genre=remix_genre.get("label"),
remix_style=remix_genre.get("safe_prompt_style"),
texture_terms=", ".join(texture.get("prompt_terms", [])),
mood=mood,
bpm=bpm,
duration=duration,
), "Selected-language prompt built from deterministic template."
def build_music_prompt(
source_era,
source_genre,
remix_era,
remix_genre,
texture,
mood,
duration,
bpm,
language_id="en",
prompt_language_mode="Best stability (English)",
vocal_mode="Instrumental only",
lyrics=None,
) -> str:
english = _english_prompt(source_era, source_genre, remix_era, remix_genre, texture, mood, duration, bpm)
localized, _ = _localized_prompt(language_id, source_era, source_genre, remix_era, remix_genre, texture, mood, duration, bpm)
if prompt_language_mode in ("Broadcast language", "Selected language prompt"):
prompt = localized
elif prompt_language_mode in ("English + broadcast language", "Bilingual prompt") and language_id != "en":
prompt = f"{english}\n{localized}"
else:
prompt = english
if vocal_mode == "Instrumental only":
prompt += " No vocals. No lyrics."
elif vocal_mode == "Wordless vocal texture":
prompt += " Optional wordless, non-lexical vocal texture only. No recognizable words. No lyrics."
elif vocal_mode == "Original micro-lyrics":
prompt += f" Use only the following original micro-lyrics. Do not add extra lyrics.\nLyrics:\n{lyrics or ''}"
prompt += " Original composition only. No copyrighted melody. Do not imitate any specific song, artist, singer, producer, DJ, hook, riff, sample, lyric, melody, or arrangement."
return prompt
def build_dj_intro(persona, source_era, source_genre, remix_era, remix_genre, mood, language_id="en") -> dict:
values = {
"source_era": source_era.get("label"),
"source_genre": source_genre.get("label"),
"remix_era": remix_era.get("label"),
"remix_genre": remix_genre.get("label"),
}
if language_id == "en":
text = (persona.get("intro_template") or "").format(**values)
status = "English persona intro template."
else:
text = INTRO_TEMPLATES.get(language_id, persona.get("intro_template", "")).format(**values)
status = "Handcrafted multilingual intro template." if language_id in INTRO_TEMPLATES else "Intro template fallback to English."
text = f"{text} {INTRO_MOOD_LINES.get(language_id, INTRO_MOOD_LINES['en']).format(mood=mood)}"
return {"text": text[:420], "status": status}
def build_mixtape_card(
source_era,
source_genre,
remix_era,
remix_genre,
persona,
texture,
mood,
bpm,
language,
prompt_language_mode,
vocal_mode,
lyric_theme,
lyrics,
statuses,
seed,
) -> str:
route = f"{source_era.get('label')} {source_genre.get('label')} -> {remix_era.get('label')} {remix_genre.get('label')}"
rows = [
("Route", route),
("Fictional DJ", persona.get("name")),
("Language", language.get("label")),
("Prompt mode", prompt_language_mode),
("Vocal mode", vocal_mode),
("Lyric theme", lyric_theme.get("label") if lyric_theme else "None"),
("Texture", texture.get("label")),
("Mood", mood),
("BPM / Seed", f"{bpm} / {seed}"),
("Model stack", "ACE-Step 1.5 music, MMS TTS spoken DJ, Tiny Aya text; graceful local fallbacks"),
("Status", " | ".join(statuses)),
("Safety", copyright_safety_note()),
]
lyric_html = ""
if lyrics:
lyric_html = f"<div class='lyrics-chip'>{html.escape(lyrics).replace(chr(10), '<br>')}</div>"
kv = "".join(f"<div class='ttm-kv'><span>{html.escape(k)}</span><b>{html.escape(str(v))}</b></div>" for k, v in rows)
return f"""
<div class="mixtape-card">
<div class="mixtape-title">Mixtape Card</div>
<h3>{html.escape(route)}</h3>
{lyric_html}
{kv}
</div>
"""