karaoke-gen 0.57.0__py3-none-any.whl → 0.71.23__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 +1815 -0
  26. karaoke_gen/video_background_processor.py +351 -0
  27. karaoke_gen-0.71.23.dist-info/METADATA +610 -0
  28. karaoke_gen-0.71.23.dist-info/RECORD +275 -0
  29. {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.23.dist-info}/WHEEL +1 -1
  30. {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.23.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.23.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,256 @@
1
+ import { useState } from 'react'
2
+ import { Paper, Typography, Box, IconButton, ToggleButton, ToggleButtonGroup } from '@mui/material'
3
+ import { TranscriptionViewProps } from './shared/types'
4
+ import { HighlightedText } from './shared/components/HighlightedText'
5
+ import { styled } from '@mui/material/styles'
6
+ import SegmentDetailsModal from './SegmentDetailsModal'
7
+ import { TranscriptionWordPosition } from './shared/types'
8
+ import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline'
9
+ import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'
10
+ import TextFieldsIcon from '@mui/icons-material/TextFields'
11
+ import TimelineIcon from '@mui/icons-material/Timeline'
12
+ import { deleteSegment } from './shared/utils/segmentOperations'
13
+ import DurationTimelineView from './DurationTimelineView'
14
+
15
+ const SegmentIndex = styled(Typography)(({ theme }) => ({
16
+ color: theme.palette.text.secondary,
17
+ width: '1.8em',
18
+ minWidth: '1.8em',
19
+ textAlign: 'right',
20
+ marginRight: theme.spacing(0.8),
21
+ userSelect: 'none',
22
+ fontFamily: 'monospace',
23
+ cursor: 'pointer',
24
+ paddingTop: '1px',
25
+ fontSize: '0.8rem',
26
+ lineHeight: 1.2,
27
+ '&:hover': {
28
+ textDecoration: 'underline',
29
+ },
30
+ }))
31
+
32
+ const TextContainer = styled(Box)({
33
+ flex: 1,
34
+ minWidth: 0,
35
+ })
36
+
37
+ const SegmentControls = styled(Box)({
38
+ display: 'flex',
39
+ alignItems: 'center',
40
+ gap: '2px',
41
+ minWidth: '2.5em',
42
+ paddingTop: '1px',
43
+ paddingRight: '4px'
44
+ })
45
+
46
+ export default function TranscriptionView({
47
+ data,
48
+ onElementClick,
49
+ onWordClick,
50
+ flashingType,
51
+ flashingHandler,
52
+ highlightInfo,
53
+ mode,
54
+ onPlaySegment,
55
+ currentTime = 0,
56
+ anchors = [],
57
+ onDataChange
58
+ }: TranscriptionViewProps) {
59
+ const [selectedSegmentIndex, setSelectedSegmentIndex] = useState<number | null>(null)
60
+ const [viewMode, setViewMode] = useState<'text' | 'duration'>('text')
61
+
62
+ const handleDeleteSegment = (segmentIndex: number) => {
63
+ if (onDataChange) {
64
+ const updatedData = deleteSegment(data, segmentIndex)
65
+ onDataChange(updatedData)
66
+ }
67
+ }
68
+
69
+ return (
70
+ <Paper sx={{ p: 0.8 }}>
71
+ <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 0.5 }}>
72
+ <Typography variant="h6" sx={{ fontSize: '0.9rem', mb: 0 }}>
73
+ Corrected Transcription
74
+ </Typography>
75
+ <ToggleButtonGroup
76
+ value={viewMode}
77
+ exclusive
78
+ onChange={(_, newMode) => newMode && setViewMode(newMode)}
79
+ size="small"
80
+ aria-label="view mode"
81
+ sx={{
82
+ '& .MuiToggleButton-root': {
83
+ px: 1.5,
84
+ py: 0.5,
85
+ fontSize: '0.75rem'
86
+ },
87
+ '& .MuiToggleButton-root.Mui-selected': {
88
+ backgroundColor: 'primary.main',
89
+ color: 'white',
90
+ '&:hover': {
91
+ backgroundColor: 'primary.dark'
92
+ }
93
+ }
94
+ }}
95
+ >
96
+ <ToggleButton value="text" aria-label="text view">
97
+ <TextFieldsIcon fontSize="small" sx={{ mr: 0.5 }} />
98
+ <span>Text</span>
99
+ </ToggleButton>
100
+ <ToggleButton value="duration" aria-label="duration view">
101
+ <TimelineIcon fontSize="small" sx={{ mr: 0.5 }} />
102
+ <span>Timeline</span>
103
+ </ToggleButton>
104
+ </ToggleButtonGroup>
105
+ </Box>
106
+ {viewMode === 'duration' ? (
107
+ <DurationTimelineView
108
+ segments={data.corrected_segments}
109
+ corrections={data.corrections || []}
110
+ anchors={data.anchor_sequences || []}
111
+ gaps={data.gap_sequences || []}
112
+ onWordClick={(wordId) => {
113
+ // Find word in segments
114
+ for (const segment of data.corrected_segments) {
115
+ const word = segment.words.find(w => w.id === wordId)
116
+ if (word) {
117
+ onWordClick?.({
118
+ word_id: wordId,
119
+ type: 'other',
120
+ anchor: undefined,
121
+ gap: undefined
122
+ })
123
+ break
124
+ }
125
+ }
126
+ }}
127
+ />
128
+ ) : (
129
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.2 }}>
130
+ {data.corrected_segments.map((segment, segmentIndex) => {
131
+ const segmentWords: TranscriptionWordPosition[] = segment.words.map(word => {
132
+ // Find if this word is part of a correction
133
+ const correction = data.corrections?.find(c =>
134
+ c.corrected_word_id === word.id ||
135
+ c.word_id === word.id
136
+ )
137
+
138
+ // Find if this word is part of an anchor sequence
139
+ const anchor = data.anchor_sequences?.find(a =>
140
+ a.transcribed_word_ids.includes(word.id)
141
+ )
142
+
143
+ // If not in anchor, check if it belongs to a gap sequence
144
+ const gap = data.gap_sequences?.find(g => {
145
+ // Check transcribed words
146
+ const inTranscribed = g.transcribed_word_ids.includes(word.id)
147
+
148
+ // Check reference words
149
+ const inReference = Object.values(g.reference_word_ids).some(ids =>
150
+ ids.includes(word.id)
151
+ )
152
+
153
+ // Check if this word is a corrected version
154
+ const isCorrection = data.corrections.some(c =>
155
+ (c.corrected_word_id === word.id || c.word_id === word.id) &&
156
+ g.transcribed_word_ids.includes(c.word_id)
157
+ )
158
+
159
+ return inTranscribed || inReference || isCorrection
160
+ })
161
+
162
+ return {
163
+ word: {
164
+ id: word.id,
165
+ text: word.text,
166
+ start_time: word.start_time ?? undefined,
167
+ end_time: word.end_time ?? undefined
168
+ },
169
+ type: anchor ? 'anchor' : gap ? 'gap' : 'other',
170
+ sequence: anchor || gap,
171
+ sequencePosition: anchor?.transcription_position ?? gap?.transcription_position ?? undefined,
172
+ isInRange: true,
173
+ isCorrected: Boolean(correction),
174
+ gap: gap
175
+ }
176
+ })
177
+
178
+ return (
179
+ <Box key={segment.id} sx={{
180
+ display: 'flex',
181
+ alignItems: 'flex-start',
182
+ width: '100%',
183
+ mb: 0,
184
+ '&:hover': {
185
+ backgroundColor: 'rgba(0, 0, 0, 0.03)'
186
+ }
187
+ }}>
188
+ <SegmentControls>
189
+ <SegmentIndex
190
+ variant="body2"
191
+ onClick={() => setSelectedSegmentIndex(segmentIndex)}
192
+ >
193
+ {segmentIndex}
194
+ </SegmentIndex>
195
+ <IconButton
196
+ size="small"
197
+ onClick={() => handleDeleteSegment(segmentIndex)}
198
+ sx={{
199
+ padding: '1px',
200
+ height: '18px',
201
+ width: '18px',
202
+ minHeight: '18px',
203
+ minWidth: '18px'
204
+ }}
205
+ title="Delete segment"
206
+ >
207
+ <DeleteOutlineIcon sx={{ fontSize: '0.9rem', color: 'error.main' }} />
208
+ </IconButton>
209
+ {segment.start_time !== null && (
210
+ <IconButton
211
+ size="small"
212
+ onClick={() => onPlaySegment?.(segment.start_time!)}
213
+ sx={{
214
+ padding: '1px',
215
+ height: '18px',
216
+ width: '18px',
217
+ minHeight: '18px',
218
+ minWidth: '18px'
219
+ }}
220
+ title="Play segment"
221
+ >
222
+ <PlayCircleOutlineIcon sx={{ fontSize: '0.9rem' }} />
223
+ </IconButton>
224
+ )}
225
+ </SegmentControls>
226
+ <TextContainer>
227
+ <HighlightedText
228
+ wordPositions={segmentWords}
229
+ anchors={anchors}
230
+ onElementClick={onElementClick}
231
+ onWordClick={onWordClick}
232
+ flashingType={flashingType}
233
+ flashingHandler={flashingHandler}
234
+ highlightInfo={highlightInfo}
235
+ mode={mode}
236
+ preserveSegments={true}
237
+ currentTime={currentTime}
238
+ gaps={data.gap_sequences}
239
+ corrections={data.corrections}
240
+ />
241
+ </TextContainer>
242
+ </Box>
243
+ )
244
+ })}
245
+ </Box>
246
+ )}
247
+
248
+ <SegmentDetailsModal
249
+ open={selectedSegmentIndex !== null}
250
+ onClose={() => setSelectedSegmentIndex(null)}
251
+ segment={selectedSegmentIndex !== null ? data.corrected_segments[selectedSegmentIndex] : null}
252
+ segmentIndex={selectedSegmentIndex}
253
+ />
254
+ </Paper>
255
+ )
256
+ }
@@ -0,0 +1,187 @@
1
+ import { Box, Button, Typography } from '@mui/material'
2
+ import AddIcon from '@mui/icons-material/Add'
3
+ import MergeIcon from '@mui/icons-material/CallMerge'
4
+ import CallSplitIcon from '@mui/icons-material/CallSplit'
5
+ import { SxProps, Theme } from '@mui/material/styles'
6
+
7
+ interface WordDividerProps {
8
+ onAddWord: () => void
9
+ onMergeWords?: () => void
10
+ onAddSegmentBefore?: () => void
11
+ onAddSegmentAfter?: () => void
12
+ onSplitSegment?: () => void
13
+ onMergeSegment?: () => void
14
+ canMerge?: boolean
15
+ isFirst?: boolean
16
+ isLast?: boolean
17
+ sx?: SxProps<Theme>
18
+ }
19
+
20
+ const buttonTextStyle = {
21
+ color: 'rgba(0, 0, 0, 0.6)',
22
+ fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
23
+ fontWeight: 400,
24
+ fontSize: '0.7rem',
25
+ lineHeight: '1.4375em',
26
+ textTransform: 'none'
27
+ }
28
+
29
+ const buttonBaseStyle = {
30
+ minHeight: 0,
31
+ padding: '2px 8px',
32
+ '& .MuiButton-startIcon': {
33
+ marginRight: 0.5
34
+ },
35
+ '& .MuiSvgIcon-root': {
36
+ fontSize: '1.2rem'
37
+ }
38
+ }
39
+
40
+ export default function WordDivider({
41
+ onAddWord,
42
+ onMergeWords,
43
+ onAddSegmentBefore,
44
+ onAddSegmentAfter,
45
+ onSplitSegment,
46
+ onMergeSegment,
47
+ canMerge = false,
48
+ isFirst = false,
49
+ isLast = false,
50
+ sx = {}
51
+ }: WordDividerProps) {
52
+ return (
53
+ <Box
54
+ sx={{
55
+ display: 'flex',
56
+ alignItems: 'center',
57
+ justifyContent: 'center',
58
+ height: '20px',
59
+ my: -0.5,
60
+ width: '50%',
61
+ backgroundColor: '#fff',
62
+ ...sx
63
+ }}
64
+ >
65
+ <Box sx={{
66
+ display: 'flex',
67
+ alignItems: 'center',
68
+ gap: 1,
69
+ backgroundColor: '#fff',
70
+ padding: '0 8px',
71
+ zIndex: 1
72
+ }}>
73
+ <Button
74
+ onClick={onAddWord}
75
+ title="Add Word"
76
+ size="small"
77
+ startIcon={<AddIcon />}
78
+ sx={{
79
+ ...buttonBaseStyle,
80
+ color: 'primary.main',
81
+ }}
82
+ >
83
+ <Typography sx={buttonTextStyle}>
84
+ Add Word
85
+ </Typography>
86
+ </Button>
87
+ {isFirst && onAddSegmentBefore && onMergeSegment && (
88
+ <>
89
+ <Button
90
+ onClick={onAddSegmentBefore}
91
+ title="Add Segment"
92
+ size="small"
93
+ startIcon={<AddIcon sx={{ transform: 'rotate(90deg)' }} />}
94
+ sx={{
95
+ ...buttonBaseStyle,
96
+ color: 'success.main',
97
+ }}
98
+ >
99
+ <Typography sx={buttonTextStyle}>
100
+ Add Segment
101
+ </Typography>
102
+ </Button>
103
+ <Button
104
+ onClick={onMergeSegment}
105
+ title="Merge with Previous Segment"
106
+ size="small"
107
+ startIcon={<MergeIcon sx={{ transform: 'rotate(90deg)' }} />}
108
+ sx={{
109
+ ...buttonBaseStyle,
110
+ color: 'warning.main',
111
+ }}
112
+ >
113
+ <Typography sx={buttonTextStyle}>
114
+ Merge Segment
115
+ </Typography>
116
+ </Button>
117
+ </>
118
+ )}
119
+ {onMergeWords && !isLast && (
120
+ <Button
121
+ onClick={onMergeWords}
122
+ title="Merge Words"
123
+ size="small"
124
+ startIcon={<MergeIcon sx={{ transform: 'rotate(90deg)' }} />}
125
+ disabled={!canMerge}
126
+ sx={{
127
+ ...buttonBaseStyle,
128
+ color: 'primary.main',
129
+ }}
130
+ >
131
+ <Typography sx={buttonTextStyle}>
132
+ Merge Words
133
+ </Typography>
134
+ </Button>
135
+ )}
136
+ {onSplitSegment && !isLast && (
137
+ <Button
138
+ onClick={onSplitSegment}
139
+ title="Split Segment"
140
+ size="small"
141
+ startIcon={<CallSplitIcon sx={{ transform: 'rotate(90deg)' }} />}
142
+ sx={{
143
+ ...buttonBaseStyle,
144
+ color: 'warning.main',
145
+ }}
146
+ >
147
+ <Typography sx={buttonTextStyle}>
148
+ Split Segment
149
+ </Typography>
150
+ </Button>
151
+ )}
152
+ {isLast && onAddSegmentAfter && onMergeSegment && (
153
+ <>
154
+ <Button
155
+ onClick={onAddSegmentAfter}
156
+ title="Add Segment"
157
+ size="small"
158
+ startIcon={<AddIcon sx={{ transform: 'rotate(90deg)' }} />}
159
+ sx={{
160
+ ...buttonBaseStyle,
161
+ color: 'success.main',
162
+ }}
163
+ >
164
+ <Typography sx={buttonTextStyle}>
165
+ Add Segment
166
+ </Typography>
167
+ </Button>
168
+ <Button
169
+ onClick={onMergeSegment}
170
+ title="Merge with Next Segment"
171
+ size="small"
172
+ startIcon={<MergeIcon sx={{ transform: 'rotate(90deg)' }} />}
173
+ sx={{
174
+ ...buttonBaseStyle,
175
+ color: 'warning.main',
176
+ }}
177
+ >
178
+ <Typography sx={buttonTextStyle}>
179
+ Merge Segment
180
+ </Typography>
181
+ </Button>
182
+ </>
183
+ )}
184
+ </Box>
185
+ </Box>
186
+ )
187
+ }