diff --git a/qwen3-tts-backend/core/audiobook_service.py b/qwen3-tts-backend/core/audiobook_service.py index e88098a..abd0268 100644 --- a/qwen3-tts-backend/core/audiobook_service.py +++ b/qwen3-tts-backend/core/audiobook_service.py @@ -16,7 +16,7 @@ from db.models import AudiobookProject, AudiobookCharacter, User logger = logging.getLogger(__name__) _LINE_RE = re.compile(r'^【(.+?)】(.*)$') -_EMO_RE = re.compile(r'((开心|愤怒|悲伤|恐惧|厌恶|低沉|惊讶):([0-9.]+))\s*$') +_EMO_RE = re.compile(r'(([^:)]+):([0-9.]+))\s*$') # Cancellation events for batch operations, keyed by project_id _cancel_events: dict[int, asyncio.Event] = {} diff --git a/qwen3-tts-backend/core/llm_service.py b/qwen3-tts-backend/core/llm_service.py index 8912996..fcf6ea5 100644 --- a/qwen3-tts-backend/core/llm_service.py +++ b/qwen3-tts-backend/core/llm_service.py @@ -378,6 +378,8 @@ class LLMService: " 【角色名】\"对话内容\"(情感词:强度)\n\n" "情感标注规则:\n" "- 情感词可选:开心、愤怒、悲伤、恐惧、厌恶、低沉、惊讶\n" + "- 可用 + 拼接多个情感词表达复杂情绪,如(开心+悲伤:0.4)、(愤怒+恐惧:0.5)\n" + "- 多情感时强度为混合情感的整体强度,每种情感对合成结果均有贡献\n" f"- 各情感强度上限(严格不超过):{limits_str}\n" "- 情感不明显时可省略(情感词:强度)整个括号\n" + narrator_rule @@ -453,12 +455,14 @@ class LLMService: "所有非对话的叙述文字归属于旁白角色。\n" "同时根据语境为每个片段判断是否有明显情绪,有则设置情绪类型(emo_text)和强度(emo_alpha),无则留空。\n" "可选情绪:开心、愤怒、悲伤、恐惧、厌恶、低沉、惊讶。\n" + "- emo_text 可用 + 拼接多个情感词(如 \"开心+悲伤\"),表达复杂混合情绪\n" "情绪不明显或旁白时,emo_text设为\"\",emo_alpha设为0。\n" "各情绪强度上限(严格不超过):开心=0.35、愤怒=0.15、悲伤=0.1、恐惧=0.1、厌恶=0.35、低沉=0.35、惊讶=0.1。\n" "同一角色的连续台词,情绪应尽量保持一致或仅有微弱变化,避免相邻片段间情绪跳跃。\n" "只输出JSON数组,不要有其他文字,格式如下:\n" '[{"character": "旁白", "text": "叙述文字", "emo_text": "", "emo_alpha": 0}, ' - '{"character": "角色名", "text": "对话内容", "emo_text": "开心", "emo_alpha": 0.3}, ...]' + '{"character": "角色名", "text": "对话内容", "emo_text": "开心", "emo_alpha": 0.3}, ' + '{"character": "角色名", "text": "带泪的笑", "emo_text": "开心+悲伤", "emo_alpha": 0.4}]' ) user_message = f"请解析以下章节文本:\n\n{chapter_text}" result = await self.stream_chat_json(system_prompt, user_message, on_token, max_tokens=16384, usage_callback=usage_callback) diff --git a/qwen3-tts-backend/db/models.py b/qwen3-tts-backend/db/models.py index 827a9f2..ff56bf0 100644 --- a/qwen3-tts-backend/db/models.py +++ b/qwen3-tts-backend/db/models.py @@ -192,7 +192,7 @@ class AudiobookSegment(Base): segment_index = Column(Integer, nullable=False) character_id = Column(Integer, ForeignKey("audiobook_characters.id"), nullable=False) text = Column(Text, nullable=False) - emo_text = Column(String(20), nullable=True) + emo_text = Column(String(100), nullable=True) emo_alpha = Column(Float, nullable=True) audio_path = Column(String(500), nullable=True) status = Column(String(20), default="pending", nullable=False) diff --git a/qwen3-tts-frontend/src/pages/Audiobook.tsx b/qwen3-tts-frontend/src/pages/Audiobook.tsx index b3dd825..d9936b6 100644 --- a/qwen3-tts-frontend/src/pages/Audiobook.tsx +++ b/qwen3-tts-frontend/src/pages/Audiobook.tsx @@ -1381,9 +1381,7 @@ function CharactersPanel({ } const EMOTION_OPTIONS = ['开心', '愤怒', '悲伤', '恐惧', '厌恶', '低沉', '惊讶', '中性'] -const EMOTION_ALPHA_DEFAULTS: Record = { - 开心: 0.6, 愤怒: 0.15, 悲伤: 0.4, 恐惧: 0.4, 厌恶: 0.6, 低沉: 0.6, 惊讶: 0.3, 中性: 0.5, -} + function ChaptersPanel({ project, @@ -1674,8 +1672,10 @@ function ChaptersPanel({ {seg.character_name || t('projectCard.segments.unknownCharacter')} {!isEditing && seg.emo_text && ( - - {seg.emo_text} + + {seg.emo_text.split('+').map(e => ( + {e.trim()} + ))} {seg.emo_alpha != null && ( {seg.emo_alpha.toFixed(2)} )} @@ -1718,22 +1718,32 @@ function ChaptersPanel({ className="text-sm min-h-[60px] resize-y" rows={3} /> -
- {t('projectCard.segments.emotion')}: - +
+
+ {t('projectCard.segments.emotion')}: + {EMOTION_OPTIONS.map(emo => { + const selectedEmos = editEmoText.split('+').filter(Boolean) + const isSelected = selectedEmos.includes(emo) + return ( + + ) + })} +
{editEmoText && ( -
+
{t('projectCard.segments.intensity')}: