Skip to content

Commit b7425ca

Browse files
committed
Update transcribe_wav.py
1 parent d6a5fd4 commit b7425ca

File tree

1 file changed

+177
-63
lines changed

1 file changed

+177
-63
lines changed

scripts/transcribe_wav.py

Lines changed: 177 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -147,42 +147,176 @@ def convert_audio_to_wav(input_path, output_path):
147147
print(f" Errore durante la conversione: {e}")
148148
return False
149149

150-
def speed_up_audio(input_path, output_path, speed_factor=2.0):
150+
def split_audio_into_chunks(input_path, chunk_duration=300):
151151
"""
152-
Accelera un file audio utilizzando FFmpeg.
152+
Divide un file audio in chunk consecutivi per il processamento parallelo.
153153
Args:
154-
input_path: Percorso del file audio originale
155-
output_path: Percorso del file audio accelerato
156-
speed_factor: Fattore di velocità (2.0 = 2x velocità)
154+
input_path: Percorso del file audio da dividere
155+
chunk_duration: Durata di ogni chunk in secondi (default: 5 minuti)
157156
Returns:
158-
bool: True se l'operazione è riuscita, False altrimenti
157+
list: Lista dei percorsi dei chunk creati, o None se fallisce
159158
"""
160159
try:
161-
# Comando FFmpeg per accelerare l'audio mantenendo il pitch
162-
cmd = [
160+
# Crea directory temporanea per i chunk
161+
temp_dir = os.path.dirname(input_path)
162+
base_name = os.path.splitext(os.path.basename(input_path))[0]
163+
164+
# Crea i percorsi per i due chunk
165+
chunk1_path = os.path.join(temp_dir, f"{base_name}_chunk1.wav")
166+
chunk2_path = os.path.join(temp_dir, f"{base_name}_chunk2.wav")
167+
168+
# Usa FFprobe per ottenere la durata totale
169+
ffprobe_cmd = [
170+
'ffprobe', '-v', 'quiet', '-show_entries',
171+
'format=duration', '-of', 'csv=p=0', input_path
172+
]
173+
174+
result = subprocess.run(ffprobe_cmd, capture_output=True, text=True, timeout=10)
175+
if result.returncode != 0:
176+
print(" Impossibile ottenere durata audio")
177+
return None
178+
179+
total_duration = float(result.stdout.strip())
180+
181+
# Se l'audio è più corto di chunk_duration * 1.5, elabora come singolo chunk
182+
if total_duration < chunk_duration * 1.5:
183+
print(f" Audio corto ({total_duration:.1f}s), elaborazione singola")
184+
return [input_path]
185+
186+
# Calcola punto di divisione (metà circa)
187+
split_point = total_duration / 2
188+
189+
print(f" Divisione audio in 2 chunk da ~{split_point:.1f}s cadauno")
190+
191+
# Crea primo chunk (da 0 a split_point)
192+
cmd1 = [
163193
'ffmpeg', '-y', '-i', input_path,
164-
'-filter:a', f'atempo={speed_factor}',
165-
'-vn', output_path
194+
'-t', str(split_point),
195+
'-acodec', 'pcm_s16le', '-ar', '44100',
196+
chunk1_path
166197
]
167198

168-
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
199+
# Crea secondo chunk (da split_point a fine)
200+
cmd2 = [
201+
'ffmpeg', '-y', '-i', input_path,
202+
'-ss', str(split_point),
203+
'-acodec', 'pcm_s16le', '-ar', '44100',
204+
chunk2_path
205+
]
169206

170-
if result.returncode == 0:
171-
print(f" Audio accelerato {speed_factor}x: {os.path.basename(input_path)}")
172-
return True
173-
else:
174-
print(f" Errore nell'accelerazione audio: {result.stderr}")
175-
return False
207+
# Crea i chunk in sequenza
208+
print(" Creazione chunk 1...")
209+
result1 = subprocess.run(cmd1, capture_output=True, text=True, timeout=60)
210+
211+
if result1.returncode != 0:
212+
print(" Errore creazione chunk 1")
213+
return None
214+
215+
print(" Creazione chunk 2...")
216+
result2 = subprocess.run(cmd2, capture_output=True, text=True, timeout=60)
217+
218+
if result2.returncode != 0:
219+
print(" Errore creazione chunk 2")
220+
# Pulisce chunk 1 se chunk 2 fallisce
221+
if os.path.exists(chunk1_path):
222+
os.remove(chunk1_path)
223+
return None
224+
225+
print(" Chunk creati con successo")
226+
return [chunk1_path, chunk2_path]
176227

177-
except subprocess.TimeoutExpired:
178-
print(" Timeout nell'accelerazione audio")
179-
return False
180-
except FileNotFoundError:
181-
print(" FFmpeg non trovato. Installa FFmpeg per utilizzare la velocità 2x")
182-
return False
183228
except Exception as e:
184-
print(f" Errore durante l'accelerazione: {e}")
185-
return False
229+
print(f" Errore durante la divisione audio: {e}")
230+
return None
231+
232+
def transcribe_chunk_parallel(chunk_path, model_name='medium', language='it'):
233+
"""
234+
Trascrive un singolo chunk audio utilizzando Whisper.
235+
Args:
236+
chunk_path: Percorso del chunk da trascrivere
237+
model_name: Nome del modello Whisper
238+
language: Lingua del contenuto
239+
Returns:
240+
str: Testo trascritto del chunk
241+
"""
242+
try:
243+
whisper, tqdm = import_required_modules()
244+
245+
# Suppress FP16 warning
246+
with warnings.catch_warnings():
247+
warnings.filterwarnings("ignore", message="FP16 is not supported on CPU; using FP32 instead")
248+
model = whisper.load_model(model_name)
249+
250+
# Trascrive il chunk
251+
result = model.transcribe(chunk_path, language=language)
252+
return result['text']
253+
254+
except Exception as e:
255+
print(f" Errore nella trascrizione del chunk {os.path.basename(chunk_path)}: {e}")
256+
return ""
257+
258+
def transcribe_audio_parallel(file_path, model_name='medium', language='it'):
259+
"""
260+
Trascrive un file audio dividendo in chunk e processando in parallelo.
261+
Args:
262+
file_path: Percorso del file audio da trascrivere
263+
model_name: Nome del modello Whisper
264+
language: Lingua del contenuto
265+
Returns:
266+
str: Testo trascritto completo
267+
"""
268+
import concurrent.futures
269+
import time
270+
271+
print("Avvio trascrizione parallela...")
272+
273+
# Dividi l'audio in chunk
274+
chunks = split_audio_into_chunks(file_path)
275+
276+
if not chunks or len(chunks) == 1:
277+
# Se non è stato possibile dividere o audio troppo corto, trascrizione singola
278+
print("Esecuzione trascrizione singola (audio corto o indivisibile)")
279+
return transcribe_podcast_with_progress(file_path, model_name, language, speed_up=False)
280+
281+
print(f"Trascrizione parallela di {len(chunks)} chunk...")
282+
283+
start_time = time.time()
284+
285+
try:
286+
# Avvia trascrizione parallela dei chunk
287+
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
288+
# Invia i job per i due chunk
289+
future1 = executor.submit(transcribe_chunk_parallel, chunks[0], model_name, language)
290+
future2 = executor.submit(transcribe_chunk_parallel, chunks[1], model_name, language)
291+
292+
# Attende i risultati
293+
print("Elaborazione chunk in corso...")
294+
chunk1_text = future1.result(timeout=600) # 10 minuti timeout
295+
chunk2_text = future2.result(timeout=600)
296+
297+
# Unisce i risultati
298+
full_transcription = chunk1_text.strip() + " " + chunk2_text.strip()
299+
300+
elapsed = time.time() - start_time
301+
print(f"Trascrizione parallela completata in {elapsed:.1f} secondi")
302+
303+
# Pulisce i chunk se sono stati creati
304+
for chunk in chunks:
305+
if chunk != file_path and os.path.exists(chunk):
306+
try:
307+
os.remove(chunk)
308+
print(f" Chunk {os.path.basename(chunk)} rimosso")
309+
except Exception as e:
310+
print(f" Attenzione: impossibile rimuovere {chunk}: {e}")
311+
312+
return full_transcription
313+
314+
except concurrent.futures.TimeoutError:
315+
print("Timeout nella trascrizione parallela, fallback a trascrizione singola")
316+
return transcribe_podcast_with_progress(file_path, model_name, language, speed_up=False)
317+
except Exception as e:
318+
print(f"Errore nella trascrizione parallela: {e}, fallback a trascrizione singola")
319+
return transcribe_podcast_with_progress(file_path, model_name, language, speed_up=False)
186320

187321
def get_audio_duration(file_path):
188322
"""
@@ -236,14 +370,14 @@ def get_audio_duration(file_path):
236370
except Exception:
237371
return 300 # Default 5 minuti se tutti i metodi falliscono
238372

239-
def transcribe_podcast_with_progress(file_path, model_name='medium', language='it', speed_up=False):
373+
def transcribe_podcast_with_progress(file_path, model_name='medium', language='it', parallel=False):
240374
"""
241-
Trascrive un file audio con barra di progresso basata su chunk temporali.
375+
Trascrive un file audio con barra di progresso e opzionale processamento parallelo.
242376
Args:
243377
file_path: Percorso del file audio da trascrivere
244378
model_name: Nome del modello Whisper da utilizzare
245379
language: Lingua del contenuto audio
246-
speed_up: Se True, accelera l'audio 2x prima della trascrizione
380+
parallel: Se True, utilizza processamento parallelo per velocizzare
247381
"""
248382
whisper, tqdm = import_required_modules()
249383

@@ -265,23 +399,11 @@ def transcribe_podcast_with_progress(file_path, model_name='medium', language='i
265399

266400
print("Trascrizione in corso...")
267401

268-
# Crea file temporaneo se necessario per velocità 2x
269-
temp_file_path = None
270-
actual_file_path = file_path
271-
272-
if speed_up:
273-
print("Accelerazione audio 2x in corso...")
274-
temp_dir = os.path.dirname(file_path)
275-
temp_filename = f"temp_speedup_{os.path.basename(file_path)}"
276-
temp_file_path = os.path.join(temp_dir, temp_filename)
277-
278-
if speed_up_audio(file_path, temp_file_path, 2.0):
279-
actual_file_path = temp_file_path
280-
print(" Audio accelerato con successo")
281-
else:
282-
print(" Impossibile accelerare l'audio, utilizzo file originale")
283-
actual_file_path = file_path
402+
# Utilizza processamento parallelo se richiesto
403+
if parallel:
404+
return transcribe_audio_parallel(file_path, model_name, language)
284405

406+
# Altrimenti, trascrizione singola tradizionale
285407
start_time = time.time()
286408

287409
# Barra di progresso basata su chunk completati
@@ -327,14 +449,6 @@ def update_progress():
327449
"Durata": f"{audio_duration:.1f}s"
328450
})
329451

330-
# Pulisce il file temporaneo se è stato creato
331-
if temp_file_path and os.path.exists(temp_file_path):
332-
try:
333-
os.remove(temp_file_path)
334-
print(" File temporaneo rimosso")
335-
except Exception as e:
336-
print(f" Attenzione: impossibile rimuovere il file temporaneo: {e}")
337-
338452
return result['text']
339453

340454
def save_transcription(transcription, output_path):
@@ -367,12 +481,12 @@ def format_time(seconds):
367481
"""
368482
return str(timedelta(seconds=int(seconds)))
369483

370-
def main(podcast_dir, speed_up=False):
484+
def main(podcast_dir, parallel=False):
371485
"""
372486
Funzione principale con barra di progresso e ETA.
373487
Args:
374488
podcast_dir: Directory contenente i file audio
375-
speed_up: Se True, accelera l'audio 2x prima della trascrizione
489+
parallel: Se True, utilizza processamento parallelo per velocizzare
376490
"""
377491
# Importa i moduli necessari
378492
whisper, tqdm = import_required_modules()
@@ -434,7 +548,7 @@ def main(podcast_dir, speed_up=False):
434548
wav_file_path = file_path
435549

436550
# Procedi con la trascrizione
437-
transcription = transcribe_podcast_with_progress(wav_file_path, speed_up=speed_up)
551+
transcription = transcribe_podcast_with_progress(wav_file_path, parallel=parallel)
438552
save_transcription(transcription, output_path)
439553

440554
processed_files += 1
@@ -494,21 +608,21 @@ def main(podcast_dir, speed_up=False):
494608
if os.path.isdir(podcast_dir):
495609
print(f"\nIniziando l'elaborazione della cartella: {podcast_dir}")
496610

497-
# Chiedi se utilizzare la velocità 2x
611+
# Chiedi se utilizzare il processamento parallelo
498612
while True:
499-
speed_choice = input("Vuoi accelerare l'audio a 2x velocità per velocizzare la trascrizione? (s/n): ").strip().lower()
500-
if speed_choice in ['s', 'si', 'yes', 'y']:
501-
speed_up = True
502-
print("Modalità velocità 2x attivata")
613+
parallel_choice = input("Vuoi utilizzare il processamento parallelo per velocizzare la trascrizione? (s/n): ").strip().lower()
614+
if parallel_choice in ['s', 'si', 'yes', 'y']:
615+
parallel = True
616+
print("Modalità processamento parallelo attivata")
503617
break
504-
elif speed_choice in ['n', 'no', 'nope']:
505-
speed_up = False
618+
elif parallel_choice in ['n', 'no', 'nope']:
619+
parallel = False
506620
print("Modalità normale attivata")
507621
break
508622
else:
509623
print("Rispondi 's' per sì o 'n' per no.")
510624

511-
main(podcast_dir, speed_up=speed_up)
625+
main(podcast_dir, parallel=parallel)
512626
else:
513627
print("Il percorso inserito non è valido. Per favore riprova.")
514628
continue

0 commit comments

Comments
 (0)