guanghulab/image-studio/generate.js

199 lines
6.4 KiB
JavaScript
Raw Normal View History

#!/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)
})
}