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.
Files changed (197) hide show
  1. backend/.coveragerc +20 -0
  2. backend/.gitignore +37 -0
  3. backend/Dockerfile +43 -0
  4. backend/Dockerfile.base +74 -0
  5. backend/README.md +242 -0
  6. backend/__init__.py +0 -0
  7. backend/api/__init__.py +0 -0
  8. backend/api/dependencies.py +457 -0
  9. backend/api/routes/__init__.py +0 -0
  10. backend/api/routes/admin.py +835 -0
  11. backend/api/routes/audio_search.py +913 -0
  12. backend/api/routes/auth.py +348 -0
  13. backend/api/routes/file_upload.py +2112 -0
  14. backend/api/routes/health.py +409 -0
  15. backend/api/routes/internal.py +435 -0
  16. backend/api/routes/jobs.py +1629 -0
  17. backend/api/routes/review.py +652 -0
  18. backend/api/routes/themes.py +162 -0
  19. backend/api/routes/users.py +1513 -0
  20. backend/config.py +172 -0
  21. backend/main.py +157 -0
  22. backend/middleware/__init__.py +5 -0
  23. backend/middleware/audit_logging.py +124 -0
  24. backend/models/__init__.py +0 -0
  25. backend/models/job.py +519 -0
  26. backend/models/requests.py +123 -0
  27. backend/models/theme.py +153 -0
  28. backend/models/user.py +254 -0
  29. backend/models/worker_log.py +164 -0
  30. backend/pyproject.toml +29 -0
  31. backend/quick-check.sh +93 -0
  32. backend/requirements.txt +29 -0
  33. backend/run_tests.sh +60 -0
  34. backend/services/__init__.py +0 -0
  35. backend/services/audio_analysis_service.py +243 -0
  36. backend/services/audio_editing_service.py +278 -0
  37. backend/services/audio_search_service.py +702 -0
  38. backend/services/auth_service.py +630 -0
  39. backend/services/credential_manager.py +792 -0
  40. backend/services/discord_service.py +172 -0
  41. backend/services/dropbox_service.py +301 -0
  42. backend/services/email_service.py +1093 -0
  43. backend/services/encoding_interface.py +454 -0
  44. backend/services/encoding_service.py +502 -0
  45. backend/services/firestore_service.py +512 -0
  46. backend/services/flacfetch_client.py +573 -0
  47. backend/services/gce_encoding/README.md +72 -0
  48. backend/services/gce_encoding/__init__.py +22 -0
  49. backend/services/gce_encoding/main.py +589 -0
  50. backend/services/gce_encoding/requirements.txt +16 -0
  51. backend/services/gdrive_service.py +356 -0
  52. backend/services/job_logging.py +258 -0
  53. backend/services/job_manager.py +853 -0
  54. backend/services/job_notification_service.py +271 -0
  55. backend/services/langfuse_preloader.py +98 -0
  56. backend/services/local_encoding_service.py +590 -0
  57. backend/services/local_preview_encoding_service.py +407 -0
  58. backend/services/lyrics_cache_service.py +216 -0
  59. backend/services/metrics.py +413 -0
  60. backend/services/nltk_preloader.py +122 -0
  61. backend/services/packaging_service.py +287 -0
  62. backend/services/rclone_service.py +106 -0
  63. backend/services/spacy_preloader.py +65 -0
  64. backend/services/storage_service.py +209 -0
  65. backend/services/stripe_service.py +371 -0
  66. backend/services/structured_logging.py +254 -0
  67. backend/services/template_service.py +330 -0
  68. backend/services/theme_service.py +469 -0
  69. backend/services/tracing.py +543 -0
  70. backend/services/user_service.py +721 -0
  71. backend/services/worker_service.py +558 -0
  72. backend/services/youtube_service.py +112 -0
  73. backend/services/youtube_upload_service.py +445 -0
  74. backend/tests/__init__.py +4 -0
  75. backend/tests/conftest.py +224 -0
  76. backend/tests/emulator/__init__.py +7 -0
  77. backend/tests/emulator/conftest.py +109 -0
  78. backend/tests/emulator/test_e2e_cli_backend.py +1053 -0
  79. backend/tests/emulator/test_emulator_integration.py +356 -0
  80. backend/tests/emulator/test_style_loading_direct.py +436 -0
  81. backend/tests/emulator/test_worker_logs_direct.py +229 -0
  82. backend/tests/emulator/test_worker_logs_subcollection.py +443 -0
  83. backend/tests/requirements-test.txt +10 -0
  84. backend/tests/requirements.txt +6 -0
  85. backend/tests/test_admin_email_endpoints.py +411 -0
  86. backend/tests/test_api_integration.py +460 -0
  87. backend/tests/test_api_routes.py +93 -0
  88. backend/tests/test_audio_analysis_service.py +294 -0
  89. backend/tests/test_audio_editing_service.py +386 -0
  90. backend/tests/test_audio_search.py +1398 -0
  91. backend/tests/test_audio_services.py +378 -0
  92. backend/tests/test_auth_firestore.py +231 -0
  93. backend/tests/test_config_extended.py +68 -0
  94. backend/tests/test_credential_manager.py +377 -0
  95. backend/tests/test_dependencies.py +54 -0
  96. backend/tests/test_discord_service.py +244 -0
  97. backend/tests/test_distribution_services.py +820 -0
  98. backend/tests/test_dropbox_service.py +472 -0
  99. backend/tests/test_email_service.py +492 -0
  100. backend/tests/test_emulator_integration.py +322 -0
  101. backend/tests/test_encoding_interface.py +412 -0
  102. backend/tests/test_file_upload.py +1739 -0
  103. backend/tests/test_flacfetch_client.py +632 -0
  104. backend/tests/test_gdrive_service.py +524 -0
  105. backend/tests/test_instrumental_api.py +431 -0
  106. backend/tests/test_internal_api.py +343 -0
  107. backend/tests/test_job_creation_regression.py +583 -0
  108. backend/tests/test_job_manager.py +356 -0
  109. backend/tests/test_job_manager_notifications.py +329 -0
  110. backend/tests/test_job_notification_service.py +443 -0
  111. backend/tests/test_jobs_api.py +283 -0
  112. backend/tests/test_local_encoding_service.py +423 -0
  113. backend/tests/test_local_preview_encoding_service.py +567 -0
  114. backend/tests/test_main.py +87 -0
  115. backend/tests/test_models.py +918 -0
  116. backend/tests/test_packaging_service.py +382 -0
  117. backend/tests/test_requests.py +201 -0
  118. backend/tests/test_routes_jobs.py +282 -0
  119. backend/tests/test_routes_review.py +337 -0
  120. backend/tests/test_services.py +556 -0
  121. backend/tests/test_services_extended.py +112 -0
  122. backend/tests/test_spacy_preloader.py +119 -0
  123. backend/tests/test_storage_service.py +448 -0
  124. backend/tests/test_style_upload.py +261 -0
  125. backend/tests/test_template_service.py +295 -0
  126. backend/tests/test_theme_service.py +516 -0
  127. backend/tests/test_unicode_sanitization.py +522 -0
  128. backend/tests/test_upload_api.py +256 -0
  129. backend/tests/test_validate.py +156 -0
  130. backend/tests/test_video_worker_orchestrator.py +847 -0
  131. backend/tests/test_worker_log_subcollection.py +509 -0
  132. backend/tests/test_worker_logging.py +365 -0
  133. backend/tests/test_workers.py +1116 -0
  134. backend/tests/test_workers_extended.py +178 -0
  135. backend/tests/test_youtube_service.py +247 -0
  136. backend/tests/test_youtube_upload_service.py +568 -0
  137. backend/utils/test_data.py +27 -0
  138. backend/validate.py +173 -0
  139. backend/version.py +27 -0
  140. backend/workers/README.md +597 -0
  141. backend/workers/__init__.py +11 -0
  142. backend/workers/audio_worker.py +618 -0
  143. backend/workers/lyrics_worker.py +683 -0
  144. backend/workers/render_video_worker.py +483 -0
  145. backend/workers/screens_worker.py +535 -0
  146. backend/workers/style_helper.py +198 -0
  147. backend/workers/video_worker.py +1277 -0
  148. backend/workers/video_worker_orchestrator.py +701 -0
  149. backend/workers/worker_logging.py +278 -0
  150. karaoke_gen/instrumental_review/static/index.html +7 -4
  151. karaoke_gen/karaoke_finalise/karaoke_finalise.py +6 -1
  152. karaoke_gen/utils/__init__.py +163 -8
  153. karaoke_gen/video_background_processor.py +9 -4
  154. {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/METADATA +1 -1
  155. {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/RECORD +196 -46
  156. lyrics_transcriber/correction/agentic/agent.py +17 -6
  157. lyrics_transcriber/correction/agentic/providers/config.py +9 -5
  158. lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +96 -93
  159. lyrics_transcriber/correction/agentic/providers/model_factory.py +27 -6
  160. lyrics_transcriber/correction/anchor_sequence.py +151 -37
  161. lyrics_transcriber/correction/corrector.py +192 -130
  162. lyrics_transcriber/correction/handlers/syllables_match.py +44 -2
  163. lyrics_transcriber/correction/operations.py +24 -9
  164. lyrics_transcriber/correction/phrase_analyzer.py +18 -0
  165. lyrics_transcriber/frontend/package-lock.json +2 -2
  166. lyrics_transcriber/frontend/package.json +1 -1
  167. lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +1 -1
  168. lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +11 -7
  169. lyrics_transcriber/frontend/src/components/EditActionBar.tsx +31 -5
  170. lyrics_transcriber/frontend/src/components/EditModal.tsx +28 -10
  171. lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +123 -27
  172. lyrics_transcriber/frontend/src/components/EditWordList.tsx +112 -60
  173. lyrics_transcriber/frontend/src/components/Header.tsx +90 -76
  174. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +53 -31
  175. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/SyncControls.tsx +44 -13
  176. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/TimelineCanvas.tsx +66 -50
  177. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/index.tsx +124 -30
  178. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +1 -1
  179. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +12 -5
  180. lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +3 -3
  181. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +1 -1
  182. lyrics_transcriber/frontend/src/components/WordDivider.tsx +11 -7
  183. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +4 -2
  184. lyrics_transcriber/frontend/src/hooks/useManualSync.ts +103 -1
  185. lyrics_transcriber/frontend/src/theme.ts +42 -15
  186. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
  187. lyrics_transcriber/frontend/vite.config.js +5 -0
  188. lyrics_transcriber/frontend/web_assets/assets/{index-BECn1o8Q.js → index-BSMgOq4Z.js} +6959 -5782
  189. lyrics_transcriber/frontend/web_assets/assets/index-BSMgOq4Z.js.map +1 -0
  190. lyrics_transcriber/frontend/web_assets/index.html +6 -2
  191. lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.svg +5 -0
  192. lyrics_transcriber/output/generator.py +17 -3
  193. lyrics_transcriber/output/video.py +60 -95
  194. lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js.map +0 -1
  195. {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/WHEEL +0 -0
  196. {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/entry_points.txt +0 -0
  197. {karaoke_gen-0.90.1.dist-info → karaoke_gen-0.99.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
1
- import { Box, Button } from '@mui/material'
1
+ import { Box, Button, useMediaQuery, useTheme } from '@mui/material'
2
2
  import DeleteIcon from '@mui/icons-material/Delete'
3
3
  import RestoreIcon from '@mui/icons-material/RestoreFromTrash'
4
4
  import HistoryIcon from '@mui/icons-material/History'
@@ -25,13 +25,29 @@ export default function EditActionBar({
25
25
  originalTranscribedSegment,
26
26
  isGlobal = false
27
27
  }: EditActionBarProps) {
28
+ const theme = useTheme()
29
+ const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
30
+
28
31
  return (
29
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, width: '100%' }}>
30
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
32
+ <Box sx={{
33
+ display: 'flex',
34
+ flexDirection: isMobile ? 'column' : 'row',
35
+ alignItems: isMobile ? 'stretch' : 'center',
36
+ gap: 1,
37
+ width: '100%'
38
+ }}>
39
+ <Box sx={{
40
+ display: 'flex',
41
+ alignItems: 'center',
42
+ gap: 1,
43
+ flexWrap: 'wrap',
44
+ justifyContent: isMobile ? 'center' : 'flex-start'
45
+ }}>
31
46
  <Button
32
47
  startIcon={<RestoreIcon />}
33
48
  onClick={onReset}
34
49
  color="warning"
50
+ size={isMobile ? 'small' : 'medium'}
35
51
  >
36
52
  Reset
37
53
  </Button>
@@ -39,6 +55,7 @@ export default function EditActionBar({
39
55
  <Button
40
56
  onClick={onRevertToOriginal}
41
57
  startIcon={<HistoryIcon />}
58
+ size={isMobile ? 'small' : 'medium'}
42
59
  >
43
60
  Un-Correct
44
61
  </Button>
@@ -48,17 +65,26 @@ export default function EditActionBar({
48
65
  startIcon={<DeleteIcon />}
49
66
  onClick={onDelete}
50
67
  color="error"
68
+ size={isMobile ? 'small' : 'medium'}
51
69
  >
52
70
  Delete Segment
53
71
  </Button>
54
72
  )}
55
73
  </Box>
56
- <Box sx={{ ml: 'auto', display: 'flex', gap: 1 }}>
57
- <Button onClick={onClose}>Cancel</Button>
74
+ <Box sx={{
75
+ ml: isMobile ? 0 : 'auto',
76
+ display: 'flex',
77
+ gap: 1,
78
+ justifyContent: isMobile ? 'center' : 'flex-end'
79
+ }}>
80
+ <Button onClick={onClose} size={isMobile ? 'small' : 'medium'}>
81
+ Cancel
82
+ </Button>
58
83
  <Button
59
84
  onClick={onSave}
60
85
  variant="contained"
61
86
  disabled={!editedSegment || editedSegment.words.length === 0}
87
+ size={isMobile ? 'small' : 'medium'}
62
88
  >
63
89
  Save
64
90
  </Button>
@@ -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: 'rgba(30, 41, 59, 0.95)', // slate-800 with opacity for dark mode
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 direction="row" spacing={1} alignItems="center">
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 Audio
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 Sync" : "Manual Sync"}
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: 2 }}
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={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
451
- <Typography variant="body2" color="text.secondary">
452
- Original Time Range: {originalStartTime?.toFixed(2) ?? 'N/A'} - {originalEndTime?.toFixed(2) ?? 'N/A'}
453
- <br />
454
- Current Time Range: {currentStartTime?.toFixed(2) ?? 'N/A'} - {currentEndTime?.toFixed(2) ?? 'N/A'}
455
- </Typography>
456
-
457
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
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 spacebar... Release when word ends" :
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
  </>