@@ -110,56 +110,96 @@ def _transcribe_single(
110110 raise TranscriptionError (f"Failed to parse transcription result: { e } " ) from e
111111
112112
113- def _is_short_text (text : str , min_words : int , min_cjk_chars : int ) -> bool :
114- """Check if the text segment is too short."""
115- has_cjk = any (
116- "\u3400 " <= c <= "\u4dbf "
117- or "\u4e00 " <= c <= "\u9fff "
118- or "\uf900 " <= c <= "\ufaff "
113+ def _has_cjk (text : str ) -> bool :
114+ """Check if the text contains CJK characters (Chinese, Japanese, or Korean)."""
115+ return any (
116+ "\u4e00 " <= c <= "\u9fff " # CJK Unified Ideographs
117+ or "\u3400 " <= c <= "\u4dbf " # Extension A
118+ or "\uf900 " <= c <= "\ufaff " # Compatibility Ideographs
119+ or "\u3040 " <= c <= "\u309f " # Hiragana
120+ or "\u30a0 " <= c <= "\u30ff " # Katakana
121+ or "\uac00 " <= c <= "\ud7af " # Hangul Syllables
122+ or "\u1100 " <= c <= "\u11ff " # Hangul Jamo
123+ or "\u3000 " <= c <= "\u303f " # CJK Symbols and Punctuation (e.g. 。、)
119124 for c in text
120125 )
121- if has_cjk :
126+
127+
128+ def _is_short_text (text : str , min_words : int , min_cjk_chars : int ) -> bool :
129+ """Check if the text segment is too short."""
130+ if _has_cjk (text ):
122131 return len (text ) < min_cjk_chars
123132 return len (text .split ()) < min_words
124133
125134
126135def _split_long_part_by_length (
127136 part : str ,
137+ part_duration : float ,
138+ max_duration_sec : float ,
128139 max_chars : int ,
129140 min_words : int ,
130141 min_cjk_chars : int ,
131142) -> list [str ]:
132- """Force-split a long text part into length-restricted chunks."""
133- has_cjk = any (
134- "\u3400 " <= c <= "\u4dbf "
135- or "\u4e00 " <= c <= "\u9fff "
136- or "\uf900 " <= c <= "\ufaff "
137- for c in part
138- )
143+ """Force-split a long text part into length/duration-restricted chunks."""
144+ has_cjk = _has_cjk (part )
145+
139146 if has_cjk :
140- chunk_size = max_chars
147+ if part_duration > 0 :
148+ chars_per_sec = len (part ) / part_duration
149+ max_len_by_dur = max (1 , int (chars_per_sec * max_duration_sec ))
150+ chunk_size = min (max_chars , max_len_by_dur )
151+ else :
152+ chunk_size = max_chars
153+
141154 chunks = [part [i : i + chunk_size ] for i in range (0 , len (part ), chunk_size )]
142155 if len (chunks ) > 1 and _is_short_text (chunks [- 1 ], min_words , min_cjk_chars ):
143156 merged_len = len (chunks [- 2 ]) + len (chunks [- 1 ])
144- if merged_len <= max_chars :
157+ merged_duration = part_duration * (merged_len / len (part ))
158+ if merged_len <= max_chars and merged_duration <= max_duration_sec :
145159 chunks [- 2 ] = chunks [- 2 ] + chunks [- 1 ]
146160 chunks .pop ()
147161 return chunks
148162
149163 words = part .split ()
150- chunk_size = max (1 , max_chars // 6 )
164+ if not words :
165+ return []
166+
167+ if part_duration > 0 :
168+ words_per_sec = len (words ) / part_duration
169+ max_words_by_dur = max (1 , int (words_per_sec * max_duration_sec ))
170+ else :
171+ max_words_by_dur = len (words )
172+
151173 word_chunks = []
152- for i in range (0 , len (words ), chunk_size ):
153- chunk = " " .join (words [i : i + chunk_size ])
154- if chunk :
155- word_chunks .append (chunk )
174+ current_chunk : list [str ] = []
175+
176+ for word in words :
177+ prospective_len = len (" " .join ([* current_chunk , word ]))
178+ if current_chunk and (
179+ prospective_len > max_chars or len (current_chunk ) >= max_words_by_dur
180+ ):
181+ word_chunks .append (" " .join (current_chunk ))
182+ current_chunk = [word ]
183+ else :
184+ current_chunk .append (word )
185+
186+ if current_chunk :
187+ word_chunks .append (" " .join (current_chunk ))
188+
156189 if len (word_chunks ) > 1 and _is_short_text (
157190 word_chunks [- 1 ], min_words , min_cjk_chars
158191 ):
159- merged_len = len (word_chunks [- 2 ]) + len (word_chunks [- 1 ]) + 1
160- if merged_len <= max_chars :
161- word_chunks [- 2 ] = f"{ word_chunks [- 2 ]} { word_chunks [- 1 ]} "
192+ merged_text = f"{ word_chunks [- 2 ]} { word_chunks [- 1 ]} "
193+ merged_words = merged_text .split ()
194+ merged_duration = part_duration * (len (merged_text ) / len (part ))
195+ if (
196+ len (merged_text ) <= max_chars
197+ and len (merged_words ) <= max_words_by_dur
198+ and merged_duration <= max_duration_sec
199+ ):
200+ word_chunks [- 2 ] = merged_text
162201 word_chunks .pop ()
202+
163203 return word_chunks
164204
165205
@@ -193,7 +233,9 @@ def _split_long_entries(
193233 continue
194234
195235 # Split by punctuation first (sentences and clauses)
196- raw_parts = re .split (r"(?<=[.?!,;\uff0c\uff1b])\s*" , entry .text )
236+ raw_parts = re .split (
237+ r"(?<=[.?!,;\uff0c\uff1b\u3002\uff01\uff1f\u3001])\s*" , entry .text
238+ )
197239 parts = [p .strip () for p in raw_parts if p .strip ()]
198240
199241 # Merge adjacent parts that are too short,
@@ -212,12 +254,7 @@ def _split_long_entries(
212254 or _is_short_text (merged_parts [- 1 ], min_words , min_cjk_chars )
213255 ) and (part_duration + last_duration <= max_duration_sec ):
214256 last_part = merged_parts [- 1 ]
215- has_cjk = any (
216- "\u3400 " <= c <= "\u4dbf "
217- or "\u4e00 " <= c <= "\u9fff "
218- or "\uf900 " <= c <= "\ufaff "
219- for c in last_part + part
220- )
257+ has_cjk = _has_cjk (last_part + part )
221258 if has_cjk :
222259 merged_parts [- 1 ] = f"{ last_part } { part } "
223260 else :
@@ -231,7 +268,12 @@ def _split_long_entries(
231268 part_duration = duration * (len (part ) / len (entry .text ))
232269 if part_duration > max_duration_sec or len (part ) > max_chars :
233270 chunks = _split_long_part_by_length (
234- part , max_chars , min_words , min_cjk_chars
271+ part ,
272+ part_duration ,
273+ max_duration_sec ,
274+ max_chars ,
275+ min_words ,
276+ min_cjk_chars ,
235277 )
236278 refined_parts .extend (chunks )
237279 else :
@@ -251,12 +293,12 @@ def _split_long_entries(
251293 continue
252294
253295 current_time = entry .start
254- for part in refined_parts :
296+ for idx , part in enumerate ( refined_parts ) :
255297 part_ratio = len (part ) / total_len
256298 part_dur = timedelta (seconds = duration * part_ratio )
257299 part_end = current_time + part_dur
258300
259- if part == refined_parts [ - 1 ] :
301+ if idx == len ( refined_parts ) - 1 :
260302 part_end = entry .end
261303
262304 if part_end > current_time :
0 commit comments