RenanOF commited on
Commit
3f4ea60
·
verified ·
1 Parent(s): cfa27bd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +171 -138
app.py CHANGED
@@ -1,165 +1,198 @@
1
  import gradio as gr
2
- from transformers import pipeline
3
  from pydub import AudioSegment
4
- from pydub.utils import make_chunks
5
  import tempfile
6
  import os
7
- import math
8
-
9
- # --- Configurações ---
10
- MODEL_NAME = "openai/whisper-small" # Ou "base", "small" - Cuidado com RAM/Tempo na CPU
11
- CHUNK_LENGTH_MS = 30_000 # 30 segundos por chunk
12
- MAX_FILE_SIZE_MB = 250 # Aumentado para ~120 min (ajuste conforme necessário)
13
- TARGET_SAMPLE_RATE = 16000
14
- # ---------------------
15
-
16
- print(f"Carregando modelo Whisper: {MODEL_NAME}...")
17
- # Inicialize o modelo Whisper
18
- transcriber = pipeline(
19
- "automatic-speech-recognition",
20
- model=MODEL_NAME,
21
- device="cpu" # Mantendo CPU conforme original
22
- )
23
- print("Modelo carregado.")
24
-
25
- # Função para dividir áudios longos
26
- def split_audio(audio_path, chunk_length=CHUNK_LENGTH_MS):
27
  try:
28
- audio = AudioSegment.from_file(audio_path)
29
- print(f"Áudio carregado: Duração={audio.duration_seconds:.2f}s, Canais={audio.channels}, Taxa={audio.frame_rate}Hz")
30
- chunks = make_chunks(audio, chunk_length)
31
- print(f"Áudio dividido em {len(chunks)} chunks de ~{chunk_length/1000}s")
32
- return chunks
33
  except Exception as e:
34
- print(f"Erro ao carregar ou dividir áudio: {e}")
35
- raise # Re-lança a exceção para ser pega na função principal
 
 
 
 
 
 
 
36
 
37
- # Função para comprimir/preparar áudio para Whisper
38
- def prepare_audio(audio_path):
39
  try:
40
- audio = AudioSegment.from_file(audio_path)
41
- # Converter para mono, taxa de amostragem alvo, profundidade de bits padrão (16)
42
- prepared_audio = audio.set_frame_rate(TARGET_SAMPLE_RATE).set_channels(1).set_sample_width(2)
 
 
 
 
 
 
 
43
 
44
- # Usar um arquivo temporário gerenciado pelo 'with' se possível
 
 
45
  with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_f:
46
- prepared_path = temp_f.name
 
 
 
47
 
48
- prepared_audio.export(prepared_path, format="wav")
49
- print(f"Áudio preparado e salvo em: {prepared_path}")
50
- return prepared_path
51
  except Exception as e:
52
- print(f"Erro ao preparar áudio: {e}")
53
- raise
54
 
55
- # Função para transcrever o áudio (agora como gerador para feedback)
56
- def transcribe_audio_generator(audio_filepath):
57
- if audio_filepath is None:
58
- yield "Erro: Nenhum arquivo de áudio enviado."
59
- return
60
 
61
  try:
62
- file_size_bytes = os.path.getsize(audio_filepath)
63
- file_size_mb = file_size_bytes / (1024 * 1024)
64
- print(f"Arquivo recebido: {audio_filepath}, Tamanho: {file_size_mb:.2f} MB")
65
-
66
- # Verificar o tamanho do arquivo
67
- if file_size_bytes > MAX_FILE_SIZE_MB * 1024 * 1024:
68
- yield f"Erro: O arquivo excede o limite de {MAX_FILE_SIZE_MB} MB. Tamanho atual: {file_size_mb:.2f} MB."
69
- return
70
-
71
- yield f"Iniciando pré-processamento (pode levar um tempo)... Tamanho: {file_size_mb:.2f} MB"
72
-
73
- prepared_audio_path = None
74
- try:
75
- prepared_audio_path = prepare_audio(audio_filepath)
76
-
77
- yield f"Pré-processamento concluído. Dividindo em chunks..."
78
- chunks = split_audio(prepared_audio_path)
79
- total_chunks = len(chunks)
80
-
81
- if total_chunks == 0:
82
- yield "Erro: Não foi possível dividir o áudio em chunks."
83
- return
84
-
85
- full_transcription = []
86
- yield f"Iniciando transcrição de {total_chunks} chunks (Modelo: {MODEL_NAME}). Isso pode demorar bastante..."
87
-
88
- # Processar cada parte separadamente
89
- for i, chunk in enumerate(chunks):
90
- # Usar arquivo temporário para o chunk
91
- with tempfile.NamedTemporaryFile(suffix=".wav", delete=True) as temp_chunk_file:
92
- chunk.export(temp_chunk_file.name, format="wav")
93
-
94
- # Transcrever o chunk
95
- # Usar chunk_length para o pipeline pode ajudar em alguns casos
96
- result = transcriber(
97
- temp_chunk_file.name,
98
- chunk_length_s=math.ceil(CHUNK_LENGTH_MS / 1000), # Whisper espera em segundos
99
- return_timestamps=False # Mantido como False
100
- )
101
- transcription = result["text"]
102
-
103
- # Adicionar ao resultado geral
104
- chunk_label = f"[Chunk {i+1}/{total_chunks}]"
105
- full_transcription.append(f"{chunk_label}: {transcription}")
106
-
107
- # Atualizar a interface a cada chunk (ou a cada N chunks)
108
- progress_update = f"Processando: {i+1}/{total_chunks} chunks...\n\n" + "\n".join(full_transcription)
109
- yield progress_update
110
-
111
- yield "Transcrição completa!\n\n" + "\n".join(full_transcription)
112
-
113
- except Exception as e:
114
- yield f"Erro durante o processamento: {str(e)}"
115
- # Log detalhado do erro no console do servidor
116
- import traceback
117
- print("Erro detalhado:")
118
- traceback.print_exc()
119
-
120
- finally:
121
- # Limpar o arquivo preparado se ele foi criado
122
- if prepared_audio_path and os.path.exists(prepared_audio_path):
123
- try:
124
- os.remove(prepared_audio_path)
125
- print(f"Arquivo temporário preparado removido: {prepared_audio_path}")
126
- except OSError as e:
127
- print(f"Erro ao remover arquivo temporário {prepared_audio_path}: {e}")
128
- # O arquivo original (audio_filepath) é gerenciado pelo Gradio
129
- # Os arquivos de chunk são gerenciados pelo 'with tempfile.NamedTemporaryFile'
130
 
131
  except Exception as e:
132
- yield f"Erro inesperado ao processar áudio: {str(e)}"
133
- import traceback
134
- print("Erro detalhado:")
135
- traceback.print_exc()
136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
- # Interface gráfica com Gradio
139
  with gr.Blocks() as demo:
140
- gr.Markdown(f"# 🎙Whisper Transcription - Áudios Longos (até {MAX_FILE_SIZE_MB} MB)")
141
- gr.Markdown(f"**Atenção:** Áudios muito longos podem levar **muito tempo** para processar (potencialmente horas), especialmente com o modelo `{MODEL_NAME}` na CPU.")
142
 
143
- with gr.Row():
144
- with gr.Column(scale=1):
145
- gr.Markdown(f"### 1️⃣ Envie seu áudio (máx. {MAX_FILE_SIZE_MB} MB)")
146
- audio_input = gr.Audio(type="filepath", label="Envie um arquivo de áudio")
147
 
 
148
  with gr.Column(scale=1):
149
- gr.Markdown("### 2️⃣ Resultado da transcrição (atualizado durante o processo)")
150
- transcription_output = gr.Textbox(label="Transcrição", lines=20, interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
- transcribe_button = gr.Button("🚀 Transcrever Áudio")
 
 
 
 
 
 
 
 
153
 
154
- # Vincular ação ao botão - Usando a função geradora
155
- transcribe_button.click(
156
- fn=transcribe_audio_generator,
157
- inputs=[audio_input],
158
- outputs=[transcription_output]
 
 
 
159
  )
160
 
161
- # Rodar a aplicação
162
- print("Iniciando interface Gradio...")
163
- # share=True pode não funcionar bem com processos muito longos em ambientes gratuitos
164
- demo.launch(share=False) # Recomendo testar localmente primeiro (share=False)
165
- print("Interface disponível.")
 
1
  import gradio as gr
 
2
  from pydub import AudioSegment
 
3
  import tempfile
4
  import os
5
+ import io
6
+
7
+ # --- Funções de Edição com Pydub ---
8
+
9
+ def get_audio_duration(audio_filepath):
10
+ """Retorna a duração do áudio em segundos."""
11
+ if not audio_filepath or not os.path.exists(audio_filepath):
12
+ return 0
 
 
 
 
 
 
 
 
 
 
 
 
13
  try:
14
+ audio = AudioSegment.from_file(audio_filepath)
15
+ return audio.duration_seconds
 
 
 
16
  except Exception as e:
17
+ print(f"Erro ao ler duração do áudio: {e}")
18
+ return 0
19
+
20
+ def apply_trim(audio_filepath, start_time_sec, end_time_sec):
21
+ """Corta o áudio entre start_time_sec e end_time_sec."""
22
+ if not audio_filepath or not os.path.exists(audio_filepath):
23
+ raise gr.Error("Nenhum áudio carregado para cortar.")
24
+ if start_time_sec >= end_time_sec:
25
+ raise gr.Error("O tempo de início deve ser menor que o tempo de fim.")
26
 
 
 
27
  try:
28
+ audio = AudioSegment.from_file(audio_filepath)
29
+ start_ms = int(start_time_sec * 1000)
30
+ end_ms = int(end_time_sec * 1000)
31
+
32
+ # Garante que os tempos estão dentro dos limites do áudio
33
+ start_ms = max(0, start_ms)
34
+ end_ms = min(len(audio), end_ms)
35
+
36
+ if start_ms >= end_ms:
37
+ raise gr.Error("Intervalo de corte inválido após ajuste aos limites.")
38
 
39
+ trimmed_audio = audio[start_ms:end_ms]
40
+
41
+ # Salva o resultado em um arquivo temporário para o Gradio exibir
42
  with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_f:
43
+ output_path = temp_f.name
44
+ trimmed_audio.export(output_path, format="wav")
45
+ print(f"Áudio cortado salvo em: {output_path}")
46
+ return output_path
47
 
 
 
 
48
  except Exception as e:
49
+ print(f"Erro ao cortar áudio: {e}")
50
+ raise gr.Error(f"Erro ao cortar áudio: {e}")
51
 
52
+ def apply_volume_change(audio_filepath, volume_db):
53
+ """Aplica uma mudança de volume em dB."""
54
+ if not audio_filepath or not os.path.exists(audio_filepath):
55
+ raise gr.Error("Nenhum áudio carregado para alterar volume.")
 
56
 
57
  try:
58
+ audio = AudioSegment.from_file(audio_filepath)
59
+ adjusted_audio = audio + volume_db # Pydub permite somar dB diretamente
60
+
61
+ # Salva o resultado
62
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_f:
63
+ output_path = temp_f.name
64
+ adjusted_audio.export(output_path, format="wav")
65
+ print(f"Áudio com volume ajustado salvo em: {output_path}")
66
+ return output_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
  except Exception as e:
69
+ print(f"Erro ao ajustar volume: {e}")
70
+ raise gr.Error(f"Erro ao ajustar volume: {e}")
 
 
71
 
72
+ def reverse_audio(audio_filepath):
73
+ """Inverte o áudio."""
74
+ if not audio_filepath or not os.path.exists(audio_filepath):
75
+ raise gr.Error("Nenhum áudio carregado para inverter.")
76
+ try:
77
+ audio = AudioSegment.from_file(audio_filepath)
78
+ reversed_audio = audio.reverse()
79
+
80
+ # Salva o resultado
81
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_f:
82
+ output_path = temp_f.name
83
+ reversed_audio.export(output_path, format="wav")
84
+ print(f"Áudio invertido salvo em: {output_path}")
85
+ return output_path
86
+ except Exception as e:
87
+ print(f"Erro ao inverter áudio: {e}")
88
+ raise gr.Error(f"Erro ao inverter áudio: {e}")
89
+
90
+ # --- Interface Gradio ---
91
 
 
92
  with gr.Blocks() as demo:
93
+ gr.Markdown("# Editor de Áudio Básico 🔊")
 
94
 
95
+ # Usaremos State para manter o caminho do arquivo de áudio atual (original ou editado)
96
+ current_audio_state = gr.State(None)
97
+ audio_duration_state = gr.State(0)
 
98
 
99
+ with gr.Row():
100
  with gr.Column(scale=1):
101
+ gr.Markdown("### 1. Carregue seu Áudio")
102
+ audio_input = gr.Audio(type="filepath", label="Upload ou Grave")
103
+ # Botão para carregar o áudio no estado
104
+ load_button = gr.Button("Carregar Áudio para Edição")
105
+
106
+ with gr.Column(scale=2):
107
+ gr.Markdown("### 2. Edite o Áudio")
108
+
109
+ with gr.Tabs():
110
+ with gr.TabItem("Cortar (Trim)"):
111
+ trim_start_slider = gr.Slider(label="Início (segundos)", minimum=0, maximum=1, step=0.1, interactive=True)
112
+ trim_end_slider = gr.Slider(label="Fim (segundos)", minimum=0, maximum=1, step=0.1, interactive=True)
113
+ apply_trim_button = gr.Button("Aplicar Corte")
114
+
115
+ with gr.TabItem("Volume"):
116
+ volume_slider = gr.Slider(label="Ajuste de Volume (dB)", minimum=-20, maximum=20, value=0, step=1, interactive=True)
117
+ apply_volume_button = gr.Button("Aplicar Volume")
118
+
119
+ with gr.TabItem("Outros Efeitos"):
120
+ reverse_button = gr.Button("Inverter Áudio")
121
+ # Adicione botões para outras funções aqui (Fade In/Out, Speed, etc.)
122
+
123
+ gr.Markdown("### 3. Resultado da Edição")
124
+ audio_output = gr.Audio(label="Áudio Editado", type="filepath", interactive=False)
125
+
126
+
127
+ # --- Lógica de Interação ---
128
+
129
+ def update_sliders(audio_filepath):
130
+ """Atualiza os máximos dos sliders de corte com base na duração do áudio."""
131
+ if not audio_filepath:
132
+ return {
133
+ trim_start_slider: gr.update(maximum=1, value=0),
134
+ trim_end_slider: gr.update(maximum=1, value=1),
135
+ audio_duration_state: 0,
136
+ current_audio_state: None
137
+ }
138
+ duration = get_audio_duration(audio_filepath)
139
+ print(f"Duração detectada: {duration}s")
140
+ return {
141
+ trim_start_slider: gr.update(maximum=duration, value=0),
142
+ trim_end_slider: gr.update(maximum=duration, value=duration),
143
+ audio_duration_state: duration,
144
+ current_audio_state: audio_filepath # Armazena o caminho do áudio carregado
145
+ }
146
+
147
+ # Quando um novo áudio é carregado ou o botão é clicado, atualiza os sliders e o estado
148
+ load_button.click(update_sliders, inputs=[audio_input], outputs=[trim_start_slider, trim_end_slider, audio_duration_state, current_audio_state])
149
+ # Também atualiza se o usuário apenas fizer upload sem clicar no botão (útil)
150
+ audio_input.change(update_sliders, inputs=[audio_input], outputs=[trim_start_slider, trim_end_slider, audio_duration_state, current_audio_state])
151
+
152
+
153
+ def update_output_and_state(new_audio_path):
154
+ """Atualiza o componente de saída e o estado com o novo áudio editado."""
155
+ duration = get_audio_duration(new_audio_path)
156
+ return {
157
+ audio_output: gr.update(value=new_audio_path), # Mostra o resultado
158
+ current_audio_state: new_audio_path, # Atualiza o estado para a próxima edição
159
+ trim_start_slider: gr.update(maximum=duration, value=0), # Atualiza sliders para nova duração
160
+ trim_end_slider: gr.update(maximum=duration, value=duration)
161
+ }
162
+
163
+ # Ações dos botões de edição
164
+ apply_trim_button.click(
165
+ apply_trim,
166
+ inputs=[current_audio_state, trim_start_slider, trim_end_slider],
167
+ outputs=[audio_output] # A saída direta vai para o componente de áudio
168
+ ).then(
169
+ update_output_and_state, # Função para atualizar o estado *depois* da edição
170
+ inputs=[audio_output], # Pega o caminho do resultado do passo anterior
171
+ outputs=[audio_output, current_audio_state, trim_start_slider, trim_end_slider] # Atualiza tudo
172
+ )
173
 
174
+ apply_volume_button.click(
175
+ apply_volume_change,
176
+ inputs=[current_audio_state, volume_slider],
177
+ outputs=[audio_output]
178
+ ).then(
179
+ update_output_and_state,
180
+ inputs=[audio_output],
181
+ outputs=[audio_output, current_audio_state, trim_start_slider, trim_end_slider]
182
+ )
183
 
184
+ reverse_button.click(
185
+ reverse_audio,
186
+ inputs=[current_audio_state],
187
+ outputs=[audio_output]
188
+ ).then(
189
+ update_output_and_state,
190
+ inputs=[audio_output],
191
+ outputs=[audio_output, current_audio_state, trim_start_slider, trim_end_slider]
192
  )
193
 
194
+
195
+ # --- Execução ---
196
+ # Lembre-se: Instale ffmpeg (sudo apt update && sudo apt install ffmpeg) se não tiver
197
+ # pip install pydub gradio
198
+ demo.launch(debug=True) # Debug=True ajuda a ver erros