feat: Introduce Remotion video creation and publishing page with a media thumbnail component and backend routes.

This commit is contained in:
Michael committed 2026-03-27 09:45:38 +01:00
1 parent e82d73c4e1
commit 0eca57efab
3 files changed
+196 -22

No files matched your search

+35 -1
View File
@@ -2,6 +2,8 @@ import { Router } from "express";
import multer from "multer";
import path from "path";
import fs from "fs";
import { exec } from "child_process";
import { promisify } from "util";
import { bundle } from "@remotion/bundler";
import { renderMedia, selectComposition } from "@remotion/renderer";
import { ffmpegService } from "../services/ffmpeg";
@@ -10,6 +12,25 @@ import { minioService as cloudinaryService, buildMinioUrl } from "../services/mi
import { facebookService } from "../services/facebook";
import * as musicMetadata from "music-metadata";
const execAsync = promisify(exec);
/**
* Generate a thumbnail from a video file using FFmpeg
* @param videoPath Path to the video file
* @param outputPath Path to save the thumbnail
* @param seekTime Time in seconds to extract the frame (default: 1s)
*/
async function generateVideoThumbnail(videoPath: string, outputPath: string, seekTime: number = 1): Promise<boolean> {
try {
const cmd = `ffmpeg -y -ss ${seekTime} -i "${videoPath}" -vframes 1 -q:v 2 "${outputPath}"`;
await execAsync(cmd);
return fs.existsSync(outputPath);
} catch (error) {
console.warn('⚠️ Failed to generate video thumbnail:', error);
return false;
}
}
export const remotionRouter = Router();
const uploadDir = path.join(process.cwd(), "uploads", "temp");
@@ -291,8 +312,21 @@ remotionRouter.post("/render", upload.fields([{ name: "images", maxCount: 4 }, {
});
console.log("✅ Render completed:", outputFilename);
// Generate thumbnail for the video
const thumbnailFilename = outputFilename.replace('.mp4', '-thumb.jpg');
const thumbnailPath = path.join(uploadDir, thumbnailFilename);
const thumbnailGenerated = await generateVideoThumbnail(outputLocation, thumbnailPath, 2);
if (thumbnailGenerated) {
console.log("🖼️ Thumbnail generated:", thumbnailFilename);
}
// Return relative URL so any client (mobile, desktop, Docker) can access it
res.json({ url: `/uploads/temp/${outputFilename}` });
res.json({
url: `/uploads/temp/${outputFilename}`,
thumbnailUrl: thumbnailGenerated ? `/uploads/temp/${thumbnailFilename}` : null,
});
} catch (err: any) {
console.error("❌ Remotion render error:", err);