diff --git a/core/backend/apps/ai/migrations/0014_video_prompt_dialogue_no_meta.py b/core/backend/apps/ai/migrations/0014_video_prompt_dialogue_no_meta.py new file mode 100644 index 0000000..2442244 --- /dev/null +++ b/core/backend/apps/ai/migrations/0014_video_prompt_dialogue_no_meta.py @@ -0,0 +1,29 @@ +from django.db import migrations + +# 视频提示词模板更新:时长/比例靠 API 参数传(不写正文)、台词带说话人(在 build 里)、 +# 尾句改风格 + 音效/字幕要求。DB 模板覆盖代码默认,故这里同步刷新 video_segment 那行。 +NEW = ( + "{设定}{分镜}【脚本】{脚本}\n" + "电商带货短视频,商品露出清晰,节奏有转化感。不要字幕,不要背景音乐,但是要有音效,逼真的音效。" +) +OLD = "{设定}{分镜}【脚本】{脚本}\n{时长}s · 9:16 竖屏电商带货短视频,镜头稳定,商品露出清晰,节奏有转化感。" + + +def _set(apps, template): + PromptTemplate = apps.get_model("ai", "PromptTemplate") + PromptTemplate.objects.filter(key="video_segment").update(template=template) + + +def forwards(apps, schema_editor): + _set(apps, NEW) + + +def backwards(apps, schema_editor): + _set(apps, OLD) + + +class Migration(migrations.Migration): + + dependencies = [("ai", "0013_seed_prompt_templates")] + + operations = [migrations.RunPython(forwards, backwards)] diff --git a/core/backend/apps/ai/services.py b/core/backend/apps/ai/services.py index a6fbeaf..2823d11 100644 --- a/core/backend/apps/ai/services.py +++ b/core/backend/apps/ai/services.py @@ -1094,16 +1094,33 @@ def _scene_context(project) -> str: return " · ".join(parts) -def _segment_script_text(segment) -> str: - """本镜脚本文本(画面 + 口播/旁白 + 商品露出),拼进故事板/视频提示词的【分镜脚本】。""" +def _segment_script_text(segment, entities=None, *, with_dialogue: bool = False, with_exposure: bool = True) -> str: + """本镜脚本文本(画面 + 台词/口播 + 商品露出),拼进故事板/视频提示词的【分镜脚本】。 + with_dialogue:有结构化对白时按「说话人:台词」原样放(说话人 id 用 entities 解析成名字),回退扁平 narration。 + with_exposure:是否带「商品露出:…」一行。""" parts = [] visual = (segment.visual_prompt or "").strip() if visual: parts.append(f"画面:{visual}") - narration = (segment.narration or "").strip() - if narration: - parts.append(f"口播/旁白:{narration}") - if getattr(segment, "product_exposure", ""): + dialogue = getattr(segment, "dialogue", None) if with_dialogue else None + name_by_id = {e.get("id"): (e.get("name") or "").strip() for e in (entities or []) if isinstance(e, dict)} + lines = [] + if isinstance(dialogue, list): + for d in dialogue: + if not isinstance(d, dict): + continue + line = (d.get("line") or "").strip() + if not line: + continue + spk = name_by_id.get(d.get("speaker")) or "" + lines.append(f"{spk}:{line}" if spk else line) + if lines: # 有对白 → 用带说话人的台词 + parts.append("台词:\n" + "\n".join(lines)) + else: # 无对白 → 回退扁平口播/旁白 + narration = (segment.narration or "").strip() + if narration: + parts.append(f"口播/旁白:{narration}") + if with_exposure and getattr(segment, "product_exposure", ""): parts.append(f"商品露出:{segment.product_exposure.strip()}") return "\n".join(parts) @@ -1141,12 +1158,15 @@ def build_video_segment_prompt(project, video_segment, scene, refs, user_prompt: storyboard_idx = n else: setup_parts.append(f"@图{n}是{r.get('label') or ''}({_ENTITY_TYPE_CN.get(r.get('type'), '参考')})") - script_text = _segment_script_text(scene) if scene is not None else "" + # 视频脚本:用带说话人的台词(原样照脚本),不带「商品露出」那行(露出靠模板尾句统一要求) + entities = (project.metadata or {}).get("script_entities", []) + script_text = _segment_script_text(scene, entities, with_dialogue=True, with_exposure=False) if scene is not None else "" extra = (user_prompt or "").strip() if extra: script_text = (script_text + "\n" + extra).strip() if script_text else extra # 正文可在 admin「提示词·视频」页改。占位符:{设定}=@图N点名、{分镜}=分镜图引用、{脚本}=本段脚本、{时长}。 - default = "{设定}{分镜}【脚本】{脚本}\n{时长}s · 9:16 竖屏电商带货短视频,镜头稳定,商品露出清晰,节奏有转化感。" + # 时长/比例靠 API 参数传(duration/ratio),不写进正文;尾句给风格 + 音效/字幕要求。 + default = "{设定}{分镜}【脚本】{脚本}\n电商带货短视频,商品露出清晰,节奏有转化感。不要字幕,不要背景音乐,但是要有音效,逼真的音效。" rendered = render_prompt( "video_segment", default, 设定=("【设定】" + ",".join(setup_parts) + "。\n" if setup_parts else ""),