Loading html/admin.html +6 −1 Original line number Diff line number Diff line Loading @@ -191,7 +191,7 @@ function drainPreviewQueue() { previewActive++; const ctrl = new AbortController(); job.img._previewAbort = ctrl; fetch(job.url, {credentials:'same-origin', signal: ctrl.signal}).then(r => { fetch(job.url, {credentials:'same-origin', headers:{'Authorization':'Bearer '+token}, signal: ctrl.signal}).then(r => { if (r.status === 200) { return r.blob().then(blob => { job.img.src = URL.createObjectURL(blob); Loading @@ -200,6 +200,11 @@ function drainPreviewQueue() { } else if (r.status === 503) { // Server busy — re-queue with delay setTimeout(() => { previewQueue.push(job); drainPreviewQueue(); }, 2000); } else { r.text().then(t => { try { const e = JSON.parse(t); console.warn('preview', job.url, r.status, e.error); } catch(_){} }); job.img.alt = '\u26a0'; job.img.style.opacity = '.5'; job.img.title = 'Preview failed (HTTP ' + r.status + ')'; } }).catch(() => {}).finally(() => { previewActive--; drainPreviewQueue(); }); } Loading src/preview.cpp +5 −2 Original line number Diff line number Diff line Loading @@ -695,8 +695,11 @@ std::optional<PreviewResult> FFmpegPreviewer::encode_frame(AVFrame* frame, const PreviewService::PreviewService(BlobCache& cache, MediaBackendApi& db, std::size_t render_threads) : cache_(cache), db_(db), render_sem_(render_threads > 0 ? render_threads : std::max(1u, std::thread::hardware_concurrency())), io_pool_(std::max(4u, std::min(16u, std::thread::hardware_concurrency()))) {} io_pool_(std::max(4u, std::min(16u, std::thread::hardware_concurrency()))), render_sem_(std::max<std::size_t>(1, render_threads > 0 ? std::min<std::size_t>(render_threads, io_pool_.size() - 1) : io_pool_.size() - std::max<std::size_t>(2, io_pool_.size() / 3))) {} std::string PreviewService::make_key(const std::string& media_id, int width, int height, const std::string& fmt, Loading src/preview.h +2 −2 Original line number Diff line number Diff line Loading @@ -82,8 +82,8 @@ private: BlobCache& cache_; MediaBackendApi& db_; FFmpegPreviewer ffmpeg_; std::counting_semaphore<256> render_sem_; // limits concurrent FFmpeg renders ThreadPool io_pool_; // bounded pool for prefetch I/O (prevents DoS via thread explosion) ThreadPool io_pool_; // bounded pool for render + prefetch I/O std::counting_semaphore<256> render_sem_; // limits concurrent renders; must be < io_pool_ size to leave threads for prefetch std::mutex inflight_mutex_; std::condition_variable inflight_cv_; std::unordered_set<std::string> inflight_; Loading Loading
html/admin.html +6 −1 Original line number Diff line number Diff line Loading @@ -191,7 +191,7 @@ function drainPreviewQueue() { previewActive++; const ctrl = new AbortController(); job.img._previewAbort = ctrl; fetch(job.url, {credentials:'same-origin', signal: ctrl.signal}).then(r => { fetch(job.url, {credentials:'same-origin', headers:{'Authorization':'Bearer '+token}, signal: ctrl.signal}).then(r => { if (r.status === 200) { return r.blob().then(blob => { job.img.src = URL.createObjectURL(blob); Loading @@ -200,6 +200,11 @@ function drainPreviewQueue() { } else if (r.status === 503) { // Server busy — re-queue with delay setTimeout(() => { previewQueue.push(job); drainPreviewQueue(); }, 2000); } else { r.text().then(t => { try { const e = JSON.parse(t); console.warn('preview', job.url, r.status, e.error); } catch(_){} }); job.img.alt = '\u26a0'; job.img.style.opacity = '.5'; job.img.title = 'Preview failed (HTTP ' + r.status + ')'; } }).catch(() => {}).finally(() => { previewActive--; drainPreviewQueue(); }); } Loading
src/preview.cpp +5 −2 Original line number Diff line number Diff line Loading @@ -695,8 +695,11 @@ std::optional<PreviewResult> FFmpegPreviewer::encode_frame(AVFrame* frame, const PreviewService::PreviewService(BlobCache& cache, MediaBackendApi& db, std::size_t render_threads) : cache_(cache), db_(db), render_sem_(render_threads > 0 ? render_threads : std::max(1u, std::thread::hardware_concurrency())), io_pool_(std::max(4u, std::min(16u, std::thread::hardware_concurrency()))) {} io_pool_(std::max(4u, std::min(16u, std::thread::hardware_concurrency()))), render_sem_(std::max<std::size_t>(1, render_threads > 0 ? std::min<std::size_t>(render_threads, io_pool_.size() - 1) : io_pool_.size() - std::max<std::size_t>(2, io_pool_.size() / 3))) {} std::string PreviewService::make_key(const std::string& media_id, int width, int height, const std::string& fmt, Loading
src/preview.h +2 −2 Original line number Diff line number Diff line Loading @@ -82,8 +82,8 @@ private: BlobCache& cache_; MediaBackendApi& db_; FFmpegPreviewer ffmpeg_; std::counting_semaphore<256> render_sem_; // limits concurrent FFmpeg renders ThreadPool io_pool_; // bounded pool for prefetch I/O (prevents DoS via thread explosion) ThreadPool io_pool_; // bounded pool for render + prefetch I/O std::counting_semaphore<256> render_sem_; // limits concurrent renders; must be < io_pool_ size to leave threads for prefetch std::mutex inflight_mutex_; std::condition_variable inflight_cv_; std::unordered_set<std::string> inflight_; Loading