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.
Files changed (84) hide show
  1. lyrics_transcriber/__init__.py +2 -1
  2. lyrics_transcriber/cli/cli_main.py +33 -12
  3. lyrics_transcriber/core/config.py +35 -0
  4. lyrics_transcriber/core/controller.py +85 -121
  5. lyrics_transcriber/correction/anchor_sequence.py +471 -0
  6. lyrics_transcriber/correction/corrector.py +237 -33
  7. lyrics_transcriber/correction/handlers/__init__.py +0 -0
  8. lyrics_transcriber/correction/handlers/base.py +30 -0
  9. lyrics_transcriber/correction/handlers/extend_anchor.py +91 -0
  10. lyrics_transcriber/correction/handlers/levenshtein.py +147 -0
  11. lyrics_transcriber/correction/handlers/no_space_punct_match.py +98 -0
  12. lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +55 -0
  13. lyrics_transcriber/correction/handlers/repeat.py +71 -0
  14. lyrics_transcriber/correction/handlers/sound_alike.py +223 -0
  15. lyrics_transcriber/correction/handlers/syllables_match.py +182 -0
  16. lyrics_transcriber/correction/handlers/word_count_match.py +54 -0
  17. lyrics_transcriber/correction/handlers/word_operations.py +135 -0
  18. lyrics_transcriber/correction/phrase_analyzer.py +426 -0
  19. lyrics_transcriber/correction/text_utils.py +30 -0
  20. lyrics_transcriber/lyrics/base_lyrics_provider.py +5 -81
  21. lyrics_transcriber/lyrics/genius.py +5 -2
  22. lyrics_transcriber/lyrics/spotify.py +3 -3
  23. lyrics_transcriber/output/ass/__init__.py +21 -0
  24. lyrics_transcriber/output/{ass.py → ass/ass.py} +150 -690
  25. lyrics_transcriber/output/ass/ass_specs.txt +732 -0
  26. lyrics_transcriber/output/ass/config.py +37 -0
  27. lyrics_transcriber/output/ass/constants.py +23 -0
  28. lyrics_transcriber/output/ass/event.py +94 -0
  29. lyrics_transcriber/output/ass/formatters.py +132 -0
  30. lyrics_transcriber/output/ass/lyrics_line.py +219 -0
  31. lyrics_transcriber/output/ass/lyrics_screen.py +252 -0
  32. lyrics_transcriber/output/ass/section_detector.py +89 -0
  33. lyrics_transcriber/output/ass/section_screen.py +106 -0
  34. lyrics_transcriber/output/ass/style.py +187 -0
  35. lyrics_transcriber/output/cdg.py +503 -0
  36. lyrics_transcriber/output/cdgmaker/__init__.py +0 -0
  37. lyrics_transcriber/output/cdgmaker/cdg.py +262 -0
  38. lyrics_transcriber/output/cdgmaker/composer.py +1919 -0
  39. lyrics_transcriber/output/cdgmaker/config.py +151 -0
  40. lyrics_transcriber/output/cdgmaker/images/instrumental.png +0 -0
  41. lyrics_transcriber/output/cdgmaker/images/intro.png +0 -0
  42. lyrics_transcriber/output/cdgmaker/pack.py +507 -0
  43. lyrics_transcriber/output/cdgmaker/render.py +346 -0
  44. lyrics_transcriber/output/cdgmaker/transitions/centertexttoplogobottomtext.png +0 -0
  45. lyrics_transcriber/output/cdgmaker/transitions/circlein.png +0 -0
  46. lyrics_transcriber/output/cdgmaker/transitions/circleout.png +0 -0
  47. lyrics_transcriber/output/cdgmaker/transitions/fizzle.png +0 -0
  48. lyrics_transcriber/output/cdgmaker/transitions/largecentertexttoplogo.png +0 -0
  49. lyrics_transcriber/output/cdgmaker/transitions/rectangle.png +0 -0
  50. lyrics_transcriber/output/cdgmaker/transitions/spiral.png +0 -0
  51. lyrics_transcriber/output/cdgmaker/transitions/topleftmusicalnotes.png +0 -0
  52. lyrics_transcriber/output/cdgmaker/transitions/wipein.png +0 -0
  53. lyrics_transcriber/output/cdgmaker/transitions/wipeleft.png +0 -0
  54. lyrics_transcriber/output/cdgmaker/transitions/wipeout.png +0 -0
  55. lyrics_transcriber/output/cdgmaker/transitions/wiperight.png +0 -0
  56. lyrics_transcriber/output/cdgmaker/utils.py +132 -0
  57. lyrics_transcriber/output/fonts/AvenirNext-Bold.ttf +0 -0
  58. lyrics_transcriber/output/fonts/DMSans-VariableFont_opsz,wght.ttf +0 -0
  59. lyrics_transcriber/output/fonts/DMSerifDisplay-Regular.ttf +0 -0
  60. lyrics_transcriber/output/fonts/Oswald-SemiBold.ttf +0 -0
  61. lyrics_transcriber/output/fonts/Zurich_Cn_BT_Bold.ttf +0 -0
  62. lyrics_transcriber/output/fonts/arial.ttf +0 -0
  63. lyrics_transcriber/output/fonts/georgia.ttf +0 -0
  64. lyrics_transcriber/output/fonts/verdana.ttf +0 -0
  65. lyrics_transcriber/output/generator.py +101 -193
  66. lyrics_transcriber/output/lyrics_file.py +102 -0
  67. lyrics_transcriber/output/plain_text.py +91 -0
  68. lyrics_transcriber/output/segment_resizer.py +416 -0
  69. lyrics_transcriber/output/subtitles.py +328 -302
  70. lyrics_transcriber/output/video.py +219 -0
  71. lyrics_transcriber/review/__init__.py +1 -0
  72. lyrics_transcriber/review/server.py +138 -0
  73. lyrics_transcriber/transcribers/audioshake.py +3 -2
  74. lyrics_transcriber/transcribers/base_transcriber.py +5 -42
  75. lyrics_transcriber/transcribers/whisper.py +3 -4
  76. lyrics_transcriber/types.py +454 -0
  77. {lyrics_transcriber-0.30.1.dist-info → lyrics_transcriber-0.32.2.dist-info}/METADATA +14 -3
  78. lyrics_transcriber-0.32.2.dist-info/RECORD +86 -0
  79. {lyrics_transcriber-0.30.1.dist-info → lyrics_transcriber-0.32.2.dist-info}/WHEEL +1 -1
  80. {lyrics_transcriber-0.30.1.dist-info → lyrics_transcriber-0.32.2.dist-info}/entry_points.txt +1 -0
  81. lyrics_transcriber/correction/base_strategy.py +0 -29
  82. lyrics_transcriber/correction/strategy_diff.py +0 -263
  83. lyrics_transcriber-0.30.1.dist-info/RECORD +0 -25
  84. {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
1
+ Metadata-Version: 2.3
2
2
  Name: lyrics-transcriber
3
- Version: 0.30.1
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,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.1
2
+ Generator: poetry-core 2.0.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,3 +1,4 @@
1
1
  [console_scripts]
2
2
  lyrics-transcriber=lyrics_transcriber.cli.cli_main:main
3
+ test-cov=tests.conftest:main
3
4
 
@@ -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