Compare commits

...

2 Commits

Author SHA1 Message Date
1eeecbf275 Merge pull request 'feat: Update video thumbnail handling in viewer frontend' (#10) from fix/viewer-video-thumbnails into dev
All checks were successful
CI / skip-ci-check (pull_request) Successful in 7s
CI / lint-and-type-check (pull_request) Successful in 51s
CI / python-lint (pull_request) Successful in 31s
CI / test-backend (pull_request) Successful in 2m27s
CI / build (pull_request) Successful in 3m22s
CI / secret-scanning (pull_request) Successful in 15s
CI / dependency-scan (pull_request) Successful in 12s
CI / sast-scan (pull_request) Successful in 1m27s
CI / workflow-summary (pull_request) Successful in 6s
Reviewed-on: #10
2026-01-28 12:56:59 -05:00
tanyar09
830c7bcaa6 feat: Update video thumbnail handling in viewer frontend
Some checks failed
CI / skip-ci-check (pull_request) Successful in 6s
CI / python-lint (pull_request) Has been cancelled
CI / test-backend (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / secret-scanning (pull_request) Has been cancelled
CI / dependency-scan (pull_request) Has been cancelled
CI / sast-scan (pull_request) Has been cancelled
CI / workflow-summary (pull_request) Has been cancelled
CI / lint-and-type-check (pull_request) Has been cancelled
- Refactored video thumbnail generation logic to fetch thumbnails from the backend instead of generating them locally.
- Removed unused placeholder generation code for remote video URLs.
- Improved error handling for backend thumbnail fetch failures, returning appropriate responses.
- Enhanced caching strategy for fetched thumbnails to optimize performance.

These changes streamline the video thumbnail process and improve the overall user experience in the viewer interface.
2026-01-28 17:55:58 +00:00

View File

@ -4,7 +4,6 @@ import { readFile } from 'fs/promises';
import { createReadStream } from 'fs';
import { existsSync, statSync } from 'fs';
import path from 'path';
import { getVideoThumbnail } from '@/lib/video-thumbnail';
// Conditionally import sharp - handle case where libvips is not installed
let sharp: any = null;
@ -168,98 +167,35 @@ export async function GET(
// Handle video thumbnail request
if (thumbnail && mediaType === 'video') {
// Check if video path is a URL (can't generate thumbnail for remote videos)
const isVideoUrl = filePath.startsWith('http://') || filePath.startsWith('https://');
if (isVideoUrl) {
// For remote videos, we can't generate thumbnails locally
// Return a placeholder
console.warn(`Cannot generate thumbnail for remote video URL: ${filePath}`);
try {
if (!sharp) throw new Error('Sharp not available');
const placeholderSvg = `
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg">
<rect width="400" height="400" fill="#e5e7eb"/>
<circle cx="200" cy="200" r="40" fill="#6b7280" opacity="0.8"/>
<polygon points="190,185 190,215 215,200" fill="white"/>
</svg>
`.trim();
const placeholderBuffer = await sharp(Buffer.from(placeholderSvg))
.png()
.toBuffer();
return new NextResponse(placeholderBuffer as unknown as BodyInit, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=60',
},
});
} catch (error) {
console.error('Error generating placeholder for remote video:', error);
const minimalPng = Buffer.from(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
'base64'
);
return new NextResponse(minimalPng as unknown as BodyInit, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=60',
},
});
}
}
const backendBaseUrl = process.env.BACKEND_BASE_URL || 'http://127.0.0.1:8000';
try {
const thumbnailBuffer = await getVideoThumbnail(filePath);
if (thumbnailBuffer && thumbnailBuffer.length > 0) {
return new NextResponse(thumbnailBuffer as unknown as BodyInit, {
headers: {
'Content-Type': 'image/jpeg',
'Cache-Control': 'public, max-age=31536000, immutable',
},
});
}
console.error(`Thumbnail generation returned empty buffer for video: ${filePath}`);
} catch (error) {
console.error(`Error generating thumbnail for video ${filePath}:`, error);
}
// Fallback: return a placeholder image (gray box with play icon)
// Generate a PNG placeholder using sharp for better compatibility
try {
if (!sharp) throw new Error('Sharp not available');
const placeholderSvg = `
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg">
<rect width="400" height="400" fill="#e5e7eb"/>
<circle cx="200" cy="200" r="40" fill="#6b7280" opacity="0.8"/>
<polygon points="190,185 190,215 215,200" fill="white"/>
</svg>
`.trim();
const placeholderBuffer = await sharp(Buffer.from(placeholderSvg))
.png()
.toBuffer();
return new NextResponse(placeholderBuffer as unknown as BodyInit, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=60', // Short cache for placeholder
},
});
} catch (error) {
console.error('Error generating placeholder:', error);
// Ultimate fallback: return a minimal 1x1 PNG
const minimalPng = Buffer.from(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
'base64'
const backendResponse = await fetch(
`${backendBaseUrl}/api/v1/videos/${photoId}/thumbnail`,
{ headers: { Accept: 'image/*' } }
);
return new NextResponse(minimalPng as unknown as BodyInit, {
if (!backendResponse.ok) {
console.error(
`Backend thumbnail fetch failed for video ${photoId}: ` +
`${backendResponse.status} ${backendResponse.statusText}`
);
return new NextResponse('Failed to fetch video thumbnail', { status: 502 });
}
const arrayBuffer = await backendResponse.arrayBuffer();
const contentType = backendResponse.headers.get('content-type') || 'image/jpeg';
return new NextResponse(arrayBuffer as unknown as BodyInit, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=60',
'Content-Type': contentType,
// Keep caching modest: backend thumbnails can be regenerated/updated.
'Cache-Control': 'public, max-age=86400',
},
});
} catch (error) {
console.error(`Error proxying backend thumbnail for video ${photoId}:`, error);
return new NextResponse('Failed to fetch video thumbnail', { status: 502 });
}
}