6e92841352
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>
149 lines
4.4 KiB
Python
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"
|