lyrics-transcriber 0.30.0__py3-none-any.whl → 0.32.1__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 (88) hide show
  1. lyrics_transcriber/__init__.py +2 -1
  2. lyrics_transcriber/cli/{main.py → cli_main.py} +47 -14
  3. lyrics_transcriber/core/config.py +35 -0
  4. lyrics_transcriber/core/controller.py +164 -166
  5. lyrics_transcriber/correction/anchor_sequence.py +471 -0
  6. lyrics_transcriber/correction/corrector.py +256 -0
  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 +125 -0
  21. lyrics_transcriber/lyrics/genius.py +73 -0
  22. lyrics_transcriber/lyrics/spotify.py +82 -0
  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 +140 -171
  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/storage/dropbox.py +110 -134
  74. lyrics_transcriber/transcribers/audioshake.py +171 -105
  75. lyrics_transcriber/transcribers/base_transcriber.py +149 -0
  76. lyrics_transcriber/transcribers/whisper.py +267 -133
  77. lyrics_transcriber/types.py +454 -0
  78. {lyrics_transcriber-0.30.0.dist-info → lyrics_transcriber-0.32.1.dist-info}/METADATA +14 -3
  79. lyrics_transcriber-0.32.1.dist-info/RECORD +86 -0
  80. {lyrics_transcriber-0.30.0.dist-info → lyrics_transcriber-0.32.1.dist-info}/WHEEL +1 -1
  81. lyrics_transcriber-0.32.1.dist-info/entry_points.txt +4 -0
  82. lyrics_transcriber/core/corrector.py +0 -56
  83. lyrics_transcriber/core/fetcher.py +0 -143
  84. lyrics_transcriber/storage/tokens.py +0 -116
  85. lyrics_transcriber/transcribers/base.py +0 -31
  86. lyrics_transcriber-0.30.0.dist-info/RECORD +0 -22
  87. lyrics_transcriber-0.30.0.dist-info/entry_points.txt +0 -3
  88. {lyrics_transcriber-0.30.0.dist-info → lyrics_transcriber-0.32.1.dist-info}/LICENSE +0 -0
@@ -1,3 +1,4 @@
1
- from .core.controller import LyricsTranscriber, TranscriberConfig, LyricsConfig, OutputConfig
1
+ from lyrics_transcriber.core.config import TranscriberConfig, LyricsConfig, OutputConfig
2
+ from lyrics_transcriber.core.controller import LyricsTranscriber
2
3
 
3
4
  __all__ = ["LyricsTranscriber", "TranscriberConfig", "LyricsConfig", "OutputConfig"]
@@ -60,9 +60,14 @@ def create_arg_parser() -> argparse.ArgumentParser:
60
60
  output_group.add_argument(
61
61
  "--cache_dir",
62
62
  type=Path,
63
- default=Path("/tmp/lyrics-transcriber-cache/"),
64
63
  help="Directory to cache downloaded/generated files. Default: /tmp/lyrics-transcriber-cache/",
65
64
  )
65
+ output_group.add_argument(
66
+ "--output_styles_json",
67
+ type=Path,
68
+ help="JSON file containing output style configurations for CDG and video generation",
69
+ )
70
+ output_group.add_argument("--generate_cdg", action="store_true", help="Generate CDG karaoke files")
66
71
 
67
72
  # Video options
68
73
  video_group = parser.add_argument_group("Video Options")
@@ -70,16 +75,22 @@ def create_arg_parser() -> argparse.ArgumentParser:
70
75
  video_group.add_argument(
71
76
  "--video_resolution", choices=["4k", "1080p", "720p", "360p"], default="360p", help="Resolution of the karaoke video. Default: 360p"
72
77
  )
73
- video_group.add_argument("--video_background_image", type=Path, help="Image file to use for karaoke video background")
74
- video_group.add_argument(
75
- "--video_background_color",
76
- default="black",
77
- help="Color for karaoke video background (hex format or FFmpeg color name). Default: black",
78
- )
79
78
 
80
79
  return parser
81
80
 
82
81
 
82
+ def parse_args(parser: argparse.ArgumentParser, args_list: list[str] | None = None) -> argparse.Namespace:
83
+ """Parse and process command line arguments."""
84
+ # Use provided args_list for testing, otherwise use sys.argv
85
+ args = parser.parse_args(args_list)
86
+
87
+ # Set default cache_dir if not provided
88
+ if not hasattr(args, "cache_dir") or args.cache_dir is None:
89
+ args.cache_dir = Path(os.getenv("LYRICS_TRANSCRIBER_CACHE_DIR", "/tmp/lyrics-transcriber-cache/"))
90
+
91
+ return args
92
+
93
+
83
94
  def get_config_from_env() -> Dict[str, str]:
84
95
  """Load configuration from environment variables."""
85
96
  load_dotenv()
@@ -121,12 +132,12 @@ def create_configs(args: argparse.Namespace, env_config: Dict[str, str]) -> tupl
121
132
  )
122
133
 
123
134
  output_config = OutputConfig(
124
- output_dir=str(args.output_dir) if args.output_dir else None,
135
+ output_styles_json=str(args.output_styles_json),
136
+ output_dir=str(args.output_dir) if args.output_dir else os.getcwd(),
125
137
  cache_dir=str(args.cache_dir),
126
138
  render_video=args.render_video,
139
+ generate_cdg=args.generate_cdg,
127
140
  video_resolution=args.video_resolution,
128
- video_background_image=str(args.video_background_image) if args.video_background_image else None,
129
- video_background_color=args.video_background_color,
130
141
  )
131
142
 
132
143
  return transcriber_config, lyrics_config, output_config
@@ -151,7 +162,7 @@ def validate_args(args: argparse.Namespace, parser: argparse.ArgumentParser, log
151
162
  def main() -> None:
152
163
  """Main entry point for the CLI."""
153
164
  parser = create_arg_parser()
154
- args = parser.parse_args()
165
+ args = parse_args(parser)
155
166
 
156
167
  # Set up logging first
157
168
  logger = setup_logging(args.log_level)
@@ -182,13 +193,35 @@ def main() -> None:
182
193
  # Log results
183
194
  logger.info("*** Success! ***")
184
195
 
196
+ # Log all generated output files
197
+ if results.original_txt:
198
+ logger.info(f"Generated original transcription: {results.original_txt}")
199
+ if results.corrections_json:
200
+ logger.info(f"Generated corrections data: {results.corrections_json}")
201
+
202
+ if results.corrected_txt:
203
+ logger.info(f"Generated corrected lyrics: {results.corrected_txt}")
185
204
  if results.lrc_filepath:
186
205
  logger.info(f"Generated LRC file: {results.lrc_filepath}")
206
+
207
+ if results.cdg_filepath:
208
+ logger.info(f"Generated CDG file: {results.cdg_filepath}")
209
+ if results.mp3_filepath:
210
+ logger.info(f"Generated MP3 file: {results.mp3_filepath}")
211
+ if results.cdg_zip_filepath:
212
+ logger.info(f"Generated CDG ZIP archive: {results.cdg_zip_filepath}")
213
+
187
214
  if results.ass_filepath:
188
- logger.info(f"Generated ASS file: {results.ass_filepath}")
215
+ logger.info(f"Generated ASS subtitles: {results.ass_filepath}")
189
216
  if results.video_filepath:
190
- logger.info(f"Generated video file: {results.video_filepath}")
217
+ logger.info(f"Generated video: {results.video_filepath}")
191
218
 
192
219
  except Exception as e:
193
- logger.error(f"Processing failed: {str(e)}")
220
+ # Get the full exception traceback
221
+ import traceback
222
+
223
+ error_details = traceback.format_exc()
224
+
225
+ # Log both the error message and the full traceback
226
+ logger.error(f"Processing failed: {str(e)}\n\nFull traceback:\n{error_details}")
194
227
  exit(1)
@@ -0,0 +1,35 @@
1
+ import os
2
+ from dataclasses import dataclass, field
3
+ from typing import Any, Dict, Optional
4
+
5
+
6
+ @dataclass
7
+ class TranscriberConfig:
8
+ """Configuration for transcription services."""
9
+
10
+ audioshake_api_token: Optional[str] = None
11
+ runpod_api_key: Optional[str] = None
12
+ whisper_runpod_id: Optional[str] = None
13
+
14
+
15
+ @dataclass
16
+ class LyricsConfig:
17
+ """Configuration for lyrics services."""
18
+
19
+ genius_api_token: Optional[str] = None
20
+ spotify_cookie: Optional[str] = None
21
+
22
+
23
+ @dataclass
24
+ class OutputConfig:
25
+ """Configuration for output generation."""
26
+
27
+ output_styles_json: str
28
+ max_line_length: int = 36
29
+ styles: Dict[str, Any] = field(default_factory=dict)
30
+ output_dir: Optional[str] = os.getcwd()
31
+ cache_dir: str = os.getenv("LYRICS_TRANSCRIBER_CACHE_DIR", "/tmp/lyrics-transcriber-cache/")
32
+ render_video: bool = False
33
+ generate_cdg: bool = False
34
+ video_resolution: str = "360p"
35
+ enable_review: bool = True
@@ -1,65 +1,44 @@
1
1
  import os
2
2
  import logging
3
- from dataclasses import dataclass
3
+ from dataclasses import dataclass, field
4
4
  from typing import Dict, Optional, List
5
- from ..transcribers.base import BaseTranscriber
6
- from ..transcribers.audioshake import AudioShakeTranscriber
7
- from ..transcribers.whisper import WhisperTranscriber
8
- from .fetcher import LyricsFetcher
9
- from ..output.generator import OutputGenerator
10
- from .corrector import LyricsTranscriptionCorrector
5
+ from lyrics_transcriber.types import (
6
+ LyricsData,
7
+ TranscriptionResult,
8
+ CorrectionResult,
9
+ )
10
+ from lyrics_transcriber.transcribers.base_transcriber import BaseTranscriber
11
+ from lyrics_transcriber.transcribers.audioshake import AudioShakeTranscriber, AudioShakeConfig
12
+ from lyrics_transcriber.transcribers.whisper import WhisperTranscriber, WhisperConfig
13
+ from lyrics_transcriber.lyrics.base_lyrics_provider import BaseLyricsProvider, LyricsProviderConfig
14
+ from lyrics_transcriber.lyrics.genius import GeniusProvider
15
+ from lyrics_transcriber.lyrics.spotify import SpotifyProvider
16
+ from lyrics_transcriber.output.generator import OutputGenerator
17
+ from lyrics_transcriber.correction.corrector import LyricsCorrector
18
+ from lyrics_transcriber.core.config import TranscriberConfig, LyricsConfig, OutputConfig
11
19
 
12
20
 
13
21
  @dataclass
14
- class TranscriberConfig:
15
- """Configuration for transcription services."""
16
-
17
- audioshake_api_token: Optional[str] = None
18
- runpod_api_key: Optional[str] = None
19
- whisper_runpod_id: Optional[str] = None
20
-
21
-
22
- @dataclass
23
- class LyricsConfig:
24
- """Configuration for lyrics services."""
25
-
26
- genius_api_token: Optional[str] = None
27
- spotify_cookie: Optional[str] = None
28
-
29
-
30
- @dataclass
31
- class OutputConfig:
32
- """Configuration for output generation."""
33
-
34
- output_dir: Optional[str] = None
35
- cache_dir: str = "/tmp/lyrics-transcriber-cache/"
36
- render_video: bool = False
37
- video_resolution: str = "360p"
38
- video_background_image: Optional[str] = None
39
- video_background_color: str = "black"
40
-
41
-
42
- @dataclass
43
- class TranscriptionResult:
22
+ class LyricsControllerResult:
44
23
  """Holds the results of the transcription and correction process."""
45
24
 
46
- # Lyrics from internet sources
47
- lyrics_text: Optional[str] = None
48
- lyrics_source: Optional[str] = None
49
- lyrics_genius: Optional[str] = None
50
- lyrics_spotify: Optional[str] = None
51
- spotify_lyrics_data: Optional[Dict] = None
25
+ # Results from different sources
26
+ lyrics_results: List[LyricsData] = field(default_factory=list)
27
+ transcription_results: List[TranscriptionResult] = field(default_factory=list)
52
28
 
53
- # Transcription results
54
- transcription_whisper: Optional[Dict] = None
55
- transcription_audioshake: Optional[Dict] = None
56
- transcription_primary: Optional[Dict] = None
57
- transcription_corrected: Optional[Dict] = None
29
+ # Corrected results
30
+ transcription_corrected: Optional[CorrectionResult] = None
58
31
 
59
32
  # Output files
60
33
  lrc_filepath: Optional[str] = None
61
34
  ass_filepath: Optional[str] = None
62
35
  video_filepath: Optional[str] = None
36
+ mp3_filepath: Optional[str] = None
37
+ cdg_filepath: Optional[str] = None
38
+ cdg_zip_filepath: Optional[str] = None
39
+ original_txt: Optional[str] = None
40
+ corrected_txt: Optional[str] = None
41
+ corrections_json: Optional[str] = None
63
42
 
64
43
 
65
44
  class LyricsTranscriber:
@@ -79,8 +58,9 @@ class LyricsTranscriber:
79
58
  transcriber_config: Optional[TranscriberConfig] = None,
80
59
  lyrics_config: Optional[LyricsConfig] = None,
81
60
  output_config: Optional[OutputConfig] = None,
82
- lyrics_fetcher: Optional[LyricsFetcher] = None,
83
- corrector: Optional[LyricsTranscriptionCorrector] = None,
61
+ transcribers: Optional[Dict[str, BaseTranscriber]] = None,
62
+ lyrics_providers: Optional[Dict[str, BaseLyricsProvider]] = None,
63
+ corrector: Optional[LyricsCorrector] = None,
84
64
  output_generator: Optional[OutputGenerator] = None,
85
65
  logger: Optional[logging.Logger] = None,
86
66
  log_level: int = logging.DEBUG,
@@ -109,175 +89,193 @@ class LyricsTranscriber:
109
89
  self.title = title
110
90
  self.output_prefix = f"{artist} - {title}" if artist and title else os.path.splitext(os.path.basename(audio_filepath))[0]
111
91
 
92
+ # Add after creating necessary folders
93
+ self.logger.debug(f"Using cache directory: {self.output_config.cache_dir}")
94
+ self.logger.debug(f"Using output directory: {self.output_config.output_dir}")
95
+
112
96
  # Create necessary folders
113
97
  os.makedirs(self.output_config.cache_dir, exist_ok=True)
114
- if self.output_config.output_dir:
115
- os.makedirs(self.output_config.output_dir, exist_ok=True)
98
+ os.makedirs(self.output_config.output_dir, exist_ok=True)
116
99
 
117
100
  # Initialize results
118
- self.results = TranscriptionResult()
101
+ self.results = LyricsControllerResult()
119
102
 
120
103
  # Initialize components (with dependency injection)
121
- self.transcribers = self._initialize_transcribers()
122
- self.lyrics_fetcher = lyrics_fetcher or self._initialize_lyrics_fetcher()
123
- self.corrector = corrector or LyricsTranscriptionCorrector(logger=self.logger)
104
+ self.transcribers = transcribers or self._initialize_transcribers()
105
+ self.lyrics_providers = lyrics_providers or self._initialize_lyrics_providers()
106
+ self.corrector = corrector or LyricsCorrector(cache_dir=self.output_config.cache_dir, logger=self.logger)
124
107
  self.output_generator = output_generator or self._initialize_output_generator()
125
108
 
126
109
  def _initialize_transcribers(self) -> Dict[str, BaseTranscriber]:
127
110
  """Initialize available transcription services."""
128
111
  transcribers = {}
129
112
 
113
+ # Add debug logging for config values
114
+ self.logger.debug(f"Initializing transcribers with config: {self.transcriber_config}")
115
+ self.logger.debug(f"Using cache directory for transcribers: {self.output_config.cache_dir}")
116
+
130
117
  if self.transcriber_config.audioshake_api_token:
131
- transcribers["audioshake"] = AudioShakeTranscriber(api_token=self.transcriber_config.audioshake_api_token, logger=self.logger)
118
+ self.logger.debug("Initializing AudioShake transcriber")
119
+ transcribers["audioshake"] = {
120
+ "instance": AudioShakeTranscriber(
121
+ cache_dir=self.output_config.cache_dir,
122
+ config=AudioShakeConfig(api_token=self.transcriber_config.audioshake_api_token),
123
+ logger=self.logger,
124
+ ),
125
+ "priority": 1, # AudioShake has highest priority
126
+ }
127
+ else:
128
+ self.logger.debug("Skipping AudioShake transcriber - no API token provided")
132
129
 
133
130
  if self.transcriber_config.runpod_api_key and self.transcriber_config.whisper_runpod_id:
134
- transcribers["whisper"] = WhisperTranscriber(
135
- logger=self.logger,
136
- runpod_api_key=self.transcriber_config.runpod_api_key,
137
- endpoint_id=self.transcriber_config.whisper_runpod_id,
138
- )
131
+ self.logger.debug("Initializing Whisper transcriber")
132
+ transcribers["whisper"] = {
133
+ "instance": WhisperTranscriber(
134
+ cache_dir=self.output_config.cache_dir,
135
+ config=WhisperConfig(
136
+ runpod_api_key=self.transcriber_config.runpod_api_key, endpoint_id=self.transcriber_config.whisper_runpod_id
137
+ ),
138
+ logger=self.logger,
139
+ ),
140
+ "priority": 2, # Whisper has lower priority
141
+ }
142
+ else:
143
+ self.logger.debug("Skipping Whisper transcriber - missing runpod_api_key or whisper_runpod_id")
139
144
 
140
145
  return transcribers
141
146
 
142
- def _initialize_lyrics_fetcher(self) -> LyricsFetcher:
143
- """Initialize lyrics fetching service."""
144
- return LyricsFetcher(
145
- genius_api_token=self.lyrics_config.genius_api_token, spotify_cookie=self.lyrics_config.spotify_cookie, logger=self.logger
147
+ def _initialize_lyrics_providers(self) -> Dict[str, BaseLyricsProvider]:
148
+ """Initialize available lyrics providers."""
149
+ providers = {}
150
+
151
+ # Create provider config with all necessary parameters
152
+ provider_config = LyricsProviderConfig(
153
+ genius_api_token=self.lyrics_config.genius_api_token,
154
+ spotify_cookie=self.lyrics_config.spotify_cookie,
155
+ cache_dir=self.output_config.cache_dir,
156
+ audio_filepath=self.audio_filepath,
146
157
  )
147
158
 
159
+ if provider_config.genius_api_token:
160
+ self.logger.debug("Initializing Genius lyrics provider")
161
+ providers["genius"] = GeniusProvider(config=provider_config, logger=self.logger)
162
+ else:
163
+ self.logger.debug("Skipping Genius provider - no API token provided")
164
+
165
+ if provider_config.spotify_cookie:
166
+ self.logger.debug("Initializing Spotify lyrics provider")
167
+ providers["spotify"] = SpotifyProvider(config=provider_config, logger=self.logger)
168
+ else:
169
+ self.logger.debug("Skipping Spotify provider - no cookie provided")
170
+
171
+ return providers
172
+
148
173
  def _initialize_output_generator(self) -> OutputGenerator:
149
174
  """Initialize output generation service."""
150
- return OutputGenerator(
151
- logger=self.logger,
152
- output_dir=self.output_config.output_dir,
153
- cache_dir=self.output_config.cache_dir,
154
- video_resolution=self.output_config.video_resolution,
155
- video_background_image=self.output_config.video_background_image,
156
- video_background_color=self.output_config.video_background_color,
157
- )
175
+ return OutputGenerator(config=self.output_config, logger=self.logger)
158
176
 
159
- def process(self) -> TranscriptionResult:
177
+ def process(self) -> LyricsControllerResult:
160
178
  """
161
179
  Main processing method that orchestrates the entire workflow.
162
180
 
163
181
  Returns:
164
- TranscriptionResult containing all outputs and generated files.
182
+ LyricsControllerResult containing all outputs and generated files.
165
183
 
166
184
  Raises:
167
185
  Exception: If a critical error occurs during processing.
168
186
  """
169
- try:
170
- # Step 1: Fetch lyrics if artist and title are provided
171
- if self.artist and self.title:
172
- self.fetch_lyrics()
173
-
174
- # Step 2: Run transcription
175
- self.transcribe()
187
+ # Step 1: Fetch lyrics if artist and title are provided
188
+ if self.artist and self.title:
189
+ self.fetch_lyrics()
176
190
 
177
- # Step 3: Process and correct lyrics
178
- if self.results.transcription_primary:
179
- self.correct_lyrics()
191
+ # Step 2: Run transcription
192
+ self.transcribe()
180
193
 
181
- # Step 4: Generate outputs
182
- if self.results.transcription_corrected:
183
- self.generate_outputs()
194
+ # Step 3: Process and correct lyrics
195
+ self.correct_lyrics()
184
196
 
185
- self.logger.info("Processing completed successfully")
186
- return self.results
197
+ # Step 4: Generate outputs
198
+ self.generate_outputs()
187
199
 
188
- except Exception as e:
189
- self.logger.error(f"Error during processing: {str(e)}")
190
- raise
200
+ self.logger.info("Processing completed successfully")
201
+ return self.results
191
202
 
192
203
  def fetch_lyrics(self) -> None:
193
- """Fetch lyrics from online sources."""
204
+ """Fetch lyrics from available providers."""
194
205
  self.logger.info(f"Fetching lyrics for {self.artist} - {self.title}")
195
206
 
196
- try:
197
- lyrics_result = self.lyrics_fetcher.fetch_lyrics(self.artist, self.title)
198
-
199
- # Update results
200
- self.results.lyrics_text = lyrics_result["lyrics"]
201
- self.results.lyrics_source = lyrics_result["source"]
202
- self.results.lyrics_genius = lyrics_result["genius_lyrics"]
203
- self.results.lyrics_spotify = lyrics_result["spotify_lyrics"]
204
- self.results.spotify_lyrics_data = lyrics_result.get("spotify_lyrics_data")
207
+ for name, provider in self.lyrics_providers.items():
208
+ try:
209
+ result = provider.fetch_lyrics(self.artist, self.title)
210
+ if result:
211
+ self.results.lyrics_results.append(result)
212
+ self.logger.info(f"Successfully fetched lyrics from {name}")
205
213
 
206
- if lyrics_result["lyrics"]:
207
- self.logger.info(f"Successfully fetched lyrics from {lyrics_result['source']}")
208
- else:
209
- self.logger.warning("No lyrics found from any source")
214
+ except Exception as e:
215
+ self.logger.error(f"Failed to fetch lyrics from {name}: {str(e)}")
216
+ continue
210
217
 
211
- except Exception as e:
212
- self.logger.error(f"Failed to fetch lyrics: {str(e)}")
213
- # Don't raise - we can continue without lyrics
218
+ if not self.results.lyrics_results:
219
+ self.logger.warning("No lyrics found from any source")
214
220
 
215
221
  def transcribe(self) -> None:
216
222
  """Run transcription using all available transcribers."""
217
- self.logger.info("Starting transcription process")
218
-
219
- for name, transcriber in self.transcribers.items():
220
- try:
221
- result = transcriber.transcribe(self.audio_filepath)
223
+ self.logger.info(f"Starting transcription with providers: {list(self.transcribers.keys())}")
222
224
 
223
- # Store result based on transcriber type
224
- if name == "whisper":
225
- self.results.transcription_whisper = result
226
- elif name == "audioshake":
227
- self.results.transcription_audioshake = result
225
+ for name, transcriber_info in self.transcribers.items():
226
+ self.logger.info(f"Running transcription with {name}")
227
+ result = transcriber_info["instance"].transcribe(self.audio_filepath)
228
+ if result:
229
+ # Add the transcriber name and priority to the result
230
+ self.results.transcription_results.append(
231
+ TranscriptionResult(name=name, priority=transcriber_info["priority"], result=result)
232
+ )
233
+ self.logger.debug(f"Transcription completed for {name}")
228
234
 
229
- # Use first successful transcription as primary
230
- if not self.results.transcription_primary:
231
- self.results.transcription_primary = result
232
-
233
- except Exception as e:
234
- self.logger.error(f"Transcription failed for {name}: {str(e)}")
235
- continue
235
+ if not self.results.transcription_results:
236
+ self.logger.warning("No successful transcriptions from any provider")
236
237
 
237
238
  def correct_lyrics(self) -> None:
238
239
  """Run lyrics correction using transcription and internet lyrics."""
239
240
  self.logger.info("Starting lyrics correction process")
240
241
 
241
- try:
242
- # Set input data for correction
243
- self.corrector.set_input_data(
244
- spotify_lyrics_data_dict=self.results.spotify_lyrics_data,
245
- spotify_lyrics_text=self.results.lyrics_spotify,
246
- genius_lyrics_text=self.results.lyrics_genius,
247
- transcription_data_dict_whisper=self.results.transcription_whisper,
248
- transcription_data_dict_audioshake=self.results.transcription_audioshake,
249
- )
250
-
251
- # Run correction
252
- corrected_data = self.corrector.run_corrector()
253
-
254
- # Store corrected results
255
- self.results.transcription_corrected = corrected_data
256
- self.logger.info("Lyrics correction completed")
257
-
258
- except Exception as e:
259
- self.logger.error(f"Failed to correct lyrics: {str(e)}")
260
- # Use uncorrected transcription as fallback
261
- self.results.transcription_corrected = self.results.transcription_primary
262
- self.logger.warning("Using uncorrected transcription as fallback")
242
+ # Run correction
243
+ corrected_data = self.corrector.run(
244
+ transcription_results=self.results.transcription_results, lyrics_results=self.results.lyrics_results
245
+ )
246
+
247
+ # Store corrected results
248
+ self.results.transcription_corrected = corrected_data
249
+ self.logger.info("Lyrics correction completed")
250
+
251
+ # Add human review step
252
+ if self.output_config.enable_review: # We'll need to add this config option
253
+ from ..review import start_review_server
254
+
255
+ self.logger.info("Starting human review process")
256
+ self.results.transcription_corrected = start_review_server(corrected_data)
257
+ self.logger.info("Human review completed")
263
258
 
264
259
  def generate_outputs(self) -> None:
265
260
  """Generate output files."""
266
261
  self.logger.info("Generating output files")
267
262
 
268
- try:
269
- output_files = self.output_generator.generate_outputs(
270
- transcription_data=self.results.transcription_corrected,
271
- output_prefix=self.output_prefix,
272
- audio_filepath=self.audio_filepath,
273
- render_video=self.output_config.render_video,
274
- )
275
-
276
- # Store output paths
277
- self.results.lrc_filepath = output_files.get("lrc")
278
- self.results.ass_filepath = output_files.get("ass")
279
- self.results.video_filepath = output_files.get("video")
280
-
281
- except Exception as e:
282
- self.logger.error(f"Failed to generate outputs: {str(e)}")
283
- raise
263
+ output_files = self.output_generator.generate_outputs(
264
+ transcription_corrected=self.results.transcription_corrected,
265
+ lyrics_results=self.results.lyrics_results,
266
+ output_prefix=self.output_prefix,
267
+ audio_filepath=self.audio_filepath,
268
+ artist=self.artist,
269
+ title=self.title,
270
+ )
271
+
272
+ # Store all output paths in results
273
+ self.results.lrc_filepath = output_files.lrc
274
+ self.results.ass_filepath = output_files.ass
275
+ self.results.video_filepath = output_files.video
276
+ self.results.original_txt = output_files.original_txt
277
+ self.results.corrected_txt = output_files.corrected_txt
278
+ self.results.corrections_json = output_files.corrections_json
279
+ self.results.cdg_filepath = output_files.cdg
280
+ self.results.mp3_filepath = output_files.mp3
281
+ self.results.cdg_zip_filepath = output_files.cdg_zip