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
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.
This commit is contained in:
parent
2f640b7b8d
commit
830c7bcaa6
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user