Process all pending movies in IMDb backfill instead of capping at 200.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
a9df10ed7c
commit
fc842aa145
5 changed files with 45 additions and 36 deletions
|
|
@ -70,7 +70,7 @@ export default function DashboardPage() {
|
||||||
async function backfillImdb() {
|
async function backfillImdb() {
|
||||||
if (
|
if (
|
||||||
!confirm(
|
!confirm(
|
||||||
"IMDb-IDs ophalen via TMDB voor films die die nog missen? Nodig zodat Cinemeta jouw lokale bronnen toont."
|
"IMDb-IDs ophalen via TMDB voor ALLE films die die nog missen? Dit kan enkele minuten duren bij een grote bibliotheek. Nodig zodat Cinemeta jouw lokale bronnen toont."
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -86,7 +86,7 @@ export default function DashboardPage() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setImdbMsg(
|
setImdbMsg(
|
||||||
`Gecontroleerd: ${json.checked ?? 0}, bijgewerkt: ${json.updated ?? 0}, nog open: ${json.skipped ?? 0}`
|
`Gecontroleerd: ${json.checked ?? 0}, bijgewerkt: ${json.updated ?? 0}, nog open: ${json.remaining ?? json.skipped ?? 0}, mislukt: ${json.failed ?? 0}`
|
||||||
);
|
);
|
||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -445,7 +445,8 @@ export async function registerAdminRoutes(app: FastifyInstance, config: Config)
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/api/v1/admin/library/backfill-imdb", { preHandler: requireAdmin }, async () => {
|
app.post("/api/v1/admin/library/backfill-imdb", { preHandler: requireAdmin }, async () => {
|
||||||
const result = await metadata.backfillMovieImdbIds({ delayMs: 150, limit: 500 });
|
// Process ALL pending movies (batched). Can take a few minutes for large libraries.
|
||||||
|
const result = await metadata.backfillMovieImdbIds({ delayMs: 120 });
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -126,16 +126,16 @@ async function main() {
|
||||||
await app.listen({ port: config.PORT, host: config.HOST });
|
await app.listen({ port: config.PORT, host: config.HOST });
|
||||||
console.log(`Master API listening on ${config.HOST}:${config.PORT}`);
|
console.log(`Master API listening on ${config.HOST}:${config.PORT}`);
|
||||||
|
|
||||||
// Background: fill missing movie imdbIds so Cinemeta detail pages can resolve local streams.
|
// Background: fill ALL missing movie imdbIds (batched) so Cinemeta can resolve local streams.
|
||||||
if (config.TMDB_API_KEY) {
|
if (config.TMDB_API_KEY) {
|
||||||
const metadata = new MetadataService(config.TMDB_API_KEY);
|
const metadata = new MetadataService(config.TMDB_API_KEY);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
void metadata
|
void metadata
|
||||||
.backfillMovieImdbIds({ delayMs: 200, limit: 200 })
|
.backfillMovieImdbIds({ delayMs: 120 })
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
if (r.checked > 0) {
|
if (r.checked > 0) {
|
||||||
console.log(
|
console.log(
|
||||||
`IMDb backfill: checked=${r.checked} updated=${r.updated} remaining=${r.skipped}`
|
`IMDb backfill: checked=${r.checked} updated=${r.updated} remaining=${r.remaining} failed=${r.failed}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -666,50 +666,58 @@ export class MetadataService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill missing imdbId for all movies that have a TMDB id.
|
* Fill missing imdbId for movies that have a TMDB id.
|
||||||
|
* Processes in batches until none remain (or `limit` is hit).
|
||||||
* Rate-limited to avoid TMDB bans. Safe for series (movies only).
|
* Rate-limited to avoid TMDB bans. Safe for series (movies only).
|
||||||
*/
|
*/
|
||||||
async backfillMovieImdbIds(options?: {
|
async backfillMovieImdbIds(options?: {
|
||||||
delayMs?: number;
|
delayMs?: number;
|
||||||
|
/** Max movies to process this run. Omit / 0 = all pending. */
|
||||||
limit?: number;
|
limit?: number;
|
||||||
}): Promise<{ checked: number; updated: number; skipped: number; failed: number }> {
|
batchSize?: number;
|
||||||
const delayMs = options?.delayMs ?? 200;
|
}): Promise<{ checked: number; updated: number; remaining: number; failed: number }> {
|
||||||
const limit = options?.limit ?? 500;
|
const delayMs = options?.delayMs ?? 150;
|
||||||
|
const batchSize = Math.min(Math.max(options?.batchSize ?? 100, 1), 500);
|
||||||
|
const maxTotal = options?.limit && options.limit > 0 ? options.limit : Number.POSITIVE_INFINITY;
|
||||||
if (!this.tmdbApiKey) {
|
if (!this.tmdbApiKey) {
|
||||||
return { checked: 0, updated: 0, skipped: 0, failed: 0 };
|
return { checked: 0, updated: 0, remaining: 0, failed: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const pending = await prisma.movie.findMany({
|
let checked = 0;
|
||||||
where: { tmdbId: { not: null }, imdbId: null },
|
|
||||||
select: { id: true, title: true, tmdbId: true },
|
|
||||||
take: limit,
|
|
||||||
orderBy: { updatedAt: "asc" },
|
|
||||||
});
|
|
||||||
|
|
||||||
let updated = 0;
|
let updated = 0;
|
||||||
let failed = 0;
|
let failed = 0;
|
||||||
for (const row of pending) {
|
|
||||||
try {
|
while (checked < maxTotal) {
|
||||||
const ok = await this.ensureMovieImdbId(row.id);
|
const take = Math.min(batchSize, maxTotal - checked);
|
||||||
if (ok) updated += 1;
|
const pending = await prisma.movie.findMany({
|
||||||
else failed += 1;
|
where: { tmdbId: { not: null }, imdbId: null },
|
||||||
} catch {
|
select: { id: true },
|
||||||
failed += 1;
|
take,
|
||||||
}
|
orderBy: { updatedAt: "asc" },
|
||||||
if (delayMs > 0) {
|
});
|
||||||
await new Promise((r) => setTimeout(r, delayMs));
|
if (pending.length === 0) break;
|
||||||
|
|
||||||
|
for (const row of pending) {
|
||||||
|
try {
|
||||||
|
const ok = await this.ensureMovieImdbId(row.id);
|
||||||
|
if (ok) updated += 1;
|
||||||
|
else failed += 1;
|
||||||
|
} catch {
|
||||||
|
failed += 1;
|
||||||
|
}
|
||||||
|
checked += 1;
|
||||||
|
if (delayMs > 0) {
|
||||||
|
await new Promise((r) => setTimeout(r, delayMs));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pending.length < take) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stillMissing = await prisma.movie.count({
|
const remaining = await prisma.movie.count({
|
||||||
where: { tmdbId: { not: null }, imdbId: null },
|
where: { tmdbId: { not: null }, imdbId: null },
|
||||||
});
|
});
|
||||||
return {
|
return { checked, updated, remaining, failed };
|
||||||
checked: pending.length,
|
|
||||||
updated,
|
|
||||||
skipped: stillMissing,
|
|
||||||
failed,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async absorbOrphanMovies(
|
private async absorbOrphanMovies(
|
||||||
|
|
|
||||||
|
|
@ -603,7 +603,7 @@ async function getStreams(
|
||||||
);
|
);
|
||||||
// Do not block this request — fill IMDb ids in background for next try.
|
// Do not block this request — fill IMDb ids in background for next try.
|
||||||
if (isImdb && missingImdb > 0) {
|
if (isImdb && missingImdb > 0) {
|
||||||
void metadata.backfillMovieImdbIds({ delayMs: 150, limit: 80 }).catch(() => undefined);
|
void metadata.backfillMovieImdbIds({ delayMs: 120, limit: 30 }).catch(() => undefined);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue