// 导出《天下·乐 全库总稿》——所有 1303 首,每首:编号(=文件名)/歌名/家族/版本/词/背景/Suno风格/1分钟截取。 // 按家族(战役/名场面/她传/他传)+诗词(按年代)分。Word 可直接打开(.doc)。 import { readFileSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const BK = path.resolve(__dirname, '..'); const REPO = path.resolve(BK, '..'); const LIB = JSON.parse(readFileSync(BK + '/src/data/music-library.json', 'utf8')); const shiciSrc = readFileSync(REPO + '/digital-me-web/src/data/shici.ts', 'utf8'); const SHI = JSON.parse(shiciSrc.match(/export const SHI: Shi\[\] = (\[[\s\S]*\]);/)[1]); const ERAS = JSON.parse(shiciSrc.match(/export const SHI_ERAS:[^=]*= (\[[\s\S]*?\]);/)[1]); const shiById = {}; for (const p of SHI) shiById[p.id] = p; const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&').replace(//g, '>'); const br = (s) => esc(s).replace(/\n/g, '
'); const fid = (id) => id.replace(/[^a-z0-9]+/gi, '_'); const verLabel = (v) => v === 'instrumental' ? '纯配乐' : v === 'male' ? '男声' : v === 'female' ? '女声' : '化用曲'; const VORD = { instrumental: 0, male: 1, female: 2, '': 3 }; const today = new Date().toISOString().slice(0, 10); function songBlock(s, idx) { const src = s.id.startsWith('shici:') ? shiById[s.id.slice(6)] : null; return `
${idx} ${esc(s.title)} ${verLabel(s.vocal)}${s.who ? ` 视角/主角:${esc(s.who)}` : ''}
编号(文件名):${esc(fid(s.id))}  → Suno 出曲后用此名(.mp3)发回即自动载入
${src ? `
原诗${esc(src.author)} · ${esc(src.dynasty)}
${br(src.text)}
${src.background ? `
背景${br(src.background)}
` : ''} ${src.kao ? `
考证${br(src.kao)}
` : ''}` : ''}
词
${br(s.lyrics)}
Suno风格
${br(s.sunoStyle)}
${s.clip ? `
1分钟截取:${br(s.clip)}
` : ''}
`; } let body = ''; let n = 0; // 锚点家族 for (const fam of ['战役', '名场面', '她传', '他传']) { const rows = LIB.filter((s) => s.family === fam); if (!rows.length) continue; const bases = []; const bm = {}; for (const s of rows) { const b = s.id.split('#')[0]; if (!bm[b]) { bm[b] = []; bases.push(b); } bm[b].push(s); } body += `

${esc(fam)} ${bases.length} 个 · ${rows.length} 首

`; for (const b of bases) { const vs = bm[b].sort((a, c) => (VORD[a.vocal] ?? 9) - (VORD[c.vocal] ?? 9)); body += `

${esc(vs[0].title)}

`; for (const s of vs) { n++; body += songBlock(s, n); } } } // 诗词按年代 const shici = LIB.filter((s) => s.id.startsWith('shici:')); if (shici.length) { body += `

诗词 ${shici.length} 首

`; for (const era of ERAS) { const rows = shici.filter((s) => { const p = shiById[s.id.slice(6)]; return p && p.eraSlug === era.slug; }); if (!rows.length) continue; body += `

${esc(era.label)} · ${rows.length} 首

`; for (const s of rows) { n++; body += songBlock(s, n); } } } const html = ` 天下·乐 全库总稿

天下 · 乐  全库总稿

共 ${LIB.length} 首 · 母版「新东方·电影史诗·黯黑」· 生成 ${today}
每首含 编号(=文件名) / 歌名 / 词 / 背景 / Suno风格 / 1分钟截取。
命名闭环:Suno 出曲后用「编号」当文件名(.mp3)发回 → 超管上传页按名自动载入天下音乐。
⚠ 本稿含 Suno 风格指令(制作配方),仅你自己看/喂 Suno,勿外传。
${body} `; const OUT = 'C:/Users/solut/Downloads/天下乐-全库总稿.doc'; writeFileSync(OUT, '\uFEFF' + html, 'utf8'); console.log('已导出:', OUT, '| 共', n, '首');