feat: optimize iPhone video quality and stabilization UI

This commit is contained in:
Michael committed 2026-01-23 17:56:39 +01:00
1 parent 027b0f7e73
commit 4954877c06
3 files changed
+78 -96

No files matched your search

+21 -22
View File
@@ -253,13 +253,30 @@ export default function MobileNewReel() {
{/* VIDEO STEP */}
{currentStep === 'video' && (
<div className="space-y-4">
<Card className="bg-primary/5 border-primary/20">
<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">Application automatique après capture</div>
</div>
<Switch
checked={stabilize}
onCheckedChange={(checked) => setStabilize(!!checked)}
/>
</div>
</CardContent>
</Card>
<div {...getRootProps()} className={`border-2 border-dashed rounded-xl p-6 text-center ${isDragActive ? 'border-primary bg-primary/5' : 'border-muted'}`}>
<input {...getInputProps()} />
<input ref={cameraInputRef} type="file" accept="video/*" capture="environment" onChange={handleCameraCapture} className="hidden" />
<div className="flex gap-2 justify-center mb-6">
<Button onClick={handleCameraButtonClick} variant="outline" size="lg" className="h-12">
<Camera className="mr-2 h-5 w-5" /> {isIOS ? 'Filmer (Qualité Max)' : 'Capturer'}
<Camera className="mr-2 h-5 w-5" /> {isIOS ? 'Filmer (Haute Qualité)' : 'Capturer'}
</Button>
<Button onClick={open} variant="outline" size="lg" className="h-12">
<Upload className="mr-2 h-5 w-5" /> Galerie
@@ -297,27 +314,9 @@ export default function MobileNewReel() {
</div>
{selectedVideo && (
<div className="space-y-3">
<Card>
<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>
<Switch
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>
<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>
)}
+47 -72
View File
@@ -3,7 +3,7 @@ import { useQuery, useMutation } from "@tanstack/react-query";
import { useLocation } from "wouter";
import {
Send, Sparkles, Video, Music, Type, Calendar,
Upload, Camera, Play, Pause, Volume2, VolumeX,
Upload, Play, Pause, Volume2, VolumeX,
ChevronRight, Loader2, Check, RefreshCw, Mic
} from "lucide-react";
import { useDropzone } from "react-dropzone";
@@ -28,7 +28,6 @@ import { useToast } from "@/hooks/use-toast";
import { apiRequest, queryClient } from "@/lib/queryClient";
import type { SocialPage, Media } from "@shared/schema";
import { DateTimePicker } from "@/components/datetime-picker";
import { CameraRecorder } from "@/components/camera-recorder";
interface MusicTrack {
id: string;
@@ -49,7 +48,6 @@ export default function NewReel() {
const [sidebarOpen, setSidebarOpen] = useState(false);
const [, navigate] = useLocation();
const { toast } = useToast();
const cameraInputRef = useRef<HTMLInputElement>(null);
const audioRef = useRef<HTMLAudioElement>(null);
// État du workflow
@@ -75,9 +73,6 @@ export default function NewReel() {
const [isPlaying, setIsPlaying] = useState<string | null>(null);
const [musicOffset, setMusicOffset] = useState(0);
// État caméra custom
const [showCamera, setShowCamera] = useState(false);
// Récupérer les pages disponibles
const { data: pages = [] } = useQuery<SocialPage[]>({
queryKey: ['/api/pages'],
@@ -232,16 +227,18 @@ export default function NewReel() {
const { getRootProps, getInputProps, isDragActive, open } = useDropzone({
onDrop,
accept: {
'video/mp4': ['.mp4', '.m4v'],
'video/quicktime': ['.mov', '.qt'],
'video/webm': ['.webm'],
'video/x-msvideo': ['.avi'],
'video/*': [], // Fallback
},
maxSize: 4 * 1024 * 1024 * 1024, // 4GB Limit
noClick: true,
noKeyboard: true,
});
accept: {
'video/mp4': ['.mp4', '.m4v'],
'video/quicktime': ['.mov', '.qt'],
'video/webm': ['.webm'],
'video/x-msvideo': ['.avi'],
'video/*': [],
},
multiple: true, // Use multiple to bypass iOS Safari automatic compression
maxSize: 4 * 1024 * 1024 * 1024, // 4GB Limit
noClick: true,
noKeyboard: true,
});
const handleCameraCapture = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
@@ -357,26 +354,6 @@ export default function NewReel() {
return (
<div className="flex h-screen overflow-hidden bg-background">
{/* Camera Recorder Modal */}
{showCamera && (
<CameraRecorder
onCapture={(file, options) => {
if (options?.stabilize) {
setStabilize(true);
toast({
title: "Stabilisation activée",
description: "La vidéo sera stabilisée lors de la création du Reel (ceci peut prendre plus de temps)",
});
} else {
setStabilize(false);
}
uploadMutation.mutate(file);
setShowCamera(false);
}}
onCancel={() => setShowCamera(false)}
/>
)}
{sidebarOpen && (
<div
className="fixed inset-0 bg-black/50 z-40 lg:hidden"
@@ -442,6 +419,7 @@ export default function NewReel() {
{/* Colonne principale */}
<div className="lg:col-span-2 space-y-6">
{/* ÉTAPE 1: Vidéo */}
{currentStep === 'video' && (
<Card className="rounded-2xl border-border/50 shadow-lg">
@@ -451,18 +429,31 @@ export default function NewReel() {
Sélectionnez une Vidéo
</CardTitle>
<CardDescription>
Choisissez une vidéo existante ou capturez-en une nouvelle
Choisissez une vidéo existante ou téléchargez-en une nouvelle
</CardDescription>
</CardHeader>
<CardContent>
<input
ref={cameraInputRef}
type="file"
accept="video/*"
capture="environment"
onChange={handleCameraCapture}
className="hidden"
/>
<CardContent className="space-y-6">
<div className="p-4 border rounded-xl bg-primary/5 border-primary/20 space-y-2">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label className="text-base font-semibold flex items-center gap-2">
<Sparkles className="w-5 h-5 text-primary" />
Stabilisation & Qualité 1080p
</Label>
<p className="text-sm text-muted-foreground">
Recommandé pour un rendu professionnel sur Facebook Reels
</p>
<div className="flex items-center gap-1.5 text-[10px] text-primary/70 bg-primary/5 px-2 py-0.5 rounded-full w-fit">
<Sparkles className="w-3 h-3" />
<span>Note iPhone : Activez la stabilisation dans Réglages > Appareil Photo</span>
</div>
</div>
<Switch
checked={stabilize}
onCheckedChange={setStabilize}
/>
</div>
</div>
<div {...getRootProps()} className={`${isDragActive ? 'bg-primary/5 border-primary' : ''}`}>
<input {...getInputProps()} />
@@ -515,28 +506,11 @@ export default function NewReel() {
</div>
{selectedVideo && (
<div className="mt-6 p-4 border rounded-xl bg-accent/30 space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label className="text-base font-semibold flex items-center gap-2">
<Sparkles className="w-5 h-5 text-primary" />
Stabilisation & Qualité 1080p
</Label>
<p className="text-sm text-muted-foreground">
Recommandé pour un rendu professionnel sur Facebook Reels
</p>
</div>
<Switch
checked={stabilize}
onCheckedChange={setStabilize}
/>
</div>
<div className="flex justify-end">
<Button onClick={() => setCurrentStep('music')}>
Continuer
<ChevronRight className="w-4 h-4 ml-2" />
</Button>
</div>
<div className="mt-6 flex justify-end">
<Button onClick={() => setCurrentStep('music')}>
Continuer
<ChevronRight className="w-4 h-4 ml-2" />
</Button>
</div>
)}
</CardContent>
@@ -548,6 +522,7 @@ export default function NewReel() {
<Card className="rounded-2xl border-border/50 shadow-lg">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Music className="w-5 h-5" />
Choisissez une Musique
</CardTitle>
@@ -1027,11 +1002,11 @@ export default function NewReel() {
</CardContent>
</Card>
</div>
</div>
</div>
</main>
</div >
</div >
</main >
{/* Overlay de progression */}
<ProcessingOverlay
< ProcessingOverlay
isVisible={createReelMutation.isPending}
stabilize={stabilize}
ttsEnabled={ttsEnabled}
+10 -2
View File
@@ -6,7 +6,7 @@ Ajout d'une fonctionnalité complète de création de Reels Facebook permettant
## Current Focus
**Phase: VERIFICATION** - Implémentation terminée, améliorations UI (preview audio, pagination musique).
**Phase: EXECUTION** - Optimisation qualité iPhone Safari (Bypass compression).
## Master Plan
@@ -107,7 +107,15 @@ Ajout d'une fonctionnalité complète de création de Reels Facebook permettant
- [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é
- [x] Frontend: Ajouter toggle Stabilisation et Qualité
- [ ] **Phase 11: Clarification UX Mobile** ✅
- [x] Modifier l'alerte iOS pour prévenir que les réglages se font après
- [x] Ajouter une flèche ou une animation pour attirer l'œil sur le Switch de stabilisation
- [x] S'assurer que le bouton "Suivant" est bien visible sous le Switch
- [x] **Phase 12: Optimisation Safari (Bypass Compression)** ✅
- [x] Ajouter l'attribut `multiple` à l'input file pour forcer l'envoi du fichier original (Bypass Safari downscaling)
- [x] Mettre à jour les MIME types pour inclure `video/quicktime` (iPhone natif)
- [x] Documenter que la stabilisation native dépend des réglages iOS de l'utilisateur
## Progress Log