Perf: Vollständiger Norm-Cache-Sync beim Start + Auto-Cache bei Upload/Import
- syncNormCache() beim Serverstart: normalisiert ALLE Sounds (nicht nur Top 50) und räumt verwaiste Cache-Dateien automatisch auf - DM-Upload: neue Datei wird sofort im Hintergrund normalisiert - URL-Import: Datei wird vor dem Abspielen normalisiert → direkt aus Cache - Detailliertes Logging: neu/vorhanden/fehlgeschlagen/verwaist + Dauer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
83b8f1acac
commit
68414ac257
1 changed files with 59 additions and 28 deletions
|
|
@ -181,7 +181,7 @@ function getNormCachePath(filePath: string): string | null {
|
||||||
function normalizeToCache(filePath: string): Promise<string> {
|
function normalizeToCache(filePath: string): Promise<string> {
|
||||||
const cacheFile = path.join(NORM_CACHE_DIR, normCacheKey(filePath));
|
const cacheFile = path.join(NORM_CACHE_DIR, normCacheKey(filePath));
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const ffArgs = ['-hide_banner', '-loglevel', 'error', '-i', filePath,
|
const ffArgs = ['-hide_banner', '-loglevel', 'error', '-y', '-i', filePath,
|
||||||
'-af', `loudnorm=I=${NORMALIZE_I}:LRA=${NORMALIZE_LRA}:TP=${NORMALIZE_TP}`,
|
'-af', `loudnorm=I=${NORMALIZE_I}:LRA=${NORMALIZE_LRA}:TP=${NORMALIZE_TP}`,
|
||||||
'-f', 's16le', '-ar', '48000', '-ac', '2', cacheFile];
|
'-f', 's16le', '-ar', '48000', '-ac', '2', cacheFile];
|
||||||
const ff = child_process.spawn('ffmpeg', ffArgs);
|
const ff = child_process.spawn('ffmpeg', ffArgs);
|
||||||
|
|
@ -193,6 +193,54 @@ function normalizeToCache(filePath: string): Promise<string> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vollständige Cache-Synchronisation:
|
||||||
|
* 1. Alle Sound-Dateien normalisieren, die noch nicht im Cache sind
|
||||||
|
* 2. Verwaiste Cache-Dateien löschen (Sound wurde gelöscht/umbenannt)
|
||||||
|
* Läuft im Hintergrund, blockiert nicht den Server.
|
||||||
|
*/
|
||||||
|
async function syncNormCache(): Promise<void> {
|
||||||
|
if (!NORMALIZE_ENABLE) return;
|
||||||
|
const t0 = Date.now();
|
||||||
|
const allSounds = listAllSounds();
|
||||||
|
|
||||||
|
// Set aller erwarteten Cache-Keys
|
||||||
|
const expectedKeys = new Set<string>();
|
||||||
|
let created = 0;
|
||||||
|
let skipped = 0;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
for (const s of allSounds) {
|
||||||
|
const fp = path.join(SOUNDS_DIR, s.relativePath);
|
||||||
|
const key = normCacheKey(fp);
|
||||||
|
expectedKeys.add(key);
|
||||||
|
if (!fs.existsSync(fp)) continue;
|
||||||
|
if (getNormCachePath(fp)) { skipped++; continue; } // bereits gecacht & gültig
|
||||||
|
try {
|
||||||
|
await normalizeToCache(fp);
|
||||||
|
created++;
|
||||||
|
} catch (e) {
|
||||||
|
failed++;
|
||||||
|
console.warn(`Norm-cache failed: ${s.relativePath}`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verwaiste Cache-Dateien aufräumen
|
||||||
|
let cleaned = 0;
|
||||||
|
try {
|
||||||
|
for (const f of fs.readdirSync(NORM_CACHE_DIR)) {
|
||||||
|
if (!expectedKeys.has(f)) {
|
||||||
|
try { fs.unlinkSync(path.join(NORM_CACHE_DIR, f)); cleaned++; } catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
const dt = ((Date.now() - t0) / 1000).toFixed(1);
|
||||||
|
console.log(
|
||||||
|
`Norm-Cache sync (${dt}s): ${created} neu, ${skipped} vorhanden, ${failed} fehlgeschlagen, ${cleaned} verwaist entfernt (${allSounds.length} Sounds gesamt)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Voice Abhängigkeiten prüfen ---
|
// --- Voice Abhängigkeiten prüfen ---
|
||||||
await sodium.ready;
|
await sodium.ready;
|
||||||
// init nacl to ensure it loads
|
// init nacl to ensure it loads
|
||||||
|
|
@ -622,6 +670,10 @@ client.on(Events.MessageCreate, async (message: Message) => {
|
||||||
if (!res.ok) throw new Error(`Download fehlgeschlagen: ${attachment.url}`);
|
if (!res.ok) throw new Error(`Download fehlgeschlagen: ${attachment.url}`);
|
||||||
const arrayBuffer = await res.arrayBuffer();
|
const arrayBuffer = await res.arrayBuffer();
|
||||||
fs.writeFileSync(targetPath, Buffer.from(arrayBuffer));
|
fs.writeFileSync(targetPath, Buffer.from(arrayBuffer));
|
||||||
|
// Sofort normalisieren für instant Play
|
||||||
|
if (NORMALIZE_ENABLE) {
|
||||||
|
normalizeToCache(targetPath).catch((e) => console.warn('Norm after upload failed:', e));
|
||||||
|
}
|
||||||
await message.author.send?.(`Sound gespeichert: ${path.basename(targetPath)}`);
|
await message.author.send?.(`Sound gespeichert: ${path.basename(targetPath)}`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -1328,6 +1380,10 @@ app.post('/api/play-url', async (req: Request, res: Response) => {
|
||||||
if (!r.ok) return res.status(400).json({ error: 'Download fehlgeschlagen' });
|
if (!r.ok) return res.status(400).json({ error: 'Download fehlgeschlagen' });
|
||||||
const buf = Buffer.from(await r.arrayBuffer());
|
const buf = Buffer.from(await r.arrayBuffer());
|
||||||
fs.writeFileSync(dest, buf);
|
fs.writeFileSync(dest, buf);
|
||||||
|
// Vor dem Abspielen normalisieren → sofort aus Cache
|
||||||
|
if (NORMALIZE_ENABLE) {
|
||||||
|
try { await normalizeToCache(dest); } catch (e) { console.warn('Norm after URL import failed:', e); }
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await playFilePath(guildId, channelId, dest, volume, path.basename(dest));
|
await playFilePath(guildId, channelId, dest, volume, path.basename(dest));
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -1352,33 +1408,8 @@ if (fs.existsSync(webDistPath)) {
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`Server läuft auf http://0.0.0.0:${PORT}`);
|
console.log(`Server läuft auf http://0.0.0.0:${PORT}`);
|
||||||
|
|
||||||
// Hintergrund-Warmup: häufigste Sounds vorab normalisieren, damit der erste Play sofort schnell ist
|
// Vollständige Cache-Synchronisation beim Start (Hintergrund)
|
||||||
if (NORMALIZE_ENABLE) {
|
syncNormCache();
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
const allSounds = listAllSounds();
|
|
||||||
// Sortiere nach Play-Count (häufigste zuerst), maximal 50 vorab cachen
|
|
||||||
const plays = persistedState.plays ?? {};
|
|
||||||
const sorted = [...allSounds].sort((a, b) => ((plays[b.relativePath] ?? 0) as number) - ((plays[a.relativePath] ?? 0) as number));
|
|
||||||
const toWarm = sorted.slice(0, 50);
|
|
||||||
let cached = 0;
|
|
||||||
for (const s of toWarm) {
|
|
||||||
const fp = path.join(SOUNDS_DIR, s.relativePath);
|
|
||||||
if (!fs.existsSync(fp)) continue;
|
|
||||||
if (getNormCachePath(fp)) continue; // schon gecacht
|
|
||||||
try {
|
|
||||||
await normalizeToCache(fp);
|
|
||||||
cached++;
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(`Warmup failed for ${s.relativePath}:`, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cached > 0) console.log(`Loudnorm-Warmup: ${cached} Sounds vorab normalisiert`);
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Loudnorm-Warmup error:', e);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue