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.
- karaoke_gen/audio_fetcher.py +461 -0
- karaoke_gen/audio_processor.py +407 -30
- karaoke_gen/config.py +62 -113
- karaoke_gen/file_handler.py +32 -59
- karaoke_gen/karaoke_finalise/karaoke_finalise.py +148 -67
- karaoke_gen/karaoke_gen.py +270 -61
- karaoke_gen/lyrics_processor.py +13 -1
- karaoke_gen/metadata.py +78 -73
- karaoke_gen/pipeline/__init__.py +87 -0
- karaoke_gen/pipeline/base.py +215 -0
- karaoke_gen/pipeline/context.py +230 -0
- karaoke_gen/pipeline/executors/__init__.py +21 -0
- karaoke_gen/pipeline/executors/local.py +159 -0
- karaoke_gen/pipeline/executors/remote.py +257 -0
- karaoke_gen/pipeline/stages/__init__.py +27 -0
- karaoke_gen/pipeline/stages/finalize.py +202 -0
- karaoke_gen/pipeline/stages/render.py +165 -0
- karaoke_gen/pipeline/stages/screens.py +139 -0
- karaoke_gen/pipeline/stages/separation.py +191 -0
- karaoke_gen/pipeline/stages/transcription.py +191 -0
- karaoke_gen/style_loader.py +531 -0
- karaoke_gen/utils/bulk_cli.py +6 -0
- karaoke_gen/utils/cli_args.py +424 -0
- karaoke_gen/utils/gen_cli.py +26 -261
- karaoke_gen/utils/remote_cli.py +1815 -0
- karaoke_gen/video_background_processor.py +351 -0
- karaoke_gen-0.71.23.dist-info/METADATA +610 -0
- karaoke_gen-0.71.23.dist-info/RECORD +275 -0
- {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.23.dist-info}/WHEEL +1 -1
- {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.23.dist-info}/entry_points.txt +1 -0
- lyrics_transcriber/__init__.py +10 -0
- lyrics_transcriber/cli/__init__.py +0 -0
- lyrics_transcriber/cli/cli_main.py +285 -0
- lyrics_transcriber/core/__init__.py +0 -0
- lyrics_transcriber/core/config.py +50 -0
- lyrics_transcriber/core/controller.py +520 -0
- lyrics_transcriber/correction/__init__.py +0 -0
- lyrics_transcriber/correction/agentic/__init__.py +9 -0
- lyrics_transcriber/correction/agentic/adapter.py +71 -0
- lyrics_transcriber/correction/agentic/agent.py +313 -0
- lyrics_transcriber/correction/agentic/feedback/aggregator.py +12 -0
- lyrics_transcriber/correction/agentic/feedback/collector.py +17 -0
- lyrics_transcriber/correction/agentic/feedback/retention.py +24 -0
- lyrics_transcriber/correction/agentic/feedback/store.py +76 -0
- lyrics_transcriber/correction/agentic/handlers/__init__.py +24 -0
- lyrics_transcriber/correction/agentic/handlers/ambiguous.py +44 -0
- lyrics_transcriber/correction/agentic/handlers/background_vocals.py +68 -0
- lyrics_transcriber/correction/agentic/handlers/base.py +51 -0
- lyrics_transcriber/correction/agentic/handlers/complex_multi_error.py +46 -0
- lyrics_transcriber/correction/agentic/handlers/extra_words.py +74 -0
- lyrics_transcriber/correction/agentic/handlers/no_error.py +42 -0
- lyrics_transcriber/correction/agentic/handlers/punctuation.py +44 -0
- lyrics_transcriber/correction/agentic/handlers/registry.py +60 -0
- lyrics_transcriber/correction/agentic/handlers/repeated_section.py +44 -0
- lyrics_transcriber/correction/agentic/handlers/sound_alike.py +126 -0
- lyrics_transcriber/correction/agentic/models/__init__.py +5 -0
- lyrics_transcriber/correction/agentic/models/ai_correction.py +31 -0
- lyrics_transcriber/correction/agentic/models/correction_session.py +30 -0
- lyrics_transcriber/correction/agentic/models/enums.py +38 -0
- lyrics_transcriber/correction/agentic/models/human_feedback.py +30 -0
- lyrics_transcriber/correction/agentic/models/learning_data.py +26 -0
- lyrics_transcriber/correction/agentic/models/observability_metrics.py +28 -0
- lyrics_transcriber/correction/agentic/models/schemas.py +46 -0
- lyrics_transcriber/correction/agentic/models/utils.py +19 -0
- lyrics_transcriber/correction/agentic/observability/__init__.py +5 -0
- lyrics_transcriber/correction/agentic/observability/langfuse_integration.py +35 -0
- lyrics_transcriber/correction/agentic/observability/metrics.py +46 -0
- lyrics_transcriber/correction/agentic/observability/performance.py +19 -0
- lyrics_transcriber/correction/agentic/prompts/__init__.py +2 -0
- lyrics_transcriber/correction/agentic/prompts/classifier.py +227 -0
- lyrics_transcriber/correction/agentic/providers/__init__.py +6 -0
- lyrics_transcriber/correction/agentic/providers/base.py +36 -0
- lyrics_transcriber/correction/agentic/providers/circuit_breaker.py +145 -0
- lyrics_transcriber/correction/agentic/providers/config.py +73 -0
- lyrics_transcriber/correction/agentic/providers/constants.py +24 -0
- lyrics_transcriber/correction/agentic/providers/health.py +28 -0
- lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +212 -0
- lyrics_transcriber/correction/agentic/providers/model_factory.py +209 -0
- lyrics_transcriber/correction/agentic/providers/response_cache.py +218 -0
- lyrics_transcriber/correction/agentic/providers/response_parser.py +111 -0
- lyrics_transcriber/correction/agentic/providers/retry_executor.py +127 -0
- lyrics_transcriber/correction/agentic/router.py +35 -0
- lyrics_transcriber/correction/agentic/workflows/__init__.py +5 -0
- lyrics_transcriber/correction/agentic/workflows/consensus_workflow.py +24 -0
- lyrics_transcriber/correction/agentic/workflows/correction_graph.py +59 -0
- lyrics_transcriber/correction/agentic/workflows/feedback_workflow.py +24 -0
- lyrics_transcriber/correction/anchor_sequence.py +1043 -0
- lyrics_transcriber/correction/corrector.py +760 -0
- lyrics_transcriber/correction/feedback/__init__.py +2 -0
- lyrics_transcriber/correction/feedback/schemas.py +107 -0
- lyrics_transcriber/correction/feedback/store.py +236 -0
- lyrics_transcriber/correction/handlers/__init__.py +0 -0
- lyrics_transcriber/correction/handlers/base.py +52 -0
- lyrics_transcriber/correction/handlers/extend_anchor.py +149 -0
- lyrics_transcriber/correction/handlers/levenshtein.py +189 -0
- lyrics_transcriber/correction/handlers/llm.py +293 -0
- lyrics_transcriber/correction/handlers/llm_providers.py +60 -0
- lyrics_transcriber/correction/handlers/no_space_punct_match.py +154 -0
- lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +85 -0
- lyrics_transcriber/correction/handlers/repeat.py +88 -0
- lyrics_transcriber/correction/handlers/sound_alike.py +259 -0
- lyrics_transcriber/correction/handlers/syllables_match.py +252 -0
- lyrics_transcriber/correction/handlers/word_count_match.py +80 -0
- lyrics_transcriber/correction/handlers/word_operations.py +187 -0
- lyrics_transcriber/correction/operations.py +352 -0
- lyrics_transcriber/correction/phrase_analyzer.py +435 -0
- lyrics_transcriber/correction/text_utils.py +30 -0
- lyrics_transcriber/frontend/.gitignore +23 -0
- lyrics_transcriber/frontend/.yarn/releases/yarn-4.7.0.cjs +935 -0
- lyrics_transcriber/frontend/.yarnrc.yml +3 -0
- lyrics_transcriber/frontend/README.md +50 -0
- lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md +210 -0
- lyrics_transcriber/frontend/__init__.py +25 -0
- lyrics_transcriber/frontend/eslint.config.js +28 -0
- lyrics_transcriber/frontend/index.html +18 -0
- lyrics_transcriber/frontend/package.json +42 -0
- lyrics_transcriber/frontend/public/android-chrome-192x192.png +0 -0
- lyrics_transcriber/frontend/public/android-chrome-512x512.png +0 -0
- lyrics_transcriber/frontend/public/apple-touch-icon.png +0 -0
- lyrics_transcriber/frontend/public/favicon-16x16.png +0 -0
- lyrics_transcriber/frontend/public/favicon-32x32.png +0 -0
- lyrics_transcriber/frontend/public/favicon.ico +0 -0
- lyrics_transcriber/frontend/public/nomad-karaoke-logo.png +0 -0
- lyrics_transcriber/frontend/src/App.tsx +212 -0
- lyrics_transcriber/frontend/src/api.ts +239 -0
- lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +77 -0
- lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +114 -0
- lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx +204 -0
- lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +180 -0
- lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +167 -0
- lyrics_transcriber/frontend/src/components/CorrectionAnnotationModal.tsx +359 -0
- lyrics_transcriber/frontend/src/components/CorrectionDetailCard.tsx +281 -0
- lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +162 -0
- lyrics_transcriber/frontend/src/components/DurationTimelineView.tsx +257 -0
- lyrics_transcriber/frontend/src/components/EditActionBar.tsx +68 -0
- lyrics_transcriber/frontend/src/components/EditModal.tsx +702 -0
- lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +496 -0
- lyrics_transcriber/frontend/src/components/EditWordList.tsx +379 -0
- lyrics_transcriber/frontend/src/components/FileUpload.tsx +77 -0
- lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
- lyrics_transcriber/frontend/src/components/Header.tsx +387 -0
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +1373 -0
- lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx +51 -0
- lyrics_transcriber/frontend/src/components/ModeSelector.tsx +67 -0
- lyrics_transcriber/frontend/src/components/ModelSelector.tsx +23 -0
- lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +144 -0
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +268 -0
- lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +688 -0
- lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +354 -0
- lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +64 -0
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +376 -0
- lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +131 -0
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +256 -0
- lyrics_transcriber/frontend/src/components/WordDivider.tsx +187 -0
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +379 -0
- lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +56 -0
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +87 -0
- lyrics_transcriber/frontend/src/components/shared/constants.ts +20 -0
- lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +180 -0
- lyrics_transcriber/frontend/src/components/shared/styles.ts +13 -0
- lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
- lyrics_transcriber/frontend/src/components/shared/types.ts +129 -0
- lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +177 -0
- lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
- lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +75 -0
- lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +360 -0
- lyrics_transcriber/frontend/src/components/shared/utils/timingUtils.ts +110 -0
- lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
- lyrics_transcriber/frontend/src/hooks/useManualSync.ts +435 -0
- lyrics_transcriber/frontend/src/main.tsx +17 -0
- lyrics_transcriber/frontend/src/theme.ts +177 -0
- lyrics_transcriber/frontend/src/types/global.d.ts +9 -0
- lyrics_transcriber/frontend/src/types.js +2 -0
- lyrics_transcriber/frontend/src/types.ts +199 -0
- lyrics_transcriber/frontend/src/validation.ts +132 -0
- lyrics_transcriber/frontend/src/vite-env.d.ts +1 -0
- lyrics_transcriber/frontend/tsconfig.app.json +26 -0
- lyrics_transcriber/frontend/tsconfig.json +25 -0
- lyrics_transcriber/frontend/tsconfig.node.json +23 -0
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -0
- lyrics_transcriber/frontend/update_version.js +11 -0
- lyrics_transcriber/frontend/vite.config.d.ts +2 -0
- lyrics_transcriber/frontend/vite.config.js +10 -0
- lyrics_transcriber/frontend/vite.config.ts +11 -0
- lyrics_transcriber/frontend/web_assets/android-chrome-192x192.png +0 -0
- lyrics_transcriber/frontend/web_assets/android-chrome-512x512.png +0 -0
- lyrics_transcriber/frontend/web_assets/apple-touch-icon.png +0 -0
- lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js +42039 -0
- lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js.map +1 -0
- lyrics_transcriber/frontend/web_assets/favicon-16x16.png +0 -0
- lyrics_transcriber/frontend/web_assets/favicon-32x32.png +0 -0
- lyrics_transcriber/frontend/web_assets/favicon.ico +0 -0
- lyrics_transcriber/frontend/web_assets/index.html +18 -0
- lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.png +0 -0
- lyrics_transcriber/frontend/yarn.lock +3752 -0
- lyrics_transcriber/lyrics/__init__.py +0 -0
- lyrics_transcriber/lyrics/base_lyrics_provider.py +211 -0
- lyrics_transcriber/lyrics/file_provider.py +95 -0
- lyrics_transcriber/lyrics/genius.py +384 -0
- lyrics_transcriber/lyrics/lrclib.py +231 -0
- lyrics_transcriber/lyrics/musixmatch.py +156 -0
- lyrics_transcriber/lyrics/spotify.py +290 -0
- lyrics_transcriber/lyrics/user_input_provider.py +44 -0
- lyrics_transcriber/output/__init__.py +0 -0
- lyrics_transcriber/output/ass/__init__.py +21 -0
- lyrics_transcriber/output/ass/ass.py +2088 -0
- lyrics_transcriber/output/ass/ass_specs.txt +732 -0
- lyrics_transcriber/output/ass/config.py +180 -0
- lyrics_transcriber/output/ass/constants.py +23 -0
- lyrics_transcriber/output/ass/event.py +94 -0
- lyrics_transcriber/output/ass/formatters.py +132 -0
- lyrics_transcriber/output/ass/lyrics_line.py +265 -0
- lyrics_transcriber/output/ass/lyrics_screen.py +252 -0
- lyrics_transcriber/output/ass/section_detector.py +89 -0
- lyrics_transcriber/output/ass/section_screen.py +106 -0
- lyrics_transcriber/output/ass/style.py +187 -0
- lyrics_transcriber/output/cdg.py +619 -0
- lyrics_transcriber/output/cdgmaker/__init__.py +0 -0
- lyrics_transcriber/output/cdgmaker/cdg.py +262 -0
- lyrics_transcriber/output/cdgmaker/composer.py +2260 -0
- lyrics_transcriber/output/cdgmaker/config.py +151 -0
- lyrics_transcriber/output/cdgmaker/images/instrumental.png +0 -0
- lyrics_transcriber/output/cdgmaker/images/intro.png +0 -0
- lyrics_transcriber/output/cdgmaker/pack.py +507 -0
- lyrics_transcriber/output/cdgmaker/render.py +346 -0
- lyrics_transcriber/output/cdgmaker/transitions/centertexttoplogobottomtext.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/circlein.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/circleout.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/fizzle.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/largecentertexttoplogo.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/rectangle.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/spiral.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/topleftmusicalnotes.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipein.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipeleft.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipeout.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wiperight.png +0 -0
- lyrics_transcriber/output/cdgmaker/utils.py +132 -0
- lyrics_transcriber/output/countdown_processor.py +267 -0
- lyrics_transcriber/output/fonts/AvenirNext-Bold.ttf +0 -0
- lyrics_transcriber/output/fonts/DMSans-VariableFont_opsz,wght.ttf +0 -0
- lyrics_transcriber/output/fonts/DMSerifDisplay-Regular.ttf +0 -0
- lyrics_transcriber/output/fonts/Oswald-SemiBold.ttf +0 -0
- lyrics_transcriber/output/fonts/Zurich_Cn_BT_Bold.ttf +0 -0
- lyrics_transcriber/output/fonts/arial.ttf +0 -0
- lyrics_transcriber/output/fonts/georgia.ttf +0 -0
- lyrics_transcriber/output/fonts/verdana.ttf +0 -0
- lyrics_transcriber/output/generator.py +257 -0
- lyrics_transcriber/output/lrc_to_cdg.py +61 -0
- lyrics_transcriber/output/lyrics_file.py +102 -0
- lyrics_transcriber/output/plain_text.py +96 -0
- lyrics_transcriber/output/segment_resizer.py +431 -0
- lyrics_transcriber/output/subtitles.py +397 -0
- lyrics_transcriber/output/video.py +544 -0
- lyrics_transcriber/review/__init__.py +0 -0
- lyrics_transcriber/review/server.py +676 -0
- lyrics_transcriber/storage/__init__.py +0 -0
- lyrics_transcriber/storage/dropbox.py +225 -0
- lyrics_transcriber/transcribers/__init__.py +0 -0
- lyrics_transcriber/transcribers/audioshake.py +290 -0
- lyrics_transcriber/transcribers/base_transcriber.py +157 -0
- lyrics_transcriber/transcribers/whisper.py +330 -0
- lyrics_transcriber/types.py +648 -0
- lyrics_transcriber/utils/__init__.py +0 -0
- lyrics_transcriber/utils/word_utils.py +27 -0
- karaoke_gen-0.57.0.dist-info/METADATA +0 -167
- karaoke_gen-0.57.0.dist-info/RECORD +0 -23
- {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.23.dist-info/licenses}/LICENSE +0 -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,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
|
+
}
|