import { randomUUID } from "crypto"; import { prisma } from "../database/client"; import { nodeConnectionManager } from "../websocket/manager"; import type { SubtitleHit } from "../opensubtitles/client"; type LocalSubCacheEntry = { contentBase64: string; fileName: string; format: string; language: string; mediaFileId: string; expiresAt: number; }; const localSubCache = new Map(); const CACHE_TTL_MS = 30 * 60_000; export function stableLocalFileId(key: string): number { let h = 2166136261; for (let i = 0; i < key.length; i++) { h ^= key.charCodeAt(i); h = Math.imul(h, 16777619); } const n = Math.abs(h) % 2_000_000_000; return n === 0 ? 1 : n; } function putLocalCache(fileId: number, entry: Omit) { localSubCache.set(fileId, { ...entry, expiresAt: Date.now() + CACHE_TTL_MS }); } export function getLocalSubtitleCache(fileId: number): LocalSubCacheEntry | null { const hit = localSubCache.get(fileId); if (!hit) return null; if (Date.now() > hit.expiresAt) { localSubCache.delete(fileId); return null; } return hit; } /** Ask node for sidecar .srt/.ass next to the video; cache + upsert library_subtitles. */ export async function discoverSidecarSubtitles(mediaFileId: string): Promise { const file = await prisma.mediaFile.findUnique({ where: { id: mediaFileId }, include: { movie: { select: { id: true, tmdbId: true, imdbId: true } }, episode: { select: { imdbId: true, series: { select: { imdbId: true, tmdbId: true } }, }, }, }, }); if (!file || !nodeConnectionManager.isOnline(file.nodeId)) return []; const requestId = randomUUID(); const ack = await nodeConnectionManager.findSidecarSubtitlesAcked( file.nodeId, { requestId, localFileId: file.localFileId }, 15_000 ); if (!ack.ok || !ack.subtitles?.length) return []; const hits: SubtitleHit[] = []; for (const sub of ack.subtitles) { const format = (sub.format || "srt").toLowerCase(); const language = (sub.language || "nl").toLowerCase(); const fileName = sub.fileName || `local.${format}`; const fileId = stableLocalFileId(`${mediaFileId}:${fileName}`); putLocalCache(fileId, { contentBase64: sub.contentBase64, fileName, format, language, mediaFileId, }); // Persist for Stremio stream.subtitles try { const content = Buffer.from(sub.contentBase64, "base64").toString("utf8"); if (content.trim()) { const movieId = file.movieId ?? null; const tmdbId = file.movie?.tmdbId ?? file.episode?.series?.tmdbId ?? null; const imdbId = file.movie?.imdbId || file.episode?.imdbId || file.episode?.series?.imdbId || null; const linkFilter = [ ...(movieId ? [{ movieId }] : []), ...(tmdbId != null ? [{ tmdbId }] : []), ...(imdbId ? [{ imdbId }] : []), ]; const existing = linkFilter.length > 0 ? await prisma.librarySubtitle.findFirst({ where: { fileName, OR: linkFilter }, }) : await prisma.librarySubtitle.findFirst({ where: { fileName, content }, }); if (existing) { await prisma.librarySubtitle.update({ where: { id: existing.id }, data: { content, language, format, updatedAt: new Date() }, }); } else { await prisma.librarySubtitle.create({ data: { movieId, tmdbId, imdbId, language, format, content, fileName, }, }); } } } catch (err) { console.warn("[sidecars] library_subtitles upsert:", err); } hits.push({ fileId, language, release: `Lokaal ยท ${fileName}`, fileName, downloadCount: 9_999_999, hearingImpaired: /\b(sdh|hi|hearing)\b/i.test(fileName), fps: null, uploader: "lokaal", source: "local", }); } return hits; }