Generate video thumbnails dynamically using the video stream
Implement FFmpeg to generate video thumbnails and add thumbnail caching. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 50814a1e-92e4-4968-856f-7bc7eedf5e8f Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/50814a1e-92e4-4968-856f-7bc7eedf5e8f/1majdC5
This commit is contained in:
parent
801309a47e
commit
8949236543
1
.replit
1
.replit
@ -4,6 +4,7 @@ hidden = [".config", ".git", "generated-icon.png", "node_modules", "dist"]
|
||||
|
||||
[nix]
|
||||
channel = "stable-24_05"
|
||||
packages = ["ffmpeg"]
|
||||
|
||||
[deployment]
|
||||
deploymentTarget = "autoscale"
|
||||
|
||||
@ -118,9 +118,62 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}
|
||||
});
|
||||
|
||||
// Temporary placeholder - waiting for user preference on thumbnail style
|
||||
// Generate real thumbnails from Bunny.net videos using FFmpeg
|
||||
app.get("/thumbnail/:videoId", async (req, res) => {
|
||||
res.redirect(`https://images.unsplash.com/photo-1536240478700-b869070f9279?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&h=225`);
|
||||
try {
|
||||
const { videoId } = req.params;
|
||||
const { exec } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Get video URL from Bunny.net
|
||||
const video = await storage.getVideo(videoId);
|
||||
if (!video) {
|
||||
return res.redirect(`https://images.unsplash.com/photo-1536240478700-b869070f9279?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&h=225`);
|
||||
}
|
||||
|
||||
// Create thumbnails directory if it doesn't exist
|
||||
const thumbnailDir = path.join(process.cwd(), 'thumbnails');
|
||||
if (!fs.existsSync(thumbnailDir)) {
|
||||
fs.mkdirSync(thumbnailDir, { recursive: true });
|
||||
}
|
||||
|
||||
const thumbnailPath = path.join(thumbnailDir, `${videoId}.jpg`);
|
||||
|
||||
// Check if thumbnail already exists
|
||||
if (fs.existsSync(thumbnailPath)) {
|
||||
res.setHeader('Content-Type', 'image/jpeg');
|
||||
res.setHeader('Cache-Control', 'public, max-age=86400');
|
||||
return res.sendFile(thumbnailPath);
|
||||
}
|
||||
|
||||
// Generate video URL for FFmpeg (use direct CDN URL without iframe)
|
||||
const directVideoUrl = `https://vz-7982dfc4-cc8.b-cdn.net/${videoId}/playlist.m3u8`;
|
||||
|
||||
// Generate thumbnail using FFmpeg
|
||||
const ffmpegCommand = `ffmpeg -i "${directVideoUrl}" -ss 00:00:05 -vframes 1 -vf "scale=400:225" "${thumbnailPath}" -y`;
|
||||
|
||||
exec(ffmpegCommand, (error: any, stdout: any, stderr: any) => {
|
||||
if (error) {
|
||||
console.error(`FFmpeg error: ${error.message}`);
|
||||
// Fallback to placeholder
|
||||
return res.redirect(`https://images.unsplash.com/photo-1536240478700-b869070f9279?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&h=225`);
|
||||
}
|
||||
|
||||
if (fs.existsSync(thumbnailPath)) {
|
||||
res.setHeader('Content-Type', 'image/jpeg');
|
||||
res.setHeader('Cache-Control', 'public, max-age=86400');
|
||||
res.sendFile(thumbnailPath);
|
||||
} else {
|
||||
// Fallback if thumbnail generation failed
|
||||
res.redirect(`https://images.unsplash.com/photo-1536240478700-b869070f9279?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&h=225`);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error generating thumbnail:", error);
|
||||
res.redirect(`https://images.unsplash.com/photo-1536240478700-b869070f9279?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&h=225`);
|
||||
}
|
||||
});
|
||||
|
||||
const httpServer = createServer(app);
|
||||
|
||||
19
test_thumbnail.jpg
Normal file
19
test_thumbnail.jpg
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
<svg width="400" height="225" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#1e40af;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#0f172a;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="400" height="225" fill="url(#grad)"/>
|
||||
<circle cx="200" cy="112.5" r="30" fill="rgba(255,255,255,0.8)"/>
|
||||
<polygon points="190,98 190,127 215,112.5" fill="#000"/>
|
||||
<text x="200" y="170" text-anchor="middle" fill="white" font-family="Arial, sans-serif" font-size="14" font-weight="bold">
|
||||
<tspan x="200" dy="0">Alex</tspan><tspan x="200" dy="18">Reichinger</tspan><tspan x="200" dy="18">-</tspan><tspan x="200" dy="18">Ciao</tspan><tspan x="200" dy="18">mia</tspan><tspan x="200" dy="18">bella</tspan>
|
||||
</text>
|
||||
<text x="350" y="20" text-anchor="middle" fill="white" font-family="Arial, sans-serif" font-size="12" font-weight="bold">
|
||||
3:02
|
||||
</text>
|
||||
</svg>
|
||||
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
Loading…
Reference in New Issue
Block a user