Codex commited on
Commit
591e827
·
1 Parent(s): cec7e01

Fix lyric display and duration controls

Browse files
Files changed (3) hide show
  1. app.py +8 -4
  2. src/safety.py +12 -1
  3. tests/test_app_logic.py +14 -0
app.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
 
3
  import random
4
  import uuid
 
5
  from pathlib import Path
6
 
7
  import gradio as gr
@@ -263,10 +264,12 @@ def generate_time_machine_mix(
263
  )
264
  status_md = "\n".join([f"- {status}" for status in statuses])
265
  route = timeline_route_html(source_era_label, source_genre_label, remix_era_label, remix_genre_label)
 
 
266
  journey = f"""
267
  {route}
268
- <div class="micro"><h3>Fictional DJ intro</h3><p>{intro['text']}</p></div>
269
- <div class="micro lyric-box"><h3>Generated micro-lyrics</h3><p>{(lyrics or 'Instrumental / no lyric text requested.').replace(chr(10), '<br>')}</p></div>
270
  """
271
  return (
272
  final_path,
@@ -298,11 +301,12 @@ CSS = """
298
  .mascot-label{position:absolute;left:22px;right:22px;bottom:22px;z-index:2;display:flex;justify-content:space-between;gap:12px;align-items:end}.mascot-label b{font-size:20px}.mascot-label span{display:block;color:#bdeff5;font-size:13px;margin-top:4px}.seal{padding:9px 12px;border-radius:999px;background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.22);font-size:12px}
299
  .panel{background:rgba(255,255,255,.66)!important;border:1px solid rgba(255,255,255,.86)!important;border-radius:32px!important;padding:20px!important;box-shadow:0 24px 70px rgba(77,79,110,.13)!important}
300
  .panel h2{margin:0 0 12px!important;font-size:18px!important}.compact-control label span{color:#64708a!important;font-size:12px!important;text-transform:uppercase!important;letter-spacing:.08em!important;font-weight:900!important}
301
- .compact-control input,.compact-control select,.compact-control textarea{background:#fffdf4!important;border:1px solid var(--line)!important;border-radius:16px!important;color:var(--ink)!important;font-weight:700!important}
 
302
  #generate-button{background:linear-gradient(135deg,var(--orange),#ffb84f)!important;color:#24121a!important;border:0!important;border-radius:18px!important;font-weight:1000!important;box-shadow:0 16px 32px rgba(242,106,56,.22)!important}
303
  #surprise-button{background:linear-gradient(135deg,#c3f7ff,#a693ff)!important;color:#15213a!important;border:0!important;border-radius:18px!important;font-weight:1000!important}
304
  .scroll{border-radius:34px;background:linear-gradient(180deg,#fffdf4,#fbebc5);border:1px solid rgba(255,255,255,.9);padding:24px;position:relative;overflow:hidden;min-height:470px}.path{display:grid;grid-template-columns:1fr auto 1fr;gap:14px;align-items:center;margin:12px 0 20px}.era-card{background:white;border:1px solid var(--line);border-radius:24px;padding:18px;box-shadow:0 14px 30px rgba(99,75,38,.08)}.era-card small{color:var(--teal);font-weight:900;text-transform:uppercase;letter-spacing:.1em}.era-card h3{font-size:26px;margin:9px 0 4px}.era-card p{margin:0;color:#657087;line-height:1.35}.swirl{width:76px;height:76px;border-radius:50%;display:grid;place-items:center;border:5px solid var(--teal);border-left-color:transparent;color:var(--orange);font-size:24px;font-weight:900}.wind-wave{height:112px;border-radius:28px;background:linear-gradient(135deg,rgba(29,184,197,.12),rgba(110,84,255,.14));border:1px dashed rgba(38,50,74,.18);position:relative;overflow:hidden}.wind-wave:before{content:"";position:absolute;inset:20px;background:repeating-radial-gradient(ellipse at center,transparent 0 12px,rgba(29,184,197,.65) 13px 16px,transparent 17px 28px);transform:skewX(-20deg)}
305
- .micro{margin-top:18px;border-radius:24px;background:#172135;color:#fff;padding:18px}.micro h3{margin:0 0 8px;color:#f9d989}.micro p{font-size:18px;line-height:1.35;margin:0}.lyric-box{background:#26324a}
306
  .audio-card{background:#172135!important;color:white!important;border-radius:24px!important;padding:14px!important}.mixtape-card{background:white;border:1px solid var(--line);border-radius:24px;padding:18px}.mixtape-title{font-size:12px;color:var(--orange);font-weight:1000;text-transform:uppercase;letter-spacing:.12em}.mixtape-card h3{margin:8px 0 12px}.ttm-kv{display:flex;justify-content:space-between;gap:12px;border-bottom:1px dashed var(--line);padding:8px 0;font-size:14px}.ttm-kv span{color:#7d8697}.ttm-kv b{text-align:right}.lyrics-chip{background:#fff4d2;border-radius:16px;padding:12px;margin:10px 0;font-weight:800}
307
  @media(max-width:900px){.path{grid-template-columns:1fr}.swirl{margin:auto}.ttm-kv{display:block}.ttm-kv b{text-align:left;display:block;margin-top:4px}}
308
  """
 
2
 
3
  import random
4
  import uuid
5
+ import html
6
  from pathlib import Path
7
 
8
  import gradio as gr
 
264
  )
265
  status_md = "\n".join([f"- {status}" for status in statuses])
266
  route = timeline_route_html(source_era_label, source_genre_label, remix_era_label, remix_genre_label)
267
+ intro_text = html.escape(intro["text"])
268
+ lyrics_html = html.escape(lyrics or "Instrumental / no lyric text requested.").replace(chr(10), "<br>")
269
  journey = f"""
270
  {route}
271
+ <div class="micro"><h3>Fictional DJ intro</h3><p>{intro_text}</p></div>
272
+ <div class="micro lyric-box"><h3>Generated micro-lyrics</h3><p>{lyrics_html}</p></div>
273
  """
274
  return (
275
  final_path,
 
301
  .mascot-label{position:absolute;left:22px;right:22px;bottom:22px;z-index:2;display:flex;justify-content:space-between;gap:12px;align-items:end}.mascot-label b{font-size:20px}.mascot-label span{display:block;color:#bdeff5;font-size:13px;margin-top:4px}.seal{padding:9px 12px;border-radius:999px;background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.22);font-size:12px}
302
  .panel{background:rgba(255,255,255,.66)!important;border:1px solid rgba(255,255,255,.86)!important;border-radius:32px!important;padding:20px!important;box-shadow:0 24px 70px rgba(77,79,110,.13)!important}
303
  .panel h2{margin:0 0 12px!important;font-size:18px!important}.compact-control label span{color:#64708a!important;font-size:12px!important;text-transform:uppercase!important;letter-spacing:.08em!important;font-weight:900!important}
304
+ .compact-control input:not([type="radio"]):not([type="checkbox"]),.compact-control select,.compact-control textarea{background:#fffdf4!important;border:1px solid var(--line)!important;border-radius:16px!important;color:var(--ink)!important;font-weight:700!important}
305
+ .compact-control input[type="radio"],.compact-control input[type="checkbox"]{accent-color:var(--orange)!important;cursor:pointer!important}.compact-control label{cursor:pointer!important}
306
  #generate-button{background:linear-gradient(135deg,var(--orange),#ffb84f)!important;color:#24121a!important;border:0!important;border-radius:18px!important;font-weight:1000!important;box-shadow:0 16px 32px rgba(242,106,56,.22)!important}
307
  #surprise-button{background:linear-gradient(135deg,#c3f7ff,#a693ff)!important;color:#15213a!important;border:0!important;border-radius:18px!important;font-weight:1000!important}
308
  .scroll{border-radius:34px;background:linear-gradient(180deg,#fffdf4,#fbebc5);border:1px solid rgba(255,255,255,.9);padding:24px;position:relative;overflow:hidden;min-height:470px}.path{display:grid;grid-template-columns:1fr auto 1fr;gap:14px;align-items:center;margin:12px 0 20px}.era-card{background:white;border:1px solid var(--line);border-radius:24px;padding:18px;box-shadow:0 14px 30px rgba(99,75,38,.08)}.era-card small{color:var(--teal);font-weight:900;text-transform:uppercase;letter-spacing:.1em}.era-card h3{font-size:26px;margin:9px 0 4px}.era-card p{margin:0;color:#657087;line-height:1.35}.swirl{width:76px;height:76px;border-radius:50%;display:grid;place-items:center;border:5px solid var(--teal);border-left-color:transparent;color:var(--orange);font-size:24px;font-weight:900}.wind-wave{height:112px;border-radius:28px;background:linear-gradient(135deg,rgba(29,184,197,.12),rgba(110,84,255,.14));border:1px dashed rgba(38,50,74,.18);position:relative;overflow:hidden}.wind-wave:before{content:"";position:absolute;inset:20px;background:repeating-radial-gradient(ellipse at center,transparent 0 12px,rgba(29,184,197,.65) 13px 16px,transparent 17px 28px);transform:skewX(-20deg)}
309
+ .micro{margin-top:18px;border-radius:24px;background:#172135;color:#fff;padding:18px}.micro h3{margin:0 0 8px;color:#f9d989!important}.micro p{font-size:18px;line-height:1.35;margin:0;color:#f7fbff!important}.lyric-box{background:#26324a}
310
  .audio-card{background:#172135!important;color:white!important;border-radius:24px!important;padding:14px!important}.mixtape-card{background:white;border:1px solid var(--line);border-radius:24px;padding:18px}.mixtape-title{font-size:12px;color:var(--orange);font-weight:1000;text-transform:uppercase;letter-spacing:.12em}.mixtape-card h3{margin:8px 0 12px}.ttm-kv{display:flex;justify-content:space-between;gap:12px;border-bottom:1px dashed var(--line);padding:8px 0;font-size:14px}.ttm-kv span{color:#7d8697}.ttm-kv b{text-align:right}.lyrics-chip{background:#fff4d2;border-radius:16px;padding:12px;margin:10px 0;font-weight:800}
311
  @media(max-width:900px){.path{grid-template-columns:1fr}.swirl{margin:auto}.ttm-kv{display:block}.ttm-kv b{text-align:left;display:block;margin-top:4px}}
312
  """
src/safety.py CHANGED
@@ -25,6 +25,15 @@ BLOCKED_PATTERNS = [
25
 
26
  FORBIDDEN_UI_TERMS = ["Aang", "Avatar", "Airbender", "Air Nomad", "four nations", "bending"]
27
 
 
 
 
 
 
 
 
 
 
28
 
29
  def copyright_safety_note() -> str:
30
  return SAFETY_NOTE
@@ -66,6 +75,9 @@ def sanitize_generated_lyrics(lyrics: str) -> tuple[str, list[str]]:
66
  line = re.sub(r"[^A-Za-zÀ-ÿ0-9 ,.'!?-]", "", line).strip()
67
  if not line:
68
  continue
 
 
 
69
  words = line.split()
70
  if len(words) > 12:
71
  warnings.append("Trimmed a lyric line to 12 words.")
@@ -76,4 +88,3 @@ def sanitize_generated_lyrics(lyrics: str) -> tuple[str, list[str]]:
76
  if not clean_lines:
77
  warnings.append("Generated lyrics were empty after sanitization.")
78
  return "\n".join(clean_lines), warnings
79
-
 
25
 
26
  FORBIDDEN_UI_TERMS = ["Aang", "Avatar", "Airbender", "Air Nomad", "four nations", "bending"]
27
 
28
+ LYRIC_META_PATTERNS = [
29
+ r"\b(no|without)\s+(explanation|punctuation|commentary|markdown)\b",
30
+ r"\breturn\s+only\b",
31
+ r"\b(two|2)\s+lyric\s+lines?\b",
32
+ r"\bmaximum\s+\d+\s+words?\b",
33
+ r"\brules?\b",
34
+ r"\blyrics?:?\b",
35
+ ]
36
+
37
 
38
  def copyright_safety_note() -> str:
39
  return SAFETY_NOTE
 
75
  line = re.sub(r"[^A-Za-zÀ-ÿ0-9 ,.'!?-]", "", line).strip()
76
  if not line:
77
  continue
78
+ if any(re.search(pattern, line, flags=re.IGNORECASE) for pattern in LYRIC_META_PATTERNS):
79
+ warnings.append("Removed non-lyric instruction text from generated lyrics.")
80
+ continue
81
  words = line.split()
82
  if len(words) > 12:
83
  warnings.append("Trimmed a lyric line to 12 words.")
 
88
  if not clean_lines:
89
  warnings.append("Generated lyrics were empty after sanitization.")
90
  return "\n".join(clean_lines), warnings
 
tests/test_app_logic.py CHANGED
@@ -10,6 +10,7 @@ import soundfile as sf
10
  import app
11
  from src.lyrics_generator import generate_micro_lyrics
12
  from src.music_generator import generate_music
 
13
  from src.tts import generate_dj_voice
14
 
15
 
@@ -49,6 +50,19 @@ class TurntableTimeMachineLogicTests(unittest.TestCase):
49
  self.assertFalse(app.update_lyric_theme_visibility("Instrumental only")["visible"])
50
  self.assertFalse(app.update_lyric_theme_visibility("Wordless vocal texture")["visible"])
51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  def test_surprise_me_returns_complete_valid_state(self):
53
  result = app.surprise_me()
54
  self.assertEqual(len(result), 15)
 
10
  import app
11
  from src.lyrics_generator import generate_micro_lyrics
12
  from src.music_generator import generate_music
13
+ from src.safety import sanitize_generated_lyrics
14
  from src.tts import generate_dj_voice
15
 
16
 
 
50
  self.assertFalse(app.update_lyric_theme_visibility("Instrumental only")["visible"])
51
  self.assertFalse(app.update_lyric_theme_visibility("Wordless vocal texture")["visible"])
52
 
53
+ def test_micro_lyrics_sanitizer_removes_instruction_text(self):
54
+ clean, warnings = sanitize_generated_lyrics(
55
+ "No explanation, no punctuation\n"
56
+ "Vapor dreams drift on neon haze\n"
57
+ "We chase tomorrow through glass streets"
58
+ )
59
+ self.assertEqual(clean, "Vapor dreams drift on neon haze\nWe chase tomorrow through glass streets")
60
+ self.assertTrue(any("instruction" in warning for warning in warnings))
61
+
62
+ def test_compact_control_css_does_not_restyle_radio_inputs(self):
63
+ self.assertIn('input:not([type="radio"]):not([type="checkbox"])', app.CSS)
64
+ self.assertIn('input[type="radio"]', app.CSS)
65
+
66
  def test_surprise_me_returns_complete_valid_state(self):
67
  result = app.surprise_me()
68
  self.assertEqual(len(result), 15)