97 lines
6.3 KiB
JavaScript
97 lines
6.3 KiB
JavaScript
// 导出《天下·乐 全库总稿》——所有 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, '<').replace(/>/g, '>');
|
||
const br = (s) => esc(s).replace(/\n/g, '<br/>');
|
||
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 `<div class="song">
|
||
<div class="sh"><span class="idx">${idx}</span> <b class="tt">${esc(s.title)}</b> <span class="ver">${verLabel(s.vocal)}</span>${s.who ? ` <span class="who">视角/主角:${esc(s.who)}</span>` : ''}</div>
|
||
<div class="fn">编号(文件名):<code>${esc(fid(s.id))}</code> → Suno 出曲后用此名(.mp3)发回即自动载入</div>
|
||
${src ? `<div class="sec"><span class="lb">原诗</span><span class="src">${esc(src.author)} · ${esc(src.dynasty)}</span><div class="text">${br(src.text)}</div></div>
|
||
${src.background ? `<div class="sec"><span class="lb">背景</span><span class="v">${br(src.background)}</span></div>` : ''}
|
||
${src.kao ? `<div class="sec"><span class="lb">考证</span><span class="v">${br(src.kao)}</span></div>` : ''}` : ''}
|
||
<div class="sec"><span class="lb">词</span><div class="lyr">${br(s.lyrics)}</div></div>
|
||
<div class="sec"><span class="lb suno">Suno风格</span><div class="suno">${br(s.sunoStyle)}</div>${s.clip ? `<div class="clip">1分钟截取:${br(s.clip)}</div>` : ''}</div>
|
||
</div>`;
|
||
}
|
||
|
||
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 += `<h2>${esc(fam)} <span class="cnt">${bases.length} 个 · ${rows.length} 首</span></h2>`;
|
||
for (const b of bases) {
|
||
const vs = bm[b].sort((a, c) => (VORD[a.vocal] ?? 9) - (VORD[c.vocal] ?? 9));
|
||
body += `<h3>${esc(vs[0].title)}</h3>`;
|
||
for (const s of vs) { n++; body += songBlock(s, n); }
|
||
}
|
||
}
|
||
// 诗词按年代
|
||
const shici = LIB.filter((s) => s.id.startsWith('shici:'));
|
||
if (shici.length) {
|
||
body += `<h2>诗词 <span class="cnt">${shici.length} 首</span></h2>`;
|
||
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 += `<h3 class="era">${esc(era.label)} · ${rows.length} 首</h3>`;
|
||
for (const s of rows) { n++; body += songBlock(s, n); }
|
||
}
|
||
}
|
||
|
||
const html = `<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-html40"><head><meta charset="utf-8"/>
|
||
<title>天下·乐 全库总稿</title>
|
||
<style>
|
||
body{ font-family:"宋体","SimSun","Microsoft YaHei",serif; color:#1e1a15; line-height:1.7; font-size:11pt; }
|
||
h1{ font-size:22pt; text-align:center; color:#7a1f16; margin:0 0 3pt; }
|
||
.meta{ text-align:center; color:#888; font-size:9pt; margin-bottom:6pt; }
|
||
.legend{ text-align:center; color:#8a6d33; font-size:9pt; margin-bottom:18pt; }
|
||
h2{ font-size:15pt; color:#9e2b1e; border-bottom:2px solid #9e2b1e; padding-bottom:3pt; margin:22pt 0 8pt; }
|
||
h2 .cnt{ font-size:9pt; color:#999; font-weight:normal; }
|
||
h3{ font-size:12.5pt; color:#7a5a20; margin:14pt 0 6pt; }
|
||
h3.era{ color:#9e2b1e; }
|
||
.song{ border:1px solid #ddd; border-radius:4px; padding:9pt 11pt; margin:0 0 10pt; page-break-inside:avoid; }
|
||
.sh{ font-size:12pt; border-bottom:1px dashed #ccc; padding-bottom:3pt; margin-bottom:5pt; }
|
||
.idx{ color:#9e2b1e; font-weight:bold; }
|
||
.tt{ font-size:12.5pt; } .ver{ color:#2c6e49; font-size:9pt; margin-left:5pt; } .who{ color:#999; font-size:9pt; margin-left:5pt; }
|
||
.fn{ font-size:9pt; color:#666; margin-bottom:5pt; }
|
||
.fn code{ font-family:Consolas,monospace; background:#eee; padding:0 4pt; color:#7a1f16; }
|
||
.sec{ margin:5pt 0; }
|
||
.lb{ display:inline-block; background:#f2ead8; color:#8a6d33; font-size:8.5pt; padding:1pt 6pt; border-radius:3px; margin-right:6pt; }
|
||
.lb.suno{ background:#e7efe8; color:#2c6e49; }
|
||
.src{ color:#8a6d33; font-size:9pt; }
|
||
.text{ padding-left:10pt; color:#444; margin-top:3pt; }
|
||
.v{ color:#444; font-size:10pt; }
|
||
.lyr{ margin-top:3pt; padding:6pt 10pt; background:#faf7ef; border-left:3px solid #c8a25e; color:#2a2a2a; font-size:10.5pt; }
|
||
.suno{ margin-top:3pt; padding:6pt 10pt; background:#f3f8f4; border-left:3px solid #2c6e49; color:#234; font-size:9.5pt; font-family:Consolas,"Courier New",monospace; }
|
||
.clip{ color:#2c6e49; font-size:9pt; margin-top:3pt; }
|
||
</style></head><body>
|
||
<h1>天下 · 乐 全库总稿</h1>
|
||
<div class="meta">共 ${LIB.length} 首 · 母版「新东方·电影史诗·黯黑」· 生成 ${today}</div>
|
||
<div class="legend">每首含 编号(=文件名) / 歌名 / 词 / 背景 / Suno风格 / 1分钟截取。<br/>命名闭环:Suno 出曲后用「编号」当文件名(.mp3)发回 → 超管上传页按名自动载入天下音乐。<br/>⚠ 本稿含 Suno 风格指令(制作配方),仅你自己看/喂 Suno,勿外传。</div>
|
||
${body}
|
||
</body></html>`;
|
||
|
||
const OUT = 'C:/Users/solut/Downloads/天下乐-全库总稿.doc';
|
||
writeFileSync(OUT, '\uFEFF' + html, 'utf8');
|
||
console.log('已导出:', OUT, '| 共', n, '首');
|