guanghulab/image-studio/generate.js
铸渊 c1b3c02ef6 🎨 铸渊图片工作室 - 小红书/即刻/海报配图生成工具
为冰朔定制专属配图生成系统,支持三种卡片类型:
- 小红书竖版卡片 (1080×1440, 3:4)
- 即刻方卡 (1080×1080, 1:1)
- 海报 (1080×1920, 9:16)

特性:多套配色方案、多种布局风格、
      Puppeteer + HTML/CSS 渲染引擎、
      可部署 Web 服务 + 预览页面

使用方式:冰朔在对话里告诉铸渊内容和类型,
         铸渊运行脚本生成,冰朔直接下载使用。
2026-05-22 23:49:02 +00:00

199 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* ═══════════════════════════════════════════════════
* 铸渊图片工作室 · 主入口
* ═══════════════════════════════════════════════════
*
* 冰朔,你告诉我内容和想要的类型,我帮你生成。
*
* 用法:
* node generate.js xiaohongshu --title "标题" --body "正文"
* node generate.js jike --title "观点" --body "内容"
* node generate.js poster --title "海报标题" --details "时间5月1日" "地点:广州"
*
* 也可以直接在代码里调用 API
* import { generate } from './generate.js'
* await generate({ type: 'xiaohongshu', title: '...', body: '...' })
*/
import { renderToImage, renderCarousel } from './renderer.js'
import { xiaohongshuCard, xiaohongshuCarousel } from './templates/xiaohongshu.js'
import { jikeCard } from './templates/jike.js'
import { posterCard } from './templates/poster.js'
import { STYLES, useScheme } from './config.js'
/**
* 统一生成入口
* @param {object} opts
* @param {'xiaohongshu'|'jike'|'poster'} opts.type - 卡片类型
* @param {string} opts.title - 标题
* @param {string} opts.body - 正文
* @param {string} opts.subtitle - 副标题
* @param {string} opts.tag - 标签
* @param {string} opts.quote - 金句(小红书 quote 布局用)
* @param {string} opts.emoji - Emoji即刻用
* @param {string} opts.footer - 底部文字
* @param {string[]} opts.details - 详情列表(海报用)
* @param {string} opts.cta - 行动号召(海报用)
* @param {'default'|'quote'|'list'|'minimal'} opts.layout - 布局
* @param {'default'|'notice'|'recruit'} opts.style - 海报风格
* @param {string} opts.scheme - 配色方案名
* @param {string} opts.output - 输出文件名(不含扩展名)
* @param {boolean} opts.carousel - 是否拆为多页轮播
* @param {string[]} opts.pages - 多页正文(轮播用)
* @returns {Promise<{files: string[], type: string}>}
*/
export async function generate(opts = {}) {
const {
type = 'xiaohongshu',
title = '',
body = '',
subtitle = '',
tag = '',
quote = '',
emoji = '',
footer = '',
details = [],
cta = '',
layout = 'default',
style = 'default',
scheme = '',
output = '',
carousel = false,
pages = [],
} = opts
// 切换配色
if (scheme) useScheme(scheme)
const baseName = output || `${type}_${Date.now()}`
const size = STYLES.sizes[type] || STYLES.sizes.xiaohongshu
let files = []
switch (type) {
case 'xiaohongshu': {
if (carousel && pages.length > 0) {
const htmls = xiaohongshuCarousel({ title, subtitle, tag, quote, layout }, pages)
files = await renderCarousel(htmls, { ...size, baseName })
} else {
const html = xiaohongshuCard({ title, body, subtitle, tag, quote, footer, layout })
files = [await renderToImage(html, { ...size, name: baseName })]
}
break
}
case 'jike': {
const html = jikeCard({ title, body, meta: subtitle, emoji, layout })
files = [await renderToImage(html, { ...size, name: baseName })]
break
}
case 'poster': {
const html = posterCard({ title, subtitle, body, details, cta, footer, tag, style })
files = [await renderToImage(html, { ...size, name: baseName })]
break
}
default:
throw new Error(`未知卡片类型: ${type}`)
}
return { files, type }
}
/* ── CLI 入口 ── */
async function cli() {
const args = process.argv.slice(2)
const type = args.find(a => !a.startsWith('--'))
if (!type || type === 'help') {
console.log(`
铸渊图片工作室 · 用法
node generate.js <类型> [选项]
类型:
xiaohongshu 小红书竖版卡片 (1080×1440)
jike 即刻方卡 (1080×1080)
poster 海报 (1080×1920)
选项:
--title 标题
--body 正文
--subtitle 副标题/元信息
--tag 标签
--quote 金句(小红书 quote 布局)
--emoji Emoji即刻
--details 详情(海报,可多个)
--cta 行动号召(海报)
--footer 底部文字
--layout 布局: default|quote|list|minimal
--style 海报风格: default|notice|recruit
--scheme 配色: guanghu|cream|night|green
--output 输出文件名
示例:
node generate.js xiaohongshu --title "如何高效学习" --body "第一点\\n第二点\\n第三点"
node generate.js jike --title "一个思考" --body "今天想到的..." --emoji "💡"
node generate.js poster --title "放假通知" --style notice --details "时间5月1日" "地点:广州"
`)
process.exit(0)
}
function getArg(key) {
const idx = args.indexOf(key)
if (idx === -1) return undefined
// 收集后续所有非 -- 开头的参数,直到遇到下一个 --
const values = []
for (let i = idx + 1; i < args.length; i++) {
if (args[i].startsWith('--')) break
values.push(args[i])
}
return values.length === 0 ? undefined
: values.length === 1 ? values[0]
: values
}
const opts = {
type,
title: getArg('--title') || '',
body: (() => {
const b = getArg('--body')
const text = Array.isArray(b) ? b.join('\n') : (b || '')
// 将 \n 转义符转为真实换行
return text.replace(/\\n/g, '\n')
})(),
subtitle: getArg('--subtitle') || '',
tag: getArg('--tag') || '',
quote: getArg('--quote') || '',
emoji: getArg('--emoji') || '',
footer: getArg('--footer') || '',
details: (() => {
const d = getArg('--details')
return Array.isArray(d) ? d : (d ? [d] : [])
})(),
cta: getArg('--cta') || '',
layout: getArg('--layout') || 'default',
style: getArg('--style') || 'default',
scheme: getArg('--scheme') || '',
output: getArg('--output') || '',
}
console.log(`🎨 生成${type}卡片...`)
const result = await generate(opts)
console.log(`✅ 生成完成!`)
result.files.forEach(f => console.log(` 📄 ${f}`))
}
// 如果直接运行
if (process.argv[1] && (process.argv[1].includes('generate.js'))) {
cli().catch(err => {
console.error('❌ 生成失败:', err.message)
process.exit(1)
})
}