jayaspjacob Claude Opus 4.8 (1M context) commited on
Commit
cd64c45
·
1 Parent(s): 75902ca

Keep emotion cues out of audio; show them as direction tags

Browse files

S1-mini (0.5B) vocalizes "(excited)"/"(chuckling)" markers instead of
acting on them (parentheses is the correct/only format per the model card;
brackets wouldn't help — the model just reads the literal text). So strip
the parenthetical cues from the text sent to TTS, and render them as small
styled tags in the review bubbles and transcript so the emotional direction
still shows. Also harden the dropdown anchor (.wrap position:relative +
card stacking on focus-within).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +34 -3
app.py CHANGED
@@ -12,6 +12,7 @@ from __future__ import annotations
12
 
13
  import html
14
  import os
 
15
  import shutil
16
  import uuid
17
  from typing import List
@@ -97,6 +98,23 @@ def _voice_config_for(name):
97
  return tts_engine.VoiceConfig(ref_audio=wav if os.path.isfile(wav) else None, ref_text=ref_text)
98
 
99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  # --------------------------------------------------------------------- HTML builders
101
  def _avatar(letter: str, idx: int, size: int = 30) -> str:
102
  grad = _AVATAR_GRADIENTS[idx % len(_AVATAR_GRADIENTS)]
@@ -136,7 +154,7 @@ def script_to_bubbles(script: str) -> str:
136
  f"<div class='pf-bubble-side'>{_avatar(spk, i)}"
137
  f"<div class='pf-bubble-meta'><span class='pf-name'>{html.escape(spk)}</span>"
138
  f"<span class='pf-role'>{roles[spk]} ▾</span></div></div>"
139
- f"<div class='pf-bubble-text'>{html.escape(txt)}</div>"
140
  "</div>"
141
  )
142
  return "<div class='pf-bubbles'>" + "".join(bubbles) + "</div>"
@@ -159,7 +177,7 @@ def script_to_transcript(script: str) -> str:
159
  f"<div class='pf-tr-row{active}'>"
160
  f"<span class='pf-ts'>{ts}</span>"
161
  f"{_avatar(spk, idx.get(spk, 0), 26)}"
162
- f"<span class='pf-tr-text'>{html.escape(txt)}</span>"
163
  "</div>"
164
  )
165
  return "<div class='pf-transcript'>" + "".join(rows) + "</div>"
@@ -347,8 +365,11 @@ def run_tts(lines, speakers, topic, bed, *voice_names, progress=gr.Progress()):
347
  name = voice_names[i] if i < len(voice_names) else (voice_names[0] if voice_names else None)
348
  voice_map[speaker] = _voice_config_for(name)
349
 
 
 
 
350
  progress(0.05, desc="Analyzing script & pacing…")
351
- sr, audio = tts_engine.generate_podcast(lines, voice_map, progress=progress)
352
 
353
  # Rebuild a script string from the (possibly edited) lines for the transcript.
354
  script = "\n".join(f"{spk}: {txt}" for spk, txt in lines)
@@ -510,6 +531,11 @@ footer { display:none !important; }
510
  .pf-config-card .wrap, .pf-config-card input, .pf-config-card select,
511
  .pf-config-card .secondary-wrap { background:transparent !important; border:none !important; color:#fff !important; }
512
  .pf-config-card .gr-box, .pf-config-card .container { border:none !important; background:transparent !important; }
 
 
 
 
 
513
 
514
  /* primary CTA buttons */
515
  .pf-cta { background:var(--pf-grad) !important; border:none !important; color:#fff !important;
@@ -527,6 +553,11 @@ footer { display:none !important; }
527
  .pf-name { font-weight:700;font-size:13.5px;color:#fff; }
528
  .pf-role { font-size:9.5px;letter-spacing:1.5px;color:var(--pf-faint);font-weight:600; }
529
  .pf-bubble-text { color:#d6d6e2;font-size:14px;line-height:1.55;flex:1; }
 
 
 
 
 
530
  .pf-empty { color:var(--pf-mut);background:var(--pf-panel);border:1px dashed var(--pf-line);
531
  border-radius:14px;padding:26px;text-align:center;font-size:13.5px; }
532
 
 
12
 
13
  import html
14
  import os
15
+ import re
16
  import shutil
17
  import uuid
18
  from typing import List
 
98
  return tts_engine.VoiceConfig(ref_audio=wav if os.path.isfile(wav) else None, ref_text=ref_text)
99
 
100
 
101
+ # --------------------------------------------------------------------------- cues
102
+ # OpenAudio emotion/tone cues like "(excited)" / "(chuckling)". The 0.5B S1-mini tends
103
+ # to *read* these aloud rather than act on them, so we strip them from the audio text
104
+ # and instead show them as styled "direction" tags in the script/transcript.
105
+ _CUE_RE = re.compile(r"\(([a-z][a-z \-']{1,28})\)")
106
+
107
+
108
+ def _strip_cues(text: str) -> str:
109
+ """Remove parenthetical emotion cues from text destined for the TTS engine."""
110
+ return re.sub(r"\s{2,}", " ", _CUE_RE.sub("", text)).strip()
111
+
112
+
113
+ def _cues_to_chips(escaped_text: str) -> str:
114
+ """Wrap cues (in already-HTML-escaped text) as small styled tags for display."""
115
+ return _CUE_RE.sub(lambda m: f"<span class='pf-cue'>{m.group(1)}</span>", escaped_text)
116
+
117
+
118
  # --------------------------------------------------------------------- HTML builders
119
  def _avatar(letter: str, idx: int, size: int = 30) -> str:
120
  grad = _AVATAR_GRADIENTS[idx % len(_AVATAR_GRADIENTS)]
 
154
  f"<div class='pf-bubble-side'>{_avatar(spk, i)}"
155
  f"<div class='pf-bubble-meta'><span class='pf-name'>{html.escape(spk)}</span>"
156
  f"<span class='pf-role'>{roles[spk]} ▾</span></div></div>"
157
+ f"<div class='pf-bubble-text'>{_cues_to_chips(html.escape(txt))}</div>"
158
  "</div>"
159
  )
160
  return "<div class='pf-bubbles'>" + "".join(bubbles) + "</div>"
 
177
  f"<div class='pf-tr-row{active}'>"
178
  f"<span class='pf-ts'>{ts}</span>"
179
  f"{_avatar(spk, idx.get(spk, 0), 26)}"
180
+ f"<span class='pf-tr-text'>{_cues_to_chips(html.escape(txt))}</span>"
181
  "</div>"
182
  )
183
  return "<div class='pf-transcript'>" + "".join(rows) + "</div>"
 
365
  name = voice_names[i] if i < len(voice_names) else (voice_names[0] if voice_names else None)
366
  voice_map[speaker] = _voice_config_for(name)
367
 
368
+ # Strip emotion cues from the spoken text (S1-mini would read them aloud).
369
+ tts_lines = [(spk, _strip_cues(txt)) for spk, txt in lines]
370
+
371
  progress(0.05, desc="Analyzing script & pacing…")
372
+ sr, audio = tts_engine.generate_podcast(tts_lines, voice_map, progress=progress)
373
 
374
  # Rebuild a script string from the (possibly edited) lines for the transcript.
375
  script = "\n".join(f"{spk}: {txt}" for spk, txt in lines)
 
531
  .pf-config-card .wrap, .pf-config-card input, .pf-config-card select,
532
  .pf-config-card .secondary-wrap { background:transparent !important; border:none !important; color:#fff !important; }
533
  .pf-config-card .gr-box, .pf-config-card .container { border:none !important; background:transparent !important; }
534
+ /* anchor the dropdown options popup to its input wrapper so it opens in place
535
+ (otherwise the absolutely-positioned list jumps to the page top-left) */
536
+ .pf-config-card .wrap { position: relative !important; }
537
+ .pf-config-card { position: relative; z-index: 1; }
538
+ .pf-config-card:focus-within { z-index: 50; }
539
 
540
  /* primary CTA buttons */
541
  .pf-cta { background:var(--pf-grad) !important; border:none !important; color:#fff !important;
 
553
  .pf-name { font-weight:700;font-size:13.5px;color:#fff; }
554
  .pf-role { font-size:9.5px;letter-spacing:1.5px;color:var(--pf-faint);font-weight:600; }
555
  .pf-bubble-text { color:#d6d6e2;font-size:14px;line-height:1.55;flex:1; }
556
+ /* emotion/tone direction tags */
557
+ .pf-cue { display:inline-block;font-size:10.5px;font-weight:600;letter-spacing:.3px;
558
+ color:#c9b8ff;background:rgba(124,92,255,.16);border:1px solid rgba(124,92,255,.3);
559
+ border-radius:6px;padding:1px 7px;margin:0 4px 0 0;vertical-align:middle;
560
+ text-transform:lowercase; }
561
  .pf-empty { color:var(--pf-mut);background:var(--pf-panel);border:1px dashed var(--pf-line);
562
  border-radius:14px;padding:26px;text-align:center;font-size:13.5px; }
563