| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- // 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',
- };
- // ==================== 人物资产多视图(character-sheet)====================
- //
- // 出处:`_kb/07-即梦API工具箱/脚本/生成-人物资产v4流水线.js`(含 P5_PROMPT 原文)。
- // 这套四步流水线的价值:跨镜头不换脸。第④步多视图是后续所有含人物镜头的参考图。
- /** 把用户的中文人物设定拼成即梦够用的基础描述(保留用户原话,只补画质约束) */
- export function buildCharacterBase(description) {
- const d = String(description || '').trim().replace(/\s+/g, ' ');
- return (
- d +
- ' 写实摄影风格,非商业修图,保留轻微噪点,高光不过曝,阴影有细节;' +
- '表情自然松弛,眼神自然不失真;皮肤质感真实,脸部清晰、五官完整、无遮挡。'
- );
- }
- /**
- * 从人物设定里抽出「服装」短语,供 ②③④ 步复用。
- *
- * 为什么需要:KB 的 P2/P3/P5 prompt 把服装写死成「白色T恤 + 波点半身裙 + 草编包」,
- * 这会导致生成出来的人「脸是对的、衣服是错的」。服装必须由用户描述驱动。
- * 抽不出来就退回调用方给的兜底值,并让调用方显式知道用了兜底(不静默)。
- */
- export function extractOutfit(description, fallback = '白色T恤 + 波点半身裙') {
- const d = String(description || '');
- // 在服装提示词附近抓一个短语(中文服装词表)
- const m = d.match(
- /(?:身穿|穿着|穿|服装|搭配|着装|衣着)[::]?\s*([^,。;;,.\n]{2,40})/
- );
- if (m && m[1]) return m[1].trim();
- // 退一步:直接找带「衣/裙/裤/衫/外套/大衣/西装/卫衣/衬衫/背心」的短语
- const m2 = d.match(/([^,。;;,.\n]{0,16}(?:上衣|T恤|t恤|衬衫|卫衣|外套|大衣|西装|背心|裙|裤|袍)[^,。;;,.\n]{0,16})/);
- if (m2 && m2[1]) return m2[1].trim();
- return fallback;
- }
- /**
- * 四步 prompt 模板(②③④ 为图生图,参考上一步选定的成品)。
- * @param outfit 服装短语(来自用户设定,见 extractOutfit)
- */
- export const PORTRAIT_PROMPTS = {
- /** ① 定妆照(文生图,3:4,3 张候选,择优) */
- step1: (base) =>
- `${base} 画面聚焦面部,高曝低饱和,整体处于暗调静谧房间的慵懒氛围中,` +
- `呈现生活快照般的随手一拍抓拍风格。中景,光影斑驳,环境为大晴天,柔和光线笼罩,` +
- `阳光透过发丝照在脸上,突出冷白皮通透自然的肌肤状态,整体神态灵动又不失随性,唯美的氛围感。`,
- /** ② 上半身(图生图,参考①) */
- step2: (outfit = '白色T恤') =>
- '严格保留人物的五官结构、面部比例、发色发型和微表情,生成一张高清上半身正视图,' +
- '浅灰色影棚背景,高级商业摄影质感,真人写实风格。上半身完整入镜,正对镜头、头不歪,' +
- `五官全部露出,长发垂坠,瓷感白肌肤,佩戴简约饰品,服装为「${outfit}」的上身部分。`,
- /** ③ 全身(图生图,参考②) */
- step3: (outfit = '白色T恤 + 波点半身裙') =>
- '生成一张原创角色全身定妆照,浅灰色影棚背景,高级商业摄影质感,真人写实风格。' +
- '人物五官、妆容、发型、表情严格不变,微表情保留。画面构图与全身比例:全身定妆照,' +
- '完整展示头顶到脚底的全身比例,人物处于画面中央,A-pose,姿态放松松弛,背景干净无杂物。' +
- '光线与画质:柔和影棚主光,85mm/50mm 人像镜头,真实皮肤纹理,发丝清晰,' +
- `服装布料与纹理细腻自然。服装:${outfit}。`,
- /**
- * ④ 多视图(图生图,参考③,9:16)
- * ★ 原文照抄自 KB 的 P5_PROMPT:上 1/3 = 正/侧双头像特写,下 2/3 = 服装正面放大 + 背面全身。
- * 服装部分改为参数化(KB 原文写死为「白色T恤 + 波点半身裙 + 草编包」)。
- */
- step4: (outfit = '白色T恤 + 波点半身裙') => `生成图中人物的真人形象设定四视图,灰色摄影棚背景,整体为专业真人角色定妆参考板,规整排版,干净简洁,不是海报,不是杂志大片,不要剧情动作,不要复杂场景。
- 整体版式严格分为上下两部分:上半部分占画面高度三分之一,下半部分占画面高度三分之二。上方区域展示两张精细真人面部特写,左侧为正面面部特写,右侧为侧面面部特写。两张特写都以头部与肩颈为主,构图紧凑,清晰展示人物五官、骨骼结构、妆容、发型、头饰与神态。人物面部特征、发型、妆造、头部配饰必须与参考形象严格一致。皮肤质感真实自然,保留毛孔、细纹、绒毛、眉睫、碎发与皮肤受光后的细微起伏,不磨皮,不过度锐化,不做人像美容,呈现高解析真人摄影棚定妆照质感。
- 下方区域展示两张身体与服装参考图,左右排列。
- 左侧图片必须是放大后的正面服装展示图,人物采用标准 A-pose 站姿。这一张图的重点不是完整全身入镜,而是"人物尺度明显放大",让角色在画面中占据更大比例。左侧图片必须使用更近距离的正面取景,人物从肩颈下方开始入画,到鞋子结束,头部因画面放大而被完整裁切到画面之外。人物双肩、双臂、双手、躯干、腰部、腿部、鞋子必须完整保留,画面边缘只允许裁掉头部,不能裁掉手、脚或身体主体。
- 右侧图片为背面全身图,标准 A-pose,完整展示从头部到鞋子的背面全身。人物背对镜头,完整保留背面头部、发型背部、服装背面结构、鞋子与整体轮廓。
- 下方两张图必须为同一个人物、同一套服装、同一组配饰、同一身体比例。整体保持超模感修长比例,但不要夸张变形。服装设计、布料层次、鞋子、穿搭风格、服装结构与配饰样式必须严格遵循原图。服装:${outfit}。
- 光线只使用干净统一的摄影棚三点式打光:正左侧主光,正右侧辅助光,后右侧轮廓光。不要杂乱光效,不要彩色灯光,不要强氛围特效。背景为纯净灰色无缝影棚背景。
- 严格要求:上方两张脸部特写占画面上三分之一;下方两张身体图占画面下三分之二;左下图必须是"放大后的正面服装展示图",人物在画面中的占比明显更大,头部因近距离取景被完整裁切出画面;右下图必须是完整背面全身图;禁止左下图做成普通比例全身小图;禁止左下图出现任何头部信息;禁止左右服装不一致;禁止人物比例漂移;禁止 AI 油腻感、塑料皮肤感、过度美颜感、廉价 CG 感。`,
- };
- // ==================== 场景图(scene)====================
- /** 场景图(无人纯环境):为镜头铺环境,供关键帧做底 */
- export function buildScenePrompt(shot, { style = '', prevScene = '', aspect = '16:9' } = {}) {
- const parts = [];
- parts.push('Empty environment plate for a film shot, NO people, NO characters, pure scenery.');
- parts.push(shot.scenePrompt || shot.description || shot.prompt || '');
- if (shot.timeOfDay) parts.push(`Time of day: ${shot.timeOfDay}`);
- if (shot.location) parts.push(`Location: ${shot.location}`);
- if (prevScene) {
- parts.push(
- `Maintain visual continuity with the previous shot of the same sequence: ${prevScene}. ` +
- 'Keep the same color temperature, same lighting direction, same lens character so adjacent shots cut together seamlessly.'
- );
- }
- if (style) parts.push(style);
- parts.push(`Empty room, clean composition with clear space for a subject in the middle ground, ${aspect}`);
- parts.push('cinematic still frame, high detail, film grain, color graded, 4k');
- return parts.filter(Boolean).join(', ');
- }
- /** 基于场景图 + 人物资产图生成关键帧(有人物) */
- export function buildSceneKeyframePrompt(shot, { appearanceAnchor = '', style = '', aspect = '16:9' } = {}) {
- const parts = [];
- parts.push('Same location, same lighting, same lens as the reference environment image — only add the person into it.');
- parts.push(shot.imagePrompt || shot.description || '');
- if (appearanceAnchor && !String(shot.imagePrompt || '').includes(String(appearanceAnchor).slice(0, 20))) {
- parts.push(`Character appearance (keep identical across shots): ${appearanceAnchor}`);
- }
- if (style) parts.push(style);
- parts.push(`Shot on the same location as the reference background, ${aspect}`);
- parts.push('cinematic still frame, high detail, film grain, color graded, 4k');
- return parts.filter(Boolean).join(', ');
- }
|