File size: 8,378 Bytes
02a4a77
a011c0d
 
 
02a4a77
a011c0d
 
02a4a77
a011c0d
bb459b1
 
a011c0d
 
 
 
 
 
 
 
 
 
 
 
 
02a4a77
 
 
 
d91a245
 
 
 
 
 
02a4a77
d91a245
 
02a4a77
beb567e
a011c0d
f6ca389
 
a011c0d
 
d91a245
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
beb567e
a011c0d
d91a245
 
 
a011c0d
 
8f49c72
f6ca389
8f49c72
a011c0d
9805c24
a011c0d
 
d91a245
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
beb567e
f6ca389
d91a245
 
a011c0d
02a4a77
 
 
 
 
d91a245
 
 
 
 
02a4a77
d91a245
 
02a4a77
f6ca389
bd84e99
0f6857a
02a4a77
8f49c72
02a4a77
 
9805c24
02a4a77
 
 
9805c24
02a4a77
 
73cb65c
 
 
9805c24
73cb65c
9805c24
73cb65c
 
02a4a77
 
 
 
10b0a2f
 
8f49c72
 
f6ca389
d91a245
f6ca389
 
8f49c72
02a4a77
 
d91a245
 
 
 
 
 
 
 
 
 
 
 
 
 
02a4a77
f6ca389
a011c0d
d91a245
 
 
a011c0d
 
02a4a77
d91a245
 
 
02a4a77
 
 
d91a245
02a4a77
 
d91a245
 
 
02a4a77
 
 
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
import gradio as gr
import torch
from transformers import pipeline
from pydub import AudioSegment, effects, silence
import os
from langdetect import detect
from langdetect.lang_detect_exception import LangDetectException

# --- Configuration ---
#LANG_MODEL_NAME = "openai/whisper-tiny"  # modèle léger pour la détection de langue
LANG_MODEL_NAME = "openai/whisper-base"  # modèle medium pour une meilleure détection de langue

device = 0 if torch.cuda.is_available() else "cpu"
torch_dtype = torch.float16 if torch.cuda.is_available() else torch.float32

# Pipeline léger pour la détection de langue
lang_pipe = pipeline(
    "automatic-speech-recognition",
    model=LANG_MODEL_NAME,
    torch_dtype=torch_dtype,
    device=device,
)

# --- Fonctions utilitaires ---

def convert_to_wav(audio_path):
    """Converte qualquer arquivo de áudio para WAV mono 16 kHz."""
    try:
        audio = AudioSegment.from_file(audio_path)
        audio = audio.set_channels(1)
        audio = audio.set_frame_rate(16000)
        wav_path = os.path.splitext(audio_path)[0] + ".wav"
        audio.export(wav_path, format="wav")
        return wav_path
    except Exception as e:
        print(f"Erro ao converter para WAV: {e}")
        return None

def make_speech_head_wav(input_wav_path, max_seconds=7):
    """
    Version simplifiée et robuste : prend les premiers max_seconds
    après suppression du silence initial (avec protection contre les boucles).
    """
    try:
        audio = AudioSegment.from_wav(input_wav_path)
        
        # Si l'audio est déjà très court, le retourner tel quel
        if len(audio) <= max_seconds * 1000:
            return input_wav_path
        
        # Normalisation simple
        normalized = effects.normalize(audio)
        
        # Tentative rapide de suppression du silence initial
        try:
            silence_thresh = normalized.dBFS - 20
            # Limiter la recherche aux 30 premières secondes maximum
            search_audio = normalized[:30000]  
            start_trim = silence.detect_leading_silence(
                search_audio,
                silence_threshold=silence_thresh,
                chunk_size=100
            )
            # Limiter le trim à 15 secondes max pour éviter les cas extrêmes
            start_trim = min(start_trim, 15000)
            trimmed = normalized[start_trim:]
        except:
            # En cas d'erreur, utiliser l'audio original
            trimmed = normalized
        
        # Si trop court après trim, utiliser l'original
        if len(trimmed) < 2000:  # Moins de 2 secondes
            trimmed = normalized
        
        # Prendre simplement les premiers max_seconds
        clip = trimmed[:max_seconds * 1000]
        
        short_path = os.path.splitext(input_wav_path)[0] + f"_head_{max_seconds}s.wav"
        clip.export(short_path, format="wav")
        return short_path
    
    except Exception as e:
        print(f"Erro ao criar o trecho: {e}")
        # En cas d'erreur, retourner le fichier original
        return input_wav_path

def detect_language_on_upload(filepath):
    """
    Détection rapide et robuste de langue avec timeout et fallbacks.
    """
    if filepath is None:
        return "auto"
    
    try:
        print(f"Début détection langue pour: {filepath}")
        
        wav_filepath = convert_to_wav(filepath)
        if not wav_filepath:
            print("Échec conversion WAV")
            return "auto"

        # Créer un extrait court (5 secondes max)
        short_wav = make_speech_head_wav(wav_filepath, max_seconds=7)
        if not short_wav:
            short_wav = wav_filepath

        print(f"Analyse du fichier: {short_wav}")
        
        # Transcrição avec paramètres conservateurs
        outputs = lang_pipe(
            short_wav,
            chunk_length_s=5,
            return_timestamps=False,
            generate_kwargs={"max_new_tokens": 50}  # Limiter pour éviter les timeouts
        )

        transcribed_text = outputs.get("text", "").strip()
        print(f"Texte transcrit: {transcribed_text[:100]}...")

        # Priorité au language détecté par Whisper
        whisper_lang = outputs.get("language")
        if whisper_lang and isinstance(whisper_lang, str) and len(whisper_lang) <= 5:
            print(f"Langue Whisper détectée: {whisper_lang}")
            return whisper_lang

        # Si texte trop court, retourner auto
        if len(transcribed_text) < 10:
            print("Texte trop court, retour auto")
            return "auto"

        # Fallback avec LangDetect
        detected_lang = detect(transcribed_text)
        print(f"Langue LangDetect: {detected_lang}")

        # Mapping des codes de langue
        lang_mapping = {
            'fr': 'fr', 'en': 'en', 'es': 'es', 'de': 'de', 'it': 'it',
            'pt': 'pt', 'nl': 'nl', 'pl': 'pl', 'ru': 'ru', 'ja': 'ja',
            'ko': 'ko', 'zh-cn': 'zh', 'zh': 'zh'
        }
        
        result = lang_mapping.get(detected_lang, "auto")
        print(f"Résultat final: {result}")
        return result
    
    except Exception as e:
        print(f"Erreur détection langue: {e}")
        return "auto"

def ensure_mp3_same_name_as_input(input_path, source_wav_path):
    """
    Cria um arquivo MP3 com o mesmo nome base do arquivo de entrada.
    """
    try:
        base, _ = os.path.splitext(os.path.basename(input_path))
        mp3_path = f"{base}.mp3"
        audio = AudioSegment.from_wav(source_wav_path)
        audio.export(mp3_path, format="mp3", bitrate="192k")
        return mp3_path
    except Exception as e:
        print(f"Erro ao exportar MP3: {e}")
        return None

# --- Fonction principale ---

def make_output_mp3(filepath, language_choice):
    """
    Conversion audio vers MP3 avec détection de langue optimisée.
    """
    if filepath is None:
        return None, None, ""

    wav_filepath = convert_to_wav(filepath)
    if not wav_filepath:
        return None, None, ""

    mp3_path = ensure_mp3_same_name_as_input(filepath, wav_filepath)
    
    # Information sur la langue
    if language_choice == "auto":
        language_info = "Langue détectée automatiquement"
    else:
        language_info = f"Langue détectée: {language_choice}"
    
    return mp3_path, mp3_path, language_info

# --- Interface Gradio ---

with gr.Blocks() as demo:
    gr.HTML("<div style='text-align:center;'><h1>Conversion audio vers format MP3</h1></div>")
    gr.Markdown("Uploadez un fichier audio. La sortie sera toujours un .mp3 avec le même nom de base, écoutable en ligne et téléchargeable.")
    
    gr.Markdown("""
    ## ⚡ **Version optimisée**
    - **Détection rapide** : Analyse les 7 premières secondes (hors silence initial)
    - **Robuste** : Fonctionne avec tous types de fichiers
    - **Timeout protection** : Évite les blocages
    """)

    with gr.Row():
        with gr.Column():
            audio_input = gr.Audio(type="filepath", label="Envoyer un fichier audio")
            language_dropdown = gr.Dropdown(
                choices=["auto", "fr", "en", "es", "de", "it", "pt", "nl", "pl", "ru", "ja", "ko", "zh"],
                value="auto",
                label="Langue (auto = détection automatique)",
                info="Détection automatique rapide après upload"
            )
            submit_btn = gr.Button("Générer MP3", variant="primary")
            reset_btn = gr.Button("Reset", variant="secondary")
        with gr.Column():
            language_info_output = gr.Textbox(label="Information sur la langue", lines=1)
            mp3_download = gr.File(label="Télécharger la sortie (.mp3)")
            mp3_playback = gr.Audio(label="Écouter la sortie (.mp3)", type="filepath")

    # Détection automatique de langue lors de l'upload
    audio_input.change(
        fn=detect_language_on_upload,
        inputs=audio_input,
        outputs=language_dropdown
    )

    submit_btn.click(
        fn=make_output_mp3,
        inputs=[audio_input, language_dropdown],
        outputs=[mp3_download, mp3_playback, language_info_output]
    )

    def reset_fields():
        return None, None, "auto", ""

    reset_btn.click(
        fn=reset_fields,
        inputs=[],
        outputs=[audio_input, mp3_download, language_dropdown, language_info_output]
    )

demo.launch(share=True)