157 lines
4.9 KiB
Python
157 lines
4.9 KiB
Python
|
|
"""Lyria 3 capabilities — synced from Google Gemini API docs + live model list."""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
from dataclasses import dataclass
|
|||
|
|
|
|||
|
|
from google import genai
|
|||
|
|
from google.genai import errors as genai_errors
|
|||
|
|
|
|||
|
|
DOCS_URL = "https://ai.google.dev/gemini-api/docs/music-generation"
|
|||
|
|
|
|||
|
|
VOCAL_MODES = [
|
|||
|
|
{
|
|||
|
|
"id": "instrumental",
|
|||
|
|
"label": "Instrumental only",
|
|||
|
|
"hint": "Lyria tip: include 'Instrumental only, no vocals' in every prompt.",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "mix",
|
|||
|
|
"label": "Mix (textures + chants)",
|
|||
|
|
"hint": "Wordless vocals, Sahel chants, whispers — no full lead lyrics unless chat asks.",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "vocals",
|
|||
|
|
"label": "Full vocals",
|
|||
|
|
"hint": "Lead vocals and lyrics; language follows prompt or Lyria language setting.",
|
|||
|
|
},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
LANGUAGES = [
|
|||
|
|
{"id": "auto", "label": "Auto (match prompt / taste)"},
|
|||
|
|
{"id": "en", "label": "English"},
|
|||
|
|
{"id": "hi", "label": "Hindi"},
|
|||
|
|
{"id": "tr", "label": "Turkish"},
|
|||
|
|
{"id": "fr", "label": "French"},
|
|||
|
|
{"id": "ar", "label": "Arabic"},
|
|||
|
|
{"id": "es", "label": "Spanish"},
|
|||
|
|
{"id": "pt", "label": "Portuguese"},
|
|||
|
|
{"id": "de", "label": "German"},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
SINGER_PROFILES = [
|
|||
|
|
{"id": "", "label": "Default (model picks)"},
|
|||
|
|
{"id": "male_baritone", "label": "Male baritone — warm, desert blues"},
|
|||
|
|
{"id": "male_tenor", "label": "Male tenor — bright, energetic"},
|
|||
|
|
{"id": "female_alto", "label": "Female alto — smoky, soulful"},
|
|||
|
|
{"id": "female_soprano", "label": "Female soprano — airy, soaring"},
|
|||
|
|
{"id": "weathered_rocker", "label": "Weathered rocker — raspy 90s grit"},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
OUTPUT_FORMATS = [
|
|||
|
|
{"id": "mp3", "label": "MP3 (default)"},
|
|||
|
|
{"id": "wav", "label": "WAV (Pro only, larger files)"},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
STATIC_MODELS = [
|
|||
|
|
{
|
|||
|
|
"id": "lyria-3-pro-preview",
|
|||
|
|
"label": "Lyria 3 Pro",
|
|||
|
|
"duration": "1–2 minutes",
|
|||
|
|
"cost_key": "lyria_pro_usd",
|
|||
|
|
"supports_wav": True,
|
|||
|
|
"supports_timestamps": True,
|
|||
|
|
"supports_image_input": True,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": "lyria-3-clip-preview",
|
|||
|
|
"label": "Lyria 3 Clip",
|
|||
|
|
"duration": "30 seconds",
|
|||
|
|
"cost_key": "lyria_clip_usd",
|
|||
|
|
"supports_wav": False,
|
|||
|
|
"supports_timestamps": False,
|
|||
|
|
"supports_image_input": True,
|
|||
|
|
},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class LyriaApiStatus:
|
|||
|
|
ok: bool
|
|||
|
|
message: str
|
|||
|
|
models_available: list[str]
|
|||
|
|
key_configured: bool
|
|||
|
|
|
|||
|
|
|
|||
|
|
def static_capabilities() -> dict:
|
|||
|
|
return {
|
|||
|
|
"docs_url": DOCS_URL,
|
|||
|
|
"models": STATIC_MODELS,
|
|||
|
|
"vocal_modes": VOCAL_MODES,
|
|||
|
|
"languages": LANGUAGES,
|
|||
|
|
"singer_profiles": SINGER_PROFILES,
|
|||
|
|
"output_formats": OUTPUT_FORMATS,
|
|||
|
|
"features": [
|
|||
|
|
"Text-to-music via generateContent",
|
|||
|
|
"Vocals + timed lyrics (Pro)",
|
|||
|
|
"Instrumental-only prompts",
|
|||
|
|
"Custom lyrics with [Verse]/[Chorus] tags",
|
|||
|
|
"Timestamp structure [0:00 - 0:30]",
|
|||
|
|
"Prompt language steers lyric language",
|
|||
|
|
"Image-to-music (multimodal)",
|
|||
|
|
"44.1 kHz stereo MP3 or WAV (Pro)",
|
|||
|
|
],
|
|||
|
|
"prompt_tips": [
|
|||
|
|
"Iterate with Clip (30s) before burning a Pro generation.",
|
|||
|
|
"Be specific: BPM, key, instruments, mood, structure.",
|
|||
|
|
"For instrumentals, always say 'Instrumental only, no vocals'.",
|
|||
|
|
"For vocals, describe singer gender, timbre, and range.",
|
|||
|
|
"Avoid 'vocal sample' / 'griot sample' — use 'Sahel blues chants'.",
|
|||
|
|
],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def probe_lyria_api(api_key: str | None) -> dict:
|
|||
|
|
if not api_key:
|
|||
|
|
return LyriaApiStatus(
|
|||
|
|
ok=False,
|
|||
|
|
message="GEMINI_API_KEY not set",
|
|||
|
|
models_available=[],
|
|||
|
|
key_configured=False,
|
|||
|
|
).__dict__
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
client = genai.Client(api_key=api_key)
|
|||
|
|
found: list[str] = []
|
|||
|
|
for model in client.models.list():
|
|||
|
|
name = (getattr(model, "name", "") or "").replace("models/", "")
|
|||
|
|
if "lyria" in name.lower():
|
|||
|
|
found.append(name)
|
|||
|
|
if not found:
|
|||
|
|
return LyriaApiStatus(
|
|||
|
|
ok=False,
|
|||
|
|
message="API key works but no Lyria models visible on this project",
|
|||
|
|
models_available=[],
|
|||
|
|
key_configured=True,
|
|||
|
|
).__dict__
|
|||
|
|
return LyriaApiStatus(
|
|||
|
|
ok=True,
|
|||
|
|
message=f"Lyria ready — {len(found)} model(s) available",
|
|||
|
|
models_available=sorted(found),
|
|||
|
|
key_configured=True,
|
|||
|
|
).__dict__
|
|||
|
|
except genai_errors.ClientError as exc:
|
|||
|
|
return LyriaApiStatus(
|
|||
|
|
ok=False,
|
|||
|
|
message=f"Gemini API error: {exc}",
|
|||
|
|
models_available=[],
|
|||
|
|
key_configured=True,
|
|||
|
|
).__dict__
|
|||
|
|
except Exception as exc:
|
|||
|
|
return LyriaApiStatus(
|
|||
|
|
ok=False,
|
|||
|
|
message=f"Probe failed: {exc}",
|
|||
|
|
models_available=[],
|
|||
|
|
key_configured=True,
|
|||
|
|
).__dict__
|