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,454 @@
|
|
1
|
+
from dataclasses import dataclass, asdict, field
|
2
|
+
from typing import Any, Dict, List, Optional, Set, Protocol, Tuple
|
3
|
+
from enum import Enum
|
4
|
+
|
5
|
+
|
6
|
+
@dataclass
|
7
|
+
class Word:
|
8
|
+
"""Represents a single word with its timing (in seconds) and confidence information."""
|
9
|
+
|
10
|
+
text: str
|
11
|
+
start_time: float
|
12
|
+
end_time: float
|
13
|
+
confidence: Optional[float] = None
|
14
|
+
|
15
|
+
def to_dict(self) -> Dict[str, Any]:
|
16
|
+
"""Convert Word to dictionary for JSON serialization."""
|
17
|
+
d = asdict(self)
|
18
|
+
# Remove confidence from output if it's None
|
19
|
+
if d["confidence"] is None:
|
20
|
+
del d["confidence"]
|
21
|
+
return d
|
22
|
+
|
23
|
+
@classmethod
|
24
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Word":
|
25
|
+
"""Create Word from dictionary."""
|
26
|
+
return cls(
|
27
|
+
text=data["text"],
|
28
|
+
start_time=data["start_time"],
|
29
|
+
end_time=data["end_time"],
|
30
|
+
confidence=data.get("confidence"), # Use get() since confidence is optional
|
31
|
+
)
|
32
|
+
|
33
|
+
|
34
|
+
@dataclass
|
35
|
+
class LyricsSegment:
|
36
|
+
"""Represents a segment/line of lyrics with timing information in seconds."""
|
37
|
+
|
38
|
+
text: str
|
39
|
+
words: List[Word]
|
40
|
+
start_time: float
|
41
|
+
end_time: float
|
42
|
+
|
43
|
+
def to_dict(self) -> Dict[str, Any]:
|
44
|
+
"""Convert LyricsSegment to dictionary for JSON serialization."""
|
45
|
+
return {
|
46
|
+
"text": self.text,
|
47
|
+
"words": [word.to_dict() for word in self.words],
|
48
|
+
"start_time": self.start_time,
|
49
|
+
"end_time": self.end_time,
|
50
|
+
}
|
51
|
+
|
52
|
+
@classmethod
|
53
|
+
def from_dict(cls, data: Dict[str, Any]) -> "LyricsSegment":
|
54
|
+
"""Create LyricsSegment from dictionary."""
|
55
|
+
return cls(
|
56
|
+
text=data["text"],
|
57
|
+
words=[Word.from_dict(w) for w in data["words"]],
|
58
|
+
start_time=data["start_time"],
|
59
|
+
end_time=data["end_time"],
|
60
|
+
)
|
61
|
+
|
62
|
+
|
63
|
+
@dataclass
|
64
|
+
class LyricsMetadata:
|
65
|
+
"""Standardized metadata for lyrics results."""
|
66
|
+
|
67
|
+
source: str
|
68
|
+
track_name: str
|
69
|
+
artist_names: str
|
70
|
+
|
71
|
+
# Common metadata fields
|
72
|
+
album_name: Optional[str] = None
|
73
|
+
duration_ms: Optional[int] = None
|
74
|
+
explicit: Optional[bool] = None
|
75
|
+
language: Optional[str] = None
|
76
|
+
is_synced: bool = False
|
77
|
+
|
78
|
+
# Lyrics provider details
|
79
|
+
lyrics_provider: Optional[str] = None
|
80
|
+
lyrics_provider_id: Optional[str] = None
|
81
|
+
|
82
|
+
# Provider-specific metadata
|
83
|
+
provider_metadata: Dict[str, Any] = None
|
84
|
+
|
85
|
+
def to_dict(self) -> Dict[str, Any]:
|
86
|
+
"""Convert metadata to dictionary for JSON serialization."""
|
87
|
+
return asdict(self)
|
88
|
+
|
89
|
+
|
90
|
+
@dataclass
|
91
|
+
class LyricsData:
|
92
|
+
"""Standardized response format for all lyrics providers."""
|
93
|
+
|
94
|
+
lyrics: str
|
95
|
+
segments: List[LyricsSegment]
|
96
|
+
metadata: LyricsMetadata
|
97
|
+
source: str # e.g., "genius", "spotify", etc.
|
98
|
+
|
99
|
+
def to_dict(self) -> Dict[str, Any]:
|
100
|
+
"""Convert result to dictionary for JSON serialization."""
|
101
|
+
return {
|
102
|
+
"lyrics": self.lyrics,
|
103
|
+
"segments": [segment.to_dict() for segment in self.segments],
|
104
|
+
"metadata": self.metadata.to_dict(),
|
105
|
+
"source": self.source,
|
106
|
+
}
|
107
|
+
|
108
|
+
|
109
|
+
@dataclass
|
110
|
+
class WordCorrection:
|
111
|
+
"""Details about a single word correction."""
|
112
|
+
|
113
|
+
original_word: str
|
114
|
+
corrected_word: str # Empty string indicates word should be deleted
|
115
|
+
segment_index: int
|
116
|
+
original_position: int
|
117
|
+
source: str # e.g., "spotify", "genius"
|
118
|
+
confidence: Optional[float]
|
119
|
+
reason: str # e.g., "matched_in_3_sources", "high_confidence_match"
|
120
|
+
alternatives: Dict[str, int] # Other possible corrections and their occurrence counts
|
121
|
+
is_deletion: bool = False # New field to explicitly mark deletions
|
122
|
+
# New fields for handling word splits
|
123
|
+
split_index: Optional[int] = None # Position in the split sequence (0-based)
|
124
|
+
split_total: Optional[int] = None # Total number of words in split
|
125
|
+
# New field to track position after corrections
|
126
|
+
corrected_position: Optional[int] = None
|
127
|
+
# New fields to match TypeScript interface
|
128
|
+
reference_positions: Optional[Dict[str, int]] = None # Maps source to position in reference text
|
129
|
+
length: int = 1 # Default to 1 for single-word corrections
|
130
|
+
|
131
|
+
def to_dict(self) -> Dict[str, Any]:
|
132
|
+
return asdict(self)
|
133
|
+
|
134
|
+
@classmethod
|
135
|
+
def from_dict(cls, data: Dict[str, Any]) -> "WordCorrection":
|
136
|
+
"""Create WordCorrection from dictionary."""
|
137
|
+
return cls(**data)
|
138
|
+
|
139
|
+
|
140
|
+
@dataclass
|
141
|
+
class TranscriptionData:
|
142
|
+
"""Structured container for transcription results."""
|
143
|
+
|
144
|
+
segments: List[LyricsSegment]
|
145
|
+
words: List[Word]
|
146
|
+
text: str
|
147
|
+
source: str # e.g., "whisper", "audioshake"
|
148
|
+
metadata: Optional[Dict[str, Any]] = None
|
149
|
+
|
150
|
+
def to_dict(self) -> Dict[str, Any]:
|
151
|
+
"""Convert TranscriptionData to dictionary for JSON serialization."""
|
152
|
+
return {
|
153
|
+
"segments": [segment.to_dict() for segment in self.segments],
|
154
|
+
"words": [word.to_dict() for word in self.words],
|
155
|
+
"text": self.text,
|
156
|
+
"source": self.source,
|
157
|
+
"metadata": self.metadata,
|
158
|
+
}
|
159
|
+
|
160
|
+
|
161
|
+
@dataclass
|
162
|
+
class TranscriptionResult:
|
163
|
+
name: str
|
164
|
+
priority: int
|
165
|
+
result: TranscriptionData
|
166
|
+
|
167
|
+
|
168
|
+
class PhraseType(Enum):
|
169
|
+
"""Types of phrases we can identify"""
|
170
|
+
|
171
|
+
COMPLETE = "complete" # Grammatically complete unit
|
172
|
+
PARTIAL = "partial" # Incomplete but valid fragment
|
173
|
+
CROSS_BOUNDARY = "cross" # Crosses natural boundaries
|
174
|
+
|
175
|
+
|
176
|
+
@dataclass
|
177
|
+
class PhraseScore:
|
178
|
+
"""Scores for a potential phrase"""
|
179
|
+
|
180
|
+
phrase_type: PhraseType
|
181
|
+
natural_break_score: float # 0-1, how well it respects natural breaks
|
182
|
+
length_score: float # 0-1, how appropriate the length is
|
183
|
+
|
184
|
+
@property
|
185
|
+
def total_score(self) -> float:
|
186
|
+
"""Calculate total score with weights"""
|
187
|
+
weights = {PhraseType.COMPLETE: 1.0, PhraseType.PARTIAL: 0.7, PhraseType.CROSS_BOUNDARY: 0.3}
|
188
|
+
return weights[self.phrase_type] * 0.5 + self.natural_break_score * 0.3 + self.length_score * 0.2
|
189
|
+
|
190
|
+
def to_dict(self) -> Dict[str, Any]:
|
191
|
+
"""Convert PhraseScore to dictionary for JSON serialization."""
|
192
|
+
return {
|
193
|
+
"phrase_type": self.phrase_type.value, # Convert enum to value for JSON
|
194
|
+
"natural_break_score": self.natural_break_score,
|
195
|
+
"length_score": self.length_score,
|
196
|
+
}
|
197
|
+
|
198
|
+
@classmethod
|
199
|
+
def from_dict(cls, data: Dict[str, Any]) -> "PhraseScore":
|
200
|
+
"""Create PhraseScore from dictionary."""
|
201
|
+
return cls(
|
202
|
+
phrase_type=PhraseType(data["phrase_type"]), natural_break_score=data["natural_break_score"], length_score=data["length_score"]
|
203
|
+
)
|
204
|
+
|
205
|
+
|
206
|
+
@dataclass
|
207
|
+
class AnchorSequence:
|
208
|
+
"""Represents a sequence of words that appears in both transcribed and reference lyrics."""
|
209
|
+
|
210
|
+
words: List[str]
|
211
|
+
transcription_position: int # Starting position in transcribed text
|
212
|
+
reference_positions: Dict[str, int] # Source -> position mapping
|
213
|
+
confidence: float
|
214
|
+
|
215
|
+
@property
|
216
|
+
def text(self) -> str:
|
217
|
+
"""Get the sequence as a space-separated string."""
|
218
|
+
return " ".join(self.words)
|
219
|
+
|
220
|
+
@property
|
221
|
+
def length(self) -> int:
|
222
|
+
"""Get the number of words in the sequence."""
|
223
|
+
return len(self.words)
|
224
|
+
|
225
|
+
def to_dict(self) -> Dict[str, Any]:
|
226
|
+
"""Convert the anchor sequence to a JSON-serializable dictionary."""
|
227
|
+
return {
|
228
|
+
"words": self.words,
|
229
|
+
"text": self.text,
|
230
|
+
"length": self.length,
|
231
|
+
"transcription_position": self.transcription_position,
|
232
|
+
"reference_positions": self.reference_positions,
|
233
|
+
"confidence": self.confidence,
|
234
|
+
}
|
235
|
+
|
236
|
+
@classmethod
|
237
|
+
def from_dict(cls, data: Dict[str, Any]) -> "AnchorSequence":
|
238
|
+
"""Create AnchorSequence from dictionary."""
|
239
|
+
return cls(
|
240
|
+
words=data["words"],
|
241
|
+
transcription_position=data["transcription_position"],
|
242
|
+
reference_positions=data["reference_positions"],
|
243
|
+
confidence=data["confidence"],
|
244
|
+
)
|
245
|
+
|
246
|
+
|
247
|
+
@dataclass
|
248
|
+
class ScoredAnchor:
|
249
|
+
"""An anchor sequence with its quality score"""
|
250
|
+
|
251
|
+
anchor: AnchorSequence
|
252
|
+
phrase_score: PhraseScore
|
253
|
+
|
254
|
+
@property
|
255
|
+
def total_score(self) -> float:
|
256
|
+
"""Combine confidence, phrase quality, and length"""
|
257
|
+
# Length bonus: (length - 1) * 0.1 gives 0.1 per extra word
|
258
|
+
length_bonus = (self.anchor.length - 1) * 0.1
|
259
|
+
# Base score heavily weighted towards confidence
|
260
|
+
base_score = self.anchor.confidence * 0.8 + self.phrase_score.total_score * 0.2
|
261
|
+
# Combine scores
|
262
|
+
return base_score + length_bonus
|
263
|
+
|
264
|
+
def to_dict(self) -> Dict[str, Any]:
|
265
|
+
"""Convert the scored anchor to a JSON-serializable dictionary."""
|
266
|
+
return {
|
267
|
+
**self.anchor.to_dict(),
|
268
|
+
"phrase_score": {
|
269
|
+
"phrase_type": self.phrase_score.phrase_type.value,
|
270
|
+
"natural_break_score": self.phrase_score.natural_break_score,
|
271
|
+
"length_score": self.phrase_score.length_score,
|
272
|
+
"total_score": self.phrase_score.total_score,
|
273
|
+
},
|
274
|
+
"total_score": self.total_score,
|
275
|
+
}
|
276
|
+
|
277
|
+
@classmethod
|
278
|
+
def from_dict(cls, data: Dict[str, Any]) -> "ScoredAnchor":
|
279
|
+
"""Create ScoredAnchor from dictionary."""
|
280
|
+
return cls(anchor=AnchorSequence.from_dict(data["anchor"]), phrase_score=PhraseScore.from_dict(data["phrase_score"]))
|
281
|
+
|
282
|
+
|
283
|
+
@dataclass
|
284
|
+
class GapSequence:
|
285
|
+
"""Represents a sequence of words between anchor sequences in transcribed lyrics."""
|
286
|
+
|
287
|
+
words: Tuple[str, ...]
|
288
|
+
transcription_position: int # Original starting position in transcription
|
289
|
+
preceding_anchor: Optional[AnchorSequence]
|
290
|
+
following_anchor: Optional[AnchorSequence]
|
291
|
+
reference_words: Dict[str, List[str]]
|
292
|
+
reference_words_original: Dict[str, List[str]]
|
293
|
+
corrections: List[WordCorrection] = field(default_factory=list)
|
294
|
+
_corrected_positions: Set[int] = field(default_factory=set, repr=False)
|
295
|
+
_position_offset: int = field(default=0, repr=False) # Track cumulative position changes
|
296
|
+
|
297
|
+
def add_correction(self, correction: WordCorrection) -> None:
|
298
|
+
"""Add a correction and mark its position as corrected."""
|
299
|
+
self.corrections.append(correction)
|
300
|
+
relative_pos = correction.original_position - self.transcription_position
|
301
|
+
self._corrected_positions.add(relative_pos)
|
302
|
+
|
303
|
+
# Update position offset based on correction type
|
304
|
+
if correction.is_deletion:
|
305
|
+
self._position_offset -= 1
|
306
|
+
elif correction.split_total:
|
307
|
+
self._position_offset += correction.split_total - 1
|
308
|
+
|
309
|
+
# Update corrected position for the correction
|
310
|
+
correction.corrected_position = correction.original_position + self._position_offset
|
311
|
+
|
312
|
+
def get_corrected_position(self, original_position: int) -> int:
|
313
|
+
"""Convert an original position to its corrected position."""
|
314
|
+
offset = sum(
|
315
|
+
-1 if c.is_deletion else (c.split_total - 1 if c.split_total else 0)
|
316
|
+
for c in self.corrections
|
317
|
+
if c.original_position < original_position
|
318
|
+
)
|
319
|
+
return original_position + offset
|
320
|
+
|
321
|
+
@property
|
322
|
+
def corrected_length(self) -> int:
|
323
|
+
"""Get the length after applying all corrections."""
|
324
|
+
return self.length + self._position_offset
|
325
|
+
|
326
|
+
def is_word_corrected(self, relative_position: int) -> bool:
|
327
|
+
"""Check if a word at the given position (relative to gap start) has been corrected."""
|
328
|
+
return relative_position in self._corrected_positions
|
329
|
+
|
330
|
+
@property
|
331
|
+
def uncorrected_words(self) -> List[Tuple[int, str]]:
|
332
|
+
"""Get list of (position, word) tuples for words that haven't been corrected yet."""
|
333
|
+
return [(i, word) for i, word in enumerate(self.words) if i not in self._corrected_positions]
|
334
|
+
|
335
|
+
@property
|
336
|
+
def is_fully_corrected(self) -> bool:
|
337
|
+
"""Check if all words in the gap have been corrected."""
|
338
|
+
return len(self._corrected_positions) == self.length
|
339
|
+
|
340
|
+
def __hash__(self):
|
341
|
+
# Hash based on words and position
|
342
|
+
return hash((self.words, self.transcription_position))
|
343
|
+
|
344
|
+
def __eq__(self, other):
|
345
|
+
if not isinstance(other, GapSequence):
|
346
|
+
return NotImplemented
|
347
|
+
return self.words == other.words and self.transcription_position == other.transcription_position
|
348
|
+
|
349
|
+
@property
|
350
|
+
def text(self) -> str:
|
351
|
+
"""Get the sequence as a space-separated string."""
|
352
|
+
return " ".join(self.words)
|
353
|
+
|
354
|
+
@property
|
355
|
+
def length(self) -> int:
|
356
|
+
"""Get the number of words in the sequence."""
|
357
|
+
return len(self.words)
|
358
|
+
|
359
|
+
@property
|
360
|
+
def was_corrected(self) -> bool:
|
361
|
+
"""Check if this gap has any corrections."""
|
362
|
+
return len(self.corrections) > 0
|
363
|
+
|
364
|
+
def to_dict(self) -> Dict[str, Any]:
|
365
|
+
"""Convert the gap sequence to a JSON-serializable dictionary."""
|
366
|
+
return {
|
367
|
+
"words": self.words,
|
368
|
+
"text": self.text,
|
369
|
+
"length": self.length,
|
370
|
+
"transcription_position": self.transcription_position,
|
371
|
+
"preceding_anchor": self.preceding_anchor.to_dict() if self.preceding_anchor else None,
|
372
|
+
"following_anchor": self.following_anchor.to_dict() if self.following_anchor else None,
|
373
|
+
"reference_words": self.reference_words,
|
374
|
+
"reference_words_original": self.reference_words_original,
|
375
|
+
"corrections": [c.to_dict() for c in self.corrections],
|
376
|
+
}
|
377
|
+
|
378
|
+
@classmethod
|
379
|
+
def from_dict(cls, data: Dict[str, Any]) -> "GapSequence":
|
380
|
+
"""Create GapSequence from dictionary."""
|
381
|
+
gap = cls(
|
382
|
+
words=tuple(data["words"]),
|
383
|
+
transcription_position=data["transcription_position"],
|
384
|
+
preceding_anchor=AnchorSequence.from_dict(data["preceding_anchor"]) if data["preceding_anchor"] else None,
|
385
|
+
following_anchor=AnchorSequence.from_dict(data["following_anchor"]) if data["following_anchor"] else None,
|
386
|
+
reference_words=data["reference_words"],
|
387
|
+
reference_words_original=data.get("reference_words_original", {}),
|
388
|
+
)
|
389
|
+
# Add any corrections from the data
|
390
|
+
if "corrections" in data:
|
391
|
+
for correction_data in data["corrections"]:
|
392
|
+
gap.add_correction(WordCorrection.from_dict(correction_data))
|
393
|
+
return gap
|
394
|
+
|
395
|
+
|
396
|
+
@dataclass
|
397
|
+
class CorrectionResult:
|
398
|
+
"""Container for correction results with detailed correction information."""
|
399
|
+
|
400
|
+
# Original (uncorrected) data
|
401
|
+
original_segments: List[LyricsSegment]
|
402
|
+
|
403
|
+
# Corrected data
|
404
|
+
corrected_segments: List[LyricsSegment]
|
405
|
+
corrected_text: str
|
406
|
+
|
407
|
+
# Correction details
|
408
|
+
corrections: List[WordCorrection]
|
409
|
+
corrections_made: int
|
410
|
+
confidence: float
|
411
|
+
|
412
|
+
# Debug/analysis information
|
413
|
+
transcribed_text: str
|
414
|
+
reference_texts: Dict[str, str]
|
415
|
+
anchor_sequences: List[AnchorSequence]
|
416
|
+
gap_sequences: List[GapSequence]
|
417
|
+
resized_segments: List[LyricsSegment]
|
418
|
+
|
419
|
+
metadata: Dict[str, Any]
|
420
|
+
|
421
|
+
def to_dict(self) -> Dict[str, Any]:
|
422
|
+
"""Convert the correction result to a JSON-serializable dictionary."""
|
423
|
+
return {
|
424
|
+
"transcribed_text": self.transcribed_text,
|
425
|
+
"original_segments": [s.to_dict() for s in self.original_segments],
|
426
|
+
"reference_texts": self.reference_texts,
|
427
|
+
"anchor_sequences": [a.to_dict() for a in self.anchor_sequences],
|
428
|
+
"gap_sequences": [g.to_dict() for g in self.gap_sequences],
|
429
|
+
"resized_segments": [s.to_dict() for s in self.resized_segments],
|
430
|
+
"corrected_text": self.corrected_text,
|
431
|
+
"corrections_made": self.corrections_made,
|
432
|
+
"confidence": self.confidence,
|
433
|
+
"corrections": [c.to_dict() for c in self.corrections],
|
434
|
+
"corrected_segments": [s.to_dict() for s in self.corrected_segments],
|
435
|
+
"metadata": self.metadata,
|
436
|
+
}
|
437
|
+
|
438
|
+
@classmethod
|
439
|
+
def from_dict(cls, data: Dict[str, Any]) -> "CorrectionResult":
|
440
|
+
"""Create CorrectionResult from dictionary."""
|
441
|
+
return cls(
|
442
|
+
original_segments=[LyricsSegment.from_dict(s) for s in data["original_segments"]],
|
443
|
+
corrected_segments=[LyricsSegment.from_dict(s) for s in data["corrected_segments"]],
|
444
|
+
corrected_text=data["corrected_text"],
|
445
|
+
corrections=[WordCorrection.from_dict(c) for c in data["corrections"]],
|
446
|
+
corrections_made=data["corrections_made"],
|
447
|
+
confidence=data["confidence"],
|
448
|
+
transcribed_text=data["transcribed_text"],
|
449
|
+
reference_texts=data["reference_texts"],
|
450
|
+
anchor_sequences=[AnchorSequence.from_dict(a) for a in data["anchor_sequences"]],
|
451
|
+
gap_sequences=[GapSequence.from_dict(g) for g in data["gap_sequences"]],
|
452
|
+
resized_segments=[LyricsSegment.from_dict(s) for s in data["resized_segments"]],
|
453
|
+
metadata=data["metadata"],
|
454
|
+
)
|
@@ -1,8 +1,7 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.3
|
2
2
|
Name: lyrics-transcriber
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.32.2
|
4
4
|
Summary: Automatically create synchronised lyrics files in ASS and MidiCo LRC formats with word-level timestamps, using Whisper and lyrics from Genius and Spotify
|
5
|
-
Home-page: https://github.com/karaokenerds/python-lyrics-transcriber
|
6
5
|
License: MIT
|
7
6
|
Author: Andrew Beveridge
|
8
7
|
Author-email: andrew@beveridge.uk
|
@@ -14,13 +13,25 @@ Classifier: Programming Language :: Python :: 3.10
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.11
|
15
14
|
Classifier: Programming Language :: Python :: 3.12
|
16
15
|
Requires-Dist: dropbox (>=12)
|
16
|
+
Requires-Dist: fastapi (>=0.115)
|
17
17
|
Requires-Dist: karaoke-lyrics-processor (>=0.4)
|
18
18
|
Requires-Dist: lyricsgenius (>=3)
|
19
|
+
Requires-Dist: metaphone (>=0.6)
|
20
|
+
Requires-Dist: nltk (>=3.9)
|
19
21
|
Requires-Dist: pydub (>=0.25)
|
20
22
|
Requires-Dist: python-dotenv (>=1)
|
23
|
+
Requires-Dist: python-levenshtein (>=0.26)
|
21
24
|
Requires-Dist: python-slugify (>=8)
|
25
|
+
Requires-Dist: spacy (>=3.8)
|
26
|
+
Requires-Dist: spacy-syllables (>=3)
|
27
|
+
Requires-Dist: syllables (>=1)
|
22
28
|
Requires-Dist: syrics (>=0)
|
29
|
+
Requires-Dist: torch (>=2)
|
30
|
+
Requires-Dist: tqdm (>=4.67)
|
31
|
+
Requires-Dist: transformers (>=4.47)
|
32
|
+
Requires-Dist: uvicorn (>=0.34)
|
23
33
|
Project-URL: Documentation, https://github.com/karaokenerds/python-lyrics-transcriber/blob/main/README.md
|
34
|
+
Project-URL: Homepage, https://github.com/karaokenerds/python-lyrics-transcriber
|
24
35
|
Project-URL: Repository, https://github.com/karaokenerds/python-lyrics-transcriber
|
25
36
|
Description-Content-Type: text/markdown
|
26
37
|
|
@@ -0,0 +1,86 @@
|
|
1
|
+
lyrics_transcriber/__init__.py,sha256=JpdjDK1MH_Be2XiSQWnb4i5Bbil1uPMA_KcuDZ3cyUI,240
|
2
|
+
lyrics_transcriber/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
lyrics_transcriber/cli/cli_main.py,sha256=pquvPfhw6brNgUyMuAzXfCUXNN0NM5tP_MyxlLWqNPc,8968
|
4
|
+
lyrics_transcriber/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
+
lyrics_transcriber/core/config.py,sha256=eddocVn_VoBGRqgBzfSvO7EZp3NuoGfSmd6mcT8wa74,941
|
6
|
+
lyrics_transcriber/core/controller.py,sha256=eQ0M67SWIA-hr23fkw6F8hmqKkklOHsxOsJnYQLXFBE,12184
|
7
|
+
lyrics_transcriber/correction/anchor_sequence.py,sha256=YpKyY24Va5i4JgzP9ssqlOIkaYu060KaldiehbfgTdk,22200
|
8
|
+
lyrics_transcriber/correction/corrector.py,sha256=VOM6YhbANu00rYs6JpKHGZXnZtD5fxArnYtRrsp1YM4,12998
|
9
|
+
lyrics_transcriber/correction/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
lyrics_transcriber/correction/handlers/base.py,sha256=Vanmp6ykP5cdejuJ5ttzjP0Wl4JgKBL-mHbo9EFaeVc,1009
|
11
|
+
lyrics_transcriber/correction/handlers/extend_anchor.py,sha256=9rBrZPmc4grMSnCL2ilkcBsWHc05s6RBL9GDyNAplJk,3821
|
12
|
+
lyrics_transcriber/correction/handlers/levenshtein.py,sha256=pe62eyDE3XxLC219rvtvvfd_lna1q0SuiGcYBYX-nwM,6425
|
13
|
+
lyrics_transcriber/correction/handlers/no_space_punct_match.py,sha256=ri4QEdkYssUC3q0SMGiToYnw4MboRp5i3WGOh9zj3Zw,4573
|
14
|
+
lyrics_transcriber/correction/handlers/relaxed_word_count_match.py,sha256=Y_4S8orGx1LQzlr2yCrGYkvtVLzjD31urtW9tkbwado,2474
|
15
|
+
lyrics_transcriber/correction/handlers/repeat.py,sha256=jeUwpgU3no1Stk7bOHweDfIOxM2xsykbttdnsD-e_Rg,3682
|
16
|
+
lyrics_transcriber/correction/handlers/sound_alike.py,sha256=mAmnpRpO29rHaP96V-U3QTS1aOM32IV2YUYgS0y9ibo,10635
|
17
|
+
lyrics_transcriber/correction/handlers/syllables_match.py,sha256=5M7-0A6G-eu4nyzxT0wiuUpN5zbqXq9d-zeTP7AdLfg,8377
|
18
|
+
lyrics_transcriber/correction/handlers/word_count_match.py,sha256=zbyZ01VE_6azaFpi8rS0Ato7c_VBxM2KV83VnDH5t3c,2522
|
19
|
+
lyrics_transcriber/correction/handlers/word_operations.py,sha256=2COTaJsEwpSWyXHXmGgjfcf2x7tbAnsQ0dIW0qyHYK4,5141
|
20
|
+
lyrics_transcriber/correction/phrase_analyzer.py,sha256=D94Ytlo7U2eTHYJ_nCVLhafmtTYExKx6v2_aczOJaQ8,16455
|
21
|
+
lyrics_transcriber/correction/text_utils.py,sha256=VkOqgZHa9wEqLJdVNi4-KLFojQ6d4lWOGl_Y_vknenU,808
|
22
|
+
lyrics_transcriber/lyrics/base_lyrics_provider.py,sha256=i4wxzu8nk2a3NDtnB_4r6rOGBZ7WvJFVlcEBjAkUYgI,5511
|
23
|
+
lyrics_transcriber/lyrics/genius.py,sha256=M4rs3yk5RKW-RYfMm9w-UxwKQ8itgYeM-kVS6LCn8D0,3295
|
24
|
+
lyrics_transcriber/lyrics/spotify.py,sha256=9n4n98xS_BrpTPZg-24n0mzyPk9vkdmhy6T8ei8imh4,3599
|
25
|
+
lyrics_transcriber/output/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
26
|
+
lyrics_transcriber/output/ass/__init__.py,sha256=EYQ45gI7_-vclVgzISL0ML8VgxCdB0odqEyPyiPCIw0,578
|
27
|
+
lyrics_transcriber/output/ass/ass.py,sha256=xHId7Rv5gqmdQ7ZrWQSy46JX7vZcB_BzpZC800DwQk8,74315
|
28
|
+
lyrics_transcriber/output/ass/ass_specs.txt,sha256=r80HVXWSjTMGU7o2NOJdHkYNBY6SVwSVf0Dyslc1OXk,39288
|
29
|
+
lyrics_transcriber/output/ass/config.py,sha256=VOa9raO88b2QDuHbLvPN_7yqOq-fdBEijwN7S893BCY,1014
|
30
|
+
lyrics_transcriber/output/ass/constants.py,sha256=CUxR7K3LnoZeA7WMuMYMAPto0J7VW3IwKlP_Ve7XB0A,536
|
31
|
+
lyrics_transcriber/output/ass/event.py,sha256=XVbA7YR6WgFa5ynrgE2R7ngauWZVZyNn86BfyNLF0u8,2657
|
32
|
+
lyrics_transcriber/output/ass/formatters.py,sha256=hx_3a1goRIVQwAeNyu9n4UZcnpO9vm87x1aiHK5TBRc,3614
|
33
|
+
lyrics_transcriber/output/ass/lyrics_line.py,sha256=WIOhNzBg4b29xL-7qklQzvyfPdWS1_LX_RYa84jz5wc,9480
|
34
|
+
lyrics_transcriber/output/ass/lyrics_screen.py,sha256=gRzUsDMLEtZZPuv77xk7M0FzCpFph5tjlE5hhFaobOw,10676
|
35
|
+
lyrics_transcriber/output/ass/section_detector.py,sha256=TsSf4E0fleC-Tzd5KK6q4m-wjGiu6TvGDtHdR6sUqvc,3922
|
36
|
+
lyrics_transcriber/output/ass/section_screen.py,sha256=QeUaIeDXs_Es33W5aqyVSaZzMwUx-b60vbAww3aQfls,4185
|
37
|
+
lyrics_transcriber/output/ass/style.py,sha256=ty3IGorlOZ_Q-TxeA02hNb5Pb0mA755dOb8bqKr1k7U,6880
|
38
|
+
lyrics_transcriber/output/cdg.py,sha256=V1T2oBmrCLWhBwLiyCiys3C2W1mDqki54JRKC_TvNIM,20732
|
39
|
+
lyrics_transcriber/output/cdgmaker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
40
|
+
lyrics_transcriber/output/cdgmaker/cdg.py,sha256=nBqkw0JOois-NI27CkwHblLuBaoL-sHyJb2SntX7m8s,6733
|
41
|
+
lyrics_transcriber/output/cdgmaker/composer.py,sha256=qUBlGonxPeCs0G7u2bqIpF58ySAZr9mZxLStWZr9ksY,76193
|
42
|
+
lyrics_transcriber/output/cdgmaker/config.py,sha256=dOsOaPg9XawR3sWdTBoQiYn7urQWafV2KzedhI6BHYU,4043
|
43
|
+
lyrics_transcriber/output/cdgmaker/images/instrumental.png,sha256=EKUcJJGj95ceNqw7M-O9ltX4HZIaCaSKjJucKVDTSb8,14834
|
44
|
+
lyrics_transcriber/output/cdgmaker/images/intro.png,sha256=XeN6i8aKaQObfRwcgHT8ajQAJIDVjXEd7C5tu7DtriU,17603
|
45
|
+
lyrics_transcriber/output/cdgmaker/pack.py,sha256=tuD7Udt3fpWiEVRnzO3VarULwmQ21QYE_MYc6XSpudg,15870
|
46
|
+
lyrics_transcriber/output/cdgmaker/render.py,sha256=K0BGr4I4BKAMn2tkX3QBtA_CWi7XUi1exP4EwBKxjZs,11028
|
47
|
+
lyrics_transcriber/output/cdgmaker/transitions/centertexttoplogobottomtext.png,sha256=GK8AqHHL3q6QGSvhCc51CzeQ-TvQGjYAfjxAgcGzZVw,37174
|
48
|
+
lyrics_transcriber/output/cdgmaker/transitions/circlein.png,sha256=jeRXV1Sf9HrN-H710_Xj6nsDk0QS8RGefdaxdD2l4FI,25030
|
49
|
+
lyrics_transcriber/output/cdgmaker/transitions/circleout.png,sha256=sQ4YSMiLy78hIuWUgvoWeuvj_OKmLUxD2XLgx5-_DqI,44026
|
50
|
+
lyrics_transcriber/output/cdgmaker/transitions/fizzle.png,sha256=8jSvglEwTuurFr07s82uBdx8SLR2yTB65g_NXE55jjU,2588
|
51
|
+
lyrics_transcriber/output/cdgmaker/transitions/largecentertexttoplogo.png,sha256=UYap1UDfNMjqQ9S3XoJzReoiwoYYhVeTfnGSvwnIOd4,35912
|
52
|
+
lyrics_transcriber/output/cdgmaker/transitions/rectangle.png,sha256=HXX1CqRxbJEhcqT7kiobtYvh2lCbcVaBL0WC2gLDMPw,27969
|
53
|
+
lyrics_transcriber/output/cdgmaker/transitions/spiral.png,sha256=zSXl8k208QetD7tJp6kMQVygtREoHvG_wDRi0tfYcus,54261
|
54
|
+
lyrics_transcriber/output/cdgmaker/transitions/topleftmusicalnotes.png,sha256=cCpsT49CQD9walZxToBbnQo2unJE5ke57ASkkLDwZaQ,14612
|
55
|
+
lyrics_transcriber/output/cdgmaker/transitions/wipein.png,sha256=hWuWt2a89dbgj_aRdvXYuElaxhbJaKJoykd4geOLcII,9880
|
56
|
+
lyrics_transcriber/output/cdgmaker/transitions/wipeleft.png,sha256=7jY6Z-mdY94PEy8stm2N0cPB9MPLgmsc_A7eRiERNWc,8613
|
57
|
+
lyrics_transcriber/output/cdgmaker/transitions/wipeout.png,sha256=YSLdv2RCGeSilOGt7PYTLAOlBfL4bofu8Vpj-PcskZo,9995
|
58
|
+
lyrics_transcriber/output/cdgmaker/transitions/wiperight.png,sha256=Dw3N6cqJaSwL3owDqyAM4W573EmdNnkjdiXhoxWC4yk,8443
|
59
|
+
lyrics_transcriber/output/cdgmaker/utils.py,sha256=TNcOnccegpxT3OogWQCexy8BrDqfihLAShMvqeM81cw,2945
|
60
|
+
lyrics_transcriber/output/fonts/AvenirNext-Bold.ttf,sha256=YxgKz2OP46lwLPCpIZhVa8COi_9KRDSXw4n8dIHHQSs,327048
|
61
|
+
"lyrics_transcriber/output/fonts/DMSans-VariableFont_opsz,wght.ttf",sha256=7uav75vmxRukpMx8wqtPeNvaxqOzlBljO400geBzYYI,238984
|
62
|
+
lyrics_transcriber/output/fonts/DMSerifDisplay-Regular.ttf,sha256=mMHMLr3CMjEQgJ5cKlYEn8YSsHwSnDtxT-Qjn_n8ffM,72220
|
63
|
+
lyrics_transcriber/output/fonts/Oswald-SemiBold.ttf,sha256=G-vSJeeyEVft7D4s7FZQtGfXAViWPjzGCImV2a4u9d8,87608
|
64
|
+
lyrics_transcriber/output/fonts/Zurich_Cn_BT_Bold.ttf,sha256=WNG5LOQ-uGUF_WWT5aQHzVbyWvQqGO5sZ4E-nRmvPuI,37780
|
65
|
+
lyrics_transcriber/output/fonts/arial.ttf,sha256=NcDzVZ2NtWnjbDEJW4pg1EFkPZX1kTneQOI_ragZuDM,275572
|
66
|
+
lyrics_transcriber/output/fonts/georgia.ttf,sha256=fQuyDGMrtZ6BoIhfVzvSFz9x9zIE3pBY_raM4DIicHI,142964
|
67
|
+
lyrics_transcriber/output/fonts/verdana.ttf,sha256=lu0UlJyktzks_yNbnEHVXBJTgqu-DA08K53WaJfK4Ms,139640
|
68
|
+
lyrics_transcriber/output/generator.py,sha256=HQa3Ft8SKJie9-cYO0NKDbAU2-h_YnnH5wACxj0qFKw,7482
|
69
|
+
lyrics_transcriber/output/lyrics_file.py,sha256=_KQyQjCOMIwQdQ0115uEAUIjQWTRmShkSfQuINPKxaw,3741
|
70
|
+
lyrics_transcriber/output/plain_text.py,sha256=3mYKq0BLYz1rGBD6ROjG2dn6BPuzbn5dxIQbWZVi4ao,3689
|
71
|
+
lyrics_transcriber/output/segment_resizer.py,sha256=xkKCNt4CTdTErUTYsYtjmllKY8YHny1srqQMrJQYbK8,17141
|
72
|
+
lyrics_transcriber/output/subtitles.py,sha256=BQy7N_2zdBBWEiHL0NWFz3ZgAerWqQvTLALgxxK3Etk,16920
|
73
|
+
lyrics_transcriber/output/video.py,sha256=kYGeEMYtoJvrGnMuyNpuSmu2DTskGDXBNlrv6ddvC8I,8485
|
74
|
+
lyrics_transcriber/review/__init__.py,sha256=_3Eqw-uXZhOZwo6_sHZLhP9vxAVkLF9EBXduUvPdLjQ,57
|
75
|
+
lyrics_transcriber/review/server.py,sha256=iAG0WUkGrqnAF7dI4ZQQayp2qaamqGGYT6rWJF9OysI,4397
|
76
|
+
lyrics_transcriber/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
77
|
+
lyrics_transcriber/storage/dropbox.py,sha256=Dyam1ULTkoxD1X5trkZ5dGp5XhBGCn998moC8IS9-68,9804
|
78
|
+
lyrics_transcriber/transcribers/audioshake.py,sha256=QzKGimVa6BovlvYFj35CbGpaGePI_DApAJGEBR_JQLc,8709
|
79
|
+
lyrics_transcriber/transcribers/base_transcriber.py,sha256=yPzUWPTCGmzE97H5Rz6g61e-qEGL77ZzUoiBOmswhts,5973
|
80
|
+
lyrics_transcriber/transcribers/whisper.py,sha256=P0kas2_oX16MO1-Qy7U5gl5KQN-RuUIJZz7LsEFLUiE,12906
|
81
|
+
lyrics_transcriber/types.py,sha256=xGf3hkTRcGZTTAjMVIev2i2DOU6co0QGpW8NxvaBQAA,16759
|
82
|
+
lyrics_transcriber-0.32.2.dist-info/LICENSE,sha256=BiPihPDxhxIPEx6yAxVfAljD5Bhm_XG2teCbPEj_m0Y,1069
|
83
|
+
lyrics_transcriber-0.32.2.dist-info/METADATA,sha256=_eW3ZTF3vCxCPtfJRzKgedrQ8VmaL1b244mAlxO71tY,5856
|
84
|
+
lyrics_transcriber-0.32.2.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
85
|
+
lyrics_transcriber-0.32.2.dist-info/entry_points.txt,sha256=ChnmR13YoalGnC3sHW0TppX5FbhEXntYIha24tVQJ1M,104
|
86
|
+
lyrics_transcriber-0.32.2.dist-info/RECORD,,
|
@@ -1,29 +0,0 @@
|
|
1
|
-
from dataclasses import dataclass
|
2
|
-
from typing import Any, Dict, List, Protocol
|
3
|
-
|
4
|
-
from lyrics_transcriber.lyrics.base_lyrics_provider import LyricsData
|
5
|
-
from ..transcribers.base_transcriber import LyricsSegment, TranscriptionResult
|
6
|
-
|
7
|
-
|
8
|
-
@dataclass
|
9
|
-
class CorrectionResult:
|
10
|
-
"""Container for correction results."""
|
11
|
-
|
12
|
-
segments: List[LyricsSegment]
|
13
|
-
text: str
|
14
|
-
confidence: float
|
15
|
-
corrections_made: int
|
16
|
-
source_mapping: Dict[str, str] # Maps corrected words to their source
|
17
|
-
metadata: Dict[str, Any]
|
18
|
-
|
19
|
-
|
20
|
-
class CorrectionStrategy(Protocol):
|
21
|
-
"""Interface for different lyrics correction strategies."""
|
22
|
-
|
23
|
-
def correct(
|
24
|
-
self,
|
25
|
-
transcription_results: List[TranscriptionResult],
|
26
|
-
lyrics_results: List[LyricsData],
|
27
|
-
) -> CorrectionResult:
|
28
|
-
"""Apply correction strategy to transcribed lyrics."""
|
29
|
-
... # pragma: no cover
|