lyrics-transcriber 0.56.1__py3-none-any.whl → 0.57.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.
- lyrics_transcriber/correction/operations.py +352 -0
- lyrics_transcriber/frontend/package.json +1 -1
- lyrics_transcriber/frontend/web_assets/assets/{index-C6nHrD6T.js → index-BEnewYea.js} +2 -2
- lyrics_transcriber/frontend/web_assets/assets/{index-C6nHrD6T.js.map → index-BEnewYea.js.map} +1 -1
- lyrics_transcriber/frontend/web_assets/index.html +1 -1
- lyrics_transcriber/review/server.py +25 -250
- lyrics_transcriber/transcribers/audioshake.py +28 -8
- {lyrics_transcriber-0.56.1.dist-info → lyrics_transcriber-0.57.1.dist-info}/METADATA +1 -1
- {lyrics_transcriber-0.56.1.dist-info → lyrics_transcriber-0.57.1.dist-info}/RECORD +12 -11
- {lyrics_transcriber-0.56.1.dist-info → lyrics_transcriber-0.57.1.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.56.1.dist-info → lyrics_transcriber-0.57.1.dist-info}/WHEEL +0 -0
- {lyrics_transcriber-0.56.1.dist-info → lyrics_transcriber-0.57.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,352 @@
|
|
1
|
+
"""
|
2
|
+
Correction Operations Module
|
3
|
+
|
4
|
+
This module contains reusable correction operations that can be shared between
|
5
|
+
the local ReviewServer and remote Modal serverless implementations.
|
6
|
+
|
7
|
+
These operations handle dynamic updates to correction results including:
|
8
|
+
- Adding new lyrics sources
|
9
|
+
- Updating correction handlers
|
10
|
+
- Generating preview videos
|
11
|
+
- Updating correction data
|
12
|
+
"""
|
13
|
+
|
14
|
+
import json
|
15
|
+
import hashlib
|
16
|
+
import logging
|
17
|
+
from typing import Dict, Any, List, Optional
|
18
|
+
from pathlib import Path
|
19
|
+
|
20
|
+
from lyrics_transcriber.types import (
|
21
|
+
CorrectionResult,
|
22
|
+
WordCorrection,
|
23
|
+
LyricsSegment,
|
24
|
+
Word,
|
25
|
+
TranscriptionResult,
|
26
|
+
TranscriptionData,
|
27
|
+
LyricsData
|
28
|
+
)
|
29
|
+
from lyrics_transcriber.correction.corrector import LyricsCorrector
|
30
|
+
from lyrics_transcriber.lyrics.user_input_provider import UserInputProvider
|
31
|
+
from lyrics_transcriber.output.generator import OutputGenerator
|
32
|
+
from lyrics_transcriber.core.config import OutputConfig
|
33
|
+
|
34
|
+
|
35
|
+
class CorrectionOperations:
|
36
|
+
"""Static methods for common correction operations."""
|
37
|
+
|
38
|
+
@staticmethod
|
39
|
+
def update_correction_result_with_data(
|
40
|
+
base_result: CorrectionResult,
|
41
|
+
updated_data: Dict[str, Any]
|
42
|
+
) -> CorrectionResult:
|
43
|
+
"""Update a CorrectionResult with new correction data from dict."""
|
44
|
+
return CorrectionResult(
|
45
|
+
corrections=[
|
46
|
+
WordCorrection(
|
47
|
+
original_word=c.get("original_word", "").strip(),
|
48
|
+
corrected_word=c.get("corrected_word", "").strip(),
|
49
|
+
original_position=c.get("original_position", 0),
|
50
|
+
source=c.get("source", "review"),
|
51
|
+
reason=c.get("reason", "manual_review"),
|
52
|
+
segment_index=c.get("segment_index", 0),
|
53
|
+
confidence=c.get("confidence"),
|
54
|
+
alternatives=c.get("alternatives", {}),
|
55
|
+
is_deletion=c.get("is_deletion", False),
|
56
|
+
split_index=c.get("split_index"),
|
57
|
+
split_total=c.get("split_total"),
|
58
|
+
corrected_position=c.get("corrected_position"),
|
59
|
+
reference_positions=c.get("reference_positions"),
|
60
|
+
length=c.get("length", 1),
|
61
|
+
handler=c.get("handler"),
|
62
|
+
word_id=c.get("word_id"),
|
63
|
+
corrected_word_id=c.get("corrected_word_id"),
|
64
|
+
)
|
65
|
+
for c in updated_data["corrections"]
|
66
|
+
],
|
67
|
+
corrected_segments=[
|
68
|
+
LyricsSegment(
|
69
|
+
id=s["id"],
|
70
|
+
text=s["text"].strip(),
|
71
|
+
words=[
|
72
|
+
Word(
|
73
|
+
id=w["id"],
|
74
|
+
text=w["text"].strip(),
|
75
|
+
start_time=w["start_time"],
|
76
|
+
end_time=w["end_time"],
|
77
|
+
confidence=w.get("confidence"),
|
78
|
+
created_during_correction=w.get("created_during_correction", False),
|
79
|
+
)
|
80
|
+
for w in s["words"]
|
81
|
+
],
|
82
|
+
start_time=s["start_time"],
|
83
|
+
end_time=s["end_time"],
|
84
|
+
)
|
85
|
+
for s in updated_data["corrected_segments"]
|
86
|
+
],
|
87
|
+
# Copy existing fields from the base result
|
88
|
+
original_segments=base_result.original_segments,
|
89
|
+
corrections_made=len(updated_data["corrections"]),
|
90
|
+
confidence=base_result.confidence,
|
91
|
+
reference_lyrics=base_result.reference_lyrics,
|
92
|
+
anchor_sequences=base_result.anchor_sequences,
|
93
|
+
gap_sequences=base_result.gap_sequences,
|
94
|
+
resized_segments=None, # Will be generated if needed
|
95
|
+
metadata=base_result.metadata,
|
96
|
+
correction_steps=base_result.correction_steps,
|
97
|
+
word_id_map=base_result.word_id_map,
|
98
|
+
segment_id_map=base_result.segment_id_map,
|
99
|
+
)
|
100
|
+
|
101
|
+
@staticmethod
|
102
|
+
def add_lyrics_source(
|
103
|
+
correction_result: CorrectionResult,
|
104
|
+
source: str,
|
105
|
+
lyrics_text: str,
|
106
|
+
cache_dir: str,
|
107
|
+
logger: Optional[logging.Logger] = None
|
108
|
+
) -> CorrectionResult:
|
109
|
+
"""
|
110
|
+
Add a new lyrics source and rerun correction.
|
111
|
+
|
112
|
+
Args:
|
113
|
+
correction_result: Current correction result
|
114
|
+
source: Name of the new lyrics source
|
115
|
+
lyrics_text: The lyrics text content
|
116
|
+
cache_dir: Cache directory for correction operations
|
117
|
+
logger: Optional logger instance
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
Updated CorrectionResult with new lyrics source and corrections
|
121
|
+
|
122
|
+
Raises:
|
123
|
+
ValueError: If source name is already in use or inputs are invalid
|
124
|
+
"""
|
125
|
+
if not logger:
|
126
|
+
logger = logging.getLogger(__name__)
|
127
|
+
|
128
|
+
logger.info(f"Adding lyrics source '{source}' with {len(lyrics_text)} characters")
|
129
|
+
|
130
|
+
# Validate inputs
|
131
|
+
if not source or not lyrics_text:
|
132
|
+
raise ValueError("Source name and lyrics text are required")
|
133
|
+
|
134
|
+
if source in correction_result.reference_lyrics:
|
135
|
+
raise ValueError(f"Source name '{source}' is already in use")
|
136
|
+
|
137
|
+
# Store existing audio hash
|
138
|
+
audio_hash = correction_result.metadata.get("audio_hash") if correction_result.metadata else None
|
139
|
+
|
140
|
+
# Create lyrics data using the provider
|
141
|
+
logger.info("Creating LyricsData using UserInputProvider")
|
142
|
+
provider = UserInputProvider(
|
143
|
+
lyrics_text=lyrics_text,
|
144
|
+
source_name=source,
|
145
|
+
metadata=correction_result.metadata or {},
|
146
|
+
logger=logger
|
147
|
+
)
|
148
|
+
lyrics_data = provider._convert_result_format({
|
149
|
+
"text": lyrics_text,
|
150
|
+
"metadata": correction_result.metadata or {}
|
151
|
+
})
|
152
|
+
logger.info(f"Created LyricsData with {len(lyrics_data.segments)} segments")
|
153
|
+
|
154
|
+
# Add to reference lyrics (create a copy to avoid modifying original)
|
155
|
+
updated_reference_lyrics = correction_result.reference_lyrics.copy()
|
156
|
+
updated_reference_lyrics[source] = lyrics_data
|
157
|
+
logger.info(f"Added source '{source}' to reference lyrics")
|
158
|
+
|
159
|
+
# Create TranscriptionData from original segments
|
160
|
+
transcription_data = TranscriptionData(
|
161
|
+
segments=correction_result.original_segments,
|
162
|
+
words=[word for segment in correction_result.original_segments for word in segment.words],
|
163
|
+
text="\n".join(segment.text for segment in correction_result.original_segments),
|
164
|
+
source="original",
|
165
|
+
)
|
166
|
+
|
167
|
+
# Get currently enabled handlers from metadata
|
168
|
+
enabled_handlers = None
|
169
|
+
if correction_result.metadata:
|
170
|
+
enabled_handlers = correction_result.metadata.get("enabled_handlers")
|
171
|
+
|
172
|
+
# Rerun correction with updated reference lyrics
|
173
|
+
logger.info("Running correction with updated reference lyrics")
|
174
|
+
corrector = LyricsCorrector(
|
175
|
+
cache_dir=cache_dir,
|
176
|
+
enabled_handlers=enabled_handlers,
|
177
|
+
logger=logger,
|
178
|
+
)
|
179
|
+
|
180
|
+
updated_result = corrector.run(
|
181
|
+
transcription_results=[TranscriptionResult(name="original", priority=1, result=transcription_data)],
|
182
|
+
lyrics_results=updated_reference_lyrics,
|
183
|
+
metadata=correction_result.metadata,
|
184
|
+
)
|
185
|
+
|
186
|
+
# Update metadata with handler state
|
187
|
+
if not updated_result.metadata:
|
188
|
+
updated_result.metadata = {}
|
189
|
+
updated_result.metadata.update({
|
190
|
+
"available_handlers": corrector.all_handlers,
|
191
|
+
"enabled_handlers": [getattr(handler, "name", handler.__class__.__name__) for handler in corrector.handlers],
|
192
|
+
})
|
193
|
+
|
194
|
+
# Restore audio hash
|
195
|
+
if audio_hash:
|
196
|
+
updated_result.metadata["audio_hash"] = audio_hash
|
197
|
+
|
198
|
+
logger.info(f"Successfully added lyrics source '{source}' and updated corrections")
|
199
|
+
return updated_result
|
200
|
+
|
201
|
+
@staticmethod
|
202
|
+
def update_correction_handlers(
|
203
|
+
correction_result: CorrectionResult,
|
204
|
+
enabled_handlers: List[str],
|
205
|
+
cache_dir: str,
|
206
|
+
logger: Optional[logging.Logger] = None
|
207
|
+
) -> CorrectionResult:
|
208
|
+
"""
|
209
|
+
Update enabled correction handlers and rerun correction.
|
210
|
+
|
211
|
+
Args:
|
212
|
+
correction_result: Current correction result
|
213
|
+
enabled_handlers: List of handler names to enable
|
214
|
+
cache_dir: Cache directory for correction operations
|
215
|
+
logger: Optional logger instance
|
216
|
+
|
217
|
+
Returns:
|
218
|
+
Updated CorrectionResult with new handler configuration
|
219
|
+
"""
|
220
|
+
if not logger:
|
221
|
+
logger = logging.getLogger(__name__)
|
222
|
+
|
223
|
+
logger.info(f"Updating correction handlers: {enabled_handlers}")
|
224
|
+
|
225
|
+
# Store existing audio hash
|
226
|
+
audio_hash = correction_result.metadata.get("audio_hash") if correction_result.metadata else None
|
227
|
+
|
228
|
+
# Update metadata with new handler configuration
|
229
|
+
updated_metadata = (correction_result.metadata or {}).copy()
|
230
|
+
updated_metadata["enabled_handlers"] = enabled_handlers
|
231
|
+
|
232
|
+
# Create TranscriptionData from original segments
|
233
|
+
transcription_data = TranscriptionData(
|
234
|
+
segments=correction_result.original_segments,
|
235
|
+
words=[word for segment in correction_result.original_segments for word in segment.words],
|
236
|
+
text="\n".join(segment.text for segment in correction_result.original_segments),
|
237
|
+
source="original",
|
238
|
+
)
|
239
|
+
|
240
|
+
# Rerun correction with updated handlers
|
241
|
+
logger.info("Running correction with updated handlers")
|
242
|
+
corrector = LyricsCorrector(
|
243
|
+
cache_dir=cache_dir,
|
244
|
+
enabled_handlers=enabled_handlers,
|
245
|
+
logger=logger,
|
246
|
+
)
|
247
|
+
|
248
|
+
updated_result = corrector.run(
|
249
|
+
transcription_results=[TranscriptionResult(name="original", priority=1, result=transcription_data)],
|
250
|
+
lyrics_results=correction_result.reference_lyrics,
|
251
|
+
metadata=updated_metadata,
|
252
|
+
)
|
253
|
+
|
254
|
+
# Update metadata with handler state
|
255
|
+
if not updated_result.metadata:
|
256
|
+
updated_result.metadata = {}
|
257
|
+
updated_result.metadata.update({
|
258
|
+
"available_handlers": corrector.all_handlers,
|
259
|
+
"enabled_handlers": [getattr(handler, "name", handler.__class__.__name__) for handler in corrector.handlers],
|
260
|
+
})
|
261
|
+
|
262
|
+
# Restore audio hash
|
263
|
+
if audio_hash:
|
264
|
+
updated_result.metadata["audio_hash"] = audio_hash
|
265
|
+
|
266
|
+
logger.info(f"Successfully updated handlers: {enabled_handlers}")
|
267
|
+
return updated_result
|
268
|
+
|
269
|
+
@staticmethod
|
270
|
+
def generate_preview_video(
|
271
|
+
correction_result: CorrectionResult,
|
272
|
+
updated_data: Dict[str, Any],
|
273
|
+
output_config: OutputConfig,
|
274
|
+
audio_filepath: str,
|
275
|
+
artist: Optional[str] = None,
|
276
|
+
title: Optional[str] = None,
|
277
|
+
logger: Optional[logging.Logger] = None
|
278
|
+
) -> Dict[str, Any]:
|
279
|
+
"""
|
280
|
+
Generate a preview video with current corrections.
|
281
|
+
|
282
|
+
Args:
|
283
|
+
correction_result: Current correction result
|
284
|
+
updated_data: Updated correction data for preview
|
285
|
+
output_config: Output configuration
|
286
|
+
audio_filepath: Path to audio file
|
287
|
+
artist: Optional artist name
|
288
|
+
title: Optional title
|
289
|
+
logger: Optional logger instance
|
290
|
+
|
291
|
+
Returns:
|
292
|
+
Dict with status, preview_hash, and video_path
|
293
|
+
|
294
|
+
Raises:
|
295
|
+
ValueError: If preview video generation fails
|
296
|
+
"""
|
297
|
+
if not logger:
|
298
|
+
logger = logging.getLogger(__name__)
|
299
|
+
|
300
|
+
logger.info("Generating preview video with corrected data")
|
301
|
+
|
302
|
+
# Create temporary correction result with updated data
|
303
|
+
temp_correction = CorrectionOperations.update_correction_result_with_data(
|
304
|
+
correction_result, updated_data
|
305
|
+
)
|
306
|
+
|
307
|
+
# Generate a unique hash for this preview
|
308
|
+
preview_data = json.dumps(updated_data, sort_keys=True).encode("utf-8")
|
309
|
+
preview_hash = hashlib.md5(preview_data).hexdigest()[:12]
|
310
|
+
|
311
|
+
# Set up preview config
|
312
|
+
preview_config = OutputConfig(
|
313
|
+
output_dir=str(Path(output_config.output_dir) / "previews"),
|
314
|
+
cache_dir=output_config.cache_dir,
|
315
|
+
output_styles_json=output_config.output_styles_json,
|
316
|
+
video_resolution="360p", # Force 360p for preview
|
317
|
+
render_video=True,
|
318
|
+
generate_cdg=False,
|
319
|
+
generate_plain_text=False,
|
320
|
+
generate_lrc=False,
|
321
|
+
fetch_lyrics=False,
|
322
|
+
run_transcription=False,
|
323
|
+
run_correction=False,
|
324
|
+
)
|
325
|
+
|
326
|
+
# Create previews directory
|
327
|
+
preview_dir = Path(output_config.output_dir) / "previews"
|
328
|
+
preview_dir.mkdir(exist_ok=True)
|
329
|
+
|
330
|
+
# Initialize output generator
|
331
|
+
output_generator = OutputGenerator(config=preview_config, logger=logger, preview_mode=True)
|
332
|
+
|
333
|
+
# Generate preview outputs
|
334
|
+
preview_outputs = output_generator.generate_outputs(
|
335
|
+
transcription_corrected=temp_correction,
|
336
|
+
lyrics_results={}, # Empty dict since we don't need lyrics results for preview
|
337
|
+
output_prefix=f"preview_{preview_hash}",
|
338
|
+
audio_filepath=audio_filepath,
|
339
|
+
artist=artist,
|
340
|
+
title=title,
|
341
|
+
)
|
342
|
+
|
343
|
+
if not preview_outputs.video:
|
344
|
+
raise ValueError("Preview video generation failed")
|
345
|
+
|
346
|
+
logger.info(f"Generated preview video: {preview_outputs.video}")
|
347
|
+
|
348
|
+
return {
|
349
|
+
"status": "success",
|
350
|
+
"preview_hash": preview_hash,
|
351
|
+
"video_path": preview_outputs.video
|
352
|
+
}
|
@@ -38915,7 +38915,7 @@ const theme = createTheme({
|
|
38915
38915
|
spacing: (factor) => `${0.6 * factor}rem`
|
38916
38916
|
// Further reduced from 0.8 * factor
|
38917
38917
|
});
|
38918
|
-
const version = "0.
|
38918
|
+
const version = "0.57.1";
|
38919
38919
|
const packageJson = {
|
38920
38920
|
version
|
38921
38921
|
};
|
@@ -38926,4 +38926,4 @@ ReactDOM$1.createRoot(document.getElementById("root")).render(
|
|
38926
38926
|
/* @__PURE__ */ jsxRuntimeExports.jsx(App, {})
|
38927
38927
|
] })
|
38928
38928
|
);
|
38929
|
-
//# sourceMappingURL=index-
|
38929
|
+
//# sourceMappingURL=index-BEnewYea.js.map
|