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,51 @@
1
+ import React from "react";
2
+
3
+ type Metrics = {
4
+ timeRange: string;
5
+ totalSessions: number;
6
+ averageAccuracy: number;
7
+ errorReduction: number;
8
+ averageProcessingTime: number;
9
+ modelPerformance: { counts: Record<string, number>; avgLatencyMs: Record<string, number>; fallbacks: number };
10
+ costSummary: Record<string, number>;
11
+ userSatisfaction: number;
12
+ };
13
+
14
+ type Props = { metrics: Metrics };
15
+
16
+ export const MetricsDashboard: React.FC<Props> = ({ metrics }) => {
17
+ const modelIds = Object.keys(metrics.modelPerformance.counts || {});
18
+ return (
19
+ <div>
20
+ <h3>Agentic Metrics</h3>
21
+ <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12 }}>
22
+ <div><b>Total Sessions</b><div>{metrics.totalSessions}</div></div>
23
+ <div><b>Avg Proc Time (ms)</b><div>{metrics.averageProcessingTime}</div></div>
24
+ <div><b>Fallbacks</b><div>{metrics.modelPerformance.fallbacks}</div></div>
25
+ </div>
26
+ <h4 style={{ marginTop: 16 }}>Per-Model</h4>
27
+ <table style={{ width: "100%", borderCollapse: "collapse" }}>
28
+ <thead>
29
+ <tr>
30
+ <th style={{ textAlign: "left" }}>Model</th>
31
+ <th style={{ textAlign: "right" }}>Count</th>
32
+ <th style={{ textAlign: "right" }}>Avg Latency (ms)</th>
33
+ </tr>
34
+ </thead>
35
+ <tbody>
36
+ {modelIds.map((id) => (
37
+ <tr key={id}>
38
+ <td>{id}</td>
39
+ <td style={{ textAlign: "right" }}>{metrics.modelPerformance.counts[id] || 0}</td>
40
+ <td style={{ textAlign: "right" }}>{metrics.modelPerformance.avgLatencyMs[id] || 0}</td>
41
+ </tr>
42
+ ))}
43
+ </tbody>
44
+ </table>
45
+ </div>
46
+ );
47
+ };
48
+
49
+ export default MetricsDashboard;
50
+
51
+
@@ -0,0 +1,67 @@
1
+ import { ToggleButton, ToggleButtonGroup, Box, Typography, Tooltip } from '@mui/material';
2
+ import HighlightIcon from '@mui/icons-material/Highlight';
3
+ import EditIcon from '@mui/icons-material/Edit';
4
+ import DeleteIcon from '@mui/icons-material/Delete';
5
+ import { InteractionMode } from '../types';
6
+
7
+ interface ModeSelectorProps {
8
+ effectiveMode: InteractionMode;
9
+ onChange: (mode: InteractionMode) => void;
10
+ }
11
+
12
+ export default function ModeSelector({ effectiveMode, onChange }: ModeSelectorProps) {
13
+ return (
14
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.2, height: '32px' }}>
15
+ <Typography variant="body2" color="text.secondary" sx={{ fontSize: '0.8rem' }}>
16
+ Mode:
17
+ </Typography>
18
+ <ToggleButtonGroup
19
+ value={effectiveMode}
20
+ exclusive
21
+ onChange={(_, newMode) => newMode === 'edit' && onChange(newMode)}
22
+ size="small"
23
+ sx={{
24
+ height: '32px',
25
+ '& .MuiToggleButton-root': {
26
+ padding: '3px 8px',
27
+ fontSize: '0.75rem',
28
+ height: '32px'
29
+ }
30
+ }}
31
+ >
32
+ <Tooltip title="Default mode; click words to edit that lyrics segment">
33
+ <ToggleButton
34
+ value="edit"
35
+ >
36
+ <EditIcon sx={{ mr: 0.5, fontSize: '1rem' }} />
37
+ Edit
38
+ </ToggleButton>
39
+ </Tooltip>
40
+
41
+ <Tooltip title="Hold SHIFT and click words to highlight the matching anchor sequence in the reference lyrics">
42
+ <span>
43
+ <ToggleButton
44
+ value="highlight"
45
+ disabled
46
+ >
47
+ <HighlightIcon sx={{ mr: 0.5, fontSize: '1rem' }} />
48
+ Highlight
49
+ </ToggleButton>
50
+ </span>
51
+ </Tooltip>
52
+
53
+ <Tooltip title="Hold CTRL and click words to delete them">
54
+ <span>
55
+ <ToggleButton
56
+ value="delete_word"
57
+ disabled
58
+ >
59
+ <DeleteIcon sx={{ mr: 0.5, fontSize: '1rem' }} />
60
+ Delete
61
+ </ToggleButton>
62
+ </span>
63
+ </Tooltip>
64
+ </ToggleButtonGroup>
65
+ </Box>
66
+ );
67
+ }
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+
3
+ type Props = {
4
+ models: { id: string; name: string; available: boolean }[];
5
+ value?: string;
6
+ onChange: (modelId: string) => void;
7
+ };
8
+
9
+ export const ModelSelector: React.FC<Props> = ({ models, value, onChange }) => {
10
+ return (
11
+ <select value={value} onChange={(e) => onChange(e.target.value)}>
12
+ {models.map((m) => (
13
+ <option key={m.id} value={m.id} disabled={!m.available}>
14
+ {m.name} {m.available ? "" : "(unavailable)"}
15
+ </option>
16
+ ))}
17
+ </select>
18
+ );
19
+ };
20
+
21
+ export default ModelSelector;
22
+
23
+
@@ -0,0 +1,144 @@
1
+ import { Box, Typography, CircularProgress, Alert, Button } from '@mui/material'
2
+ import { useState, useEffect } from 'react'
3
+ import { ApiClient } from '../api'
4
+ import { CorrectionData } from '../types'
5
+ import { applyOffsetToCorrectionData } from './shared/utils/timingUtils'
6
+
7
+ interface PreviewVideoSectionProps {
8
+ apiClient: ApiClient | null
9
+ isModalOpen: boolean
10
+ updatedData: CorrectionData
11
+ videoRef?: React.RefObject<HTMLVideoElement>
12
+ timingOffsetMs?: number
13
+ }
14
+
15
+ export default function PreviewVideoSection({
16
+ apiClient,
17
+ isModalOpen,
18
+ updatedData,
19
+ videoRef,
20
+ timingOffsetMs = 0
21
+ }: PreviewVideoSectionProps) {
22
+ const [previewState, setPreviewState] = useState<{
23
+ status: 'loading' | 'ready' | 'error';
24
+ videoUrl?: string;
25
+ error?: string;
26
+ }>({ status: 'loading' });
27
+
28
+ // Generate preview when modal opens
29
+ useEffect(() => {
30
+ if (isModalOpen && apiClient) {
31
+ const generatePreview = async () => {
32
+ setPreviewState({ status: 'loading' });
33
+ try {
34
+ // Debug logging for timing offset
35
+ console.log(`[TIMING] PreviewVideoSection - Current timing offset: ${timingOffsetMs}ms`);
36
+
37
+ // Apply timing offset if needed
38
+ const dataToPreview = timingOffsetMs !== 0
39
+ ? applyOffsetToCorrectionData(updatedData, timingOffsetMs)
40
+ : updatedData;
41
+
42
+ // Log some example timestamps after potential offset application
43
+ if (dataToPreview.corrected_segments.length > 0) {
44
+ const firstSegment = dataToPreview.corrected_segments[0];
45
+ console.log(`[TIMING] Preview - First segment id: ${firstSegment.id}`);
46
+ console.log(`[TIMING] - start_time: ${firstSegment.start_time}, end_time: ${firstSegment.end_time}`);
47
+
48
+ if (firstSegment.words.length > 0) {
49
+ const firstWord = firstSegment.words[0];
50
+ console.log(`[TIMING] - first word "${firstWord.text}" time: ${firstWord.start_time} -> ${firstWord.end_time}`);
51
+ }
52
+ }
53
+
54
+ const response = await apiClient.generatePreviewVideo(dataToPreview);
55
+
56
+ if (response.status === 'error') {
57
+ setPreviewState({
58
+ status: 'error',
59
+ error: response.message || 'Failed to generate preview video'
60
+ });
61
+ return;
62
+ }
63
+
64
+ if (!response.preview_hash) {
65
+ setPreviewState({
66
+ status: 'error',
67
+ error: 'No preview hash received from server'
68
+ });
69
+ return;
70
+ }
71
+
72
+ const videoUrl = apiClient.getPreviewVideoUrl(response.preview_hash);
73
+ setPreviewState({
74
+ status: 'ready',
75
+ videoUrl
76
+ });
77
+ } catch (error) {
78
+ setPreviewState({
79
+ status: 'error',
80
+ error: (error as Error).message || 'Failed to generate preview video'
81
+ });
82
+ }
83
+ };
84
+
85
+ generatePreview();
86
+ }
87
+ }, [isModalOpen, apiClient, updatedData, timingOffsetMs]);
88
+
89
+ if (!apiClient) return null;
90
+
91
+ return (
92
+ <Box sx={{ mb: 2 }}>
93
+ {previewState.status === 'loading' && (
94
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 2, p: 2 }}>
95
+ <CircularProgress size={24} />
96
+ <Typography>Generating preview video...</Typography>
97
+ </Box>
98
+ )}
99
+
100
+ {previewState.status === 'error' && (
101
+ <Box sx={{ mb: 2 }}>
102
+ <Alert
103
+ severity="error"
104
+ action={
105
+ <Button
106
+ color="inherit"
107
+ size="small"
108
+ onClick={() => {
109
+ // Re-trigger the effect by toggling isModalOpen
110
+ setPreviewState({ status: 'loading' });
111
+ }}
112
+ >
113
+ Retry
114
+ </Button>
115
+ }
116
+ >
117
+ {previewState.error}
118
+ </Alert>
119
+ </Box>
120
+ )}
121
+
122
+ {previewState.status === 'ready' && previewState.videoUrl && (
123
+ <Box sx={{
124
+ width: '100%',
125
+ margin: '0',
126
+ }}>
127
+ <video
128
+ ref={videoRef}
129
+ controls
130
+ autoPlay
131
+ src={previewState.videoUrl}
132
+ style={{
133
+ display: 'block',
134
+ width: '100%',
135
+ height: 'auto',
136
+ }}
137
+ >
138
+ Your browser does not support the video tag.
139
+ </video>
140
+ </Box>
141
+ )}
142
+ </Box>
143
+ );
144
+ }
@@ -0,0 +1,268 @@
1
+ import { useMemo } from 'react'
2
+ import { Paper, Typography, Box, IconButton } from '@mui/material'
3
+ import { ReferenceViewProps } from './shared/types'
4
+ import { calculateReferenceLinePositions } from './shared/utils/referenceLineCalculator'
5
+ import { SourceSelector } from './shared/components/SourceSelector'
6
+ import { HighlightedText } from './shared/components/HighlightedText'
7
+ import { TranscriptionWordPosition } from './shared/types'
8
+ import { getWordsFromIds } from './shared/utils/wordUtils'
9
+ import ContentCopyIcon from '@mui/icons-material/ContentCopy'
10
+ import { styled } from '@mui/material/styles'
11
+
12
+ const SegmentControls = styled(Box)({
13
+ display: 'flex',
14
+ alignItems: 'center',
15
+ gap: '2px',
16
+ paddingTop: '1px',
17
+ paddingRight: '4px'
18
+ })
19
+
20
+ const TextContainer = styled(Box)({
21
+ flex: 1,
22
+ minWidth: 0,
23
+ })
24
+
25
+ export default function ReferenceView({
26
+ referenceSources,
27
+ anchors,
28
+ onElementClick,
29
+ onWordClick,
30
+ flashingType,
31
+ corrected_segments,
32
+ currentSource,
33
+ onSourceChange,
34
+ highlightInfo,
35
+ mode,
36
+ gaps,
37
+ corrections,
38
+ onAddLyrics
39
+ }: ReferenceViewProps) {
40
+ // Get available sources from referenceSources object
41
+ const availableSources = useMemo(() =>
42
+ Object.keys(referenceSources),
43
+ [referenceSources]
44
+ )
45
+
46
+ // Ensure we always have a valid currentSource
47
+ const effectiveCurrentSource = useMemo(() =>
48
+ currentSource || (availableSources.length > 0 ? availableSources[0] : ''),
49
+ [currentSource, availableSources]
50
+ )
51
+
52
+ // Create word positions for the reference view
53
+ const referenceWordPositions = useMemo(() => {
54
+ const positions: TranscriptionWordPosition[] = [];
55
+ const allPositions = new Map<number, TranscriptionWordPosition[]>();
56
+
57
+ // Map anchor words
58
+ anchors?.forEach(anchor => {
59
+ const position = anchor.reference_positions[effectiveCurrentSource];
60
+ if (position === undefined) return;
61
+
62
+ if (!allPositions.has(position)) {
63
+ allPositions.set(position, []);
64
+ }
65
+
66
+ const referenceWords = getWordsFromIds(
67
+ referenceSources[effectiveCurrentSource].segments,
68
+ anchor.reference_word_ids[effectiveCurrentSource] || []
69
+ );
70
+
71
+ referenceWords.forEach(word => {
72
+ const wordPosition: TranscriptionWordPosition = {
73
+ word: {
74
+ id: word.id,
75
+ text: word.text,
76
+ start_time: word.start_time ?? undefined,
77
+ end_time: word.end_time ?? undefined
78
+ },
79
+ type: 'anchor',
80
+ sequence: anchor,
81
+ isInRange: true
82
+ };
83
+ allPositions.get(position)!.push(wordPosition);
84
+ });
85
+ });
86
+
87
+ // Map gap words
88
+ gaps?.forEach(gap => {
89
+ const precedingAnchor = gap.preceding_anchor_id ?
90
+ anchors?.find(a => a.id === gap.preceding_anchor_id) :
91
+ undefined;
92
+ const followingAnchor = gap.following_anchor_id ?
93
+ anchors?.find(a => a.id === gap.following_anchor_id) :
94
+ undefined;
95
+
96
+ const position = precedingAnchor?.reference_positions[effectiveCurrentSource] ??
97
+ followingAnchor?.reference_positions[effectiveCurrentSource];
98
+
99
+ if (position === undefined) return;
100
+
101
+ const gapPosition = precedingAnchor ? position + 1 : position - 1;
102
+
103
+ if (!allPositions.has(gapPosition)) {
104
+ allPositions.set(gapPosition, []);
105
+ }
106
+
107
+ const referenceWords = getWordsFromIds(
108
+ referenceSources[effectiveCurrentSource].segments,
109
+ gap.reference_word_ids[effectiveCurrentSource] || []
110
+ );
111
+
112
+ referenceWords.forEach(word => {
113
+ // Find if this word has a correction
114
+ const isWordCorrected = corrections?.some(correction =>
115
+ correction.reference_positions?.[effectiveCurrentSource]?.toString() === word.id &&
116
+ gap.transcribed_word_ids.includes(correction.word_id)
117
+ );
118
+
119
+ const wordPosition: TranscriptionWordPosition = {
120
+ word: {
121
+ id: word.id,
122
+ text: word.text,
123
+ start_time: word.start_time ?? undefined,
124
+ end_time: word.end_time ?? undefined
125
+ },
126
+ type: 'gap',
127
+ sequence: gap,
128
+ isInRange: true,
129
+ isCorrected: isWordCorrected
130
+ };
131
+ allPositions.get(gapPosition)!.push(wordPosition);
132
+ });
133
+ });
134
+
135
+ // Sort by position and flatten
136
+ Array.from(allPositions.entries())
137
+ .sort(([a], [b]) => a - b)
138
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
139
+ .forEach(([_, words]) => {
140
+ positions.push(...words);
141
+ });
142
+
143
+ return positions;
144
+ }, [anchors, gaps, effectiveCurrentSource, referenceSources, corrections]);
145
+
146
+ const { linePositions } = useMemo(() =>
147
+ calculateReferenceLinePositions(
148
+ corrected_segments,
149
+ anchors,
150
+ effectiveCurrentSource
151
+ ),
152
+ [corrected_segments, anchors, effectiveCurrentSource]
153
+ )
154
+
155
+ // Create a mapping of reference words to their corrections
156
+ const referenceCorrections = useMemo(() => {
157
+ const correctionMap = new Map<string, string>();
158
+
159
+ corrections?.forEach(correction => {
160
+ const referencePosition = correction.reference_positions?.[effectiveCurrentSource];
161
+ if (referencePosition !== undefined) {
162
+ correctionMap.set(referencePosition.toString(), correction.corrected_word);
163
+ }
164
+ });
165
+
166
+ return correctionMap;
167
+ }, [corrections, effectiveCurrentSource]);
168
+
169
+ // Get the segments for the current source
170
+ const currentSourceSegments = referenceSources[effectiveCurrentSource]?.segments || [];
171
+
172
+ // Helper function to copy text to clipboard
173
+ const copyToClipboard = (text: string) => {
174
+ navigator.clipboard.writeText(text);
175
+ };
176
+
177
+ // Helper function to copy all reference text from current source
178
+ const copyAllReferenceText = () => {
179
+ const allText = currentSourceSegments
180
+ .map(segment => segment.words.map(w => w.text).join(' '))
181
+ .join('\n');
182
+ copyToClipboard(allText);
183
+ };
184
+
185
+ return (
186
+ <Paper sx={{ p: 0.8, position: 'relative' }}>
187
+ <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 0.5 }}>
188
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
189
+ <Typography variant="h6" sx={{ fontSize: '0.9rem', mb: 0 }}>
190
+ Reference Lyrics
191
+ </Typography>
192
+ <IconButton
193
+ size="small"
194
+ onClick={copyAllReferenceText}
195
+ sx={{
196
+ padding: '2px',
197
+ height: '20px',
198
+ width: '20px',
199
+ minHeight: '20px',
200
+ minWidth: '20px'
201
+ }}
202
+ title="Copy all reference lyrics"
203
+ >
204
+ <ContentCopyIcon sx={{ fontSize: '1rem' }} />
205
+ </IconButton>
206
+ </Box>
207
+ <SourceSelector
208
+ availableSources={availableSources}
209
+ currentSource={effectiveCurrentSource}
210
+ onSourceChange={onSourceChange}
211
+ onAddLyrics={onAddLyrics}
212
+ />
213
+ </Box>
214
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.2 }}>
215
+ {currentSourceSegments.map((segment, index) => (
216
+ <Box
217
+ key={index}
218
+ sx={{
219
+ display: 'flex',
220
+ alignItems: 'flex-start',
221
+ width: '100%',
222
+ mb: 0,
223
+ '&:hover': {
224
+ backgroundColor: 'rgba(0, 0, 0, 0.03)'
225
+ }
226
+ }}
227
+ >
228
+ <SegmentControls>
229
+ <IconButton
230
+ size="small"
231
+ onClick={() => copyToClipboard(segment.words.map(w => w.text).join(' '))}
232
+ sx={{
233
+ padding: '1px',
234
+ height: '18px',
235
+ width: '18px',
236
+ minHeight: '18px',
237
+ minWidth: '18px'
238
+ }}
239
+ >
240
+ <ContentCopyIcon sx={{ fontSize: '0.9rem' }} />
241
+ </IconButton>
242
+ </SegmentControls>
243
+ <TextContainer>
244
+ <HighlightedText
245
+ wordPositions={referenceWordPositions.filter(wp =>
246
+ segment.words.some(w => w.id === wp.word.id)
247
+ )}
248
+ segments={[segment]}
249
+ anchors={anchors}
250
+ onElementClick={onElementClick}
251
+ onWordClick={onWordClick}
252
+ flashingType={flashingType}
253
+ highlightInfo={highlightInfo}
254
+ mode={mode}
255
+ isReference={true}
256
+ currentSource={effectiveCurrentSource}
257
+ linePositions={linePositions}
258
+ referenceCorrections={referenceCorrections}
259
+ gaps={gaps}
260
+ preserveSegments={true}
261
+ />
262
+ </TextContainer>
263
+ </Box>
264
+ ))}
265
+ </Box>
266
+ </Paper>
267
+ )
268
+ }