From 4fcc8cf53c472a9f90ed8e91a169b35c26f52b60 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Tue, 3 Mar 2026 12:34:09 +0100 Subject: [PATCH] feat: Add music search and favorite API routes, and introduce FFmpeg service for Reels. --- ffmpeg-service/main.py | 18 ++++++++++++++++-- server/routes/reels.ts | 17 +++++++++++++++-- server/services/ffmpeg.ts | 5 +++++ task.md | 9 ++++++++- 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/ffmpeg-service/main.py b/ffmpeg-service/main.py index 365c0f5..3bd7f4a 100644 --- a/ffmpeg-service/main.py +++ b/ffmpeg-service/main.py @@ -153,6 +153,7 @@ class ReelRequest(BaseModel): music_id: Optional[str] = None music_url: Optional[str] = None watermark_url: Optional[str] = None + store_name: Optional[str] = None word_duration: float = 0.6 font_size: int = 64 music_volume: float = 0.25 @@ -819,8 +820,21 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)): v_chain += text_filter if has_watermark: - fc_parts.append(f"[{watermark_idx}:v]scale=200:-1[wm]") - v_chain += "[v_pre_wm];[v_pre_wm][wm]overlay=W-w-20:H-h-20" + if request.store_name: + # Ouro Party Mode + # 1. Scaling the logo large for the center + fc_parts.append(f"[{watermark_idx}:v]scale=-1:300[wm]") + # 2. Placing it in the center, and fading it IN during the last 2 seconds + v_chain += f"[v_pre_wm];[v_pre_wm][wm]overlay=(W-w)/2:(H-h)/2-100:enable='between(t,{fade_start},{video_duration})'" + # 3. Drawing the Store Name below the logo, with a fade in as well + escaped_store_name = request.store_name.replace("'", "\\'").replace( + ":", "\\:" + ) + v_chain += f",drawtext=text='{escaped_store_name}':x=(w-text_w)/2:y=(h-text_h)/2+150:fontsize=70:fontcolor=white:shadowcolor=black:shadowx=3:shadowy=3:enable='between(t,{fade_start},{video_duration})'" + else: + # Normal watermark (bottom right) + fc_parts.append(f"[{watermark_idx}:v]scale=200:-1[wm]") + v_chain += "[v_pre_wm];[v_pre_wm][wm]overlay=W-w-20:H-h-20" # Add Video Fade Out v_chain += f",fade=t=out:st={fade_start}:d={fade_duration}" diff --git a/server/routes/reels.ts b/server/routes/reels.ts index 664c0aa..71cdc4c 100644 --- a/server/routes/reels.ts +++ b/server/routes/reels.ts @@ -470,7 +470,8 @@ async function processReelBackground( musicVolume?: number; drawText?: boolean; stabilize?: boolean; - } + }, + storeName?: string ) { const { videoMediaId, @@ -567,6 +568,7 @@ async function processReelBackground( drawText, stabilize, watermarkUrl, + storeName, }); console.log(`⏱️ [Background] FFmpeg took ${(Date.now() - startTime) / 1000}s`); @@ -746,6 +748,17 @@ reelsRouter.post('/reels', async (req: Request, res: Response) => { ? "File d'attente pleine. Votre vidéo sera traitée dès que possible." : "Traitement démarré en arrière-plan."; + // Récupérer le nom de la première page pour l'utiliser comme storeName + let storeName: string | undefined = undefined; + try { + const page = await storage.getSocialPage(pageIds[0]); + if (page) { + storeName = page.pageName; + } + } catch (e) { + console.error('Erreur récupération nom de page', e); + } + // Créer immédiatement le Post en base // ON STOCKE LES PARAMS DU JOB DANS productInfo POUR POUVOIR LE REPRENDRE PLUS TARD // C'est un hack car on n'a pas de table params_job, mais ça marche car productInfo est jsonb @@ -764,7 +777,7 @@ reelsRouter.post('/reels', async (req: Request, res: Response) => { if (!isQueueBusy) { // Démarrer le traitement en arrière-plan (Fire & Forget) - processReelBackground(user.id, post.id, req.body).catch(err => { + processReelBackground(user.id, post.id, req.body, storeName).catch(err => { console.error('🔥 Unhandled background error:', err); }); } else { diff --git a/server/services/ffmpeg.ts b/server/services/ffmpeg.ts index 3fae73d..9dfe5f1 100644 --- a/server/services/ffmpeg.ts +++ b/server/services/ffmpeg.ts @@ -19,6 +19,7 @@ interface FFmpegReelRequest { draw_text?: boolean; // Dessiner le texte sur la vidéo (default: true) stabilize?: boolean; // Stabilisation vidéo via vidstab (default: false) watermark_url?: string; // URL du logo + store_name?: string; // Nom du magasin pour l'outro } interface FFmpegReelResponse { @@ -75,6 +76,7 @@ export class FFmpegService { drawText?: boolean; stabilize?: boolean; watermarkUrl?: string; + storeName?: string; } = {} ): Promise<{ success: boolean; videoBase64?: string; duration?: number; error?: string }> { const config = this.ensureConfigured(); @@ -92,6 +94,7 @@ export class FFmpegService { draw_text: options.drawText ?? true, stabilize: options.stabilize ?? false, watermark_url: options.watermarkUrl, + store_name: options.storeName, }; // Remove undefined values @@ -172,6 +175,7 @@ export class FFmpegService { drawText?: boolean; stabilize?: boolean; watermarkUrl?: string; + storeName?: string; } = {} ): Promise<{ success: boolean; videoBase64?: string; duration?: number; error?: string }> { const config = this.ensureConfigured(); @@ -189,6 +193,7 @@ export class FFmpegService { draw_text: options.drawText ?? true, stabilize: options.stabilize ?? false, watermark_url: options.watermarkUrl, + store_name: options.storeName, }; // Remove undefined values diff --git a/task.md b/task.md index d99d8a8..4e0782b 100644 --- a/task.md +++ b/task.md @@ -6,10 +6,17 @@ Ajout d'une fonctionnalité complète de création de Reels Facebook permettant ## Current Focus -**Phase: EXECUTION** - Implémentation de la gestion interne des musiques (MP3) et incrustation du logo existant sur les Reels. +**Phase: EXECUTION** - Implémenter l'effet Outro (Logo central + nom du magasin) à la fin des Réels via le Party Mode. ## Master Plan +### Party Mode & Outro (Nouvelle fonctionnalité) + +- [x] Créer l'Implementation Plan basé sur la réflexion du Party Mode +- [/] `server/routes/reels.ts` : Passer `storeName` à FFmpeg (le nom de la page). +- [/] `ffmpeg-service/main.py` : Ajouter le scale du logo et `drawtext` du `storeName` centrés. +- [/] Appliquer ces filtres avec un fondu d'entrée correspondant à la durée du fondu de sortie global. + ### Phase 0: Clarifications ✅ - [x] Confirmer source de musique → FreeSound API (ex-Jamendo)