feat: add continue script functionality for AI-generated audiobook projects

This commit is contained in:
2026-03-13 11:59:37 +08:00
parent 7129047c3f
commit 7644584c39
11 changed files with 355 additions and 7 deletions

View File

@@ -499,6 +499,162 @@ async def generate_ai_script_chapters(project_id: int, user: User, db: Session)
crud.update_audiobook_project_status(db, project_id, "error", error_message=str(e))
async def continue_ai_script_chapters(project_id: int, additional_chapters: int, user: User, db: Session) -> None:
from core.database import SessionLocal
project = db.query(AudiobookProject).filter(AudiobookProject.id == project_id).first()
if not project or not project.script_config:
return
key = str(project_id)
ps.reset(key)
crud.update_audiobook_project_status(db, project_id, "generating")
cfg = project.script_config
try:
genre = cfg.get("genre", "")
subgenre = cfg.get("subgenre", "")
premise = cfg.get("premise", "")
style = cfg.get("style", "")
llm = _get_llm_service(db)
_llm_model = crud.get_system_setting(db, "llm_model")
_user_id = user.id
def _log_usage(prompt_tokens: int, completion_tokens: int) -> None:
log_db = SessionLocal()
try:
crud.create_usage_log(log_db, _user_id, prompt_tokens, completion_tokens,
model=_llm_model, context="ai_script_continue")
finally:
log_db.close()
def on_token(token: str) -> None:
ps.append_token(key, token)
db_characters = crud.list_audiobook_characters(db, project_id)
characters_data = [
{"name": c.name, "gender": c.gender or "未知", "description": c.description or "", "instruct": c.instruct or ""}
for c in db_characters
]
char_map = {c.name: c for c in db_characters}
backend_type = user.user_preferences.get("default_backend", "aliyun") if user.user_preferences else "aliyun"
existing_chapters = crud.list_audiobook_chapters(db, project_id)
existing_chapters_data = [
{"index": ch.chapter_index, "title": ch.title or f"{ch.chapter_index + 1}", "summary": ""}
for ch in existing_chapters
]
start_index = max((ch.chapter_index for ch in existing_chapters), default=-1) + 1
ps.append_line(key, f"[AI剧本] 续写 {additional_chapters} 章,从第 {start_index + 1} 章开始...\n")
ps.append_line(key, "")
new_chapters_data = await llm.generate_additional_chapter_outline(
genre=genre, subgenre=subgenre, premise=premise, style=style,
existing_chapters=existing_chapters_data, additional_chapters=additional_chapters,
characters=characters_data, usage_callback=_log_usage,
)
ps.append_line(key, f"\n\n[完成] 续写大纲:{len(new_chapters_data)}")
assigned = []
for offset, ch_data in enumerate(new_chapters_data):
idx = start_index + offset
title = ch_data.get("title", f"{idx + 1}")
summary = ch_data.get("summary", "")
crud.create_audiobook_chapter(db, project_id, idx, summary, title=title)
assigned.append((idx, title, summary))
ps.append_line(key, f"\n[Step 2] 逐章生成对话脚本...\n")
for idx, title, summary in assigned:
ps.append_line(key, f"\n{idx + 1} 章「{title}」→ ")
ps.append_line(key, "")
chapter_obj = crud.get_audiobook_chapter_by_index(db, project_id, idx)
if not chapter_obj:
continue
try:
script_text = await llm.generate_chapter_script(
genre=genre, premise=premise,
chapter_index=idx, chapter_title=title, chapter_summary=summary,
characters=characters_data, on_token=on_token, usage_callback=_log_usage,
)
chapter_obj.source_text = script_text
db.commit()
segments_data = parse_ai_script(script_text, char_map)
unknown_speakers = {
seg["character"] for seg in segments_data
if seg["character"] != "旁白" and seg["character"] not in char_map
}
for speaker_name in sorted(unknown_speakers):
try:
npc_instruct = (
"音色信息:普通自然的中性成年人声音,语调平和\n"
"身份背景:故事中的路人或配角\n"
"年龄设定:成年人\n"
"外貌特征:普通外貌\n"
"性格特质:平淡自然\n"
"叙事风格:语速正常,语气自然"
)
npc_voice = crud.create_voice_design(
db=db, user_id=user.id,
name=f"[有声书] {project.title} - {speaker_name}",
instruct=npc_instruct, backend_type=backend_type,
)
npc_char = crud.create_audiobook_character(
db=db, project_id=project_id, name=speaker_name,
description=f"配角:{speaker_name}",
instruct=npc_instruct, voice_design_id=npc_voice.id,
)
char_map[speaker_name] = npc_char
ps.append_line(key, f"\n[NPC] 自动创建配角:{speaker_name}")
except Exception as e:
logger.error(f"Failed to create NPC {speaker_name}: {e}")
crud.delete_audiobook_segments_for_chapter(db, project_id, idx)
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("旁白")
if not char:
continue
crud.create_audiobook_segment(
db, project_id, char.id, seg_text,
chapter_index=idx, segment_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_obj.id, "ready")
ps.append_line(key, f"\n{seg_counter}")
except Exception as e:
logger.error(f"Chapter {idx} script generation failed: {e}", exc_info=True)
ps.append_line(key, f"\n[错误] {e}")
crud.update_audiobook_chapter_status(db, chapter_obj.id, "error", error_message=str(e))
crud.update_audiobook_project_status(db, project_id, "ready")
ps.append_line(key, f"\n\n[完成] 续写 {len(assigned)} 章完毕,项目已就绪")
ps.mark_done(key)
logger.info(f"continue_ai_script_chapters complete for project {project_id}, added {len(assigned)} chapters")
except Exception as e:
logger.error(f"continue_ai_script_chapters failed for project {project_id}: {e}", exc_info=True)
ps.append_line(key, f"\n[错误] {e}")
ps.mark_done(key)
crud.update_audiobook_project_status(db, project_id, "error", error_message=str(e))
async def analyze_project(project_id: int, user: User, db: Session, turbo: bool = False) -> None:
project = db.query(AudiobookProject).filter(AudiobookProject.id == project_id).first()
if not project: