PunimTag Web Application - Major Feature Release #1

Open
tanyar09 wants to merge 106 commits from dev into master
Showing only changes of commit 830c7bcaa6 - Show all commits

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 });
}
}