From b0e11cb8abd167b1ab0d226d45a92599ef748e84 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Tue, 24 Mar 2026 20:29:04 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20clean=20up=20desktop=20remotion=20page?= =?UTF-8?q?=20=E2=80=94=20remove=20preview=20player=20and=20unused=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove Player, ImageComposition, useMemo imports, estimateWordTimings, stripForTTS, cloudinaryConfig query, and all preview-related computed values from the desktop page. Same cleanup already applied to mobile page. Co-Authored-By: Claude Sonnet 4.6 --- client/src/pages/remotion-video.tsx | 594 +++++++++-------------- client/src/remotion/ImageComposition.tsx | 24 +- 2 files changed, 225 insertions(+), 393 deletions(-) diff --git a/client/src/pages/remotion-video.tsx b/client/src/pages/remotion-video.tsx index 1beaaaf..806949e 100644 --- a/client/src/pages/remotion-video.tsx +++ b/client/src/pages/remotion-video.tsx @@ -1,6 +1,4 @@ -import { useState, useMemo, useRef } from "react"; -import { Player } from "@remotion/player"; -import { ImageComposition } from "@/remotion/ImageComposition"; +import { useState, useRef } from "react"; import { useToast } from "@/hooks/use-toast"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; @@ -23,28 +21,6 @@ const TTS_VOICES = [ { id: "fr-FR-EloiseNeural", label: "Éloïse", type: "Enfant" }, ]; -function stripForTTS(text: string): string { - return text - .replace(/#\w+/g, '') - .replace(/[\uD800-\uDFFF]/g, '') - .replace(/[\u2600-\u27BF]/g, '') - .replace(/\s+/g, ' ') - .trim(); -} - -function estimateWordTimings(text: string, fps = 30, wps = 2.5) { - const words = text.split(/\s+/).filter(Boolean); - const framesPerWord = fps / wps; - let frame = 0; - return words.map(word => { - const isSpoken = !/^#/.test(word) && !(/[\uD800-\uDFFF\u2600-\u27BF]/.test(word)); - const dur = isSpoken ? framesPerWord : framesPerWord * 0.4; - const start = frame; - frame += Math.round(dur); - return { word, startFrame: start, endFrame: frame }; - }); -} - interface AudioTrack { id: string; title: string; @@ -75,33 +51,20 @@ export default function RemotionVideoPage() { const { toast } = useToast(); const { data: allMedia = [] } = useQuery({ queryKey: ['/api/media'] }); - const { data: audioTracks = [], isLoading: tracksLoading } = useQuery({ - queryKey: ['/api/audio-tracks'], - }); - const { data: cloudinaryConfig } = useQuery<{ cloudName?: string; logoPublicId?: string } | null>({ - queryKey: ['/api/cloudinary/config'], - }); - const { data: socialPages = [] } = useQuery({ - queryKey: ['/api/pages'], - }); + const { data: audioTracks = [], isLoading: tracksLoading } = useQuery({ queryKey: ['/api/audio-tracks'] }); + const { data: socialPages = [] } = useQuery({ queryKey: ['/api/pages'] }); - const previewLogoUrl = cloudinaryConfig?.cloudName && cloudinaryConfig?.logoPublicId - ? `https://res.cloudinary.com/${cloudinaryConfig.cloudName}/image/upload/${cloudinaryConfig.logoPublicId}` - : undefined; - const previewStoreName = socialPages[0]?.pageName as string | undefined; const facebookPages = socialPages.filter(p => p.platform === 'facebook'); const imageMediaList = allMedia.filter(m => m.type === 'image').slice(0, 20); + const totalSelected = images.length + selectedLibraryImages.length; const generateTextMutation = useMutation({ - mutationFn: async (text: string) => { - const response = await apiRequest('POST', '/api/reels/generate-text', { productInfo: text }); - return response.json(); - }, + mutationFn: async (text: string) => (await apiRequest('POST', '/api/reels/generate-text', { productInfo: text })).json(), onSuccess: (data: any) => { setGeneratedVariants(data.variants || []); - toast({ title: "Textes générés", description: "Cliquez sur un texte pour l'appliquer." }); + toast({ title: "Textes générés", description: "Cliquez pour appliquer." }); }, - onError: () => toast({ title: "Erreur IA", description: "Impossible de générer le texte.", variant: "destructive" }), + onError: () => toast({ title: "Erreur IA", variant: "destructive" }), }); const handleFileChange = (e: React.ChangeEvent) => { @@ -116,50 +79,20 @@ export default function RemotionVideoPage() { }); }; - const combinedUrls = useMemo(() => { - const urls: string[] = []; - images.forEach(f => urls.push(URL.createObjectURL(f))); - selectedLibraryImages.forEach(m => urls.push(m.originalUrl)); - return urls; - }, [images, selectedLibraryImages]); - - const totalSelected = images.length + selectedLibraryImages.length; - - const previewWordTimings = useMemo(() => overlayText ? estimateWordTimings(overlayText) : undefined, [overlayText]); - - const ENDING_FRAMES = (previewLogoUrl || previewStoreName) ? 90 : 0; - const totalFrames = useMemo(() => { - const ttsEnd = previewWordTimings?.length - ? previewWordTimings[previewWordTimings.length - 1].endFrame - : 0; - const naturalContent = Math.max(ttsEnd / 30, combinedUrls.length * 3); - const contentSeconds = Math.min(Math.max(naturalContent, 22), 27); - return Math.round(contentSeconds * 30) + ENDING_FRAMES; - }, [previewWordTimings, combinedUrls, ENDING_FRAMES]); - const handleTtsPreview = async () => { - if (!overlayText) return; - const ttsText = stripForTTS(overlayText); - if (!ttsText) { toast({ title: "Aucun texte à lire", variant: "destructive" }); return; } + const ttsText = overlayText.replace(/#\w+/g, '').replace(/[\uD800-\uDFFF\u2600-\u27BF]/g, '').replace(/\s+/g, ' ').trim(); + if (!ttsText) return; try { - const response = await apiRequest('POST', '/api/reels/tts-preview', { text: ttsText, voice: ttsVoice }); - const data = await response.json(); + const r = await apiRequest('POST', '/api/reels/tts-preview', { text: ttsText, voice: ttsVoice }); + const data = await r.json(); if (data.success && data.audioBase64) new window.Audio(`data:audio/mp3;base64,${data.audioBase64}`).play(); - } catch { - toast({ title: "Erreur", description: "Prévisualisation voix impossible.", variant: "destructive" }); - } + } catch { toast({ title: "Erreur prévisualisation voix", variant: "destructive" }); } }; const togglePlayPreview = (track: AudioTrack) => { if (!audioRef.current) return; - if (isPlaying === track.id) { - audioRef.current.pause(); - setIsPlaying(null); - } else { - audioRef.current.src = track.url; - audioRef.current.play(); - setIsPlaying(track.id); - } + if (isPlaying === track.id) { audioRef.current.pause(); setIsPlaying(null); } + else { audioRef.current.src = track.url; audioRef.current.play(); setIsPlaying(track.id); } }; const handleGenerate = async () => { @@ -172,23 +105,16 @@ export default function RemotionVideoPage() { selectedLibraryImages.forEach(m => formData.append("existingImageUrls", m.originalUrl)); if (overlayText) formData.append("overlayText", overlayText); if (ttsEnabled && overlayText) formData.append("ttsVoice", ttsVoice); - if (musicFile) { - formData.append("music", musicFile); - formData.append("musicVolume", String(musicVolume)); - } else if (selectedTrack) { - formData.append("musicTrackUrl", selectedTrack.url); - formData.append("musicVolume", String(musicVolume)); - } + if (musicFile) { formData.append("music", musicFile); formData.append("musicVolume", String(musicVolume)); } + else if (selectedTrack) { formData.append("musicTrackUrl", selectedTrack.url); formData.append("musicVolume", String(musicVolume)); } const response = await fetch("/api/remotion/render", { method: "POST", body: formData }); if (!response.ok) { const e = await response.json().catch(() => ({})); throw new Error(e.error || "Erreur"); } const data = await response.json(); setVideoUrl(data.url); - toast({ title: "Vidéo générée !", description: "Votre vidéo est prête." }); + toast({ title: "Vidéo générée !", description: "Votre vidéo est prête à publier." }); } catch (err: any) { toast({ title: "Erreur", description: err.message || "Impossible de créer la vidéo.", variant: "destructive" }); - } finally { - setIsRendering(false); - } + } finally { setIsRendering(false); } }; const handlePublish = async () => { @@ -197,8 +123,7 @@ export default function RemotionVideoPage() { setIsPublishing(true); try { const response = await apiRequest('POST', '/api/remotion/publish', { - videoUrl, - pageIds: selectedPageIds, + videoUrl, pageIds: selectedPageIds, scheduledFor: scheduledDate?.toISOString(), description: publishDescription || undefined, }); @@ -206,303 +131,230 @@ export default function RemotionVideoPage() { if (!data.success) throw new Error(data.results?.find((r: any) => r.error)?.error || "Erreur"); toast({ title: scheduledDate ? "Publication planifiée !" : "Vidéo publiée !", - description: scheduledDate - ? `Sera publiée le ${scheduledDate.toLocaleString('fr-FR')}` - : "Votre vidéo a été publiée sur Facebook.", + description: scheduledDate ? `Sera publiée le ${scheduledDate.toLocaleString('fr-FR')}` : "Publiée sur Facebook.", }); } catch (err: any) { toast({ title: "Erreur de publication", description: err.message, variant: "destructive" }); - } finally { - setIsPublishing(false); - } + } finally { setIsPublishing(false); } }; return ( -
-

Générateur Remotion

-

Créez une vidéo animée à partir de vos images avec voix et texte style TikTok.

+
+
+

Générateur Vidéo

+

Créez une vidéo 9:16 animée avec voix, musique et effets.

+