From 7223bbbfb26f6da4aedbf6e920787e407e8e2f28 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Fri, 23 Jan 2026 17:19:32 +0100 Subject: [PATCH] feat(reels): optimize quality to 1080p, increase text size to 64, and add stabilization toggle --- client/src/pages/mobile/new-reel.tsx | 28 +++++++++++-- ffmpeg-service/main.py | 59 ++++++++-------------------- server/routes/reels.ts | 4 +- server/services/ffmpeg.ts | 4 +- task.md | 6 +++ 5 files changed, 51 insertions(+), 50 deletions(-) diff --git a/client/src/pages/mobile/new-reel.tsx b/client/src/pages/mobile/new-reel.tsx index 6cceb9a..5bb89bd 100644 --- a/client/src/pages/mobile/new-reel.tsx +++ b/client/src/pages/mobile/new-reel.tsx @@ -76,6 +76,9 @@ export default function MobileNewReel() { // État audio preview const [isPlaying, setIsPlaying] = useState(null); const [musicOffset, setMusicOffset] = useState(0); + const [stabilize, setStabilize] = useState(true); // Activé par défaut pour les Reels + const [force1080p, setForce1080p] = useState(true); // Activé par défaut + const { data: pages = [] } = useQuery({ queryKey: ['/api/pages'], @@ -209,6 +212,7 @@ export default function MobileNewReel() { pageIds: selectedPages, scheduledFor: scheduledDate?.toISOString(), musicVolume: musicVolume[0] / 100, + stabilize: stabilize, }); }; @@ -293,9 +297,27 @@ export default function MobileNewReel() { {selectedVideo && ( - +
+ + +
+
+
+ Stabilisation & Qualité 1080p +
+
Recommandé pour Facebook Reels
+
+ setStabilize(!!checked)} + /> +
+
+
+ +
)} )} diff --git a/ffmpeg-service/main.py b/ffmpeg-service/main.py index fb141c6..6b2c6c0 100644 --- a/ffmpeg-service/main.py +++ b/ffmpeg-service/main.py @@ -31,7 +31,7 @@ class ReelRequest(BaseModel): music_id: Optional[str] = None music_url: Optional[str] = None word_duration: float = 0.6 - font_size: int = 10 + font_size: int = 64 music_volume: float = 0.25 tts_enabled: bool = False tts_voice: str = "fr-FR-VivienneMultilingualNeural" @@ -359,7 +359,8 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)): if has_tts: # Use subtitles filter # Force style to look like TikTok/Reels text (No Black Box, just Outline) - style = f"FontName=Arial,FontSize={request.font_size},PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BorderStyle=1,Outline=3,Shadow=0,Bold=1,Alignment=2,MarginV=150" + # Bigger font size (64 by default) and better vertical margin for 1080p + style = f"FontName=Arial,FontSize={request.font_size},PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BorderStyle=1,Outline=3,Shadow=0,Bold=1,Alignment=2,MarginV=300" # Escape path for FFmpeg filter vtt_path_str = str(tts_vtt_path).replace("\\", "/").replace(":", "\\:") video_filters.append( @@ -367,16 +368,12 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)): ) else: # Standard Drawtext logic + # Scale font for 1080p sanitized_text = request.text.replace("'", "").replace(":", "\\:") - drawtext = f"drawtext=fontfile={FONT_PATH}:text='{sanitized_text}':fontcolor=white:fontsize={request.font_size}:borderw=3:bordercolor=black:x=(w-text_w)/2:y=h-text_h-150" + drawtext = f"drawtext=fontfile={FONT_PATH}:text='{sanitized_text}':fontcolor=white:fontsize={request.font_size}:borderw=3:bordercolor=black:x=(w-text_w)/2:y=h-text_h-300" video_filters.append(drawtext) # Audio Mixing Strategy - # We need to mix: - # 1. Original Video Audio (0:a) - if exists - # 2. Background Music (1:a) - if has_music - # 3. TTS Voice (1:a or 2:a) - if has_tts - # Detect if original video has audio has_original_audio = False try: @@ -400,67 +397,43 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)): filter_complex_parts = [] mix_inputs = 0 - if has_music: cmd.extend(["-i", str(input_audio_path)]) - if has_tts: cmd.extend(["-i", str(tts_audio_path)]) - # Prepare inputs for mixing - # ALWAYS ignore original video audio - only use music and/or TTS - # (Original audio is never used per user request) - if has_music: - # Music volume - ensure it's properly scaled (0.0 to 1.0) - # Apply volume more aggressively for noticeable effect music_vol = request.music_volume if has_tts: - # Reduce music even more when TTS is active so voice is clear music_vol = music_vol * 0.3 - print( - f"🎵 Music volume applied: {music_vol:.2f} (requested: {request.music_volume})" - ) filter_complex_parts.append(f"[1:a]volume={music_vol}[a1]") mix_inputs += 1 if has_tts: tts_idx = 2 if has_music else 1 - # Voice needs to be loud and clear filter_complex_parts.append(f"[{tts_idx}:a]volume=1.5[a2]") mix_inputs += 1 - # Build mix command if mix_inputs > 0: - inputs_str = "" - # Note: Original audio is never mixed in (always muted) - if has_music: - inputs_str += "[a1]" - if has_tts: - inputs_str += "[a2]" - - # IMPORTANT: normalize=0 prevents amix from auto-adjusting volumes - # which was causing volume slider to have no effect + inputs_str = "".join( + ["[a1]" if has_music else "", "[a2]" if has_tts else ""] + ) filter_complex_parts.append( f"{inputs_str}amix=inputs={mix_inputs}:duration=first:dropout_transition=2:normalize=0[aout]" ) - cmd.extend(["-filter_complex", ";".join(filter_complex_parts)]) - - # Map processed video and audio cmd.extend(["-map", "0:v", "-map", "[aout]"]) else: - # No audio at all, just video cmd.extend(["-map", "0:v"]) - # Apply Video Filters if any + # Scaling to 1080x1920 (Vertical HD Reel Format) + # unsharp is added to counteract any blurring from scaling + format_filter = "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,unsharp=5:5:0.8:3:3:0.4" + video_filters.insert(0, format_filter) + if video_filters: cmd.extend(["-vf", ",".join(video_filters)]) - # -shortest not needed with duration=first in amix, but good practice if logic changes - # actually duration=first in amix takes the length of the first input (usually video audio or music if mapped first) - # We want the video length to dictate. - # Easier: just use -shortest to cut audio to video length cmd.extend(["-shortest"]) # Quality settings @@ -469,15 +442,15 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)): "-c:v", "libx264", "-preset", - "medium", + "slow", "-crf", - "18", # High quality output (lower = better, 18 is visually lossless) + "17", "-c:a", "aac", "-b:a", "192k", "-pix_fmt", - "yuv420p", # Ensure compatibility + "yuv420p", "-movflags", "+faststart", ] diff --git a/server/routes/reels.ts b/server/routes/reels.ts index afde872..c07a2da 100644 --- a/server/routes/reels.ts +++ b/server/routes/reels.ts @@ -185,7 +185,7 @@ reelsRouter.post('/reels/preview', async (req: Request, res: Response) => { ttsEnabled, ttsVoice, wordDuration = 0.6, - fontSize = 16, + fontSize = 64, musicVolume = 0.25, drawText = true, stabilize = false, @@ -281,7 +281,7 @@ reelsRouter.post('/reels', async (req: Request, res: Response) => { pageIds, scheduledFor, wordDuration = 0.6, - fontSize = 16, + fontSize = 64, musicVolume = 0.25, drawText = true, stabilize = false, diff --git a/server/services/ffmpeg.ts b/server/services/ffmpeg.ts index c943c7d..ad90266 100644 --- a/server/services/ffmpeg.ts +++ b/server/services/ffmpeg.ts @@ -85,7 +85,7 @@ export class FFmpegService { tts_enabled: options.ttsEnabled, tts_voice: options.ttsVoice, word_duration: options.wordDuration ?? 0.6, - font_size: options.fontSize ?? 16, + font_size: options.fontSize ?? 64, music_volume: options.musicVolume ?? 0.25, draw_text: options.drawText ?? true, stabilize: options.stabilize ?? false, @@ -180,7 +180,7 @@ export class FFmpegService { tts_enabled: options.ttsEnabled, tts_voice: options.ttsVoice, word_duration: options.wordDuration ?? 0.6, - font_size: options.fontSize ?? 16, + font_size: options.fontSize ?? 64, music_volume: options.musicVolume ?? 0.25, draw_text: options.drawText ?? true, stabilize: options.stabilize ?? false, diff --git a/task.md b/task.md index ba2af26..9c33c29 100644 --- a/task.md +++ b/task.md @@ -102,6 +102,12 @@ Ajout d'une fonctionnalité complète de création de Reels Facebook permettant - [x] **Fix API 500** (`server/routes/reels.ts`) : Variable `stabilize` manquante corrigée. - [x] **Fix iOS Upload** (`client/src/pages/new-reel.tsx`) : Ajout support `.mov` et augmentation limite taille (500MB). - [ ] **Support Gros Fichiers (4GB)** : Passage à DiskStorage et streaming Cloudinary. +- [ ] **Phase 10: Optimisation 1080p & Qualité Maximale** ⏳ + - [x] FFmpeg: Forcer scaling 1080x1920 (Vertical HD) + - [x] FFmpeg: Augmenter CRF à 17 et preset à `slow` + - [x] FFmpeg: Augmenter taille police par défaut à 64 + - [x] FFmpeg: Positionner texte plus bas (proportionnel 1080p) + - [/] Frontend: Ajouter toggle Stabilisation et Qualité ## Progress Log