lyrics-transcriber 0.43.0__py3-none-any.whl → 0.44.0__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 (50) hide show
  1. lyrics_transcriber/core/controller.py +58 -24
  2. lyrics_transcriber/correction/anchor_sequence.py +22 -8
  3. lyrics_transcriber/correction/corrector.py +47 -3
  4. lyrics_transcriber/correction/handlers/llm.py +15 -12
  5. lyrics_transcriber/correction/handlers/llm_providers.py +60 -0
  6. lyrics_transcriber/frontend/.yarn/install-state.gz +0 -0
  7. lyrics_transcriber/frontend/dist/assets/{index-D0Gr3Ep7.js → index-DVoI6Z16.js} +10799 -7490
  8. lyrics_transcriber/frontend/dist/assets/index-DVoI6Z16.js.map +1 -0
  9. lyrics_transcriber/frontend/dist/index.html +1 -1
  10. lyrics_transcriber/frontend/src/App.tsx +4 -4
  11. lyrics_transcriber/frontend/src/api.ts +37 -0
  12. lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +114 -0
  13. lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +14 -10
  14. lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +62 -56
  15. lyrics_transcriber/frontend/src/components/EditModal.tsx +232 -237
  16. lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
  17. lyrics_transcriber/frontend/src/components/GlobalSyncEditor.tsx +675 -0
  18. lyrics_transcriber/frontend/src/components/Header.tsx +141 -101
  19. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +146 -80
  20. lyrics_transcriber/frontend/src/components/ModeSelector.tsx +22 -13
  21. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +1 -0
  22. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +29 -12
  23. lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +21 -4
  24. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +29 -15
  25. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +34 -16
  26. lyrics_transcriber/frontend/src/components/WordDivider.tsx +186 -0
  27. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +89 -41
  28. lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +9 -2
  29. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +28 -3
  30. lyrics_transcriber/frontend/src/components/shared/types.ts +17 -2
  31. lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +63 -14
  32. lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +192 -0
  33. lyrics_transcriber/frontend/src/hooks/useManualSync.ts +267 -0
  34. lyrics_transcriber/frontend/src/main.tsx +7 -1
  35. lyrics_transcriber/frontend/src/theme.ts +177 -0
  36. lyrics_transcriber/frontend/src/types.ts +1 -1
  37. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
  38. lyrics_transcriber/lyrics/base_lyrics_provider.py +2 -2
  39. lyrics_transcriber/lyrics/user_input_provider.py +44 -0
  40. lyrics_transcriber/output/generator.py +40 -12
  41. lyrics_transcriber/output/video.py +18 -8
  42. lyrics_transcriber/review/server.py +238 -8
  43. {lyrics_transcriber-0.43.0.dist-info → lyrics_transcriber-0.44.0.dist-info}/METADATA +3 -2
  44. {lyrics_transcriber-0.43.0.dist-info → lyrics_transcriber-0.44.0.dist-info}/RECORD +47 -41
  45. lyrics_transcriber/frontend/dist/assets/index-D0Gr3Ep7.js.map +0 -1
  46. lyrics_transcriber/frontend/src/components/DetailsModal.tsx +0 -252
  47. lyrics_transcriber/frontend/src/components/WordEditControls.tsx +0 -110
  48. {lyrics_transcriber-0.43.0.dist-info → lyrics_transcriber-0.44.0.dist-info}/LICENSE +0 -0
  49. {lyrics_transcriber-0.43.0.dist-info → lyrics_transcriber-0.44.0.dist-info}/WHEEL +0 -0
  50. {lyrics_transcriber-0.43.0.dist-info → lyrics_transcriber-0.44.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,177 @@
1
+ import { createTheme } from '@mui/material/styles';
2
+
3
+ // Create a theme with smaller typography and spacing
4
+ const theme = createTheme({
5
+ typography: {
6
+ // Scale down all typography by about 20%
7
+ fontSize: 14, // Default is 16
8
+ h1: {
9
+ fontSize: '2.5rem', // Default is ~3rem
10
+ },
11
+ h2: {
12
+ fontSize: '2rem', // Default is ~2.5rem
13
+ },
14
+ h3: {
15
+ fontSize: '1.5rem', // Default is ~1.75rem
16
+ },
17
+ h4: {
18
+ fontSize: '1.2rem', // Default is ~1.5rem
19
+ marginBottom: '0.5rem',
20
+ },
21
+ h5: {
22
+ fontSize: '1rem', // Default is ~1.25rem
23
+ },
24
+ h6: {
25
+ fontSize: '0.9rem', // Default is ~1.1rem
26
+ marginBottom: '0.5rem',
27
+ },
28
+ body1: {
29
+ fontSize: '0.85rem', // Default is ~1rem
30
+ },
31
+ body2: {
32
+ fontSize: '0.75rem', // Default is ~0.875rem
33
+ },
34
+ button: {
35
+ fontSize: '0.8rem', // Default is ~0.875rem
36
+ },
37
+ caption: {
38
+ fontSize: '0.7rem', // Default is ~0.75rem
39
+ },
40
+ },
41
+ components: {
42
+ MuiButton: {
43
+ styleOverrides: {
44
+ root: {
45
+ padding: '3px 10px', // Further reduced from 4px 12px
46
+ minHeight: '30px', // Further reduced from 32px
47
+ },
48
+ sizeSmall: {
49
+ padding: '1px 6px', // Further reduced from 2px 8px
50
+ minHeight: '24px', // Further reduced from 28px
51
+ },
52
+ },
53
+ },
54
+ MuiIconButton: {
55
+ styleOverrides: {
56
+ root: {
57
+ padding: '4px', // Further reduced from 6px
58
+ },
59
+ sizeSmall: {
60
+ padding: '2px', // Further reduced from 4px
61
+ },
62
+ },
63
+ },
64
+ MuiTextField: {
65
+ styleOverrides: {
66
+ root: {
67
+ '& .MuiInputBase-root': {
68
+ minHeight: '32px', // Further reduced from 36px
69
+ },
70
+ },
71
+ },
72
+ },
73
+ MuiDialog: {
74
+ styleOverrides: {
75
+ paper: {
76
+ padding: '8px', // Further reduced from 12px
77
+ },
78
+ },
79
+ },
80
+ MuiDialogTitle: {
81
+ styleOverrides: {
82
+ root: {
83
+ padding: '8px 12px', // Further reduced from 12px 16px
84
+ },
85
+ },
86
+ },
87
+ MuiDialogContent: {
88
+ styleOverrides: {
89
+ root: {
90
+ padding: '6px 12px', // Further reduced from 8px 16px
91
+ },
92
+ },
93
+ },
94
+ MuiDialogActions: {
95
+ styleOverrides: {
96
+ root: {
97
+ padding: '6px 12px', // Further reduced from 8px 16px
98
+ },
99
+ },
100
+ },
101
+ MuiPaper: {
102
+ styleOverrides: {
103
+ root: {
104
+ padding: '8px', // Further reduced from 12px
105
+ },
106
+ },
107
+ },
108
+ MuiList: {
109
+ styleOverrides: {
110
+ root: {
111
+ padding: '2px 0', // Further reduced from 4px 0
112
+ },
113
+ },
114
+ },
115
+ MuiListItem: {
116
+ styleOverrides: {
117
+ root: {
118
+ padding: '2px 8px', // Further reduced from 4px 12px
119
+ },
120
+ },
121
+ },
122
+ MuiTableCell: {
123
+ styleOverrides: {
124
+ root: {
125
+ padding: '4px 8px', // Further reduced from 8px 12px
126
+ },
127
+ },
128
+ },
129
+ MuiCard: {
130
+ styleOverrides: {
131
+ root: {
132
+ padding: '8px',
133
+ },
134
+ },
135
+ },
136
+ MuiCardContent: {
137
+ styleOverrides: {
138
+ root: {
139
+ padding: '8px',
140
+ '&:last-child': {
141
+ paddingBottom: '8px',
142
+ },
143
+ },
144
+ },
145
+ },
146
+ MuiCardHeader: {
147
+ styleOverrides: {
148
+ root: {
149
+ padding: '8px',
150
+ },
151
+ },
152
+ },
153
+ MuiCardActions: {
154
+ styleOverrides: {
155
+ root: {
156
+ padding: '4px 8px',
157
+ },
158
+ },
159
+ },
160
+ MuiGrid: {
161
+ styleOverrides: {
162
+ container: {
163
+ marginTop: '-4px',
164
+ marginLeft: '-4px',
165
+ width: 'calc(100% + 8px)',
166
+ },
167
+ item: {
168
+ paddingTop: '4px',
169
+ paddingLeft: '4px',
170
+ },
171
+ },
172
+ },
173
+ },
174
+ spacing: (factor: number) => `${0.6 * factor}rem`, // Further reduced from 0.8 * factor
175
+ });
176
+
177
+ export default theme;
@@ -136,4 +136,4 @@ export interface HighlightInfo {
136
136
  correction?: WordCorrection
137
137
  }
138
138
 
139
- export type InteractionMode = 'highlight' | 'details' | 'edit'
139
+ export type InteractionMode = 'highlight' | 'edit'
@@ -1 +1 @@
1
- {"root":["./src/app.tsx","./src/api.ts","./src/main.tsx","./src/types.ts","./src/validation.ts","./src/vite-env.d.ts","./src/components/audioplayer.tsx","./src/components/correctionmetrics.tsx","./src/components/detailsmodal.tsx","./src/components/editmodal.tsx","./src/components/fileupload.tsx","./src/components/header.tsx","./src/components/lyricsanalyzer.tsx","./src/components/modeselector.tsx","./src/components/previewvideosection.tsx","./src/components/referenceview.tsx","./src/components/reviewchangesmodal.tsx","./src/components/segmentdetailsmodal.tsx","./src/components/timelineeditor.tsx","./src/components/transcriptionview.tsx","./src/components/wordeditcontrols.tsx","./src/components/shared/constants.ts","./src/components/shared/styles.ts","./src/components/shared/types.ts","./src/components/shared/components/highlightedtext.tsx","./src/components/shared/components/sourceselector.tsx","./src/components/shared/components/word.tsx","./src/components/shared/hooks/usewordclick.ts","./src/components/shared/utils/keyboardhandlers.ts","./src/components/shared/utils/localstorage.ts","./src/components/shared/utils/referencelinecalculator.ts","./src/components/shared/utils/segmentoperations.ts","./src/components/shared/utils/wordutils.ts","./src/types/global.d.ts"],"version":"5.6.3"}
1
+ {"root":["./src/app.tsx","./src/api.ts","./src/main.tsx","./src/theme.ts","./src/types.ts","./src/validation.ts","./src/vite-env.d.ts","./src/components/addlyricsmodal.tsx","./src/components/audioplayer.tsx","./src/components/correctionmetrics.tsx","./src/components/editmodal.tsx","./src/components/fileupload.tsx","./src/components/findreplacemodal.tsx","./src/components/globalsynceditor.tsx","./src/components/header.tsx","./src/components/lyricsanalyzer.tsx","./src/components/modeselector.tsx","./src/components/previewvideosection.tsx","./src/components/referenceview.tsx","./src/components/reviewchangesmodal.tsx","./src/components/segmentdetailsmodal.tsx","./src/components/timelineeditor.tsx","./src/components/transcriptionview.tsx","./src/components/worddivider.tsx","./src/components/shared/constants.ts","./src/components/shared/styles.ts","./src/components/shared/types.ts","./src/components/shared/components/highlightedtext.tsx","./src/components/shared/components/sourceselector.tsx","./src/components/shared/components/word.tsx","./src/components/shared/hooks/usewordclick.ts","./src/components/shared/utils/keyboardhandlers.ts","./src/components/shared/utils/localstorage.ts","./src/components/shared/utils/referencelinecalculator.ts","./src/components/shared/utils/segmentoperations.ts","./src/components/shared/utils/wordutils.ts","./src/hooks/usemanualsync.ts","./src/types/global.d.ts"],"version":"5.6.3"}
@@ -47,14 +47,14 @@ class BaseLyricsProvider(ABC):
47
47
  converted_cache_path = self._get_cache_path(cache_key, "converted")
48
48
  converted_data = self._load_from_cache(converted_cache_path)
49
49
  if converted_data:
50
- self.logger.info(f"Using cached converted lyrics for {artist} - {title}")
50
+ self.logger.info(f"Using cached converted lyrics for {artist} - {title} from file: {converted_cache_path}")
51
51
  return LyricsData.from_dict(converted_data)
52
52
 
53
53
  # Check raw cache next
54
54
  raw_cache_path = self._get_cache_path(cache_key, "raw")
55
55
  raw_data = self._load_from_cache(raw_cache_path)
56
56
  if raw_data:
57
- self.logger.info(f"Using cached raw lyrics for {artist} - {title}")
57
+ self.logger.info(f"Using cached raw lyrics for {artist} - {title} from file: {raw_cache_path}")
58
58
  converted_result = self._convert_result_format(raw_data)
59
59
  self._save_to_cache(converted_cache_path, converted_result.to_dict())
60
60
  return converted_result
@@ -0,0 +1,44 @@
1
+ from typing import Optional, Dict, Any
2
+ from lyrics_transcriber.lyrics.base_lyrics_provider import BaseLyricsProvider, LyricsProviderConfig
3
+ from lyrics_transcriber.types import LyricsData, LyricsMetadata
4
+
5
+
6
+ class UserInputProvider(BaseLyricsProvider):
7
+ """Provider for manually input lyrics text."""
8
+
9
+ def __init__(self, lyrics_text: str, source_name: str, metadata: Dict[str, Any], *args, **kwargs):
10
+ """Initialize with the user's input text."""
11
+ super().__init__(LyricsProviderConfig(), *args, **kwargs)
12
+ self.lyrics_text = lyrics_text
13
+ self.source_name = source_name
14
+ self.input_metadata = metadata
15
+
16
+ def _fetch_data_from_source(self, artist: str, title: str) -> Optional[Dict[str, Any]]:
17
+ """Return the user's input text as raw data."""
18
+ return {"text": self.lyrics_text, "metadata": self.input_metadata}
19
+
20
+ def _convert_result_format(self, raw_data: Dict[str, Any]) -> LyricsData:
21
+ """Convert the raw text into LyricsData format."""
22
+ # Create segments with words from the text
23
+ segments = self._create_segments_with_words(raw_data["text"])
24
+
25
+ # Create metadata
26
+ metadata = LyricsMetadata(
27
+ source=self.source_name,
28
+ track_name=raw_data["metadata"].get("title", ""),
29
+ artist_names=raw_data["metadata"].get("artist", ""),
30
+ is_synced=False,
31
+ lyrics_provider="manual",
32
+ lyrics_provider_id="",
33
+ album_name=None,
34
+ duration_ms=None,
35
+ explicit=None,
36
+ language=None,
37
+ provider_metadata={},
38
+ )
39
+
40
+ return LyricsData(segments=segments, metadata=metadata, source=self.source_name)
41
+
42
+ def get_name(self) -> str:
43
+ """Return the provider name."""
44
+ return "UserInput"
@@ -37,6 +37,7 @@ class OutputGenerator:
37
37
  self,
38
38
  config: OutputConfig,
39
39
  logger: Optional[logging.Logger] = None,
40
+ preview_mode: bool = False,
40
41
  ):
41
42
  """
42
43
  Initialize OutputGenerator with configuration.
@@ -44,20 +45,12 @@ class OutputGenerator:
44
45
  Args:
45
46
  config: OutputConfig instance with required paths and settings
46
47
  logger: Optional logger instance
48
+ preview_mode: Boolean indicating if the generator is in preview mode
47
49
  """
48
50
  self.config = config
49
51
  self.logger = logger or logging.getLogger(__name__)
50
52
 
51
- self.logger.debug(f"Initializing OutputGenerator with config: {self.config}")
52
-
53
- # Set video resolution parameters
54
- self.video_resolution_num, self.font_size, self.line_height = self._get_video_params(self.config.video_resolution)
55
-
56
- self.segment_resizer = SegmentResizer(max_line_length=self.config.max_line_length, logger=self.logger)
57
-
58
- # Initialize generators
59
- self.plain_text = PlainTextGenerator(self.config.output_dir, self.logger)
60
- self.lyrics_file = LyricsFileGenerator(self.config.output_dir, self.logger)
53
+ self.logger.info(f"Initializing OutputGenerator with config: {self.config}")
61
54
 
62
55
  if self.config.render_video or self.config.generate_cdg:
63
56
  # Load output styles from JSON
@@ -68,10 +61,46 @@ class OutputGenerator:
68
61
  except Exception as e:
69
62
  raise ValueError(f"Failed to load output styles file: {str(e)}")
70
63
 
64
+ # Set video resolution parameters
65
+ self.video_resolution_num, self.font_size, self.line_height = self._get_video_params(self.config.video_resolution)
66
+ self.logger.info(f"Video resolution: {self.video_resolution_num}, font size: {self.font_size}, line height: {self.line_height}")
67
+
68
+ self.segment_resizer = SegmentResizer(max_line_length=self.config.max_line_length, logger=self.logger)
69
+
70
+ # Initialize generators
71
+ self.plain_text = PlainTextGenerator(self.config.output_dir, self.logger)
72
+ self.lyrics_file = LyricsFileGenerator(self.config.output_dir, self.logger)
73
+
71
74
  if self.config.generate_cdg:
72
75
  self.cdg = CDGGenerator(self.config.output_dir, self.logger)
73
76
 
77
+ self.preview_mode = preview_mode
74
78
  if self.config.render_video:
79
+ # Apply preview mode scaling if needed
80
+ if self.preview_mode:
81
+ # Scale down from 4K (2160p) to 360p - factor of 1/6
82
+ scale_factor = 1 / 6
83
+
84
+ # Scale down top padding for preview if it exists
85
+ if "karaoke" in self.config.styles and "top_padding" in self.config.styles["karaoke"]:
86
+ self.logger.info(f"Preview mode: Found top_padding: {self.config.styles['karaoke']['top_padding']}")
87
+ original_padding = self.config.styles["karaoke"]["top_padding"]
88
+ if original_padding is not None:
89
+ # Scale down from 4K (2160p) to 360p - factor of 1/6
90
+ self.config.styles["karaoke"]["top_padding"] = original_padding * scale_factor
91
+ self.logger.info(f"Preview mode: Scaled down top_padding to: {self.config.styles['karaoke']['top_padding']}")
92
+
93
+ # Scale down font size for preview if it exists
94
+ if "karaoke" in self.config.styles and "font_size" in self.config.styles["karaoke"]:
95
+ self.logger.info(f"Preview mode: Found font_size: {self.config.styles['karaoke']['font_size']}")
96
+ original_font_size = self.config.styles["karaoke"]["font_size"]
97
+ if original_font_size is not None:
98
+ # Scale down from 4K (2160p) to 360p - factor of 1/6
99
+ self.font_size = original_font_size * scale_factor
100
+ self.config.styles["karaoke"]["font_size"] = self.font_size
101
+ self.logger.info(f"Preview mode: Scaled down font_size to: {self.font_size}")
102
+
103
+ # Initialize subtitle generator with potentially scaled values
75
104
  self.subtitle = SubtitlesGenerator(
76
105
  output_dir=self.config.output_dir,
77
106
  video_resolution=self.video_resolution_num,
@@ -102,7 +131,6 @@ class OutputGenerator:
102
131
  audio_filepath: str,
103
132
  artist: Optional[str] = None,
104
133
  title: Optional[str] = None,
105
- preview_mode: bool = False,
106
134
  ) -> OutputPaths:
107
135
  """Generate all requested output formats."""
108
136
  outputs = OutputPaths()
@@ -116,7 +144,7 @@ class OutputGenerator:
116
144
  transcription_corrected.resized_segments = resized_segments
117
145
 
118
146
  # For preview, we only need to generate ASS and video
119
- if preview_mode:
147
+ if self.preview_mode:
120
148
  # Generate ASS subtitles for preview
121
149
  outputs.ass = self.subtitle.generate_ass(transcription_corrected.resized_segments, output_prefix, audio_filepath)
122
150
 
@@ -63,8 +63,12 @@ class VideoGenerator:
63
63
  raise FileNotFoundError(f"Audio file not found: {audio_path}")
64
64
 
65
65
  try:
66
- # Create a temporary copy of the ASS file with a safe filename
67
- temp_ass_path = os.path.join(self.cache_dir, "temp_subtitles.ass")
66
+ # Create a temporary copy of the ASS file with a unique filename
67
+ import time
68
+
69
+ safe_prefix = "".join(c if c.isalnum() else "_" for c in output_prefix)
70
+ timestamp = int(time.time() * 1000)
71
+ temp_ass_path = os.path.join(self.cache_dir, f"temp_subtitles_{safe_prefix}_{timestamp}.ass")
68
72
  import shutil
69
73
 
70
74
  shutil.copy2(ass_path, temp_ass_path)
@@ -75,13 +79,14 @@ class VideoGenerator:
75
79
  self.logger.info(f"Video generated: {output_path}")
76
80
 
77
81
  # Clean up temporary file
78
- os.remove(temp_ass_path)
82
+ if os.path.exists(temp_ass_path):
83
+ os.remove(temp_ass_path)
79
84
  return output_path
80
85
 
81
86
  except Exception as e:
82
87
  self.logger.error(f"Failed to generate video: {str(e)}")
83
88
  # Clean up temporary file in case of error
84
- if "temp_ass_path" in locals():
89
+ if "temp_ass_path" in locals() and os.path.exists(temp_ass_path):
85
90
  try:
86
91
  os.remove(temp_ass_path)
87
92
  except:
@@ -109,8 +114,12 @@ class VideoGenerator:
109
114
  raise FileNotFoundError(f"Audio file not found: {audio_path}")
110
115
 
111
116
  try:
112
- # Create a temporary copy of the ASS file with a safe filename
113
- temp_ass_path = os.path.join(self.cache_dir, "temp_preview_subtitles.ass")
117
+ # Create a temporary copy of the ASS file with a unique filename
118
+ import time
119
+
120
+ safe_prefix = "".join(c if c.isalnum() else "_" for c in output_prefix)
121
+ timestamp = int(time.time() * 1000)
122
+ temp_ass_path = os.path.join(self.cache_dir, f"temp_preview_subtitles_{safe_prefix}_{timestamp}.ass")
114
123
  import shutil
115
124
 
116
125
  shutil.copy2(ass_path, temp_ass_path)
@@ -121,13 +130,14 @@ class VideoGenerator:
121
130
  self.logger.info(f"Preview video generated: {output_path}")
122
131
 
123
132
  # Clean up temporary file
124
- os.remove(temp_ass_path)
133
+ if os.path.exists(temp_ass_path):
134
+ os.remove(temp_ass_path)
125
135
  return output_path
126
136
 
127
137
  except Exception as e:
128
138
  self.logger.error(f"Failed to generate preview video: {str(e)}")
129
139
  # Clean up temporary file in case of error
130
- if "temp_ass_path" in locals():
140
+ if "temp_ass_path" in locals() and os.path.exists(temp_ass_path):
131
141
  try:
132
142
  os.remove(temp_ass_path)
133
143
  except: