181 lines
9.2 KiB
JavaScript
181 lines
9.2 KiB
JavaScript
// 步骤⑧: 逐镜 HLDP 提示词生成 (doubao-seed-2-1-pro)
|
||
const https = require('https');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const ENV_PATH = path.join(__dirname, '..', '..', '.env');
|
||
const ARK_KEY = fs.readFileSync(ENV_PATH, 'utf-8').match(/ARK_API_KEY=(.+)/)[1].trim();
|
||
|
||
// 10镜合并方案
|
||
const SHOTS = [
|
||
{
|
||
id: 'M01', duration: 8,
|
||
scene: '漆黑画面,警报滴滴声持续。林昊主观视角缓缓睁眼,天花板灯泡忽明忽暗。昏暗灯光下,破旧船员宿舍:两张三层钢架床铺凌乱,空无一人。宿舍唯一的舱门——厚重水密钢门,布满铁锈,上方嵌有矩形铆钉框观察窗。',
|
||
refs: 'ENV-001-crew-quarters.png (作为场景底板)',
|
||
camera: 'POV 主观视角,缓慢呼吸感起伏',
|
||
style: '深海科幻恐怖,3D写实,冷蓝工业光,忽明忽暗'
|
||
},
|
||
{
|
||
id: 'M02', duration: 8,
|
||
scene: '林昊抬起双手至脸前,布满老茧,指甲缝嵌满黑色机油污渍。神色迷茫。突然轰隆巨响!船舱猛烈震颤!林昊从下铺猛地坐起,死死抓住床头铁杆固定身体。特写铁杆上镌刻的"秦山号"标记。',
|
||
refs: 'CHAR-001-林昊, ENV-001-crew-quarters.png',
|
||
camera: '近景手部→中景全身→推近至铁杆标记特写',
|
||
style: '同上,镜头随撞击晃动'
|
||
},
|
||
{
|
||
id: 'M03', duration: 8,
|
||
scene: '舱外传来极速沉重的脚步声。林昊猛地转头盯向锈迹斑斑的舱门。透过矩形观察窗,一道形状诡异、速度不似人类的黑影飞速闪过。震动渐停,灰尘震落。林昊跳下床,因船体倾斜险些摔倒,扶住床沿。',
|
||
refs: 'CHAR-001-林昊, ENV-002-hatch-window.png',
|
||
camera: '中景→快速甩镜至舱门→短暂 POV 透过观察窗',
|
||
style: '同上,甩镜制造紧张感'
|
||
},
|
||
{
|
||
id: 'M04', duration: 10,
|
||
scene: '林昊稳住身形,捂头皱眉,踉跄走向舱门。走到门前,伸手擦拭矩形观察窗内侧玻璃——四层钢化玻璃,内侧擦净,外侧仍被厚重灰尘糊住。拧动舱门把手,伴随刺耳吱呀声,把手纹丝不动。他看向掌心沾满的铁锈。',
|
||
refs: 'CHAR-001-林昊, ENV-002-hatch-window.png',
|
||
camera: '中景跟拍走向门→近景擦窗→近景手部+把手',
|
||
style: '同上,倾斜构图强调船体倾斜'
|
||
},
|
||
{
|
||
id: 'M05', duration: 6,
|
||
scene: '林昊瞥见舱门缝下渗出黑色液压油,皱眉面露不耐。转身看见下铺旁悬桌上放着一个硕大黑色破旧工具包。快步走过去,一把拉开拉链。',
|
||
refs: 'CHAR-001-林昊, ENV-002-hatch-window.png, PROP-001-tool-bag.png',
|
||
camera: '近景液压油→转身中景→近景拉开拉链',
|
||
style: '同上'
|
||
},
|
||
{
|
||
id: 'M06', duration: 6,
|
||
scene: '工具包内物品:便携式手电筒、备用电源、急救绷带、半米长管钳、军绿水壶,以及一个破旧工牌。推至工牌特写:塑料壳泛黄开裂,编号000768,姓名林昊,岗位模糊不清。照片的人脸部位被利器故意划烂,仅余年轻帅气的下颌轮廓。',
|
||
refs: 'PROP-001-tool-bag.png, PROP-002-id-badge.png, PROP-003-pipe-wrench.png, PROP-004-flashlight.png',
|
||
camera: '特写包内物品慢摇→推至工牌极致特写',
|
||
style: '同上'
|
||
},
|
||
{
|
||
id: 'M07', duration: 9,
|
||
scene: '林昊猛抬头看向舱门矩形观察窗——又是一道黑影一闪而逝,更近更快。警报声变急促。林昊面露惧色,压低声音自语,呼吸急促。迅速收起工具包背在身上,抄起半米管钳紧握手中,大步走到舱门前。',
|
||
refs: 'CHAR-001-林昊, ENV-002-hatch-window.png, PROP-001-tool-bag.png, PROP-003-pipe-wrench.png',
|
||
camera: '中景抬头→快速甩镜至窗→回林昊恐惧面部→中景武装动作',
|
||
style: '同上,紧张节奏加快'
|
||
},
|
||
{
|
||
id: 'M08', duration: 8,
|
||
scene: '管钳卡住门把手,林昊咬紧牙关猛地用力——把手纹丝不动,铁锈碎屑飘落。盯着门缝下渗出的液压油渍,目光一凛,举管钳高高扬起,冲门把手下方液压凸起狠狠砸下!撞击瞬间:金属变形,白色泄压蒸汽猛然喷出。呲——林昊嘴角微扬,再次用管钳撬动把手,伴随刺耳吱呀声,把手终于缓缓转动。',
|
||
refs: 'PROP-003-pipe-wrench.png, ENV-002-hatch-window.png',
|
||
camera: '近景把手+管钳→中景举管钳→撞击晃动→定格雷泄压蒸汽→近景把手转动',
|
||
style: '同上,动作戏节奏紧凑'
|
||
},
|
||
{
|
||
id: 'M09', duration: 7,
|
||
scene: '砰!!一声巨响——一道不似人形的黑影重重砸在舱门外矩形观察窗上。一只手:指甲全部脱落,指尖露出白骨,血肉模糊,顺着玻璃缓缓滑下,留下五道暗红色血痕。然后一张脸贴上玻璃——半人半孢子。右半边:四十岁男人脸,苍白,眼泪滑落,嘴唇无声翕动。左半边:皮肤透明隆起,颗粒蠕动,左眼黑色菌膜覆盖,嘴角撕裂至耳根呈蜂窝状孔洞,黑色孢子在每个孔洞中脉动。每次呼吸,黑色孢子雾从孔洞喷出,蒙上玻璃。',
|
||
refs: 'ENV-002-hatch-window.png, CHAR-002-半孢感染者',
|
||
camera: '中景→撞击晃动→慢推至窗外→极致特写半孢脸→推至孢子雾',
|
||
style: '同上,恐怖高潮,冷蓝光让血呈现近乎黑色'
|
||
},
|
||
{
|
||
id: 'M10', duration: 5,
|
||
scene: '林昊僵在原地,瞳孔剧烈收缩,手中紧握的管钳不停发抖。画面骤然全黑。急促警报声拉至最大音量,持续不断。',
|
||
refs: 'CHAR-001-林昊',
|
||
camera: '近景林昊面部恐惧→瞳孔特写→渐隐至全黑',
|
||
style: '同上,尾声渐暗'
|
||
}
|
||
];
|
||
|
||
function callDoubao(prompt, system) {
|
||
return new Promise((resolve, reject) => {
|
||
var payload = JSON.stringify({
|
||
model: 'doubao-seed-2-1-pro-260628',
|
||
messages: [
|
||
{ role: 'system', content: system },
|
||
{ role: 'user', content: prompt }
|
||
],
|
||
temperature: 0.3,
|
||
max_tokens: 512
|
||
});
|
||
var options = {
|
||
hostname: 'ark.cn-beijing.volces.com',
|
||
path: '/api/v3/chat/completions',
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': 'Bearer ' + ARK_KEY,
|
||
'Content-Type': 'application/json',
|
||
'Content-Length': Buffer.byteLength(payload, 'utf-8')
|
||
}
|
||
};
|
||
var req = https.request(options, function(res) {
|
||
var body = '';
|
||
res.on('data', function(c) { body += c; });
|
||
res.on('end', function() {
|
||
try {
|
||
var d = JSON.parse(body);
|
||
if (d.error) { reject(d.error); return; }
|
||
resolve(d.choices[0].message.content.trim());
|
||
} catch(e) { reject(e); }
|
||
});
|
||
});
|
||
req.on('error', reject);
|
||
req.setTimeout(120000, function() { req.destroy(); reject(new Error('timeout')); });
|
||
req.write(payload);
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
async function main() {
|
||
var projPath = path.join(__dirname, '..', 'projects', 'deep-sea-voyage');
|
||
var outputLines = [];
|
||
outputLines.push('# 深海迷航 · EP01 · PROMPTS-EP01');
|
||
outputLines.push('# 引擎: doubao-seed-2-1-pro-260628');
|
||
outputLines.push('# 10镜 · Seedance 2.0 · 720p');
|
||
outputLines.push('');
|
||
|
||
var totalCost = 0;
|
||
for (var i = 0; i < SHOTS.length; i++) {
|
||
var s = SHOTS[i];
|
||
console.log('--- ' + s.id + ' (' + s.duration + 's) ---');
|
||
|
||
var cost = (0.5 * s.duration).toFixed(1);
|
||
totalCost += parseFloat(cost);
|
||
|
||
outputLines.push('## ' + s.id + ' · ' + s.duration + 's');
|
||
outputLines.push('△ ' + s.scene);
|
||
outputLines.push('相机: ' + s.camera);
|
||
outputLines.push('参考图: ' + s.refs);
|
||
outputLines.push('');
|
||
|
||
// AI 生成英文 Seedance prompt
|
||
var sysPrompt = '你是一个专业的 AI 视频生成提示词工程师。将中文场景描述转化为 Seedance 2.0 英文视频生成提示词。不超过 200 词。包含景别、动作、灯光、风格。不要加引号。';
|
||
var userPrompt = '场景: ' + s.scene + '\n相机运动: ' + s.camera + '\n风格: ' + s.style;
|
||
|
||
console.log(' 调用AI生成英文提示词...');
|
||
try {
|
||
var seedancePrompt = await callDoubao(userPrompt, sysPrompt);
|
||
outputLines.push('Seedance prompt:');
|
||
outputLines.push(seedancePrompt);
|
||
outputLines.push('');
|
||
console.log(' ✅ ' + s.id + ' (' + seedancePrompt.length + ' chars)');
|
||
} catch(e) {
|
||
outputLines.push('Seedance prompt: [生成失败: ' + (e.message || e) + ']');
|
||
outputLines.push('');
|
||
console.log(' ❌ ' + s.id + ': ' + (e.message || e));
|
||
}
|
||
}
|
||
|
||
outputLines.push('---');
|
||
outputLines.push('## 成本预估');
|
||
outputLines.push('');
|
||
outputLines.push('| M# | 时长 | 单价 | 费用 |');
|
||
outputLines.push('|:--:|:--:|:--:|:--:|');
|
||
for (var j = 0; j < SHOTS.length; j++) {
|
||
var s2 = SHOTS[j];
|
||
var c = (0.5 * s2.duration).toFixed(1);
|
||
outputLines.push('| ' + s2.id + ' | ' + s2.duration + 's | ¥0.5/s | ¥' + c + ' |');
|
||
}
|
||
outputLines.push('| **合计** | **75s** | — | **¥' + totalCost.toFixed(1) + '** |');
|
||
|
||
var outPath = path.join(projPath, 'PROMPTS-EP01.hdlp');
|
||
fs.writeFileSync(outPath, outputLines.join('\n'), 'utf-8');
|
||
console.log('\n🎬 SAVED: ' + outPath);
|
||
console.log('总计 10镜 75s 预估 ¥' + totalCost.toFixed(1));
|
||
}
|
||
|
||
main().catch(function(e) { console.error('FATAL:', e.message || e); });
|