// scripts/lyrics.mjs — 作曲填词 // // 主题 → 歌词 + 曲风 prompt + 结构标记(intro/verse/chorus/bridge/outro) // 走 chat 通道(实测可用)。产物落 work/song.json。 import { parseArgs, writeJSON, readJSON, paths, ensureWork, log, chatJSON, } from '../lib/index.mjs'; import { buildLyricsPrompt } from '../lib/prompts.mjs'; const SYSTEM = `你是资深华语流行音乐制作人兼作词人,为 AI 音乐生成模型(Suno 类)撰写可直接投喂的素材。 硬性要求: 1. 歌词必须自带结构标记,每段用方括号起头,例如 [Intro] [Verse 1] [Pre-Chorus] [Chorus] [Bridge] [Outro]。 2. Chorus 要朗朗上口、有记忆点,副歌句尾押韵(中文押韵即可,不必强求工整)。 3. 只输出 JSON,不要任何解释文字、不要 markdown 代码围栏。 4. stylePrompt 字段用英文逗号短语描述曲风/编曲/情绪,供音乐模型直接使用(例:"dreamy city pop, 88 bpm, warm analog synths, female vocal, lush reverb")。 5. 歌词内容必须为原创,不得抄袭任何已有歌曲的句子。`; const SCHEMA_HINT = `严格输出如下 JSON(字段名不可改): { "title": "歌名(4-10字,中文)", "genre": "曲风(中文,如:城市流行 City Pop)", "mood": "情绪关键词(中文,2-4个词)", "bpm": 92, "language": "zh", "structure": ["Intro","Verse 1","Pre-Chorus","Chorus","Verse 2","Chorus","Bridge","Chorus","Outro"], "lyrics": "[Intro]\\n(器乐)\\n\\n[Verse 1]\\n第一段歌词...\\n\\n[Chorus]\\n副歌...", "stylePrompt": "英文风格提示词,逗号分隔", "summary": "一句话说明这首歌讲什么(中文)" }`; export async function buildLyrics(theme, opts = {}) { const { genre = '', mood = '', style = '', language = '中文', extra = '', model } = opts; const userPrompt = buildLyricsPrompt({ theme, genre, mood, style, language, extra }) + '\n\n' + SCHEMA_HINT; const res = await chatJSON({ model, system: SYSTEM, messages: [{ role: 'user', content: userPrompt }], temperature: 0.9, max_tokens: 4096, }); const parsed = res.json; return { ...parsed, _meta: { model: res.model, createdBy: 'fmode-mv/lyrics', theme, maxTokensUsed: res.maxTokensUsed, truncated: !!res.repaired, }, }; } export async function main(argv = process.argv.slice(2)) { const { _, flags } = parseArgs(argv); const theme = _.join(' ').trim() || flags.theme; if (!theme || flags.help) { console.log(` fmode-mv lyrics — 作曲填词(chat 通道) 用法: fmode-mv lyrics "主题描述" [选项] 选项: --genre "曲风" 如 "city pop" / "国风民谣" / "电子舞曲" --mood "情绪" 如 "温暖治愈" / "孤独疏离" --style "补充要求" 如 "女声,钢琴为主" --lang zh|en 歌词语言(默认 zh) --model 覆盖 LLM 模型(默认 deepseek-v4-flash) --out 输出文件(默认 work/song.json) --json 只打印 JSON 示例: fmode-mv lyrics "深夜加班后独自回家的城市白领" --genre "city pop" --mood "孤独但温暖" `); return 0; } ensureWork(); log.step(`作曲填词:${theme}`); if (flags.genre) log.key('曲风', flags.genre); if (flags.mood) log.key('情绪', flags.mood); const song = await buildLyrics(theme, { genre: flags.genre, mood: flags.mood, style: flags.style, language: flags.lang === 'en' ? 'English' : '中文', model: flags.model, }); const outFile = typeof flags.out === 'string' ? flags.out : paths.song; writeJSON(outFile, song); if (flags.json) { console.log(JSON.stringify(song, null, 2)); return 0; } log.ok(`歌名:《${song.title}》`); log.key('曲风', `${song.genre || '-'} / ${song.bpm || '?'} BPM / ${song.mood || '-'}`); log.key('结构', (song.structure || []).join(' → ')); log.key('风格 prompt', song.stylePrompt || '-'); console.log(''); console.log(song.lyrics || '(空)'); console.log(''); log.ok(`已写入 ${outFile}`); // 自检:歌词结构标记 & 副歌存在 const lyr = String(song.lyrics || ''); const issues = []; if (!/\[Verse|\[verse|\[主歌/.test(lyr)) issues.push('缺少 Verse 段落标记'); if (!/\[Chorus|\[chorus|\[副歌/.test(lyr)) issues.push('缺少 Chorus 段落标记'); if (lyr.replace(/\[[^\]]*\]/g, '').trim().length < 60) issues.push('歌词正文过短'); if (!song.stylePrompt) issues.push('缺少 stylePrompt(音乐模型需要)'); if (issues.length) { log.warn(`歌词自检提示:${issues.join(';')}`); } else { log.ok('歌词自检通过(含主歌/副歌结构标记 + 风格 prompt)'); } return 0; } if (import.meta.url === `file://${process.argv[1]}`) { main().then((c) => process.exit(Number(c) || 0)).catch((e) => { console.error(`✗ ${e.message}`); process.exit(1); }); }