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 filesS1-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>
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(
|
| 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 |
|