// 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(', '); }