diff --git a/qwen3-tts-backend/core/audiobook_service.py b/qwen3-tts-backend/core/audiobook_service.py index 013e568..ce09250 100644 --- a/qwen3-tts-backend/core/audiobook_service.py +++ b/qwen3-tts-backend/core/audiobook_service.py @@ -398,9 +398,12 @@ async def parse_one_chapter(project_id: int, chapter_id: int, user: User, db) -> char = char_map.get(seg.get("character", "narrator")) or char_map.get("narrator") if not char: continue + seg_emo_text = seg.get("emo_text", "") or None + seg_emo_alpha = seg.get("emo_alpha") if seg_emo_text else None crud.create_audiobook_segment( db, project_id, char.id, seg_text, chapter.chapter_index, seg_counter, + emo_text=seg_emo_text, emo_alpha=seg_emo_alpha, ) seg_counter += 1 chunk_count += 1 @@ -580,8 +583,8 @@ async def generate_project(project_id: int, user: User, db: Session, chapter_ind text=seg.text, spk_audio_prompt=design.ref_audio_path, output_path=str(audio_path), - emo_text=char.instruct or None, - emo_alpha=0.6, + emo_text=seg.emo_text or None, + emo_alpha=seg.emo_alpha if seg.emo_text else 0.5, ) else: if design.voice_cache_id: diff --git a/qwen3-tts-backend/core/llm_service.py b/qwen3-tts-backend/core/llm_service.py index 9908104..b93613b 100644 --- a/qwen3-tts-backend/core/llm_service.py +++ b/qwen3-tts-backend/core/llm_service.py @@ -201,9 +201,14 @@ class LLMService: system_prompt = ( "你是一个专业的有声书制作助手。请将给定的章节文本解析为对话片段列表。" f"已知角色列表(必须从中选择):{names_str}。" - "所有非对话的叙述文字归属于narrator角色。" + "所有非对话的叙述文字归属于narrator角色。\n" + "同时根据语境为每个片段判断情绪,可选情绪及对应强度如下(必须严格使用以下值):\n" + "开心(emo_alpha=0.6)、愤怒(emo_alpha=0.15)、悲伤(emo_alpha=0.4)、恐惧(emo_alpha=0.4)、" + "厌恶(emo_alpha=0.6)、低沉(emo_alpha=0.6)、惊讶(emo_alpha=0.3)、中性(emo_alpha=0.5)。\n" + "narrator旁白及情绪不明显的片段,emo_text设为\"\",emo_alpha设为0.5。\n" "只输出JSON数组,不要有其他文字,格式如下:\n" - '[{"character": "narrator", "text": "叙述文字"}, {"character": "角色名", "text": "对话内容"}, ...]' + '[{"character": "narrator", "text": "叙述文字", "emo_text": "", "emo_alpha": 0.5}, ' + '{"character": "角色名", "text": "对话内容", "emo_text": "开心", "emo_alpha": 0.6}, ...]' ) user_message = f"请解析以下章节文本:\n\n{chapter_text}" result = await self.stream_chat_json(system_prompt, user_message, on_token, max_tokens=16384) diff --git a/qwen3-tts-backend/db/crud.py b/qwen3-tts-backend/db/crud.py index 1d0b220..789f2e3 100644 --- a/qwen3-tts-backend/db/crud.py +++ b/qwen3-tts-backend/db/crud.py @@ -593,6 +593,8 @@ def create_audiobook_segment( text: str, chapter_index: int = 0, segment_index: int = 0, + emo_text: Optional[str] = None, + emo_alpha: Optional[float] = None, ) -> AudiobookSegment: seg = AudiobookSegment( project_id=project_id, @@ -600,6 +602,8 @@ def create_audiobook_segment( text=text, chapter_index=chapter_index, segment_index=segment_index, + emo_text=emo_text or None, + emo_alpha=emo_alpha, status="pending", ) db.add(seg) diff --git a/qwen3-tts-backend/db/database.py b/qwen3-tts-backend/db/database.py index f81b2eb..2d87988 100644 --- a/qwen3-tts-backend/db/database.py +++ b/qwen3-tts-backend/db/database.py @@ -40,3 +40,12 @@ def init_db(): conn.commit() except Exception: pass + for col_def in [ + "ALTER TABLE audiobook_segments ADD COLUMN emo_text VARCHAR(20)", + "ALTER TABLE audiobook_segments ADD COLUMN emo_alpha REAL", + ]: + try: + conn.execute(__import__("sqlalchemy").text(col_def)) + conn.commit() + except Exception: + pass diff --git a/qwen3-tts-backend/db/models.py b/qwen3-tts-backend/db/models.py index 59b2667..b456c52 100644 --- a/qwen3-tts-backend/db/models.py +++ b/qwen3-tts-backend/db/models.py @@ -1,6 +1,6 @@ from datetime import datetime from enum import Enum -from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Text, Index, JSON +from sqlalchemy import Column, Integer, String, Boolean, DateTime, Float, ForeignKey, Text, Index, JSON from sqlalchemy.orm import relationship from db.database import Base @@ -189,6 +189,8 @@ 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_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-backend/schemas/audiobook.py b/qwen3-tts-backend/schemas/audiobook.py index 936f9cd..c2ee3f3 100644 --- a/qwen3-tts-backend/schemas/audiobook.py +++ b/qwen3-tts-backend/schemas/audiobook.py @@ -81,6 +81,8 @@ class AudiobookSegmentResponse(BaseModel): character_id: int character_name: Optional[str] = None text: str + emo_text: Optional[str] = None + emo_alpha: Optional[float] = None audio_path: Optional[str] = None status: str diff --git a/qwen3-tts-frontend/src/lib/api/audiobook.ts b/qwen3-tts-frontend/src/lib/api/audiobook.ts index 155e6e6..99d4ec8 100644 --- a/qwen3-tts-frontend/src/lib/api/audiobook.ts +++ b/qwen3-tts-frontend/src/lib/api/audiobook.ts @@ -45,6 +45,8 @@ export interface AudiobookSegment { character_id: number character_name?: string text: string + emo_text?: string + emo_alpha?: number audio_path?: string status: string } diff --git a/qwen3-tts-frontend/src/pages/Audiobook.tsx b/qwen3-tts-frontend/src/pages/Audiobook.tsx index 5acdae1..a45537d 100644 --- a/qwen3-tts-frontend/src/pages/Audiobook.tsx +++ b/qwen3-tts-frontend/src/pages/Audiobook.tsx @@ -1082,6 +1082,11 @@ function ProjectCard({ project, onRefresh }: { project: AudiobookProject; onRefr {seg.character_name || t('projectCard.segments.unknownCharacter')} + {seg.emo_text && ( + + {seg.emo_text} + + )} {seg.status === 'generating' && } {seg.status === 'error' && {t('projectCard.segments.errorBadge')}}