feat: enhance parseAllChapters API to support force parsing and update Audiobook component for AI mode handling

This commit is contained in:
2026-03-15 01:12:33 +08:00
parent c8dd762aad
commit 1193d63e68
4 changed files with 85 additions and 7 deletions

View File

@@ -17,6 +17,7 @@ logger = logging.getLogger(__name__)
_LINE_RE = re.compile(r'^【(.+?)】(.*)$')
_EMO_RE = re.compile(r'([^]+)\s*$')
_EMO_PREFIX_RE = re.compile(r'^([^]+)\s*')
def _parse_emo(raw: str) -> tuple[Optional[str], Optional[float]]:
@@ -225,6 +226,14 @@ def parse_ai_script(script_text: str, char_map: dict) -> list[dict]:
emo_text, emo_alpha = et, ea
content = content[:emo_m.start()].strip()
if emo_text is None:
emo_m = _EMO_PREFIX_RE.match(content)
if emo_m:
et, ea = _parse_emo(emo_m.group(1))
if et is not None:
emo_text, emo_alpha = et, ea
content = content[emo_m.end():].strip()
if content.startswith('"') and content.endswith('"'):
content = content[1:-1].strip()
elif content.startswith('"') and content.endswith('"'):
@@ -238,6 +247,14 @@ def parse_ai_script(script_text: str, char_map: dict) -> list[dict]:
emo_text, emo_alpha = et, ea
content = content[:emo_m.start()].strip()
if emo_text is None:
emo_m = _EMO_PREFIX_RE.match(content)
if emo_m:
et, ea = _parse_emo(emo_m.group(1))
if et is not None:
emo_text, emo_alpha = et, ea
content = content[emo_m.end():].strip()
character = speaker
results.append({
@@ -937,16 +954,66 @@ def identify_chapters(project_id: int, db, project) -> None:
logger.info(f"Project {project_id} chapters identified: {real_idx} chapters")
async def _parse_ai_chapter(project_id: int, chapter_id: int, chapter, user: User, db, key: str) -> None:
try:
characters = crud.list_audiobook_characters(db, project_id)
char_map: dict[str, AudiobookCharacter] = {c.name: c for c in characters}
label = chapter.title or f"{chapter.chapter_index + 1}"
ps.append_line(key, f"[{label}] 重新解析 AI 剧本 ({len(chapter.source_text or '')} 字)")
crud.delete_audiobook_segments_for_chapter(db, project_id, chapter.chapter_index)
segments_dir = Path(settings.OUTPUT_DIR) / "audiobook" / str(project_id) / "segments"
if segments_dir.exists():
chapter_prefix = f"ch{chapter.chapter_index:03d}_"
for f in segments_dir.glob(f"{chapter_prefix}*.wav"):
f.unlink(missing_ok=True)
segments_data = parse_ai_script(chapter.source_text or "", char_map)
seg_counter = 0
for seg in segments_data:
seg_text = seg.get("text", "").strip()
if not seg_text:
continue
char = char_map.get(seg.get("character", "旁白")) or char_map.get("旁白") or char_map.get("narrator")
if not char:
continue
crud.create_audiobook_segment(
db, project_id, char.id, seg_text,
chapter.chapter_index, seg_counter,
emo_text=seg.get("emo_text"), emo_alpha=seg.get("emo_alpha"),
)
seg_counter += 1
crud.update_audiobook_chapter_status(db, chapter_id, "ready")
ps.append_line(key, f"\n[完成] 共 {seg_counter}")
ps.mark_done(key)
logger.info(f"AI chapter {chapter_id} reparsed: {seg_counter} segments")
except Exception as e:
logger.error(f"_parse_ai_chapter {chapter_id} failed: {e}", exc_info=True)
ps.append_line(key, f"\n[错误] {e}")
ps.mark_done(key)
crud.update_audiobook_chapter_status(db, chapter_id, "error", error_message=str(e))
async def parse_one_chapter(project_id: int, chapter_id: int, user: User, db) -> None:
chapter = crud.get_audiobook_chapter(db, chapter_id)
if not chapter:
return
project = db.query(AudiobookProject).filter(AudiobookProject.id == project_id).first()
is_ai_mode = project and project.source_type == "ai_generated"
key = f"ch_{chapter_id}"
ps.reset(key)
try:
crud.update_audiobook_chapter_status(db, chapter_id, "parsing")
if is_ai_mode:
return await _parse_ai_chapter(project_id, chapter_id, chapter, user, db, key)
llm = _get_llm_service(db)
_llm_model = crud.get_system_setting(db, "llm_model")
_user_id = user.id