diff --git a/.env.example b/.env.example index fdcb0ed..91920e8 100644 --- a/.env.example +++ b/.env.example @@ -132,6 +132,17 @@ GEMINI_API_KEY= # demande de reconstruire ffmpeg-api. # WHISPER_MODEL=base # +# Voix locale Qwen3-TTS (service qwen-tts) : gratuite, française, sans quota, +# mais générée sur CPU (5 à 7 fois la durée de la voix sur 4 cœurs). +# Modèle choisi à la construction de l'image : +# - Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice (défaut) : ton et émotion pilotés ; +# - Qwen/Qwen3-TTS-12Hz-0.6B-CustomVoice : ~30 % plus rapide, moins expressif. +# QWEN_TTS_MODEL=Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice +# Clonage des voix déposées dans qwen-tts/voices (vide pour désactiver). +# QWEN_TTS_CLONE_MODEL=Qwen/Qwen3-TTS-12Hz-0.6B-Base +# QWEN_TTS_THREADS=0 +# QWEN_TTS_MEMORY=10g +# # Moteur de rendu des Reels vidéo : # - remotion (défaut) : sous-titres animés, logo et effet de fin identiques à # l'aperçu affiché dans l'application ; ~30 à 60 s pour un Reel de 30 s ; diff --git a/client/src/components/reels/voice-picker.tsx b/client/src/components/reels/voice-picker.tsx index c58f57e..d53c86c 100644 --- a/client/src/components/reels/voice-picker.tsx +++ b/client/src/components/reels/voice-picker.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; import { Loader2, Play, Square } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; @@ -19,6 +20,7 @@ import { voicesFor, type TtsEngine, type TtsStyle, + type VoiceOption, } from "@shared/voices"; import type { TimedWord } from "@shared/captions"; @@ -76,7 +78,13 @@ export function VoicePicker({ value, onChange, sampleText, compact = false, onPr useEffect(() => () => audioRef.current?.pause(), []); - const voices = voicesFor(value.engine); + // Voix Qwen réellement installées (voix clonées comprises) ; moteur grisé si le service local ne répond pas + const { data: qwenCatalog } = useQuery<{ qwenAvailable: boolean; qwen: VoiceOption[] }>({ + queryKey: ["/api/reels/voices"], + staleTime: 5 * 60_000, + }); + const qwenAvailable = qwenCatalog?.qwenAvailable ?? false; + const voices = value.engine === "qwen" && qwenCatalog?.qwen.length ? qwenCatalog.qwen : voicesFor(value.engine); const groups = [ { label: "Voix féminines", items: voices.filter((v) => v.gender === "female") }, { label: "Voix masculines", items: voices.filter((v) => v.gender === "male") }, @@ -133,7 +141,18 @@ export function VoicePicker({ value, onChange, sampleText, compact = false, onPr
-
+
+