Files
ozan 6e92841352 Fix generation JSON/Lyria errors, add Winamp player, and ship Echoes of the Sahel.
Harden DeepSeek JSON parsing with retry, pre-sanitize Lyria prompts, and instrumental fallback. Add pure HTML Winamp skin at /winamp with playlist export support.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 16:38:52 +01:00

149 lines
4.4 KiB
Python

from __future__ import annotations
from types import SimpleNamespace
import pytest
from google.genai import errors as genai_errors
from ozan_radio.dj import TrackPlan
from ozan_radio.lyria import (
_block_reason,
_build_lyria_prompt,
_extract_audio,
_extract_lyrics,
_response_parts,
_sanitize_for_retry,
_wants_vocals,
friendly_lyria_error,
)
from ozan_radio.settings import LyriaSettings
def test_wants_vocals_explicit_no():
assert _wants_vocals("Instrumental. No vocals. Build slowly.") is False
def test_wants_vocals_whispered_texture():
assert _wants_vocals("whispered vocal textures drift in and out") is True
def test_wants_vocals_griot_request():
assert _wants_vocals("West African griot vocals over desert dub") is True
def test_build_lyria_prompt_instrumental(sample_plan, lyria_instrumental):
out = _build_lyria_prompt(sample_plan, lyria_instrumental)
assert "Instrumental only, no vocals" in out
assert sample_plan.lyria_prompt in out
def test_build_lyria_prompt_mix(sample_plan, lyria_mix):
out = _build_lyria_prompt(sample_plan, lyria_mix)
assert "Wordless vocal textures" in out
assert "Instrumental only" not in out
def test_build_lyria_prompt_vocals_override_mode(sample_plan, lyria_instrumental):
plan = TrackPlan(
id="v1",
title="Griot Night",
mood="",
dj_line="",
lyria_prompt="Sahel griot male vocal chants over dub.",
)
out = _build_lyria_prompt(plan, lyria_instrumental)
assert "Include lead vocals" in out
def test_build_lyria_prompt_hindi_language(sample_plan):
cfg = LyriaSettings(
model="lyria-3-pro-preview",
vocal_mode="vocals",
language="hi",
singer_profile="male_baritone",
output_format="mp3",
prefer_instrumental=False,
)
plan = TrackPlan(
id="h1",
title="Devotional",
mood="",
dj_line="",
lyria_prompt="Hindi devotional desert dub.",
)
out = _build_lyria_prompt(plan, cfg)
assert "Hindi" in out
assert "Male Baritone" in out
def test_sanitize_for_retry_softens_griot():
raw = "West African griot vocal sample over dub"
soft = _sanitize_for_retry(raw)
assert "griot" not in soft.lower()
assert soft != raw
def test_sanitize_for_retry_softens_throat_singing():
raw = "Mongolian overtone throat singing over dub bass"
soft = _sanitize_for_retry(raw)
assert "throat singing" not in soft.lower()
assert "wordless" in soft.lower()
def test_sanitize_for_retry_softens_anatolian():
raw = "Anatolian saz and ney with dub delay"
soft = _sanitize_for_retry(raw)
assert "anatolian" not in soft.lower()
def test_instrumental_fallback_strips_vocals():
from ozan_radio.lyria import _instrumental_fallback
raw = "88 BPM dub.\nWordless male baritone hum layer.\nSub bass pulse."
out = _instrumental_fallback(raw)
assert "baritone" not in out.lower()
assert "Instrumental only" in out
def test_response_parts_from_candidates():
part = SimpleNamespace(text=None, inline_data=SimpleNamespace(data=b"audio"))
content = SimpleNamespace(parts=[part])
candidate = SimpleNamespace(content=content, finish_reason="STOP")
response = SimpleNamespace(parts=None, candidates=[candidate])
parts = _response_parts(response)
assert len(parts) == 1
assert _extract_audio(parts) == b"audio"
def test_response_parts_empty_candidates():
response = SimpleNamespace(parts=None, candidates=[])
assert _response_parts(response) == []
def test_extract_lyrics_joins_text():
parts = [
SimpleNamespace(text="[[A0]]", inline_data=None),
SimpleNamespace(text="line two", inline_data=None),
]
assert "line two" in _extract_lyrics(parts)
def test_block_reason_strips_enum_prefix():
feedback = SimpleNamespace(block_reason="BlockedReason.PROHIBITED_CONTENT")
response = SimpleNamespace(prompt_feedback=feedback)
assert _block_reason(response) == "PROHIBITED_CONTENT"
def test_friendly_lyria_error_quota():
exc = genai_errors.ClientError(
429,
{"error": {"message": "resource_exhausted", "status": "RESOURCE_EXHAUSTED"}},
None,
)
msg = friendly_lyria_error(exc)
assert "quota" in msg.lower() or "resource" in msg.lower()
def test_friendly_lyria_error_runtime_passthrough():
assert friendly_lyria_error(RuntimeError("custom")) == "custom"