pierreguillou commited on
Commit
bd84e99
·
verified ·
1 Parent(s): dbed228

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +32 -223
app.py CHANGED
@@ -4,26 +4,22 @@ from transformers import pipeline
4
  from pyannote.audio import Pipeline
5
  from pydub import AudioSegment, effects, silence
6
  import os
7
- import datetime
8
- from langdetect import detect
9
- from langdetect.lang_detect_exception import LangDetectException
10
 
11
  # --- Configuração ---
12
  HF_TOKEN = os.environ.get("HF_TOKEN")
13
- MODEL_NAME = "openai/whisper-medium" # modelo principal para a transcrição final
14
- LANG_MODEL_NAME = "openai/whisper-tiny" # modelo leve dedicado à detecção rápida
15
 
16
  device = 0 if torch.cuda.is_available() else "cpu"
17
  torch_dtype = torch.float16 if torch.cuda.is_available() else torch.float32
18
 
19
- # --- Inicialização dos modelos ---
20
  pipe = pipeline(
21
  "automatic-speech-recognition",
22
  model=MODEL_NAME,
23
  torch_dtype=torch_dtype,
24
  device=device,
25
  )
26
-
27
  lang_pipe = pipeline(
28
  "automatic-speech-recognition",
29
  model=LANG_MODEL_NAME,
@@ -41,14 +37,14 @@ else:
41
  pyannote_pipeline = None
42
  print("Aviso: O token Hugging Face não está definido. A diarização será desativada.")
43
 
44
- # --- Funções de utilidade ---
45
 
46
  def convert_to_wav(audio_path):
47
  """Converte qualquer arquivo de áudio para WAV mono 16 kHz."""
48
  try:
49
  audio = AudioSegment.from_file(audio_path)
50
  audio = audio.set_channels(1)
51
- audio = audio.set_frame_rate(16000) # padrão Whisper
52
  wav_path = os.path.splitext(audio_path)[0] + ".wav"
53
  audio.export(wav_path, format="wav")
54
  return wav_path
@@ -56,96 +52,6 @@ def convert_to_wav(audio_path):
56
  print(f"Erro ao converter para WAV: {e}")
57
  return None
58
 
59
- def make_speech_head_wav(input_wav_path, max_seconds=6, min_silence_len_ms=300, silence_thresh_db=None):
60
- """
61
- Cria um trecho inicial (até max_seconds) contendo fala.
62
- - Remove o silêncio inicial.
63
- - Garante captura de fala em janela.
64
- """
65
- try:
66
- audio = AudioSegment.from_wav(input_wav_path)
67
- normalized = effects.normalize(audio)
68
-
69
- if silence_thresh_db is None:
70
- silence_thresh_db = normalized.dBFS - 16 # mais permissivo
71
-
72
- start_trim = silence.detect_leading_silence(
73
- normalized,
74
- silence_thresh=silence_thresh_db,
75
- chunk_size=10
76
- )
77
- trimmed = normalized[start_trim:]
78
-
79
- if len(trimmed) < 500:
80
- clip = normalized[: max_seconds * 1000]
81
- else:
82
- window_ms = 6000
83
- step_ms = 3000
84
- pos = 0
85
- selected = None
86
- while pos < len(trimmed) and pos < 60000:
87
- candidate = trimmed[pos: pos + window_ms]
88
- nonsil = silence.detect_nonsilent(
89
- candidate,
90
- min_silence_len=min_silence_len_ms,
91
- silence_thresh=silence_thresh_db
92
- )
93
- if nonsil:
94
- selected = candidate
95
- break
96
- pos += step_ms
97
- clip = selected if selected is not None else trimmed[: window_ms]
98
-
99
- clip = clip[: max_seconds * 1000]
100
- short_path = os.path.splitext(input_wav_path)[0] + f"_head_speech_{max_seconds}s.wav"
101
- clip.export(short_path, format="wav")
102
- return short_path
103
- except Exception as e:
104
- print(f"Erro ao criar o trecho de fala: {e}")
105
- return None
106
-
107
- def detect_language_on_upload(filepath):
108
- """Detecta rapidamente o idioma via Whisper-tiny + LangDetect."""
109
- if filepath is None:
110
- return "auto"
111
- try:
112
- wav_filepath = convert_to_wav(filepath)
113
- if not wav_filepath:
114
- return "auto"
115
-
116
- short_wav = make_speech_head_wav(wav_filepath, max_seconds=6) or wav_filepath
117
-
118
- outputs = lang_pipe(
119
- short_wav,
120
- chunk_length_s=6,
121
- return_timestamps=False
122
- )
123
-
124
- transcribed_text = outputs.get("text", "").strip()
125
- whisper_lang = outputs.get("language")
126
- if whisper_lang and isinstance(whisper_lang, str) and len(whisper_lang) <= 5:
127
- return whisper_lang
128
-
129
- if len(transcribed_text) < 10:
130
- return "auto"
131
-
132
- detected_lang = detect(transcribed_text)
133
- lang_mapping = {
134
- 'fr': 'fr','en': 'en','es': 'es','de': 'de','it': 'it','pt': 'pt',
135
- 'nl': 'nl','pl': 'pl','ru': 'ru','ja': 'ja','ko': 'ko','zh-cn': 'zh','zh': 'zh'
136
- }
137
- return lang_mapping.get(detected_lang, "auto")
138
- except (LangDetectException, Exception) as e:
139
- print(f"Erro ao detectar idioma: {e}")
140
- return "auto"
141
-
142
- def save_txt(content, filename):
143
- if not content or content.strip() == "":
144
- return None
145
- with open(filename, "w", encoding="utf-8") as f:
146
- f.write(content)
147
- return filename
148
-
149
  def ensure_mp3_same_name_as_input(input_path, source_wav_path):
150
  """
151
  Cria um arquivo MP3 com o mesmo nome base do arquivo de entrada.
@@ -161,160 +67,63 @@ def ensure_mp3_same_name_as_input(input_path, source_wav_path):
161
  print(f"Erro ao exportar MP3: {e}")
162
  return None
163
 
164
- def transcribe_audio(filepath, diarize, language_choice):
 
 
165
  """
166
- Mantém o input como está. Output principal: sempre gerar um MP3 para ouvir e baixar,
167
- com o mesmo nome do arquivo de entrada (extensão .mp3).
168
- Também devolve os campos de texto/arquivo já existentes para compatibilidade.
 
 
169
  """
170
  if filepath is None:
171
- return (
172
- "Nenhum arquivo de áudio fornecido.",
173
- "Por favor, envie um arquivo de áudio.",
174
- "",
175
- None,
176
- None,
177
- None, # mp3 file (download)
178
- None # mp3 playable (audio component)
179
- )
180
 
181
  wav_filepath = convert_to_wav(filepath)
182
  if not wav_filepath:
183
- return (
184
- "Erro: O arquivo de áudio não pôde ser convertido.",
185
- "Falha na conversão.",
186
- "",
187
- None,
188
- None,
189
- None,
190
- None
191
- )
192
-
193
- whisper_params = {
194
- "chunk_length_s": 30,
195
- "batch_size": 24,
196
- "return_timestamps": True
197
- }
198
- if language_choice != "auto":
199
- whisper_params["generate_kwargs"] = {"language": language_choice}
200
-
201
- outputs = pipe(wav_filepath, **whisper_params)
202
- transcription = outputs.get("text", "").strip()
203
-
204
- detected_language = outputs.get("language", "Não disponível")
205
- language_info = f"Idioma detectado: {detected_language}"
206
- if language_choice != "auto":
207
- language_info += f" (Idioma forçado: {language_choice})"
208
-
209
- diarized_transcription = ""
210
- if diarize and pyannote_pipeline:
211
- try:
212
- diarization = pyannote_pipeline(wav_filepath)
213
- for turn, _, speaker in diarization.itertracks(yield_label=True):
214
- segment_start = turn.start
215
- segment_end = turn.end
216
- segment_text = ""
217
- for chunk in outputs.get("chunks", []):
218
- chunk_start = chunk['timestamp'][0]
219
- chunk_end = chunk['timestamp'][1]
220
- if chunk_start is not None and chunk_end is not None:
221
- if max(segment_start, chunk_start) < min(segment_end, chunk_end):
222
- segment_text += chunk['text']
223
- start_time = str(datetime.timedelta(seconds=int(segment_start)))
224
- diarized_transcription += f"[{start_time}] {speaker}:{segment_text.strip()}\n"
225
- except Exception as e:
226
- diarized_transcription = f"Erro durante a diarização: {e}"
227
- elif diarize:
228
- diarized_transcription = "Diarização ativada, mas o modelo não pôde ser carregado (token ausente?)."
229
- else:
230
- diarized_transcription = "Diarização não ativada."
231
 
232
- transcription_file = save_txt(transcription, "transcription.txt")
233
- diarization_file = save_txt(diarized_transcription, "transcription_diarized.txt")
234
-
235
- # Sempre gerar MP3 com o mesmo nome do arquivo de entrada
236
  mp3_path = ensure_mp3_same_name_as_input(filepath, wav_filepath)
237
-
238
- # Retornos: textos + arquivos .txt + arquivo MP3 (download) + MP3 tocável
239
- # O gr.Audio aceita caminho de arquivo para tocar o áudio.
240
- return (
241
- transcription,
242
- diarized_transcription,
243
- language_info,
244
- transcription_file,
245
- diarization_file,
246
- mp3_path, # File para download
247
- mp3_path # Audio para playback
248
- )
249
 
250
  # --- Interface Gradio ---
251
 
252
  with gr.Blocks() as demo:
253
- gr.HTML("<div style='text-align:center;'><h1>Transcrição e Diarização de Arquivos Áudio</h1></div>")
254
- gr.Markdown("Transcreva e diarize automaticamente seus arquivos de áudio (WhatsApp (opus), wav, mp3, m4a, etc.) com Whisper e pyannote, diretamente neste Space.")
255
-
256
- gr.Markdown("""
257
- ## 🚀 Como usar o aplicativo
258
-
259
- 1. Envie um arquivo de áudio (opus, wav, mp3, m4a, etc.): o idioma principal será detectado automaticamente ou permanecerá em "auto"
260
- 2. Escolha o idioma ou deixe em "auto"
261
- 3. Ative ou não a opção "Diarização"
262
- 4. Clique em "Transcrever"
263
- 5. Obtenha a transcrição e, se ativado, a versão diarizada (por locutor)
264
- 6. Agora o output inclui sempre um MP3 com o mesmo nome do arquivo de entrada (para ouvir e baixar)
265
- 7. Resete os arquivos antes de uma nova transcrição
266
- """)
267
 
268
  with gr.Row():
269
  with gr.Column():
270
- audio_input = gr.Audio(type="filepath", label="Enviar um arquivo de áudio")
 
271
  language_dropdown = gr.Dropdown(
272
  choices=["auto", "fr", "en", "es", "de", "it", "pt", "nl", "pl", "ru", "ja", "ko", "zh"],
273
  value="auto",
274
- label="Idioma (auto = detecção automática)",
275
- info="Escolha o idioma ou deixe em 'auto' para detecção automática"
276
  )
277
- diarize_checkbox = gr.Checkbox(label="Ativar Diarização", value=True)
278
- submit_btn = gr.Button("Transcrever", variant="primary")
279
- reset_btn = gr.Button("Resetar", variant="secondary")
280
  with gr.Column():
281
- language_info_output = gr.Textbox(label="Informação sobre o idioma", lines=1)
282
- transcription_file = gr.File(label="Baixar transcrição (.txt)")
283
- transcription_output = gr.Textbox(label="Transcrição Completa", lines=10)
284
- diarization_file = gr.File(label="Baixar transcrição diarizada (.txt)")
285
- diarization_output = gr.Textbox(label="Transcrição com Diarização (por locutor)", lines=15)
286
-
287
- # Novos componentes para MP3:
288
- mp3_download = gr.File(label="Baixar áudio de saída (.mp3)")
289
- mp3_playback = gr.Audio(label="Ouvir áudio de saída (.mp3)", type="filepath")
290
-
291
- audio_input.change(
292
- fn=detect_language_on_upload,
293
- inputs=audio_input,
294
- outputs=language_dropdown
295
- )
296
 
297
  submit_btn.click(
298
- fn=transcribe_audio,
299
  inputs=[audio_input, diarize_checkbox, language_dropdown],
300
- outputs=[
301
- transcription_output, diarization_output, language_info_output,
302
- transcription_file, diarization_file,
303
- mp3_download, mp3_playback
304
- ]
305
  )
306
 
307
  def reset_fields():
308
- return "", "", "", None, None, None, None, "auto", True
309
 
310
  reset_btn.click(
311
  fn=reset_fields,
312
  inputs=[],
313
- outputs=[
314
- transcription_output, diarization_output, language_info_output,
315
- transcription_file, diarization_file, mp3_download, mp3_playback,
316
- language_dropdown, diarize_checkbox
317
- ]
318
  )
319
 
320
  demo.launch(share=True)
 
4
  from pyannote.audio import Pipeline
5
  from pydub import AudioSegment, effects, silence
6
  import os
 
 
 
7
 
8
  # --- Configuração ---
9
  HF_TOKEN = os.environ.get("HF_TOKEN")
10
+ MODEL_NAME = "openai/whisper-medium" # mantido (não utilizado para saída MP3)
11
+ LANG_MODEL_NAME = "openai/whisper-tiny" # mantido (não utilizado para saída MP3)
12
 
13
  device = 0 if torch.cuda.is_available() else "cpu"
14
  torch_dtype = torch.float16 if torch.cuda.is_available() else torch.float32
15
 
16
+ # Mantidos para não quebrar a inicialização (não usados na saída)
17
  pipe = pipeline(
18
  "automatic-speech-recognition",
19
  model=MODEL_NAME,
20
  torch_dtype=torch_dtype,
21
  device=device,
22
  )
 
23
  lang_pipe = pipeline(
24
  "automatic-speech-recognition",
25
  model=LANG_MODEL_NAME,
 
37
  pyannote_pipeline = None
38
  print("Aviso: O token Hugging Face não está definido. A diarização será desativada.")
39
 
40
+ # --- Utilitários mínimos ---
41
 
42
  def convert_to_wav(audio_path):
43
  """Converte qualquer arquivo de áudio para WAV mono 16 kHz."""
44
  try:
45
  audio = AudioSegment.from_file(audio_path)
46
  audio = audio.set_channels(1)
47
+ audio = audio.set_frame_rate(16000)
48
  wav_path = os.path.splitext(audio_path)[0] + ".wav"
49
  audio.export(wav_path, format="wav")
50
  return wav_path
 
52
  print(f"Erro ao converter para WAV: {e}")
53
  return None
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  def ensure_mp3_same_name_as_input(input_path, source_wav_path):
56
  """
57
  Cria um arquivo MP3 com o mesmo nome base do arquivo de entrada.
 
67
  print(f"Erro ao exportar MP3: {e}")
68
  return None
69
 
70
+ # --- Função principal (apenas MP3 como saída) ---
71
+
72
+ def make_output_mp3(filepath, diarize, language_choice):
73
  """
74
+ Mantém o input como está, mas ignora para a saída:
75
+ - Sempre gera e retorna apenas um MP3 (mesmo nome do arquivo de entrada, .mp3).
76
+ Retorna duas saídas:
77
+ - Caminho do MP3 para download (gr.File)
78
+ - Caminho do MP3 para playback (gr.Audio)
79
  """
80
  if filepath is None:
81
+ return None, None
 
 
 
 
 
 
 
 
82
 
83
  wav_filepath = convert_to_wav(filepath)
84
  if not wav_filepath:
85
+ return None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
 
 
 
 
87
  mp3_path = ensure_mp3_same_name_as_input(filepath, wav_filepath)
88
+ return mp3_path, mp3_path
 
 
 
 
 
 
 
 
 
 
 
89
 
90
  # --- Interface Gradio ---
91
 
92
  with gr.Blocks() as demo:
93
+ gr.HTML("<div style='text-align:center;'><h1>Sortie MP3 (même nom que l'entrée)</h1></div>")
94
+ gr.Markdown("Uploadez un fichier audio. La sortie sera toujours un .mp3 avec le même nom de base, jouable et téléchargeable.")
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
  with gr.Row():
97
  with gr.Column():
98
+ # Partie input conservée (inchangée)
99
+ audio_input = gr.Audio(type="filepath", label="Envoyer un fichier audio")
100
  language_dropdown = gr.Dropdown(
101
  choices=["auto", "fr", "en", "es", "de", "it", "pt", "nl", "pl", "ru", "ja", "ko", "zh"],
102
  value="auto",
103
+ label="Langue (auto = détection automatique)",
104
+ info="Conservé pour compatibilité (non utilisé pour la sortie MP3)"
105
  )
106
+ diarize_checkbox = gr.Checkbox(label="Activer Diarisation", value=True)
107
+ submit_btn = gr.Button("Générer MP3", variant="primary")
108
+ reset_btn = gr.Button("Reset", variant="secondary")
109
  with gr.Column():
110
+ # Seules sorties: MP3 en download + player
111
+ mp3_download = gr.File(label="Télécharger la sortie (.mp3)")
112
+ mp3_playback = gr.Audio(label="Écouter la sortie (.mp3)", type="filepath")
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  submit_btn.click(
115
+ fn=make_output_mp3,
116
  inputs=[audio_input, diarize_checkbox, language_dropdown],
117
+ outputs=[mp3_download, mp3_playback]
 
 
 
 
118
  )
119
 
120
  def reset_fields():
121
+ return None, None, "auto", True
122
 
123
  reset_btn.click(
124
  fn=reset_fields,
125
  inputs=[],
126
+ outputs=[mp3_download, mp3_playback, language_dropdown, diarize_checkbox]
 
 
 
 
127
  )
128
 
129
  demo.launch(share=True)