- 新增 gen-covers 系列脚本(kling/gpt/t2i/batch/direct/final等方案) - 执黑先行二号添加9种风格提示词目录(梦核/剪纸/水墨/毛毡/硬核线条等) - 执黑先行添加封面提示词、执黑先行二号更新图片提示词 - product_viral_factory 账号配置扩充并添加 cover_template 参考图 - capcut_assemble/kling-video-generator/oss-upload/poll-utils 细节修复 - CLAUDE.md 更新流程文档 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
171 lines
15 KiB
JavaScript
171 lines
15 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* 封面生成 v2 — GPT direct 一步出图
|
|
* 对齐 prompts/封面提示词.md 模板
|
|
* 参考图=场景大图(优先)/视频首帧(降级) → GPT edits → 直接出带字海报 → OSS
|
|
*/
|
|
const fs = require('fs'), path = require('path'), https = require('https');
|
|
const { execSync } = require('child_process');
|
|
|
|
const cfg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'config.json'), 'utf-8'));
|
|
const DB = cfg.jianyingDraftPath;
|
|
const AK = cfg.gptImageApiKey;
|
|
const OUT = '/Users/lc/video-create/output/执黑先行_补封面/v2_final';
|
|
fs.mkdirSync(OUT, { recursive: true });
|
|
|
|
// 8 drafts with topicA, line1/line2, imagePrompt styleDesc, and scene image path (if available)
|
|
const LIST = [
|
|
{
|
|
s:'01', t:'闭嘴做事·才是狠人', l1:'闭嘴做事', l2:'才是狠人',
|
|
ref: '/Users/lc/Movies/JianyingPro/User Data/Projects/com.lveditor.draft/执黑先行_0513_01_闭嘴搞钱/assets/images/scene_07_%E5%A4%A7%E9%83%A8%E5%88%86%E5%A4%B1%E8%B4%A5.jpeg.jpeg',
|
|
ip: 'A lone powerful figure in a sleek black designer coat strides through a rain-slicked urban street at night, warm amber street light cutting across their silhouette, confident posture, head held high. Audrey Hepburn modern urban elegance, sweet yet cool expression, clean-cut silhouette. Tailored black cashmere coat over fitted ivory silk blouse, high-waisted dark trousers, subtle gold earrings. Rain-slicked asphalt reflecting golden street lights, neon signs casting blue ambient glow, city skyscrapers behind. Quiet confidence, controlled power, the calm of someone who has stopped seeking approval and started winning. Warm amber-golden street light as main source, cool blue night ambient, golden rim light, atmospheric haze, shadows deep but translucent. modern urban digital illustration, semi-realistic, geometric color-block faces, visible brushwork texture, cinematic composition, full bleed, no border, no frame, no margin. Vertical format, aspect ratio 9:16.'
|
|
},
|
|
{
|
|
s:'02', t:'你只是没钱罢了', l1:'你只是没钱罢了', l2:'',
|
|
ref: null, d:'执黑先行_0513_02_穷是原罪',
|
|
ip: 'A person standing alone in an empty luxurious penthouse overlooking a glittering city at night, hand in empty pockets, expression of raw honest realization. Audrey Hepburn modern urban elegance, sweet yet cool with a hint of bitter truth, clean-cut silhouette. Simple elegant white shirt slightly wrinkled, dark slim trousers, barefoot on cold marble floor. Floor-to-ceiling windows showing city skyline, warm golden interior light clashing with cold blue night outside, empty space emphasizing isolation. Raw honesty, the moment of seeing things as they really are, no pretense. Warm amber light inside, cold blue moonlight through windows, golden rim light on figure, atmospheric haze. modern urban digital illustration, semi-realistic, geometric color-block faces, visible brushwork texture, cinematic composition, full bleed. Vertical format, aspect ratio 9:16.'
|
|
},
|
|
{
|
|
s:'03', t:'别跪了·你配的上', l1:'别跪了', l2:'你配的上',
|
|
ref: null, d:'执黑先行_0513_03_别跪',
|
|
ip: 'A person standing tall with perfect posture in a grand marble hall, chin lifted, light breaking through tall windows behind them, dignity radiating from their stance. Audrey Hepburn modern urban elegance, calm confident expression, clean-cut silhouette. Elegant fitted black blazer over a silk camisole, tailored wide-leg trousers, subtle pearl earrings, designer heels. Grand marble architecture with towering columns, warm golden sunlight flooding through tall windows, dust particles floating in light beams. Dignity, self-worth, the power of knowing your own value. Warm golden sunlight as main source, cool shadows in corners, golden rim light on the figure, atmospheric light haze. modern urban digital illustration, semi-realistic, geometric color-block faces, visible brushwork texture, cinematic composition, full bleed. Vertical format, aspect ratio 9:16.'
|
|
},
|
|
{
|
|
s:'04', t:'没人说的·阶层真相', l1:'没人说的', l2:'阶层真相',
|
|
ref: null, d:'执黑先行_0513_04_阶层密码',
|
|
ip: 'A figure standing at the edge of two contrasting worlds — below a crowded grey muted street of ordinary people, above a warm golden elite realm visible through floor-to-ceiling glass. The figure looks through this invisible barrier with knowing eyes. Audrey Hepburn modern urban elegance, expression of quiet understanding, clean-cut silhouette. Simple but expensive-looking dark coat, quality fabric, understated luxury. Split-level urban scene: grey muted street below, golden warm elite space above visible through glass, the figure positioned at the threshold between both worlds. Revelation, understanding the unspoken rules, seeing through the illusion. Cold blue-grey ambient below, warm golden light above, the figure bathed in both lighting zones at the boundary. modern urban digital illustration, semi-realistic, geometric color-block faces, visible brushwork texture, cinematic composition, full bleed. Vertical format, aspect ratio 9:16.'
|
|
},
|
|
{
|
|
s:'05', t:'出轨真相·不是道德', l1:'出轨真相', l2:'不是道德',
|
|
ref: null, d:'执黑先行_0513_05_能量掠夺',
|
|
ip: 'Two silhouettes facing away from each other in a rain-soaked city street at night, neon reflections on wet pavement, one looking back with a complex expression that is neither guilty nor angry — just clear. Audrey Hepburn modern urban elegance for both figures, clean-cut silhouettes, expressions of complex emotional truth rather than simple morality. Elegant trench coats, one dark one light, rain-soaked city street fashion, subtle details. Rain-soaked asphalt reflecting red and blue neon signs, steam rising from street vents, city lights blurred by rain. Emotional complexity, truth beyond simple right and wrong, the grey area of human desire. Red neon glow from one side, cool blue from the other, warm amber street lights, wet reflections creating multiple light sources. modern urban digital illustration, semi-realistic, geometric color-block faces, visible brushwork texture, cinematic composition, full bleed. Vertical format, aspect ratio 9:16.'
|
|
},
|
|
{
|
|
s:'06', t:'不凑合·最好的风水', l1:'不凑合', l2:'最好的风水',
|
|
ref: null, d:'执黑先行_0513_06_不凑合',
|
|
ip: 'A person confidently walking away from a crumbling grey low-energy building toward a vibrant sunlit golden path, head held high, not looking back. Audrey Hepburn modern urban elegance, expression of quiet determination and self-respect, clean-cut silhouette. Stylish tailored coat catching the wind, quality leather boots, a single statement gold bracelet catching sunlight. Contrast scene: behind is grey crumbling architecture in cold shadow, ahead is a sun-drenched vibrant path with warm golden light and hints of greenery. Empowerment, choosing yourself, the courage to leave what does not serve you. Cold blue-grey shadows behind the figure, warm golden sunlight ahead flooding the scene, golden rim light on the figure stepping forward. modern urban digital illustration, semi-realistic, geometric color-block faces, visible brushwork texture, cinematic composition, full bleed. Vertical format, aspect ratio 9:16.'
|
|
},
|
|
{
|
|
s:'07', t:'跟对人·只管赢', l1:'跟对人', l2:'只管赢',
|
|
ref: null, d:'执黑先行_0513_07_真领导',
|
|
ip: 'A powerful mentor-like figure in shadow standing behind a rising protégé in golden spotlight, like a chess grandmaster and their strongest piece in perfect alignment. The protégé has Audrey Hepburn modern urban elegance, expression of focused confidence, clean-cut silhouette. Protégé in sharp tailored blazer and sleek trousers, mentor figure in darker tones partially in shadow, both exuding effortless cool. Dramatic scene: protégé in warm golden spotlight center stage, mentor in cool blue shadow behind, chess-like floor pattern, high-end urban setting. Strategic power, alignment, the moment of recognizing the right path through the right person. Strong golden spotlight on protégé, cool blue ambient on mentor figure, dramatic light-shadow contrast, cinematic atmosphere. modern urban digital illustration, semi-realistic, geometric color-block faces, visible brushwork texture, cinematic composition, full bleed. Vertical format, aspect ratio 9:16.'
|
|
},
|
|
{
|
|
s:'10', t:'哭惨的人·榨你最狠', l1:'哭惨的人', l2:'榨你最狠',
|
|
ref: null, d:'执黑先行_0513_10_榨你最狠',
|
|
ip: 'A person pulling their hand away from multiple grasping hands reaching out from cold blue darkness, expression of cold clarity and self-preservation, the moment of breaking free. The central figure has Audrey Hepburn modern urban elegance, expression of clear-eyed boundary-setting, clean-cut silhouette. Central figure in a crisp white shirt with rolled-up sleeves, fitted dark trousers, pulling away decisively. Dark atmospheric void with grasping hands emerging from cold blue shadow, the central figure stepping into warm golden protective light, creating a stark contrast between toxic attachment and self-preservation. Boundary-setting, recognizing emotional vampires, the cold clarity of self-protection. Grasping hands in cold blue shadow, the escaping figure bathed in warm protective amber-golden light, dramatic contrast. modern urban digital illustration, semi-realistic, geometric color-block faces, visible brushwork texture, cinematic composition, full bleed. Vertical format, aspect ratio 9:16.'
|
|
},
|
|
];
|
|
|
|
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
|
|
async function gptEdit(prompt, imgPath, size, retries=4) {
|
|
for (let a = 0; a <= retries; a++) {
|
|
if (a > 0) { console.log(` retry ${a}/${retries} in ${a*10}s...`); await sleep(a*10000); }
|
|
try {
|
|
const boundary = '----FB' + Math.random().toString(36).slice(2);
|
|
const imgBuf = fs.readFileSync(imgPath);
|
|
const parts = [];
|
|
const af = (n, v) => parts.push(Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="${n}"\r\n\r\n${v}\r\n`));
|
|
const aff = (n, fn, buf) => { parts.push(Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="${n}"; filename="${fn}"\r\nContent-Type: image/${fn.endsWith('.jpeg')||fn.endsWith('.jpg')?'jpeg':'png'}\r\n\r\n`)); parts.push(buf); parts.push(Buffer.from('\r\n')); };
|
|
af('model', 'gpt-image-2'); af('prompt', prompt); af('size', size);
|
|
aff('image', path.basename(imgPath), imgBuf);
|
|
parts.push(Buffer.from(`--${boundary}--\r\n`));
|
|
const body = Buffer.concat(parts);
|
|
const res = await new Promise((resolve, reject) => {
|
|
const req = https.request({
|
|
hostname: 'yunwu.ai', port: 443, path: '/v1/images/edits', method: 'POST',
|
|
headers: { 'Authorization': 'Bearer ' + AK, 'Content-Type': 'multipart/form-data; boundary=' + boundary, 'Content-Length': String(body.length) },
|
|
timeout: 180000,
|
|
}, (res) => { const c = []; res.on('data', d => c.push(d)); res.on('end', () => resolve({ s: res.statusCode, d: Buffer.concat(c).toString() })); });
|
|
req.on('error', reject);
|
|
req.write(body); req.end();
|
|
});
|
|
if (res.s === 200) {
|
|
const j = JSON.parse(res.d);
|
|
if (j.data?.[0]?.b64_json) return Buffer.from(j.data[0].b64_json, 'base64');
|
|
if (j.data?.[0]?.url) return j.data[0].url;
|
|
throw new Error('No image');
|
|
}
|
|
console.log(` ⚠️ ${res.s}: ${res.d.substring(0,100)}`);
|
|
if (res.s >= 500 || res.s === 429) continue;
|
|
} catch (e) { console.log(` ⚠️ ${e.message}`); }
|
|
}
|
|
throw new Error('GPT edits exhausted');
|
|
}
|
|
|
|
function getFirstVideo(draftDir) {
|
|
const data = JSON.parse(fs.readFileSync(path.join(draftDir, 'draft_content.json'), 'utf-8'));
|
|
for (const tr of data.tracks) {
|
|
if (tr.type === 'video' && tr.segments?.length > 0)
|
|
for (const v of data.materials.videos) if (v.id === tr.segments[0].material_id) return v.path;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function doOne(item, idx) {
|
|
const { s, t, l1, l2, ref, d, ip } = item;
|
|
const dir = path.join(OUT, s); fs.mkdirSync(dir, { recursive: true });
|
|
console.log(`\n${'='.repeat(50)}\n[${idx+1}/8] ${t}\n${'='.repeat(50)}`);
|
|
|
|
// Get reference image
|
|
let refPath;
|
|
if (ref && fs.existsSync(ref)) {
|
|
refPath = ref;
|
|
console.log(' Ref: scene image');
|
|
} else if (d) {
|
|
const vp = getFirstVideo(path.join(DB, d));
|
|
if (!vp) throw new Error('No reference available');
|
|
refPath = path.join(dir, '_frame.png');
|
|
execSync(`ffmpeg -y -i "${vp}" -vframes 1 -q:v 2 "${refPath}" 2>/dev/null`, { timeout: 10000 });
|
|
console.log(' Ref: video frame (scene image unavailable)');
|
|
} else {
|
|
throw new Error('No reference');
|
|
}
|
|
|
|
// styleDesc from imagePrompt first sentence
|
|
const styleDesc = ip.split('。')[0] + '.';
|
|
console.log(` Style: ${styleDesc.substring(0,80)}...`);
|
|
|
|
const sizes = [
|
|
{ lb: '抖音', sz: '1088x1920', ratio: '9:16', nm: `cover_${s}_抖音` },
|
|
{ lb: '小红书', sz: '1152x1536', ratio: '3:4', nm: `cover_${s}_小红书` },
|
|
];
|
|
|
|
for (const { lb, sz, ratio, nm } of sizes) {
|
|
// Build editPrompt from template
|
|
const line1 = l1 || t;
|
|
const line2 = l2 || '';
|
|
const textPart = line2
|
|
? `At the center of the image, prominently display the Chinese text "${line1}" on the first line and "${line2}" on the second line, in very large bold white characters with a thick golden metallic outline, like a movie poster title. The text should be the dominant visual element, taking up most of the frame.`
|
|
: `At the center of the image, prominently display the Chinese text "${line1}" in very large bold white characters with a thick golden metallic outline, like a movie poster title. The text should be the dominant visual element, taking up most of the frame.`;
|
|
|
|
const editPrompt = `Create a vertical ${ratio} social media cover poster. Keep the EXACT same art style, color palette, lighting, atmosphere and aesthetic as the reference image: ${styleDesc} The composition should be tighter and more poster-like with a strong focal point. ${textPart}`;
|
|
|
|
console.log(` 🎨 ${lb} (${sz})...`);
|
|
const result = await gptEdit(editPrompt, refPath, sz);
|
|
const outPath = path.join(dir, `${nm}.png`);
|
|
if (Buffer.isBuffer(result)) {
|
|
fs.writeFileSync(outPath, result);
|
|
} else {
|
|
await new Promise((resolve, reject) => {
|
|
const file = fs.createWriteStream(outPath);
|
|
https.get(result, (res) => { res.pipe(file); file.on('finish', () => { file.close(); resolve(); }); }).on('error', reject);
|
|
});
|
|
}
|
|
console.log(` ✅ ${lb}`);
|
|
}
|
|
|
|
// Clean up frame if extracted
|
|
if (d) { try { fs.unlinkSync(path.join(dir, '_frame.png')); } catch (_) {} }
|
|
}
|
|
|
|
async function main() {
|
|
for (let i = 0; i < LIST.length; i++) {
|
|
try { await doOne(LIST[i], i); if (i < LIST.length-1) { console.log(' ⏸️ 3s...'); await sleep(3000); } }
|
|
catch (e) { console.error(`❌ [${LIST[i].s} ${LIST[i].t}]: ${e.message}`); }
|
|
}
|
|
console.log('\nDone.');
|
|
}
|
|
main().catch(e => { console.error(e.message); process.exit(1); });
|