lyrics-transcriber 0.68.0__py3-none-any.whl → 0.69.0__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.
- lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md +210 -0
- lyrics_transcriber/frontend/package.json +1 -1
- lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +4 -2
- lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +257 -134
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +35 -237
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +27 -3
- lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +688 -0
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +29 -18
- lyrics_transcriber/frontend/src/hooks/useManualSync.ts +198 -30
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
- lyrics_transcriber/frontend/web_assets/assets/{index-D7BQUJXK.js → index-izP9z1oB.js} +985 -327
- lyrics_transcriber/frontend/web_assets/assets/index-izP9z1oB.js.map +1 -0
- lyrics_transcriber/frontend/web_assets/index.html +1 -1
- {lyrics_transcriber-0.68.0.dist-info → lyrics_transcriber-0.69.0.dist-info}/METADATA +1 -1
- {lyrics_transcriber-0.68.0.dist-info → lyrics_transcriber-0.69.0.dist-info}/RECORD +18 -16
- lyrics_transcriber/frontend/web_assets/assets/index-D7BQUJXK.js.map +0 -1
- {lyrics_transcriber-0.68.0.dist-info → lyrics_transcriber-0.69.0.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.68.0.dist-info → lyrics_transcriber-0.69.0.dist-info}/WHEEL +0 -0
- {lyrics_transcriber-0.68.0.dist-info → lyrics_transcriber-0.69.0.dist-info}/entry_points.txt +0 -0
@@ -16,6 +16,7 @@ import TranscriptionView from './TranscriptionView'
|
|
16
16
|
import { WordClickInfo, FlashType } from './shared/types'
|
17
17
|
import EditModal from './EditModal'
|
18
18
|
import ReviewChangesModal from './ReviewChangesModal'
|
19
|
+
import ReplaceAllLyricsModal from './ReplaceAllLyricsModal'
|
19
20
|
import {
|
20
21
|
addSegmentBefore,
|
21
22
|
splitSegment,
|
@@ -39,6 +40,8 @@ declare global {
|
|
39
40
|
interface Window {
|
40
41
|
toggleAudioPlayback?: () => void;
|
41
42
|
seekAndPlayAudio?: (startTime: number) => void;
|
43
|
+
getAudioDuration?: () => number;
|
44
|
+
isAudioPlaying?: boolean;
|
42
45
|
}
|
43
46
|
}
|
44
47
|
|
@@ -265,11 +268,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
265
268
|
index: number
|
266
269
|
originalSegment: LyricsSegment
|
267
270
|
} | null>(null)
|
268
|
-
const [
|
269
|
-
const [globalEditSegment, setGlobalEditSegment] = useState<LyricsSegment | null>(null)
|
270
|
-
const [originalGlobalSegment, setOriginalGlobalSegment] = useState<LyricsSegment | null>(null)
|
271
|
-
const [originalTranscribedGlobalSegment, setOriginalTranscribedGlobalSegment] = useState<LyricsSegment | null>(null)
|
272
|
-
const [isLoadingGlobalEdit, setIsLoadingGlobalEdit] = useState(false)
|
271
|
+
const [isReplaceAllLyricsModalOpen, setIsReplaceAllLyricsModalOpen] = useState(false)
|
273
272
|
const [isReviewModalOpen, setIsReviewModalOpen] = useState(false)
|
274
273
|
const [currentAudioTime, setCurrentAudioTime] = useState(0)
|
275
274
|
const [isUpdatingHandlers, setIsUpdatingHandlers] = useState(false)
|
@@ -397,11 +396,11 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
397
396
|
isReviewModalOpen ||
|
398
397
|
isAddLyricsModalOpen ||
|
399
398
|
isFindReplaceModalOpen ||
|
400
|
-
|
399
|
+
isReplaceAllLyricsModalOpen ||
|
401
400
|
isTimingOffsetModalOpen
|
402
401
|
)
|
403
402
|
setIsAnyModalOpen(modalOpen)
|
404
|
-
}, [modalContent, editModalSegment, isReviewModalOpen, isAddLyricsModalOpen, isFindReplaceModalOpen,
|
403
|
+
}, [modalContent, editModalSegment, isReviewModalOpen, isAddLyricsModalOpen, isFindReplaceModalOpen, isReplaceAllLyricsModalOpen, isTimingOffsetModalOpen])
|
405
404
|
|
406
405
|
// Calculate effective mode based on modifier key states
|
407
406
|
const effectiveMode = isCtrlPressed ? 'delete_word' : (isShiftPressed ? 'highlight' : interactionMode)
|
@@ -783,212 +782,33 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
783
782
|
updateDataWithHistory(newData, 'find/replace'); // Update history
|
784
783
|
}
|
785
784
|
|
786
|
-
// Add handler for Edit All functionality
|
787
|
-
const handleEditAll = useCallback(() => {
|
788
|
-
console.log('EditAll - Starting process');
|
789
|
-
|
790
|
-
// Create empty placeholder segments to prevent the modal from closing
|
791
|
-
const placeholderSegment: LyricsSegment = {
|
792
|
-
id: 'loading-placeholder',
|
793
|
-
words: [],
|
794
|
-
text: '',
|
795
|
-
start_time: 0,
|
796
|
-
end_time: 1
|
797
|
-
};
|
798
|
-
|
799
|
-
// Set placeholder segments first
|
800
|
-
setGlobalEditSegment(placeholderSegment);
|
801
|
-
setOriginalGlobalSegment(placeholderSegment);
|
802
|
-
|
803
|
-
// Show loading state
|
804
|
-
setIsLoadingGlobalEdit(true);
|
805
|
-
console.log('EditAll - Set loading state to true');
|
806
|
-
|
807
|
-
// Open the modal with placeholder data
|
808
|
-
setIsEditAllModalOpen(true);
|
809
|
-
console.log('EditAll - Set modal open to true');
|
810
|
-
|
811
|
-
// Use requestAnimationFrame to ensure the modal with loading state is rendered
|
812
|
-
// before doing the expensive operation
|
813
|
-
requestAnimationFrame(() => {
|
814
|
-
console.log('EditAll - Inside requestAnimationFrame');
|
815
|
-
|
816
|
-
// Use setTimeout to allow the modal to render before doing the expensive operation
|
817
|
-
setTimeout(() => {
|
818
|
-
console.log('EditAll - Inside setTimeout, starting data processing');
|
819
|
-
|
820
|
-
try {
|
821
|
-
console.time('EditAll - Data processing');
|
822
|
-
|
823
|
-
// Create a combined segment with all words from all segments
|
824
|
-
const allWords = data.corrected_segments.flatMap(segment => segment.words)
|
825
|
-
console.log(`EditAll - Collected ${allWords.length} words from all segments`);
|
826
|
-
|
827
|
-
// Sort words by start time to maintain chronological order
|
828
|
-
const sortedWords = [...allWords].sort((a, b) => {
|
829
|
-
const aTime = a.start_time ?? 0
|
830
|
-
const bTime = b.start_time ?? 0
|
831
|
-
return aTime - bTime
|
832
|
-
})
|
833
|
-
console.log('EditAll - Sorted words by start time');
|
834
|
-
|
835
|
-
// Create a global segment containing all words
|
836
|
-
const globalSegment: LyricsSegment = {
|
837
|
-
id: 'global-edit',
|
838
|
-
words: sortedWords,
|
839
|
-
text: sortedWords.map(w => w.text).join(' '),
|
840
|
-
start_time: sortedWords[0]?.start_time ?? null,
|
841
|
-
end_time: sortedWords[sortedWords.length - 1]?.end_time ?? null
|
842
|
-
}
|
843
|
-
console.log('EditAll - Created global segment');
|
844
|
-
|
845
|
-
// Store the original global segment for reset functionality
|
846
|
-
setGlobalEditSegment(globalSegment)
|
847
|
-
console.log('EditAll - Set global edit segment');
|
848
|
-
|
849
|
-
setOriginalGlobalSegment(JSON.parse(JSON.stringify(globalSegment)))
|
850
|
-
console.log('EditAll - Set original global segment');
|
851
|
-
|
852
|
-
// Create the original transcribed global segment for Un-Correct functionality
|
853
|
-
if (originalData.original_segments) {
|
854
|
-
console.log('EditAll - Processing original segments for Un-Correct functionality');
|
855
|
-
|
856
|
-
// Get all words from original segments
|
857
|
-
const originalWords = originalData.original_segments.flatMap((segment: LyricsSegment) => segment.words)
|
858
|
-
console.log(`EditAll - Collected ${originalWords.length} words from original segments`);
|
859
|
-
|
860
|
-
// Sort words by start time
|
861
|
-
const sortedOriginalWords = [...originalWords].sort((a, b) => {
|
862
|
-
const aTime = a.start_time ?? 0
|
863
|
-
const bTime = b.start_time ?? 0
|
864
|
-
return aTime - bTime
|
865
|
-
})
|
866
|
-
console.log('EditAll - Sorted original words by start time');
|
867
|
-
|
868
|
-
// Create the original transcribed global segment
|
869
|
-
const originalTranscribedGlobal: LyricsSegment = {
|
870
|
-
id: 'original-transcribed-global',
|
871
|
-
words: sortedOriginalWords,
|
872
|
-
text: sortedOriginalWords.map(w => w.text).join(' '),
|
873
|
-
start_time: sortedOriginalWords[0]?.start_time ?? null,
|
874
|
-
end_time: sortedOriginalWords[sortedOriginalWords.length - 1]?.end_time ?? null
|
875
|
-
}
|
876
|
-
console.log('EditAll - Created original transcribed global segment');
|
877
|
-
|
878
|
-
setOriginalTranscribedGlobalSegment(originalTranscribedGlobal)
|
879
|
-
console.log('EditAll - Set original transcribed global segment');
|
880
|
-
} else {
|
881
|
-
setOriginalTranscribedGlobalSegment(null)
|
882
|
-
console.log('EditAll - No original segments found, set original transcribed global segment to null');
|
883
|
-
}
|
884
|
-
|
885
|
-
console.timeEnd('EditAll - Data processing');
|
886
|
-
} catch (error) {
|
887
|
-
console.error('Error preparing global edit data:', error);
|
888
|
-
} finally {
|
889
|
-
// Clear loading state
|
890
|
-
console.log('EditAll - Finished processing, setting loading state to false');
|
891
|
-
setIsLoadingGlobalEdit(false);
|
892
|
-
}
|
893
|
-
}, 100); // Small delay to allow the modal to render
|
894
|
-
});
|
895
|
-
}, [data.corrected_segments, originalData.original_segments])
|
896
|
-
|
897
|
-
// Handle saving the global edit
|
898
|
-
const handleSaveGlobalEdit = useCallback((updatedSegment: LyricsSegment) => {
|
899
|
-
console.log('Global Edit - Saving with new approach:', {
|
900
|
-
updatedSegmentId: updatedSegment.id,
|
901
|
-
wordCount: updatedSegment.words.length,
|
902
|
-
originalSegmentCount: data.corrected_segments.length,
|
903
|
-
originalTotalWordCount: data.corrected_segments.reduce((count, segment) => count + segment.words.length, 0)
|
904
|
-
})
|
905
785
|
|
906
|
-
// Get the updated words from the global segment
|
907
|
-
const updatedWords = updatedSegment.words
|
908
786
|
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
for (const segment of data.corrected_segments) {
|
915
|
-
const originalWordCount = segment.words.length
|
916
|
-
|
917
|
-
// Get the words for this segment from the updated global segment
|
918
|
-
const segmentWords = []
|
919
|
-
const endIndex = Math.min(wordIndex + originalWordCount, updatedWords.length)
|
920
|
-
|
921
|
-
for (let i = wordIndex; i < endIndex; i++) {
|
922
|
-
segmentWords.push(updatedWords[i])
|
923
|
-
}
|
924
|
-
|
925
|
-
// Update the word index for the next segment
|
926
|
-
wordIndex = endIndex
|
927
|
-
|
928
|
-
// If we have words for this segment, create an updated segment
|
929
|
-
if (segmentWords.length > 0) {
|
930
|
-
// Recalculate segment start and end times
|
931
|
-
const validStartTimes = segmentWords.map(w => w.start_time).filter((t): t is number => t !== null)
|
932
|
-
const validEndTimes = segmentWords.map(w => w.end_time).filter((t): t is number => t !== null)
|
933
|
-
|
934
|
-
const segmentStartTime = validStartTimes.length > 0 ? Math.min(...validStartTimes) : null
|
935
|
-
const segmentEndTime = validEndTimes.length > 0 ? Math.max(...validEndTimes) : null
|
936
|
-
|
937
|
-
// Create the updated segment
|
938
|
-
updatedSegments.push({
|
939
|
-
...segment,
|
940
|
-
words: segmentWords,
|
941
|
-
text: segmentWords.map(w => w.text).join(' '),
|
942
|
-
start_time: segmentStartTime,
|
943
|
-
end_time: segmentEndTime
|
944
|
-
})
|
945
|
-
}
|
946
|
-
}
|
947
|
-
|
948
|
-
// If there are any remaining words, add them to the last segment
|
949
|
-
if (wordIndex < updatedWords.length) {
|
950
|
-
const remainingWords = updatedWords.slice(wordIndex)
|
951
|
-
const lastSegment = updatedSegments[updatedSegments.length - 1]
|
952
|
-
|
953
|
-
// Combine the remaining words with the last segment
|
954
|
-
const combinedWords = [...lastSegment.words, ...remainingWords]
|
955
|
-
|
956
|
-
// Recalculate segment start and end times
|
957
|
-
const validStartTimes = combinedWords.map(w => w.start_time).filter((t): t is number => t !== null)
|
958
|
-
const validEndTimes = combinedWords.map(w => w.end_time).filter((t): t is number => t !== null)
|
959
|
-
|
960
|
-
const segmentStartTime = validStartTimes.length > 0 ? Math.min(...validStartTimes) : null
|
961
|
-
const segmentEndTime = validEndTimes.length > 0 ? Math.max(...validEndTimes) : null
|
962
|
-
|
963
|
-
// Update the last segment
|
964
|
-
updatedSegments[updatedSegments.length - 1] = {
|
965
|
-
...lastSegment,
|
966
|
-
words: combinedWords,
|
967
|
-
text: combinedWords.map(w => w.text).join(' '),
|
968
|
-
start_time: segmentStartTime,
|
969
|
-
end_time: segmentEndTime
|
970
|
-
}
|
971
|
-
}
|
787
|
+
// Add handler for Replace All Lyrics functionality
|
788
|
+
const handleReplaceAllLyrics = useCallback(() => {
|
789
|
+
console.log('ReplaceAllLyrics - Opening modal')
|
790
|
+
setIsReplaceAllLyricsModalOpen(true)
|
791
|
+
}, [])
|
972
792
|
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
793
|
+
// Handle saving new segments from Replace All Lyrics
|
794
|
+
const handleSaveReplaceAllLyrics = useCallback((newSegments: LyricsSegment[]) => {
|
795
|
+
console.log('ReplaceAllLyrics - Saving new segments:', {
|
796
|
+
segmentCount: newSegments.length,
|
797
|
+
totalWords: newSegments.reduce((count, segment) => count + segment.words.length, 0)
|
978
798
|
})
|
979
799
|
|
980
|
-
//
|
800
|
+
// Create new data with the replaced segments
|
981
801
|
const newData = {
|
982
802
|
...data,
|
983
|
-
corrected_segments:
|
984
|
-
}
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
setIsEditAllModalOpen(false)
|
989
|
-
setGlobalEditSegment(null)
|
803
|
+
corrected_segments: newSegments
|
804
|
+
}
|
805
|
+
|
806
|
+
updateDataWithHistory(newData, 'replace all lyrics')
|
807
|
+
setIsReplaceAllLyricsModalOpen(false)
|
990
808
|
}, [data, updateDataWithHistory])
|
991
809
|
|
810
|
+
|
811
|
+
|
992
812
|
// Undo/Redo handlers
|
993
813
|
const handleUndo = useCallback(() => {
|
994
814
|
if (historyIndex > 0) {
|
@@ -1087,7 +907,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
1087
907
|
isUpdatingHandlers={isUpdatingHandlers}
|
1088
908
|
onHandlerClick={handleHandlerClick}
|
1089
909
|
onFindReplace={() => setIsFindReplaceModalOpen(true)}
|
1090
|
-
onEditAll={
|
910
|
+
onEditAll={handleReplaceAllLyrics}
|
1091
911
|
onTimingOffset={handleOpenTimingOffsetModal}
|
1092
912
|
timingOffsetMs={timingOffsetMs}
|
1093
913
|
onUndo={handleUndo}
|
@@ -1162,38 +982,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
1162
982
|
</Grid>
|
1163
983
|
</Grid>
|
1164
984
|
|
1165
|
-
|
1166
|
-
open={isEditAllModalOpen}
|
1167
|
-
onClose={() => {
|
1168
|
-
setIsEditAllModalOpen(false)
|
1169
|
-
setGlobalEditSegment(null)
|
1170
|
-
setOriginalGlobalSegment(null)
|
1171
|
-
setOriginalTranscribedGlobalSegment(null)
|
1172
|
-
handleSetModalSpacebarHandler(undefined)
|
1173
|
-
}}
|
1174
|
-
segment={globalEditSegment ?
|
1175
|
-
timingOffsetMs !== 0 ?
|
1176
|
-
applyOffsetToSegment(globalEditSegment, timingOffsetMs) :
|
1177
|
-
globalEditSegment :
|
1178
|
-
null}
|
1179
|
-
segmentIndex={null}
|
1180
|
-
originalSegment={originalGlobalSegment ?
|
1181
|
-
timingOffsetMs !== 0 ?
|
1182
|
-
applyOffsetToSegment(originalGlobalSegment, timingOffsetMs) :
|
1183
|
-
originalGlobalSegment :
|
1184
|
-
null}
|
1185
|
-
onSave={handleSaveGlobalEdit}
|
1186
|
-
onPlaySegment={handlePlaySegment}
|
1187
|
-
currentTime={currentAudioTime}
|
1188
|
-
setModalSpacebarHandler={handleSetModalSpacebarHandler}
|
1189
|
-
originalTranscribedSegment={originalTranscribedGlobalSegment ?
|
1190
|
-
timingOffsetMs !== 0 ?
|
1191
|
-
applyOffsetToSegment(originalTranscribedGlobalSegment, timingOffsetMs) :
|
1192
|
-
originalTranscribedGlobalSegment :
|
1193
|
-
null}
|
1194
|
-
isGlobal={true}
|
1195
|
-
isLoading={isLoadingGlobalEdit}
|
1196
|
-
/>
|
985
|
+
|
1197
986
|
|
1198
987
|
<EditModal
|
1199
988
|
open={Boolean(editModalSegment)}
|
@@ -1266,6 +1055,15 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
1266
1055
|
currentOffset={timingOffsetMs}
|
1267
1056
|
onApply={handleApplyTimingOffset}
|
1268
1057
|
/>
|
1058
|
+
|
1059
|
+
<ReplaceAllLyricsModal
|
1060
|
+
open={isReplaceAllLyricsModalOpen}
|
1061
|
+
onClose={() => setIsReplaceAllLyricsModalOpen(false)}
|
1062
|
+
onSave={handleSaveReplaceAllLyrics}
|
1063
|
+
onPlaySegment={handlePlaySegment}
|
1064
|
+
currentTime={currentAudioTime}
|
1065
|
+
setModalSpacebarHandler={handleSetModalSpacebarHandler}
|
1066
|
+
/>
|
1269
1067
|
</Box>
|
1270
1068
|
)
|
1271
1069
|
}
|
@@ -174,12 +174,36 @@ export default function ReferenceView({
|
|
174
174
|
navigator.clipboard.writeText(text);
|
175
175
|
};
|
176
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
|
+
|
177
185
|
return (
|
178
186
|
<Paper sx={{ p: 0.8, position: 'relative' }}>
|
179
187
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 0.5 }}>
|
180
|
-
<
|
181
|
-
|
182
|
-
|
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>
|
183
207
|
<SourceSelector
|
184
208
|
availableSources={availableSources}
|
185
209
|
currentSource={effectiveCurrentSource}
|