44import asyncio
55import bisect
66import re
7+ import sys
78import traceback
89import wave
910from base64 import b64decode
@@ -248,6 +249,27 @@ def truncate_text(text: str, max_len: int = 25) -> str:
248249 remaining = len (text ) - max_len
249250 return f"{ text [:max_len ]} ...(+{ remaining } )"
250251
252+ def flatten_dict_to_paths (d : dict | list , prefix : str = "" ) -> list [tuple [str , Any ]]:
253+ """Flatten nested dict/list to materialized paths like ('a.b.c', value)"""
254+ result = []
255+
256+ if isinstance (d , dict ):
257+ for key , value in d .items ():
258+ new_prefix = f"{ prefix } .{ key } " if prefix else key
259+ if isinstance (value , (dict , list )):
260+ result .extend (flatten_dict_to_paths (value , new_prefix ))
261+ else :
262+ result .append ((new_prefix , value ))
263+ elif isinstance (d , list ):
264+ for i , value in enumerate (d ):
265+ new_prefix = f"{ prefix } .{ i } "
266+ if isinstance (value , (dict , list )):
267+ result .extend (flatten_dict_to_paths (value , new_prefix ))
268+ else :
269+ result .append ((new_prefix , value ))
270+
271+ return result
272+
251273def format_report (report : Report , io_data : IoData , source_lang : str , target_lang : str , in_file : str , out_file : str , config : Config ) -> str :
252274 """Format report as text with tables and histogram"""
253275 lines = []
@@ -273,6 +295,23 @@ def format_report(report: Report, io_data: IoData, source_lang: str, target_lang
273295 lines .append (f"TTS autotempo: ✅ on ({ queue_config .min_tempo } -{ queue_config .max_tempo } )" )
274296 else :
275297 lines .append (f"TTS autotempo: ❌ off" )
298+
299+ # CONFIG - exact same dict that goes to SetTaskMessage
300+ lines .append ("" )
301+ lines .append ("CONFIG (sent to set_task)" )
302+ lines .append ("-" * 80 )
303+ config_dict = config .to_dict () # Same as SetTaskMessage.from_config uses!
304+ config_paths = flatten_dict_to_paths (config_dict )
305+
306+ table = PrettyTable ()
307+ table .field_names = ["Key" , "Value" ]
308+ table .align ["Key" ] = "l"
309+ table .align ["Value" ] = "l"
310+
311+ for key , value in config_paths :
312+ table .add_row ([key , value ])
313+
314+ lines .append (str (table ))
276315 lines .append ("" )
277316
278317 # Metrics summary table
@@ -374,8 +413,12 @@ def main():
374413 timestamp = datetime .now ().strftime ("%Y%m%d_%H%M%S" )
375414
376415 # Save sysinfo immediately at startup
416+ sysinfo = get_system_info ()
417+ sysinfo ["command" ] = " " .join (sys .argv )
418+ sysinfo ["argv" ] = sys .argv
419+ sysinfo ["cwd" ] = str (Path .cwd ())
377420 sysinfo_path = output_dir / f"{ timestamp } _bench_sysinfo.json"
378- sysinfo_path .write_bytes (to_json (get_system_info () , True ))
421+ sysinfo_path .write_bytes (to_json (sysinfo , True ))
379422
380423 # Get audio duration for progress tracking
381424 with av .open (str (audio_path )) as container :
@@ -426,6 +469,11 @@ def on_transcription(msg):
426469 config .debug = True
427470 config .log_file = str (output_dir / f"{ timestamp } _bench.log" )
428471
472+ # Save exact config that goes to set_task (SetTaskMessage.from_config uses to_dict)
473+ config_dict = config .to_dict ()
474+ config_path = output_dir / f"{ timestamp } _bench_config.json"
475+ config_path .write_bytes (to_json (config_dict , True ))
476+
429477 # Create progress bar with language info
430478 progress_bar [0 ] = tqdm (
431479 total = 100 ,
@@ -475,18 +523,26 @@ def on_transcription(msg):
475523 print ("This usually means:" )
476524 print (" - User interrupted with Ctrl+C" )
477525 print (" - Task was cancelled by timeout" )
478- print (" - Internal cancellation due to error\n " )
526+ print (" - Internal cancellation due to error" )
527+ print (" - One of the subtasks failed and caused cascade cancellation\n " )
528+
529+ # For CancelledError, show ALL logs to understand what happened
530+ if result .log_data and result .log_data .logs :
531+ print (f"Full logs (all { len (result .log_data .logs )} entries):" )
532+ for log_line in result .log_data .logs :
533+ print (log_line , end = '' )
534+ print ()
479535 else :
480536 print (f"\n { '=' * 80 } " )
481537 print (f"BENCHMARK FAILED: { exc_type } : { exc_msg } " )
482538 print (f"{ '=' * 80 } \n " )
483539
484- # Print more logs (100 instead of 50)
485- if result .log_data and result .log_data .logs :
486- print ("Last 100 log entries:" )
487- for log_line in result .log_data .logs [- 100 :]:
488- print (log_line , end = '' )
489- print ()
540+ # For other errors, show last 100
541+ if result .log_data and result .log_data .logs :
542+ print ("Last 100 log entries:" )
543+ for log_line in result .log_data .logs [- 100 :]:
544+ print (log_line , end = '' )
545+ print ()
490546
491547 # Print traceback from exception if available
492548 if hasattr (result .exc , '__traceback__' ) and result .exc .__traceback__ :
@@ -542,21 +598,47 @@ def on_transcription(msg):
542598 print ("\n " + report_text )
543599
544600 except Exception as e :
601+ # Capture traceback IMMEDIATELY - must be done in except block!
602+ tb_string = traceback .format_exc ()
603+
545604 # Print full traceback to console
546605 print (f"\n { '=' * 80 } " )
547606 print ("BENCHMARK CRASHED - FULL TRACEBACK:" )
548607 print (f"{ '=' * 80 } \n " )
549- traceback . print_exc ( )
608+ print ( tb_string )
550609
551610 # Save error to file if output directory exists
552611 if output_dir and timestamp :
553612 try :
554613 error_file = output_dir / f"{ timestamp } _bench_error.txt"
555- error_file .write_text (f"Benchmark Error:\n \n { traceback . format_exc () } " )
614+ error_file .write_text (f"Benchmark Error:\n \n { tb_string } " )
556615 print (f"\n Error details saved to: { error_file } " )
557616 except Exception as save_error :
558617 print (f"Failed to save error file: { save_error } " )
559618
619+ # Try to save partial report/audio even on error (for debugging)
620+ if output_dir and timestamp and result and result .io_data :
621+ try :
622+ print ("\n Attempting to save partial results for debugging..." )
623+
624+ # Try to parse report
625+ report , in_audio , out_audio = Report .parse (result .io_data )
626+
627+ # Save report files
628+ report_path = output_dir / f"{ timestamp } _bench_report_partial.json"
629+ report_path .write_bytes (to_json (report , True ))
630+ print (f"✓ Partial report saved to: { report_path } " )
631+
632+ # Save audio (always when --out is specified)
633+ in_wav = output_dir / f"{ timestamp } _bench_in_partial.wav"
634+ out_wav = output_dir / f"{ timestamp } _bench_out_partial.wav"
635+ save_wav (in_audio , in_wav , result .io_data .in_sr , result .io_data .channels )
636+ save_wav (out_audio , out_wav , result .io_data .out_sr , result .io_data .channels )
637+ print (f"✓ Partial audio saved: { in_wav .name } , { out_wav .name } " )
638+
639+ except Exception as save_err :
640+ print (f"Could not save partial results: { save_err } " )
641+
560642 # Re-raise the exception
561643 raise
562644
0 commit comments