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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +138 -171
app.py CHANGED
@@ -1,198 +1,165 @@
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
 
 
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.")