mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-23 15:08:13 +02:00
feat(mobile-reel): suppression bouton caméra, amélioration sync TTS (chunks 3 mots + overlap) et style sous-titres (outline/shadow)
This commit is contained in:
1 parent
cdabf13e94
commit
11b5ef0ad2
2 files changed
+14
-48
No files matched your search
@@ -1,10 +1,9 @@
|
|||||||
import { useState, useCallback, useRef, useMemo } from "react";
|
import { useState, useCallback, useRef } from "react";
|
||||||
import { CameraRecorder } from "@/components/camera-recorder";
|
|
||||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
import {
|
import {
|
||||||
Send, Sparkles, Video, Music, Type, Calendar,
|
Send, Sparkles, Video, Music, Type, Calendar,
|
||||||
Upload, Camera, Play, Pause, Volume2,
|
Upload, Play, Pause, Volume2,
|
||||||
ChevronRight, Loader2, Check, RefreshCw, ChevronLeft,
|
ChevronRight, Loader2, Check, RefreshCw, ChevronLeft,
|
||||||
Menu, Mic
|
Menu, Mic
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
@@ -41,27 +40,8 @@ type Step = 'video' | 'music' | 'text' | 'publish';
|
|||||||
export default function MobileNewReel() {
|
export default function MobileNewReel() {
|
||||||
const [, navigate] = useLocation();
|
const [, navigate] = useLocation();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const cameraInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const audioRef = useRef<HTMLAudioElement>(null);
|
const audioRef = useRef<HTMLAudioElement>(null);
|
||||||
|
|
||||||
const [showCamera, setShowCamera] = useState(false);
|
|
||||||
|
|
||||||
// Détection iOS pour utiliser la caméra native (meilleure qualité + stabilisation)
|
|
||||||
const isIOS = useMemo(() => {
|
|
||||||
if (typeof navigator === 'undefined') return false;
|
|
||||||
return /iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
||||||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Handler pour le bouton caméra - iOS utilise l'input natif, Android utilise WebRTC
|
|
||||||
const handleCameraButtonClick = useCallback(() => {
|
|
||||||
if (isIOS) {
|
|
||||||
cameraInputRef.current?.click();
|
|
||||||
} else {
|
|
||||||
setShowCamera(true);
|
|
||||||
}
|
|
||||||
}, [isIOS]);
|
|
||||||
|
|
||||||
// État du workflow
|
// État du workflow
|
||||||
const [currentStep, setCurrentStep] = useState<Step>('video');
|
const [currentStep, setCurrentStep] = useState<Step>('video');
|
||||||
|
|
||||||
@@ -192,10 +172,7 @@ export default function MobileNewReel() {
|
|||||||
noKeyboard: true,
|
noKeyboard: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleCameraCapture = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const file = e.target.files?.[0];
|
|
||||||
if (file) uploadMutation.mutate(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
const togglePlayPreview = async (track: MusicTrack) => {
|
const togglePlayPreview = async (track: MusicTrack) => {
|
||||||
if (isPlaying === track.id) {
|
if (isPlaying === track.id) {
|
||||||
@@ -274,28 +251,13 @@ export default function MobileNewReel() {
|
|||||||
|
|
||||||
<div {...getRootProps()} className={`border-2 border-dashed rounded-xl p-6 text-center ${isDragActive ? 'border-primary bg-primary/5' : 'border-muted'}`}>
|
<div {...getRootProps()} className={`border-2 border-dashed rounded-xl p-6 text-center ${isDragActive ? 'border-primary bg-primary/5' : 'border-muted'}`}>
|
||||||
<input {...getInputProps()} />
|
<input {...getInputProps()} />
|
||||||
<input ref={cameraInputRef} type="file" accept="video/*" capture="environment" onChange={handleCameraCapture} className="hidden" />
|
|
||||||
|
|
||||||
<div className="flex gap-2 justify-center mb-6">
|
<div className="flex justify-center mb-6">
|
||||||
<Button onClick={handleCameraButtonClick} variant="outline" size="lg" className="h-12">
|
|
||||||
<Camera className="mr-2 h-5 w-5" /> {isIOS ? 'Filmer (Haute Qualité)' : 'Capturer'}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={open} variant="outline" size="lg" className="h-12">
|
<Button onClick={open} variant="outline" size="lg" className="h-12">
|
||||||
<Upload className="mr-2 h-5 w-5" /> Galerie
|
<Upload className="mr-2 h-5 w-5" /> Galerie
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* CameraRecorder pour Android uniquement */}
|
|
||||||
{showCamera && !isIOS && (
|
|
||||||
<CameraRecorder
|
|
||||||
onCapture={(file) => {
|
|
||||||
uploadMutation.mutate(file);
|
|
||||||
setShowCamera(false);
|
|
||||||
}}
|
|
||||||
onCancel={() => setShowCamera(false)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
{selectedVideo ? (
|
{selectedVideo ? (
|
||||||
<div className="relative rounded-lg overflow-hidden aspect-[9/16] bg-black max-h-[50vh] mx-auto shadow-lg">
|
<div className="relative rounded-lg overflow-hidden aspect-[9/16] bg-black max-h-[50vh] mx-auto shadow-lg">
|
||||||
|
|||||||
+10
-6
@@ -334,9 +334,9 @@ def generate_ass_from_word_boundaries(
|
|||||||
for i, wb in enumerate(word_boundaries):
|
for i, wb in enumerate(word_boundaries):
|
||||||
current_words.append(wb["text"])
|
current_words.append(wb["text"])
|
||||||
is_last = i == len(word_boundaries) - 1
|
is_last = i == len(word_boundaries) - 1
|
||||||
# Split at punctuation or every 5 words
|
# Split at punctuation or every 3 words (tighter sync with voice)
|
||||||
ends_sentence = wb["text"].rstrip().endswith((".", "!", "?", ":", ","))
|
ends_sentence = wb["text"].rstrip().endswith((".", "!", "?", ":", ","))
|
||||||
at_limit = len(current_words) >= 5
|
at_limit = len(current_words) >= 3
|
||||||
|
|
||||||
if is_last or ends_sentence or at_limit:
|
if is_last or ends_sentence or at_limit:
|
||||||
# End time = this word's offset + its duration
|
# End time = this word's offset + its duration
|
||||||
@@ -358,6 +358,10 @@ def generate_ass_from_word_boundaries(
|
|||||||
if total_duration and chunks:
|
if total_duration and chunks:
|
||||||
chunks[-1]["end"] = max(chunks[-1]["end"], total_duration)
|
chunks[-1]["end"] = max(chunks[-1]["end"], total_duration)
|
||||||
|
|
||||||
|
# Add 50ms overlap between consecutive chunks to prevent flickering
|
||||||
|
for i in range(len(chunks) - 1):
|
||||||
|
chunks[i]["end"] = max(chunks[i]["end"], chunks[i + 1]["start"] + 0.05)
|
||||||
|
|
||||||
# ASS Header
|
# ASS Header
|
||||||
header = f"""[Script Info]
|
header = f"""[Script Info]
|
||||||
ScriptType: v4.00+
|
ScriptType: v4.00+
|
||||||
@@ -367,7 +371,7 @@ ScaledBorderAndShadow: yes
|
|||||||
|
|
||||||
[V4+ Styles]
|
[V4+ Styles]
|
||||||
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
||||||
Style: Default,Sans,{font_size},&H00FFFFFF,&H000000FF,&H00000000,&H80000000,-1,0,0,0,100,100,0,0,1,2,0,5,50,50,0,1
|
Style: Default,Sans,{font_size},&H00FFFFFF,&H000000FF,&H00000000,&H80000000,-1,0,0,0,100,100,0,0,1,4,2,5,50,50,0,1
|
||||||
|
|
||||||
[Events]
|
[Events]
|
||||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||||
@@ -400,7 +404,7 @@ def generate_simple_ass(
|
|||||||
|
|
||||||
for word in words:
|
for word in words:
|
||||||
current_chunk.append(word)
|
current_chunk.append(word)
|
||||||
if len(current_chunk) >= 5 or word.endswith((".", "!", "?", ":")):
|
if len(current_chunk) >= 3 or word.endswith((".", "!", "?", ":")):
|
||||||
chunks.append(" ".join(current_chunk))
|
chunks.append(" ".join(current_chunk))
|
||||||
current_chunk = []
|
current_chunk = []
|
||||||
|
|
||||||
@@ -418,7 +422,7 @@ ScaledBorderAndShadow: yes
|
|||||||
|
|
||||||
[V4+ Styles]
|
[V4+ Styles]
|
||||||
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
||||||
Style: Default,Sans,{font_size},&H0000FFFF,&H00FFFFFF,&H00000000,&H80000000,-1,0,0,0,100,100,0,0,1,3,1,5,50,50,0,1
|
Style: Default,Sans,{font_size},&H0000FFFF,&H00FFFFFF,&H00000000,&H80000000,-1,0,0,0,100,100,0,0,1,4,2,5,50,50,0,1
|
||||||
|
|
||||||
[Events]
|
[Events]
|
||||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||||
@@ -521,7 +525,7 @@ ScaledBorderAndShadow: yes
|
|||||||
|
|
||||||
[V4+ Styles]
|
[V4+ Styles]
|
||||||
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
||||||
Style: Default,Sans,{font_size},&H0000FFFF,&H00FFFFFF,&H00000000,&H80000000,-1,0,0,0,100,100,0,0,1,3,1,5,50,50,0,1
|
Style: Default,Sans,{font_size},&H0000FFFF,&H00FFFFFF,&H00000000,&H80000000,-1,0,0,0,100,100,0,0,1,4,2,5,50,50,0,1
|
||||||
|
|
||||||
[Events]
|
[Events]
|
||||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||||
|
|||||||
Reference in new issue
Block a user