lyrics-transcriber 0.30.1__py3-none-any.whl → 0.32.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lyrics_transcriber/__init__.py +2 -1
- lyrics_transcriber/cli/cli_main.py +33 -12
- lyrics_transcriber/core/config.py +35 -0
- lyrics_transcriber/core/controller.py +85 -121
- lyrics_transcriber/correction/anchor_sequence.py +471 -0
- lyrics_transcriber/correction/corrector.py +237 -33
- lyrics_transcriber/correction/handlers/__init__.py +0 -0
- lyrics_transcriber/correction/handlers/base.py +30 -0
- lyrics_transcriber/correction/handlers/extend_anchor.py +91 -0
- lyrics_transcriber/correction/handlers/levenshtein.py +147 -0
- lyrics_transcriber/correction/handlers/no_space_punct_match.py +98 -0
- lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +55 -0
- lyrics_transcriber/correction/handlers/repeat.py +71 -0
- lyrics_transcriber/correction/handlers/sound_alike.py +223 -0
- lyrics_transcriber/correction/handlers/syllables_match.py +182 -0
- lyrics_transcriber/correction/handlers/word_count_match.py +54 -0
- lyrics_transcriber/correction/handlers/word_operations.py +135 -0
- lyrics_transcriber/correction/phrase_analyzer.py +426 -0
- lyrics_transcriber/correction/text_utils.py +30 -0
- lyrics_transcriber/lyrics/base_lyrics_provider.py +5 -81
- lyrics_transcriber/lyrics/genius.py +5 -2
- lyrics_transcriber/lyrics/spotify.py +3 -3
- lyrics_transcriber/output/ass/__init__.py +21 -0
- lyrics_transcriber/output/{ass.py → ass/ass.py} +150 -690
- lyrics_transcriber/output/ass/ass_specs.txt +732 -0
- lyrics_transcriber/output/ass/config.py +37 -0
- lyrics_transcriber/output/ass/constants.py +23 -0
- lyrics_transcriber/output/ass/event.py +94 -0
- lyrics_transcriber/output/ass/formatters.py +132 -0
- lyrics_transcriber/output/ass/lyrics_line.py +219 -0
- lyrics_transcriber/output/ass/lyrics_screen.py +252 -0
- lyrics_transcriber/output/ass/section_detector.py +89 -0
- lyrics_transcriber/output/ass/section_screen.py +106 -0
- lyrics_transcriber/output/ass/style.py +187 -0
- lyrics_transcriber/output/cdg.py +503 -0
- lyrics_transcriber/output/cdgmaker/__init__.py +0 -0
- lyrics_transcriber/output/cdgmaker/cdg.py +262 -0
- lyrics_transcriber/output/cdgmaker/composer.py +1919 -0
- lyrics_transcriber/output/cdgmaker/config.py +151 -0
- lyrics_transcriber/output/cdgmaker/images/instrumental.png +0 -0
- lyrics_transcriber/output/cdgmaker/images/intro.png +0 -0
- lyrics_transcriber/output/cdgmaker/pack.py +507 -0
- lyrics_transcriber/output/cdgmaker/render.py +346 -0
- lyrics_transcriber/output/cdgmaker/transitions/centertexttoplogobottomtext.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/circlein.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/circleout.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/fizzle.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/largecentertexttoplogo.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/rectangle.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/spiral.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/topleftmusicalnotes.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipein.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipeleft.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipeout.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wiperight.png +0 -0
- lyrics_transcriber/output/cdgmaker/utils.py +132 -0
- lyrics_transcriber/output/fonts/AvenirNext-Bold.ttf +0 -0
- lyrics_transcriber/output/fonts/DMSans-VariableFont_opsz,wght.ttf +0 -0
- lyrics_transcriber/output/fonts/DMSerifDisplay-Regular.ttf +0 -0
- lyrics_transcriber/output/fonts/Oswald-SemiBold.ttf +0 -0
- lyrics_transcriber/output/fonts/Zurich_Cn_BT_Bold.ttf +0 -0
- lyrics_transcriber/output/fonts/arial.ttf +0 -0
- lyrics_transcriber/output/fonts/georgia.ttf +0 -0
- lyrics_transcriber/output/fonts/verdana.ttf +0 -0
- lyrics_transcriber/output/generator.py +101 -193
- lyrics_transcriber/output/lyrics_file.py +102 -0
- lyrics_transcriber/output/plain_text.py +91 -0
- lyrics_transcriber/output/segment_resizer.py +416 -0
- lyrics_transcriber/output/subtitles.py +328 -302
- lyrics_transcriber/output/video.py +219 -0
- lyrics_transcriber/review/__init__.py +1 -0
- lyrics_transcriber/review/server.py +138 -0
- lyrics_transcriber/transcribers/audioshake.py +3 -2
- lyrics_transcriber/transcribers/base_transcriber.py +5 -42
- lyrics_transcriber/transcribers/whisper.py +3 -4
- lyrics_transcriber/types.py +454 -0
- {lyrics_transcriber-0.30.1.dist-info → lyrics_transcriber-0.32.2.dist-info}/METADATA +14 -3
- lyrics_transcriber-0.32.2.dist-info/RECORD +86 -0
- {lyrics_transcriber-0.30.1.dist-info → lyrics_transcriber-0.32.2.dist-info}/WHEEL +1 -1
- {lyrics_transcriber-0.30.1.dist-info → lyrics_transcriber-0.32.2.dist-info}/entry_points.txt +1 -0
- lyrics_transcriber/correction/base_strategy.py +0 -29
- lyrics_transcriber/correction/strategy_diff.py +0 -263
- lyrics_transcriber-0.30.1.dist-info/RECORD +0 -25
- {lyrics_transcriber-0.30.1.dist-info → lyrics_transcriber-0.32.2.dist-info}/LICENSE +0 -0
@@ -0,0 +1,416 @@
|
|
1
|
+
import logging
|
2
|
+
import re
|
3
|
+
from typing import List, Optional, Tuple
|
4
|
+
|
5
|
+
from lyrics_transcriber.types import LyricsSegment, Word
|
6
|
+
|
7
|
+
|
8
|
+
class SegmentResizer:
|
9
|
+
"""Handles resizing of lyrics segments to ensure proper line lengths and natural breaks.
|
10
|
+
|
11
|
+
This class processes lyrics segments and splits them into smaller segments when they exceed
|
12
|
+
a maximum line length. It attempts to split at natural break points like sentence endings,
|
13
|
+
commas, or conjunctions to maintain readability.
|
14
|
+
|
15
|
+
Example:
|
16
|
+
resizer = SegmentResizer(max_line_length=36)
|
17
|
+
segments = [
|
18
|
+
LyricsSegment(
|
19
|
+
text="This is a very long sentence that needs to be split into multiple lines for better readability",
|
20
|
+
words=[...], # List of Word objects with timing information
|
21
|
+
start_time=0.0,
|
22
|
+
end_time=5.0
|
23
|
+
)
|
24
|
+
]
|
25
|
+
resized = resizer.resize_segments(segments)
|
26
|
+
# Results in:
|
27
|
+
# [
|
28
|
+
# LyricsSegment(text="This is a very long sentence", ...),
|
29
|
+
# LyricsSegment(text="that needs to be split", ...),
|
30
|
+
# LyricsSegment(text="into multiple lines", ...),
|
31
|
+
# LyricsSegment(text="for better readability", ...)
|
32
|
+
# ]
|
33
|
+
"""
|
34
|
+
|
35
|
+
def __init__(self, max_line_length: int = 36, logger: Optional[logging.Logger] = None):
|
36
|
+
"""Initialize the SegmentResizer.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
max_line_length: Maximum allowed length for a single line of text
|
40
|
+
logger: Optional logger for debugging information
|
41
|
+
"""
|
42
|
+
self.max_line_length = max_line_length
|
43
|
+
self.logger = logger or logging.getLogger(__name__)
|
44
|
+
|
45
|
+
def resize_segments(self, segments: List[LyricsSegment]) -> List[LyricsSegment]:
|
46
|
+
"""Main entry point for resizing segments.
|
47
|
+
|
48
|
+
Takes a list of potentially long segments and splits them into smaller ones
|
49
|
+
while preserving word timing information.
|
50
|
+
|
51
|
+
Example:
|
52
|
+
Input segment: "Hello world, this is a test. And here's another sentence."
|
53
|
+
Output segments: [
|
54
|
+
"Hello world, this is a test.",
|
55
|
+
"And here's another sentence."
|
56
|
+
]
|
57
|
+
|
58
|
+
Args:
|
59
|
+
segments: List of LyricsSegment objects to process
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
List of resized LyricsSegment objects
|
63
|
+
"""
|
64
|
+
self._log_input_segments(segments)
|
65
|
+
resized_segments: List[LyricsSegment] = []
|
66
|
+
|
67
|
+
for segment_idx, segment in enumerate(segments):
|
68
|
+
cleaned_segment = self._create_cleaned_segment(segment)
|
69
|
+
|
70
|
+
# Only split if the segment is longer than max_line_length
|
71
|
+
if len(cleaned_segment.text) <= self.max_line_length:
|
72
|
+
resized_segments.append(cleaned_segment)
|
73
|
+
continue
|
74
|
+
|
75
|
+
# Process oversized segments
|
76
|
+
resized_segments.extend(self._split_oversized_segment(segment_idx, segment))
|
77
|
+
|
78
|
+
self._log_output_segments(resized_segments)
|
79
|
+
return resized_segments
|
80
|
+
|
81
|
+
def _clean_text(self, text: str) -> str:
|
82
|
+
"""Clean text by removing newlines and extra whitespace.
|
83
|
+
|
84
|
+
Example:
|
85
|
+
Input: "Hello\n World \n!"
|
86
|
+
Output: "Hello World !"
|
87
|
+
|
88
|
+
Args:
|
89
|
+
text: String to clean
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
Cleaned string with normalized whitespace
|
93
|
+
"""
|
94
|
+
return " ".join(text.replace("\n", " ").split())
|
95
|
+
|
96
|
+
def _create_cleaned_segment(self, segment: LyricsSegment) -> LyricsSegment:
|
97
|
+
"""Create a new segment with cleaned text while preserving timing info.
|
98
|
+
|
99
|
+
Example:
|
100
|
+
Input: LyricsSegment(text="Hello\n World\n", words=[...])
|
101
|
+
Output: LyricsSegment(text="Hello World", words=[...])
|
102
|
+
"""
|
103
|
+
cleaned_text = self._clean_text(segment.text)
|
104
|
+
return LyricsSegment(text=cleaned_text, words=segment.words, start_time=segment.start_time, end_time=segment.end_time)
|
105
|
+
|
106
|
+
def _create_cleaned_word(self, word: Word) -> Word:
|
107
|
+
"""Create a new word with cleaned text."""
|
108
|
+
cleaned_text = self._clean_text(word.text)
|
109
|
+
return Word(
|
110
|
+
text=cleaned_text,
|
111
|
+
start_time=word.start_time,
|
112
|
+
end_time=word.end_time,
|
113
|
+
confidence=word.confidence if hasattr(word, "confidence") else None,
|
114
|
+
)
|
115
|
+
|
116
|
+
def _split_oversized_segment(self, segment_idx: int, segment: LyricsSegment) -> List[LyricsSegment]:
|
117
|
+
"""Split an oversized segment into multiple segments at natural break points.
|
118
|
+
|
119
|
+
Example:
|
120
|
+
Input: "This is a long sentence. Here's another one."
|
121
|
+
Output: [
|
122
|
+
LyricsSegment(text="This is a long sentence.", ...),
|
123
|
+
LyricsSegment(text="Here's another one.", ...)
|
124
|
+
]
|
125
|
+
"""
|
126
|
+
self.logger.info(f"Processing oversized segment {segment_idx}: '{segment.text}'")
|
127
|
+
segment_text = self._clean_text(segment.text)
|
128
|
+
split_lines = self._process_segment_text(segment_text)
|
129
|
+
self.logger.debug(f"Split into {len(split_lines)} lines: {split_lines}")
|
130
|
+
|
131
|
+
return self._create_segments_from_lines(segment_text, split_lines, segment.words)
|
132
|
+
|
133
|
+
def _create_segments_from_lines(self, segment_text: str, split_lines: List[str], words: List[Word]) -> List[LyricsSegment]:
|
134
|
+
"""Create segments from split lines while preserving word timing.
|
135
|
+
|
136
|
+
Matches words to their corresponding lines based on text position and
|
137
|
+
creates new segments with the correct timing information.
|
138
|
+
|
139
|
+
Example:
|
140
|
+
segment_text: "Hello world, how are you"
|
141
|
+
split_lines: ["Hello world,", "how are you"]
|
142
|
+
words: [Word("Hello", 0.0, 1.0), Word("world", 1.0, 2.0), ...]
|
143
|
+
|
144
|
+
Returns segments with words properly assigned to each line.
|
145
|
+
"""
|
146
|
+
segments: List[LyricsSegment] = []
|
147
|
+
words_to_process = words.copy()
|
148
|
+
current_pos = 0
|
149
|
+
|
150
|
+
for line in split_lines:
|
151
|
+
line_words = []
|
152
|
+
line_text = line.strip()
|
153
|
+
remaining_line = line_text
|
154
|
+
|
155
|
+
# Keep processing words until we've found all words for this line
|
156
|
+
while words_to_process and remaining_line:
|
157
|
+
word = words_to_process[0]
|
158
|
+
word_clean = self._clean_text(word.text)
|
159
|
+
|
160
|
+
# Check if the cleaned word appears in the remaining line text
|
161
|
+
if word_clean in remaining_line:
|
162
|
+
word_pos = remaining_line.find(word_clean)
|
163
|
+
if word_pos != -1:
|
164
|
+
line_words.append(words_to_process.pop(0))
|
165
|
+
# Remove the word and any following spaces from remaining line
|
166
|
+
remaining_line = remaining_line[word_pos + len(word_clean):].strip()
|
167
|
+
continue
|
168
|
+
|
169
|
+
# If we can't find the word in the remaining line, we're done with this line
|
170
|
+
break
|
171
|
+
|
172
|
+
if line_words:
|
173
|
+
segments.append(self._create_segment_from_words(line, line_words))
|
174
|
+
current_pos += len(line) + 1 # +1 for the space between lines
|
175
|
+
|
176
|
+
# If we have any remaining words, create a final segment with them
|
177
|
+
if words_to_process:
|
178
|
+
remaining_text = " ".join(self._clean_text(w.text) for w in words_to_process)
|
179
|
+
segments.append(self._create_segment_from_words(remaining_text, words_to_process))
|
180
|
+
|
181
|
+
return segments
|
182
|
+
|
183
|
+
def _create_line_segment(
|
184
|
+
self, line_idx: int, line: str, segment_text: str, available_words: List[Word], current_pos: int
|
185
|
+
) -> Optional[LyricsSegment]:
|
186
|
+
"""Create a single segment from a line of text."""
|
187
|
+
line_pos = segment_text.find(line, current_pos)
|
188
|
+
if line_pos == -1:
|
189
|
+
self.logger.error(f"Failed to find line '{line}' in segment text '{segment_text}' " f"starting from position {current_pos}")
|
190
|
+
return None
|
191
|
+
|
192
|
+
line_words = self._find_words_for_line(line, line_pos, len(line), segment_text, available_words, current_pos)
|
193
|
+
|
194
|
+
if line_words:
|
195
|
+
return self._create_segment_from_words(line, line_words)
|
196
|
+
else:
|
197
|
+
self.logger.warning(f"No words found for line '{line}'")
|
198
|
+
return None
|
199
|
+
|
200
|
+
def _find_words_for_line(
|
201
|
+
self, line: str, line_pos: int, line_length: int, segment_text: str, available_words: List[Word], current_pos: int
|
202
|
+
) -> List[Word]:
|
203
|
+
"""Find words that belong to a specific line."""
|
204
|
+
line_words = []
|
205
|
+
line_text = line.strip()
|
206
|
+
remaining_text = line_text
|
207
|
+
|
208
|
+
for word in available_words:
|
209
|
+
# Skip if word isn't in remaining text
|
210
|
+
if word.text not in remaining_text:
|
211
|
+
continue
|
212
|
+
|
213
|
+
# Find position of word in line
|
214
|
+
word_pos = remaining_text.find(word.text)
|
215
|
+
if word_pos != -1:
|
216
|
+
line_words.append(word)
|
217
|
+
# Remove processed text up to and including this word
|
218
|
+
remaining_text = remaining_text[word_pos + len(word.text) :].strip()
|
219
|
+
|
220
|
+
if not remaining_text: # All words found
|
221
|
+
break
|
222
|
+
|
223
|
+
return line_words
|
224
|
+
|
225
|
+
def _create_segment_from_words(self, line: str, words: List[Word]) -> LyricsSegment:
|
226
|
+
"""Create a new segment from a list of words."""
|
227
|
+
cleaned_text = self._clean_text(line)
|
228
|
+
return LyricsSegment(text=cleaned_text, words=words, start_time=words[0].start_time, end_time=words[-1].end_time)
|
229
|
+
|
230
|
+
def _process_segment_text(self, text: str) -> List[str]:
|
231
|
+
"""Process segment text to determine optimal split points."""
|
232
|
+
self.logger.debug(f"Processing segment text: '{text}'")
|
233
|
+
processed_lines: List[str] = []
|
234
|
+
remaining_text = text.strip()
|
235
|
+
|
236
|
+
while remaining_text:
|
237
|
+
self.logger.debug(f"Remaining text to process: '{remaining_text}'")
|
238
|
+
|
239
|
+
# If remaining text is within limit, add it and we're done
|
240
|
+
if len(remaining_text) <= self.max_line_length:
|
241
|
+
processed_lines.append(remaining_text)
|
242
|
+
break
|
243
|
+
|
244
|
+
# Find best split point
|
245
|
+
split_point = self._find_best_split_point(remaining_text)
|
246
|
+
first_part = remaining_text[:split_point].strip()
|
247
|
+
second_part = remaining_text[split_point:].strip()
|
248
|
+
|
249
|
+
# Only split if:
|
250
|
+
# 1. We found a valid split point
|
251
|
+
# 2. First part isn't too long
|
252
|
+
# 3. Both parts are non-empty
|
253
|
+
if split_point < len(remaining_text) and len(first_part) <= self.max_line_length and first_part and second_part:
|
254
|
+
|
255
|
+
processed_lines.append(first_part)
|
256
|
+
remaining_text = second_part
|
257
|
+
else:
|
258
|
+
# If we can't find a good split, keep the whole text
|
259
|
+
processed_lines.append(remaining_text)
|
260
|
+
break
|
261
|
+
|
262
|
+
return processed_lines
|
263
|
+
|
264
|
+
def _find_best_split_point(self, line: str) -> int:
|
265
|
+
"""Find the best split point that creates natural, well-balanced segments."""
|
266
|
+
self.logger.debug(f"Finding best split point for line: '{line}' (length: {len(line)})")
|
267
|
+
|
268
|
+
# If line is within max length, don't split
|
269
|
+
if len(line) <= self.max_line_length:
|
270
|
+
return len(line)
|
271
|
+
|
272
|
+
break_points = self._find_break_points(line)
|
273
|
+
best_point = None
|
274
|
+
best_score = float("-inf")
|
275
|
+
|
276
|
+
# Try each break point and score it
|
277
|
+
for priority, points in enumerate(break_points):
|
278
|
+
for point in sorted(points): # Sort points to prefer earlier ones in same priority
|
279
|
+
if point <= 0 or point >= len(line):
|
280
|
+
continue
|
281
|
+
|
282
|
+
first_part = line[:point].strip()
|
283
|
+
|
284
|
+
# Skip if first part is too long
|
285
|
+
if len(first_part) > self.max_line_length:
|
286
|
+
continue
|
287
|
+
|
288
|
+
# Score this break point
|
289
|
+
score = self._score_break_point(line, point, priority)
|
290
|
+
if score > best_score:
|
291
|
+
best_score = score
|
292
|
+
best_point = point
|
293
|
+
|
294
|
+
# If no good break points found, fall back to last space before max_length
|
295
|
+
if best_point is None:
|
296
|
+
last_space = line.rfind(" ", 0, self.max_line_length)
|
297
|
+
if last_space != -1:
|
298
|
+
return last_space
|
299
|
+
|
300
|
+
return best_point if best_point is not None else self.max_line_length
|
301
|
+
|
302
|
+
def _score_break_point(self, line: str, point: int, priority: int) -> float:
|
303
|
+
"""Score a potential break point based on multiple factors.
|
304
|
+
|
305
|
+
Factors considered:
|
306
|
+
1. Priority of the break point type (sentence > clause > comma, etc.)
|
307
|
+
2. Balance of segment lengths
|
308
|
+
3. Proximity to target length
|
309
|
+
|
310
|
+
Example:
|
311
|
+
line: "This is a sentence. And more text."
|
312
|
+
point: 18 (after "sentence.")
|
313
|
+
priority: 0 (sentence break)
|
314
|
+
|
315
|
+
Returns a score where higher is better. Score components:
|
316
|
+
- Base score (100-20*priority): 100 for priority 0
|
317
|
+
- Length ratio bonus (0-10): Based on segment balance
|
318
|
+
- Target length bonus (0-5): Based on proximity to ideal length
|
319
|
+
"""
|
320
|
+
first_segment = line[:point].strip()
|
321
|
+
second_segment = line[point:].strip()
|
322
|
+
|
323
|
+
# Base score starts with priority
|
324
|
+
score = 100 - (priority * 20) # Priorities 0-4 give scores 100,80,60,40,20
|
325
|
+
|
326
|
+
# Length ratio bonus
|
327
|
+
length_ratio = min(len(first_segment), len(second_segment)) / max(len(first_segment), len(second_segment))
|
328
|
+
score += length_ratio * 10
|
329
|
+
|
330
|
+
# Target length bonus
|
331
|
+
target_length = self.max_line_length * 0.7
|
332
|
+
first_length_score = 1 - abs(len(first_segment) - target_length) / self.max_line_length
|
333
|
+
score += first_length_score * 5
|
334
|
+
|
335
|
+
return score
|
336
|
+
|
337
|
+
def _find_break_points(self, line: str) -> List[List[int]]:
|
338
|
+
"""Find potential break points in order of preference.
|
339
|
+
|
340
|
+
Returns a list of lists, where each inner list contains break points
|
341
|
+
of the same priority. Break points are indices where text should be split.
|
342
|
+
|
343
|
+
Priority order:
|
344
|
+
1. Sentence endings (., !, ?)
|
345
|
+
2. Major clause breaks (;, -)
|
346
|
+
3. Comma breaks
|
347
|
+
4. Coordinating conjunctions (and, but, or)
|
348
|
+
5. Prepositions/articles (in, at, the, a)
|
349
|
+
|
350
|
+
Example:
|
351
|
+
Input: "Hello, world. This is a test"
|
352
|
+
Output: [
|
353
|
+
[12], # sentence break after "world."
|
354
|
+
[], # no semicolons or dashes
|
355
|
+
[5], # comma after "Hello,"
|
356
|
+
[], # no conjunctions
|
357
|
+
[15] # preposition "is"
|
358
|
+
]
|
359
|
+
"""
|
360
|
+
break_points = []
|
361
|
+
|
362
|
+
# Priority 1: Sentence endings
|
363
|
+
sentence_breaks = []
|
364
|
+
for punct in [".", "!", "?"]:
|
365
|
+
for match in re.finditer(rf"\{punct}\s+", line):
|
366
|
+
sentence_breaks.append(match.start() + 1)
|
367
|
+
break_points.append(sentence_breaks)
|
368
|
+
|
369
|
+
# Priority 2: Major clause breaks (semicolons, dashes)
|
370
|
+
major_breaks = []
|
371
|
+
for punct in [";", " - "]:
|
372
|
+
for match in re.finditer(re.escape(punct), line):
|
373
|
+
major_breaks.append(match.start()) # Position before the punctuation
|
374
|
+
break_points.append(major_breaks)
|
375
|
+
|
376
|
+
# Priority 3: Comma breaks, typically marking natural pauses
|
377
|
+
comma_breaks = []
|
378
|
+
for match in re.finditer(r",\s+", line):
|
379
|
+
comma_breaks.append(match.start() + 1) # Position after the comma
|
380
|
+
break_points.append(comma_breaks)
|
381
|
+
|
382
|
+
# Priority 4: Coordinating conjunctions with surrounding spaces
|
383
|
+
conjunction_breaks = []
|
384
|
+
for conj in [" and ", " but ", " or "]:
|
385
|
+
for match in re.finditer(re.escape(conj), line):
|
386
|
+
conjunction_breaks.append(match.start()) # Position before the conjunction
|
387
|
+
break_points.append(conjunction_breaks)
|
388
|
+
|
389
|
+
# Priority 5: Prepositions or articles with surrounding spaces (last resort)
|
390
|
+
minor_breaks = []
|
391
|
+
for prep in [" in ", " at ", " the ", " a "]:
|
392
|
+
for match in re.finditer(re.escape(prep), line):
|
393
|
+
minor_breaks.append(match.start()) # Position before the preposition
|
394
|
+
break_points.append(minor_breaks)
|
395
|
+
|
396
|
+
return break_points
|
397
|
+
|
398
|
+
def _log_input_segments(self, segments: List[LyricsSegment]) -> None:
|
399
|
+
"""Log input segment information."""
|
400
|
+
self.logger.info(f"Starting segment resize. Input: {len(segments)} segments")
|
401
|
+
for idx, segment in enumerate(segments):
|
402
|
+
self.logger.debug(
|
403
|
+
f"Input segment {idx}: text='{segment.text}', "
|
404
|
+
f"words={len(segment.words)} words, "
|
405
|
+
f"time={segment.start_time:.2f}-{segment.end_time:.2f}"
|
406
|
+
)
|
407
|
+
|
408
|
+
def _log_output_segments(self, segments: List[LyricsSegment]) -> None:
|
409
|
+
"""Log output segment information."""
|
410
|
+
self.logger.info(f"Finished resizing. Output: {len(segments)} segments")
|
411
|
+
for idx, segment in enumerate(segments):
|
412
|
+
self.logger.debug(
|
413
|
+
f"Output segment {idx}: text='{segment.text}', "
|
414
|
+
f"words={len(segment.words)} words, "
|
415
|
+
f"time={segment.start_time:.2f}-{segment.end_time:.2f}"
|
416
|
+
)
|