Spaces:
Sleeping
Sleeping
File size: 5,804 Bytes
978d539 0932438 b9f3a40 919124a b9f3a40 978d539 b9f3a40 978d539 919124a 978d539 67c29f4 919124a b9f3a40 919124a 978d539 919124a b9f3a40 919124a b9f3a40 c3a70d1 919124a b9f3a40 978d539 b9f3a40 978d539 b9f3a40 1e97ad3 b9f3a40 978d539 551e973 b9f3a40 551e973 b9f3a40 551e973 b9f3a40 551e973 b9f3a40 551e973 b9f3a40 551e973 b9f3a40 551e973 b9f3a40 551e973 b9f3a40 551e973 b9f3a40 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | import os
import sys
import subprocess
import io
import wave
from typing import Generator
# Configuration de la sortie audio (doit être WAV 16-bit 22050 Hz pour le streaming)
SAMPLE_RATE = 22050
BITS_PER_SAMPLE = 16
CHANNELS = 1
# --- Fonctions de compatibilité (inchangées) ---
def get_tts_engine():
return True
def reset_tts_engine():
pass
# --- Logique de Langues/Voix ---
def get_available_languages():
# Nous utilisons les voix espeak-ng par défaut pour le français
return {
'Français (Qualité Optimale)': 'fr-fr',
'Anglais (US)': 'en-us',
'Espagnol': 'es',
'Allemand': 'de',
'Italien': 'it',
}
# --- Logique de Génération Audio EN MODE STREAMING ---
def split_text_into_chunks(text, max_chars=2000):
"""Découpe le texte en morceaux pour la synthèse, améliorant la réactivité."""
# Cette logique simple est suffisante, mais peut être améliorée (sur les points/virgules)
chunks = []
while text:
chunk = text[:max_chars]
text = text[max_chars:]
chunks.append(chunk)
return chunks
def generate_wav_header(data_size):
"""Génère l'en-tête WAV pour un flux audio brut."""
header = io.BytesIO()
# RIFF chunk
header.write(b'RIFF')
header.write((data_size + 36).to_bytes(4, byteorder='little')) # ChunkSize
header.write(b'WAVE')
# fmt chunk
header.write(b'fmt ')
# 💥 CORRECTION ici (enlever un point)
header.write((16).to_bytes(4, byteorder='little')) # Subchunk1Size
# 💥 CORRECTION ici (enlever un point)
header.write((1).to_bytes(2, byteorder='little')) # AudioFormat (PCM=1)
header.write(CHANNELS.to_bytes(2, byteorder='little'))
header.write(SAMPLE_RATE.to_bytes(4, byteorder='little'))
# ByteRate = SampleRate * NumChannels * BitsPerSample/8
byte_rate = SAMPLE_RATE * CHANNELS * BITS_PER_SAMPLE // 8
header.write(byte_rate.to_bytes(4, byteorder='little'))
# BlockAlign = NumChannels * BitsPerSample/8
block_align = CHANNELS * BITS_PER_SAMPLE // 8
header.write(block_align.to_bytes(2, byteorder='little'))
header.write(BITS_PER_SAMPLE.to_bytes(2, byteorder='little'))
# data chunk
header.write(b'data')
header.write(data_size.to_bytes(4, byteorder='little'))
return header.getvalue()
def stream_text_to_audio(text: str, voice_id: str) -> Generator[bytes, None, None]:
"""
Convertit le texte en audio et le *yield* (renvoie) par morceaux (streaming).
La première sortie est l'en-tête WAV, suivie des données audio brutes.
PARAMÈTRES ESPEAK-NG AJUSTÉS POUR AMÉLIORER LA CLARTÉ ET LE RYTHME :
-s 150: Vitesse de 150 mots par minute (par défaut 175). Plus lent pour plus de clarté.
-p 60: Hauteur (pitch) de 60 (par défaut 50). Peut réduire l'effet monotone.
"""
# 1. Découpage du texte en morceaux pour la synthèse
chunks = split_text_into_chunks(text)
full_audio_data = io.BytesIO()
total_audio_length = 0
# 2. Générer et collecter chaque morceau audio
for chunk in chunks:
# espeak-ng -v [VOIX] -s [VITESSE] -p [HAUTEUR] --stdout "[TEXTE]"
command = [
"espeak-ng",
"-v", voice_id,
# --- AJUSTEMENTS DE VOIX ---
"-s", "150", # Vitesse: 150 Mots/Minute
"-p", "60", # Hauteur: 60/99
# ---------------------------
"--stdout", # Écrit le WAV sur la sortie standard
chunk
]
try:
# Exécution de la commande et capture de la sortie binaire
# Attention: subprocess.run() ne streame pas la sortie immédiatement,
# il la collecte en mémoire pour les chunks.
result = subprocess.run(command, check=True, capture_output=True, timeout=10)
# Le résultat est un fichier WAV complet pour le chunk
chunk_wav_data = result.stdout
# Ouvrir le chunk WAV pour extraire les données audio brutes (après l'en-tête de 44 octets)
if len(chunk_wav_data) < 44:
continue
# Simplement ajouter les données audio brutes (après l'en-tête)
raw_audio_data = chunk_wav_data[44:]
full_audio_data.write(raw_audio_data)
total_audio_length += len(raw_audio_data)
except subprocess.CalledProcessError as e:
# Renvoyer l'erreur de la commande si elle a échoué
error_message = f"La commande espeak-ng a échoué. Vérifiez la voix '{voice_id}'. Erreur: {e.stderr.decode()}"
sys.stderr.write(f"ERROR: {error_message}\n")
raise Exception(error_message)
except FileNotFoundError:
raise Exception("Le programme 'espeak-ng' n'a pas été trouvé. Vérifiez le Dockerfile.")
# 3. Réinitialiser la position de lecture
full_audio_data.seek(0)
# 4. Générer l'en-tête WAV final avec la taille totale
# NOTE: generate_wav_header() DOIT être corrigée pour utiliser 16.to_bytes() et 1.to_bytes()
final_header = generate_wav_header(total_audio_length)
# 5. Yield l'en-tête
yield final_header
# 6. Yield les données audio brutes par morceaux
chunk_size = 4096 # Taille de chaque morceau envoyé au client
while True:
data = full_audio_data.read(chunk_size)
if not data:
break
yield data
def text_to_audio_file(text, voice_id, output_path="output.wav"):
"""Ancienne fonction, non utilisée dans cette nouvelle approche."""
raise NotImplementedError("Utilisez 'stream_text_to_audio' pour le streaming.") |