Add social media sharing previews for videos
Implement a server-side API to generate dynamic social media shareable image thumbnails for videos and update the client to utilize this new API for open graph and Twitter meta tags. Replit-Commit-Author: Agent Replit-Commit-Session-Id: ab9cd02a-d0b2-4288-9ceb-1964d0059648 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/ab9cd02a-d0b2-4288-9ceb-1964d0059648/mbX83Mu
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
import multer from "multer";
|
||||
import { randomUUID } from "crypto";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import session from "express-session";
|
||||
import sharp from "sharp";
|
||||
|
||||
@@ -755,6 +756,76 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}
|
||||
});
|
||||
|
||||
// Social media thumbnail generation endpoint
|
||||
app.get('/api/video-thumbnail/:videoId', async (req, res) => {
|
||||
try {
|
||||
const { videoId } = req.params;
|
||||
const video = await storage.getVideo(videoId);
|
||||
|
||||
if (!video) {
|
||||
return res.status(404).send('Video not found');
|
||||
}
|
||||
|
||||
// Ustvarimo social media thumbnail sliko z video informacijami
|
||||
const width = 1200;
|
||||
const height = 630; // Facebook/Twitter optimalne dimenzije
|
||||
|
||||
const svg = `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2D1B69;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#6366f1;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="logoGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:0.9" />
|
||||
<stop offset="100%" style="stop-color:#ffffff;stop-opacity:0.7" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Ozadje -->
|
||||
<rect width="${width}" height="${height}" fill="url(#bgGradient)"/>
|
||||
|
||||
<!-- Dekorativni elementi -->
|
||||
<polygon points="100,100 180,180 70,180" fill="#ffffff" opacity="0.1"/>
|
||||
<polygon points="1000,200 1120,280 880,280" fill="#ffffff" opacity="0.08"/>
|
||||
<polygon points="200,450 260,510 140,510" fill="#ffffff" opacity="0.12"/>
|
||||
<polygon points="950,500 1040,590 860,590" fill="#ffffff" opacity="0.06"/>
|
||||
|
||||
<!-- Glavni play button v levi strani -->
|
||||
<circle cx="300" cy="315" r="80" fill="url(#logoGradient)" opacity="0.9"/>
|
||||
<polygon points="270,285 270,345 330,315" fill="#6366f1"/>
|
||||
|
||||
<!-- Video naslov -->
|
||||
<text x="450" y="250" font-family="Arial, sans-serif" font-size="42" font-weight="bold" fill="white" text-anchor="start">${video.title.length > 35 ? video.title.substring(0, 32) + '...' : video.title}</text>
|
||||
|
||||
<!-- Video opis -->
|
||||
<text x="450" y="300" font-family="Arial, sans-serif" font-size="24" fill="rgba(255,255,255,0.8)" text-anchor="start">${video.description && video.description.length > 60 ? video.description.substring(0, 57) + '...' : video.description || 'Watch this video on go4.video'}</text>
|
||||
|
||||
<!-- go4.video logo -->
|
||||
<text x="450" y="400" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="white" text-anchor="start">go4.video</text>
|
||||
<text x="450" y="430" font-family="Arial, sans-serif" font-size="18" fill="rgba(255,255,255,0.7)" text-anchor="start">Professional Video Streaming Platform</text>
|
||||
|
||||
<!-- Video dolžina -->
|
||||
<rect x="950" y="500" width="200" height="40" rx="20" fill="rgba(0,0,0,0.7)"/>
|
||||
<text x="1050" y="525" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="white" text-anchor="middle">${Math.floor(video.duration / 60)}:${(video.duration % 60).toString().padStart(2, '0')}</text>
|
||||
</svg>`;
|
||||
|
||||
// Pretvorimo SVG v PNG
|
||||
const pngBuffer = await sharp(Buffer.from(svg))
|
||||
.png({ quality: 90, compressionLevel: 6 })
|
||||
.toBuffer();
|
||||
|
||||
res.setHeader('Content-Type', 'image/png');
|
||||
res.setHeader('Cache-Control', 'public, max-age=3600'); // Cache za 1 uro
|
||||
res.send(pngBuffer);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error generating video thumbnail:', error);
|
||||
res.status(500).send('Error generating thumbnail');
|
||||
}
|
||||
});
|
||||
|
||||
const httpServer = createServer(app);
|
||||
return httpServer;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user