karaoke-gen 0.75.54__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.

Potentially problematic release.


This version of karaoke-gen might be problematic. Click here for more details.

Files changed (287) hide show
  1. karaoke_gen/__init__.py +38 -0
  2. karaoke_gen/audio_fetcher.py +1614 -0
  3. karaoke_gen/audio_processor.py +790 -0
  4. karaoke_gen/config.py +83 -0
  5. karaoke_gen/file_handler.py +387 -0
  6. karaoke_gen/instrumental_review/__init__.py +45 -0
  7. karaoke_gen/instrumental_review/analyzer.py +408 -0
  8. karaoke_gen/instrumental_review/editor.py +322 -0
  9. karaoke_gen/instrumental_review/models.py +171 -0
  10. karaoke_gen/instrumental_review/server.py +475 -0
  11. karaoke_gen/instrumental_review/static/index.html +1529 -0
  12. karaoke_gen/instrumental_review/waveform.py +409 -0
  13. karaoke_gen/karaoke_finalise/__init__.py +1 -0
  14. karaoke_gen/karaoke_finalise/karaoke_finalise.py +1833 -0
  15. karaoke_gen/karaoke_gen.py +1026 -0
  16. karaoke_gen/lyrics_processor.py +474 -0
  17. karaoke_gen/metadata.py +160 -0
  18. karaoke_gen/pipeline/__init__.py +87 -0
  19. karaoke_gen/pipeline/base.py +215 -0
  20. karaoke_gen/pipeline/context.py +230 -0
  21. karaoke_gen/pipeline/executors/__init__.py +21 -0
  22. karaoke_gen/pipeline/executors/local.py +159 -0
  23. karaoke_gen/pipeline/executors/remote.py +257 -0
  24. karaoke_gen/pipeline/stages/__init__.py +27 -0
  25. karaoke_gen/pipeline/stages/finalize.py +202 -0
  26. karaoke_gen/pipeline/stages/render.py +165 -0
  27. karaoke_gen/pipeline/stages/screens.py +139 -0
  28. karaoke_gen/pipeline/stages/separation.py +191 -0
  29. karaoke_gen/pipeline/stages/transcription.py +191 -0
  30. karaoke_gen/resources/AvenirNext-Bold.ttf +0 -0
  31. karaoke_gen/resources/Montserrat-Bold.ttf +0 -0
  32. karaoke_gen/resources/Oswald-Bold.ttf +0 -0
  33. karaoke_gen/resources/Oswald-SemiBold.ttf +0 -0
  34. karaoke_gen/resources/Zurich_Cn_BT_Bold.ttf +0 -0
  35. karaoke_gen/style_loader.py +531 -0
  36. karaoke_gen/utils/__init__.py +18 -0
  37. karaoke_gen/utils/bulk_cli.py +492 -0
  38. karaoke_gen/utils/cli_args.py +432 -0
  39. karaoke_gen/utils/gen_cli.py +978 -0
  40. karaoke_gen/utils/remote_cli.py +3268 -0
  41. karaoke_gen/video_background_processor.py +351 -0
  42. karaoke_gen/video_generator.py +424 -0
  43. karaoke_gen-0.75.54.dist-info/METADATA +718 -0
  44. karaoke_gen-0.75.54.dist-info/RECORD +287 -0
  45. karaoke_gen-0.75.54.dist-info/WHEEL +4 -0
  46. karaoke_gen-0.75.54.dist-info/entry_points.txt +5 -0
  47. karaoke_gen-0.75.54.dist-info/licenses/LICENSE +21 -0
  48. lyrics_transcriber/__init__.py +10 -0
  49. lyrics_transcriber/cli/__init__.py +0 -0
  50. lyrics_transcriber/cli/cli_main.py +285 -0
  51. lyrics_transcriber/core/__init__.py +0 -0
  52. lyrics_transcriber/core/config.py +50 -0
  53. lyrics_transcriber/core/controller.py +594 -0
  54. lyrics_transcriber/correction/__init__.py +0 -0
  55. lyrics_transcriber/correction/agentic/__init__.py +9 -0
  56. lyrics_transcriber/correction/agentic/adapter.py +71 -0
  57. lyrics_transcriber/correction/agentic/agent.py +313 -0
  58. lyrics_transcriber/correction/agentic/feedback/aggregator.py +12 -0
  59. lyrics_transcriber/correction/agentic/feedback/collector.py +17 -0
  60. lyrics_transcriber/correction/agentic/feedback/retention.py +24 -0
  61. lyrics_transcriber/correction/agentic/feedback/store.py +76 -0
  62. lyrics_transcriber/correction/agentic/handlers/__init__.py +24 -0
  63. lyrics_transcriber/correction/agentic/handlers/ambiguous.py +44 -0
  64. lyrics_transcriber/correction/agentic/handlers/background_vocals.py +68 -0
  65. lyrics_transcriber/correction/agentic/handlers/base.py +51 -0
  66. lyrics_transcriber/correction/agentic/handlers/complex_multi_error.py +46 -0
  67. lyrics_transcriber/correction/agentic/handlers/extra_words.py +74 -0
  68. lyrics_transcriber/correction/agentic/handlers/no_error.py +42 -0
  69. lyrics_transcriber/correction/agentic/handlers/punctuation.py +44 -0
  70. lyrics_transcriber/correction/agentic/handlers/registry.py +60 -0
  71. lyrics_transcriber/correction/agentic/handlers/repeated_section.py +44 -0
  72. lyrics_transcriber/correction/agentic/handlers/sound_alike.py +126 -0
  73. lyrics_transcriber/correction/agentic/models/__init__.py +5 -0
  74. lyrics_transcriber/correction/agentic/models/ai_correction.py +31 -0
  75. lyrics_transcriber/correction/agentic/models/correction_session.py +30 -0
  76. lyrics_transcriber/correction/agentic/models/enums.py +38 -0
  77. lyrics_transcriber/correction/agentic/models/human_feedback.py +30 -0
  78. lyrics_transcriber/correction/agentic/models/learning_data.py +26 -0
  79. lyrics_transcriber/correction/agentic/models/observability_metrics.py +28 -0
  80. lyrics_transcriber/correction/agentic/models/schemas.py +46 -0
  81. lyrics_transcriber/correction/agentic/models/utils.py +19 -0
  82. lyrics_transcriber/correction/agentic/observability/__init__.py +5 -0
  83. lyrics_transcriber/correction/agentic/observability/langfuse_integration.py +35 -0
  84. lyrics_transcriber/correction/agentic/observability/metrics.py +46 -0
  85. lyrics_transcriber/correction/agentic/observability/performance.py +19 -0
  86. lyrics_transcriber/correction/agentic/prompts/__init__.py +2 -0
  87. lyrics_transcriber/correction/agentic/prompts/classifier.py +227 -0
  88. lyrics_transcriber/correction/agentic/providers/__init__.py +6 -0
  89. lyrics_transcriber/correction/agentic/providers/base.py +36 -0
  90. lyrics_transcriber/correction/agentic/providers/circuit_breaker.py +145 -0
  91. lyrics_transcriber/correction/agentic/providers/config.py +73 -0
  92. lyrics_transcriber/correction/agentic/providers/constants.py +24 -0
  93. lyrics_transcriber/correction/agentic/providers/health.py +28 -0
  94. lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +212 -0
  95. lyrics_transcriber/correction/agentic/providers/model_factory.py +209 -0
  96. lyrics_transcriber/correction/agentic/providers/response_cache.py +218 -0
  97. lyrics_transcriber/correction/agentic/providers/response_parser.py +111 -0
  98. lyrics_transcriber/correction/agentic/providers/retry_executor.py +127 -0
  99. lyrics_transcriber/correction/agentic/router.py +35 -0
  100. lyrics_transcriber/correction/agentic/workflows/__init__.py +5 -0
  101. lyrics_transcriber/correction/agentic/workflows/consensus_workflow.py +24 -0
  102. lyrics_transcriber/correction/agentic/workflows/correction_graph.py +59 -0
  103. lyrics_transcriber/correction/agentic/workflows/feedback_workflow.py +24 -0
  104. lyrics_transcriber/correction/anchor_sequence.py +919 -0
  105. lyrics_transcriber/correction/corrector.py +760 -0
  106. lyrics_transcriber/correction/feedback/__init__.py +2 -0
  107. lyrics_transcriber/correction/feedback/schemas.py +107 -0
  108. lyrics_transcriber/correction/feedback/store.py +236 -0
  109. lyrics_transcriber/correction/handlers/__init__.py +0 -0
  110. lyrics_transcriber/correction/handlers/base.py +52 -0
  111. lyrics_transcriber/correction/handlers/extend_anchor.py +149 -0
  112. lyrics_transcriber/correction/handlers/levenshtein.py +189 -0
  113. lyrics_transcriber/correction/handlers/llm.py +293 -0
  114. lyrics_transcriber/correction/handlers/llm_providers.py +60 -0
  115. lyrics_transcriber/correction/handlers/no_space_punct_match.py +154 -0
  116. lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +85 -0
  117. lyrics_transcriber/correction/handlers/repeat.py +88 -0
  118. lyrics_transcriber/correction/handlers/sound_alike.py +259 -0
  119. lyrics_transcriber/correction/handlers/syllables_match.py +252 -0
  120. lyrics_transcriber/correction/handlers/word_count_match.py +80 -0
  121. lyrics_transcriber/correction/handlers/word_operations.py +187 -0
  122. lyrics_transcriber/correction/operations.py +352 -0
  123. lyrics_transcriber/correction/phrase_analyzer.py +435 -0
  124. lyrics_transcriber/correction/text_utils.py +30 -0
  125. lyrics_transcriber/frontend/.gitignore +23 -0
  126. lyrics_transcriber/frontend/.yarn/releases/yarn-4.7.0.cjs +935 -0
  127. lyrics_transcriber/frontend/.yarnrc.yml +3 -0
  128. lyrics_transcriber/frontend/README.md +50 -0
  129. lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md +210 -0
  130. lyrics_transcriber/frontend/__init__.py +25 -0
  131. lyrics_transcriber/frontend/eslint.config.js +28 -0
  132. lyrics_transcriber/frontend/index.html +18 -0
  133. lyrics_transcriber/frontend/package.json +42 -0
  134. lyrics_transcriber/frontend/public/android-chrome-192x192.png +0 -0
  135. lyrics_transcriber/frontend/public/android-chrome-512x512.png +0 -0
  136. lyrics_transcriber/frontend/public/apple-touch-icon.png +0 -0
  137. lyrics_transcriber/frontend/public/favicon-16x16.png +0 -0
  138. lyrics_transcriber/frontend/public/favicon-32x32.png +0 -0
  139. lyrics_transcriber/frontend/public/favicon.ico +0 -0
  140. lyrics_transcriber/frontend/public/nomad-karaoke-logo.png +0 -0
  141. lyrics_transcriber/frontend/src/App.tsx +214 -0
  142. lyrics_transcriber/frontend/src/api.ts +254 -0
  143. lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +77 -0
  144. lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +114 -0
  145. lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx +204 -0
  146. lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +180 -0
  147. lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +167 -0
  148. lyrics_transcriber/frontend/src/components/CorrectionAnnotationModal.tsx +359 -0
  149. lyrics_transcriber/frontend/src/components/CorrectionDetailCard.tsx +281 -0
  150. lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +162 -0
  151. lyrics_transcriber/frontend/src/components/DurationTimelineView.tsx +257 -0
  152. lyrics_transcriber/frontend/src/components/EditActionBar.tsx +68 -0
  153. lyrics_transcriber/frontend/src/components/EditModal.tsx +702 -0
  154. lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +496 -0
  155. lyrics_transcriber/frontend/src/components/EditWordList.tsx +379 -0
  156. lyrics_transcriber/frontend/src/components/FileUpload.tsx +77 -0
  157. lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
  158. lyrics_transcriber/frontend/src/components/Header.tsx +413 -0
  159. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +1387 -0
  160. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/SyncControls.tsx +185 -0
  161. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/TimelineCanvas.tsx +704 -0
  162. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/UpcomingWordsBar.tsx +80 -0
  163. lyrics_transcriber/frontend/src/components/LyricsSynchronizer/index.tsx +905 -0
  164. lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx +51 -0
  165. lyrics_transcriber/frontend/src/components/ModeSelectionModal.tsx +127 -0
  166. lyrics_transcriber/frontend/src/components/ModeSelector.tsx +67 -0
  167. lyrics_transcriber/frontend/src/components/ModelSelector.tsx +23 -0
  168. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +144 -0
  169. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +268 -0
  170. lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +336 -0
  171. lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +354 -0
  172. lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +64 -0
  173. lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +376 -0
  174. lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +131 -0
  175. lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +256 -0
  176. lyrics_transcriber/frontend/src/components/WordDivider.tsx +187 -0
  177. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +379 -0
  178. lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +56 -0
  179. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +87 -0
  180. lyrics_transcriber/frontend/src/components/shared/constants.ts +20 -0
  181. lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +180 -0
  182. lyrics_transcriber/frontend/src/components/shared/styles.ts +13 -0
  183. lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
  184. lyrics_transcriber/frontend/src/components/shared/types.ts +129 -0
  185. lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +177 -0
  186. lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
  187. lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +75 -0
  188. lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +360 -0
  189. lyrics_transcriber/frontend/src/components/shared/utils/timingUtils.ts +110 -0
  190. lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
  191. lyrics_transcriber/frontend/src/hooks/useManualSync.ts +435 -0
  192. lyrics_transcriber/frontend/src/main.tsx +17 -0
  193. lyrics_transcriber/frontend/src/theme.ts +177 -0
  194. lyrics_transcriber/frontend/src/types/global.d.ts +9 -0
  195. lyrics_transcriber/frontend/src/types.js +2 -0
  196. lyrics_transcriber/frontend/src/types.ts +199 -0
  197. lyrics_transcriber/frontend/src/validation.ts +132 -0
  198. lyrics_transcriber/frontend/src/vite-env.d.ts +1 -0
  199. lyrics_transcriber/frontend/tsconfig.app.json +26 -0
  200. lyrics_transcriber/frontend/tsconfig.json +25 -0
  201. lyrics_transcriber/frontend/tsconfig.node.json +23 -0
  202. lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -0
  203. lyrics_transcriber/frontend/update_version.js +11 -0
  204. lyrics_transcriber/frontend/vite.config.d.ts +2 -0
  205. lyrics_transcriber/frontend/vite.config.js +10 -0
  206. lyrics_transcriber/frontend/vite.config.ts +11 -0
  207. lyrics_transcriber/frontend/web_assets/android-chrome-192x192.png +0 -0
  208. lyrics_transcriber/frontend/web_assets/android-chrome-512x512.png +0 -0
  209. lyrics_transcriber/frontend/web_assets/apple-touch-icon.png +0 -0
  210. lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js +43288 -0
  211. lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js.map +1 -0
  212. lyrics_transcriber/frontend/web_assets/favicon-16x16.png +0 -0
  213. lyrics_transcriber/frontend/web_assets/favicon-32x32.png +0 -0
  214. lyrics_transcriber/frontend/web_assets/favicon.ico +0 -0
  215. lyrics_transcriber/frontend/web_assets/index.html +18 -0
  216. lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.png +0 -0
  217. lyrics_transcriber/frontend/yarn.lock +3752 -0
  218. lyrics_transcriber/lyrics/__init__.py +0 -0
  219. lyrics_transcriber/lyrics/base_lyrics_provider.py +211 -0
  220. lyrics_transcriber/lyrics/file_provider.py +95 -0
  221. lyrics_transcriber/lyrics/genius.py +384 -0
  222. lyrics_transcriber/lyrics/lrclib.py +231 -0
  223. lyrics_transcriber/lyrics/musixmatch.py +156 -0
  224. lyrics_transcriber/lyrics/spotify.py +290 -0
  225. lyrics_transcriber/lyrics/user_input_provider.py +44 -0
  226. lyrics_transcriber/output/__init__.py +0 -0
  227. lyrics_transcriber/output/ass/__init__.py +21 -0
  228. lyrics_transcriber/output/ass/ass.py +2088 -0
  229. lyrics_transcriber/output/ass/ass_specs.txt +732 -0
  230. lyrics_transcriber/output/ass/config.py +180 -0
  231. lyrics_transcriber/output/ass/constants.py +23 -0
  232. lyrics_transcriber/output/ass/event.py +94 -0
  233. lyrics_transcriber/output/ass/formatters.py +132 -0
  234. lyrics_transcriber/output/ass/lyrics_line.py +265 -0
  235. lyrics_transcriber/output/ass/lyrics_screen.py +252 -0
  236. lyrics_transcriber/output/ass/section_detector.py +89 -0
  237. lyrics_transcriber/output/ass/section_screen.py +106 -0
  238. lyrics_transcriber/output/ass/style.py +187 -0
  239. lyrics_transcriber/output/cdg.py +619 -0
  240. lyrics_transcriber/output/cdgmaker/__init__.py +0 -0
  241. lyrics_transcriber/output/cdgmaker/cdg.py +262 -0
  242. lyrics_transcriber/output/cdgmaker/composer.py +2260 -0
  243. lyrics_transcriber/output/cdgmaker/config.py +151 -0
  244. lyrics_transcriber/output/cdgmaker/images/instrumental.png +0 -0
  245. lyrics_transcriber/output/cdgmaker/images/intro.png +0 -0
  246. lyrics_transcriber/output/cdgmaker/pack.py +507 -0
  247. lyrics_transcriber/output/cdgmaker/render.py +346 -0
  248. lyrics_transcriber/output/cdgmaker/transitions/centertexttoplogobottomtext.png +0 -0
  249. lyrics_transcriber/output/cdgmaker/transitions/circlein.png +0 -0
  250. lyrics_transcriber/output/cdgmaker/transitions/circleout.png +0 -0
  251. lyrics_transcriber/output/cdgmaker/transitions/fizzle.png +0 -0
  252. lyrics_transcriber/output/cdgmaker/transitions/largecentertexttoplogo.png +0 -0
  253. lyrics_transcriber/output/cdgmaker/transitions/rectangle.png +0 -0
  254. lyrics_transcriber/output/cdgmaker/transitions/spiral.png +0 -0
  255. lyrics_transcriber/output/cdgmaker/transitions/topleftmusicalnotes.png +0 -0
  256. lyrics_transcriber/output/cdgmaker/transitions/wipein.png +0 -0
  257. lyrics_transcriber/output/cdgmaker/transitions/wipeleft.png +0 -0
  258. lyrics_transcriber/output/cdgmaker/transitions/wipeout.png +0 -0
  259. lyrics_transcriber/output/cdgmaker/transitions/wiperight.png +0 -0
  260. lyrics_transcriber/output/cdgmaker/utils.py +132 -0
  261. lyrics_transcriber/output/countdown_processor.py +306 -0
  262. lyrics_transcriber/output/fonts/AvenirNext-Bold.ttf +0 -0
  263. lyrics_transcriber/output/fonts/DMSans-VariableFont_opsz,wght.ttf +0 -0
  264. lyrics_transcriber/output/fonts/DMSerifDisplay-Regular.ttf +0 -0
  265. lyrics_transcriber/output/fonts/Oswald-SemiBold.ttf +0 -0
  266. lyrics_transcriber/output/fonts/Zurich_Cn_BT_Bold.ttf +0 -0
  267. lyrics_transcriber/output/fonts/arial.ttf +0 -0
  268. lyrics_transcriber/output/fonts/georgia.ttf +0 -0
  269. lyrics_transcriber/output/fonts/verdana.ttf +0 -0
  270. lyrics_transcriber/output/generator.py +257 -0
  271. lyrics_transcriber/output/lrc_to_cdg.py +61 -0
  272. lyrics_transcriber/output/lyrics_file.py +102 -0
  273. lyrics_transcriber/output/plain_text.py +96 -0
  274. lyrics_transcriber/output/segment_resizer.py +431 -0
  275. lyrics_transcriber/output/subtitles.py +397 -0
  276. lyrics_transcriber/output/video.py +544 -0
  277. lyrics_transcriber/review/__init__.py +0 -0
  278. lyrics_transcriber/review/server.py +676 -0
  279. lyrics_transcriber/storage/__init__.py +0 -0
  280. lyrics_transcriber/storage/dropbox.py +225 -0
  281. lyrics_transcriber/transcribers/__init__.py +0 -0
  282. lyrics_transcriber/transcribers/audioshake.py +379 -0
  283. lyrics_transcriber/transcribers/base_transcriber.py +157 -0
  284. lyrics_transcriber/transcribers/whisper.py +330 -0
  285. lyrics_transcriber/types.py +650 -0
  286. lyrics_transcriber/utils/__init__.py +0 -0
  287. lyrics_transcriber/utils/word_utils.py +27 -0
@@ -0,0 +1,180 @@
1
+ import { useCallback } from 'react'
2
+ import { AnchorSequence, GapSequence, InteractionMode, WordCorrection } from '../../../types'
3
+ import { ModalContent } from '../../LyricsAnalyzer'
4
+ import { WordClickInfo } from '../types'
5
+
6
+ export interface UseWordClickProps {
7
+ mode: InteractionMode
8
+ onElementClick: (content: ModalContent) => void
9
+ onWordClick?: (info: WordClickInfo) => void
10
+ isReference?: boolean
11
+ currentSource?: string
12
+ gaps?: GapSequence[]
13
+ anchors?: AnchorSequence[]
14
+ corrections?: WordCorrection[]
15
+ }
16
+
17
+ export function useWordClick({
18
+ mode,
19
+ onElementClick,
20
+ onWordClick,
21
+ isReference = false,
22
+ currentSource = '',
23
+ gaps = [],
24
+ anchors = [],
25
+ corrections = []
26
+ }: UseWordClickProps) {
27
+ const handleWordClick = useCallback((
28
+ word: string,
29
+ wordId: string,
30
+ anchor?: AnchorSequence,
31
+ gap?: GapSequence
32
+ ) => {
33
+ // Check if word belongs to anchor
34
+ const belongsToAnchor = anchor && (
35
+ isReference
36
+ ? anchor.reference_word_ids[currentSource]?.includes(wordId)
37
+ : anchor.transcribed_word_ids.includes(wordId)
38
+ )
39
+
40
+ // Find matching gap if not provided
41
+ const matchingGap = gap || gaps.find(g =>
42
+ g.transcribed_word_ids.includes(wordId) ||
43
+ Object.values(g.reference_word_ids).some(ids => ids.includes(wordId))
44
+ )
45
+
46
+ // Check if word belongs to gap - include both original and corrected words
47
+ const belongsToGap = matchingGap && (
48
+ isReference
49
+ ? matchingGap.reference_word_ids[currentSource]?.includes(wordId)
50
+ : (matchingGap.transcribed_word_ids.includes(wordId) ||
51
+ corrections.some(c =>
52
+ c.corrected_word_id === wordId ||
53
+ c.word_id === wordId
54
+ ))
55
+ )
56
+
57
+ // Debug info
58
+ console.log('Word Click Debug:', {
59
+ clickInfo: {
60
+ word,
61
+ wordId,
62
+ isReference,
63
+ currentSource,
64
+ mode
65
+ },
66
+ anchorInfo: anchor && {
67
+ id: anchor.id,
68
+ transcribedWordIds: anchor.transcribed_word_ids,
69
+ referenceWordIds: anchor.reference_word_ids,
70
+ belongsToAnchor
71
+ },
72
+ gapInfo: matchingGap && {
73
+ id: matchingGap.id,
74
+ transcribedWordIds: matchingGap.transcribed_word_ids,
75
+ referenceWordIds: matchingGap.reference_word_ids,
76
+ belongsToGap,
77
+ relatedCorrections: corrections.filter(c =>
78
+ matchingGap.transcribed_word_ids.includes(c.word_id) ||
79
+ c.corrected_word_id === wordId ||
80
+ c.word_id === wordId
81
+ )
82
+ }
83
+ })
84
+
85
+ // For reference view clicks, find the corresponding gap
86
+ if (isReference && currentSource) {
87
+ const matchingGap = gaps?.find(g =>
88
+ g.reference_word_ids[currentSource]?.includes(wordId)
89
+ )
90
+
91
+ if (matchingGap) {
92
+ console.log('Found matching gap for reference click:', {
93
+ wordId,
94
+ gap: matchingGap
95
+ })
96
+ gap = matchingGap
97
+ }
98
+ }
99
+
100
+ if (mode === 'highlight' || mode === 'edit' || mode === 'delete_word') {
101
+ if (belongsToAnchor && anchor) {
102
+ onWordClick?.({
103
+ word_id: wordId,
104
+ type: 'anchor',
105
+ anchor,
106
+ gap: undefined
107
+ })
108
+ } else if (belongsToGap && gap) {
109
+ onWordClick?.({
110
+ word_id: wordId,
111
+ type: 'gap',
112
+ anchor: undefined,
113
+ gap
114
+ })
115
+ } else if (corrections.some(c =>
116
+ (c.corrected_word_id === wordId || c.word_id === wordId) &&
117
+ gap?.transcribed_word_ids.includes(c.word_id)
118
+ )) {
119
+ // If the word is part of a correction, mark it as a gap
120
+ onWordClick?.({
121
+ word_id: wordId,
122
+ type: 'gap',
123
+ anchor: undefined,
124
+ gap
125
+ })
126
+ } else {
127
+ onWordClick?.({
128
+ word_id: wordId,
129
+ type: 'other',
130
+ anchor: undefined,
131
+ gap: undefined
132
+ })
133
+ }
134
+ } else {
135
+ // This is a fallback for any future modes
136
+ if (belongsToAnchor && anchor) {
137
+ onElementClick({
138
+ type: 'anchor',
139
+ data: {
140
+ ...anchor,
141
+ wordId,
142
+ word,
143
+ anchor_sequences: anchors
144
+ }
145
+ })
146
+ } else if (belongsToGap && gap) {
147
+ onElementClick({
148
+ type: 'gap',
149
+ data: {
150
+ ...gap,
151
+ wordId,
152
+ word,
153
+ anchor_sequences: anchors
154
+ }
155
+ })
156
+ } else if (!isReference) {
157
+ // Create synthetic gap for non-sequence words (transcription view only)
158
+ const syntheticGap: GapSequence = {
159
+ id: `synthetic-${wordId}`,
160
+ transcribed_word_ids: [wordId],
161
+ transcription_position: -1,
162
+ preceding_anchor_id: null,
163
+ following_anchor_id: null,
164
+ reference_word_ids: {}
165
+ }
166
+ onElementClick({
167
+ type: 'gap',
168
+ data: {
169
+ ...syntheticGap,
170
+ wordId,
171
+ word,
172
+ anchor_sequences: anchors
173
+ }
174
+ })
175
+ }
176
+ }
177
+ }, [mode, onWordClick, onElementClick, isReference, currentSource, gaps, anchors, corrections])
178
+
179
+ return { handleWordClick }
180
+ }
@@ -0,0 +1,13 @@
1
+ import { styled } from '@mui/system'
2
+ import { flashAnimation } from './constants'
3
+
4
+ export const HighlightedWord = styled('span')<{ shouldFlash: boolean }>(
5
+ ({ shouldFlash }) => ({
6
+ display: 'inline-block',
7
+ marginRight: '0.25em',
8
+ transition: 'background-color 0.2s ease',
9
+ ...(shouldFlash && {
10
+ animation: `${flashAnimation} 0.4s ease-in-out 3`,
11
+ }),
12
+ })
13
+ )
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,129 @@
1
+ import { AnchorSequence, GapSequence, HighlightInfo, InteractionMode, CorrectionData, LyricsSegment, ReferenceSource, WordCorrection } from '../../types'
2
+ import { ModalContent } from '../LyricsAnalyzer'
3
+
4
+ // Add FlashType definition directly in shared types
5
+ export type FlashType = 'anchor' | 'corrected' | 'uncorrected' | 'word' | 'handler' | null
6
+
7
+ // Common word click handling
8
+ export interface WordClickInfo {
9
+ word_id: string
10
+ type: 'anchor' | 'gap' | 'other'
11
+ anchor?: AnchorSequence
12
+ gap?: GapSequence
13
+ }
14
+
15
+ // Base props shared between components
16
+ export interface BaseViewProps {
17
+ onElementClick: (content: ModalContent) => void
18
+ onWordClick?: (info: WordClickInfo) => void
19
+ flashingType: FlashType
20
+ highlightInfo: HighlightInfo | null
21
+ mode: InteractionMode
22
+ }
23
+
24
+ // Base word position interface - remove the word property from here
25
+ export interface BaseWordPosition {
26
+ type: 'anchor' | 'gap' | 'other'
27
+ sequence?: AnchorSequence | GapSequence
28
+ }
29
+
30
+ // Transcription-specific word position with timing info
31
+ export interface TranscriptionWordPosition extends BaseWordPosition {
32
+ word: {
33
+ id: string
34
+ text: string
35
+ start_time?: number
36
+ end_time?: number
37
+ }
38
+ type: 'anchor' | 'gap' | 'other'
39
+ sequence?: AnchorSequence | GapSequence
40
+ isInRange: boolean
41
+ isCorrected?: boolean
42
+ }
43
+
44
+ // Reference-specific word position with simple string word
45
+ export interface ReferenceWordPosition extends BaseWordPosition {
46
+ index: number
47
+ isHighlighted: boolean
48
+ word: string // Simple string word for reference view
49
+ }
50
+
51
+ // Word component props
52
+ export interface WordProps {
53
+ word: string
54
+ shouldFlash: boolean
55
+ isAnchor?: boolean
56
+ isCorrectedGap?: boolean
57
+ isUncorrectedGap?: boolean
58
+ isCurrentlyPlaying?: boolean
59
+ padding?: string
60
+ onClick?: () => void
61
+ correction?: {
62
+ originalWord: string
63
+ handler: string
64
+ confidence: number
65
+ source: string
66
+ reason?: string
67
+ } | null
68
+ }
69
+
70
+ // Text segment props
71
+ export interface TextSegmentProps extends BaseViewProps {
72
+ wordPositions: TranscriptionWordPosition[] | ReferenceWordPosition[]
73
+ }
74
+
75
+ // View-specific props
76
+ export interface TranscriptionViewProps {
77
+ data: CorrectionData
78
+ onElementClick: (content: ModalContent) => void
79
+ onWordClick?: (info: WordClickInfo) => void
80
+ flashingType: FlashType
81
+ highlightInfo: HighlightInfo | null
82
+ mode: InteractionMode
83
+ onPlaySegment?: (startTime: number) => void
84
+ currentTime?: number
85
+ anchors?: AnchorSequence[]
86
+ flashingHandler?: string | null
87
+ onDataChange?: (updatedData: CorrectionData) => void
88
+ }
89
+
90
+ // Add LinePosition type here since it's used in multiple places
91
+ export interface LinePosition {
92
+ position: number
93
+ lineNumber: number
94
+ isEmpty?: boolean
95
+ }
96
+
97
+ // Reference-specific props
98
+ export interface ReferenceViewProps extends BaseViewProps {
99
+ referenceSources: Record<string, ReferenceSource>
100
+ anchors: CorrectionData['anchor_sequences']
101
+ gaps: CorrectionData['gap_sequences']
102
+ currentSource: string
103
+ onSourceChange: (source: string) => void
104
+ corrected_segments: LyricsSegment[]
105
+ corrections: WordCorrection[]
106
+ onAddLyrics?: () => void
107
+ }
108
+
109
+ // Update HighlightedTextProps to include linePositions
110
+ export interface HighlightedTextProps extends BaseViewProps {
111
+ text?: string
112
+ segments?: LyricsSegment[]
113
+ wordPositions: TranscriptionWordPosition[] | ReferenceWordPosition[]
114
+ anchors: AnchorSequence[]
115
+ highlightInfo: HighlightInfo | null
116
+ mode: InteractionMode
117
+ onElementClick: (content: ModalContent) => void
118
+ onWordClick?: (info: WordClickInfo) => void
119
+ flashingType: FlashType
120
+ isReference?: boolean
121
+ currentSource?: string
122
+ preserveSegments?: boolean
123
+ linePositions?: LinePosition[]
124
+ currentTime?: number
125
+ referenceCorrections?: Map<string, string>
126
+ gaps?: GapSequence[]
127
+ flashingHandler?: string | null
128
+ corrections?: WordCorrection[]
129
+ }
@@ -0,0 +1,177 @@
1
+ // Add a global ref for the modal handler
2
+ let currentModalHandler: ((e: KeyboardEvent) => void) | undefined
3
+ let isModalOpen = false
4
+ const debugLog = false
5
+
6
+ type KeyboardState = {
7
+ setIsShiftPressed: (value: boolean) => void
8
+ setIsCtrlPressed?: (value: boolean) => void
9
+ modalHandler?: {
10
+ isOpen: boolean
11
+ onSpacebar?: (e: KeyboardEvent) => void
12
+ }
13
+ }
14
+
15
+ // Add functions to update the modal handler state
16
+ export const setModalHandler = (handler: ((e: KeyboardEvent) => void) | undefined, open: boolean) => {
17
+ if (debugLog) {
18
+ console.log('setModalHandler called', {
19
+ hasHandler: !!handler,
20
+ open,
21
+ previousState: {
22
+ hadHandler: !!currentModalHandler,
23
+ wasOpen: isModalOpen
24
+ }
25
+ })
26
+ }
27
+
28
+ currentModalHandler = handler
29
+ isModalOpen = open
30
+ }
31
+
32
+ export const setupKeyboardHandlers = (state: KeyboardState) => {
33
+ const handlerId = Math.random().toString(36).substr(2, 9)
34
+ if (debugLog) {
35
+ console.log(`Setting up keyboard handlers [${handlerId}]`)
36
+ }
37
+
38
+ // Function to reset modifier key states
39
+ const resetModifierStates = () => {
40
+ if (debugLog) {
41
+ console.log(`Resetting modifier states [${handlerId}]`)
42
+ }
43
+ state.setIsShiftPressed(false)
44
+ state.setIsCtrlPressed?.(false)
45
+ document.body.style.userSelect = ''
46
+ }
47
+
48
+ const handleKeyDown = (e: KeyboardEvent) => {
49
+ if (debugLog) {
50
+ console.log(`Keyboard event captured [${handlerId}]`, {
51
+ key: e.key,
52
+ code: e.code,
53
+ type: e.type,
54
+ target: e.target,
55
+ currentTarget: e.currentTarget,
56
+ eventPhase: e.eventPhase,
57
+ isModalOpen,
58
+ hasModalHandler: !!currentModalHandler
59
+ })
60
+ }
61
+
62
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
63
+ if (debugLog) {
64
+ console.log(`[${handlerId}] Ignoring keydown in input/textarea`)
65
+ }
66
+ return
67
+ }
68
+
69
+ if (e.key === 'Shift') {
70
+ state.setIsShiftPressed(true)
71
+ document.body.style.userSelect = 'none'
72
+ } else if (e.key === 'Control' || e.key === 'Ctrl' || e.key === 'Meta') {
73
+ state.setIsCtrlPressed?.(true)
74
+ } else if (e.key === ' ' || e.code === 'Space') {
75
+ if (debugLog) {
76
+ console.log('Keyboard handler - Spacebar pressed down', {
77
+ modalOpen: isModalOpen,
78
+ hasModalHandler: !!currentModalHandler,
79
+ hasGlobalToggle: !!window.toggleAudioPlayback,
80
+ target: e.target,
81
+ eventPhase: e.eventPhase,
82
+ handlerFunction: currentModalHandler?.toString().slice(0, 100)
83
+ })
84
+ }
85
+
86
+ e.preventDefault()
87
+
88
+ if (isModalOpen && currentModalHandler) {
89
+ currentModalHandler(e)
90
+ } else if (window.toggleAudioPlayback && !isModalOpen) {
91
+ if (debugLog) {
92
+ console.log('Keyboard handler - Using global audio toggle')
93
+ }
94
+ window.toggleAudioPlayback()
95
+ }
96
+ }
97
+ }
98
+
99
+ const handleKeyUp = (e: KeyboardEvent) => {
100
+ if (debugLog) {
101
+ console.log(`Keyboard up event captured [${handlerId}]`, {
102
+ key: e.key,
103
+ code: e.code,
104
+ type: e.type,
105
+ target: e.target,
106
+ eventPhase: e.eventPhase,
107
+ isModalOpen,
108
+ hasModalHandler: !!currentModalHandler
109
+ })
110
+ }
111
+
112
+ // Ignore keyup events in input and textarea elements
113
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
114
+ if (debugLog) {
115
+ console.log(`[${handlerId}] Ignoring keyup in input/textarea`)
116
+ }
117
+ return
118
+ }
119
+
120
+ // Always reset the modifier states regardless of the key which was released
121
+ // to help prevent accidentally getting stuck in a mode or accidentally deleting words
122
+ resetModifierStates()
123
+
124
+ if (e.key === ' ' || e.code === 'Space') {
125
+ if (debugLog) {
126
+ console.log('Keyboard handler - Spacebar released', {
127
+ modalOpen: isModalOpen,
128
+ hasModalHandler: !!currentModalHandler,
129
+ target: e.target,
130
+ eventPhase: e.eventPhase
131
+ })
132
+ }
133
+
134
+ e.preventDefault()
135
+
136
+ if (isModalOpen && currentModalHandler) {
137
+ currentModalHandler(e)
138
+ }
139
+ }
140
+ }
141
+
142
+ // Handle window blur event (user switches tabs or apps)
143
+ const handleWindowBlur = () => {
144
+ if (debugLog) {
145
+ console.log(`Window blur detected [${handlerId}], resetting modifier states`)
146
+ }
147
+ resetModifierStates()
148
+ }
149
+
150
+ // Handle window focus event (user returns to the app)
151
+ const handleWindowFocus = () => {
152
+ if (debugLog) {
153
+ console.log(`Window focus detected [${handlerId}], ensuring modifier states are reset`)
154
+ }
155
+ resetModifierStates()
156
+ }
157
+
158
+ // Add window event listeners
159
+ window.addEventListener('blur', handleWindowBlur)
160
+ window.addEventListener('focus', handleWindowFocus)
161
+
162
+ // Return a cleanup function that includes removing the window event listeners
163
+ return {
164
+ handleKeyDown,
165
+ handleKeyUp,
166
+ cleanup: () => {
167
+ window.removeEventListener('blur', handleWindowBlur)
168
+ window.removeEventListener('focus', handleWindowFocus)
169
+ }
170
+ }
171
+ }
172
+
173
+ // Export these for external use
174
+ export const getModalState = () => ({
175
+ currentModalHandler,
176
+ isModalOpen
177
+ })
@@ -0,0 +1,78 @@
1
+ import { CorrectionData, LyricsSegment } from '../../../types'
2
+
3
+ // Change the key generation to use a hash of the first segment's text instead
4
+ export const generateStorageKey = (data: CorrectionData): string => {
5
+ const text = data.original_segments[0]?.text || ''
6
+ let hash = 0
7
+ for (let i = 0; i < text.length; i++) {
8
+ const char = text.charCodeAt(i)
9
+ hash = ((hash << 5) - hash) + char
10
+ hash = hash & hash // Convert to 32-bit integer
11
+ }
12
+ return `song_${hash}`
13
+ }
14
+
15
+ const stripIds = (obj: CorrectionData): LyricsSegment[] => {
16
+ const clone = JSON.parse(JSON.stringify(obj))
17
+ return clone.corrected_segments.map((segment: LyricsSegment) => {
18
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
19
+ const { id: _id, ...strippedSegment } = segment
20
+ return {
21
+ ...strippedSegment,
22
+ words: segment.words.map(word => {
23
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
24
+ const { id: _wordId, ...strippedWord } = word
25
+ return strippedWord
26
+ })
27
+ }
28
+ })
29
+ }
30
+
31
+ export const loadSavedData = (initialData: CorrectionData): CorrectionData | null => {
32
+ const storageKey = generateStorageKey(initialData)
33
+ const savedDataStr = localStorage.getItem('lyrics_analyzer_data')
34
+ const savedDataObj = savedDataStr ? JSON.parse(savedDataStr) : {}
35
+
36
+ if (savedDataObj[storageKey]) {
37
+ try {
38
+ const parsed = savedDataObj[storageKey]
39
+ // Compare first segment text instead of transcribed_text
40
+ if (parsed.original_segments[0]?.text === initialData.original_segments[0]?.text) {
41
+ const strippedSaved = stripIds(parsed)
42
+ const strippedInitial = stripIds(initialData)
43
+ const hasChanges = JSON.stringify(strippedSaved) !== JSON.stringify(strippedInitial)
44
+
45
+ if (hasChanges) {
46
+ return parsed
47
+ } else {
48
+ // Clean up storage if no changes
49
+ delete savedDataObj[storageKey]
50
+ localStorage.setItem('lyrics_analyzer_data', JSON.stringify(savedDataObj))
51
+ }
52
+ }
53
+ } catch (error) {
54
+ console.error('Failed to parse saved data:', error)
55
+ delete savedDataObj[storageKey]
56
+ localStorage.setItem('lyrics_analyzer_data', JSON.stringify(savedDataObj))
57
+ }
58
+ }
59
+ return null
60
+ }
61
+
62
+ export const saveData = (data: CorrectionData, initialData: CorrectionData): void => {
63
+ const storageKey = generateStorageKey(initialData)
64
+ const savedDataStr = localStorage.getItem('lyrics_analyzer_data')
65
+ const savedDataObj = savedDataStr ? JSON.parse(savedDataStr) : {}
66
+
67
+ savedDataObj[storageKey] = data
68
+ localStorage.setItem('lyrics_analyzer_data', JSON.stringify(savedDataObj))
69
+ }
70
+
71
+ export const clearSavedData = (data: CorrectionData): void => {
72
+ const storageKey = generateStorageKey(data)
73
+ const savedDataStr = localStorage.getItem('lyrics_analyzer_data')
74
+ const savedDataObj = savedDataStr ? JSON.parse(savedDataStr) : {}
75
+
76
+ delete savedDataObj[storageKey]
77
+ localStorage.setItem('lyrics_analyzer_data', JSON.stringify(savedDataObj))
78
+ }
@@ -0,0 +1,75 @@
1
+ import { AnchorSequence, LyricsSegment } from '../../../types'
2
+ import { LinePosition } from '../types'
3
+
4
+ export function calculateReferenceLinePositions(
5
+ corrected_segments: LyricsSegment[],
6
+ anchors: AnchorSequence[],
7
+ currentSource: string
8
+ ): { linePositions: LinePosition[] } {
9
+ const linePositions: LinePosition[] = []
10
+ let currentReferencePosition = 0
11
+
12
+ // First, find all anchor sequences that cover entire lines
13
+ const fullLineAnchors = anchors?.map(anchor => {
14
+ // Check if we have reference word IDs for this source
15
+ const referenceWordIds = anchor.reference_word_ids[currentSource]
16
+ if (!referenceWordIds?.length) return null
17
+
18
+ return {
19
+ referenceWordIds,
20
+ transcriptionLine: corrected_segments.findIndex((segment) => {
21
+ const wordIds = segment.words.map(w => w.id)
22
+ if (!wordIds.length) return false
23
+
24
+ // Check if all word IDs in this segment are part of the anchor's transcribed word IDs
25
+ return wordIds.every(id =>
26
+ anchor.transcribed_word_ids.includes(id)
27
+ )
28
+ })
29
+ }
30
+ })?.filter((a): a is NonNullable<typeof a> => a !== null) ?? []
31
+
32
+ // Sort by first reference word ID to process in order
33
+ fullLineAnchors.sort((a, b) => {
34
+ const firstIdA = a.referenceWordIds[0]
35
+ const firstIdB = b.referenceWordIds[0]
36
+ return firstIdA.localeCompare(firstIdB)
37
+ })
38
+
39
+ // Add line positions with padding
40
+ let currentLine = 0
41
+ fullLineAnchors.forEach(anchor => {
42
+ // Add empty lines if needed to match transcription line number
43
+ while (currentLine < anchor.transcriptionLine) {
44
+ linePositions.push({
45
+ position: currentReferencePosition,
46
+ lineNumber: currentLine,
47
+ isEmpty: false
48
+ })
49
+ currentReferencePosition += 1
50
+ currentLine++
51
+ }
52
+
53
+ // Add the actual line position
54
+ linePositions.push({
55
+ position: currentReferencePosition,
56
+ lineNumber: currentLine,
57
+ isEmpty: false
58
+ })
59
+ currentLine++
60
+ currentReferencePosition++
61
+ })
62
+
63
+ // Add any remaining lines after the last anchor
64
+ while (currentLine < corrected_segments.length) {
65
+ linePositions.push({
66
+ position: currentReferencePosition,
67
+ lineNumber: currentLine,
68
+ isEmpty: false
69
+ })
70
+ currentReferencePosition += 1
71
+ currentLine++
72
+ }
73
+
74
+ return { linePositions }
75
+ }