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