karaoke-gen 0.71.42__py3-none-any.whl → 0.75.16__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/__init__.py +32 -1
- karaoke_gen/audio_fetcher.py +476 -56
- karaoke_gen/audio_processor.py +11 -3
- karaoke_gen/instrumental_review/server.py +154 -860
- karaoke_gen/instrumental_review/static/index.html +1506 -0
- karaoke_gen/karaoke_finalise/karaoke_finalise.py +62 -1
- karaoke_gen/karaoke_gen.py +114 -1
- karaoke_gen/lyrics_processor.py +81 -4
- karaoke_gen/utils/bulk_cli.py +3 -0
- karaoke_gen/utils/cli_args.py +4 -2
- karaoke_gen/utils/gen_cli.py +196 -5
- karaoke_gen/utils/remote_cli.py +523 -34
- {karaoke_gen-0.71.42.dist-info → karaoke_gen-0.75.16.dist-info}/METADATA +4 -1
- {karaoke_gen-0.71.42.dist-info → karaoke_gen-0.75.16.dist-info}/RECORD +31 -25
- lyrics_transcriber/frontend/package.json +1 -1
- lyrics_transcriber/frontend/src/components/Header.tsx +38 -12
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +17 -3
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/SyncControls.tsx +185 -0
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/TimelineCanvas.tsx +704 -0
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/UpcomingWordsBar.tsx +80 -0
- lyrics_transcriber/frontend/src/components/LyricsSynchronizer/index.tsx +905 -0
- lyrics_transcriber/frontend/src/components/ModeSelectionModal.tsx +127 -0
- lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +190 -542
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
- lyrics_transcriber/frontend/web_assets/assets/{index-DdJTDWH3.js → index-COYImAcx.js} +1722 -489
- lyrics_transcriber/frontend/web_assets/assets/index-COYImAcx.js.map +1 -0
- lyrics_transcriber/frontend/web_assets/index.html +1 -1
- lyrics_transcriber/review/server.py +5 -5
- lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js.map +0 -1
- {karaoke_gen-0.71.42.dist-info → karaoke_gen-0.75.16.dist-info}/WHEEL +0 -0
- {karaoke_gen-0.71.42.dist-info → karaoke_gen-0.75.16.dist-info}/entry_points.txt +0 -0
- {karaoke_gen-0.71.42.dist-info → karaoke_gen-0.75.16.dist-info}/licenses/LICENSE +0 -0
|
@@ -36255,7 +36255,7 @@ const ZoomInIcon = createSvgIcon([/* @__PURE__ */ jsxRuntimeExports.jsx("path",
|
|
|
36255
36255
|
const ZoomOutIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
36256
36256
|
d: "M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14M7 9h5v1H7z"
|
|
36257
36257
|
}), "ZoomOut");
|
|
36258
|
-
const
|
|
36258
|
+
const ArrowBackIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
36259
36259
|
d: "M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20z"
|
|
36260
36260
|
}), "ArrowBack");
|
|
36261
36261
|
const ArrowForwardIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
@@ -36614,7 +36614,7 @@ const TimelineControls = reactExports.memo(({
|
|
|
36614
36614
|
onClick: onScrollLeft,
|
|
36615
36615
|
disabled: visibleStartTime <= startTime,
|
|
36616
36616
|
size: "small",
|
|
36617
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
36617
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowBackIcon, {})
|
|
36618
36618
|
}
|
|
36619
36619
|
) }),
|
|
36620
36620
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Zoom Out (Show More Time)", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -38118,6 +38118,12 @@ function PreviewVideoSection({
|
|
|
38118
38118
|
) })
|
|
38119
38119
|
] });
|
|
38120
38120
|
}
|
|
38121
|
+
const BlockIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38122
|
+
d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2M4 12c0-4.42 3.58-8 8-8 1.85 0 3.55.63 4.9 1.69L5.69 16.9C4.63 15.55 4 13.85 4 12m8 8c-1.85 0-3.55-.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8"
|
|
38123
|
+
}), "Block");
|
|
38124
|
+
const ClearAllIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38125
|
+
d: "M5 13h14v-2H5zm-2 4h14v-2H3zM7 7v2h14V7z"
|
|
38126
|
+
}), "ClearAll");
|
|
38121
38127
|
const CloudUpload = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38122
38128
|
d: "M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96M14 13v4h-4v-4H7l5-5 5 5z"
|
|
38123
38129
|
}), "CloudUpload");
|
|
@@ -38127,6 +38133,9 @@ const ContentPasteIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("pa
|
|
|
38127
38133
|
const EditIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38128
38134
|
d: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.996.996 0 0 0-1.41 0l-1.83 1.83 3.75 3.75z"
|
|
38129
38135
|
}), "Edit");
|
|
38136
|
+
const EditNoteIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38137
|
+
d: "M3 10h11v2H3zm0-2h11V6H3zm0 8h7v-2H3zm15.01-3.13.71-.71c.39-.39 1.02-.39 1.41 0l.71.71c.39.39.39 1.02 0 1.41l-.71.71zm-.71.71-5.3 5.3V21h2.12l5.3-5.3z"
|
|
38138
|
+
}), "EditNote");
|
|
38130
38139
|
const FindReplaceIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38131
38140
|
d: "M11 6c1.38 0 2.63.56 3.54 1.46L12 10h6V4l-2.05 2.05C14.68 4.78 12.93 4 11 4c-3.53 0-6.43 2.61-6.92 6H6.1c.46-2.28 2.48-4 4.9-4m5.64 9.14c.66-.9 1.12-1.97 1.28-3.14H15.9c-.46 2.28-2.48 4-4.9 4-1.38 0-2.63-.56-3.54-1.46L10 12H4v6l2.05-2.05C7.32 17.22 9.07 18 11 18c1.55 0 2.98-.51 4.14-1.36L20 21.49 21.49 20z"
|
|
38132
38141
|
}), "FindReplace");
|
|
@@ -38142,12 +38151,18 @@ const OndemandVideo = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path"
|
|
|
38142
38151
|
const PauseIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38143
38152
|
d: "M6 19h4V5H6zm8-14v14h4V5z"
|
|
38144
38153
|
}), "Pause");
|
|
38154
|
+
const RateReviewIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38155
|
+
d: "M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2M6 14v-2.47l6.88-6.88c.2-.2.51-.2.71 0l1.77 1.77c.2.2.2.51 0 .71L8.47 14zm12 0h-7.5l2-2H18z"
|
|
38156
|
+
}), "RateReview");
|
|
38145
38157
|
const RedoIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38146
38158
|
d: "M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7z"
|
|
38147
38159
|
}), "Redo");
|
|
38148
38160
|
const RestoreIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38149
38161
|
d: "M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9m-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8z"
|
|
38150
38162
|
}), "Restore");
|
|
38163
|
+
const SyncIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38164
|
+
d: "M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8m0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4z"
|
|
38165
|
+
}), "Sync");
|
|
38151
38166
|
const TimerIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38152
38167
|
d: "M9 1h6v2H9zm10.03 6.39 1.42-1.42c-.43-.51-.9-.99-1.41-1.41l-1.42 1.42C16.07 4.74 14.12 4 12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9 9-4.03 9-9c0-2.12-.74-4.07-1.97-5.61M13 14h-2V8h2z"
|
|
38153
38168
|
}), "Timer");
|
|
@@ -38408,7 +38423,7 @@ function ReviewChangesModal({
|
|
|
38408
38423
|
{
|
|
38409
38424
|
onClick: onClose,
|
|
38410
38425
|
color: "warning",
|
|
38411
|
-
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
38426
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowBackIcon, {}),
|
|
38412
38427
|
sx: { mr: "auto" },
|
|
38413
38428
|
children: "Cancel"
|
|
38414
38429
|
}
|
|
@@ -38428,26 +38443,1498 @@ function ReviewChangesModal({
|
|
|
38428
38443
|
}
|
|
38429
38444
|
);
|
|
38430
38445
|
}
|
|
38446
|
+
function ModeSelectionModal({
|
|
38447
|
+
open,
|
|
38448
|
+
onClose,
|
|
38449
|
+
onSelectReplace,
|
|
38450
|
+
onSelectResync,
|
|
38451
|
+
hasExistingLyrics
|
|
38452
|
+
}) {
|
|
38453
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
38454
|
+
Dialog,
|
|
38455
|
+
{
|
|
38456
|
+
open,
|
|
38457
|
+
onClose,
|
|
38458
|
+
maxWidth: "sm",
|
|
38459
|
+
fullWidth: true,
|
|
38460
|
+
children: [
|
|
38461
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogTitle, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
|
|
38462
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { flex: 1 }, children: "Edit All Lyrics" }),
|
|
38463
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(IconButton, { onClick: onClose, sx: { ml: "auto" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, {}) })
|
|
38464
|
+
] }),
|
|
38465
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { dividers: true, children: [
|
|
38466
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body1", sx: { mb: 3 }, children: "Choose how you want to edit the lyrics:" }),
|
|
38467
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 2 }, children: [
|
|
38468
|
+
hasExistingLyrics && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
38469
|
+
Paper,
|
|
38470
|
+
{
|
|
38471
|
+
sx: {
|
|
38472
|
+
p: 2,
|
|
38473
|
+
cursor: "pointer",
|
|
38474
|
+
border: 2,
|
|
38475
|
+
borderColor: "primary.main",
|
|
38476
|
+
"&:hover": {
|
|
38477
|
+
bgcolor: "action.hover",
|
|
38478
|
+
borderColor: "primary.dark"
|
|
38479
|
+
}
|
|
38480
|
+
},
|
|
38481
|
+
onClick: onSelectResync,
|
|
38482
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", alignItems: "flex-start", gap: 2 }, children: [
|
|
38483
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, { color: "primary", sx: { fontSize: 40, mt: 0.5 } }),
|
|
38484
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { children: [
|
|
38485
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", color: "primary", children: "Re-sync Existing Lyrics" }),
|
|
38486
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body2", color: "text.secondary", children: "Keep the current lyrics text and fix timing issues. Use this when lyrics are correct but timing has drifted, especially in the second half of the song." }),
|
|
38487
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "caption", color: "success.main", sx: { mt: 1, display: "block" }, children: "Recommended for fixing timing drift" })
|
|
38488
|
+
] })
|
|
38489
|
+
] })
|
|
38490
|
+
}
|
|
38491
|
+
),
|
|
38492
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
38493
|
+
Paper,
|
|
38494
|
+
{
|
|
38495
|
+
sx: {
|
|
38496
|
+
p: 2,
|
|
38497
|
+
cursor: "pointer",
|
|
38498
|
+
border: 1,
|
|
38499
|
+
borderColor: "divider",
|
|
38500
|
+
"&:hover": {
|
|
38501
|
+
bgcolor: "action.hover",
|
|
38502
|
+
borderColor: "text.secondary"
|
|
38503
|
+
}
|
|
38504
|
+
},
|
|
38505
|
+
onClick: onSelectReplace,
|
|
38506
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", alignItems: "flex-start", gap: 2 }, children: [
|
|
38507
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(ContentPasteIcon, { sx: { fontSize: 40, mt: 0.5, color: "text.secondary" } }),
|
|
38508
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { children: [
|
|
38509
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", children: "Replace All Lyrics" }),
|
|
38510
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body2", color: "text.secondary", children: "Paste completely new lyrics from clipboard and manually sync timing for all words from scratch." }),
|
|
38511
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "caption", color: "warning.main", sx: { mt: 1, display: "block" }, children: "All existing timing data will be lost" })
|
|
38512
|
+
] })
|
|
38513
|
+
] })
|
|
38514
|
+
}
|
|
38515
|
+
)
|
|
38516
|
+
] })
|
|
38517
|
+
] }),
|
|
38518
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(DialogActions, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: onClose, color: "inherit", children: "Cancel" }) })
|
|
38519
|
+
]
|
|
38520
|
+
}
|
|
38521
|
+
);
|
|
38522
|
+
}
|
|
38523
|
+
const TIME_BAR_HEIGHT = 28;
|
|
38524
|
+
const WORD_BLOCK_HEIGHT = 24;
|
|
38525
|
+
const WORD_LEVEL_SPACING = 50;
|
|
38526
|
+
const CANVAS_PADDING = 8;
|
|
38527
|
+
const TEXT_ABOVE_BLOCK = 14;
|
|
38528
|
+
const RESIZE_HANDLE_SIZE = 8;
|
|
38529
|
+
const RESIZE_HANDLE_HITAREA = 12;
|
|
38530
|
+
const PLAYHEAD_COLOR = "#ffffff";
|
|
38531
|
+
const WORD_BLOCK_COLOR = "#d32f2f";
|
|
38532
|
+
const WORD_BLOCK_SELECTED_COLOR = "#b71c1c";
|
|
38533
|
+
const WORD_BLOCK_CURRENT_COLOR = "#f44336";
|
|
38534
|
+
const WORD_TEXT_CURRENT_COLOR = "#d32f2f";
|
|
38535
|
+
const UPCOMING_WORD_BG = "#fff9c4";
|
|
38536
|
+
const UPCOMING_WORD_TEXT = "#000000";
|
|
38537
|
+
const TIME_BAR_BG = "#f5f5f5";
|
|
38538
|
+
const TIME_BAR_TEXT = "#666666";
|
|
38539
|
+
const TIMELINE_BG = "#e0e0e0";
|
|
38540
|
+
function buildWordToSegmentMap(segments) {
|
|
38541
|
+
const map = /* @__PURE__ */ new Map();
|
|
38542
|
+
segments.forEach((segment, idx) => {
|
|
38543
|
+
segment.words.forEach((word) => {
|
|
38544
|
+
map.set(word.id, idx);
|
|
38545
|
+
});
|
|
38546
|
+
});
|
|
38547
|
+
return map;
|
|
38548
|
+
}
|
|
38549
|
+
function calculateWordLevels(words, segments) {
|
|
38550
|
+
const levels = /* @__PURE__ */ new Map();
|
|
38551
|
+
const wordToSegment = buildWordToSegmentMap(segments);
|
|
38552
|
+
const segmentsWithTiming = segments.map((segment, idx) => {
|
|
38553
|
+
const timedWords = segment.words.filter((w) => w.start_time !== null);
|
|
38554
|
+
const minStart = timedWords.length > 0 ? Math.min(...timedWords.map((w) => w.start_time)) : Infinity;
|
|
38555
|
+
return { idx, minStart };
|
|
38556
|
+
}).filter((s) => s.minStart !== Infinity).sort((a, b) => a.minStart - b.minStart);
|
|
38557
|
+
const segmentLevels = /* @__PURE__ */ new Map();
|
|
38558
|
+
segmentsWithTiming.forEach(({ idx }, orderIndex) => {
|
|
38559
|
+
segmentLevels.set(idx, orderIndex % 2);
|
|
38560
|
+
});
|
|
38561
|
+
for (const word of words) {
|
|
38562
|
+
const segmentIdx = wordToSegment.get(word.id);
|
|
38563
|
+
if (segmentIdx !== void 0 && segmentLevels.has(segmentIdx)) {
|
|
38564
|
+
levels.set(word.id, segmentLevels.get(segmentIdx));
|
|
38565
|
+
} else {
|
|
38566
|
+
levels.set(word.id, 0);
|
|
38567
|
+
}
|
|
38568
|
+
}
|
|
38569
|
+
return levels;
|
|
38570
|
+
}
|
|
38571
|
+
function formatTime(seconds) {
|
|
38572
|
+
const mins = Math.floor(seconds / 60);
|
|
38573
|
+
const secs = Math.floor(seconds % 60);
|
|
38574
|
+
return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
|
|
38575
|
+
}
|
|
38576
|
+
const TimelineCanvas = reactExports.memo(function TimelineCanvas2({
|
|
38577
|
+
words,
|
|
38578
|
+
segments,
|
|
38579
|
+
visibleStartTime,
|
|
38580
|
+
visibleEndTime,
|
|
38581
|
+
currentTime,
|
|
38582
|
+
selectedWordIds,
|
|
38583
|
+
onWordClick,
|
|
38584
|
+
onBackgroundClick,
|
|
38585
|
+
onTimeBarClick,
|
|
38586
|
+
onSelectionComplete,
|
|
38587
|
+
onWordTimingChange,
|
|
38588
|
+
onWordsMove,
|
|
38589
|
+
syncWordIndex,
|
|
38590
|
+
isManualSyncing,
|
|
38591
|
+
onScrollChange,
|
|
38592
|
+
audioDuration,
|
|
38593
|
+
zoomSeconds,
|
|
38594
|
+
height: height2 = 200
|
|
38595
|
+
}) {
|
|
38596
|
+
const canvasRef = reactExports.useRef(null);
|
|
38597
|
+
const containerRef = reactExports.useRef(null);
|
|
38598
|
+
const [canvasWidth, setCanvasWidth] = reactExports.useState(800);
|
|
38599
|
+
const animationFrameRef = reactExports.useRef();
|
|
38600
|
+
const wordLevelsRef = reactExports.useRef(/* @__PURE__ */ new Map());
|
|
38601
|
+
const [dragMode, setDragMode] = reactExports.useState("none");
|
|
38602
|
+
const dragStartRef = reactExports.useRef(null);
|
|
38603
|
+
const dragWordIdRef = reactExports.useRef(null);
|
|
38604
|
+
const dragOriginalTimesRef = reactExports.useRef(/* @__PURE__ */ new Map());
|
|
38605
|
+
const [selectionRect, setSelectionRect] = reactExports.useState(null);
|
|
38606
|
+
const [hoveredWordId, setHoveredWordId] = reactExports.useState(null);
|
|
38607
|
+
const [cursorStyle, setCursorStyle] = reactExports.useState("default");
|
|
38608
|
+
reactExports.useEffect(() => {
|
|
38609
|
+
const updateWidth = () => {
|
|
38610
|
+
if (containerRef.current) {
|
|
38611
|
+
setCanvasWidth(containerRef.current.clientWidth);
|
|
38612
|
+
}
|
|
38613
|
+
};
|
|
38614
|
+
updateWidth();
|
|
38615
|
+
const resizeObserver = new ResizeObserver(updateWidth);
|
|
38616
|
+
if (containerRef.current) {
|
|
38617
|
+
resizeObserver.observe(containerRef.current);
|
|
38618
|
+
}
|
|
38619
|
+
return () => resizeObserver.disconnect();
|
|
38620
|
+
}, []);
|
|
38621
|
+
reactExports.useEffect(() => {
|
|
38622
|
+
wordLevelsRef.current = calculateWordLevels(words, segments);
|
|
38623
|
+
}, [words, segments]);
|
|
38624
|
+
const timeToX = reactExports.useCallback((time) => {
|
|
38625
|
+
const duration2 = visibleEndTime - visibleStartTime;
|
|
38626
|
+
if (duration2 <= 0) return 0;
|
|
38627
|
+
return CANVAS_PADDING + (time - visibleStartTime) / duration2 * (canvasWidth - CANVAS_PADDING * 2);
|
|
38628
|
+
}, [visibleStartTime, visibleEndTime, canvasWidth]);
|
|
38629
|
+
const xToTime = reactExports.useCallback((x) => {
|
|
38630
|
+
const duration2 = visibleEndTime - visibleStartTime;
|
|
38631
|
+
return visibleStartTime + (x - CANVAS_PADDING) / (canvasWidth - CANVAS_PADDING * 2) * duration2;
|
|
38632
|
+
}, [visibleStartTime, visibleEndTime, canvasWidth]);
|
|
38633
|
+
const getWordBounds = reactExports.useCallback((word) => {
|
|
38634
|
+
if (word.start_time === null || word.end_time === null) return null;
|
|
38635
|
+
const level = wordLevelsRef.current.get(word.id) || 0;
|
|
38636
|
+
const startX = timeToX(word.start_time);
|
|
38637
|
+
const endX = timeToX(word.end_time);
|
|
38638
|
+
const blockWidth = Math.max(endX - startX, 4);
|
|
38639
|
+
const y = TIME_BAR_HEIGHT + CANVAS_PADDING + TEXT_ABOVE_BLOCK + level * WORD_LEVEL_SPACING;
|
|
38640
|
+
return { startX, endX, blockWidth, y, level };
|
|
38641
|
+
}, [timeToX]);
|
|
38642
|
+
const isNearResizeHandlePos = reactExports.useCallback((word, x, y) => {
|
|
38643
|
+
const bounds = getWordBounds(word);
|
|
38644
|
+
if (!bounds) return false;
|
|
38645
|
+
const handleX = bounds.startX + bounds.blockWidth - RESIZE_HANDLE_SIZE / 2;
|
|
38646
|
+
const handleY = bounds.y + WORD_BLOCK_HEIGHT / 2;
|
|
38647
|
+
return Math.abs(x - handleX) < RESIZE_HANDLE_HITAREA / 2 && Math.abs(y - handleY) < RESIZE_HANDLE_HITAREA / 2;
|
|
38648
|
+
}, [getWordBounds]);
|
|
38649
|
+
const findWordAtPosition = reactExports.useCallback((x, y) => {
|
|
38650
|
+
for (const word of words) {
|
|
38651
|
+
const bounds = getWordBounds(word);
|
|
38652
|
+
if (!bounds) continue;
|
|
38653
|
+
if (x >= bounds.startX && x <= bounds.startX + bounds.blockWidth && y >= bounds.y && y <= bounds.y + WORD_BLOCK_HEIGHT) {
|
|
38654
|
+
return word;
|
|
38655
|
+
}
|
|
38656
|
+
}
|
|
38657
|
+
return null;
|
|
38658
|
+
}, [words, getWordBounds]);
|
|
38659
|
+
const findWordsInRect = reactExports.useCallback((rect) => {
|
|
38660
|
+
const rectLeft = Math.min(rect.startX, rect.endX);
|
|
38661
|
+
const rectRight = Math.max(rect.startX, rect.endX);
|
|
38662
|
+
const rectTop = Math.min(rect.startY, rect.endY);
|
|
38663
|
+
const rectBottom = Math.max(rect.startY, rect.endY);
|
|
38664
|
+
const selectedIds = [];
|
|
38665
|
+
for (const word of words) {
|
|
38666
|
+
const bounds = getWordBounds(word);
|
|
38667
|
+
if (!bounds) continue;
|
|
38668
|
+
if (bounds.startX + bounds.blockWidth >= rectLeft && bounds.startX <= rectRight && bounds.y + WORD_BLOCK_HEIGHT >= rectTop && bounds.y <= rectBottom) {
|
|
38669
|
+
selectedIds.push(word.id);
|
|
38670
|
+
}
|
|
38671
|
+
}
|
|
38672
|
+
return selectedIds;
|
|
38673
|
+
}, [words, getWordBounds]);
|
|
38674
|
+
const draw = reactExports.useCallback(() => {
|
|
38675
|
+
var _a;
|
|
38676
|
+
const canvas = canvasRef.current;
|
|
38677
|
+
if (!canvas) return;
|
|
38678
|
+
const ctx = canvas.getContext("2d");
|
|
38679
|
+
if (!ctx) return;
|
|
38680
|
+
const dpr = window.devicePixelRatio || 1;
|
|
38681
|
+
canvas.width = canvasWidth * dpr;
|
|
38682
|
+
canvas.height = height2 * dpr;
|
|
38683
|
+
ctx.scale(dpr, dpr);
|
|
38684
|
+
ctx.fillStyle = TIMELINE_BG;
|
|
38685
|
+
ctx.fillRect(0, 0, canvasWidth, height2);
|
|
38686
|
+
ctx.fillStyle = TIME_BAR_BG;
|
|
38687
|
+
ctx.fillRect(0, 0, canvasWidth, TIME_BAR_HEIGHT);
|
|
38688
|
+
const duration2 = visibleEndTime - visibleStartTime;
|
|
38689
|
+
const secondsPerTick = duration2 > 15 ? 2 : duration2 > 8 ? 1 : 0.5;
|
|
38690
|
+
const startSecond = Math.ceil(visibleStartTime / secondsPerTick) * secondsPerTick;
|
|
38691
|
+
ctx.fillStyle = TIME_BAR_TEXT;
|
|
38692
|
+
ctx.font = "11px system-ui, -apple-system, sans-serif";
|
|
38693
|
+
ctx.textAlign = "center";
|
|
38694
|
+
for (let t = startSecond; t <= visibleEndTime; t += secondsPerTick) {
|
|
38695
|
+
const x = timeToX(t);
|
|
38696
|
+
ctx.beginPath();
|
|
38697
|
+
ctx.strokeStyle = "#999999";
|
|
38698
|
+
ctx.lineWidth = 1;
|
|
38699
|
+
ctx.moveTo(x, TIME_BAR_HEIGHT - 6);
|
|
38700
|
+
ctx.lineTo(x, TIME_BAR_HEIGHT);
|
|
38701
|
+
ctx.stroke();
|
|
38702
|
+
if (t % 1 === 0) {
|
|
38703
|
+
ctx.fillText(formatTime(t), x, TIME_BAR_HEIGHT - 10);
|
|
38704
|
+
}
|
|
38705
|
+
}
|
|
38706
|
+
ctx.beginPath();
|
|
38707
|
+
ctx.strokeStyle = "#cccccc";
|
|
38708
|
+
ctx.lineWidth = 1;
|
|
38709
|
+
ctx.moveTo(0, TIME_BAR_HEIGHT);
|
|
38710
|
+
ctx.lineTo(canvasWidth, TIME_BAR_HEIGHT);
|
|
38711
|
+
ctx.stroke();
|
|
38712
|
+
const wordToSegment = buildWordToSegmentMap(segments);
|
|
38713
|
+
const syncedWords = words.filter((w) => w.start_time !== null && w.end_time !== null);
|
|
38714
|
+
const currentWordId = ((_a = syncedWords.find(
|
|
38715
|
+
(w) => currentTime >= w.start_time && currentTime <= w.end_time
|
|
38716
|
+
)) == null ? void 0 : _a.id) || null;
|
|
38717
|
+
for (const word of syncedWords) {
|
|
38718
|
+
const bounds = getWordBounds(word);
|
|
38719
|
+
if (!bounds) continue;
|
|
38720
|
+
const isSelected = selectedWordIds.has(word.id);
|
|
38721
|
+
const isCurrent = word.id === currentWordId;
|
|
38722
|
+
const isHovered = word.id === hoveredWordId;
|
|
38723
|
+
if (isSelected) {
|
|
38724
|
+
ctx.fillStyle = WORD_BLOCK_SELECTED_COLOR;
|
|
38725
|
+
} else if (isCurrent) {
|
|
38726
|
+
ctx.fillStyle = WORD_BLOCK_CURRENT_COLOR;
|
|
38727
|
+
} else {
|
|
38728
|
+
ctx.fillStyle = WORD_BLOCK_COLOR;
|
|
38729
|
+
}
|
|
38730
|
+
ctx.fillRect(bounds.startX, bounds.y, bounds.blockWidth, WORD_BLOCK_HEIGHT);
|
|
38731
|
+
if (isSelected) {
|
|
38732
|
+
ctx.strokeStyle = "#ffffff";
|
|
38733
|
+
ctx.lineWidth = 2;
|
|
38734
|
+
ctx.strokeRect(bounds.startX, bounds.y, bounds.blockWidth, WORD_BLOCK_HEIGHT);
|
|
38735
|
+
if (isHovered || selectedWordIds.size === 1) {
|
|
38736
|
+
const handleX = bounds.startX + bounds.blockWidth - RESIZE_HANDLE_SIZE / 2;
|
|
38737
|
+
const handleY = bounds.y + WORD_BLOCK_HEIGHT / 2;
|
|
38738
|
+
ctx.beginPath();
|
|
38739
|
+
ctx.fillStyle = "#ffffff";
|
|
38740
|
+
ctx.arc(handleX, handleY, RESIZE_HANDLE_SIZE / 2, 0, Math.PI * 2);
|
|
38741
|
+
ctx.fill();
|
|
38742
|
+
ctx.strokeStyle = "#666666";
|
|
38743
|
+
ctx.lineWidth = 1;
|
|
38744
|
+
ctx.stroke();
|
|
38745
|
+
}
|
|
38746
|
+
}
|
|
38747
|
+
}
|
|
38748
|
+
const wordsBySegment = /* @__PURE__ */ new Map();
|
|
38749
|
+
for (const word of syncedWords) {
|
|
38750
|
+
const segIdx = wordToSegment.get(word.id);
|
|
38751
|
+
if (segIdx !== void 0) {
|
|
38752
|
+
if (!wordsBySegment.has(segIdx)) {
|
|
38753
|
+
wordsBySegment.set(segIdx, []);
|
|
38754
|
+
}
|
|
38755
|
+
wordsBySegment.get(segIdx).push(word);
|
|
38756
|
+
}
|
|
38757
|
+
}
|
|
38758
|
+
ctx.font = "11px system-ui, -apple-system, sans-serif";
|
|
38759
|
+
ctx.textAlign = "left";
|
|
38760
|
+
for (const [, segmentWords] of wordsBySegment) {
|
|
38761
|
+
const sortedWords = [...segmentWords].sort(
|
|
38762
|
+
(a, b) => (a.start_time || 0) - (b.start_time || 0)
|
|
38763
|
+
);
|
|
38764
|
+
if (sortedWords.length === 0) continue;
|
|
38765
|
+
const level = wordLevelsRef.current.get(sortedWords[0].id) || 0;
|
|
38766
|
+
const textY = TIME_BAR_HEIGHT + CANVAS_PADDING + TEXT_ABOVE_BLOCK + level * WORD_LEVEL_SPACING - 3;
|
|
38767
|
+
let rightmostTextEnd = -Infinity;
|
|
38768
|
+
for (const word of sortedWords) {
|
|
38769
|
+
const blockStartX = timeToX(word.start_time);
|
|
38770
|
+
const textWidth = ctx.measureText(word.text).width;
|
|
38771
|
+
const textStartX = Math.max(blockStartX, rightmostTextEnd + 3);
|
|
38772
|
+
if (textStartX < canvasWidth - 10) {
|
|
38773
|
+
const isCurrent = word.id === currentWordId;
|
|
38774
|
+
ctx.fillStyle = isCurrent ? WORD_TEXT_CURRENT_COLOR : "#333333";
|
|
38775
|
+
ctx.fillText(word.text, textStartX, textY);
|
|
38776
|
+
rightmostTextEnd = textStartX + textWidth;
|
|
38777
|
+
}
|
|
38778
|
+
}
|
|
38779
|
+
}
|
|
38780
|
+
if (isManualSyncing && syncWordIndex >= 0) {
|
|
38781
|
+
const upcomingWords = words.slice(syncWordIndex).filter((w) => w.start_time === null);
|
|
38782
|
+
const playheadX = timeToX(currentTime);
|
|
38783
|
+
let offsetX = playheadX + 10;
|
|
38784
|
+
ctx.font = "11px system-ui, -apple-system, sans-serif";
|
|
38785
|
+
for (let i = 0; i < Math.min(upcomingWords.length, 12); i++) {
|
|
38786
|
+
const word = upcomingWords[i];
|
|
38787
|
+
const textWidth = ctx.measureText(word.text).width + 10;
|
|
38788
|
+
ctx.fillStyle = UPCOMING_WORD_BG;
|
|
38789
|
+
ctx.fillRect(offsetX, TIME_BAR_HEIGHT + CANVAS_PADDING + WORD_LEVEL_SPACING + 60, textWidth, 20);
|
|
38790
|
+
ctx.fillStyle = UPCOMING_WORD_TEXT;
|
|
38791
|
+
ctx.textAlign = "left";
|
|
38792
|
+
ctx.fillText(word.text, offsetX + 5, TIME_BAR_HEIGHT + CANVAS_PADDING + WORD_LEVEL_SPACING + 74);
|
|
38793
|
+
offsetX += textWidth + 3;
|
|
38794
|
+
if (offsetX > canvasWidth - 20) break;
|
|
38795
|
+
}
|
|
38796
|
+
}
|
|
38797
|
+
if (currentTime >= visibleStartTime && currentTime <= visibleEndTime) {
|
|
38798
|
+
const playheadX = timeToX(currentTime);
|
|
38799
|
+
ctx.beginPath();
|
|
38800
|
+
ctx.fillStyle = PLAYHEAD_COLOR;
|
|
38801
|
+
ctx.strokeStyle = "#333333";
|
|
38802
|
+
ctx.lineWidth = 1;
|
|
38803
|
+
ctx.moveTo(playheadX - 6, 2);
|
|
38804
|
+
ctx.lineTo(playheadX + 6, 2);
|
|
38805
|
+
ctx.lineTo(playheadX, TIME_BAR_HEIGHT - 4);
|
|
38806
|
+
ctx.closePath();
|
|
38807
|
+
ctx.fill();
|
|
38808
|
+
ctx.stroke();
|
|
38809
|
+
ctx.beginPath();
|
|
38810
|
+
ctx.strokeStyle = PLAYHEAD_COLOR;
|
|
38811
|
+
ctx.lineWidth = 2;
|
|
38812
|
+
ctx.moveTo(playheadX, TIME_BAR_HEIGHT);
|
|
38813
|
+
ctx.lineTo(playheadX, height2);
|
|
38814
|
+
ctx.stroke();
|
|
38815
|
+
ctx.beginPath();
|
|
38816
|
+
ctx.strokeStyle = "rgba(0,0,0,0.4)";
|
|
38817
|
+
ctx.lineWidth = 1;
|
|
38818
|
+
ctx.moveTo(playheadX + 1, TIME_BAR_HEIGHT);
|
|
38819
|
+
ctx.lineTo(playheadX + 1, height2);
|
|
38820
|
+
ctx.stroke();
|
|
38821
|
+
}
|
|
38822
|
+
if (selectionRect) {
|
|
38823
|
+
ctx.fillStyle = "rgba(25, 118, 210, 0.2)";
|
|
38824
|
+
ctx.strokeStyle = "rgba(25, 118, 210, 0.8)";
|
|
38825
|
+
ctx.lineWidth = 1;
|
|
38826
|
+
const rectX = Math.min(selectionRect.startX, selectionRect.endX);
|
|
38827
|
+
const rectY = Math.min(selectionRect.startY, selectionRect.endY);
|
|
38828
|
+
const rectW = Math.abs(selectionRect.endX - selectionRect.startX);
|
|
38829
|
+
const rectH = Math.abs(selectionRect.endY - selectionRect.startY);
|
|
38830
|
+
ctx.fillRect(rectX, rectY, rectW, rectH);
|
|
38831
|
+
ctx.strokeRect(rectX, rectY, rectW, rectH);
|
|
38832
|
+
}
|
|
38833
|
+
}, [
|
|
38834
|
+
canvasWidth,
|
|
38835
|
+
height2,
|
|
38836
|
+
visibleStartTime,
|
|
38837
|
+
visibleEndTime,
|
|
38838
|
+
currentTime,
|
|
38839
|
+
words,
|
|
38840
|
+
segments,
|
|
38841
|
+
selectedWordIds,
|
|
38842
|
+
selectionRect,
|
|
38843
|
+
hoveredWordId,
|
|
38844
|
+
syncWordIndex,
|
|
38845
|
+
isManualSyncing,
|
|
38846
|
+
timeToX,
|
|
38847
|
+
getWordBounds
|
|
38848
|
+
]);
|
|
38849
|
+
reactExports.useEffect(() => {
|
|
38850
|
+
const animate = () => {
|
|
38851
|
+
draw();
|
|
38852
|
+
animationFrameRef.current = requestAnimationFrame(animate);
|
|
38853
|
+
};
|
|
38854
|
+
animate();
|
|
38855
|
+
return () => {
|
|
38856
|
+
if (animationFrameRef.current) {
|
|
38857
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
38858
|
+
}
|
|
38859
|
+
};
|
|
38860
|
+
}, [draw]);
|
|
38861
|
+
const handleMouseDown = reactExports.useCallback((e) => {
|
|
38862
|
+
var _a;
|
|
38863
|
+
const rect = (_a = canvasRef.current) == null ? void 0 : _a.getBoundingClientRect();
|
|
38864
|
+
if (!rect) return;
|
|
38865
|
+
const x = e.clientX - rect.left;
|
|
38866
|
+
const y = e.clientY - rect.top;
|
|
38867
|
+
const time = xToTime(x);
|
|
38868
|
+
if (y < TIME_BAR_HEIGHT) {
|
|
38869
|
+
onTimeBarClick(Math.max(0, time));
|
|
38870
|
+
return;
|
|
38871
|
+
}
|
|
38872
|
+
const clickedWord = findWordAtPosition(x, y);
|
|
38873
|
+
if (clickedWord && selectedWordIds.has(clickedWord.id)) {
|
|
38874
|
+
if (isNearResizeHandlePos(clickedWord, x, y)) {
|
|
38875
|
+
setDragMode("resize");
|
|
38876
|
+
dragStartRef.current = { x, y, time };
|
|
38877
|
+
dragWordIdRef.current = clickedWord.id;
|
|
38878
|
+
dragOriginalTimesRef.current = /* @__PURE__ */ new Map([[clickedWord.id, {
|
|
38879
|
+
start: clickedWord.start_time,
|
|
38880
|
+
end: clickedWord.end_time
|
|
38881
|
+
}]]);
|
|
38882
|
+
return;
|
|
38883
|
+
}
|
|
38884
|
+
setDragMode("move");
|
|
38885
|
+
dragStartRef.current = { x, y, time };
|
|
38886
|
+
dragWordIdRef.current = clickedWord.id;
|
|
38887
|
+
const originalTimes = /* @__PURE__ */ new Map();
|
|
38888
|
+
for (const wordId of selectedWordIds) {
|
|
38889
|
+
const word = words.find((w) => w.id === wordId);
|
|
38890
|
+
if (word && word.start_time !== null && word.end_time !== null) {
|
|
38891
|
+
originalTimes.set(wordId, { start: word.start_time, end: word.end_time });
|
|
38892
|
+
}
|
|
38893
|
+
}
|
|
38894
|
+
dragOriginalTimesRef.current = originalTimes;
|
|
38895
|
+
return;
|
|
38896
|
+
}
|
|
38897
|
+
if (clickedWord) {
|
|
38898
|
+
onWordClick(clickedWord.id, e);
|
|
38899
|
+
return;
|
|
38900
|
+
}
|
|
38901
|
+
setDragMode("selection");
|
|
38902
|
+
dragStartRef.current = { x, y, time };
|
|
38903
|
+
setSelectionRect({ startX: x, startY: y, endX: x, endY: y });
|
|
38904
|
+
}, [xToTime, onTimeBarClick, findWordAtPosition, selectedWordIds, isNearResizeHandlePos, onWordClick, words]);
|
|
38905
|
+
const handleMouseMove = reactExports.useCallback((e) => {
|
|
38906
|
+
var _a;
|
|
38907
|
+
const rect = (_a = canvasRef.current) == null ? void 0 : _a.getBoundingClientRect();
|
|
38908
|
+
if (!rect) return;
|
|
38909
|
+
const x = e.clientX - rect.left;
|
|
38910
|
+
const y = e.clientY - rect.top;
|
|
38911
|
+
const time = xToTime(x);
|
|
38912
|
+
if (dragMode === "none") {
|
|
38913
|
+
const hoveredWord = findWordAtPosition(x, y);
|
|
38914
|
+
setHoveredWordId((hoveredWord == null ? void 0 : hoveredWord.id) || null);
|
|
38915
|
+
if (hoveredWord && selectedWordIds.has(hoveredWord.id)) {
|
|
38916
|
+
const nearHandle = isNearResizeHandlePos(hoveredWord, x, y);
|
|
38917
|
+
setCursorStyle(nearHandle ? "ew-resize" : "grab");
|
|
38918
|
+
} else if (hoveredWord) {
|
|
38919
|
+
setCursorStyle("pointer");
|
|
38920
|
+
} else if (y < TIME_BAR_HEIGHT) {
|
|
38921
|
+
setCursorStyle("pointer");
|
|
38922
|
+
} else {
|
|
38923
|
+
setCursorStyle("default");
|
|
38924
|
+
}
|
|
38925
|
+
}
|
|
38926
|
+
if (!dragStartRef.current) return;
|
|
38927
|
+
if (dragMode === "selection") {
|
|
38928
|
+
setSelectionRect({
|
|
38929
|
+
startX: dragStartRef.current.x,
|
|
38930
|
+
startY: dragStartRef.current.y,
|
|
38931
|
+
endX: x,
|
|
38932
|
+
endY: y
|
|
38933
|
+
});
|
|
38934
|
+
} else if (dragMode === "resize" && dragWordIdRef.current) {
|
|
38935
|
+
const originalTimes = dragOriginalTimesRef.current.get(dragWordIdRef.current);
|
|
38936
|
+
if (originalTimes) {
|
|
38937
|
+
const deltaTime = time - dragStartRef.current.time;
|
|
38938
|
+
const newEndTime = Math.max(originalTimes.start + 0.05, originalTimes.end + deltaTime);
|
|
38939
|
+
onWordTimingChange(dragWordIdRef.current, originalTimes.start, newEndTime);
|
|
38940
|
+
}
|
|
38941
|
+
setCursorStyle("ew-resize");
|
|
38942
|
+
} else if (dragMode === "move") {
|
|
38943
|
+
const deltaTime = time - dragStartRef.current.time;
|
|
38944
|
+
const updates = [];
|
|
38945
|
+
for (const [wordId, originalTimes] of dragOriginalTimesRef.current) {
|
|
38946
|
+
updates.push({
|
|
38947
|
+
wordId,
|
|
38948
|
+
newStartTime: Math.max(0, originalTimes.start + deltaTime),
|
|
38949
|
+
newEndTime: Math.max(0.05, originalTimes.end + deltaTime)
|
|
38950
|
+
});
|
|
38951
|
+
}
|
|
38952
|
+
if (updates.length > 0) {
|
|
38953
|
+
onWordsMove(updates);
|
|
38954
|
+
}
|
|
38955
|
+
setCursorStyle("grabbing");
|
|
38956
|
+
}
|
|
38957
|
+
}, [dragMode, xToTime, findWordAtPosition, selectedWordIds, isNearResizeHandlePos, onWordTimingChange, onWordsMove]);
|
|
38958
|
+
const handleMouseUp = reactExports.useCallback((e) => {
|
|
38959
|
+
var _a;
|
|
38960
|
+
const rect = (_a = canvasRef.current) == null ? void 0 : _a.getBoundingClientRect();
|
|
38961
|
+
if (dragMode === "selection" && dragStartRef.current && rect) {
|
|
38962
|
+
const endX = e.clientX - rect.left;
|
|
38963
|
+
const endY = e.clientY - rect.top;
|
|
38964
|
+
const dragDistance = Math.sqrt(
|
|
38965
|
+
Math.pow(endX - dragStartRef.current.x, 2) + Math.pow(endY - dragStartRef.current.y, 2)
|
|
38966
|
+
);
|
|
38967
|
+
if (dragDistance < 5) {
|
|
38968
|
+
onBackgroundClick();
|
|
38969
|
+
} else {
|
|
38970
|
+
const finalRect = {
|
|
38971
|
+
startX: dragStartRef.current.x,
|
|
38972
|
+
startY: dragStartRef.current.y,
|
|
38973
|
+
endX,
|
|
38974
|
+
endY
|
|
38975
|
+
};
|
|
38976
|
+
const selectedIds = findWordsInRect(finalRect);
|
|
38977
|
+
if (selectedIds.length > 0) {
|
|
38978
|
+
onSelectionComplete(selectedIds);
|
|
38979
|
+
}
|
|
38980
|
+
}
|
|
38981
|
+
}
|
|
38982
|
+
setDragMode("none");
|
|
38983
|
+
dragStartRef.current = null;
|
|
38984
|
+
dragWordIdRef.current = null;
|
|
38985
|
+
dragOriginalTimesRef.current = /* @__PURE__ */ new Map();
|
|
38986
|
+
setSelectionRect(null);
|
|
38987
|
+
setCursorStyle("default");
|
|
38988
|
+
}, [dragMode, onBackgroundClick, findWordsInRect, onSelectionComplete]);
|
|
38989
|
+
const handleWheel = reactExports.useCallback((e) => {
|
|
38990
|
+
const delta = e.deltaX !== 0 ? e.deltaX : e.deltaY;
|
|
38991
|
+
const scrollAmount = delta / 100 * (zoomSeconds / 4);
|
|
38992
|
+
let newStart = Math.max(0, Math.min(audioDuration - zoomSeconds, visibleStartTime + scrollAmount));
|
|
38993
|
+
if (newStart !== visibleStartTime) {
|
|
38994
|
+
onScrollChange(newStart);
|
|
38995
|
+
}
|
|
38996
|
+
}, [visibleStartTime, zoomSeconds, audioDuration, onScrollChange]);
|
|
38997
|
+
const handleScrollLeft = reactExports.useCallback(() => {
|
|
38998
|
+
const newStart = Math.max(0, visibleStartTime - zoomSeconds * 0.25);
|
|
38999
|
+
onScrollChange(newStart);
|
|
39000
|
+
}, [visibleStartTime, zoomSeconds, onScrollChange]);
|
|
39001
|
+
const handleScrollRight = reactExports.useCallback(() => {
|
|
39002
|
+
const newStart = Math.min(audioDuration - zoomSeconds, visibleStartTime + zoomSeconds * 0.25);
|
|
39003
|
+
onScrollChange(Math.max(0, newStart));
|
|
39004
|
+
}, [visibleStartTime, zoomSeconds, audioDuration, onScrollChange]);
|
|
39005
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { display: "flex", flexDirection: "column", gap: 0.5 }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
|
|
39006
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Scroll Left", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39007
|
+
IconButton,
|
|
39008
|
+
{
|
|
39009
|
+
size: "small",
|
|
39010
|
+
onClick: handleScrollLeft,
|
|
39011
|
+
disabled: visibleStartTime <= 0,
|
|
39012
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowBackIcon, { fontSize: "small" })
|
|
39013
|
+
}
|
|
39014
|
+
) }),
|
|
39015
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39016
|
+
Box,
|
|
39017
|
+
{
|
|
39018
|
+
ref: containerRef,
|
|
39019
|
+
sx: {
|
|
39020
|
+
flexGrow: 1,
|
|
39021
|
+
height: height2,
|
|
39022
|
+
cursor: cursorStyle,
|
|
39023
|
+
borderRadius: 1,
|
|
39024
|
+
overflow: "hidden"
|
|
39025
|
+
},
|
|
39026
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39027
|
+
"canvas",
|
|
39028
|
+
{
|
|
39029
|
+
ref: canvasRef,
|
|
39030
|
+
style: {
|
|
39031
|
+
width: "100%",
|
|
39032
|
+
height: "100%",
|
|
39033
|
+
display: "block",
|
|
39034
|
+
cursor: cursorStyle
|
|
39035
|
+
},
|
|
39036
|
+
onMouseDown: handleMouseDown,
|
|
39037
|
+
onMouseMove: handleMouseMove,
|
|
39038
|
+
onMouseUp: handleMouseUp,
|
|
39039
|
+
onMouseLeave: handleMouseUp,
|
|
39040
|
+
onWheel: handleWheel
|
|
39041
|
+
}
|
|
39042
|
+
)
|
|
39043
|
+
}
|
|
39044
|
+
),
|
|
39045
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Scroll Right", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39046
|
+
IconButton,
|
|
39047
|
+
{
|
|
39048
|
+
size: "small",
|
|
39049
|
+
onClick: handleScrollRight,
|
|
39050
|
+
disabled: visibleStartTime >= audioDuration - zoomSeconds,
|
|
39051
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowForwardIcon, { fontSize: "small" })
|
|
39052
|
+
}
|
|
39053
|
+
) })
|
|
39054
|
+
] }) });
|
|
39055
|
+
});
|
|
39056
|
+
const UpcomingWordsBar = reactExports.memo(function UpcomingWordsBar2({
|
|
39057
|
+
words,
|
|
39058
|
+
syncWordIndex,
|
|
39059
|
+
isManualSyncing,
|
|
39060
|
+
maxWordsToShow = 20
|
|
39061
|
+
}) {
|
|
39062
|
+
const upcomingWords = reactExports.useMemo(() => {
|
|
39063
|
+
if (!isManualSyncing || syncWordIndex < 0) return [];
|
|
39064
|
+
return words.slice(syncWordIndex).filter((w) => w.start_time === null).slice(0, maxWordsToShow);
|
|
39065
|
+
}, [words, syncWordIndex, isManualSyncing, maxWordsToShow]);
|
|
39066
|
+
const totalRemaining = reactExports.useMemo(() => {
|
|
39067
|
+
if (!isManualSyncing || syncWordIndex < 0) return 0;
|
|
39068
|
+
return words.slice(syncWordIndex).filter((w) => w.start_time === null).length;
|
|
39069
|
+
}, [words, syncWordIndex, isManualSyncing]);
|
|
39070
|
+
if (upcomingWords.length === 0) {
|
|
39071
|
+
return null;
|
|
39072
|
+
}
|
|
39073
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: {
|
|
39074
|
+
height: 44,
|
|
39075
|
+
bgcolor: "grey.100",
|
|
39076
|
+
borderRadius: 1,
|
|
39077
|
+
display: "flex",
|
|
39078
|
+
alignItems: "center",
|
|
39079
|
+
px: 1,
|
|
39080
|
+
gap: 0.5,
|
|
39081
|
+
overflow: "hidden",
|
|
39082
|
+
boxSizing: "border-box"
|
|
39083
|
+
}, children: [
|
|
39084
|
+
upcomingWords.map((word, index) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39085
|
+
Box,
|
|
39086
|
+
{
|
|
39087
|
+
sx: {
|
|
39088
|
+
px: 1,
|
|
39089
|
+
py: 0.5,
|
|
39090
|
+
borderRadius: 0.5,
|
|
39091
|
+
bgcolor: index === 0 ? "error.main" : "grey.300",
|
|
39092
|
+
color: index === 0 ? "white" : "text.primary",
|
|
39093
|
+
fontWeight: index === 0 ? "bold" : "normal",
|
|
39094
|
+
fontSize: "13px",
|
|
39095
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
39096
|
+
whiteSpace: "nowrap",
|
|
39097
|
+
border: index === 0 ? "2px solid" : "none",
|
|
39098
|
+
borderColor: "error.dark"
|
|
39099
|
+
},
|
|
39100
|
+
children: word.text
|
|
39101
|
+
},
|
|
39102
|
+
word.id
|
|
39103
|
+
)),
|
|
39104
|
+
totalRemaining > maxWordsToShow && /* @__PURE__ */ jsxRuntimeExports.jsxs(Typography, { variant: "caption", color: "text.secondary", sx: { ml: 1 }, children: [
|
|
39105
|
+
"+",
|
|
39106
|
+
totalRemaining - maxWordsToShow,
|
|
39107
|
+
" more"
|
|
39108
|
+
] })
|
|
39109
|
+
] });
|
|
39110
|
+
});
|
|
39111
|
+
const SyncControls = reactExports.memo(function SyncControls2({
|
|
39112
|
+
isManualSyncing,
|
|
39113
|
+
isPaused,
|
|
39114
|
+
onStartSync,
|
|
39115
|
+
onPauseSync,
|
|
39116
|
+
onResumeSync,
|
|
39117
|
+
onClearSync,
|
|
39118
|
+
onEditLyrics,
|
|
39119
|
+
onPlay,
|
|
39120
|
+
onStop,
|
|
39121
|
+
isPlaying,
|
|
39122
|
+
hasSelectedWords,
|
|
39123
|
+
selectedWordCount,
|
|
39124
|
+
onUnsyncFromCursor,
|
|
39125
|
+
onEditSelectedWord,
|
|
39126
|
+
onDeleteSelected,
|
|
39127
|
+
canUnsyncFromCursor
|
|
39128
|
+
}) {
|
|
39129
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 1.5 }, children: [
|
|
39130
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", flexWrap: "wrap", children: [
|
|
39131
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39132
|
+
Button,
|
|
39133
|
+
{
|
|
39134
|
+
variant: "outlined",
|
|
39135
|
+
color: "primary",
|
|
39136
|
+
onClick: onPlay,
|
|
39137
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(PlayArrowIcon, {}),
|
|
39138
|
+
size: "small",
|
|
39139
|
+
disabled: isPlaying,
|
|
39140
|
+
children: "Play"
|
|
39141
|
+
}
|
|
39142
|
+
),
|
|
39143
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39144
|
+
Button,
|
|
39145
|
+
{
|
|
39146
|
+
variant: "outlined",
|
|
39147
|
+
color: "error",
|
|
39148
|
+
onClick: onStop,
|
|
39149
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(StopIcon, {}),
|
|
39150
|
+
size: "small",
|
|
39151
|
+
disabled: !isPlaying,
|
|
39152
|
+
children: "Stop"
|
|
39153
|
+
}
|
|
39154
|
+
),
|
|
39155
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Divider, { orientation: "vertical", flexItem: true, sx: { mx: 0.5 } }),
|
|
39156
|
+
isManualSyncing ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
39157
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39158
|
+
Button,
|
|
39159
|
+
{
|
|
39160
|
+
variant: "contained",
|
|
39161
|
+
color: "error",
|
|
39162
|
+
onClick: onStartSync,
|
|
39163
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(StopIcon, {}),
|
|
39164
|
+
size: "small",
|
|
39165
|
+
children: "Stop Sync"
|
|
39166
|
+
}
|
|
39167
|
+
),
|
|
39168
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39169
|
+
Button,
|
|
39170
|
+
{
|
|
39171
|
+
variant: "outlined",
|
|
39172
|
+
color: isPaused ? "success" : "warning",
|
|
39173
|
+
onClick: isPaused ? onResumeSync : onPauseSync,
|
|
39174
|
+
startIcon: isPaused ? /* @__PURE__ */ jsxRuntimeExports.jsx(PlayArrowIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(PauseIcon, {}),
|
|
39175
|
+
size: "small",
|
|
39176
|
+
children: isPaused ? "Resume" : "Pause"
|
|
39177
|
+
}
|
|
39178
|
+
)
|
|
39179
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39180
|
+
Button,
|
|
39181
|
+
{
|
|
39182
|
+
variant: "contained",
|
|
39183
|
+
color: "primary",
|
|
39184
|
+
onClick: onStartSync,
|
|
39185
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(PlayCircleOutlineIcon, {}),
|
|
39186
|
+
size: "small",
|
|
39187
|
+
children: "Start Sync"
|
|
39188
|
+
}
|
|
39189
|
+
)
|
|
39190
|
+
] }),
|
|
39191
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", flexWrap: "wrap", children: [
|
|
39192
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39193
|
+
Button,
|
|
39194
|
+
{
|
|
39195
|
+
variant: "outlined",
|
|
39196
|
+
color: "warning",
|
|
39197
|
+
onClick: onClearSync,
|
|
39198
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(ClearAllIcon, {}),
|
|
39199
|
+
size: "small",
|
|
39200
|
+
disabled: isManualSyncing && !isPaused,
|
|
39201
|
+
children: "Clear Sync"
|
|
39202
|
+
}
|
|
39203
|
+
),
|
|
39204
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39205
|
+
Button,
|
|
39206
|
+
{
|
|
39207
|
+
variant: "outlined",
|
|
39208
|
+
onClick: onEditLyrics,
|
|
39209
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(EditNoteIcon, {}),
|
|
39210
|
+
size: "small",
|
|
39211
|
+
disabled: isManualSyncing && !isPaused,
|
|
39212
|
+
children: "Edit Lyrics"
|
|
39213
|
+
}
|
|
39214
|
+
),
|
|
39215
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Divider, { orientation: "vertical", flexItem: true, sx: { mx: 0.5 } }),
|
|
39216
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39217
|
+
Button,
|
|
39218
|
+
{
|
|
39219
|
+
variant: "outlined",
|
|
39220
|
+
onClick: onUnsyncFromCursor,
|
|
39221
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(BlockIcon, {}),
|
|
39222
|
+
size: "small",
|
|
39223
|
+
disabled: !canUnsyncFromCursor || isManualSyncing && !isPaused,
|
|
39224
|
+
children: "Unsync from Cursor"
|
|
39225
|
+
}
|
|
39226
|
+
),
|
|
39227
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39228
|
+
Button,
|
|
39229
|
+
{
|
|
39230
|
+
variant: "outlined",
|
|
39231
|
+
onClick: onEditSelectedWord,
|
|
39232
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(EditIcon, {}),
|
|
39233
|
+
size: "small",
|
|
39234
|
+
disabled: !hasSelectedWords || selectedWordCount !== 1,
|
|
39235
|
+
children: "Edit Word"
|
|
39236
|
+
}
|
|
39237
|
+
),
|
|
39238
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
39239
|
+
Button,
|
|
39240
|
+
{
|
|
39241
|
+
variant: "outlined",
|
|
39242
|
+
color: "error",
|
|
39243
|
+
onClick: onDeleteSelected,
|
|
39244
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(DeleteIcon, {}),
|
|
39245
|
+
size: "small",
|
|
39246
|
+
disabled: !hasSelectedWords || isManualSyncing && !isPaused,
|
|
39247
|
+
children: [
|
|
39248
|
+
"Delete",
|
|
39249
|
+
hasSelectedWords && selectedWordCount > 0 ? ` (${selectedWordCount})` : ""
|
|
39250
|
+
]
|
|
39251
|
+
}
|
|
39252
|
+
)
|
|
39253
|
+
] })
|
|
39254
|
+
] });
|
|
39255
|
+
});
|
|
39256
|
+
const MIN_ZOOM_SECONDS = 4.5;
|
|
39257
|
+
const MAX_ZOOM_SECONDS = 24;
|
|
39258
|
+
const ZOOM_STEPS = 50;
|
|
39259
|
+
function getAllWords(segments) {
|
|
39260
|
+
return segments.flatMap((s) => s.words);
|
|
39261
|
+
}
|
|
39262
|
+
function cloneSegments(segments) {
|
|
39263
|
+
return JSON.parse(JSON.stringify(segments));
|
|
39264
|
+
}
|
|
39265
|
+
const LyricsSynchronizer = reactExports.memo(function LyricsSynchronizer2({
|
|
39266
|
+
segments: initialSegments,
|
|
39267
|
+
currentTime,
|
|
39268
|
+
onPlaySegment,
|
|
39269
|
+
onSave,
|
|
39270
|
+
onCancel,
|
|
39271
|
+
setModalSpacebarHandler
|
|
39272
|
+
}) {
|
|
39273
|
+
const [workingSegments, setWorkingSegments] = reactExports.useState(
|
|
39274
|
+
() => cloneSegments(initialSegments)
|
|
39275
|
+
);
|
|
39276
|
+
const allWords = reactExports.useMemo(() => getAllWords(workingSegments), [workingSegments]);
|
|
39277
|
+
const audioDuration = reactExports.useMemo(() => {
|
|
39278
|
+
if (window.getAudioDuration) {
|
|
39279
|
+
const duration2 = window.getAudioDuration();
|
|
39280
|
+
return duration2 > 0 ? duration2 : 300;
|
|
39281
|
+
}
|
|
39282
|
+
return 300;
|
|
39283
|
+
}, []);
|
|
39284
|
+
const [zoomSeconds, setZoomSeconds] = reactExports.useState(12);
|
|
39285
|
+
const [visibleStartTime, setVisibleStartTime] = reactExports.useState(0);
|
|
39286
|
+
const visibleEndTime = reactExports.useMemo(
|
|
39287
|
+
() => Math.min(visibleStartTime + zoomSeconds, audioDuration),
|
|
39288
|
+
[visibleStartTime, zoomSeconds, audioDuration]
|
|
39289
|
+
);
|
|
39290
|
+
const [isManualSyncing, setIsManualSyncing] = reactExports.useState(false);
|
|
39291
|
+
const [isPaused, setIsPaused] = reactExports.useState(false);
|
|
39292
|
+
const [syncWordIndex, setSyncWordIndex] = reactExports.useState(-1);
|
|
39293
|
+
const [isSpacebarPressed, setIsSpacebarPressed] = reactExports.useState(false);
|
|
39294
|
+
const wordStartTimeRef = reactExports.useRef(null);
|
|
39295
|
+
const spacebarPressTimeRef = reactExports.useRef(null);
|
|
39296
|
+
const currentTimeRef = reactExports.useRef(currentTime);
|
|
39297
|
+
const [selectedWordIds, setSelectedWordIds] = reactExports.useState(/* @__PURE__ */ new Set());
|
|
39298
|
+
const [showEditLyricsModal, setShowEditLyricsModal] = reactExports.useState(false);
|
|
39299
|
+
const [editLyricsText, setEditLyricsText] = reactExports.useState("");
|
|
39300
|
+
const [showEditWordModal, setShowEditWordModal] = reactExports.useState(false);
|
|
39301
|
+
const [editWordText, setEditWordText] = reactExports.useState("");
|
|
39302
|
+
const [editWordId, setEditWordId] = reactExports.useState(null);
|
|
39303
|
+
reactExports.useEffect(() => {
|
|
39304
|
+
currentTimeRef.current = currentTime;
|
|
39305
|
+
}, [currentTime]);
|
|
39306
|
+
reactExports.useEffect(() => {
|
|
39307
|
+
if (isManualSyncing && !isPaused && currentTime > 0) {
|
|
39308
|
+
if (currentTime > visibleEndTime - zoomSeconds * 0.1) {
|
|
39309
|
+
const newStart = Math.max(0, currentTime - zoomSeconds * 0.1);
|
|
39310
|
+
setVisibleStartTime(newStart);
|
|
39311
|
+
} else if (currentTime < visibleStartTime) {
|
|
39312
|
+
setVisibleStartTime(Math.max(0, currentTime - 1));
|
|
39313
|
+
}
|
|
39314
|
+
}
|
|
39315
|
+
}, [currentTime, isManualSyncing, isPaused, visibleStartTime, visibleEndTime, zoomSeconds]);
|
|
39316
|
+
const handleZoomChange = reactExports.useCallback((_, value) => {
|
|
39317
|
+
const zoomValue = value;
|
|
39318
|
+
const newZoomSeconds = MIN_ZOOM_SECONDS + zoomValue / ZOOM_STEPS * (MAX_ZOOM_SECONDS - MIN_ZOOM_SECONDS);
|
|
39319
|
+
setZoomSeconds(newZoomSeconds);
|
|
39320
|
+
}, []);
|
|
39321
|
+
const sliderValue = reactExports.useMemo(() => {
|
|
39322
|
+
return (zoomSeconds - MIN_ZOOM_SECONDS) / (MAX_ZOOM_SECONDS - MIN_ZOOM_SECONDS) * ZOOM_STEPS;
|
|
39323
|
+
}, [zoomSeconds]);
|
|
39324
|
+
const handleScrollChange = reactExports.useCallback((newStartTime) => {
|
|
39325
|
+
setVisibleStartTime(newStartTime);
|
|
39326
|
+
}, []);
|
|
39327
|
+
const updateWords = reactExports.useCallback((newWords) => {
|
|
39328
|
+
setWorkingSegments((prevSegments) => {
|
|
39329
|
+
const newSegments = cloneSegments(prevSegments);
|
|
39330
|
+
const wordMap = new Map(newWords.map((w) => [w.id, w]));
|
|
39331
|
+
for (const segment of newSegments) {
|
|
39332
|
+
segment.words = segment.words.map((w) => wordMap.get(w.id) || w);
|
|
39333
|
+
const timedWords = segment.words.filter(
|
|
39334
|
+
(w) => w.start_time !== null && w.end_time !== null
|
|
39335
|
+
);
|
|
39336
|
+
if (timedWords.length > 0) {
|
|
39337
|
+
segment.start_time = Math.min(...timedWords.map((w) => w.start_time));
|
|
39338
|
+
segment.end_time = Math.max(...timedWords.map((w) => w.end_time));
|
|
39339
|
+
} else {
|
|
39340
|
+
segment.start_time = null;
|
|
39341
|
+
segment.end_time = null;
|
|
39342
|
+
}
|
|
39343
|
+
}
|
|
39344
|
+
return newSegments;
|
|
39345
|
+
});
|
|
39346
|
+
}, []);
|
|
39347
|
+
const [isPlaying, setIsPlaying] = reactExports.useState(false);
|
|
39348
|
+
reactExports.useEffect(() => {
|
|
39349
|
+
const checkPlaying = () => {
|
|
39350
|
+
setIsPlaying(window.isAudioPlaying || false);
|
|
39351
|
+
};
|
|
39352
|
+
checkPlaying();
|
|
39353
|
+
const interval = setInterval(checkPlaying, 100);
|
|
39354
|
+
return () => clearInterval(interval);
|
|
39355
|
+
}, []);
|
|
39356
|
+
const handlePlayAudio = reactExports.useCallback(() => {
|
|
39357
|
+
if (onPlaySegment) {
|
|
39358
|
+
onPlaySegment(currentTimeRef.current);
|
|
39359
|
+
}
|
|
39360
|
+
}, [onPlaySegment]);
|
|
39361
|
+
const handleStopAudio = reactExports.useCallback(() => {
|
|
39362
|
+
if (window.toggleAudioPlayback && window.isAudioPlaying) {
|
|
39363
|
+
window.toggleAudioPlayback();
|
|
39364
|
+
}
|
|
39365
|
+
if (isManualSyncing) {
|
|
39366
|
+
setIsManualSyncing(false);
|
|
39367
|
+
setIsPaused(false);
|
|
39368
|
+
setIsSpacebarPressed(false);
|
|
39369
|
+
}
|
|
39370
|
+
}, [isManualSyncing]);
|
|
39371
|
+
const handleStartSync = reactExports.useCallback(() => {
|
|
39372
|
+
if (isManualSyncing) {
|
|
39373
|
+
setIsManualSyncing(false);
|
|
39374
|
+
setIsPaused(false);
|
|
39375
|
+
setSyncWordIndex(-1);
|
|
39376
|
+
setIsSpacebarPressed(false);
|
|
39377
|
+
handleStopAudio();
|
|
39378
|
+
return;
|
|
39379
|
+
}
|
|
39380
|
+
const firstUnsyncedIndex = allWords.findIndex(
|
|
39381
|
+
(w) => w.start_time === null || w.end_time === null
|
|
39382
|
+
);
|
|
39383
|
+
const startIndex = firstUnsyncedIndex !== -1 ? firstUnsyncedIndex : 0;
|
|
39384
|
+
setIsManualSyncing(true);
|
|
39385
|
+
setIsPaused(false);
|
|
39386
|
+
setSyncWordIndex(startIndex);
|
|
39387
|
+
setIsSpacebarPressed(false);
|
|
39388
|
+
if (onPlaySegment) {
|
|
39389
|
+
onPlaySegment(Math.max(0, currentTimeRef.current - 1));
|
|
39390
|
+
}
|
|
39391
|
+
}, [isManualSyncing, allWords, onPlaySegment, handleStopAudio]);
|
|
39392
|
+
const handlePauseSync = reactExports.useCallback(() => {
|
|
39393
|
+
setIsPaused(true);
|
|
39394
|
+
handleStopAudio();
|
|
39395
|
+
}, [handleStopAudio]);
|
|
39396
|
+
const handleResumeSync = reactExports.useCallback(() => {
|
|
39397
|
+
setIsPaused(false);
|
|
39398
|
+
const firstUnsyncedIndex = allWords.findIndex(
|
|
39399
|
+
(w) => w.start_time === null || w.end_time === null
|
|
39400
|
+
);
|
|
39401
|
+
if (firstUnsyncedIndex !== -1 && firstUnsyncedIndex !== syncWordIndex) {
|
|
39402
|
+
setSyncWordIndex(firstUnsyncedIndex);
|
|
39403
|
+
}
|
|
39404
|
+
if (onPlaySegment) {
|
|
39405
|
+
onPlaySegment(currentTimeRef.current);
|
|
39406
|
+
}
|
|
39407
|
+
}, [allWords, syncWordIndex, onPlaySegment]);
|
|
39408
|
+
const handleClearSync = reactExports.useCallback(() => {
|
|
39409
|
+
setWorkingSegments((prevSegments) => {
|
|
39410
|
+
const newSegments = cloneSegments(prevSegments);
|
|
39411
|
+
for (const segment of newSegments) {
|
|
39412
|
+
for (const word of segment.words) {
|
|
39413
|
+
word.start_time = null;
|
|
39414
|
+
word.end_time = null;
|
|
39415
|
+
}
|
|
39416
|
+
segment.start_time = null;
|
|
39417
|
+
segment.end_time = null;
|
|
39418
|
+
}
|
|
39419
|
+
return newSegments;
|
|
39420
|
+
});
|
|
39421
|
+
setSyncWordIndex(-1);
|
|
39422
|
+
}, []);
|
|
39423
|
+
const handleUnsyncFromCursor = reactExports.useCallback(() => {
|
|
39424
|
+
const cursorTime = currentTimeRef.current;
|
|
39425
|
+
setWorkingSegments((prevSegments) => {
|
|
39426
|
+
const newSegments = cloneSegments(prevSegments);
|
|
39427
|
+
for (const segment of newSegments) {
|
|
39428
|
+
for (const word of segment.words) {
|
|
39429
|
+
if (word.start_time !== null && word.start_time > cursorTime) {
|
|
39430
|
+
word.start_time = null;
|
|
39431
|
+
word.end_time = null;
|
|
39432
|
+
}
|
|
39433
|
+
}
|
|
39434
|
+
const timedWords = segment.words.filter(
|
|
39435
|
+
(w) => w.start_time !== null && w.end_time !== null
|
|
39436
|
+
);
|
|
39437
|
+
if (timedWords.length > 0) {
|
|
39438
|
+
segment.start_time = Math.min(...timedWords.map((w) => w.start_time));
|
|
39439
|
+
segment.end_time = Math.max(...timedWords.map((w) => w.end_time));
|
|
39440
|
+
} else {
|
|
39441
|
+
segment.start_time = null;
|
|
39442
|
+
segment.end_time = null;
|
|
39443
|
+
}
|
|
39444
|
+
}
|
|
39445
|
+
return newSegments;
|
|
39446
|
+
});
|
|
39447
|
+
}, []);
|
|
39448
|
+
const canUnsyncFromCursor = reactExports.useMemo(() => {
|
|
39449
|
+
const cursorTime = currentTimeRef.current;
|
|
39450
|
+
return allWords.some(
|
|
39451
|
+
(w) => w.start_time !== null && w.start_time > cursorTime
|
|
39452
|
+
);
|
|
39453
|
+
}, [allWords, currentTime]);
|
|
39454
|
+
const handleEditLyrics = reactExports.useCallback(() => {
|
|
39455
|
+
const text = workingSegments.map((s) => s.text).join("\n");
|
|
39456
|
+
setEditLyricsText(text);
|
|
39457
|
+
setShowEditLyricsModal(true);
|
|
39458
|
+
}, [workingSegments]);
|
|
39459
|
+
const handleSaveEditedLyrics = reactExports.useCallback(() => {
|
|
39460
|
+
const lines = editLyricsText.split("\n").filter((l) => l.trim());
|
|
39461
|
+
const newSegments = lines.map((line2, idx) => {
|
|
39462
|
+
const words = line2.trim().split(/\s+/).map((text, wIdx) => ({
|
|
39463
|
+
id: `word-${idx}-${wIdx}-${Date.now()}`,
|
|
39464
|
+
text,
|
|
39465
|
+
start_time: null,
|
|
39466
|
+
end_time: null,
|
|
39467
|
+
confidence: 1
|
|
39468
|
+
}));
|
|
39469
|
+
return {
|
|
39470
|
+
id: `segment-${idx}-${Date.now()}`,
|
|
39471
|
+
text: line2.trim(),
|
|
39472
|
+
words,
|
|
39473
|
+
start_time: null,
|
|
39474
|
+
end_time: null
|
|
39475
|
+
};
|
|
39476
|
+
});
|
|
39477
|
+
setWorkingSegments(newSegments);
|
|
39478
|
+
setShowEditLyricsModal(false);
|
|
39479
|
+
setSyncWordIndex(-1);
|
|
39480
|
+
}, [editLyricsText]);
|
|
39481
|
+
const handleEditSelectedWord = reactExports.useCallback(() => {
|
|
39482
|
+
if (selectedWordIds.size !== 1) return;
|
|
39483
|
+
const wordId = Array.from(selectedWordIds)[0];
|
|
39484
|
+
const word = allWords.find((w) => w.id === wordId);
|
|
39485
|
+
if (word) {
|
|
39486
|
+
setEditWordId(wordId);
|
|
39487
|
+
setEditWordText(word.text);
|
|
39488
|
+
setShowEditWordModal(true);
|
|
39489
|
+
}
|
|
39490
|
+
}, [selectedWordIds, allWords]);
|
|
39491
|
+
const handleSaveEditedWord = reactExports.useCallback(() => {
|
|
39492
|
+
if (!editWordId) return;
|
|
39493
|
+
const newText = editWordText.trim();
|
|
39494
|
+
if (!newText) return;
|
|
39495
|
+
const newWords = newText.split(/\s+/);
|
|
39496
|
+
if (newWords.length === 1) {
|
|
39497
|
+
const updatedWords = allWords.map(
|
|
39498
|
+
(w) => w.id === editWordId ? { ...w, text: newWords[0] } : w
|
|
39499
|
+
);
|
|
39500
|
+
updateWords(updatedWords);
|
|
39501
|
+
} else {
|
|
39502
|
+
const originalWord = allWords.find((w) => w.id === editWordId);
|
|
39503
|
+
if (!originalWord) return;
|
|
39504
|
+
setWorkingSegments((prevSegments) => {
|
|
39505
|
+
const newSegments = cloneSegments(prevSegments);
|
|
39506
|
+
for (const segment of newSegments) {
|
|
39507
|
+
const wordIndex = segment.words.findIndex((w) => w.id === editWordId);
|
|
39508
|
+
if (wordIndex !== -1) {
|
|
39509
|
+
const newWordObjects = newWords.map((text, idx) => ({
|
|
39510
|
+
id: idx === 0 ? editWordId : `${editWordId}-split-${idx}`,
|
|
39511
|
+
text,
|
|
39512
|
+
start_time: idx === 0 ? originalWord.start_time : null,
|
|
39513
|
+
end_time: idx === 0 ? originalWord.end_time : null,
|
|
39514
|
+
confidence: 1
|
|
39515
|
+
}));
|
|
39516
|
+
segment.words.splice(wordIndex, 1, ...newWordObjects);
|
|
39517
|
+
segment.text = segment.words.map((w) => w.text).join(" ");
|
|
39518
|
+
break;
|
|
39519
|
+
}
|
|
39520
|
+
}
|
|
39521
|
+
return newSegments;
|
|
39522
|
+
});
|
|
39523
|
+
}
|
|
39524
|
+
setShowEditWordModal(false);
|
|
39525
|
+
setEditWordId(null);
|
|
39526
|
+
setEditWordText("");
|
|
39527
|
+
setSelectedWordIds(/* @__PURE__ */ new Set());
|
|
39528
|
+
}, [editWordId, editWordText, allWords, updateWords]);
|
|
39529
|
+
const handleDeleteSelected = reactExports.useCallback(() => {
|
|
39530
|
+
if (selectedWordIds.size === 0) return;
|
|
39531
|
+
setWorkingSegments((prevSegments) => {
|
|
39532
|
+
const newSegments = cloneSegments(prevSegments);
|
|
39533
|
+
for (const segment of newSegments) {
|
|
39534
|
+
segment.words = segment.words.filter((w) => !selectedWordIds.has(w.id));
|
|
39535
|
+
segment.text = segment.words.map((w) => w.text).join(" ");
|
|
39536
|
+
const timedWords = segment.words.filter(
|
|
39537
|
+
(w) => w.start_time !== null && w.end_time !== null
|
|
39538
|
+
);
|
|
39539
|
+
if (timedWords.length > 0) {
|
|
39540
|
+
segment.start_time = Math.min(...timedWords.map((w) => w.start_time));
|
|
39541
|
+
segment.end_time = Math.max(...timedWords.map((w) => w.end_time));
|
|
39542
|
+
} else {
|
|
39543
|
+
segment.start_time = null;
|
|
39544
|
+
segment.end_time = null;
|
|
39545
|
+
}
|
|
39546
|
+
}
|
|
39547
|
+
return newSegments.filter((s) => s.words.length > 0);
|
|
39548
|
+
});
|
|
39549
|
+
setSelectedWordIds(/* @__PURE__ */ new Set());
|
|
39550
|
+
}, [selectedWordIds]);
|
|
39551
|
+
const handleWordClick = reactExports.useCallback((wordId, event) => {
|
|
39552
|
+
if (event.shiftKey || event.ctrlKey || event.metaKey) {
|
|
39553
|
+
setSelectedWordIds((prev2) => {
|
|
39554
|
+
const newSet = new Set(prev2);
|
|
39555
|
+
if (newSet.has(wordId)) {
|
|
39556
|
+
newSet.delete(wordId);
|
|
39557
|
+
} else {
|
|
39558
|
+
newSet.add(wordId);
|
|
39559
|
+
}
|
|
39560
|
+
return newSet;
|
|
39561
|
+
});
|
|
39562
|
+
} else {
|
|
39563
|
+
setSelectedWordIds(/* @__PURE__ */ new Set([wordId]));
|
|
39564
|
+
}
|
|
39565
|
+
}, []);
|
|
39566
|
+
const handleBackgroundClick = reactExports.useCallback(() => {
|
|
39567
|
+
setSelectedWordIds(/* @__PURE__ */ new Set());
|
|
39568
|
+
}, []);
|
|
39569
|
+
const handleWordTimingChange = reactExports.useCallback((wordId, newStartTime, newEndTime) => {
|
|
39570
|
+
setWorkingSegments((prevSegments) => {
|
|
39571
|
+
const newSegments = cloneSegments(prevSegments);
|
|
39572
|
+
for (const segment of newSegments) {
|
|
39573
|
+
const word = segment.words.find((w) => w.id === wordId);
|
|
39574
|
+
if (word) {
|
|
39575
|
+
word.start_time = Math.max(0, newStartTime);
|
|
39576
|
+
word.end_time = Math.max(word.start_time + 0.05, newEndTime);
|
|
39577
|
+
const timedWords = segment.words.filter(
|
|
39578
|
+
(w) => w.start_time !== null && w.end_time !== null
|
|
39579
|
+
);
|
|
39580
|
+
if (timedWords.length > 0) {
|
|
39581
|
+
segment.start_time = Math.min(...timedWords.map((w) => w.start_time));
|
|
39582
|
+
segment.end_time = Math.max(...timedWords.map((w) => w.end_time));
|
|
39583
|
+
}
|
|
39584
|
+
break;
|
|
39585
|
+
}
|
|
39586
|
+
}
|
|
39587
|
+
return newSegments;
|
|
39588
|
+
});
|
|
39589
|
+
}, []);
|
|
39590
|
+
const handleWordsMove = reactExports.useCallback((updates) => {
|
|
39591
|
+
setWorkingSegments((prevSegments) => {
|
|
39592
|
+
const newSegments = cloneSegments(prevSegments);
|
|
39593
|
+
const updateMap = new Map(updates.map((u) => [u.wordId, u]));
|
|
39594
|
+
for (const segment of newSegments) {
|
|
39595
|
+
for (const word of segment.words) {
|
|
39596
|
+
const update = updateMap.get(word.id);
|
|
39597
|
+
if (update) {
|
|
39598
|
+
word.start_time = update.newStartTime;
|
|
39599
|
+
word.end_time = update.newEndTime;
|
|
39600
|
+
}
|
|
39601
|
+
}
|
|
39602
|
+
const timedWords = segment.words.filter(
|
|
39603
|
+
(w) => w.start_time !== null && w.end_time !== null
|
|
39604
|
+
);
|
|
39605
|
+
if (timedWords.length > 0) {
|
|
39606
|
+
segment.start_time = Math.min(...timedWords.map((w) => w.start_time));
|
|
39607
|
+
segment.end_time = Math.max(...timedWords.map((w) => w.end_time));
|
|
39608
|
+
}
|
|
39609
|
+
}
|
|
39610
|
+
return newSegments;
|
|
39611
|
+
});
|
|
39612
|
+
}, []);
|
|
39613
|
+
const handleTimeBarClick = reactExports.useCallback((time) => {
|
|
39614
|
+
const newStart = Math.max(0, time - zoomSeconds / 2);
|
|
39615
|
+
setVisibleStartTime(Math.min(newStart, Math.max(0, audioDuration - zoomSeconds)));
|
|
39616
|
+
if (onPlaySegment) {
|
|
39617
|
+
onPlaySegment(time);
|
|
39618
|
+
setTimeout(() => {
|
|
39619
|
+
if (window.toggleAudioPlayback && window.isAudioPlaying) {
|
|
39620
|
+
window.toggleAudioPlayback();
|
|
39621
|
+
}
|
|
39622
|
+
}, 50);
|
|
39623
|
+
}
|
|
39624
|
+
}, [zoomSeconds, audioDuration, onPlaySegment]);
|
|
39625
|
+
const handleSelectionComplete = reactExports.useCallback((wordIds) => {
|
|
39626
|
+
setSelectedWordIds(new Set(wordIds));
|
|
39627
|
+
}, []);
|
|
39628
|
+
const handleKeyDown = reactExports.useCallback((e) => {
|
|
39629
|
+
if (e.code !== "Space") return;
|
|
39630
|
+
if (!isManualSyncing || isPaused) return;
|
|
39631
|
+
if (syncWordIndex < 0 || syncWordIndex >= allWords.length) return;
|
|
39632
|
+
e.preventDefault();
|
|
39633
|
+
e.stopPropagation();
|
|
39634
|
+
if (isSpacebarPressed) return;
|
|
39635
|
+
setIsSpacebarPressed(true);
|
|
39636
|
+
wordStartTimeRef.current = currentTimeRef.current;
|
|
39637
|
+
spacebarPressTimeRef.current = Date.now();
|
|
39638
|
+
const newWords = [...allWords];
|
|
39639
|
+
const currentWord = newWords[syncWordIndex];
|
|
39640
|
+
currentWord.start_time = currentTimeRef.current;
|
|
39641
|
+
if (syncWordIndex > 0) {
|
|
39642
|
+
const prevWord = newWords[syncWordIndex - 1];
|
|
39643
|
+
if (prevWord.start_time !== null && prevWord.end_time === null) {
|
|
39644
|
+
const gap2 = currentTimeRef.current - prevWord.start_time;
|
|
39645
|
+
if (gap2 > 1) {
|
|
39646
|
+
prevWord.end_time = prevWord.start_time + 0.5;
|
|
39647
|
+
} else {
|
|
39648
|
+
prevWord.end_time = currentTimeRef.current - 5e-3;
|
|
39649
|
+
}
|
|
39650
|
+
}
|
|
39651
|
+
}
|
|
39652
|
+
updateWords(newWords);
|
|
39653
|
+
}, [isManualSyncing, isPaused, syncWordIndex, allWords, isSpacebarPressed, updateWords]);
|
|
39654
|
+
const handleKeyUp = reactExports.useCallback((e) => {
|
|
39655
|
+
if (e.code !== "Space") return;
|
|
39656
|
+
if (!isManualSyncing || isPaused) return;
|
|
39657
|
+
if (!isSpacebarPressed) return;
|
|
39658
|
+
e.preventDefault();
|
|
39659
|
+
e.stopPropagation();
|
|
39660
|
+
setIsSpacebarPressed(false);
|
|
39661
|
+
const pressDuration = spacebarPressTimeRef.current ? Date.now() - spacebarPressTimeRef.current : 0;
|
|
39662
|
+
const isTap = pressDuration < 200;
|
|
39663
|
+
const newWords = [...allWords];
|
|
39664
|
+
const currentWord = newWords[syncWordIndex];
|
|
39665
|
+
if (isTap) {
|
|
39666
|
+
currentWord.end_time = (wordStartTimeRef.current || currentTimeRef.current) + 0.5;
|
|
39667
|
+
} else {
|
|
39668
|
+
currentWord.end_time = currentTimeRef.current;
|
|
39669
|
+
}
|
|
39670
|
+
updateWords(newWords);
|
|
39671
|
+
if (syncWordIndex < allWords.length - 1) {
|
|
39672
|
+
setSyncWordIndex(syncWordIndex + 1);
|
|
39673
|
+
} else {
|
|
39674
|
+
setIsManualSyncing(false);
|
|
39675
|
+
setSyncWordIndex(-1);
|
|
39676
|
+
handleStopAudio();
|
|
39677
|
+
}
|
|
39678
|
+
wordStartTimeRef.current = null;
|
|
39679
|
+
spacebarPressTimeRef.current = null;
|
|
39680
|
+
}, [isManualSyncing, isPaused, isSpacebarPressed, syncWordIndex, allWords, updateWords, handleStopAudio]);
|
|
39681
|
+
const handleSpacebar = reactExports.useCallback((e) => {
|
|
39682
|
+
if (e.type === "keydown") {
|
|
39683
|
+
handleKeyDown(e);
|
|
39684
|
+
} else if (e.type === "keyup") {
|
|
39685
|
+
handleKeyUp(e);
|
|
39686
|
+
}
|
|
39687
|
+
}, [handleKeyDown, handleKeyUp]);
|
|
39688
|
+
const spacebarHandlerRef = reactExports.useRef(handleSpacebar);
|
|
39689
|
+
spacebarHandlerRef.current = handleSpacebar;
|
|
39690
|
+
reactExports.useEffect(() => {
|
|
39691
|
+
const handler = (e) => {
|
|
39692
|
+
if (e.code === "Space") {
|
|
39693
|
+
e.preventDefault();
|
|
39694
|
+
e.stopPropagation();
|
|
39695
|
+
spacebarHandlerRef.current(e);
|
|
39696
|
+
}
|
|
39697
|
+
};
|
|
39698
|
+
setModalSpacebarHandler(() => handler);
|
|
39699
|
+
return () => {
|
|
39700
|
+
setModalSpacebarHandler(void 0);
|
|
39701
|
+
};
|
|
39702
|
+
}, [setModalSpacebarHandler]);
|
|
39703
|
+
const handleSave = reactExports.useCallback(() => {
|
|
39704
|
+
onSave(workingSegments);
|
|
39705
|
+
}, [workingSegments, onSave]);
|
|
39706
|
+
const stats = reactExports.useMemo(() => {
|
|
39707
|
+
const total = allWords.length;
|
|
39708
|
+
const synced = allWords.filter(
|
|
39709
|
+
(w) => w.start_time !== null && w.end_time !== null
|
|
39710
|
+
).length;
|
|
39711
|
+
return { total, synced, remaining: total - synced };
|
|
39712
|
+
}, [allWords]);
|
|
39713
|
+
const getInstructionText = reactExports.useCallback(() => {
|
|
39714
|
+
if (isManualSyncing) {
|
|
39715
|
+
if (isSpacebarPressed) {
|
|
39716
|
+
return { primary: "⏱️ Holding... release when word ends", secondary: "Release spacebar when the word finishes" };
|
|
39717
|
+
}
|
|
39718
|
+
if (stats.remaining === 0) {
|
|
39719
|
+
return { primary: "✅ All words synced!", secondary: 'Click "Stop Sync" then "Apply" to save' };
|
|
39720
|
+
}
|
|
39721
|
+
return { primary: "👆 Press SPACEBAR when you hear each word", secondary: "Tap for short words, hold for longer words" };
|
|
39722
|
+
}
|
|
39723
|
+
if (stats.synced === 0) {
|
|
39724
|
+
return { primary: 'Click "Start Sync" to begin timing words', secondary: "Audio will play and you'll tap spacebar for each word" };
|
|
39725
|
+
}
|
|
39726
|
+
if (stats.remaining > 0) {
|
|
39727
|
+
return { primary: `${stats.remaining} words remaining to sync`, secondary: 'Click "Start Sync" to continue, or "Unsync from Cursor" to re-sync from a point' };
|
|
39728
|
+
}
|
|
39729
|
+
return { primary: "✅ All words synced!", secondary: 'Click "Apply" to save changes, or make adjustments first' };
|
|
39730
|
+
}, [isManualSyncing, isSpacebarPressed, stats.synced, stats.remaining]);
|
|
39731
|
+
const instruction = getInstructionText();
|
|
39732
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", flexDirection: "column", height: "100%", gap: 1 }, children: [
|
|
39733
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { display: "flex", justifyContent: "flex-end", alignItems: "center", height: 24 }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Typography, { variant: "body2", color: "text.secondary", children: [
|
|
39734
|
+
stats.synced,
|
|
39735
|
+
" / ",
|
|
39736
|
+
stats.total,
|
|
39737
|
+
" words synced",
|
|
39738
|
+
stats.remaining > 0 && ` (${stats.remaining} remaining)`
|
|
39739
|
+
] }) }),
|
|
39740
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39741
|
+
Box,
|
|
39742
|
+
{
|
|
39743
|
+
sx: {
|
|
39744
|
+
height: 56,
|
|
39745
|
+
flexShrink: 0
|
|
39746
|
+
},
|
|
39747
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
39748
|
+
Paper,
|
|
39749
|
+
{
|
|
39750
|
+
sx: {
|
|
39751
|
+
p: 1.5,
|
|
39752
|
+
height: "100%",
|
|
39753
|
+
bgcolor: isManualSyncing ? "info.main" : "grey.100",
|
|
39754
|
+
color: isManualSyncing ? "info.contrastText" : "text.primary",
|
|
39755
|
+
display: "flex",
|
|
39756
|
+
flexDirection: "column",
|
|
39757
|
+
justifyContent: "center",
|
|
39758
|
+
overflow: "hidden",
|
|
39759
|
+
boxSizing: "border-box"
|
|
39760
|
+
},
|
|
39761
|
+
children: [
|
|
39762
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body2", sx: { fontWeight: 500, lineHeight: 1.3 }, children: instruction.primary }),
|
|
39763
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "caption", sx: { opacity: 0.85, display: "block", lineHeight: 1.3 }, children: instruction.secondary })
|
|
39764
|
+
]
|
|
39765
|
+
}
|
|
39766
|
+
)
|
|
39767
|
+
}
|
|
39768
|
+
),
|
|
39769
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { height: 88, flexShrink: 0 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39770
|
+
SyncControls,
|
|
39771
|
+
{
|
|
39772
|
+
isManualSyncing,
|
|
39773
|
+
isPaused,
|
|
39774
|
+
onStartSync: handleStartSync,
|
|
39775
|
+
onPauseSync: handlePauseSync,
|
|
39776
|
+
onResumeSync: handleResumeSync,
|
|
39777
|
+
onClearSync: handleClearSync,
|
|
39778
|
+
onEditLyrics: handleEditLyrics,
|
|
39779
|
+
onPlay: handlePlayAudio,
|
|
39780
|
+
onStop: handleStopAudio,
|
|
39781
|
+
isPlaying,
|
|
39782
|
+
hasSelectedWords: selectedWordIds.size > 0,
|
|
39783
|
+
selectedWordCount: selectedWordIds.size,
|
|
39784
|
+
onUnsyncFromCursor: handleUnsyncFromCursor,
|
|
39785
|
+
onEditSelectedWord: handleEditSelectedWord,
|
|
39786
|
+
onDeleteSelected: handleDeleteSelected,
|
|
39787
|
+
canUnsyncFromCursor
|
|
39788
|
+
}
|
|
39789
|
+
) }),
|
|
39790
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { height: 44, flexShrink: 0 }, children: isManualSyncing && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39791
|
+
UpcomingWordsBar,
|
|
39792
|
+
{
|
|
39793
|
+
words: allWords,
|
|
39794
|
+
syncWordIndex,
|
|
39795
|
+
isManualSyncing
|
|
39796
|
+
}
|
|
39797
|
+
) }),
|
|
39798
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { flexGrow: 1, minHeight: 200 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39799
|
+
TimelineCanvas,
|
|
39800
|
+
{
|
|
39801
|
+
words: allWords,
|
|
39802
|
+
segments: workingSegments,
|
|
39803
|
+
visibleStartTime,
|
|
39804
|
+
visibleEndTime,
|
|
39805
|
+
currentTime,
|
|
39806
|
+
selectedWordIds,
|
|
39807
|
+
onWordClick: handleWordClick,
|
|
39808
|
+
onBackgroundClick: handleBackgroundClick,
|
|
39809
|
+
onTimeBarClick: handleTimeBarClick,
|
|
39810
|
+
onSelectionComplete: handleSelectionComplete,
|
|
39811
|
+
onWordTimingChange: handleWordTimingChange,
|
|
39812
|
+
onWordsMove: handleWordsMove,
|
|
39813
|
+
syncWordIndex,
|
|
39814
|
+
isManualSyncing,
|
|
39815
|
+
onScrollChange: handleScrollChange,
|
|
39816
|
+
audioDuration,
|
|
39817
|
+
zoomSeconds,
|
|
39818
|
+
height: 200
|
|
39819
|
+
}
|
|
39820
|
+
) }),
|
|
39821
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 2, px: 2 }, children: [
|
|
39822
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(ZoomInIcon, { color: "action", fontSize: "small" }),
|
|
39823
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39824
|
+
Slider,
|
|
39825
|
+
{
|
|
39826
|
+
value: sliderValue,
|
|
39827
|
+
onChange: handleZoomChange,
|
|
39828
|
+
min: 0,
|
|
39829
|
+
max: ZOOM_STEPS,
|
|
39830
|
+
step: 1,
|
|
39831
|
+
sx: { flexGrow: 1 },
|
|
39832
|
+
disabled: isManualSyncing && !isPaused
|
|
39833
|
+
}
|
|
39834
|
+
),
|
|
39835
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOutIcon, { color: "action", fontSize: "small" }),
|
|
39836
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Typography, { variant: "caption", color: "text.secondary", sx: { minWidth: 60 }, children: [
|
|
39837
|
+
zoomSeconds.toFixed(1),
|
|
39838
|
+
"s view"
|
|
39839
|
+
] })
|
|
39840
|
+
] }),
|
|
39841
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", justifyContent: "flex-end", gap: 2, pt: 2, borderTop: 1, borderColor: "divider" }, children: [
|
|
39842
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: onCancel, color: "inherit", children: "Cancel" }),
|
|
39843
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39844
|
+
Button,
|
|
39845
|
+
{
|
|
39846
|
+
onClick: handleSave,
|
|
39847
|
+
variant: "contained",
|
|
39848
|
+
color: "primary",
|
|
39849
|
+
disabled: isManualSyncing && !isPaused,
|
|
39850
|
+
children: "Apply"
|
|
39851
|
+
}
|
|
39852
|
+
)
|
|
39853
|
+
] }),
|
|
39854
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
39855
|
+
Dialog,
|
|
39856
|
+
{
|
|
39857
|
+
open: showEditLyricsModal,
|
|
39858
|
+
onClose: () => setShowEditLyricsModal(false),
|
|
39859
|
+
maxWidth: "md",
|
|
39860
|
+
fullWidth: true,
|
|
39861
|
+
children: [
|
|
39862
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(DialogTitle, { children: "Edit Lyrics" }),
|
|
39863
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { children: [
|
|
39864
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: "Editing lyrics will reset all timing data. You will need to re-sync the entire song." }),
|
|
39865
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39866
|
+
TextField,
|
|
39867
|
+
{
|
|
39868
|
+
multiline: true,
|
|
39869
|
+
rows: 15,
|
|
39870
|
+
fullWidth: true,
|
|
39871
|
+
value: editLyricsText,
|
|
39872
|
+
onChange: (e) => setEditLyricsText(e.target.value),
|
|
39873
|
+
placeholder: "Enter lyrics, one line per segment..."
|
|
39874
|
+
}
|
|
39875
|
+
)
|
|
39876
|
+
] }),
|
|
39877
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogActions, { children: [
|
|
39878
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: () => setShowEditLyricsModal(false), children: "Cancel" }),
|
|
39879
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: handleSaveEditedLyrics, variant: "contained", color: "warning", children: "Save & Reset Timing" })
|
|
39880
|
+
] })
|
|
39881
|
+
]
|
|
39882
|
+
}
|
|
39883
|
+
),
|
|
39884
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
39885
|
+
Dialog,
|
|
39886
|
+
{
|
|
39887
|
+
open: showEditWordModal,
|
|
39888
|
+
onClose: () => setShowEditWordModal(false),
|
|
39889
|
+
maxWidth: "xs",
|
|
39890
|
+
fullWidth: true,
|
|
39891
|
+
children: [
|
|
39892
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(DialogTitle, { children: "Edit Word" }),
|
|
39893
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { children: [
|
|
39894
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mb: 2 }, children: "Edit the word text. Enter multiple words separated by spaces to split." }),
|
|
39895
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39896
|
+
TextField,
|
|
39897
|
+
{
|
|
39898
|
+
fullWidth: true,
|
|
39899
|
+
value: editWordText,
|
|
39900
|
+
onChange: (e) => setEditWordText(e.target.value),
|
|
39901
|
+
autoFocus: true,
|
|
39902
|
+
onKeyDown: (e) => {
|
|
39903
|
+
if (e.key === "Enter") {
|
|
39904
|
+
handleSaveEditedWord();
|
|
39905
|
+
}
|
|
39906
|
+
}
|
|
39907
|
+
}
|
|
39908
|
+
)
|
|
39909
|
+
] }),
|
|
39910
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogActions, { children: [
|
|
39911
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: () => setShowEditWordModal(false), children: "Cancel" }),
|
|
39912
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: handleSaveEditedWord, variant: "contained", children: "Save" })
|
|
39913
|
+
] })
|
|
39914
|
+
]
|
|
39915
|
+
}
|
|
39916
|
+
)
|
|
39917
|
+
] });
|
|
39918
|
+
});
|
|
38431
39919
|
function ReplaceAllLyricsModal({
|
|
38432
39920
|
open,
|
|
38433
39921
|
onClose,
|
|
38434
39922
|
onSave,
|
|
38435
39923
|
onPlaySegment,
|
|
38436
39924
|
currentTime = 0,
|
|
38437
|
-
setModalSpacebarHandler
|
|
39925
|
+
setModalSpacebarHandler,
|
|
39926
|
+
existingSegments = []
|
|
38438
39927
|
}) {
|
|
39928
|
+
const [mode, setMode] = reactExports.useState("selection");
|
|
38439
39929
|
const [inputText, setInputText] = reactExports.useState("");
|
|
38440
|
-
const [
|
|
38441
|
-
|
|
38442
|
-
|
|
38443
|
-
|
|
38444
|
-
|
|
38445
|
-
|
|
38446
|
-
const duration2 = window.getAudioDuration();
|
|
38447
|
-
return duration2 > 0 ? duration2 : 600;
|
|
39930
|
+
const [newSegments, setNewSegments] = reactExports.useState([]);
|
|
39931
|
+
reactExports.useEffect(() => {
|
|
39932
|
+
if (open) {
|
|
39933
|
+
setMode("selection");
|
|
39934
|
+
setInputText("");
|
|
39935
|
+
setNewSegments([]);
|
|
38448
39936
|
}
|
|
38449
|
-
|
|
38450
|
-
}, []);
|
|
39937
|
+
}, [open]);
|
|
38451
39938
|
const parseInfo = reactExports.useMemo(() => {
|
|
38452
39939
|
if (!inputText.trim()) return { lines: 0, words: 0 };
|
|
38453
39940
|
const lines = inputText.trim().split("\n").filter((line2) => line2.trim().length > 0);
|
|
@@ -38459,295 +39946,101 @@ function ReplaceAllLyricsModal({
|
|
|
38459
39946
|
const processLyrics = reactExports.useCallback(() => {
|
|
38460
39947
|
if (!inputText.trim()) return;
|
|
38461
39948
|
const lines = inputText.trim().split("\n").filter((line2) => line2.trim().length > 0);
|
|
38462
|
-
const
|
|
38463
|
-
const allWords = [];
|
|
39949
|
+
const segments = [];
|
|
38464
39950
|
lines.forEach((line2) => {
|
|
38465
39951
|
const words = line2.trim().split(/\s+/).filter((word) => word.length > 0);
|
|
38466
|
-
const segmentWords =
|
|
38467
|
-
words.forEach((wordText) => {
|
|
38468
|
-
const word = {
|
|
38469
|
-
id: nanoid(),
|
|
38470
|
-
text: wordText,
|
|
38471
|
-
start_time: null,
|
|
38472
|
-
end_time: null,
|
|
38473
|
-
confidence: 1,
|
|
38474
|
-
created_during_correction: true
|
|
38475
|
-
};
|
|
38476
|
-
segmentWords.push(word);
|
|
38477
|
-
allWords.push(word);
|
|
38478
|
-
});
|
|
38479
|
-
const segment = {
|
|
39952
|
+
const segmentWords = words.map((wordText) => ({
|
|
38480
39953
|
id: nanoid(),
|
|
38481
|
-
text:
|
|
38482
|
-
words: segmentWords,
|
|
39954
|
+
text: wordText,
|
|
38483
39955
|
start_time: null,
|
|
38484
|
-
end_time: null
|
|
38485
|
-
|
|
38486
|
-
|
|
38487
|
-
|
|
38488
|
-
|
|
38489
|
-
|
|
38490
|
-
|
|
38491
|
-
|
|
38492
|
-
|
|
38493
|
-
|
|
38494
|
-
|
|
38495
|
-
const globalSegment2 = {
|
|
38496
|
-
id: "global-replacement",
|
|
38497
|
-
text: allWords.map((w) => w.text).join(" "),
|
|
38498
|
-
words: allWords,
|
|
38499
|
-
start_time: 0,
|
|
38500
|
-
end_time: endTime
|
|
38501
|
-
};
|
|
38502
|
-
setCurrentSegments(newSegments);
|
|
38503
|
-
setOriginalSegments(JSON.parse(JSON.stringify(newSegments)));
|
|
38504
|
-
setGlobalSegment(globalSegment2);
|
|
38505
|
-
setIsReplaced(true);
|
|
38506
|
-
}, [inputText, getAudioDuration]);
|
|
38507
|
-
const handlePasteFromClipboard = reactExports.useCallback(async () => {
|
|
38508
|
-
try {
|
|
38509
|
-
const text = await navigator.clipboard.readText();
|
|
38510
|
-
setInputText(text);
|
|
38511
|
-
} catch (error) {
|
|
38512
|
-
console.error("Failed to read from clipboard:", error);
|
|
38513
|
-
alert("Failed to read from clipboard. Please paste manually.");
|
|
38514
|
-
}
|
|
38515
|
-
}, []);
|
|
38516
|
-
const updateSegment2 = reactExports.useCallback((newWords) => {
|
|
38517
|
-
if (!globalSegment) return;
|
|
38518
|
-
const validStartTimes = newWords.map((w) => w.start_time).filter((t) => t !== null);
|
|
38519
|
-
const validEndTimes = newWords.map((w) => w.end_time).filter((t) => t !== null);
|
|
38520
|
-
const segmentStartTime = validStartTimes.length > 0 ? Math.min(...validStartTimes) : null;
|
|
38521
|
-
const segmentEndTime = validEndTimes.length > 0 ? Math.max(...validEndTimes) : null;
|
|
38522
|
-
const updatedGlobalSegment = {
|
|
38523
|
-
...globalSegment,
|
|
38524
|
-
words: newWords,
|
|
38525
|
-
text: newWords.map((w) => w.text).join(" "),
|
|
38526
|
-
start_time: segmentStartTime,
|
|
38527
|
-
end_time: segmentEndTime
|
|
38528
|
-
};
|
|
38529
|
-
setGlobalSegment(updatedGlobalSegment);
|
|
38530
|
-
const updatedSegments = currentSegments.map((segment) => {
|
|
38531
|
-
const segmentWordsWithTiming = segment.words.map((segmentWord) => {
|
|
38532
|
-
const globalWord = newWords.find((w) => w.id === segmentWord.id);
|
|
38533
|
-
return globalWord || segmentWord;
|
|
38534
|
-
});
|
|
38535
|
-
const wordsWithTiming = segmentWordsWithTiming.filter(
|
|
38536
|
-
(w) => w.start_time !== null && w.end_time !== null
|
|
38537
|
-
);
|
|
38538
|
-
if (wordsWithTiming.length === segmentWordsWithTiming.length && wordsWithTiming.length > 0) {
|
|
38539
|
-
const segmentStart = Math.min(...wordsWithTiming.map((w) => w.start_time));
|
|
38540
|
-
const segmentEnd = Math.max(...wordsWithTiming.map((w) => w.end_time));
|
|
38541
|
-
return {
|
|
38542
|
-
...segment,
|
|
38543
|
-
words: segmentWordsWithTiming,
|
|
38544
|
-
start_time: segmentStart,
|
|
38545
|
-
end_time: segmentEnd
|
|
38546
|
-
};
|
|
38547
|
-
} else {
|
|
38548
|
-
return {
|
|
38549
|
-
...segment,
|
|
38550
|
-
words: segmentWordsWithTiming
|
|
38551
|
-
};
|
|
38552
|
-
}
|
|
39956
|
+
end_time: null,
|
|
39957
|
+
confidence: 1,
|
|
39958
|
+
created_during_correction: true
|
|
39959
|
+
}));
|
|
39960
|
+
segments.push({
|
|
39961
|
+
id: nanoid(),
|
|
39962
|
+
text: line2.trim(),
|
|
39963
|
+
words: segmentWords,
|
|
39964
|
+
start_time: null,
|
|
39965
|
+
end_time: null
|
|
39966
|
+
});
|
|
38553
39967
|
});
|
|
38554
|
-
|
|
38555
|
-
|
|
38556
|
-
|
|
38557
|
-
|
|
38558
|
-
|
|
38559
|
-
|
|
38560
|
-
|
|
38561
|
-
|
|
38562
|
-
|
|
38563
|
-
|
|
38564
|
-
handleSpacebar,
|
|
38565
|
-
isSpacebarPressed
|
|
38566
|
-
} = useManualSync({
|
|
38567
|
-
editedSegment: globalSegment,
|
|
38568
|
-
currentTime,
|
|
38569
|
-
onPlaySegment,
|
|
38570
|
-
updateSegment: updateSegment2
|
|
38571
|
-
});
|
|
38572
|
-
const handleWordUpdate = reactExports.useCallback((wordIndex, updates) => {
|
|
38573
|
-
var _a;
|
|
38574
|
-
if (!globalSegment) return;
|
|
38575
|
-
if (isManualSyncing && !isPaused) {
|
|
38576
|
-
console.log("ReplaceAllLyricsModal - Ignoring word update during active manual sync");
|
|
38577
|
-
return;
|
|
39968
|
+
setNewSegments(segments);
|
|
39969
|
+
setMode("resync");
|
|
39970
|
+
}, [inputText]);
|
|
39971
|
+
const handlePasteFromClipboard = reactExports.useCallback(async () => {
|
|
39972
|
+
try {
|
|
39973
|
+
const text = await navigator.clipboard.readText();
|
|
39974
|
+
setInputText(text);
|
|
39975
|
+
} catch (error) {
|
|
39976
|
+
console.error("Failed to read from clipboard:", error);
|
|
39977
|
+
alert("Failed to read from clipboard. Please paste manually.");
|
|
38578
39978
|
}
|
|
38579
|
-
|
|
38580
|
-
wordIndex,
|
|
38581
|
-
wordText: (_a = globalSegment.words[wordIndex]) == null ? void 0 : _a.text,
|
|
38582
|
-
updates,
|
|
38583
|
-
isManualSyncing,
|
|
38584
|
-
isPaused
|
|
38585
|
-
});
|
|
38586
|
-
const newWords = [...globalSegment.words];
|
|
38587
|
-
newWords[wordIndex] = {
|
|
38588
|
-
...newWords[wordIndex],
|
|
38589
|
-
...updates
|
|
38590
|
-
};
|
|
38591
|
-
updateSegment2(newWords);
|
|
38592
|
-
}, [globalSegment, updateSegment2, isManualSyncing, isPaused]);
|
|
38593
|
-
const handleUnsyncWord = reactExports.useCallback((wordIndex) => {
|
|
38594
|
-
var _a;
|
|
38595
|
-
if (!globalSegment) return;
|
|
38596
|
-
console.log("ReplaceAllLyricsModal - Un-syncing word", {
|
|
38597
|
-
wordIndex,
|
|
38598
|
-
wordText: (_a = globalSegment.words[wordIndex]) == null ? void 0 : _a.text
|
|
38599
|
-
});
|
|
38600
|
-
const newWords = [...globalSegment.words];
|
|
38601
|
-
newWords[wordIndex] = {
|
|
38602
|
-
...newWords[wordIndex],
|
|
38603
|
-
start_time: null,
|
|
38604
|
-
end_time: null
|
|
38605
|
-
};
|
|
38606
|
-
updateSegment2(newWords);
|
|
38607
|
-
}, [globalSegment, updateSegment2]);
|
|
39979
|
+
}, []);
|
|
38608
39980
|
const handleClose = reactExports.useCallback(() => {
|
|
38609
|
-
|
|
39981
|
+
setMode("selection");
|
|
38610
39982
|
setInputText("");
|
|
38611
|
-
|
|
38612
|
-
setGlobalSegment(null);
|
|
38613
|
-
setOriginalSegments([]);
|
|
38614
|
-
setCurrentSegments([]);
|
|
39983
|
+
setNewSegments([]);
|
|
38615
39984
|
onClose();
|
|
38616
|
-
}, [onClose
|
|
38617
|
-
const handleSave = reactExports.useCallback(() => {
|
|
38618
|
-
|
|
38619
|
-
const finalSegments = [];
|
|
38620
|
-
let wordIndex = 0;
|
|
38621
|
-
currentSegments.forEach((segment) => {
|
|
38622
|
-
const originalWordCount = segment.words.length;
|
|
38623
|
-
const segmentWords = globalSegment.words.slice(wordIndex, wordIndex + originalWordCount);
|
|
38624
|
-
wordIndex += originalWordCount;
|
|
38625
|
-
if (segmentWords.length > 0) {
|
|
38626
|
-
const validStartTimes = segmentWords.map((w) => w.start_time).filter((t) => t !== null);
|
|
38627
|
-
const validEndTimes = segmentWords.map((w) => w.end_time).filter((t) => t !== null);
|
|
38628
|
-
const segmentStartTime = validStartTimes.length > 0 ? Math.min(...validStartTimes) : null;
|
|
38629
|
-
const segmentEndTime = validEndTimes.length > 0 ? Math.max(...validEndTimes) : null;
|
|
38630
|
-
finalSegments.push({
|
|
38631
|
-
...segment,
|
|
38632
|
-
words: segmentWords,
|
|
38633
|
-
text: segmentWords.map((w) => w.text).join(" "),
|
|
38634
|
-
start_time: segmentStartTime,
|
|
38635
|
-
end_time: segmentEndTime
|
|
38636
|
-
});
|
|
38637
|
-
}
|
|
38638
|
-
});
|
|
38639
|
-
console.log("ReplaceAllLyricsModal - Saving new segments:", {
|
|
38640
|
-
originalSegmentCount: currentSegments.length,
|
|
38641
|
-
finalSegmentCount: finalSegments.length,
|
|
38642
|
-
totalWords: finalSegments.reduce((count, seg) => count + seg.words.length, 0)
|
|
38643
|
-
});
|
|
38644
|
-
onSave(finalSegments);
|
|
39985
|
+
}, [onClose]);
|
|
39986
|
+
const handleSave = reactExports.useCallback((segments) => {
|
|
39987
|
+
onSave(segments);
|
|
38645
39988
|
handleClose();
|
|
38646
|
-
}, [
|
|
38647
|
-
const
|
|
38648
|
-
|
|
38649
|
-
|
|
38650
|
-
|
|
38651
|
-
|
|
38652
|
-
|
|
38653
|
-
|
|
38654
|
-
|
|
38655
|
-
|
|
38656
|
-
);
|
|
38657
|
-
|
|
38658
|
-
|
|
38659
|
-
|
|
38660
|
-
|
|
38661
|
-
|
|
38662
|
-
|
|
38663
|
-
|
|
38664
|
-
|
|
38665
|
-
|
|
38666
|
-
|
|
38667
|
-
|
|
38668
|
-
|
|
38669
|
-
|
|
38670
|
-
|
|
38671
|
-
|
|
38672
|
-
|
|
38673
|
-
|
|
38674
|
-
|
|
38675
|
-
|
|
38676
|
-
|
|
38677
|
-
|
|
38678
|
-
|
|
38679
|
-
|
|
38680
|
-
|
|
38681
|
-
|
|
38682
|
-
|
|
38683
|
-
|
|
38684
|
-
|
|
38685
|
-
|
|
38686
|
-
|
|
38687
|
-
|
|
38688
|
-
|
|
38689
|
-
|
|
38690
|
-
|
|
38691
|
-
|
|
38692
|
-
|
|
38693
|
-
|
|
38694
|
-
|
|
38695
|
-
|
|
38696
|
-
|
|
38697
|
-
|
|
38698
|
-
|
|
38699
|
-
|
|
38700
|
-
|
|
38701
|
-
}
|
|
38702
|
-
}, [open, isReplaced, setModalSpacebarHandler]);
|
|
38703
|
-
const timeRange = reactExports.useMemo(() => {
|
|
38704
|
-
const audioDuration = getAudioDuration();
|
|
38705
|
-
return { start: 0, end: audioDuration };
|
|
38706
|
-
}, [getAudioDuration]);
|
|
38707
|
-
const segmentProgressProps = reactExports.useMemo(() => ({
|
|
38708
|
-
currentSegments,
|
|
38709
|
-
globalSegment,
|
|
38710
|
-
syncWordIndex
|
|
38711
|
-
}), [currentSegments, globalSegment, syncWordIndex]);
|
|
38712
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
38713
|
-
Dialog,
|
|
38714
|
-
{
|
|
38715
|
-
open,
|
|
38716
|
-
onClose: handleClose,
|
|
38717
|
-
maxWidth: false,
|
|
38718
|
-
fullWidth: true,
|
|
38719
|
-
onKeyDown: (e) => {
|
|
38720
|
-
if (e.key === "Enter" && !e.shiftKey && isReplaced) {
|
|
38721
|
-
e.preventDefault();
|
|
38722
|
-
handleSave();
|
|
38723
|
-
}
|
|
38724
|
-
},
|
|
38725
|
-
PaperProps: {
|
|
38726
|
-
sx: {
|
|
38727
|
-
height: "90vh",
|
|
38728
|
-
margin: "5vh 2vh",
|
|
38729
|
-
maxWidth: "calc(100vw - 4vh)",
|
|
38730
|
-
width: "calc(100vw - 4vh)"
|
|
38731
|
-
}
|
|
38732
|
-
},
|
|
38733
|
-
children: [
|
|
38734
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogTitle, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
|
|
38735
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { flex: 1 }, children: "Replace All Lyrics" }),
|
|
38736
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(IconButton, { onClick: handleClose, sx: { ml: "auto" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, {}) })
|
|
38737
|
-
] }),
|
|
38738
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
38739
|
-
DialogContent,
|
|
38740
|
-
{
|
|
38741
|
-
dividers: true,
|
|
38742
|
-
sx: {
|
|
38743
|
-
display: "flex",
|
|
38744
|
-
flexDirection: "column",
|
|
38745
|
-
flexGrow: 1,
|
|
38746
|
-
overflow: "hidden"
|
|
38747
|
-
},
|
|
38748
|
-
children: !isReplaced ? (
|
|
38749
|
-
// Step 1: Input new lyrics
|
|
38750
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 2, height: "100%" }, children: [
|
|
39989
|
+
}, [onSave, handleClose]);
|
|
39990
|
+
const handleSelectReplace = reactExports.useCallback(() => {
|
|
39991
|
+
setMode("replace");
|
|
39992
|
+
}, []);
|
|
39993
|
+
const handleSelectResync = reactExports.useCallback(() => {
|
|
39994
|
+
setMode("resync");
|
|
39995
|
+
}, []);
|
|
39996
|
+
const handleBackToSelection = reactExports.useCallback(() => {
|
|
39997
|
+
setMode("selection");
|
|
39998
|
+
setInputText("");
|
|
39999
|
+
setNewSegments([]);
|
|
40000
|
+
}, []);
|
|
40001
|
+
const segmentsForSync = mode === "resync" && newSegments.length > 0 ? newSegments : existingSegments;
|
|
40002
|
+
const hasExistingLyrics = existingSegments.length > 0 && existingSegments.some((s) => s.words.length > 0);
|
|
40003
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
40004
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
40005
|
+
ModeSelectionModal,
|
|
40006
|
+
{
|
|
40007
|
+
open: open && mode === "selection",
|
|
40008
|
+
onClose: handleClose,
|
|
40009
|
+
onSelectReplace: handleSelectReplace,
|
|
40010
|
+
onSelectResync: handleSelectResync,
|
|
40011
|
+
hasExistingLyrics
|
|
40012
|
+
}
|
|
40013
|
+
),
|
|
40014
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
40015
|
+
Dialog,
|
|
40016
|
+
{
|
|
40017
|
+
open: open && mode === "replace",
|
|
40018
|
+
onClose: handleClose,
|
|
40019
|
+
maxWidth: "md",
|
|
40020
|
+
fullWidth: true,
|
|
40021
|
+
PaperProps: {
|
|
40022
|
+
sx: {
|
|
40023
|
+
height: "80vh",
|
|
40024
|
+
maxHeight: "80vh"
|
|
40025
|
+
}
|
|
40026
|
+
},
|
|
40027
|
+
children: [
|
|
40028
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogTitle, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
|
|
40029
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(IconButton, { onClick: handleBackToSelection, size: "small", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowBackIcon, {}) }),
|
|
40030
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { flex: 1 }, children: "Replace All Lyrics" }),
|
|
40031
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(IconButton, { onClick: handleClose, sx: { ml: "auto" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, {}) })
|
|
40032
|
+
] }),
|
|
40033
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
40034
|
+
DialogContent,
|
|
40035
|
+
{
|
|
40036
|
+
dividers: true,
|
|
40037
|
+
sx: {
|
|
40038
|
+
display: "flex",
|
|
40039
|
+
flexDirection: "column",
|
|
40040
|
+
flexGrow: 1,
|
|
40041
|
+
overflow: "hidden"
|
|
40042
|
+
},
|
|
40043
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 2, height: "100%" }, children: [
|
|
38751
40044
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", gutterBottom: true, children: "Paste your new lyrics below:" }),
|
|
38752
40045
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body2", color: "text.secondary", gutterBottom: true, children: "Each line will become a separate segment. Words will be separated by spaces." }),
|
|
38753
40046
|
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", gap: 2, mb: 2 }, children: [
|
|
@@ -38788,185 +40081,95 @@ function ReplaceAllLyricsModal({
|
|
|
38788
40081
|
}
|
|
38789
40082
|
}
|
|
38790
40083
|
}
|
|
38791
|
-
)
|
|
38792
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { display: "flex", justifyContent: "flex-end", gap: 2 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
38793
|
-
Button,
|
|
38794
|
-
{
|
|
38795
|
-
variant: "contained",
|
|
38796
|
-
onClick: processLyrics,
|
|
38797
|
-
disabled: !inputText.trim(),
|
|
38798
|
-
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(AutoFixHighIcon, {}),
|
|
38799
|
-
children: "Replace All Lyrics"
|
|
38800
|
-
}
|
|
38801
|
-
) })
|
|
38802
|
-
] })
|
|
38803
|
-
) : (
|
|
38804
|
-
// Step 2: Manual sync interface
|
|
38805
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", flexDirection: "column", height: "100%", gap: 2 }, children: [
|
|
38806
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(Paper, { sx: { p: 2, bgcolor: "background.paper" }, children: [
|
|
38807
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", gutterBottom: true, children: "Lyrics Replaced Successfully" }),
|
|
38808
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(Typography, { variant: "body2", color: "text.secondary", children: [
|
|
38809
|
-
"Created ",
|
|
38810
|
-
currentSegments.length,
|
|
38811
|
-
" segments with ",
|
|
38812
|
-
globalSegment == null ? void 0 : globalSegment.words.length,
|
|
38813
|
-
" words total. Use Manual Sync to set timing for all words."
|
|
38814
|
-
] })
|
|
38815
|
-
] }),
|
|
38816
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Divider, {}),
|
|
38817
|
-
globalSegment && /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", gap: 2, flexGrow: 1, minHeight: 0 }, children: [
|
|
38818
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { flex: 2, display: "flex", flexDirection: "column", minHeight: 0 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
38819
|
-
EditTimelineSection,
|
|
38820
|
-
{
|
|
38821
|
-
words: globalSegment.words,
|
|
38822
|
-
startTime: timeRange.start,
|
|
38823
|
-
endTime: timeRange.end,
|
|
38824
|
-
originalStartTime: 0,
|
|
38825
|
-
originalEndTime: getAudioDuration(),
|
|
38826
|
-
currentStartTime: globalSegment.start_time,
|
|
38827
|
-
currentEndTime: globalSegment.end_time,
|
|
38828
|
-
currentTime,
|
|
38829
|
-
isManualSyncing,
|
|
38830
|
-
syncWordIndex,
|
|
38831
|
-
isSpacebarPressed,
|
|
38832
|
-
onWordUpdate: handleWordUpdate,
|
|
38833
|
-
onUnsyncWord: handleUnsyncWord,
|
|
38834
|
-
onPlaySegment,
|
|
38835
|
-
onStopAudio: () => {
|
|
38836
|
-
if (window.toggleAudioPlayback && window.isAudioPlaying) {
|
|
38837
|
-
window.toggleAudioPlayback();
|
|
38838
|
-
}
|
|
38839
|
-
},
|
|
38840
|
-
startManualSync,
|
|
38841
|
-
pauseManualSync,
|
|
38842
|
-
resumeManualSync,
|
|
38843
|
-
isPaused,
|
|
38844
|
-
isGlobal: true,
|
|
38845
|
-
defaultZoomLevel: 10,
|
|
38846
|
-
isReplaceAllMode: true
|
|
38847
|
-
}
|
|
38848
|
-
) }),
|
|
38849
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
38850
|
-
SegmentProgressPanel,
|
|
38851
|
-
{
|
|
38852
|
-
currentSegments: segmentProgressProps.currentSegments,
|
|
38853
|
-
globalSegment: segmentProgressProps.globalSegment,
|
|
38854
|
-
syncWordIndex: segmentProgressProps.syncWordIndex
|
|
38855
|
-
}
|
|
38856
|
-
)
|
|
38857
|
-
] })
|
|
40084
|
+
)
|
|
38858
40085
|
] })
|
|
40086
|
+
}
|
|
40087
|
+
),
|
|
40088
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogActions, { children: [
|
|
40089
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: handleClose, color: "inherit", children: "Cancel" }),
|
|
40090
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
40091
|
+
Button,
|
|
40092
|
+
{
|
|
40093
|
+
variant: "contained",
|
|
40094
|
+
onClick: processLyrics,
|
|
40095
|
+
disabled: !inputText.trim(),
|
|
40096
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(AutoFixHighIcon, {}),
|
|
40097
|
+
children: "Continue to Sync"
|
|
40098
|
+
}
|
|
38859
40099
|
)
|
|
38860
|
-
}
|
|
38861
|
-
),
|
|
38862
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(DialogActions, { children: isReplaced && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
38863
|
-
EditActionBar,
|
|
38864
|
-
{
|
|
38865
|
-
onReset: handleReset,
|
|
38866
|
-
onClose: handleClose,
|
|
38867
|
-
onSave: handleSave,
|
|
38868
|
-
editedSegment: globalSegment,
|
|
38869
|
-
isGlobal: true
|
|
38870
|
-
}
|
|
38871
|
-
) })
|
|
38872
|
-
]
|
|
38873
|
-
}
|
|
38874
|
-
);
|
|
38875
|
-
}
|
|
38876
|
-
const SegmentProgressItem = reactExports.memo(({
|
|
38877
|
-
segment,
|
|
38878
|
-
index,
|
|
38879
|
-
isActive
|
|
38880
|
-
}) => {
|
|
38881
|
-
const wordsWithTiming = segment.words.filter(
|
|
38882
|
-
(w) => w.start_time !== null && w.end_time !== null
|
|
38883
|
-
).length;
|
|
38884
|
-
const totalWords = segment.words.length;
|
|
38885
|
-
const isComplete = wordsWithTiming === totalWords;
|
|
38886
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
38887
|
-
Paper,
|
|
38888
|
-
{
|
|
38889
|
-
ref: isActive ? (el) => {
|
|
38890
|
-
if (el) {
|
|
38891
|
-
el.scrollIntoView({
|
|
38892
|
-
behavior: "smooth",
|
|
38893
|
-
block: "center"
|
|
38894
|
-
});
|
|
38895
|
-
}
|
|
38896
|
-
} : void 0,
|
|
38897
|
-
sx: {
|
|
38898
|
-
p: 1,
|
|
38899
|
-
mb: 1,
|
|
38900
|
-
bgcolor: isActive ? "primary.light" : isComplete ? "success.light" : "background.paper",
|
|
38901
|
-
border: isActive ? 2 : 1,
|
|
38902
|
-
borderColor: isActive ? "primary.main" : "divider"
|
|
38903
|
-
},
|
|
38904
|
-
children: [
|
|
38905
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
38906
|
-
Typography,
|
|
38907
|
-
{
|
|
38908
|
-
variant: "body2",
|
|
38909
|
-
sx: {
|
|
38910
|
-
fontWeight: isActive ? "bold" : "normal",
|
|
38911
|
-
mb: 0.5
|
|
38912
|
-
},
|
|
38913
|
-
children: [
|
|
38914
|
-
"Segment ",
|
|
38915
|
-
index + 1,
|
|
38916
|
-
": ",
|
|
38917
|
-
segment.text.slice(0, 50),
|
|
38918
|
-
segment.text.length > 50 ? "..." : ""
|
|
38919
|
-
]
|
|
38920
|
-
}
|
|
38921
|
-
),
|
|
38922
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(Typography, { variant: "caption", color: "text.secondary", children: [
|
|
38923
|
-
wordsWithTiming,
|
|
38924
|
-
"/",
|
|
38925
|
-
totalWords,
|
|
38926
|
-
" words synced",
|
|
38927
|
-
isComplete && segment.start_time !== null && segment.end_time !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
38928
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
|
|
38929
|
-
segment.start_time.toFixed(2),
|
|
38930
|
-
"s - ",
|
|
38931
|
-
segment.end_time.toFixed(2),
|
|
38932
|
-
"s"
|
|
38933
40100
|
] })
|
|
38934
|
-
]
|
|
38935
|
-
|
|
38936
|
-
|
|
38937
|
-
|
|
38938
|
-
|
|
38939
|
-
|
|
38940
|
-
|
|
38941
|
-
|
|
38942
|
-
|
|
38943
|
-
|
|
38944
|
-
|
|
38945
|
-
|
|
38946
|
-
|
|
38947
|
-
|
|
38948
|
-
|
|
38949
|
-
|
|
38950
|
-
|
|
38951
|
-
borderColor: "divider",
|
|
38952
|
-
borderRadius: 1,
|
|
38953
|
-
p: 1
|
|
38954
|
-
}, children: currentSegments.map((segment, index) => {
|
|
38955
|
-
const isActive = Boolean(
|
|
38956
|
-
globalSegment && syncWordIndex >= 0 && syncWordIndex < globalSegment.words.length && globalSegment.words[syncWordIndex] && segment.words.some((w) => w.id === globalSegment.words[syncWordIndex].id)
|
|
38957
|
-
);
|
|
38958
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
38959
|
-
SegmentProgressItem,
|
|
38960
|
-
{
|
|
38961
|
-
segment,
|
|
38962
|
-
index,
|
|
38963
|
-
isActive
|
|
40101
|
+
]
|
|
40102
|
+
}
|
|
40103
|
+
),
|
|
40104
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
40105
|
+
Dialog,
|
|
40106
|
+
{
|
|
40107
|
+
open: open && mode === "resync",
|
|
40108
|
+
onClose: handleClose,
|
|
40109
|
+
maxWidth: false,
|
|
40110
|
+
fullWidth: true,
|
|
40111
|
+
PaperProps: {
|
|
40112
|
+
sx: {
|
|
40113
|
+
height: "90vh",
|
|
40114
|
+
margin: "5vh 2vw",
|
|
40115
|
+
maxWidth: "calc(100vw - 4vw)",
|
|
40116
|
+
width: "calc(100vw - 4vw)"
|
|
40117
|
+
}
|
|
38964
40118
|
},
|
|
38965
|
-
|
|
38966
|
-
|
|
38967
|
-
|
|
40119
|
+
children: [
|
|
40120
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogTitle, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
|
|
40121
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(IconButton, { onClick: handleBackToSelection, size: "small", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowBackIcon, {}) }),
|
|
40122
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { flex: 1 }, children: newSegments.length > 0 ? "Sync New Lyrics" : "Re-sync Existing Lyrics" }),
|
|
40123
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(IconButton, { onClick: handleClose, sx: { ml: "auto" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, {}) })
|
|
40124
|
+
] }),
|
|
40125
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
40126
|
+
DialogContent,
|
|
40127
|
+
{
|
|
40128
|
+
dividers: true,
|
|
40129
|
+
sx: {
|
|
40130
|
+
display: "flex",
|
|
40131
|
+
flexDirection: "column",
|
|
40132
|
+
flexGrow: 1,
|
|
40133
|
+
overflow: "hidden",
|
|
40134
|
+
p: 2
|
|
40135
|
+
},
|
|
40136
|
+
children: segmentsForSync.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
40137
|
+
LyricsSynchronizer,
|
|
40138
|
+
{
|
|
40139
|
+
segments: segmentsForSync,
|
|
40140
|
+
currentTime,
|
|
40141
|
+
onPlaySegment,
|
|
40142
|
+
onSave: handleSave,
|
|
40143
|
+
onCancel: handleClose,
|
|
40144
|
+
setModalSpacebarHandler
|
|
40145
|
+
}
|
|
40146
|
+
) : /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: {
|
|
40147
|
+
display: "flex",
|
|
40148
|
+
flexDirection: "column",
|
|
40149
|
+
alignItems: "center",
|
|
40150
|
+
justifyContent: "center",
|
|
40151
|
+
height: "100%",
|
|
40152
|
+
gap: 2
|
|
40153
|
+
}, children: [
|
|
40154
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", color: "text.secondary", children: "No lyrics to sync" }),
|
|
40155
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body2", color: "text.secondary", children: "Go back and paste new lyrics, or close this modal." }),
|
|
40156
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
40157
|
+
Button,
|
|
40158
|
+
{
|
|
40159
|
+
variant: "outlined",
|
|
40160
|
+
onClick: handleBackToSelection,
|
|
40161
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowBackIcon, {}),
|
|
40162
|
+
children: "Back to Selection"
|
|
40163
|
+
}
|
|
40164
|
+
)
|
|
40165
|
+
] })
|
|
40166
|
+
}
|
|
40167
|
+
)
|
|
40168
|
+
]
|
|
40169
|
+
}
|
|
40170
|
+
)
|
|
38968
40171
|
] });
|
|
38969
|
-
}
|
|
40172
|
+
}
|
|
38970
40173
|
const ANNOTATION_TYPES = [
|
|
38971
40174
|
{
|
|
38972
40175
|
value: "SOUND_ALIKE",
|
|
@@ -39866,7 +41069,7 @@ function AudioPlayer({ apiClient, onTimeUpdate, audioHash }) {
|
|
|
39866
41069
|
audioRef.current.currentTime = time;
|
|
39867
41070
|
setCurrentTime(time);
|
|
39868
41071
|
};
|
|
39869
|
-
const
|
|
41072
|
+
const formatTime2 = (seconds) => {
|
|
39870
41073
|
const mins = Math.floor(seconds / 60);
|
|
39871
41074
|
const secs = Math.floor(seconds % 60);
|
|
39872
41075
|
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
@@ -39918,7 +41121,7 @@ function AudioPlayer({ apiClient, onTimeUpdate, audioHash }) {
|
|
|
39918
41121
|
children: isPlaying ? /* @__PURE__ */ jsxRuntimeExports.jsx(PauseIcon, { fontSize: "small" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(PlayArrowIcon, { fontSize: "small" })
|
|
39919
41122
|
}
|
|
39920
41123
|
),
|
|
39921
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body2", sx: { minWidth: 32, fontSize: "0.75rem" }, children:
|
|
41124
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body2", sx: { minWidth: 32, fontSize: "0.75rem" }, children: formatTime2(currentTime) }),
|
|
39922
41125
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39923
41126
|
Slider,
|
|
39924
41127
|
{
|
|
@@ -39940,7 +41143,7 @@ function AudioPlayer({ apiClient, onTimeUpdate, audioHash }) {
|
|
|
39940
41143
|
}
|
|
39941
41144
|
}
|
|
39942
41145
|
),
|
|
39943
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body2", sx: { minWidth: 32, fontSize: "0.75rem" }, children:
|
|
41146
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body2", sx: { minWidth: 32, fontSize: "0.75rem" }, children: formatTime2(duration2) })
|
|
39944
41147
|
] });
|
|
39945
41148
|
}
|
|
39946
41149
|
function Header({
|
|
@@ -39964,7 +41167,9 @@ function Header({
|
|
|
39964
41167
|
onUndo,
|
|
39965
41168
|
onRedo,
|
|
39966
41169
|
canUndo,
|
|
39967
|
-
canRedo
|
|
41170
|
+
canRedo,
|
|
41171
|
+
annotationsEnabled = true,
|
|
41172
|
+
onAnnotationsToggle
|
|
39968
41173
|
}) {
|
|
39969
41174
|
var _a, _b, _c;
|
|
39970
41175
|
const theme2 = useTheme();
|
|
@@ -40011,17 +41216,34 @@ function Header({
|
|
|
40011
41216
|
mb: 1
|
|
40012
41217
|
}, children: [
|
|
40013
41218
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h4", sx: { fontSize: isMobile ? "1.3rem" : "1.5rem" }, children: "Nomad Karaoke: Lyrics Transcription Review" }),
|
|
40014
|
-
|
|
40015
|
-
|
|
40016
|
-
|
|
40017
|
-
|
|
40018
|
-
|
|
40019
|
-
|
|
40020
|
-
|
|
40021
|
-
|
|
40022
|
-
|
|
40023
|
-
|
|
40024
|
-
|
|
41219
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
|
|
41220
|
+
!isReadOnly && onAnnotationsToggle && /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: annotationsEnabled ? "Click to disable annotation prompts when editing" : "Click to enable annotation prompts when editing", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
41221
|
+
Chip,
|
|
41222
|
+
{
|
|
41223
|
+
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(RateReviewIcon, {}),
|
|
41224
|
+
label: annotationsEnabled ? "Feedback On" : "Feedback Off",
|
|
41225
|
+
onClick: () => onAnnotationsToggle(!annotationsEnabled),
|
|
41226
|
+
color: annotationsEnabled ? "primary" : "default",
|
|
41227
|
+
variant: annotationsEnabled ? "filled" : "outlined",
|
|
41228
|
+
size: "small",
|
|
41229
|
+
sx: {
|
|
41230
|
+
cursor: "pointer",
|
|
41231
|
+
"& .MuiChip-icon": { fontSize: "1rem" }
|
|
41232
|
+
}
|
|
41233
|
+
}
|
|
41234
|
+
) }),
|
|
41235
|
+
isReadOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
41236
|
+
Button,
|
|
41237
|
+
{
|
|
41238
|
+
variant: "outlined",
|
|
41239
|
+
size: "small",
|
|
41240
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(UploadFileIcon, {}),
|
|
41241
|
+
onClick: onFileLoad,
|
|
41242
|
+
fullWidth: isMobile,
|
|
41243
|
+
children: "Load File"
|
|
41244
|
+
}
|
|
41245
|
+
)
|
|
41246
|
+
] })
|
|
40025
41247
|
] }),
|
|
40026
41248
|
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: {
|
|
40027
41249
|
display: "flex",
|
|
@@ -40859,7 +42081,9 @@ const MemoizedHeader = reactExports.memo(function MemoizedHeader2({
|
|
|
40859
42081
|
onRedo,
|
|
40860
42082
|
canUndo,
|
|
40861
42083
|
canRedo,
|
|
40862
|
-
onUnCorrectAll
|
|
42084
|
+
onUnCorrectAll,
|
|
42085
|
+
annotationsEnabled,
|
|
42086
|
+
onAnnotationsToggle
|
|
40863
42087
|
}) {
|
|
40864
42088
|
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
40865
42089
|
Header,
|
|
@@ -40884,7 +42108,9 @@ const MemoizedHeader = reactExports.memo(function MemoizedHeader2({
|
|
|
40884
42108
|
onRedo,
|
|
40885
42109
|
canUndo,
|
|
40886
42110
|
canRedo,
|
|
40887
|
-
onUnCorrectAll
|
|
42111
|
+
onUnCorrectAll,
|
|
42112
|
+
annotationsEnabled,
|
|
42113
|
+
onAnnotationsToggle
|
|
40888
42114
|
}
|
|
40889
42115
|
);
|
|
40890
42116
|
});
|
|
@@ -40920,10 +42146,14 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
|
|
|
40920
42146
|
const [annotations, setAnnotations] = reactExports.useState([]);
|
|
40921
42147
|
const [isAnnotationModalOpen, setIsAnnotationModalOpen] = reactExports.useState(false);
|
|
40922
42148
|
const [pendingAnnotation, setPendingAnnotation] = reactExports.useState(null);
|
|
40923
|
-
const [annotationsEnabled] = reactExports.useState(() => {
|
|
42149
|
+
const [annotationsEnabled, setAnnotationsEnabled] = reactExports.useState(() => {
|
|
40924
42150
|
const saved = localStorage.getItem("annotationsEnabled");
|
|
40925
42151
|
return saved !== null ? saved === "true" : true;
|
|
40926
42152
|
});
|
|
42153
|
+
const handleAnnotationsToggle = reactExports.useCallback((enabled) => {
|
|
42154
|
+
setAnnotationsEnabled(enabled);
|
|
42155
|
+
localStorage.setItem("annotationsEnabled", String(enabled));
|
|
42156
|
+
}, []);
|
|
40927
42157
|
const [correctionDetailOpen, setCorrectionDetailOpen] = reactExports.useState(false);
|
|
40928
42158
|
const [selectedCorrection, setSelectedCorrection] = reactExports.useState(null);
|
|
40929
42159
|
const theme2 = useTheme();
|
|
@@ -41473,7 +42703,9 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
|
|
|
41473
42703
|
onRedo: handleRedo,
|
|
41474
42704
|
canUndo,
|
|
41475
42705
|
canRedo,
|
|
41476
|
-
onUnCorrectAll: handleUnCorrectAll
|
|
42706
|
+
onUnCorrectAll: handleUnCorrectAll,
|
|
42707
|
+
annotationsEnabled,
|
|
42708
|
+
onAnnotationsToggle: handleAnnotationsToggle
|
|
41477
42709
|
}
|
|
41478
42710
|
),
|
|
41479
42711
|
/* @__PURE__ */ jsxRuntimeExports.jsxs(Grid, { container: true, direction: isMobile ? "column" : "row", children: [
|
|
@@ -41620,7 +42852,8 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
|
|
|
41620
42852
|
onSave: handleSaveReplaceAllLyrics,
|
|
41621
42853
|
onPlaySegment: handlePlaySegment,
|
|
41622
42854
|
currentTime: currentAudioTime,
|
|
41623
|
-
setModalSpacebarHandler: handleSetModalSpacebarHandler
|
|
42855
|
+
setModalSpacebarHandler: handleSetModalSpacebarHandler,
|
|
42856
|
+
existingSegments: data.corrected_segments
|
|
41624
42857
|
}
|
|
41625
42858
|
),
|
|
41626
42859
|
pendingAnnotation && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -42025,7 +43258,7 @@ const theme = createTheme({
|
|
|
42025
43258
|
spacing: (factor) => `${0.6 * factor}rem`
|
|
42026
43259
|
// Further reduced from 0.8 * factor
|
|
42027
43260
|
});
|
|
42028
|
-
const version = "0.
|
|
43261
|
+
const version = "0.82.0";
|
|
42029
43262
|
const packageJson = {
|
|
42030
43263
|
version
|
|
42031
43264
|
};
|
|
@@ -42036,4 +43269,4 @@ ReactDOM$1.createRoot(document.getElementById("root")).render(
|
|
|
42036
43269
|
/* @__PURE__ */ jsxRuntimeExports.jsx(App, {})
|
|
42037
43270
|
] })
|
|
42038
43271
|
);
|
|
42039
|
-
//# sourceMappingURL=index-
|
|
43272
|
+
//# sourceMappingURL=index-COYImAcx.js.map
|