Files
admin 0593ff7e62
nginx serve /uploads direct (audio speed) / patch-nginx (push) Failing after 1m34s
数字我 Digital Me:全量入库(大文件走 Git LFS)
2026-10-10 16:24:43 +08:00

97 lines
6.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 导出《天下·乐 全库总稿》——所有 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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> &nbsp;→ 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>天下 · 乐 &nbsp;全库总稿</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, '首');