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.")