karaoke-gen 0.90.1__py3-none-any.whl → 0.99.3__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.
- backend/.coveragerc +20 -0
- backend/.gitignore +37 -0
- backend/Dockerfile +43 -0
- backend/Dockerfile.base +74 -0
- backend/README.md +242 -0
- backend/__init__.py +0 -0
- backend/api/__init__.py +0 -0
- backend/api/dependencies.py +457 -0
- backend/api/routes/__init__.py +0 -0
- backend/api/routes/admin.py +835 -0
- backend/api/routes/audio_search.py +913 -0
- backend/api/routes/auth.py +348 -0
- backend/api/routes/file_upload.py +2112 -0
- backend/api/routes/health.py +409 -0
- backend/api/routes/internal.py +435 -0
- backend/api/routes/jobs.py +1629 -0
- backend/api/routes/review.py +652 -0
- backend/api/routes/themes.py +162 -0
- backend/api/routes/users.py +1513 -0
- backend/config.py +172 -0
- backend/main.py +157 -0
- backend/middleware/__init__.py +5 -0
- backend/middleware/audit_logging.py +124 -0
- backend/models/__init__.py +0 -0
- backend/models/job.py +519 -0
- backend/models/requests.py +123 -0
- backend/models/theme.py +153 -0
- backend/models/user.py +254 -0
- backend/models/worker_log.py +164 -0
- backend/pyproject.toml +29 -0
- backend/quick-check.sh +93 -0
- backend/requirements.txt +29 -0
- backend/run_tests.sh +60 -0
- backend/services/__init__.py +0 -0
- backend/services/audio_analysis_service.py +243 -0
- backend/services/audio_editing_service.py +278 -0
- backend/services/audio_search_service.py +702 -0
- backend/services/auth_service.py +630 -0
- backend/services/credential_manager.py +792 -0
- backend/services/discord_service.py +172 -0
- backend/services/dropbox_service.py +301 -0
- backend/services/email_service.py +1093 -0
- backend/services/encoding_interface.py +454 -0
- backend/services/encoding_service.py +502 -0
- backend/services/firestore_service.py +512 -0
- backend/services/flacfetch_client.py +573 -0
- backend/services/gce_encoding/README.md +72 -0
- backend/services/gce_encoding/__init__.py +22 -0
- backend/services/gce_encoding/main.py +589 -0
- backend/services/gce_encoding/requirements.txt +16 -0
- backend/services/gdrive_service.py +356 -0
- backend/services/job_logging.py +258 -0
- backend/services/job_manager.py +853 -0
- backend/services/job_notification_service.py +271 -0
- backend/services/langfuse_preloader.py +98 -0
- backend/services/local_encoding_service.py +590 -0
- backend/services/local_preview_encoding_service.py +407 -0
- backend/services/lyrics_cache_service.py +216 -0
- backend/services/metrics.py +413 -0
- backend/services/nltk_preloader.py +122 -0
- backend/services/packaging_service.py +287 -0
- backend/services/rclone_service.py +106 -0
- backend/services/spacy_preloader.py +65 -0
- backend/services/storage_service.py +209 -0
- backend/services/stripe_service.py +371 -0
- backend/services/structured_logging.py +254 -0
- backend/services/template_service.py +330 -0
- backend/services/theme_service.py +469 -0
- backend/services/tracing.py +543 -0
- backend/services/user_service.py +721 -0
- backend/services/worker_service.py +558 -0
- backend/services/youtube_service.py +112 -0
- backend/services/youtube_upload_service.py +445 -0
- backend/tests/__init__.py +4 -0
- backend/tests/conftest.py +224 -0
- backend/tests/emulator/__init__.py +7 -0
- backend/tests/emulator/conftest.py +109 -0
- backend/tests/emulator/test_e2e_cli_backend.py +1053 -0
- backend/tests/emulator/test_emulator_integration.py +356 -0
- backend/tests/emulator/test_style_loading_direct.py +436 -0
- backend/tests/emulator/test_worker_logs_direct.py +229 -0
- backend/tests/emulator/test_worker_logs_subcollection.py +443 -0
- backend/tests/requirements-test.txt +10 -0
- backend/tests/requirements.txt +6 -0
- backend/tests/test_admin_email_endpoints.py +411 -0
- backend/tests/test_api_integration.py +460 -0
- backend/tests/test_api_routes.py +93 -0
- backend/tests/test_audio_analysis_service.py +294 -0
- backend/tests/test_audio_editing_service.py +386 -0
- backend/tests/test_audio_search.py +1398 -0
- backend/tests/test_audio_services.py +378 -0
- backend/tests/test_auth_firestore.py +231 -0
- backend/tests/test_config_extended.py +68 -0
- backend/tests/test_credential_manager.py +377 -0
- backend/tests/test_dependencies.py +54 -0
- backend/tests/test_discord_service.py +244 -0
- backend/tests/test_distribution_services.py +820 -0
- backend/tests/test_dropbox_service.py +472 -0
- backend/tests/test_email_service.py +492 -0
- backend/tests/test_emulator_integration.py +322 -0
- backend/tests/test_encoding_interface.py +412 -0
- backend/tests/test_file_upload.py +1739 -0
- backend/tests/test_flacfetch_client.py +632 -0
- backend/tests/test_gdrive_service.py +524 -0
- backend/tests/test_instrumental_api.py +431 -0
- backend/tests/test_internal_api.py +343 -0
- backend/tests/test_job_creation_regression.py +583 -0
- backend/tests/test_job_manager.py +356 -0
- backend/tests/test_job_manager_notifications.py +329 -0
- backend/tests/test_job_notification_service.py +443 -0
- backend/tests/test_jobs_api.py +283 -0
- backend/tests/test_local_encoding_service.py +423 -0
- backend/tests/test_local_preview_encoding_service.py +567 -0
- backend/tests/test_main.py +87 -0
- backend/tests/test_models.py +918 -0
- backend/tests/test_packaging_service.py +382 -0
- backend/tests/test_requests.py +201 -0
- backend/tests/test_routes_jobs.py +282 -0
- backend/tests/test_routes_review.py +337 -0
- backend/tests/test_services.py +556 -0
- backend/tests/test_services_extended.py +112 -0
- backend/tests/test_spacy_preloader.py +119 -0
- backend/tests/test_storage_service.py +448 -0
- backend/tests/test_style_upload.py +261 -0
- backend/tests/test_template_service.py +295 -0
- backend/tests/test_theme_service.py +516 -0
- backend/tests/test_unicode_sanitization.py +522 -0
- backend/tests/test_upload_api.py +256 -0
- backend/tests/test_validate.py +156 -0
- backend/tests/test_video_worker_orchestrator.py +847 -0
- backend/tests/test_worker_log_subcollection.py +509 -0
- backend/tests/test_worker_logging.py +365 -0
- backend/tests/test_workers.py +1116 -0
- backend/tests/test_workers_extended.py +178 -0
- backend/tests/test_youtube_service.py +247 -0
- backend/tests/test_youtube_upload_service.py +568 -0
- backend/utils/test_data.py +27 -0
- backend/validate.py +173 -0
- backend/version.py +27 -0
- backend/workers/README.md +597 -0
- backend/workers/__init__.py +11 -0
- backend/workers/audio_worker.py +618 -0
- backend/workers/lyrics_worker.py +683 -0
- backend/workers/render_video_worker.py +483 -0
- backend/workers/screens_worker.py +535 -0
- backend/workers/style_helper.py +198 -0
- backend/workers/video_worker.py +1277 -0
- backend/workers/video_worker_orchestrator.py +701 -0
- backend/workers/worker_logging.py +278 -0
- karaoke_gen/instrumental_review/static/index.html +7 -4
- karaoke_gen/karaoke_finalise/karaoke_finalise.py +6 -1
- karaoke_gen/utils/__init__.py +163 -8
- karaoke_gen/video_background_processor.py +9 -4
- {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/METADATA +1 -1
- {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/RECORD +196 -46
- lyrics_transcriber/correction/agentic/agent.py +17 -6
- lyrics_transcriber/correction/agentic/providers/config.py +9 -5
- lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +96 -93
- lyrics_transcriber/correction/agentic/providers/model_factory.py +27 -6
- lyrics_transcriber/correction/anchor_sequence.py +151 -37
- lyrics_transcriber/correction/corrector.py +192 -130
- lyrics_transcriber/correction/handlers/syllables_match.py +44 -2
- lyrics_transcriber/correction/operations.py +24 -9
- lyrics_transcriber/correction/phrase_analyzer.py +18 -0
- lyrics_transcriber/frontend/package-lock.json +2 -2
- lyrics_transcriber/frontend/package.json +1 -1
- lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +1 -1
- lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +11 -7
- lyrics_transcriber/frontend/src/components/EditActionBar.tsx +31 -5
- lyrics_transcriber/frontend/src/components/EditModal.tsx +28 -10
- lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +123 -27
- lyrics_transcriber/frontend/src/components/EditWordList.tsx +112 -60
- lyrics_transcriber/frontend/src/components/Header.tsx +90 -76
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +53 -31
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/SyncControls.tsx +44 -13
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/TimelineCanvas.tsx +66 -50
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/index.tsx +124 -30
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +1 -1
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +12 -5
- lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +3 -3
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +1 -1
- lyrics_transcriber/frontend/src/components/WordDivider.tsx +11 -7
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +4 -2
- lyrics_transcriber/frontend/src/hooks/useManualSync.ts +103 -1
- lyrics_transcriber/frontend/src/theme.ts +42 -15
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
- lyrics_transcriber/frontend/vite.config.js +5 -0
- lyrics_transcriber/frontend/web_assets/assets/{index-BECn1o8Q.js → index-BSMgOq4Z.js} +6959 -5782
- lyrics_transcriber/frontend/web_assets/assets/index-BSMgOq4Z.js.map +1 -0
- lyrics_transcriber/frontend/web_assets/index.html +6 -2
- lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.svg +5 -0
- lyrics_transcriber/output/generator.py +17 -3
- lyrics_transcriber/output/video.py +60 -95
- lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js.map +0 -1
- {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/WHEEL +0 -0
- {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/entry_points.txt +0 -0
- {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
IconButton,
|
|
5
5
|
Button,
|
|
6
6
|
Pagination,
|
|
7
|
-
Typography
|
|
7
|
+
Typography,
|
|
8
|
+
useMediaQuery,
|
|
9
|
+
useTheme
|
|
8
10
|
} from '@mui/material'
|
|
9
11
|
import DeleteIcon from '@mui/icons-material/Delete'
|
|
10
12
|
import SplitIcon from '@mui/icons-material/CallSplit'
|
|
@@ -34,7 +36,8 @@ const WordRow = memo(function WordRow({
|
|
|
34
36
|
onSplitWord,
|
|
35
37
|
onRemoveWord,
|
|
36
38
|
wordsLength,
|
|
37
|
-
onTabNavigation
|
|
39
|
+
onTabNavigation,
|
|
40
|
+
isMobile
|
|
38
41
|
}: {
|
|
39
42
|
word: Word
|
|
40
43
|
index: number
|
|
@@ -43,11 +46,10 @@ const WordRow = memo(function WordRow({
|
|
|
43
46
|
onRemoveWord: (index: number) => void
|
|
44
47
|
wordsLength: number
|
|
45
48
|
onTabNavigation: (currentIndex: number) => void
|
|
49
|
+
isMobile: boolean
|
|
46
50
|
}) {
|
|
47
51
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
48
|
-
// console.log('KeyDown event:', e.key, 'Shift:', e.shiftKey, 'Index:', index);
|
|
49
52
|
if (e.key === 'Tab' && !e.shiftKey) {
|
|
50
|
-
// console.log('Tab key detected, preventing default and navigating');
|
|
51
53
|
e.preventDefault();
|
|
52
54
|
onTabNavigation(index);
|
|
53
55
|
}
|
|
@@ -56,54 +58,99 @@ const WordRow = memo(function WordRow({
|
|
|
56
58
|
return (
|
|
57
59
|
<Box sx={{
|
|
58
60
|
display: 'flex',
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
flexDirection: isMobile ? 'column' : 'row',
|
|
62
|
+
gap: isMobile ? 1 : 2,
|
|
63
|
+
alignItems: isMobile ? 'stretch' : 'center',
|
|
61
64
|
padding: '4px 0',
|
|
62
65
|
}}>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
66
|
+
{/* Word text field - full width on mobile */}
|
|
67
|
+
<Box sx={{
|
|
68
|
+
display: 'flex',
|
|
69
|
+
gap: 1,
|
|
70
|
+
alignItems: 'center',
|
|
71
|
+
flex: isMobile ? 'none' : 1
|
|
72
|
+
}}>
|
|
73
|
+
<TextField
|
|
74
|
+
label={`Word ${index}`}
|
|
75
|
+
value={word.text}
|
|
76
|
+
onChange={(e) => onWordUpdate(index, { text: e.target.value })}
|
|
77
|
+
onKeyDown={handleKeyDown}
|
|
78
|
+
fullWidth
|
|
79
|
+
size="small"
|
|
80
|
+
id={`word-text-${index}`}
|
|
81
|
+
/>
|
|
82
|
+
{/* Action buttons inline with word on mobile */}
|
|
83
|
+
{isMobile && (
|
|
84
|
+
<>
|
|
85
|
+
<IconButton
|
|
86
|
+
onClick={() => onSplitWord(index)}
|
|
87
|
+
title="Split Word"
|
|
88
|
+
sx={{ color: 'primary.main' }}
|
|
89
|
+
size="small"
|
|
90
|
+
>
|
|
91
|
+
<SplitIcon fontSize="small" />
|
|
92
|
+
</IconButton>
|
|
93
|
+
<IconButton
|
|
94
|
+
onClick={() => onRemoveWord(index)}
|
|
95
|
+
disabled={wordsLength <= 1}
|
|
96
|
+
title="Remove Word"
|
|
97
|
+
sx={{ color: 'error.main' }}
|
|
98
|
+
size="small"
|
|
99
|
+
>
|
|
100
|
+
<DeleteIcon fontSize="small" />
|
|
101
|
+
</IconButton>
|
|
102
|
+
</>
|
|
103
|
+
)}
|
|
104
|
+
</Box>
|
|
105
|
+
|
|
106
|
+
{/* Time fields - row on desktop, separate row on mobile */}
|
|
107
|
+
<Box sx={{
|
|
108
|
+
display: 'flex',
|
|
109
|
+
gap: 1,
|
|
110
|
+
alignItems: 'center',
|
|
111
|
+
justifyContent: isMobile ? 'flex-start' : 'flex-end'
|
|
112
|
+
}}>
|
|
113
|
+
<TextField
|
|
114
|
+
label="Start"
|
|
115
|
+
value={word.start_time?.toFixed(2) ?? ''}
|
|
116
|
+
onChange={(e) => onWordUpdate(index, { start_time: parseFloat(e.target.value) })}
|
|
117
|
+
type="number"
|
|
118
|
+
inputProps={{ step: 0.01 }}
|
|
119
|
+
sx={{ width: isMobile ? '80px' : '100px' }}
|
|
120
|
+
size="small"
|
|
121
|
+
/>
|
|
122
|
+
<TextField
|
|
123
|
+
label="End"
|
|
124
|
+
value={word.end_time?.toFixed(2) ?? ''}
|
|
125
|
+
onChange={(e) => onWordUpdate(index, { end_time: parseFloat(e.target.value) })}
|
|
126
|
+
type="number"
|
|
127
|
+
inputProps={{ step: 0.01 }}
|
|
128
|
+
sx={{ width: isMobile ? '80px' : '100px' }}
|
|
129
|
+
size="small"
|
|
130
|
+
/>
|
|
131
|
+
{/* Action buttons on desktop only */}
|
|
132
|
+
{!isMobile && (
|
|
133
|
+
<>
|
|
134
|
+
<IconButton
|
|
135
|
+
onClick={() => onSplitWord(index)}
|
|
136
|
+
title="Split Word"
|
|
137
|
+
sx={{ color: 'primary.main' }}
|
|
138
|
+
size="small"
|
|
139
|
+
>
|
|
140
|
+
<SplitIcon fontSize="small" />
|
|
141
|
+
</IconButton>
|
|
142
|
+
<IconButton
|
|
143
|
+
onClick={() => onRemoveWord(index)}
|
|
144
|
+
disabled={wordsLength <= 1}
|
|
145
|
+
title="Remove Word"
|
|
146
|
+
sx={{ color: 'error.main' }}
|
|
147
|
+
size="small"
|
|
148
|
+
>
|
|
149
|
+
<DeleteIcon fontSize="small" />
|
|
150
|
+
</IconButton>
|
|
151
|
+
</>
|
|
152
|
+
)}
|
|
153
|
+
</Box>
|
|
107
154
|
</Box>
|
|
108
155
|
);
|
|
109
156
|
});
|
|
@@ -122,7 +169,8 @@ const WordItem = memo(function WordItem({
|
|
|
122
169
|
onMergeSegment,
|
|
123
170
|
wordsLength,
|
|
124
171
|
isGlobal,
|
|
125
|
-
onTabNavigation
|
|
172
|
+
onTabNavigation,
|
|
173
|
+
isMobile
|
|
126
174
|
}: {
|
|
127
175
|
word: Word
|
|
128
176
|
index: number
|
|
@@ -137,6 +185,7 @@ const WordItem = memo(function WordItem({
|
|
|
137
185
|
wordsLength: number
|
|
138
186
|
isGlobal: boolean
|
|
139
187
|
onTabNavigation: (currentIndex: number) => void
|
|
188
|
+
isMobile: boolean
|
|
140
189
|
}) {
|
|
141
190
|
return (
|
|
142
191
|
<Box key={word.id}>
|
|
@@ -148,6 +197,7 @@ const WordItem = memo(function WordItem({
|
|
|
148
197
|
onRemoveWord={onRemoveWord}
|
|
149
198
|
wordsLength={wordsLength}
|
|
150
199
|
onTabNavigation={onTabNavigation}
|
|
200
|
+
isMobile={isMobile}
|
|
151
201
|
/>
|
|
152
202
|
|
|
153
203
|
{/* Word divider with merge/split functionality */}
|
|
@@ -168,16 +218,14 @@ const WordItem = memo(function WordItem({
|
|
|
168
218
|
}
|
|
169
219
|
canMerge={index < wordsLength - 1}
|
|
170
220
|
isLast={index === wordsLength - 1}
|
|
171
|
-
|
|
172
|
-
/>
|
|
221
|
+
/>
|
|
173
222
|
)}
|
|
174
223
|
{isGlobal && (
|
|
175
224
|
<WordDivider
|
|
176
225
|
onAddWord={() => onAddWord(index)}
|
|
177
226
|
onMergeWords={index < wordsLength - 1 ? () => onMergeWords(index) : undefined}
|
|
178
227
|
canMerge={index < wordsLength - 1}
|
|
179
|
-
|
|
180
|
-
/>
|
|
228
|
+
/>
|
|
181
229
|
)}
|
|
182
230
|
</Box>
|
|
183
231
|
);
|
|
@@ -195,6 +243,9 @@ export default function EditWordList({
|
|
|
195
243
|
onMergeSegment,
|
|
196
244
|
isGlobal = false
|
|
197
245
|
}: EditWordListProps) {
|
|
246
|
+
const theme = useTheme()
|
|
247
|
+
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
|
|
248
|
+
|
|
198
249
|
const [replacementText, setReplacementText] = useState('')
|
|
199
250
|
const [page, setPage] = useState(1)
|
|
200
251
|
const pageSize = isGlobal ? 50 : words.length // Use pagination only in global mode
|
|
@@ -289,14 +340,12 @@ export default function EditWordList({
|
|
|
289
340
|
onAddSegmentBefore={() => onAddSegment?.(0)}
|
|
290
341
|
onMergeSegment={() => onMergeSegment?.(false)}
|
|
291
342
|
isFirst={true}
|
|
292
|
-
|
|
293
|
-
/>
|
|
343
|
+
/>
|
|
294
344
|
)}
|
|
295
345
|
{isGlobal && (
|
|
296
346
|
<WordDivider
|
|
297
347
|
onAddWord={() => onAddWord(-1)}
|
|
298
|
-
|
|
299
|
-
/>
|
|
348
|
+
/>
|
|
300
349
|
)}
|
|
301
350
|
|
|
302
351
|
{/* Word list with scrolling */}
|
|
@@ -312,7 +361,9 @@ export default function EditWordList({
|
|
|
312
361
|
width: '8px',
|
|
313
362
|
},
|
|
314
363
|
'&::-webkit-scrollbar-thumb': {
|
|
315
|
-
backgroundColor:
|
|
364
|
+
backgroundColor: (theme) => theme.palette.mode === 'dark'
|
|
365
|
+
? 'rgba(248, 250, 252, 0.2)' // light scrollbar for dark mode
|
|
366
|
+
: 'rgba(30, 41, 59, 0.3)', // dark scrollbar for light mode
|
|
316
367
|
borderRadius: '4px',
|
|
317
368
|
},
|
|
318
369
|
scrollbarWidth: 'thin',
|
|
@@ -336,6 +387,7 @@ export default function EditWordList({
|
|
|
336
387
|
wordsLength={words.length}
|
|
337
388
|
isGlobal={isGlobal}
|
|
338
389
|
onTabNavigation={handleTabNavigation}
|
|
390
|
+
isMobile={isMobile}
|
|
339
391
|
/>
|
|
340
392
|
);
|
|
341
393
|
})}
|
|
@@ -39,6 +39,7 @@ interface HeaderProps {
|
|
|
39
39
|
onFindReplace?: () => void
|
|
40
40
|
onEditAll?: () => void
|
|
41
41
|
onUnCorrectAll?: () => void
|
|
42
|
+
onResetCorrections?: () => void
|
|
42
43
|
onTimingOffset?: () => void
|
|
43
44
|
timingOffsetMs?: number
|
|
44
45
|
onUndo: () => void
|
|
@@ -72,6 +73,7 @@ export default function Header({
|
|
|
72
73
|
onFindReplace,
|
|
73
74
|
onEditAll,
|
|
74
75
|
onUnCorrectAll,
|
|
76
|
+
onResetCorrections,
|
|
75
77
|
onTimingOffset,
|
|
76
78
|
timingOffsetMs = 0,
|
|
77
79
|
onUndo,
|
|
@@ -144,68 +146,44 @@ export default function Header({
|
|
|
144
146
|
</Box>
|
|
145
147
|
)}
|
|
146
148
|
|
|
149
|
+
{/* Review Mode toggle and Load File button */}
|
|
147
150
|
<Box sx={{
|
|
148
151
|
display: 'flex',
|
|
149
|
-
flexDirection: isMobile ? 'column' : 'row',
|
|
150
152
|
gap: 1,
|
|
151
|
-
justifyContent: '
|
|
152
|
-
alignItems:
|
|
153
|
+
justifyContent: 'flex-end',
|
|
154
|
+
alignItems: 'center',
|
|
153
155
|
mb: 1
|
|
154
156
|
}}>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
<
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
label={reviewMode ? "Review Mode" : "Review Off"}
|
|
167
|
-
onClick={() => onReviewModeToggle(!reviewMode)}
|
|
168
|
-
color={reviewMode ? "secondary" : "default"}
|
|
169
|
-
variant={reviewMode ? "filled" : "outlined"}
|
|
170
|
-
size="small"
|
|
171
|
-
sx={{
|
|
172
|
-
cursor: 'pointer',
|
|
173
|
-
'& .MuiChip-icon': { fontSize: '1rem' }
|
|
174
|
-
}}
|
|
175
|
-
/>
|
|
176
|
-
</Tooltip>
|
|
177
|
-
)}
|
|
178
|
-
{!isReadOnly && onAnnotationsToggle && (
|
|
179
|
-
<Tooltip title={annotationsEnabled
|
|
180
|
-
? "Click to disable annotation prompts when editing"
|
|
181
|
-
: "Click to enable annotation prompts when editing"
|
|
182
|
-
}>
|
|
183
|
-
<Chip
|
|
184
|
-
icon={<RateReviewIcon />}
|
|
185
|
-
label={annotationsEnabled ? "Feedback On" : "Feedback Off"}
|
|
186
|
-
onClick={() => onAnnotationsToggle(!annotationsEnabled)}
|
|
187
|
-
color={annotationsEnabled ? "primary" : "default"}
|
|
188
|
-
variant={annotationsEnabled ? "filled" : "outlined"}
|
|
189
|
-
size="small"
|
|
190
|
-
sx={{
|
|
191
|
-
cursor: 'pointer',
|
|
192
|
-
'& .MuiChip-icon': { fontSize: '1rem' }
|
|
193
|
-
}}
|
|
194
|
-
/>
|
|
195
|
-
</Tooltip>
|
|
196
|
-
)}
|
|
197
|
-
{isReadOnly && (
|
|
198
|
-
<Button
|
|
199
|
-
variant="outlined"
|
|
157
|
+
{!isReadOnly && isAgenticMode && onReviewModeToggle && (
|
|
158
|
+
<Tooltip title={reviewMode
|
|
159
|
+
? "Hide inline correction actions"
|
|
160
|
+
: "Show inline actions on all corrections for quick review"
|
|
161
|
+
}>
|
|
162
|
+
<Chip
|
|
163
|
+
icon={<VisibilityIcon />}
|
|
164
|
+
label={reviewMode ? "Review Mode" : "Review Off"}
|
|
165
|
+
onClick={() => onReviewModeToggle(!reviewMode)}
|
|
166
|
+
color={reviewMode ? "secondary" : "default"}
|
|
167
|
+
variant={reviewMode ? "filled" : "outlined"}
|
|
200
168
|
size="small"
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
169
|
+
sx={{
|
|
170
|
+
cursor: 'pointer',
|
|
171
|
+
'& .MuiChip-icon': { fontSize: '1rem' }
|
|
172
|
+
}}
|
|
173
|
+
/>
|
|
174
|
+
</Tooltip>
|
|
175
|
+
)}
|
|
176
|
+
{isReadOnly && (
|
|
177
|
+
<Button
|
|
178
|
+
variant="outlined"
|
|
179
|
+
size="small"
|
|
180
|
+
startIcon={<UploadFileIcon />}
|
|
181
|
+
onClick={onFileLoad}
|
|
182
|
+
fullWidth={isMobile}
|
|
183
|
+
>
|
|
184
|
+
Load File
|
|
185
|
+
</Button>
|
|
186
|
+
)}
|
|
209
187
|
</Box>
|
|
210
188
|
|
|
211
189
|
<Box sx={{
|
|
@@ -213,12 +191,14 @@ export default function Header({
|
|
|
213
191
|
gap: 1,
|
|
214
192
|
mb: 1,
|
|
215
193
|
flexDirection: isMobile ? 'column' : 'row',
|
|
216
|
-
height: '140px'
|
|
194
|
+
height: isMobile ? 'auto' : '140px',
|
|
195
|
+
minHeight: isMobile ? 'auto' : '140px'
|
|
217
196
|
}}>
|
|
218
197
|
<Box sx={{
|
|
219
|
-
width: '280px',
|
|
198
|
+
width: isMobile ? '100%' : '280px',
|
|
199
|
+
minWidth: isMobile ? '100%' : '280px',
|
|
220
200
|
position: 'relative',
|
|
221
|
-
height: '100%'
|
|
201
|
+
height: isMobile ? 'auto' : '100%'
|
|
222
202
|
}}>
|
|
223
203
|
{isAgenticMode ? (
|
|
224
204
|
<AgenticCorrectionMetrics
|
|
@@ -385,15 +365,30 @@ export default function Header({
|
|
|
385
365
|
}}>
|
|
386
366
|
<Box sx={{
|
|
387
367
|
display: 'flex',
|
|
388
|
-
gap:
|
|
389
|
-
flexDirection:
|
|
390
|
-
|
|
391
|
-
|
|
368
|
+
gap: 0.5,
|
|
369
|
+
flexDirection: 'row',
|
|
370
|
+
flexWrap: 'wrap',
|
|
371
|
+
alignItems: 'center',
|
|
372
|
+
minHeight: '32px'
|
|
392
373
|
}}>
|
|
393
374
|
<ModeSelector
|
|
394
375
|
effectiveMode={effectiveMode}
|
|
395
376
|
onChange={onModeChange}
|
|
396
377
|
/>
|
|
378
|
+
{!isReadOnly && onResetCorrections && (
|
|
379
|
+
<Tooltip title="Undo all your changes">
|
|
380
|
+
<Button
|
|
381
|
+
variant="outlined"
|
|
382
|
+
size="small"
|
|
383
|
+
color="warning"
|
|
384
|
+
onClick={onResetCorrections}
|
|
385
|
+
startIcon={<UndoIcon />}
|
|
386
|
+
sx={{ minWidth: 'fit-content', height: '32px' }}
|
|
387
|
+
>
|
|
388
|
+
Undo All
|
|
389
|
+
</Button>
|
|
390
|
+
</Tooltip>
|
|
391
|
+
)}
|
|
397
392
|
{!isReadOnly && (
|
|
398
393
|
<Box sx={{ display: 'flex', height: '32px' }}>
|
|
399
394
|
<Tooltip title="Undo">
|
|
@@ -457,15 +452,17 @@ export default function Header({
|
|
|
457
452
|
</Button>
|
|
458
453
|
)}
|
|
459
454
|
{!isReadOnly && onUnCorrectAll && (
|
|
460
|
-
<
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
455
|
+
<Tooltip title="Revert only automatic AI corrections (keeps your manual edits)">
|
|
456
|
+
<Button
|
|
457
|
+
variant="outlined"
|
|
458
|
+
size="small"
|
|
459
|
+
onClick={onUnCorrectAll}
|
|
460
|
+
startIcon={<RestoreIcon />}
|
|
461
|
+
sx={{ minWidth: 'fit-content', height: '32px' }}
|
|
462
|
+
>
|
|
463
|
+
Undo Auto Corrections
|
|
464
|
+
</Button>
|
|
465
|
+
</Tooltip>
|
|
469
466
|
)}
|
|
470
467
|
{!isReadOnly && onTimingOffset && (
|
|
471
468
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
|
@@ -480,10 +477,10 @@ export default function Header({
|
|
|
480
477
|
Timing Offset
|
|
481
478
|
</Button>
|
|
482
479
|
{timingOffsetMs !== 0 && (
|
|
483
|
-
<Typography
|
|
484
|
-
variant="body2"
|
|
485
|
-
sx={{
|
|
486
|
-
ml: 1,
|
|
480
|
+
<Typography
|
|
481
|
+
variant="body2"
|
|
482
|
+
sx={{
|
|
483
|
+
ml: 1,
|
|
487
484
|
fontWeight: 'bold',
|
|
488
485
|
color: theme.palette.secondary.main
|
|
489
486
|
}}
|
|
@@ -493,6 +490,23 @@ export default function Header({
|
|
|
493
490
|
)}
|
|
494
491
|
</Box>
|
|
495
492
|
)}
|
|
493
|
+
{!isReadOnly && onAnnotationsToggle && (
|
|
494
|
+
<Tooltip title={annotationsEnabled
|
|
495
|
+
? "Click to disable annotation prompts when editing"
|
|
496
|
+
: "Click to enable annotation prompts when editing"
|
|
497
|
+
}>
|
|
498
|
+
<Button
|
|
499
|
+
variant="outlined"
|
|
500
|
+
size="small"
|
|
501
|
+
onClick={() => onAnnotationsToggle(!annotationsEnabled)}
|
|
502
|
+
startIcon={<RateReviewIcon />}
|
|
503
|
+
color={annotationsEnabled ? "primary" : "inherit"}
|
|
504
|
+
sx={{ minWidth: 'fit-content', height: '32px' }}
|
|
505
|
+
>
|
|
506
|
+
{annotationsEnabled ? "Feedback On" : "Feedback Off"}
|
|
507
|
+
</Button>
|
|
508
|
+
</Tooltip>
|
|
509
|
+
)}
|
|
496
510
|
<AudioPlayer
|
|
497
511
|
apiClient={apiClient}
|
|
498
512
|
onTimeUpdate={onTimeUpdate}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
WordCorrection,
|
|
11
11
|
CorrectionAnnotation
|
|
12
12
|
} from '../types'
|
|
13
|
-
import { Box, Button, Grid, useMediaQuery, useTheme } from '@mui/material'
|
|
13
|
+
import { Box, Button, Grid, Typography, useMediaQuery, useTheme } from '@mui/material'
|
|
14
14
|
import { ApiClient } from '../api'
|
|
15
15
|
import ReferenceView from './ReferenceView'
|
|
16
16
|
import TranscriptionView from './TranscriptionView'
|
|
@@ -33,7 +33,7 @@ import { setupKeyboardHandlers, setModalHandler, getModalState } from './shared/
|
|
|
33
33
|
import Header from './Header'
|
|
34
34
|
import { getWordsFromIds } from './shared/utils/wordUtils'
|
|
35
35
|
import AddLyricsModal from './AddLyricsModal'
|
|
36
|
-
import {
|
|
36
|
+
import { OndemandVideo } from '@mui/icons-material'
|
|
37
37
|
import FindReplaceModal from './FindReplaceModal'
|
|
38
38
|
import TimingOffsetModal from './TimingOffsetModal'
|
|
39
39
|
import { applyOffsetToCorrectionData, applyOffsetToSegment } from './shared/utils/timingUtils'
|
|
@@ -216,6 +216,7 @@ interface MemoizedHeaderProps {
|
|
|
216
216
|
canUndo: boolean
|
|
217
217
|
canRedo: boolean
|
|
218
218
|
onUnCorrectAll: () => void
|
|
219
|
+
onResetCorrections: () => void
|
|
219
220
|
annotationsEnabled: boolean
|
|
220
221
|
onAnnotationsToggle: (enabled: boolean) => void
|
|
221
222
|
// Review mode props
|
|
@@ -250,6 +251,7 @@ const MemoizedHeader = memo(function MemoizedHeader({
|
|
|
250
251
|
canUndo,
|
|
251
252
|
canRedo,
|
|
252
253
|
onUnCorrectAll,
|
|
254
|
+
onResetCorrections,
|
|
253
255
|
annotationsEnabled,
|
|
254
256
|
onAnnotationsToggle,
|
|
255
257
|
reviewMode,
|
|
@@ -281,6 +283,7 @@ const MemoizedHeader = memo(function MemoizedHeader({
|
|
|
281
283
|
canUndo={canUndo}
|
|
282
284
|
canRedo={canRedo}
|
|
283
285
|
onUnCorrectAll={onUnCorrectAll}
|
|
286
|
+
onResetCorrections={onResetCorrections}
|
|
284
287
|
annotationsEnabled={annotationsEnabled}
|
|
285
288
|
onAnnotationsToggle={onAnnotationsToggle}
|
|
286
289
|
reviewMode={reviewMode}
|
|
@@ -1255,8 +1258,6 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
|
1255
1258
|
|
|
1256
1259
|
return (
|
|
1257
1260
|
<Box sx={{
|
|
1258
|
-
p: 1,
|
|
1259
|
-
pb: 3,
|
|
1260
1261
|
maxWidth: '100%',
|
|
1261
1262
|
overflowX: 'hidden'
|
|
1262
1263
|
}}>
|
|
@@ -1282,6 +1283,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
|
1282
1283
|
canUndo={canUndo}
|
|
1283
1284
|
canRedo={canRedo}
|
|
1284
1285
|
onUnCorrectAll={handleUnCorrectAll}
|
|
1286
|
+
onResetCorrections={handleResetCorrections}
|
|
1285
1287
|
annotationsEnabled={annotationsEnabled}
|
|
1286
1288
|
onAnnotationsToggle={handleAnnotationsToggle}
|
|
1287
1289
|
reviewMode={reviewMode}
|
|
@@ -1291,7 +1293,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
|
1291
1293
|
onRevertAllCorrections={handleRevertAllCorrections}
|
|
1292
1294
|
/>
|
|
1293
1295
|
|
|
1294
|
-
<Grid container direction={isMobile ? 'column' : 'row'}>
|
|
1296
|
+
<Grid container direction={isMobile ? 'column' : 'row'} spacing={1}>
|
|
1295
1297
|
<Grid item xs={12} md={6}>
|
|
1296
1298
|
<MemoizedTranscriptionView
|
|
1297
1299
|
data={displayData}
|
|
@@ -1316,32 +1318,6 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
|
1316
1318
|
onAcceptCorrection={handleAcceptCorrection}
|
|
1317
1319
|
onShowCorrectionDetail={handleShowCorrectionDetail}
|
|
1318
1320
|
/>
|
|
1319
|
-
{!isReadOnly && apiClient && (
|
|
1320
|
-
<Box sx={{
|
|
1321
|
-
mt: 2,
|
|
1322
|
-
mb: 3,
|
|
1323
|
-
display: 'flex',
|
|
1324
|
-
justifyContent: 'space-between',
|
|
1325
|
-
width: '100%'
|
|
1326
|
-
}}>
|
|
1327
|
-
<Button
|
|
1328
|
-
variant="outlined"
|
|
1329
|
-
color="warning"
|
|
1330
|
-
onClick={handleResetCorrections}
|
|
1331
|
-
startIcon={<RestoreFromTrash />}
|
|
1332
|
-
>
|
|
1333
|
-
Reset Corrections
|
|
1334
|
-
</Button>
|
|
1335
|
-
<Button
|
|
1336
|
-
variant="contained"
|
|
1337
|
-
onClick={handleFinishReview}
|
|
1338
|
-
disabled={isReviewComplete}
|
|
1339
|
-
endIcon={<OndemandVideo />}
|
|
1340
|
-
>
|
|
1341
|
-
{isReviewComplete ? 'Review Complete' : 'Preview Video'}
|
|
1342
|
-
</Button>
|
|
1343
|
-
</Box>
|
|
1344
|
-
)}
|
|
1345
1321
|
</Grid>
|
|
1346
1322
|
<Grid item xs={12} md={6}>
|
|
1347
1323
|
<MemoizedReferenceView
|
|
@@ -1362,6 +1338,52 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
|
1362
1338
|
</Grid>
|
|
1363
1339
|
</Grid>
|
|
1364
1340
|
|
|
1341
|
+
{/* Spacer for sticky footer */}
|
|
1342
|
+
{!isReadOnly && apiClient && <Box sx={{ height: 64 }} />}
|
|
1343
|
+
|
|
1344
|
+
{/* Sticky footer bar with Preview Video button */}
|
|
1345
|
+
{!isReadOnly && apiClient && (
|
|
1346
|
+
<Box
|
|
1347
|
+
sx={{
|
|
1348
|
+
position: 'fixed',
|
|
1349
|
+
bottom: 0,
|
|
1350
|
+
left: 0,
|
|
1351
|
+
right: 0,
|
|
1352
|
+
bgcolor: 'background.paper',
|
|
1353
|
+
borderTop: 1,
|
|
1354
|
+
borderColor: 'divider',
|
|
1355
|
+
boxShadow: '0 -2px 10px rgba(0,0,0,0.1)',
|
|
1356
|
+
py: 1.5,
|
|
1357
|
+
px: 2,
|
|
1358
|
+
zIndex: 1100,
|
|
1359
|
+
display: 'flex',
|
|
1360
|
+
justifyContent: 'center',
|
|
1361
|
+
alignItems: 'center',
|
|
1362
|
+
gap: 2
|
|
1363
|
+
}}
|
|
1364
|
+
>
|
|
1365
|
+
<Typography
|
|
1366
|
+
variant="body2"
|
|
1367
|
+
sx={{ color: 'text.secondary' }}
|
|
1368
|
+
>
|
|
1369
|
+
Lyrics look good?
|
|
1370
|
+
</Typography>
|
|
1371
|
+
<Button
|
|
1372
|
+
variant="contained"
|
|
1373
|
+
onClick={handleFinishReview}
|
|
1374
|
+
disabled={isReviewComplete}
|
|
1375
|
+
endIcon={<OndemandVideo />}
|
|
1376
|
+
sx={{
|
|
1377
|
+
px: 2.5,
|
|
1378
|
+
py: 0.75,
|
|
1379
|
+
fontWeight: 500
|
|
1380
|
+
}}
|
|
1381
|
+
>
|
|
1382
|
+
{isReviewComplete ? 'Review Complete' : 'Preview Video'}
|
|
1383
|
+
</Button>
|
|
1384
|
+
</Box>
|
|
1385
|
+
)}
|
|
1386
|
+
|
|
1365
1387
|
|
|
1366
1388
|
|
|
1367
1389
|
<EditModal
|