emotion_text_utils.py 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. """LLM 情感标签解析与展示文本清理(字幕 / TTS 共用)"""
  2. from __future__ import annotations
  3. import re
  4. from typing import Optional
  5. VALID_EMOTIONS = frozenset(
  6. {
  7. "neutral",
  8. "happy",
  9. "sad",
  10. "excited",
  11. "angry",
  12. "surprised",
  13. "thoughtful",
  14. "encouraging",
  15. "confused",
  16. "worried",
  17. "satisfied",
  18. "friendly",
  19. "sorry",
  20. }
  21. )
  22. EMOTION_TAG_PATTERN = re.compile(r"\[EMOTION:\s*(\w+)\]", re.IGNORECASE)
  23. _EMOTION_WORDS_ALT = "|".join(re.escape(w) for w in sorted(VALID_EMOTIONS, key=len, reverse=True))
  24. _LEADING_PLAIN_EMOTION = re.compile(
  25. rf"^\s*(?P<word>{_EMOTION_WORDS_ALT})(?:\s*[\r\n]+|\s+(?=\S)|\s*$)",
  26. re.IGNORECASE,
  27. )
  28. def normalize_emotion(raw: str) -> str:
  29. word = (raw or "").strip().lower()
  30. return word if word in VALID_EMOTIONS else "neutral"
  31. def parse_emotion_from_text(text: str) -> Optional[str]:
  32. """从 LLM 输出中解析情感(支持 [EMOTION:xxx] 及首行裸词)。"""
  33. if not text:
  34. return None
  35. tag_match = EMOTION_TAG_PATTERN.search(text)
  36. if tag_match:
  37. return normalize_emotion(tag_match.group(1))
  38. plain_match = _LEADING_PLAIN_EMOTION.match(text)
  39. if plain_match:
  40. return normalize_emotion(plain_match.group("word"))
  41. return None
  42. def strip_emotion_from_text(text: str) -> str:
  43. """移除情感标签/裸词,供字幕与 TTS 使用。"""
  44. cleaned = EMOTION_TAG_PATTERN.sub("", text)
  45. cleaned = _LEADING_PLAIN_EMOTION.sub("", cleaned, count=1)
  46. cleaned = cleaned.strip()
  47. if re.fullmatch(rf"(?:{_EMOTION_WORDS_ALT})", cleaned, re.IGNORECASE):
  48. return ""
  49. return cleaned
  50. def clean_text_for_display(text: str) -> str:
  51. """字幕展示文本清理(emotion 已通过独立 WebSocket 消息下发)。"""
  52. return strip_emotion_from_text(text)