karaoke-gen 0.90.1__py3-none-any.whl → 0.96.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.
- 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 +742 -0
- backend/api/routes/audio_search.py +903 -0
- backend/api/routes/auth.py +348 -0
- backend/api/routes/file_upload.py +2076 -0
- backend/api/routes/health.py +344 -0
- backend/api/routes/internal.py +435 -0
- backend/api/routes/jobs.py +1610 -0
- backend/api/routes/review.py +652 -0
- backend/api/routes/themes.py +162 -0
- backend/api/routes/users.py +1014 -0
- backend/config.py +172 -0
- backend/main.py +133 -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 +405 -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 +842 -0
- backend/services/job_notification_service.py +271 -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/packaging_service.py +287 -0
- backend/services/rclone_service.py +106 -0
- backend/services/storage_service.py +209 -0
- backend/services/stripe_service.py +275 -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 +88 -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 +339 -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 +273 -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_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/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 +525 -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.96.0.dist-info}/METADATA +1 -1
- {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.96.0.dist-info}/RECORD +186 -41
- lyrics_transcriber/correction/agentic/providers/config.py +9 -5
- lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +1 -51
- lyrics_transcriber/correction/corrector.py +192 -130
- lyrics_transcriber/correction/operations.py +24 -9
- 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.96.0.dist-info}/WHEEL +0 -0
- {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.96.0.dist-info}/entry_points.txt +0 -0
- {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.96.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
IconButton,
|
|
7
7
|
Box,
|
|
8
8
|
CircularProgress,
|
|
9
|
-
Typography
|
|
9
|
+
Typography,
|
|
10
|
+
useMediaQuery,
|
|
11
|
+
useTheme
|
|
10
12
|
} from '@mui/material'
|
|
11
13
|
import CloseIcon from '@mui/icons-material/Close'
|
|
12
14
|
import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline'
|
|
@@ -33,6 +35,8 @@ interface TimelineSectionProps {
|
|
|
33
35
|
onPlaySegment?: (startTime: number) => void
|
|
34
36
|
startManualSync: () => void
|
|
35
37
|
isGlobal: boolean
|
|
38
|
+
onTapStart?: () => void
|
|
39
|
+
onTapEnd?: () => void
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
const MemoizedTimelineSection = memo(function TimelineSection({
|
|
@@ -47,7 +51,9 @@ const MemoizedTimelineSection = memo(function TimelineSection({
|
|
|
47
51
|
onWordUpdate,
|
|
48
52
|
onPlaySegment,
|
|
49
53
|
startManualSync,
|
|
50
|
-
isGlobal
|
|
54
|
+
isGlobal,
|
|
55
|
+
onTapStart,
|
|
56
|
+
onTapEnd
|
|
51
57
|
}: TimelineSectionProps) {
|
|
52
58
|
return (
|
|
53
59
|
<EditTimelineSection
|
|
@@ -66,6 +72,8 @@ const MemoizedTimelineSection = memo(function TimelineSection({
|
|
|
66
72
|
onPlaySegment={onPlaySegment}
|
|
67
73
|
startManualSync={startManualSync}
|
|
68
74
|
isGlobal={isGlobal}
|
|
75
|
+
onTapStart={onTapStart}
|
|
76
|
+
onTapEnd={onTapEnd}
|
|
69
77
|
/>
|
|
70
78
|
)
|
|
71
79
|
})
|
|
@@ -195,6 +203,9 @@ export default function EditModal({
|
|
|
195
203
|
// hasOriginalTranscribedSegment: !!originalTranscribedSegment
|
|
196
204
|
// });
|
|
197
205
|
|
|
206
|
+
const theme = useTheme()
|
|
207
|
+
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
|
|
208
|
+
|
|
198
209
|
const [editedSegment, setEditedSegment] = useState<LyricsSegment | null>(segment)
|
|
199
210
|
const [isPlaying, setIsPlaying] = useState(false)
|
|
200
211
|
|
|
@@ -224,7 +235,9 @@ export default function EditModal({
|
|
|
224
235
|
startManualSync,
|
|
225
236
|
cleanupManualSync,
|
|
226
237
|
handleSpacebar,
|
|
227
|
-
isSpacebarPressed
|
|
238
|
+
isSpacebarPressed,
|
|
239
|
+
handleTapStart,
|
|
240
|
+
handleTapEnd
|
|
228
241
|
} = useManualSync({
|
|
229
242
|
editedSegment,
|
|
230
243
|
currentTime,
|
|
@@ -588,6 +601,7 @@ export default function EditModal({
|
|
|
588
601
|
onClose={handleClose}
|
|
589
602
|
maxWidth="md"
|
|
590
603
|
fullWidth
|
|
604
|
+
fullScreen={isMobile}
|
|
591
605
|
onKeyDown={(e) => {
|
|
592
606
|
if (e.key === 'Enter' && !e.shiftKey && !isLoading) {
|
|
593
607
|
e.preventDefault()
|
|
@@ -596,8 +610,8 @@ export default function EditModal({
|
|
|
596
610
|
}}
|
|
597
611
|
PaperProps={{
|
|
598
612
|
sx: {
|
|
599
|
-
height: '90vh',
|
|
600
|
-
margin: '5vh 0'
|
|
613
|
+
height: isMobile ? '100%' : '90vh',
|
|
614
|
+
margin: isMobile ? 0 : '5vh 0'
|
|
601
615
|
}
|
|
602
616
|
}}
|
|
603
617
|
>
|
|
@@ -614,17 +628,19 @@ export default function EditModal({
|
|
|
614
628
|
}}
|
|
615
629
|
>
|
|
616
630
|
{isLoading && (
|
|
617
|
-
<Box sx={{
|
|
618
|
-
display: 'flex',
|
|
631
|
+
<Box sx={{
|
|
632
|
+
display: 'flex',
|
|
619
633
|
flexDirection: 'column',
|
|
620
|
-
alignItems: 'center',
|
|
621
|
-
justifyContent: 'center',
|
|
634
|
+
alignItems: 'center',
|
|
635
|
+
justifyContent: 'center',
|
|
622
636
|
height: '100%',
|
|
623
637
|
width: '100%',
|
|
624
638
|
position: 'absolute',
|
|
625
639
|
top: 0,
|
|
626
640
|
left: 0,
|
|
627
|
-
backgroundColor:
|
|
641
|
+
backgroundColor: (theme) => theme.palette.mode === 'dark'
|
|
642
|
+
? 'rgba(30, 41, 59, 0.95)' // slate-800 with opacity for dark mode
|
|
643
|
+
: 'rgba(248, 250, 252, 0.95)', // light background for light mode
|
|
628
644
|
zIndex: 10
|
|
629
645
|
}}>
|
|
630
646
|
<CircularProgress size={60} thickness={4} />
|
|
@@ -652,6 +668,8 @@ export default function EditModal({
|
|
|
652
668
|
onPlaySegment={onPlaySegment}
|
|
653
669
|
startManualSync={startManualSync}
|
|
654
670
|
isGlobal={isGlobal}
|
|
671
|
+
onTapStart={handleTapStart}
|
|
672
|
+
onTapEnd={handleTapEnd}
|
|
655
673
|
/>
|
|
656
674
|
|
|
657
675
|
<MemoizedWordList
|
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
Typography,
|
|
5
5
|
IconButton,
|
|
6
6
|
Tooltip,
|
|
7
|
-
Stack
|
|
7
|
+
Stack,
|
|
8
|
+
useMediaQuery,
|
|
9
|
+
useTheme
|
|
8
10
|
} from '@mui/material'
|
|
9
11
|
import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline'
|
|
10
12
|
import CancelIcon from '@mui/icons-material/Cancel'
|
|
@@ -17,10 +19,68 @@ import PauseCircleOutlineIcon from '@mui/icons-material/PauseCircleOutline'
|
|
|
17
19
|
import PlayArrowIcon from '@mui/icons-material/PlayArrow'
|
|
18
20
|
import StopIcon from '@mui/icons-material/Stop'
|
|
19
21
|
import CenterFocusStrongIcon from '@mui/icons-material/CenterFocusStrong'
|
|
22
|
+
import TouchAppIcon from '@mui/icons-material/TouchApp'
|
|
20
23
|
import TimelineEditor from './TimelineEditor'
|
|
21
24
|
import { Word } from '../types'
|
|
22
25
|
import { useState, useEffect, useCallback, useRef, useMemo, memo } from 'react'
|
|
23
26
|
|
|
27
|
+
// Separate TapButton component to properly track local press state
|
|
28
|
+
// This prevents issues where onMouseLeave fires before parent state updates
|
|
29
|
+
const TapButton = memo(function TapButton({
|
|
30
|
+
isSpacebarPressed,
|
|
31
|
+
onTapStart,
|
|
32
|
+
onTapEnd
|
|
33
|
+
}: {
|
|
34
|
+
isSpacebarPressed: boolean
|
|
35
|
+
onTapStart: () => void
|
|
36
|
+
onTapEnd: () => void
|
|
37
|
+
}) {
|
|
38
|
+
const isPressedRef = useRef(false)
|
|
39
|
+
|
|
40
|
+
const handleTapStart = useCallback(() => {
|
|
41
|
+
isPressedRef.current = true
|
|
42
|
+
onTapStart()
|
|
43
|
+
}, [onTapStart])
|
|
44
|
+
|
|
45
|
+
const handleTapEnd = useCallback(() => {
|
|
46
|
+
if (isPressedRef.current) {
|
|
47
|
+
isPressedRef.current = false
|
|
48
|
+
onTapEnd()
|
|
49
|
+
}
|
|
50
|
+
}, [onTapEnd])
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Button
|
|
54
|
+
variant="contained"
|
|
55
|
+
color={isSpacebarPressed ? "secondary" : "primary"}
|
|
56
|
+
onTouchStart={(e) => {
|
|
57
|
+
e.preventDefault()
|
|
58
|
+
handleTapStart()
|
|
59
|
+
}}
|
|
60
|
+
onTouchEnd={(e) => {
|
|
61
|
+
e.preventDefault()
|
|
62
|
+
handleTapEnd()
|
|
63
|
+
}}
|
|
64
|
+
onMouseDown={handleTapStart}
|
|
65
|
+
onMouseUp={handleTapEnd}
|
|
66
|
+
onMouseLeave={handleTapEnd}
|
|
67
|
+
startIcon={<TouchAppIcon />}
|
|
68
|
+
sx={{
|
|
69
|
+
py: 2,
|
|
70
|
+
fontSize: '1.1rem',
|
|
71
|
+
fontWeight: 'bold',
|
|
72
|
+
width: '100%',
|
|
73
|
+
minHeight: '56px',
|
|
74
|
+
userSelect: 'none',
|
|
75
|
+
WebkitUserSelect: 'none',
|
|
76
|
+
touchAction: 'manipulation'
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
{isSpacebarPressed ? "HOLD..." : "TAP"}
|
|
80
|
+
</Button>
|
|
81
|
+
)
|
|
82
|
+
})
|
|
83
|
+
|
|
24
84
|
interface EditTimelineSectionProps {
|
|
25
85
|
words: Word[]
|
|
26
86
|
startTime: number
|
|
@@ -44,6 +104,8 @@ interface EditTimelineSectionProps {
|
|
|
44
104
|
isGlobal?: boolean
|
|
45
105
|
defaultZoomLevel?: number
|
|
46
106
|
isReplaceAllMode?: boolean
|
|
107
|
+
onTapStart?: () => void
|
|
108
|
+
onTapEnd?: () => void
|
|
47
109
|
}
|
|
48
110
|
|
|
49
111
|
// Memoized control buttons to prevent unnecessary re-renders
|
|
@@ -91,16 +153,21 @@ const TimelineControls = memo(({
|
|
|
91
153
|
onStopAudio?: () => void
|
|
92
154
|
}) => {
|
|
93
155
|
return (
|
|
94
|
-
<Stack
|
|
156
|
+
<Stack
|
|
157
|
+
direction="row"
|
|
158
|
+
spacing={0.5}
|
|
159
|
+
alignItems="center"
|
|
160
|
+
sx={{ flexWrap: 'wrap', justifyContent: 'center', gap: 0.5 }}
|
|
161
|
+
>
|
|
95
162
|
{isGlobal && (
|
|
96
|
-
<>
|
|
163
|
+
<>
|
|
97
164
|
<Tooltip title="Scroll Left">
|
|
98
165
|
<IconButton
|
|
99
166
|
onClick={onScrollLeft}
|
|
100
167
|
disabled={visibleStartTime <= startTime}
|
|
101
168
|
size="small"
|
|
102
169
|
>
|
|
103
|
-
<ArrowBackIcon />
|
|
170
|
+
<ArrowBackIcon fontSize="small" />
|
|
104
171
|
</IconButton>
|
|
105
172
|
</Tooltip>
|
|
106
173
|
<Tooltip title="Zoom Out (Show More Time)">
|
|
@@ -109,7 +176,7 @@ const TimelineControls = memo(({
|
|
|
109
176
|
disabled={zoomLevel >= (endTime - startTime) || (isReplaceAllMode && isManualSyncing && !isPaused)}
|
|
110
177
|
size="small"
|
|
111
178
|
>
|
|
112
|
-
<ZoomOutIcon />
|
|
179
|
+
<ZoomOutIcon fontSize="small" />
|
|
113
180
|
</IconButton>
|
|
114
181
|
</Tooltip>
|
|
115
182
|
<Tooltip title="Zoom In (Show Less Time)">
|
|
@@ -118,7 +185,7 @@ const TimelineControls = memo(({
|
|
|
118
185
|
disabled={zoomLevel <= 2 || (isReplaceAllMode && isManualSyncing && !isPaused)}
|
|
119
186
|
size="small"
|
|
120
187
|
>
|
|
121
|
-
<ZoomInIcon />
|
|
188
|
+
<ZoomInIcon fontSize="small" />
|
|
122
189
|
</IconButton>
|
|
123
190
|
</Tooltip>
|
|
124
191
|
<Tooltip title="Scroll Right">
|
|
@@ -127,7 +194,7 @@ const TimelineControls = memo(({
|
|
|
127
194
|
disabled={visibleEndTime >= endTime}
|
|
128
195
|
size="small"
|
|
129
196
|
>
|
|
130
|
-
<ArrowForwardIcon />
|
|
197
|
+
<ArrowForwardIcon fontSize="small" />
|
|
131
198
|
</IconButton>
|
|
132
199
|
</Tooltip>
|
|
133
200
|
<Tooltip
|
|
@@ -140,7 +207,7 @@ const TimelineControls = memo(({
|
|
|
140
207
|
color={autoScrollEnabled ? "primary" : "default"}
|
|
141
208
|
size="small"
|
|
142
209
|
>
|
|
143
|
-
{autoScrollEnabled ? <AutorenewIcon /> : <PauseCircleOutlineIcon />}
|
|
210
|
+
{autoScrollEnabled ? <AutorenewIcon fontSize="small" /> : <PauseCircleOutlineIcon fontSize="small" />}
|
|
144
211
|
</IconButton>
|
|
145
212
|
</Tooltip>
|
|
146
213
|
<Tooltip title="Jump to Current Playback Position">
|
|
@@ -149,7 +216,7 @@ const TimelineControls = memo(({
|
|
|
149
216
|
disabled={!currentTime}
|
|
150
217
|
size="small"
|
|
151
218
|
>
|
|
152
|
-
<CenterFocusStrongIcon />
|
|
219
|
+
<CenterFocusStrongIcon fontSize="small" />
|
|
153
220
|
</IconButton>
|
|
154
221
|
</Tooltip>
|
|
155
222
|
</>
|
|
@@ -158,26 +225,27 @@ const TimelineControls = memo(({
|
|
|
158
225
|
<Button
|
|
159
226
|
variant="outlined"
|
|
160
227
|
onClick={onStopAudio}
|
|
161
|
-
startIcon={<StopIcon />}
|
|
228
|
+
startIcon={<StopIcon fontSize="small" />}
|
|
162
229
|
color="error"
|
|
163
230
|
size="small"
|
|
164
231
|
>
|
|
165
|
-
Stop
|
|
232
|
+
Stop
|
|
166
233
|
</Button>
|
|
167
234
|
)}
|
|
168
235
|
<Button
|
|
169
236
|
variant={isManualSyncing ? "outlined" : "contained"}
|
|
170
237
|
onClick={onStartManualSync}
|
|
171
|
-
startIcon={isManualSyncing ? <CancelIcon /> : <PlayCircleOutlineIcon />}
|
|
238
|
+
startIcon={isManualSyncing ? <CancelIcon fontSize="small" /> : <PlayCircleOutlineIcon fontSize="small" />}
|
|
172
239
|
color={isManualSyncing ? "error" : "primary"}
|
|
240
|
+
size="small"
|
|
173
241
|
>
|
|
174
|
-
{isManualSyncing ? "Cancel
|
|
242
|
+
{isManualSyncing ? "Cancel" : "Tap To Sync"}
|
|
175
243
|
</Button>
|
|
176
244
|
{isManualSyncing && isReplaceAllMode && (
|
|
177
245
|
<Button
|
|
178
246
|
variant="outlined"
|
|
179
247
|
onClick={onPauseResume}
|
|
180
|
-
startIcon={isPaused ? <PlayArrowIcon /> : <PauseCircleOutlineIcon />}
|
|
248
|
+
startIcon={isPaused ? <PlayArrowIcon fontSize="small" /> : <PauseCircleOutlineIcon fontSize="small" />}
|
|
181
249
|
color={isPaused ? "success" : "warning"}
|
|
182
250
|
size="small"
|
|
183
251
|
>
|
|
@@ -210,8 +278,12 @@ export default function EditTimelineSection({
|
|
|
210
278
|
isPaused = false,
|
|
211
279
|
isGlobal = false,
|
|
212
280
|
defaultZoomLevel = 10,
|
|
213
|
-
isReplaceAllMode = false
|
|
281
|
+
isReplaceAllMode = false,
|
|
282
|
+
onTapStart,
|
|
283
|
+
onTapEnd
|
|
214
284
|
}: EditTimelineSectionProps) {
|
|
285
|
+
const theme = useTheme()
|
|
286
|
+
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
|
|
215
287
|
// Add state for zoom level - use larger default for Replace All mode
|
|
216
288
|
const [zoomLevel, setZoomLevel] = useState(defaultZoomLevel)
|
|
217
289
|
const [visibleStartTime, setVisibleStartTime] = useState(startTime)
|
|
@@ -432,7 +504,7 @@ export default function EditTimelineSection({
|
|
|
432
504
|
return (
|
|
433
505
|
<>
|
|
434
506
|
<Box
|
|
435
|
-
sx={{ height: '120px', mb:
|
|
507
|
+
sx={{ height: isMobile ? '80px' : '120px', mb: 0 }}
|
|
436
508
|
ref={timelineRef}
|
|
437
509
|
onWheel={handleScroll}
|
|
438
510
|
>
|
|
@@ -447,14 +519,30 @@ export default function EditTimelineSection({
|
|
|
447
519
|
/>
|
|
448
520
|
</Box>
|
|
449
521
|
|
|
450
|
-
<Box sx={{
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
522
|
+
<Box sx={{
|
|
523
|
+
display: 'flex',
|
|
524
|
+
flexDirection: isMobile ? 'column' : 'row',
|
|
525
|
+
alignItems: isMobile ? 'stretch' : 'center',
|
|
526
|
+
justifyContent: 'space-between',
|
|
527
|
+
gap: isMobile ? 1 : 0,
|
|
528
|
+
mt: isMobile ? 1.5 : 0,
|
|
529
|
+
mb: isMobile ? 2 : 0
|
|
530
|
+
}}>
|
|
531
|
+
{/* Time range info - hidden on mobile to save space */}
|
|
532
|
+
{!isMobile && (
|
|
533
|
+
<Typography variant="body2" color="text.secondary">
|
|
534
|
+
Original Time Range: {originalStartTime?.toFixed(2) ?? 'N/A'} - {originalEndTime?.toFixed(2) ?? 'N/A'}
|
|
535
|
+
<br />
|
|
536
|
+
Current Time Range: {currentStartTime?.toFixed(2) ?? 'N/A'} - {currentEndTime?.toFixed(2) ?? 'N/A'}
|
|
537
|
+
</Typography>
|
|
538
|
+
)}
|
|
539
|
+
|
|
540
|
+
<Box sx={{
|
|
541
|
+
display: 'flex',
|
|
542
|
+
flexDirection: isMobile ? 'column' : 'row',
|
|
543
|
+
alignItems: isMobile ? 'stretch' : 'center',
|
|
544
|
+
gap: isMobile ? 1 : 2
|
|
545
|
+
}}>
|
|
458
546
|
<TimelineControls
|
|
459
547
|
isGlobal={isGlobal}
|
|
460
548
|
visibleStartTime={visibleStartTime}
|
|
@@ -478,17 +566,25 @@ export default function EditTimelineSection({
|
|
|
478
566
|
onStopAudio={onStopAudio}
|
|
479
567
|
/>
|
|
480
568
|
{currentWordInfo && (
|
|
481
|
-
<Box>
|
|
569
|
+
<Box sx={{ textAlign: isMobile ? 'center' : 'left' }}>
|
|
482
570
|
<Typography variant="body2">
|
|
483
571
|
Word {currentWordInfo.index} of {currentWordInfo.total}: <strong>{currentWordInfo.text}</strong>
|
|
484
572
|
</Typography>
|
|
485
573
|
<Typography variant="caption" color="text.secondary">
|
|
486
574
|
{isSpacebarPressed ?
|
|
487
|
-
"Holding
|
|
488
|
-
"Press spacebar when word starts (tap for short words, hold for long words)"}
|
|
575
|
+
"Holding... Release when word ends" :
|
|
576
|
+
(isMobile ? "Tap the button when word starts" : "Press spacebar when word starts (tap for short words, hold for long words)")}
|
|
489
577
|
</Typography>
|
|
490
578
|
</Box>
|
|
491
579
|
)}
|
|
580
|
+
{/* Mobile TAP button for manual sync */}
|
|
581
|
+
{isMobile && isManualSyncing && onTapStart && onTapEnd && (
|
|
582
|
+
<TapButton
|
|
583
|
+
isSpacebarPressed={isSpacebarPressed}
|
|
584
|
+
onTapStart={onTapStart}
|
|
585
|
+
onTapEnd={onTapEnd}
|
|
586
|
+
/>
|
|
587
|
+
)}
|
|
492
588
|
</Box>
|
|
493
589
|
</Box>
|
|
494
590
|
</>
|
|
@@ -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
|
})}
|