Add resumable bulk lyrics operations

This commit is contained in:
Jonathan Sykes
2026-10-14 02:36:30 +08:00
parent 555027a61c
commit 703aef617b
25 changed files with 547 additions and 55 deletions

View File

@@ -683,7 +683,7 @@ export function registerNoteRoutes(app, deps) {
if (deps.analytics) registerAnalyticsRoutes(app, { adminAuth: requireAdminOrToken, runYtdlp, ...deps.analytics });
registerTranscriptionRoutes(app, {
const transcriptionService = registerTranscriptionRoutes(app, {
db, adminAuth: requireAdminOrToken, sanitizeLyrics,
workerEnabled: !!workerToken && workerToken.length >= 24,
workerAuth: async (c, next) => {
@@ -827,8 +827,36 @@ export function registerNoteRoutes(app, deps) {
console.warn('[notes] backup failed:', err.message);
}
}
async function pullLyricsForAdmin(id) {
if (badId(id)) return { ok: false, error: 'invalid video id' };
const existing = (await db.getNotes(id)).lyrics;
if (existing?.data?.lines?.length) return { ok: false, error: 'lyrics already exist; clear or edit them before pulling LRCLIB', rev: existing.rev };
let meta = {}, row = null;
if (id.startsWith('upl_')) {
const upload = await db.getUpload(id);
if (upload) meta = { title: upload.title, channel: upload.artist, album: upload.album, duration: upload.duration };
} else {
row = await db.getMedia(id);
try { meta = JSON.parse(row?.meta || '{}'); } catch {}
try {
const catalogRow = (await (db.db || db).execute({ sql: 'SELECT card FROM video_meta WHERE id=?', args: [id] })).rows[0];
meta = { ...JSON.parse(catalogRow?.card || '{}'), ...meta };
} catch {}
}
const title = cleanTitle(meta.title || ''), artist = cleanArtist(meta.channel || meta.artist || meta.uploader || ''), duration = Number(row?.duration || meta.duration || 0) || 0;
if (!title) return { ok: false, error: 'no title known for this video' };
const hit = await lrclibLookup({ title, artist, album: meta.album, duration });
if (!hit) return { ok: false, error: `no lyrics on LRCLIB for “${title}”${artist ? ` by ${artist}` : ''}` };
const data = sanitizeLyrics({ lines: hit.lines, tags: [hit.synced ? 'from LRCLIB (synced)' : 'from LRCLIB (plain text)'], offset: 0 });
const saved = await db.saveNote({ videoId: id, kind: 'lyrics', data, source: 'auto', updatedBy: 'admin bulk LRCLIB', force: true });
await db.autoResolveFlags(id, new Set(data.lines.map(line => line.text)));
await audit(db, 'admin', 'lyrics.save.lrclib', id, { rev: saved.rev, lines: data.lines.length, bulk: true });
return { ok: true, rev: saved.rev, lines: data.lines.length, synced: hit.synced };
}
return {
requireAdminOrToken,
pullLyricsForAdmin,
queueWhisperForAdmin: transcriptionService.enqueue,
startBackups() {
setTimeout(writeBackup, 60_000);
setInterval(writeBackup, 24 * 3600_000).unref?.();