feat: add continue script functionality for AI-generated audiobook projects
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user