From 3f71769ec61e4887ccf2ef98e54337462770c147 Mon Sep 17 00:00:00 2001 From: diced Date: Sat, 9 May 2026 17:18:14 -0700 Subject: [PATCH] fix: #1069 --- src/lib/tasks/run/thumbnails.ts | 4 +++- src/offload/thumbnails.ts | 35 +++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/lib/tasks/run/thumbnails.ts b/src/lib/tasks/run/thumbnails.ts index 21780db0..bc57dc25 100644 --- a/src/lib/tasks/run/thumbnails.ts +++ b/src/lib/tasks/run/thumbnails.ts @@ -4,7 +4,8 @@ export function runThumbnailWorkers(workers: WorkerTask[], files: string[]) { const thumbToWorker: { id: string; worker: number }[] = []; let workerIndex = 0; - for (const file of files) { + const unique = new Set(files); + for (const file of unique) { thumbToWorker.push({ id: file, worker: workerIndex, @@ -42,6 +43,7 @@ export default function thumbnails(prisma: typeof globalThis.__db__) { type: { startsWith: 'video/', }, + size: { gt: 0 }, }, }); if (!thumbnailNeeded.length) return; diff --git a/src/offload/thumbnails.ts b/src/offload/thumbnails.ts index 57963279..e18a4911 100644 --- a/src/offload/thumbnails.ts +++ b/src/offload/thumbnails.ts @@ -4,6 +4,7 @@ import { getDatasource } from '@/lib/datasource'; import { Datasource } from '@/lib/datasource/Datasource'; import type { File } from '@/lib/db/models/file'; import { log } from '@/lib/logger'; +import { randomCharacters } from '@/lib/random'; import ffmpeg from 'fluent-ffmpeg'; import { createWriteStream, existsSync, readFileSync, unlinkSync } from 'fs'; import { join } from 'path'; @@ -40,6 +41,8 @@ const formatMimes = { webp: 'image/webp', }; +const workerId = randomCharacters(8); + function name(str: string) { return `${str}.${config.features.thumbnails.format}`; } @@ -63,6 +66,7 @@ function genThumbnail(input: string, output: string): Promise { stream.pipe(writeStream); @@ -116,15 +130,14 @@ async function generate(config: Config, datasource: Datasource, ids: string[]) { writeStream.on('finish', resolve as any); }); - const thumbnailTmpFile = join(config.core.tempDirectory, name(`zthumbnail_${file.id}`)); + const thumbnailTmpFile = join(config.core.tempDirectory, name(`zthumbnail_${file.id}_${workerId}`)); const thumbnail = await genThumbnail(tmpFile, thumbnailTmpFile); - if (!thumbnail) return; + if (!thumbnail || thumbnail.length === 0) continue; const existing = await datasource.size(name(`.thumbnail.${file.id}`)); if (existing || existing === 0) { await datasource.delete(name(`.thumbnail.${file.id}`)); } - await datasource.put(name(`.thumbnail.${file.id}`), thumbnail, { mimetype: formatMimes[config.features.thumbnails.format] || 'image/jpeg', }); @@ -172,7 +185,13 @@ async function main() { switch (type) { case 0: logger.debug('received thumbnail generation request', { ids: data }); - await generate(config, datasource, data!); + try { + await generate(config, datasource, data!); + } catch (err) { + logger.error('thumbnail generation failed', { + err: err instanceof Error ? err.message : String(err), + }); + } break; case 1: logger.debug('received kill request');