@@ -20,9 +20,12 @@ def clean_text(text):
2020
2121
2222def _srt_time (ms ):
23- s = ms / 1000.0
24- h , m , sec = int (s // 3600 ), int ((s % 3600 ) // 60 ), int (s % 60 )
25- return f"{ h :02d} :{ m :02d} :{ sec :02d} ,{ int ((s % 1 ) * 1000 ):03d} "
23+ ms = max (0 , int (round (ms )))
24+ h = ms // 3600000
25+ m = (ms % 3600000 ) // 60000
26+ sec = (ms % 60000 ) // 1000
27+ ms_rem = ms % 1000
28+ return f"{ h :02d} :{ m :02d} :{ sec :02d} ,{ ms_rem :03d} "
2629
2730
2831def format_srt (segments ):
@@ -39,6 +42,38 @@ def format_tsv(segments):
3942 return "\n " .join (lines )
4043
4144
45+ def _parse_ms (value , scale = 1 ):
46+ if value is None :
47+ return None
48+ try :
49+ return int (float (value ) * scale )
50+ except (TypeError , ValueError ):
51+ return None
52+
53+
54+ def _timestamp_bounds_ms (result ):
55+ bounds = []
56+ for key in ("timestamp" , "timestamps" ):
57+ for ts in result .get (key , []) or []:
58+ if isinstance (ts , dict ):
59+ start = ts .get ("start_time" , ts .get ("start" ))
60+ end = ts .get ("end_time" , ts .get ("end" ))
61+ start_ms = _parse_ms (start , 1000 )
62+ end_ms = _parse_ms (end , 1000 )
63+ elif isinstance (ts , (list , tuple )) and len (ts ) >= 2 :
64+ start_ms = _parse_ms (ts [0 ])
65+ end_ms = _parse_ms (ts [1 ])
66+ else :
67+ continue
68+ if start_ms is None or end_ms is None :
69+ continue
70+ if end_ms > start_ms :
71+ bounds .append ((start_ms , end_ms ))
72+ if not bounds :
73+ return None
74+ return min (start for start , _ in bounds ), max (end for _ , end in bounds )
75+
76+
4277def _format_output (text , segments , timestamps , fmt , audio_path , model_name , language , elapsed ):
4378 if fmt == "text" :
4479 return text
@@ -58,8 +93,12 @@ def _format_output(text, segments, timestamps, fmt, audio_path, model_name, lang
5893 elif fmt == "srt" :
5994 if segments :
6095 return format_srt (segments )
61- # No per-sentence timestamps: emit one valid cue spanning the whole audio
62- # (instead of a bogus 99:59:59 end time).
96+ # No per-sentence timestamps: emit one valid cue spanning the known
97+ # timestamp/audio bounds instead of a bogus 99:59:59 end time.
98+ timestamp_bounds = _timestamp_bounds_ms ({"timestamp" : timestamps })
99+ if timestamp_bounds :
100+ start_ms , end_ms = timestamp_bounds
101+ return f"1\n { _srt_time (start_ms )} --> { _srt_time (end_ms )} \n { text } \n "
63102 try :
64103 import soundfile as sf
65104 dur_ms = int (sf .info (audio_path ).duration * 1000 )
@@ -115,8 +154,9 @@ def main():
115154 config ["hub" ] = args .hub
116155 if args .spk and "spk_model" not in config :
117156 config ["spk_model" ] = "cam++"
118- if "punc_model" not in config and args .model not in ("fun-asr-nano" , "sensevoice" ):
119- config ["punc_model" ] = "ct-punc"
157+ if "punc_model" not in config and args .model != "fun-asr-nano" :
158+ if args .model != "sensevoice" or args .output_format in ("srt" , "tsv" ):
159+ config ["punc_model" ] = "ct-punc"
120160
121161 t_load = time .time ()
122162 model = AutoModel (device = device , disable_update = True , ** config )
@@ -147,6 +187,15 @@ def main():
147187 else :
148188 gen_kw ["hotwords" ] = hotwords
149189
190+ if args .output_format in ("srt" , "tsv" ):
191+ gen_kw .update (
192+ {
193+ "sentence_timestamp" : True ,
194+ "output_timestamp" : True ,
195+ "return_time_stamps" : True ,
196+ }
197+ )
198+
150199 result = model .generate (** gen_kw )
151200 elapsed = time .time () - t0
152201
@@ -159,7 +208,9 @@ def main():
159208 s ["speaker" ] = seg ["spk" ]
160209 segments .append (s )
161210
162- timestamps = result [0 ].get ("timestamps" ) if args .timestamps else None
211+ timestamps = result [0 ].get ("timestamps" ) or result [0 ].get ("timestamp" )
212+ if not args .timestamps and args .output_format not in ("srt" , "tsv" ):
213+ timestamps = None
163214 output = _format_output (text , segments , timestamps , args .output_format , audio_path , args .model , args .language , elapsed )
164215
165216 if args .output_dir :
0 commit comments