prompts.mjs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. // lib/prompts.mjs — 提示词模板库(代码侧)
  2. //
  3. // 人读版见 references/prompt-templates.md;本文件是脚本直接调用的构建器。
  4. // 原则:人物一致性靠「固定外观锚点(appearance anchor)」逐镜头复述,
  5. // 空镜靠「场景锚点 + 运镜词」,避免 AI 每镜换脸/换景。
  6. /** 作曲填词的用户提示词 */
  7. export function buildLyricsPrompt({ theme, genre = '', mood = '', style = '', language = '中文', extra = '' }) {
  8. return `请为下面这个主题创作一首${language}流行歌曲的完整素材。
  9. 【主题】${theme}
  10. ${genre ? `【指定曲风】${genre}` : '【曲风】你来定,但需说明理由'}
  11. ${mood ? `【情绪基调】${mood}` : ''}
  12. ${style ? `【补充要求】${style}` : ''}
  13. ${extra ? `【其它】${extra}` : ''}
  14. 要求:
  15. - 歌词长度适合 2.5~3.5 分钟的歌曲,段落完整(主歌2段、副歌2-3次、可含桥段)。
  16. - 副歌必须有记忆点,句尾尽量押韵。
  17. - 歌词必须原创,不得使用任何已有歌曲的句子。
  18. - 给出英文 stylePrompt,供 AI 音乐模型直接使用(含曲风、乐器、速度、人声类型、混音质感)。`;
  19. }
  20. /** 分镜脚本的用户提示词(产线A:歌曲 MV) */
  21. export function buildStoryboardPrompt({ song, shotCount = 8, aspect = '16:9', perShot = 6, project = '' }) {
  22. const structure = (song.structure || []).join(' → ');
  23. return `为下面这首歌设计一支 AI 生成的音乐 MV 分镜(全程无实拍,纯 AI 生成画面)。
  24. 【歌名】${song.title}
  25. 【曲风/情绪】${song.genre || '-'} / ${song.mood || '-'} / ${song.bpm || '?'} BPM
  26. 【结构】${structure}
  27. 【画面比例】${aspect}
  28. 【镜头数量】${shotCount} 个(必须控制在 ${shotCount}±2 个,这是硬性约束)
  29. 【单镜时长】每镜 ${perShot} 秒左右,**必须在 ${Math.max(3, perShot - 2)}~${Math.min(8, perShot + 2)} 秒之间**。
  30. 注意:AI 图生视频单段现实上限约 8 秒,**不要**生成 20 秒、30 秒的镜头。
  31. 【总时长】因此全片约 ${shotCount * perShot} 秒;不要为了凑原曲长度而增加镜头数量或拉长单镜。
  32. 如需覆盖整首歌,请由使用者调大 --shots,而不是让单个镜头变长。
  33. ${project ? `【项目背景】${project}` : ''}
  34. 【歌词】
  35. ${song.lyrics}
  36. 设计要求:
  37. 1. 先定义「人物设定 appearanceAnchor」:一句话锁定主角外貌(性别/年龄/发型/服装/气质/镜头感),
  38. 之后每个含人物的镜头 description 与 imagePrompt 都必须原样复述这段锚点,保证跨镜头人物一致。
  39. 2. 镜头分为两类:
  40. - "broll":空镜/场景镜头(城市、天空、房间、物件特写…),无人物或人物非主体。
  41. - "perf":人物演唱镜头(正面/侧脸/半身,情绪表演)。
  42. 两类交替出现,情绪曲线从铺垫 → 蓄积 → 爆发 → 收束,与歌词段落对应。
  43. 3. 每个镜头的 imagePrompt 必须是「可独立投喂文生图模型」的完整英文提示词:
  44. 主体 + 动作/状态 + 环境 + 光线 + 镜头语言(焦段/景别/角度)+ 风格 + 画质词,
  45. 并在含人物时包含 appearanceAnchor 的英文版本。
  46. 4. 每个镜头的 videoPrompt 是「图生视频」的运镜描述(英文):
  47. 主体动作 + 镜头运动(push in / pull back / pan left / orbit / handheld)+ 节奏感,
  48. 不含画面内容描述(内容来自关键帧),6 秒内能完成。
  49. 5. 若某镜头需要唇形驱动(人物正脸演唱),把 "lipSync": true,其余为 false。
  50. 唇形驱动镜头总数建议 ≤ 4 个。
  51. 6. 所有镜头时长之和应接近歌曲总时长。`;
  52. }
  53. /** 口播稿的用户提示词(产线B) */
  54. export function buildScriptPrompt({ topic, durationSec = 90, audience = '', tone = '专业但口语化', points = [], extra = '' }) {
  55. const words = Math.round(durationSec * 3.6); // 中文口播约 3.5~4 字/秒
  56. return `写一篇可直接用于 AI 语音合成(TTS)的口播讲解稿。
  57. 【主题】${topic}
  58. 【目标时长】约 ${durationSec} 秒(约 ${words} 字,中文口播语速约 3.6 字/秒)
  59. ${audience ? `【受众】${audience}` : ''}
  60. 【语气】${tone}
  61. ${points.length ? `【必须覆盖的要点】\n- ${points.join('\n- ')}` : ''}
  62. ${extra ? `【其它】${extra}` : ''}
  63. 写作要求:
  64. 1. 口语化、短句为主,避免书面语与长难句(TTS 读长句会断气)。
  65. 2. 开头 3 秒必须有钩子(提问/反常识/利益点),结尾一句行动号召。
  66. 3. 数字与专有名词要写出来(会直接读出来),必要时用中文读法(如 "百分之三十")。`;
  67. }
  68. /** 产线B 讲解画面分镜的用户提示词 */
  69. export function buildTalkStoryboardPrompt({ script, durationSec, aspect = '16:9', shotCount = 8, project = '', hostAnchor = '' }) {
  70. return `为下面这篇口播稿设计 AI 生成的讲解视频分镜(纯 AI 生成画面,无实拍)。
  71. 【画面比例】${aspect}
  72. 【总时长】约 ${durationSec} 秒
  73. 【镜头数量】${shotCount} 个(必须控制在 ${shotCount}±2 个,这是硬性约束)
  74. 【单镜时长】每镜约 ${Math.round(durationSec / shotCount)} 秒,且**必须落在 4~10 秒之间**(AI 图生视频单段上限约 10 秒)
  75. ${project ? `【项目背景】${project}` : ''}
  76. ${hostAnchor ? `【讲者设定】${hostAnchor}(每个含讲者的镜头都必须原样复述该外观锚点)` : '【讲者设定】请先定义一位讲者的外观锚点 appearanceAnchor'}
  77. 【口播稿全文】
  78. ${script}
  79. 设计要求:
  80. 1. 每句/每小段口播都要有对应画面,画面与语义强相关(讲数据就给图表感画面,讲场景就给场景画面)。
  81. 2. 镜头两类交替:
  82. - "talking":讲者正面半身镜头(可选唇形驱动 lipSync:true,总数 ≤ 3 个)。
  83. - "broll":相关场景/物件/概念图(大量使用,占多数,降低口型不一致的观感风险)。
  84. 3. 每个镜头给出:start(秒)、duration(秒)、section(对应口播文本片段)、
  85. narration(该镜头对应的口播文字,用于生成字幕)、
  86. description(中文画面描述)、imagePrompt(英文,完整可投喂)、videoPrompt(英文运镜)。
  87. 4. 每个镜头的中文字幕文本放在 "subtitle" 字段(≤ 20 字,便于单行显示)。`;
  88. }
  89. /** 关键帧:把分镜镜头转成文生图 prompt(含人物一致性锚点) */
  90. export function buildKeyframePrompt(shot, { appearanceAnchor = '', style = '', aspect = '16:9' } = {}) {
  91. const parts = [];
  92. if (shot.imagePrompt) parts.push(shot.imagePrompt);
  93. else parts.push(shot.description || shot.prompt || '');
  94. if (shot.hasCharacter !== false && appearanceAnchor && !String(shot.imagePrompt || '').includes(appearanceAnchor.slice(0, 20))) {
  95. parts.push(`Character appearance (keep identical across shots): ${appearanceAnchor}`);
  96. }
  97. if (style) parts.push(style);
  98. parts.push('cinematic still frame, high detail, film grain, color graded, 4k');
  99. return parts.filter(Boolean).join(', ');
  100. }
  101. /** 视频生成:把分镜镜头转成图生视频 prompt */
  102. export function buildVideoPrompt(shot) {
  103. return shot.videoPrompt || 'subtle natural motion, slow push in, cinematic';
  104. }
  105. /** 画面比例 → 文生图尺寸 */
  106. export function aspectToSize(aspect = '16:9', tier = 'standard') {
  107. const map = {
  108. '16:9': { standard: '1536x864', large: '1792x1024' },
  109. '9:16': { standard: '864x1536', large: '1024x1792' },
  110. '1:1': { standard: '1024x1024', large: '1024x1024' },
  111. '4:3': { standard: '1152x864', large: '1365x1024' },
  112. };
  113. return (map[aspect] || map['16:9'])[tier] || '1024x1024';
  114. }
  115. /** 视频生成:比例 → 视频尺寸 */
  116. export function aspectToVideoSize(aspect = '16:9') {
  117. return aspect === '9:16' ? '720x1280' : aspect === '1:1' ? '960x960' : '1280x720';
  118. }
  119. /** 默认人物外观锚点模板(用户不给时使用) */
  120. export const DEFAULT_ANCHOR = {
  121. song: 'Asian woman in her late 20s, shoulder-length black hair with soft waves, natural makeup, wearing a deep navy oversized jacket over a white tee, calm melancholic gaze, cinematic presence',
  122. talk: 'Asian man in his 30s, short neat black hair, light stubble, wearing a charcoal knit sweater, warm approachable expression, professional presenter presence',
  123. };