// 导出《天下·乐 全库总稿》——所有 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)}
` : ''}` : ''}
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, '首');