Backfill device song metadata and queue guarded server caching

This commit is contained in:
Jonathan Sykes
2026-10-03 17:55:49 +08:00
parent 9e960cf70f
commit 856ddda6a6
8 changed files with 187 additions and 7 deletions

View File

@@ -1,8 +1,9 @@
// One catalog for every discovery source. Media-file caching is independent.
import { db } from './db.js';
import { mergeMissing } from './media-meta-core.js';
const VIDEO_ID = /^[\w-]{11}$/;
const SOURCES = new Set(['search', 'search-cache', 'client-search', 'channel', 'streams', 'playlist', 'profile', 'sync', 'related', 'backfill', 'collector']);
const SOURCES = new Set(['search', 'search-cache', 'client-search', 'channel', 'streams', 'playlist', 'profile', 'sync', 'related', 'backfill', 'collector', 'device-play']);
const text = (v, n = 300) => typeof v === 'string' ? v.trim().slice(0, n) : '';
const positive = v => Number.isFinite(Number(v)) && Number(v) > 0 ? Number(v) : 0;
const canonicalThumb = id => `https://i.ytimg.com/vi/${id}/hqdefault.jpg`;
@@ -48,13 +49,13 @@ export function extractCards(value, limit = 5000) {
let ingestion = Promise.resolve();
let writes = 0;
export function ingest(cards, source = 'search') {
const task = ingestion.then(() => ingestBatch(cards, source));
export function ingest(cards, source = 'search', { fillMissing = false } = {}) {
const task = ingestion.then(() => ingestBatch(cards, source, fillMissing));
ingestion = task.catch(() => {});
return task;
}
async function ingestBatch(cards, source) {
async function ingestBatch(cards, source, fillMissing) {
source = SOURCES.has(source) ? source : 'playlist';
const unique = extractCards(cards);
const now = Date.now();
@@ -65,7 +66,7 @@ async function ingestBatch(cards, source) {
const old = new Map(existing.rows.map(r => [r.id, JSON.parse(r.card)]));
await db.batch(batch.flatMap(c => {
// Sparse playlist cards must not erase richer extractor metadata.
const merged = { ...(old.get(c.id) || {}), ...Object.fromEntries(Object.entries(c).filter(([, v]) => v !== '' && v !== 0 && (!Array.isArray(v) || v.length))) };
const merged = fillMissing ? mergeMissing(old.get(c.id) || {}, c) : { ...(old.get(c.id) || {}), ...Object.fromEntries(Object.entries(c).filter(([, v]) => v !== '' && v !== 0 && (!Array.isArray(v) || v.length))) };
return [{ sql: `INSERT INTO video_meta (id,card,hay,seen,updated_at) VALUES (?,?,?,1,?)
ON CONFLICT(id) DO UPDATE SET card=excluded.card,hay=excluded.hay,seen=seen+1,updated_at=excluded.updated_at`,
args: [c.id, JSON.stringify(merged), `${merged.title} ${merged.channel || ''} ${(merged.tags || []).join(' ')}`.toLowerCase(), now] },