mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-21 05:58:12 +02:00
feat: Implement Remotion video generation with dedicated client UI, components, and server-side rendering.
This commit is contained in:
1 parent
177f4db455
commit
e41c43c1da
13 files changed
+2998
-41
No files matched your search
@@ -1,4 +1,6 @@
|
||||
import express, { type Request, Response, NextFunction } from "express";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import session from "express-session";
|
||||
import connectPgSimple from "connect-pg-simple";
|
||||
import pg from "pg";
|
||||
@@ -64,6 +66,11 @@ app.use('/api/auth/login', authLimiter);
|
||||
const AUDIO_UPLOAD_DIR = process.env.AUDIO_UPLOAD_DIR || '/app/uploads/audio';
|
||||
app.use('/uploads/audio', express.static(AUDIO_UPLOAD_DIR));
|
||||
|
||||
// Serve temp uploads for Remotion
|
||||
const TEMP_UPLOAD_DIR = process.env.TEMP_UPLOAD_DIR || path.join(process.cwd(), 'uploads', 'temp');
|
||||
try { fs.mkdirSync(TEMP_UPLOAD_DIR, { recursive: true }); } catch { /* ignore */ }
|
||||
app.use('/uploads/temp', express.static(TEMP_UPLOAD_DIR));
|
||||
|
||||
declare module 'http' {
|
||||
interface IncomingMessage {
|
||||
rawBody: unknown
|
||||
|
||||
@@ -15,6 +15,7 @@ import type { User, InsertUser, ScheduledPost, FreesoundConfig } from "@shared/s
|
||||
import { freeSoundService } from "./services/freesound";
|
||||
import { analyticsRouter } from "./routes/analytics";
|
||||
import { reelsRouter } from "./routes/reels";
|
||||
import { remotionRouter } from "./routes/remotion";
|
||||
import { insertAudioTrackSchema } from "@shared/schema";
|
||||
|
||||
// Types MIME autorisés pour les uploads
|
||||
@@ -438,6 +439,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
|
||||
// Reels & Music Routes
|
||||
app.use("/api", requireAuth, reelsRouter);
|
||||
app.use("/api/remotion", requireAuth, remotionRouter);
|
||||
|
||||
|
||||
// Route SQL (réservée aux admins) - DÉSACTIVÉE EN PRODUCTION sauf si explicitement autorisée
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Router } from "express";
|
||||
import multer from "multer";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import { bundle } from "@remotion/bundler";
|
||||
import { renderMedia, selectComposition } from "@remotion/renderer";
|
||||
|
||||
export const remotionRouter = Router();
|
||||
|
||||
const uploadDir = path.join(process.cwd(), "uploads", "temp");
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true });
|
||||
}
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination: uploadDir,
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
|
||||
cb(null, `remotion-${uniqueSuffix}${path.extname(file.originalname)}`);
|
||||
},
|
||||
});
|
||||
|
||||
const upload = multer({ storage });
|
||||
|
||||
remotionRouter.post("/render", upload.array("images", 4), async (req, res) => {
|
||||
try {
|
||||
const files = req.files as Express.Multer.File[];
|
||||
if (!files || files.length === 0) {
|
||||
return res.status(400).json({ error: "Aucune image fournie" });
|
||||
}
|
||||
|
||||
const host = req.protocol + "://" + req.get("host");
|
||||
const imageUrls = files.map(file => {
|
||||
return `${host}/uploads/temp/${path.basename(file.path)}`;
|
||||
});
|
||||
|
||||
console.log("Bundling Remotion project...");
|
||||
const bundleLocation = await bundle({
|
||||
entryPoint: path.resolve(process.cwd(), "client/src/remotion/index.ts"),
|
||||
});
|
||||
|
||||
console.log("Selecting composition...");
|
||||
const composition = await selectComposition({
|
||||
serveUrl: bundleLocation,
|
||||
id: "ImageVideo",
|
||||
inputProps: {
|
||||
images: imageUrls,
|
||||
},
|
||||
});
|
||||
|
||||
const outputFilename = `out-${Date.now()}.mp4`;
|
||||
const outputLocation = path.join(uploadDir, outputFilename);
|
||||
|
||||
console.log("Rendering media to", outputLocation);
|
||||
await renderMedia({
|
||||
composition,
|
||||
serveUrl: bundleLocation,
|
||||
codec: "h264",
|
||||
outputLocation,
|
||||
inputProps: {
|
||||
images: imageUrls,
|
||||
},
|
||||
chromiumOptions: {
|
||||
disableWebSecurity: true
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Render completed:", outputFilename);
|
||||
res.json({ url: `${host}/uploads/temp/${outputFilename}` });
|
||||
|
||||
} catch (err: any) {
|
||||
console.error("Remotion render error:", err);
|
||||
res.status(500).json({ error: "Erreur lors du rendu de la vidéo: " + err.message });
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user