karaoke-gen 0.57.0__py3-none-any.whl → 0.71.27__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 (268) hide show
  1. karaoke_gen/audio_fetcher.py +461 -0
  2. karaoke_gen/audio_processor.py +407 -30
  3. karaoke_gen/config.py +62 -113
  4. karaoke_gen/file_handler.py +32 -59
  5. karaoke_gen/karaoke_finalise/karaoke_finalise.py +148 -67
  6. karaoke_gen/karaoke_gen.py +270 -61
  7. karaoke_gen/lyrics_processor.py +13 -1
  8. karaoke_gen/metadata.py +78 -73
  9. karaoke_gen/pipeline/__init__.py +87 -0
  10. karaoke_gen/pipeline/base.py +215 -0
  11. karaoke_gen/pipeline/context.py +230 -0
  12. karaoke_gen/pipeline/executors/__init__.py +21 -0
  13. karaoke_gen/pipeline/executors/local.py +159 -0
  14. karaoke_gen/pipeline/executors/remote.py +257 -0
  15. karaoke_gen/pipeline/stages/__init__.py +27 -0
  16. karaoke_gen/pipeline/stages/finalize.py +202 -0
  17. karaoke_gen/pipeline/stages/render.py +165 -0
  18. karaoke_gen/pipeline/stages/screens.py +139 -0
  19. karaoke_gen/pipeline/stages/separation.py +191 -0
  20. karaoke_gen/pipeline/stages/transcription.py +191 -0
  21. karaoke_gen/style_loader.py +531 -0
  22. karaoke_gen/utils/bulk_cli.py +6 -0
  23. karaoke_gen/utils/cli_args.py +424 -0
  24. karaoke_gen/utils/gen_cli.py +26 -261
  25. karaoke_gen/utils/remote_cli.py +1965 -0
  26. karaoke_gen/video_background_processor.py +351 -0
  27. karaoke_gen-0.71.27.dist-info/METADATA +610 -0
  28. karaoke_gen-0.71.27.dist-info/RECORD +275 -0
  29. {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.27.dist-info}/WHEEL +1 -1
  30. {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.27.dist-info}/entry_points.txt +1 -0
  31. lyrics_transcriber/__init__.py +10 -0
  32. lyrics_transcriber/cli/__init__.py +0 -0
  33. lyrics_transcriber/cli/cli_main.py +285 -0
  34. lyrics_transcriber/core/__init__.py +0 -0
  35. lyrics_transcriber/core/config.py +50 -0
  36. lyrics_transcriber/core/controller.py +520 -0
  37. lyrics_transcriber/correction/__init__.py +0 -0
  38. lyrics_transcriber/correction/agentic/__init__.py +9 -0
  39. lyrics_transcriber/correction/agentic/adapter.py +71 -0
  40. lyrics_transcriber/correction/agentic/agent.py +313 -0
  41. lyrics_transcriber/correction/agentic/feedback/aggregator.py +12 -0
  42. lyrics_transcriber/correction/agentic/feedback/collector.py +17 -0
  43. lyrics_transcriber/correction/agentic/feedback/retention.py +24 -0
  44. lyrics_transcriber/correction/agentic/feedback/store.py +76 -0
  45. lyrics_transcriber/correction/agentic/handlers/__init__.py +24 -0
  46. lyrics_transcriber/correction/agentic/handlers/ambiguous.py +44 -0
  47. lyrics_transcriber/correction/agentic/handlers/background_vocals.py +68 -0
  48. lyrics_transcriber/correction/agentic/handlers/base.py +51 -0
  49. lyrics_transcriber/correction/agentic/handlers/complex_multi_error.py +46 -0
  50. lyrics_transcriber/correction/agentic/handlers/extra_words.py +74 -0
  51. lyrics_transcriber/correction/agentic/handlers/no_error.py +42 -0
  52. lyrics_transcriber/correction/agentic/handlers/punctuation.py +44 -0
  53. lyrics_transcriber/correction/agentic/handlers/registry.py +60 -0
  54. lyrics_transcriber/correction/agentic/handlers/repeated_section.py +44 -0
  55. lyrics_transcriber/correction/agentic/handlers/sound_alike.py +126 -0
  56. lyrics_transcriber/correction/agentic/models/__init__.py +5 -0
  57. lyrics_transcriber/correction/agentic/models/ai_correction.py +31 -0
  58. lyrics_transcriber/correction/agentic/models/correction_session.py +30 -0
  59. lyrics_transcriber/correction/agentic/models/enums.py +38 -0
  60. lyrics_transcriber/correction/agentic/models/human_feedback.py +30 -0
  61. lyrics_transcriber/correction/agentic/models/learning_data.py +26 -0
  62. lyrics_transcriber/correction/agentic/models/observability_metrics.py +28 -0
  63. lyrics_transcriber/correction/agentic/models/schemas.py +46 -0
  64. lyrics_transcriber/correction/agentic/models/utils.py +19 -0
  65. lyrics_transcriber/correction/agentic/observability/__init__.py +5 -0
  66. lyrics_transcriber/correction/agentic/observability/langfuse_integration.py +35 -0
  67. lyrics_transcriber/correction/agentic/observability/metrics.py +46 -0
  68. lyrics_transcriber/correction/agentic/observability/performance.py +19 -0
  69. lyrics_transcriber/correction/agentic/prompts/__init__.py +2 -0
  70. lyrics_transcriber/correction/agentic/prompts/classifier.py +227 -0
  71. lyrics_transcriber/correction/agentic/providers/__init__.py +6 -0
  72. lyrics_transcriber/correction/agentic/providers/base.py +36 -0
  73. lyrics_transcriber/correction/agentic/providers/circuit_breaker.py +145 -0
  74. lyrics_transcriber/correction/agentic/providers/config.py +73 -0
  75. lyrics_transcriber/correction/agentic/providers/constants.py +24 -0
  76. lyrics_transcriber/correction/agentic/providers/health.py +28 -0
  77. lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +212 -0
  78. lyrics_transcriber/correction/agentic/providers/model_factory.py +209 -0
  79. lyrics_transcriber/correction/agentic/providers/response_cache.py +218 -0
  80. lyrics_transcriber/correction/agentic/providers/response_parser.py +111 -0
  81. lyrics_transcriber/correction/agentic/providers/retry_executor.py +127 -0
  82. lyrics_transcriber/correction/agentic/router.py +35 -0
  83. lyrics_transcriber/correction/agentic/workflows/__init__.py +5 -0
  84. lyrics_transcriber/correction/agentic/workflows/consensus_workflow.py +24 -0
  85. lyrics_transcriber/correction/agentic/workflows/correction_graph.py +59 -0
  86. lyrics_transcriber/correction/agentic/workflows/feedback_workflow.py +24 -0
  87. lyrics_transcriber/correction/anchor_sequence.py +1043 -0
  88. lyrics_transcriber/correction/corrector.py +760 -0
  89. lyrics_transcriber/correction/feedback/__init__.py +2 -0
  90. lyrics_transcriber/correction/feedback/schemas.py +107 -0
  91. lyrics_transcriber/correction/feedback/store.py +236 -0
  92. lyrics_transcriber/correction/handlers/__init__.py +0 -0
  93. lyrics_transcriber/correction/handlers/base.py +52 -0
  94. lyrics_transcriber/correction/handlers/extend_anchor.py +149 -0
  95. lyrics_transcriber/correction/handlers/levenshtein.py +189 -0
  96. lyrics_transcriber/correction/handlers/llm.py +293 -0
  97. lyrics_transcriber/correction/handlers/llm_providers.py +60 -0
  98. lyrics_transcriber/correction/handlers/no_space_punct_match.py +154 -0
  99. lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +85 -0
  100. lyrics_transcriber/correction/handlers/repeat.py +88 -0
  101. lyrics_transcriber/correction/handlers/sound_alike.py +259 -0
  102. lyrics_transcriber/correction/handlers/syllables_match.py +252 -0
  103. lyrics_transcriber/correction/handlers/word_count_match.py +80 -0
  104. lyrics_transcriber/correction/handlers/word_operations.py +187 -0
  105. lyrics_transcriber/correction/operations.py +352 -0
  106. lyrics_transcriber/correction/phrase_analyzer.py +435 -0
  107. lyrics_transcriber/correction/text_utils.py +30 -0
  108. lyrics_transcriber/frontend/.gitignore +23 -0
  109. lyrics_transcriber/frontend/.yarn/releases/yarn-4.7.0.cjs +935 -0
  110. lyrics_transcriber/frontend/.yarnrc.yml +3 -0
  111. lyrics_transcriber/frontend/README.md +50 -0
  112. lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md +210 -0
  113. lyrics_transcriber/frontend/__init__.py +25 -0
  114. lyrics_transcriber/frontend/eslint.config.js +28 -0
  115. lyrics_transcriber/frontend/index.html +18 -0
  116. lyrics_transcriber/frontend/package.json +42 -0
  117. lyrics_transcriber/frontend/public/android-chrome-192x192.png +0 -0
  118. lyrics_transcriber/frontend/public/android-chrome-512x512.png +0 -0
  119. lyrics_transcriber/frontend/public/apple-touch-icon.png +0 -0
  120. lyrics_transcriber/frontend/public/favicon-16x16.png +0 -0
  121. lyrics_transcriber/frontend/public/favicon-32x32.png +0 -0
  122. lyrics_transcriber/frontend/public/favicon.ico +0 -0
  123. lyrics_transcriber/frontend/public/nomad-karaoke-logo.png +0 -0
  124. lyrics_transcriber/frontend/src/App.tsx +212 -0
  125. lyrics_transcriber/frontend/src/api.ts +239 -0
  126. lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +77 -0
  127. lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +114 -0
  128. lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx +204 -0
  129. lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +180 -0
  130. lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +167 -0
  131. lyrics_transcriber/frontend/src/components/CorrectionAnnotationModal.tsx +359 -0
  132. lyrics_transcriber/frontend/src/components/CorrectionDetailCard.tsx +281 -0
  133. lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +162 -0
  134. lyrics_transcriber/frontend/src/components/DurationTimelineView.tsx +257 -0
  135. lyrics_transcriber/frontend/src/components/EditActionBar.tsx +68 -0
  136. lyrics_transcriber/frontend/src/components/EditModal.tsx +702 -0
  137. lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +496 -0
  138. lyrics_transcriber/frontend/src/components/EditWordList.tsx +379 -0
  139. lyrics_transcriber/frontend/src/components/FileUpload.tsx +77 -0
  140. lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
  141. lyrics_transcriber/frontend/src/components/Header.tsx +387 -0
  142. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +1373 -0
  143. lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx +51 -0
  144. lyrics_transcriber/frontend/src/components/ModeSelector.tsx +67 -0
  145. lyrics_transcriber/frontend/src/components/ModelSelector.tsx +23 -0
  146. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +144 -0
  147. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +268 -0
  148. lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +688 -0
  149. lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +354 -0
  150. lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +64 -0
  151. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +376 -0
  152. lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +131 -0
  153. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +256 -0
  154. lyrics_transcriber/frontend/src/components/WordDivider.tsx +187 -0
  155. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +379 -0
  156. lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +56 -0
  157. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +87 -0
  158. lyrics_transcriber/frontend/src/components/shared/constants.ts +20 -0
  159. lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +180 -0
  160. lyrics_transcriber/frontend/src/components/shared/styles.ts +13 -0
  161. lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
  162. lyrics_transcriber/frontend/src/components/shared/types.ts +129 -0
  163. lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +177 -0
  164. lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
  165. lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +75 -0
  166. lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +360 -0
  167. lyrics_transcriber/frontend/src/components/shared/utils/timingUtils.ts +110 -0
  168. lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
  169. lyrics_transcriber/frontend/src/hooks/useManualSync.ts +435 -0
  170. lyrics_transcriber/frontend/src/main.tsx +17 -0
  171. lyrics_transcriber/frontend/src/theme.ts +177 -0
  172. lyrics_transcriber/frontend/src/types/global.d.ts +9 -0
  173. lyrics_transcriber/frontend/src/types.js +2 -0
  174. lyrics_transcriber/frontend/src/types.ts +199 -0
  175. lyrics_transcriber/frontend/src/validation.ts +132 -0
  176. lyrics_transcriber/frontend/src/vite-env.d.ts +1 -0
  177. lyrics_transcriber/frontend/tsconfig.app.json +26 -0
  178. lyrics_transcriber/frontend/tsconfig.json +25 -0
  179. lyrics_transcriber/frontend/tsconfig.node.json +23 -0
  180. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -0
  181. lyrics_transcriber/frontend/update_version.js +11 -0
  182. lyrics_transcriber/frontend/vite.config.d.ts +2 -0
  183. lyrics_transcriber/frontend/vite.config.js +10 -0
  184. lyrics_transcriber/frontend/vite.config.ts +11 -0
  185. lyrics_transcriber/frontend/web_assets/android-chrome-192x192.png +0 -0
  186. lyrics_transcriber/frontend/web_assets/android-chrome-512x512.png +0 -0
  187. lyrics_transcriber/frontend/web_assets/apple-touch-icon.png +0 -0
  188. lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js +42039 -0
  189. lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js.map +1 -0
  190. lyrics_transcriber/frontend/web_assets/favicon-16x16.png +0 -0
  191. lyrics_transcriber/frontend/web_assets/favicon-32x32.png +0 -0
  192. lyrics_transcriber/frontend/web_assets/favicon.ico +0 -0
  193. lyrics_transcriber/frontend/web_assets/index.html +18 -0
  194. lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.png +0 -0
  195. lyrics_transcriber/frontend/yarn.lock +3752 -0
  196. lyrics_transcriber/lyrics/__init__.py +0 -0
  197. lyrics_transcriber/lyrics/base_lyrics_provider.py +211 -0
  198. lyrics_transcriber/lyrics/file_provider.py +95 -0
  199. lyrics_transcriber/lyrics/genius.py +384 -0
  200. lyrics_transcriber/lyrics/lrclib.py +231 -0
  201. lyrics_transcriber/lyrics/musixmatch.py +156 -0
  202. lyrics_transcriber/lyrics/spotify.py +290 -0
  203. lyrics_transcriber/lyrics/user_input_provider.py +44 -0
  204. lyrics_transcriber/output/__init__.py +0 -0
  205. lyrics_transcriber/output/ass/__init__.py +21 -0
  206. lyrics_transcriber/output/ass/ass.py +2088 -0
  207. lyrics_transcriber/output/ass/ass_specs.txt +732 -0
  208. lyrics_transcriber/output/ass/config.py +180 -0
  209. lyrics_transcriber/output/ass/constants.py +23 -0
  210. lyrics_transcriber/output/ass/event.py +94 -0
  211. lyrics_transcriber/output/ass/formatters.py +132 -0
  212. lyrics_transcriber/output/ass/lyrics_line.py +265 -0
  213. lyrics_transcriber/output/ass/lyrics_screen.py +252 -0
  214. lyrics_transcriber/output/ass/section_detector.py +89 -0
  215. lyrics_transcriber/output/ass/section_screen.py +106 -0
  216. lyrics_transcriber/output/ass/style.py +187 -0
  217. lyrics_transcriber/output/cdg.py +619 -0
  218. lyrics_transcriber/output/cdgmaker/__init__.py +0 -0
  219. lyrics_transcriber/output/cdgmaker/cdg.py +262 -0
  220. lyrics_transcriber/output/cdgmaker/composer.py +2260 -0
  221. lyrics_transcriber/output/cdgmaker/config.py +151 -0
  222. lyrics_transcriber/output/cdgmaker/images/instrumental.png +0 -0
  223. lyrics_transcriber/output/cdgmaker/images/intro.png +0 -0
  224. lyrics_transcriber/output/cdgmaker/pack.py +507 -0
  225. lyrics_transcriber/output/cdgmaker/render.py +346 -0
  226. lyrics_transcriber/output/cdgmaker/transitions/centertexttoplogobottomtext.png +0 -0
  227. lyrics_transcriber/output/cdgmaker/transitions/circlein.png +0 -0
  228. lyrics_transcriber/output/cdgmaker/transitions/circleout.png +0 -0
  229. lyrics_transcriber/output/cdgmaker/transitions/fizzle.png +0 -0
  230. lyrics_transcriber/output/cdgmaker/transitions/largecentertexttoplogo.png +0 -0
  231. lyrics_transcriber/output/cdgmaker/transitions/rectangle.png +0 -0
  232. lyrics_transcriber/output/cdgmaker/transitions/spiral.png +0 -0
  233. lyrics_transcriber/output/cdgmaker/transitions/topleftmusicalnotes.png +0 -0
  234. lyrics_transcriber/output/cdgmaker/transitions/wipein.png +0 -0
  235. lyrics_transcriber/output/cdgmaker/transitions/wipeleft.png +0 -0
  236. lyrics_transcriber/output/cdgmaker/transitions/wipeout.png +0 -0
  237. lyrics_transcriber/output/cdgmaker/transitions/wiperight.png +0 -0
  238. lyrics_transcriber/output/cdgmaker/utils.py +132 -0
  239. lyrics_transcriber/output/countdown_processor.py +267 -0
  240. lyrics_transcriber/output/fonts/AvenirNext-Bold.ttf +0 -0
  241. lyrics_transcriber/output/fonts/DMSans-VariableFont_opsz,wght.ttf +0 -0
  242. lyrics_transcriber/output/fonts/DMSerifDisplay-Regular.ttf +0 -0
  243. lyrics_transcriber/output/fonts/Oswald-SemiBold.ttf +0 -0
  244. lyrics_transcriber/output/fonts/Zurich_Cn_BT_Bold.ttf +0 -0
  245. lyrics_transcriber/output/fonts/arial.ttf +0 -0
  246. lyrics_transcriber/output/fonts/georgia.ttf +0 -0
  247. lyrics_transcriber/output/fonts/verdana.ttf +0 -0
  248. lyrics_transcriber/output/generator.py +257 -0
  249. lyrics_transcriber/output/lrc_to_cdg.py +61 -0
  250. lyrics_transcriber/output/lyrics_file.py +102 -0
  251. lyrics_transcriber/output/plain_text.py +96 -0
  252. lyrics_transcriber/output/segment_resizer.py +431 -0
  253. lyrics_transcriber/output/subtitles.py +397 -0
  254. lyrics_transcriber/output/video.py +544 -0
  255. lyrics_transcriber/review/__init__.py +0 -0
  256. lyrics_transcriber/review/server.py +676 -0
  257. lyrics_transcriber/storage/__init__.py +0 -0
  258. lyrics_transcriber/storage/dropbox.py +225 -0
  259. lyrics_transcriber/transcribers/__init__.py +0 -0
  260. lyrics_transcriber/transcribers/audioshake.py +290 -0
  261. lyrics_transcriber/transcribers/base_transcriber.py +157 -0
  262. lyrics_transcriber/transcribers/whisper.py +330 -0
  263. lyrics_transcriber/types.py +648 -0
  264. lyrics_transcriber/utils/__init__.py +0 -0
  265. lyrics_transcriber/utils/word_utils.py +27 -0
  266. karaoke_gen-0.57.0.dist-info/METADATA +0 -167
  267. karaoke_gen-0.57.0.dist-info/RECORD +0 -23
  268. {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.27.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,354 @@
1
+ import {
2
+ Dialog,
3
+ DialogTitle,
4
+ DialogContent,
5
+ DialogActions,
6
+ Button,
7
+ Box,
8
+ Typography,
9
+ Paper
10
+ } from '@mui/material'
11
+ import { CorrectionData } from '../types'
12
+ import { useMemo, useRef, useEffect } from 'react'
13
+ import { ApiClient } from '../api'
14
+ import PreviewVideoSection from './PreviewVideoSection'
15
+ import { CloudUpload, ArrowBack } from '@mui/icons-material'
16
+
17
+ interface ReviewChangesModalProps {
18
+ open: boolean
19
+ onClose: () => void
20
+ originalData: CorrectionData
21
+ updatedData: CorrectionData
22
+ onSubmit: () => void
23
+ apiClient: ApiClient | null
24
+ setModalSpacebarHandler: (handler: (() => (e: KeyboardEvent) => void) | undefined) => void
25
+ timingOffsetMs?: number
26
+ }
27
+
28
+ interface DiffResult {
29
+ type: 'added' | 'removed' | 'modified'
30
+ path: string
31
+ segmentIndex?: number
32
+ oldValue?: string
33
+ newValue?: string
34
+ wordChanges?: DiffResult[]
35
+ }
36
+
37
+ // Add interfaces for the word and segment structures
38
+ interface Word {
39
+ text: string
40
+ start_time: number | null
41
+ end_time: number | null
42
+ id?: string
43
+ }
44
+
45
+ interface Segment {
46
+ text: string
47
+ start_time: number | null
48
+ end_time: number | null
49
+ words: Word[]
50
+ id?: string
51
+ }
52
+
53
+ const normalizeWordForComparison = (word: Word): Omit<Word, 'id'> => ({
54
+ text: word.text,
55
+ start_time: word.start_time ?? 0, // Default to 0 for comparison
56
+ end_time: word.end_time ?? 0 // Default to 0 for comparison
57
+ })
58
+
59
+ const normalizeSegmentForComparison = (segment: Segment): Omit<Segment, 'id'> => ({
60
+ text: segment.text,
61
+ start_time: segment.start_time ?? 0, // Default to 0 for comparison
62
+ end_time: segment.end_time ?? 0, // Default to 0 for comparison
63
+ words: segment.words.map(normalizeWordForComparison)
64
+ })
65
+
66
+ export default function ReviewChangesModal({
67
+ open,
68
+ onClose,
69
+ originalData,
70
+ updatedData,
71
+ onSubmit,
72
+ apiClient,
73
+ setModalSpacebarHandler,
74
+ timingOffsetMs = 0
75
+ }: ReviewChangesModalProps) {
76
+ // Add ref to video element
77
+ const videoRef = useRef<HTMLVideoElement>(null)
78
+
79
+ // Stop audio playback when modal opens
80
+ useEffect(() => {
81
+ if (open && window.isAudioPlaying && window.toggleAudioPlayback) {
82
+ window.toggleAudioPlayback()
83
+ }
84
+ }, [open])
85
+
86
+ // Add effect to handle spacebar
87
+ useEffect(() => {
88
+ if (open) {
89
+ setModalSpacebarHandler(() => (e: KeyboardEvent) => {
90
+ if (e.type === 'keydown') {
91
+ e.preventDefault()
92
+ e.stopPropagation()
93
+
94
+ if (videoRef.current) {
95
+ if (videoRef.current.paused) {
96
+ videoRef.current.play()
97
+ } else {
98
+ videoRef.current.pause()
99
+ }
100
+ }
101
+ }
102
+ })
103
+ } else {
104
+ setModalSpacebarHandler(undefined)
105
+ }
106
+
107
+ return () => {
108
+ setModalSpacebarHandler(undefined)
109
+ }
110
+ }, [open, setModalSpacebarHandler])
111
+
112
+ // Debug logging for timing offset
113
+ useEffect(() => {
114
+ if (open) {
115
+ console.log(`[TIMING] ReviewChangesModal opened - timingOffsetMs: ${timingOffsetMs}ms`);
116
+ }
117
+ }, [open, timingOffsetMs]);
118
+
119
+ const differences = useMemo(() => {
120
+ const diffs: DiffResult[] = []
121
+
122
+ originalData.corrected_segments.forEach((originalSegment, index) => {
123
+ const updatedSegment = updatedData.corrected_segments[index]
124
+ if (!updatedSegment) {
125
+ diffs.push({
126
+ type: 'removed',
127
+ path: `Segment ${index}`,
128
+ segmentIndex: index,
129
+ oldValue: originalSegment.text
130
+ })
131
+ return
132
+ }
133
+
134
+ const normalizedOriginal = normalizeSegmentForComparison(originalSegment)
135
+ const normalizedUpdated = normalizeSegmentForComparison(updatedSegment)
136
+ const wordChanges: DiffResult[] = []
137
+
138
+ // Compare word-level changes
139
+ normalizedOriginal.words.forEach((word, wordIndex) => {
140
+ const updatedWord = normalizedUpdated.words[wordIndex]
141
+ if (!updatedWord) {
142
+ wordChanges.push({
143
+ type: 'removed',
144
+ path: `Word ${wordIndex}`,
145
+ oldValue: `"${word.text}" (${word.start_time?.toFixed(4) ?? 'N/A'} - ${word.end_time?.toFixed(4) ?? 'N/A'})`
146
+ })
147
+ return
148
+ }
149
+
150
+ if (word.text !== updatedWord.text ||
151
+ Math.abs((word.start_time ?? 0) - (updatedWord.start_time ?? 0)) > 0.0001 ||
152
+ Math.abs((word.end_time ?? 0) - (updatedWord.end_time ?? 0)) > 0.0001) {
153
+ wordChanges.push({
154
+ type: 'modified',
155
+ path: `Word ${wordIndex}`,
156
+ oldValue: `"${word.text}" (${word.start_time?.toFixed(4) ?? 'N/A'} - ${word.end_time?.toFixed(4) ?? 'N/A'})`,
157
+ newValue: `"${updatedWord.text}" (${updatedWord.start_time?.toFixed(4) ?? 'N/A'} - ${updatedWord.end_time?.toFixed(4) ?? 'N/A'})`
158
+ })
159
+ }
160
+ })
161
+
162
+ // Check for added words
163
+ if (normalizedUpdated.words.length > normalizedOriginal.words.length) {
164
+ for (let i = normalizedOriginal.words.length; i < normalizedUpdated.words.length; i++) {
165
+ const word = normalizedUpdated.words[i]
166
+ wordChanges.push({
167
+ type: 'added',
168
+ path: `Word ${i}`,
169
+ newValue: `"${word.text}" (${word.start_time?.toFixed(4) ?? 'N/A'} - ${word.end_time?.toFixed(4) ?? 'N/A'})`
170
+ })
171
+ }
172
+ }
173
+
174
+ // Compare segment-level changes
175
+ if (normalizedOriginal.text !== normalizedUpdated.text ||
176
+ Math.abs((normalizedOriginal.start_time ?? 0) - (normalizedUpdated.start_time ?? 0)) > 0.0001 ||
177
+ Math.abs((normalizedOriginal.end_time ?? 0) - (normalizedUpdated.end_time ?? 0)) > 0.0001 ||
178
+ wordChanges.length > 0) {
179
+ diffs.push({
180
+ type: 'modified',
181
+ path: `Segment ${index}`,
182
+ segmentIndex: index,
183
+ oldValue: `"${normalizedOriginal.text}" (${normalizedOriginal.start_time?.toFixed(4) ?? 'N/A'} - ${normalizedOriginal.end_time?.toFixed(4) ?? 'N/A'})`,
184
+ newValue: `"${normalizedUpdated.text}" (${normalizedUpdated.start_time?.toFixed(4) ?? 'N/A'} - ${normalizedUpdated.end_time?.toFixed(4) ?? 'N/A'})`,
185
+ wordChanges: wordChanges.length > 0 ? wordChanges : undefined
186
+ })
187
+ }
188
+ })
189
+
190
+ // Check for added segments
191
+ if (updatedData.corrected_segments.length > originalData.corrected_segments.length) {
192
+ for (let i = originalData.corrected_segments.length; i < updatedData.corrected_segments.length; i++) {
193
+ const segment = updatedData.corrected_segments[i]
194
+ diffs.push({
195
+ type: 'added',
196
+ path: `Segment ${i}`,
197
+ segmentIndex: i,
198
+ newValue: `"${segment.text}" (${segment.start_time?.toFixed(4) ?? 'N/A'} - ${segment.end_time?.toFixed(4) ?? 'N/A'})`
199
+ })
200
+ }
201
+ }
202
+
203
+ return diffs
204
+ }, [originalData, updatedData])
205
+
206
+ const renderCompactDiff = (diff: DiffResult) => {
207
+ if (diff.type !== 'modified') {
208
+ // For added/removed segments, show them as before but in a single line
209
+ return (
210
+ <Typography
211
+ key={diff.path}
212
+ color={diff.type === 'added' ? 'success.main' : 'error.main'}
213
+ sx={{ mb: 0.5 }}
214
+ >
215
+ {diff.segmentIndex}: {diff.type === 'added' ? '+ ' : '- '}
216
+ {diff.type === 'added' ? diff.newValue : diff.oldValue}
217
+ </Typography>
218
+ )
219
+ }
220
+
221
+ // For modified segments, create a unified inline diff view
222
+ const oldText = diff.oldValue?.split('"')[1] || ''
223
+ const newText = diff.newValue?.split('"')[1] || ''
224
+ const oldWords = oldText.split(' ')
225
+ const newWords = newText.split(' ')
226
+
227
+ // Extract timing info and format with 2 decimal places
228
+ const timingMatch = diff.newValue?.match(/\(([\d.]+) - ([\d.]+)\)/)
229
+ const timing = timingMatch ?
230
+ `(${parseFloat(timingMatch[1]).toFixed(2)} - ${parseFloat(timingMatch[2]).toFixed(2)})` :
231
+ ''
232
+
233
+ // Create unified diff of words
234
+ const unifiedDiff = []
235
+ let i = 0, j = 0
236
+
237
+ while (i < oldWords.length || j < newWords.length) {
238
+ if (i < oldWords.length && j < newWords.length && oldWords[i] === newWords[j]) {
239
+ // Unchanged word
240
+ unifiedDiff.push({ type: 'unchanged', text: oldWords[i] })
241
+ i++
242
+ j++
243
+ } else if (i < oldWords.length && (!newWords[j] || oldWords[i] !== newWords[j])) {
244
+ // Deleted word
245
+ unifiedDiff.push({ type: 'deleted', text: oldWords[i] })
246
+ i++
247
+ } else if (j < newWords.length) {
248
+ // Added word
249
+ unifiedDiff.push({ type: 'added', text: newWords[j] })
250
+ j++
251
+ }
252
+ }
253
+
254
+ return (
255
+ <Box key={diff.path} sx={{ mb: 0.5, display: 'flex', alignItems: 'center' }}>
256
+ <Typography variant="body2" color="text.secondary" sx={{ mr: 1, minWidth: '30px' }}>
257
+ {diff.segmentIndex}:
258
+ </Typography>
259
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', flexGrow: 1, alignItems: 'center' }}>
260
+ {unifiedDiff.map((word, idx) => (
261
+ <Typography
262
+ key={idx}
263
+ component="span"
264
+ color={
265
+ word.type === 'unchanged' ? 'text.primary' :
266
+ word.type === 'deleted' ? 'error.main' : 'success.main'
267
+ }
268
+ sx={{
269
+ textDecoration: word.type === 'deleted' ? 'line-through' : 'none',
270
+ mr: 0.5
271
+ }}
272
+ >
273
+ {word.text}
274
+ </Typography>
275
+ ))}
276
+ <Typography variant="body2" color="text.secondary" sx={{ ml: 1 }}>
277
+ {timing}
278
+ </Typography>
279
+ </Box>
280
+ </Box>
281
+ )
282
+ }
283
+
284
+ return (
285
+ <Dialog
286
+ open={open}
287
+ onClose={onClose}
288
+ maxWidth="md"
289
+ fullWidth
290
+ >
291
+ <DialogTitle>Preview Video (With Vocals)</DialogTitle>
292
+ <DialogContent
293
+ dividers
294
+ sx={{
295
+ p: 0, // Remove default padding
296
+ '&:first-of-type': { pt: 0 } // Remove default top padding
297
+ }}
298
+ >
299
+ <PreviewVideoSection
300
+ apiClient={apiClient}
301
+ isModalOpen={open}
302
+ updatedData={updatedData}
303
+ videoRef={videoRef} // Pass the ref to PreviewVideoSection
304
+ timingOffsetMs={timingOffsetMs}
305
+ />
306
+
307
+ <Box sx={{ p: 2, mt: 0 }}>
308
+ {timingOffsetMs !== 0 && (
309
+ <Typography variant="body2" fontWeight="bold" sx={{ mt: 1 }}>
310
+ Global Timing Offset applied to all words: {timingOffsetMs > 0 ? '+' : ''}{timingOffsetMs}ms
311
+ </Typography>
312
+ )}
313
+ {differences.length === 0 ? (
314
+ <Box>
315
+ <Typography color="text.secondary">
316
+ No manual corrections detected. If everything looks good in the preview, click submit and the server will generate the final karaoke video.
317
+ </Typography>
318
+ <Typography variant="body2" color="text.secondary">
319
+ Total segments: {updatedData.corrected_segments.length}
320
+ </Typography>
321
+ </Box>
322
+ ) : (
323
+ <Box>
324
+ <Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
325
+ {differences.length} segment{differences.length !== 1 ? 's' : ''} modified:
326
+ </Typography>
327
+ <Paper sx={{ p: 2 }}>
328
+ {differences.map(renderCompactDiff)}
329
+ </Paper>
330
+ </Box>
331
+ )}
332
+ </Box>
333
+ </DialogContent>
334
+ <DialogActions>
335
+ <Button
336
+ onClick={onClose}
337
+ color="warning"
338
+ startIcon={<ArrowBack />}
339
+ sx={{ mr: 'auto' }}
340
+ >
341
+ Cancel
342
+ </Button>
343
+ <Button
344
+ onClick={onSubmit}
345
+ variant="contained"
346
+ color="success"
347
+ endIcon={<CloudUpload />}
348
+ >
349
+ Complete Review
350
+ </Button>
351
+ </DialogActions>
352
+ </Dialog>
353
+ )
354
+ }
@@ -0,0 +1,64 @@
1
+ import {
2
+ Dialog,
3
+ DialogTitle,
4
+ DialogContent,
5
+ IconButton, Box
6
+ } from '@mui/material'
7
+ import CloseIcon from '@mui/icons-material/Close'
8
+ import { LyricsSegment } from '../types'
9
+
10
+ interface SegmentDetailsModalProps {
11
+ open: boolean
12
+ onClose: () => void
13
+ segment: LyricsSegment | null
14
+ segmentIndex: number | null
15
+ }
16
+
17
+ export default function SegmentDetailsModal({
18
+ open,
19
+ onClose,
20
+ segment,
21
+ segmentIndex
22
+ }: SegmentDetailsModalProps) {
23
+ if (!segment || segmentIndex === null) return null
24
+
25
+ return (
26
+ <Dialog
27
+ open={open}
28
+ onClose={onClose}
29
+ maxWidth="sm"
30
+ fullWidth
31
+ PaperProps={{
32
+ sx: { position: 'relative' },
33
+ }}
34
+ >
35
+ <IconButton
36
+ onClick={onClose}
37
+ sx={{
38
+ position: 'absolute',
39
+ right: 8,
40
+ top: 8,
41
+ }}
42
+ >
43
+ <CloseIcon />
44
+ </IconButton>
45
+ <DialogTitle>
46
+ Segment {segmentIndex} Details
47
+ </DialogTitle>
48
+ <DialogContent dividers>
49
+ <Box
50
+ component="pre"
51
+ sx={{
52
+ margin: 0,
53
+ fontFamily: 'monospace',
54
+ fontSize: '0.875rem',
55
+ whiteSpace: 'pre-wrap',
56
+ wordBreak: 'break-word'
57
+ }}
58
+ >
59
+ {JSON.stringify(segment, null, 2)}
60
+ </Box>
61
+ </DialogContent>
62
+ </Dialog>
63
+ )
64
+ }