feat(reels): optimize quality to 1080p, increase text size to 64, and add stabilization toggle

This commit is contained in:
Michael committed 2026-01-23 17:19:32 +01:00
1 parent f95f400202
commit 7223bbbfb2
5 files changed
+51 -50

No files matched your search

+25 -3
View File
@@ -76,6 +76,9 @@ export default function MobileNewReel() {
// État audio preview // État audio preview
const [isPlaying, setIsPlaying] = useState<string | null>(null); const [isPlaying, setIsPlaying] = useState<string | null>(null);
const [musicOffset, setMusicOffset] = useState(0); 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<SocialPage[]>({ const { data: pages = [] } = useQuery<SocialPage[]>({
queryKey: ['/api/pages'], queryKey: ['/api/pages'],
@@ -209,6 +212,7 @@ export default function MobileNewReel() {
pageIds: selectedPages, pageIds: selectedPages,
scheduledFor: scheduledDate?.toISOString(), scheduledFor: scheduledDate?.toISOString(),
musicVolume: musicVolume[0] / 100, musicVolume: musicVolume[0] / 100,
stabilize: stabilize,
}); });
}; };
@@ -293,9 +297,27 @@ export default function MobileNewReel() {
</div> </div>
{selectedVideo && ( {selectedVideo && (
<Button className="w-full h-12 text-lg sticky bottom-4 shadow-xl" onClick={goNext}> <div className="space-y-3">
Suivant <ChevronRight className="ml-2 w-5 h-5" /> <Card>
</Button> <CardContent className="p-4 space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<div className="text-sm font-medium flex items-center gap-2">
<Sparkles className="w-4 h-4 text-primary" /> Stabilisation & Qualité 1080p
</div>
<div className="text-xs text-muted-foreground">Recommandé pour Facebook Reels</div>
</div>
<Checkbox
checked={stabilize}
onCheckedChange={(checked) => setStabilize(!!checked)}
/>
</div>
</CardContent>
</Card>
<Button className="w-full h-12 text-lg sticky bottom-4 shadow-xl" onClick={goNext}>
Suivant <ChevronRight className="ml-2 w-5 h-5" />
</Button>
</div>
)} )}
</div> </div>
)} )}
+16 -43
View File
@@ -31,7 +31,7 @@ class ReelRequest(BaseModel):
music_id: Optional[str] = None music_id: Optional[str] = None
music_url: Optional[str] = None music_url: Optional[str] = None
word_duration: float = 0.6 word_duration: float = 0.6
font_size: int = 10 font_size: int = 64
music_volume: float = 0.25 music_volume: float = 0.25
tts_enabled: bool = False tts_enabled: bool = False
tts_voice: str = "fr-FR-VivienneMultilingualNeural" 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: if has_tts:
# Use subtitles filter # Use subtitles filter
# Force style to look like TikTok/Reels text (No Black Box, just Outline) # 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 # Escape path for FFmpeg filter
vtt_path_str = str(tts_vtt_path).replace("\\", "/").replace(":", "\\:") vtt_path_str = str(tts_vtt_path).replace("\\", "/").replace(":", "\\:")
video_filters.append( video_filters.append(
@@ -367,16 +368,12 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)):
) )
else: else:
# Standard Drawtext logic # Standard Drawtext logic
# Scale font for 1080p
sanitized_text = request.text.replace("'", "").replace(":", "\\:") 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) video_filters.append(drawtext)
# Audio Mixing Strategy # 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 # Detect if original video has audio
has_original_audio = False has_original_audio = False
try: try:
@@ -400,67 +397,43 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)):
filter_complex_parts = [] filter_complex_parts = []
mix_inputs = 0 mix_inputs = 0
if has_music: if has_music:
cmd.extend(["-i", str(input_audio_path)]) cmd.extend(["-i", str(input_audio_path)])
if has_tts: if has_tts:
cmd.extend(["-i", str(tts_audio_path)]) 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: 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 music_vol = request.music_volume
if has_tts: if has_tts:
# Reduce music even more when TTS is active so voice is clear
music_vol = music_vol * 0.3 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]") filter_complex_parts.append(f"[1:a]volume={music_vol}[a1]")
mix_inputs += 1 mix_inputs += 1
if has_tts: if has_tts:
tts_idx = 2 if has_music else 1 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]") filter_complex_parts.append(f"[{tts_idx}:a]volume=1.5[a2]")
mix_inputs += 1 mix_inputs += 1
# Build mix command
if mix_inputs > 0: if mix_inputs > 0:
inputs_str = "" inputs_str = "".join(
# Note: Original audio is never mixed in (always muted) ["[a1]" if has_music else "", "[a2]" if has_tts else ""]
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
filter_complex_parts.append( filter_complex_parts.append(
f"{inputs_str}amix=inputs={mix_inputs}:duration=first:dropout_transition=2:normalize=0[aout]" f"{inputs_str}amix=inputs={mix_inputs}:duration=first:dropout_transition=2:normalize=0[aout]"
) )
cmd.extend(["-filter_complex", ";".join(filter_complex_parts)]) cmd.extend(["-filter_complex", ";".join(filter_complex_parts)])
# Map processed video and audio
cmd.extend(["-map", "0:v", "-map", "[aout]"]) cmd.extend(["-map", "0:v", "-map", "[aout]"])
else: else:
# No audio at all, just video
cmd.extend(["-map", "0:v"]) 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: if video_filters:
cmd.extend(["-vf", ",".join(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"]) cmd.extend(["-shortest"])
# Quality settings # Quality settings
@@ -469,15 +442,15 @@ async def process_reel(request: ReelRequest, x_api_key: str = Header(None)):
"-c:v", "-c:v",
"libx264", "libx264",
"-preset", "-preset",
"medium", "slow",
"-crf", "-crf",
"18", # High quality output (lower = better, 18 is visually lossless) "17",
"-c:a", "-c:a",
"aac", "aac",
"-b:a", "-b:a",
"192k", "192k",
"-pix_fmt", "-pix_fmt",
"yuv420p", # Ensure compatibility "yuv420p",
"-movflags", "-movflags",
"+faststart", "+faststart",
] ]
+2 -2
View File
@@ -185,7 +185,7 @@ reelsRouter.post('/reels/preview', async (req: Request, res: Response) => {
ttsEnabled, ttsEnabled,
ttsVoice, ttsVoice,
wordDuration = 0.6, wordDuration = 0.6,
fontSize = 16, fontSize = 64,
musicVolume = 0.25, musicVolume = 0.25,
drawText = true, drawText = true,
stabilize = false, stabilize = false,
@@ -281,7 +281,7 @@ reelsRouter.post('/reels', async (req: Request, res: Response) => {
pageIds, pageIds,
scheduledFor, scheduledFor,
wordDuration = 0.6, wordDuration = 0.6,
fontSize = 16, fontSize = 64,
musicVolume = 0.25, musicVolume = 0.25,
drawText = true, drawText = true,
stabilize = false, stabilize = false,
+2 -2
View File
@@ -85,7 +85,7 @@ export class FFmpegService {
tts_enabled: options.ttsEnabled, tts_enabled: options.ttsEnabled,
tts_voice: options.ttsVoice, tts_voice: options.ttsVoice,
word_duration: options.wordDuration ?? 0.6, word_duration: options.wordDuration ?? 0.6,
font_size: options.fontSize ?? 16, font_size: options.fontSize ?? 64,
music_volume: options.musicVolume ?? 0.25, music_volume: options.musicVolume ?? 0.25,
draw_text: options.drawText ?? true, draw_text: options.drawText ?? true,
stabilize: options.stabilize ?? false, stabilize: options.stabilize ?? false,
@@ -180,7 +180,7 @@ export class FFmpegService {
tts_enabled: options.ttsEnabled, tts_enabled: options.ttsEnabled,
tts_voice: options.ttsVoice, tts_voice: options.ttsVoice,
word_duration: options.wordDuration ?? 0.6, word_duration: options.wordDuration ?? 0.6,
font_size: options.fontSize ?? 16, font_size: options.fontSize ?? 64,
music_volume: options.musicVolume ?? 0.25, music_volume: options.musicVolume ?? 0.25,
draw_text: options.drawText ?? true, draw_text: options.drawText ?? true,
stabilize: options.stabilize ?? false, stabilize: options.stabilize ?? false,
+6
View File
@@ -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 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). - [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. - [ ] **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 ## Progress Log