karaoke-gen 0.71.42__py3-none-any.whl → 0.75.53__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 +1220 -67
- karaoke_gen/audio_processor.py +15 -3
- karaoke_gen/instrumental_review/server.py +154 -860
- karaoke_gen/instrumental_review/static/index.html +1529 -0
- karaoke_gen/karaoke_finalise/karaoke_finalise.py +87 -2
- karaoke_gen/karaoke_gen.py +131 -14
- karaoke_gen/lyrics_processor.py +172 -4
- karaoke_gen/utils/bulk_cli.py +3 -0
- karaoke_gen/utils/cli_args.py +7 -4
- karaoke_gen/utils/gen_cli.py +221 -5
- karaoke_gen/utils/remote_cli.py +786 -43
- {karaoke_gen-0.71.42.dist-info → karaoke_gen-0.75.53.dist-info}/METADATA +109 -4
- {karaoke_gen-0.71.42.dist-info → karaoke_gen-0.75.53.dist-info}/RECORD +37 -31
- lyrics_transcriber/core/controller.py +76 -2
- lyrics_transcriber/frontend/package.json +1 -1
- lyrics_transcriber/frontend/src/App.tsx +6 -4
- lyrics_transcriber/frontend/src/api.ts +25 -10
- 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-BECn1o8Q.js} +1802 -553
- lyrics_transcriber/frontend/web_assets/assets/index-BECn1o8Q.js.map +1 -0
- lyrics_transcriber/frontend/web_assets/index.html +1 -1
- lyrics_transcriber/output/countdown_processor.py +39 -0
- lyrics_transcriber/review/server.py +5 -5
- lyrics_transcriber/transcribers/audioshake.py +96 -7
- lyrics_transcriber/types.py +14 -12
- lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js.map +0 -1
- {karaoke_gen-0.71.42.dist-info → karaoke_gen-0.75.53.dist-info}/WHEEL +0 -0
- {karaoke_gen-0.71.42.dist-info → karaoke_gen-0.75.53.dist-info}/entry_points.txt +0 -0
- {karaoke_gen-0.71.42.dist-info → karaoke_gen-0.75.53.dist-info}/licenses/LICENSE +0 -0
|
@@ -34056,13 +34056,26 @@ function validateCorrectionData(data) {
|
|
|
34056
34056
|
return CorrectionDataSchema.parse(data);
|
|
34057
34057
|
}
|
|
34058
34058
|
class LiveApiClient {
|
|
34059
|
-
constructor(baseUrl) {
|
|
34059
|
+
constructor(baseUrl, reviewToken) {
|
|
34060
|
+
__publicField(this, "reviewToken");
|
|
34060
34061
|
__publicField(this, "isUpdatingHandlers", false);
|
|
34061
34062
|
this.baseUrl = baseUrl;
|
|
34062
34063
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
34064
|
+
this.reviewToken = reviewToken;
|
|
34065
|
+
}
|
|
34066
|
+
/**
|
|
34067
|
+
* Build URL with reviewToken query parameter if available
|
|
34068
|
+
*/
|
|
34069
|
+
buildUrl(path) {
|
|
34070
|
+
const url = `${this.baseUrl}${path}`;
|
|
34071
|
+
if (this.reviewToken) {
|
|
34072
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
34073
|
+
return `${url}${separator}review_token=${encodeURIComponent(this.reviewToken)}`;
|
|
34074
|
+
}
|
|
34075
|
+
return url;
|
|
34063
34076
|
}
|
|
34064
34077
|
async getCorrectionData() {
|
|
34065
|
-
const response = await fetch(
|
|
34078
|
+
const response = await fetch(this.buildUrl("/correction-data"));
|
|
34066
34079
|
if (!response.ok) {
|
|
34067
34080
|
throw new Error(`API error: ${response.statusText}`);
|
|
34068
34081
|
}
|
|
@@ -34079,7 +34092,7 @@ class LiveApiClient {
|
|
|
34079
34092
|
corrections: data.corrections,
|
|
34080
34093
|
corrected_segments: data.corrected_segments
|
|
34081
34094
|
};
|
|
34082
|
-
const response = await fetch(
|
|
34095
|
+
const response = await fetch(this.buildUrl("/complete"), {
|
|
34083
34096
|
method: "POST",
|
|
34084
34097
|
headers: {
|
|
34085
34098
|
"Content-Type": "application/json"
|
|
@@ -34091,14 +34104,14 @@ class LiveApiClient {
|
|
|
34091
34104
|
}
|
|
34092
34105
|
}
|
|
34093
34106
|
getAudioUrl(audioHash) {
|
|
34094
|
-
return
|
|
34107
|
+
return this.buildUrl(`/audio/${audioHash}`);
|
|
34095
34108
|
}
|
|
34096
34109
|
async generatePreviewVideo(data) {
|
|
34097
34110
|
const updatePayload = {
|
|
34098
34111
|
corrections: data.corrections,
|
|
34099
34112
|
corrected_segments: data.corrected_segments
|
|
34100
34113
|
};
|
|
34101
|
-
const response = await fetch(
|
|
34114
|
+
const response = await fetch(this.buildUrl("/preview-video"), {
|
|
34102
34115
|
method: "POST",
|
|
34103
34116
|
headers: {
|
|
34104
34117
|
"Content-Type": "application/json"
|
|
@@ -34114,14 +34127,14 @@ class LiveApiClient {
|
|
|
34114
34127
|
return await response.json();
|
|
34115
34128
|
}
|
|
34116
34129
|
getPreviewVideoUrl(previewHash) {
|
|
34117
|
-
return
|
|
34130
|
+
return this.buildUrl(`/preview-video/${previewHash}`);
|
|
34118
34131
|
}
|
|
34119
34132
|
async updateHandlers(enabledHandlers) {
|
|
34120
34133
|
console.log("API: Starting handler update...");
|
|
34121
34134
|
this.isUpdatingHandlers = true;
|
|
34122
34135
|
console.log("API: Set isUpdatingHandlers to", this.isUpdatingHandlers);
|
|
34123
34136
|
try {
|
|
34124
|
-
const response = await fetch(
|
|
34137
|
+
const response = await fetch(this.buildUrl("/handlers"), {
|
|
34125
34138
|
method: "POST",
|
|
34126
34139
|
headers: {
|
|
34127
34140
|
"Content-Type": "application/json"
|
|
@@ -34147,7 +34160,7 @@ class LiveApiClient {
|
|
|
34147
34160
|
source,
|
|
34148
34161
|
lyrics
|
|
34149
34162
|
};
|
|
34150
|
-
const response = await fetch(
|
|
34163
|
+
const response = await fetch(this.buildUrl("/add-lyrics"), {
|
|
34151
34164
|
method: "POST",
|
|
34152
34165
|
headers: {
|
|
34153
34166
|
"Content-Type": "application/json"
|
|
@@ -34165,7 +34178,7 @@ class LiveApiClient {
|
|
|
34165
34178
|
}
|
|
34166
34179
|
async submitAnnotations(annotations) {
|
|
34167
34180
|
for (const annotation of annotations) {
|
|
34168
|
-
const response = await fetch(
|
|
34181
|
+
const response = await fetch(this.buildUrl("/v1/annotations"), {
|
|
34169
34182
|
method: "POST",
|
|
34170
34183
|
headers: {
|
|
34171
34184
|
"Content-Type": "application/json"
|
|
@@ -34178,7 +34191,7 @@ class LiveApiClient {
|
|
|
34178
34191
|
}
|
|
34179
34192
|
}
|
|
34180
34193
|
async getAnnotationStats() {
|
|
34181
|
-
const response = await fetch(
|
|
34194
|
+
const response = await fetch(this.buildUrl("/v1/annotations/stats"));
|
|
34182
34195
|
if (!response.ok) {
|
|
34183
34196
|
throw new Error(`API error: ${response.statusText}`);
|
|
34184
34197
|
}
|
|
@@ -36255,7 +36268,7 @@ const ZoomInIcon = createSvgIcon([/* @__PURE__ */ jsxRuntimeExports.jsx("path",
|
|
|
36255
36268
|
const ZoomOutIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
36256
36269
|
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
36270
|
}), "ZoomOut");
|
|
36258
|
-
const
|
|
36271
|
+
const ArrowBackIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
36259
36272
|
d: "M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20z"
|
|
36260
36273
|
}), "ArrowBack");
|
|
36261
36274
|
const ArrowForwardIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
@@ -36614,7 +36627,7 @@ const TimelineControls = reactExports.memo(({
|
|
|
36614
36627
|
onClick: onScrollLeft,
|
|
36615
36628
|
disabled: visibleStartTime <= startTime,
|
|
36616
36629
|
size: "small",
|
|
36617
|
-
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
36630
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowBackIcon, {})
|
|
36618
36631
|
}
|
|
36619
36632
|
) }),
|
|
36620
36633
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Zoom Out (Show More Time)", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -38118,6 +38131,12 @@ function PreviewVideoSection({
|
|
|
38118
38131
|
) })
|
|
38119
38132
|
] });
|
|
38120
38133
|
}
|
|
38134
|
+
const BlockIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38135
|
+
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"
|
|
38136
|
+
}), "Block");
|
|
38137
|
+
const ClearAllIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38138
|
+
d: "M5 13h14v-2H5zm-2 4h14v-2H3zM7 7v2h14V7z"
|
|
38139
|
+
}), "ClearAll");
|
|
38121
38140
|
const CloudUpload = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38122
38141
|
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
38142
|
}), "CloudUpload");
|
|
@@ -38127,6 +38146,9 @@ const ContentPasteIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("pa
|
|
|
38127
38146
|
const EditIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38128
38147
|
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
38148
|
}), "Edit");
|
|
38149
|
+
const EditNoteIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38150
|
+
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"
|
|
38151
|
+
}), "EditNote");
|
|
38130
38152
|
const FindReplaceIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38131
38153
|
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
38154
|
}), "FindReplace");
|
|
@@ -38142,12 +38164,18 @@ const OndemandVideo = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path"
|
|
|
38142
38164
|
const PauseIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38143
38165
|
d: "M6 19h4V5H6zm8-14v14h4V5z"
|
|
38144
38166
|
}), "Pause");
|
|
38167
|
+
const RateReviewIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38168
|
+
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"
|
|
38169
|
+
}), "RateReview");
|
|
38145
38170
|
const RedoIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38146
38171
|
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
38172
|
}), "Redo");
|
|
38148
38173
|
const RestoreIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38149
38174
|
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
38175
|
}), "Restore");
|
|
38176
|
+
const SyncIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38177
|
+
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"
|
|
38178
|
+
}), "Sync");
|
|
38151
38179
|
const TimerIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
|
|
38152
38180
|
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
38181
|
}), "Timer");
|
|
@@ -38408,7 +38436,7 @@ function ReviewChangesModal({
|
|
|
38408
38436
|
{
|
|
38409
38437
|
onClick: onClose,
|
|
38410
38438
|
color: "warning",
|
|
38411
|
-
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
38439
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowBackIcon, {}),
|
|
38412
38440
|
sx: { mr: "auto" },
|
|
38413
38441
|
children: "Cancel"
|
|
38414
38442
|
}
|
|
@@ -38428,545 +38456,1735 @@ function ReviewChangesModal({
|
|
|
38428
38456
|
}
|
|
38429
38457
|
);
|
|
38430
38458
|
}
|
|
38431
|
-
function
|
|
38459
|
+
function ModeSelectionModal({
|
|
38432
38460
|
open,
|
|
38433
38461
|
onClose,
|
|
38434
|
-
|
|
38435
|
-
|
|
38436
|
-
|
|
38437
|
-
setModalSpacebarHandler
|
|
38462
|
+
onSelectReplace,
|
|
38463
|
+
onSelectResync,
|
|
38464
|
+
hasExistingLyrics
|
|
38438
38465
|
}) {
|
|
38439
|
-
const [inputText, setInputText] = reactExports.useState("");
|
|
38440
|
-
const [isReplaced, setIsReplaced] = reactExports.useState(false);
|
|
38441
|
-
const [globalSegment, setGlobalSegment] = reactExports.useState(null);
|
|
38442
|
-
const [originalSegments, setOriginalSegments] = reactExports.useState([]);
|
|
38443
|
-
const [currentSegments, setCurrentSegments] = reactExports.useState([]);
|
|
38444
|
-
const getAudioDuration = reactExports.useCallback(() => {
|
|
38445
|
-
if (window.getAudioDuration) {
|
|
38446
|
-
const duration2 = window.getAudioDuration();
|
|
38447
|
-
return duration2 > 0 ? duration2 : 600;
|
|
38448
|
-
}
|
|
38449
|
-
return 600;
|
|
38450
|
-
}, []);
|
|
38451
|
-
const parseInfo = reactExports.useMemo(() => {
|
|
38452
|
-
if (!inputText.trim()) return { lines: 0, words: 0 };
|
|
38453
|
-
const lines = inputText.trim().split("\n").filter((line2) => line2.trim().length > 0);
|
|
38454
|
-
const totalWords = lines.reduce((count, line2) => {
|
|
38455
|
-
return count + line2.trim().split(/\s+/).length;
|
|
38456
|
-
}, 0);
|
|
38457
|
-
return { lines: lines.length, words: totalWords };
|
|
38458
|
-
}, [inputText]);
|
|
38459
|
-
const processLyrics = reactExports.useCallback(() => {
|
|
38460
|
-
if (!inputText.trim()) return;
|
|
38461
|
-
const lines = inputText.trim().split("\n").filter((line2) => line2.trim().length > 0);
|
|
38462
|
-
const newSegments = [];
|
|
38463
|
-
const allWords = [];
|
|
38464
|
-
lines.forEach((line2) => {
|
|
38465
|
-
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 = {
|
|
38480
|
-
id: nanoid(),
|
|
38481
|
-
text: line2.trim(),
|
|
38482
|
-
words: segmentWords,
|
|
38483
|
-
start_time: null,
|
|
38484
|
-
end_time: null
|
|
38485
|
-
};
|
|
38486
|
-
newSegments.push(segment);
|
|
38487
|
-
});
|
|
38488
|
-
const audioDuration = getAudioDuration();
|
|
38489
|
-
const endTime = Math.max(audioDuration, 3600);
|
|
38490
|
-
console.log("ReplaceAllLyricsModal - Creating global segment", {
|
|
38491
|
-
audioDuration,
|
|
38492
|
-
endTime,
|
|
38493
|
-
wordCount: allWords.length
|
|
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
|
-
}
|
|
38553
|
-
});
|
|
38554
|
-
setCurrentSegments(updatedSegments);
|
|
38555
|
-
}, [globalSegment, currentSegments]);
|
|
38556
|
-
const {
|
|
38557
|
-
isManualSyncing,
|
|
38558
|
-
isPaused,
|
|
38559
|
-
syncWordIndex,
|
|
38560
|
-
startManualSync,
|
|
38561
|
-
pauseManualSync,
|
|
38562
|
-
resumeManualSync,
|
|
38563
|
-
cleanupManualSync,
|
|
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;
|
|
38578
|
-
}
|
|
38579
|
-
console.log("ReplaceAllLyricsModal - Manual word update", {
|
|
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]);
|
|
38608
|
-
const handleClose = reactExports.useCallback(() => {
|
|
38609
|
-
cleanupManualSync();
|
|
38610
|
-
setInputText("");
|
|
38611
|
-
setIsReplaced(false);
|
|
38612
|
-
setGlobalSegment(null);
|
|
38613
|
-
setOriginalSegments([]);
|
|
38614
|
-
setCurrentSegments([]);
|
|
38615
|
-
onClose();
|
|
38616
|
-
}, [onClose, cleanupManualSync]);
|
|
38617
|
-
const handleSave = reactExports.useCallback(() => {
|
|
38618
|
-
if (!globalSegment || !currentSegments.length) return;
|
|
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);
|
|
38645
|
-
handleClose();
|
|
38646
|
-
}, [globalSegment, currentSegments, onSave, handleClose]);
|
|
38647
|
-
const handleReset = reactExports.useCallback(() => {
|
|
38648
|
-
if (!originalSegments.length) return;
|
|
38649
|
-
console.log("ReplaceAllLyricsModal - Resetting to original state");
|
|
38650
|
-
const resetWords = originalSegments.flatMap(
|
|
38651
|
-
(segment) => segment.words.map((word) => ({
|
|
38652
|
-
...word,
|
|
38653
|
-
start_time: null,
|
|
38654
|
-
end_time: null
|
|
38655
|
-
}))
|
|
38656
|
-
);
|
|
38657
|
-
const audioDuration = getAudioDuration();
|
|
38658
|
-
const resetGlobalSegment = {
|
|
38659
|
-
id: "global-replacement",
|
|
38660
|
-
text: resetWords.map((w) => w.text).join(" "),
|
|
38661
|
-
words: resetWords,
|
|
38662
|
-
start_time: 0,
|
|
38663
|
-
end_time: Math.max(audioDuration, 3600)
|
|
38664
|
-
// At least 1 hour to prevent auto-stop
|
|
38665
|
-
};
|
|
38666
|
-
const resetCurrentSegments = originalSegments.map((segment) => ({
|
|
38667
|
-
...segment,
|
|
38668
|
-
words: segment.words.map((word) => ({
|
|
38669
|
-
...word,
|
|
38670
|
-
start_time: null,
|
|
38671
|
-
end_time: null
|
|
38672
|
-
})),
|
|
38673
|
-
start_time: null,
|
|
38674
|
-
end_time: null
|
|
38675
|
-
}));
|
|
38676
|
-
setGlobalSegment(resetGlobalSegment);
|
|
38677
|
-
setCurrentSegments(resetCurrentSegments);
|
|
38678
|
-
}, [originalSegments, getAudioDuration]);
|
|
38679
|
-
const spacebarHandlerRef = reactExports.useRef(handleSpacebar);
|
|
38680
|
-
spacebarHandlerRef.current = handleSpacebar;
|
|
38681
|
-
reactExports.useEffect(() => {
|
|
38682
|
-
if (open && isReplaced) {
|
|
38683
|
-
console.log("ReplaceAllLyricsModal - Setting up spacebar handler");
|
|
38684
|
-
const handleKeyEvent = (e) => {
|
|
38685
|
-
if (e.code === "Space") {
|
|
38686
|
-
console.log("ReplaceAllLyricsModal - Spacebar captured in modal");
|
|
38687
|
-
e.preventDefault();
|
|
38688
|
-
e.stopPropagation();
|
|
38689
|
-
spacebarHandlerRef.current(e);
|
|
38690
|
-
}
|
|
38691
|
-
};
|
|
38692
|
-
setModalSpacebarHandler(() => handleKeyEvent);
|
|
38693
|
-
return () => {
|
|
38694
|
-
if (!open) {
|
|
38695
|
-
console.log("ReplaceAllLyricsModal - Clearing spacebar handler");
|
|
38696
|
-
setModalSpacebarHandler(void 0);
|
|
38697
|
-
}
|
|
38698
|
-
};
|
|
38699
|
-
} else if (open) {
|
|
38700
|
-
setModalSpacebarHandler(void 0);
|
|
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
38466
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
38713
38467
|
Dialog,
|
|
38714
38468
|
{
|
|
38715
38469
|
open,
|
|
38716
|
-
onClose
|
|
38717
|
-
maxWidth:
|
|
38470
|
+
onClose,
|
|
38471
|
+
maxWidth: "sm",
|
|
38718
38472
|
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
38473
|
children: [
|
|
38734
38474
|
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogTitle, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
|
|
38735
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { flex: 1 }, children: "
|
|
38736
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(IconButton, { onClick:
|
|
38475
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { flex: 1 }, children: "Edit All Lyrics" }),
|
|
38476
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(IconButton, { onClick: onClose, sx: { ml: "auto" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, {}) })
|
|
38737
38477
|
] }),
|
|
38738
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
38739
|
-
|
|
38740
|
-
{
|
|
38741
|
-
|
|
38742
|
-
|
|
38743
|
-
|
|
38744
|
-
|
|
38745
|
-
|
|
38746
|
-
|
|
38747
|
-
|
|
38748
|
-
|
|
38749
|
-
|
|
38750
|
-
|
|
38751
|
-
|
|
38752
|
-
/* @__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
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", gap: 2, mb: 2 }, children: [
|
|
38754
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
38755
|
-
Button,
|
|
38756
|
-
{
|
|
38757
|
-
variant: "outlined",
|
|
38758
|
-
onClick: handlePasteFromClipboard,
|
|
38759
|
-
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(ContentPasteIcon, {}),
|
|
38760
|
-
size: "small",
|
|
38761
|
-
children: "Paste from Clipboard"
|
|
38762
|
-
}
|
|
38763
|
-
),
|
|
38764
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(Typography, { variant: "body2", sx: {
|
|
38765
|
-
alignSelf: "center",
|
|
38766
|
-
color: "text.secondary",
|
|
38767
|
-
fontWeight: "medium"
|
|
38768
|
-
}, children: [
|
|
38769
|
-
parseInfo.lines,
|
|
38770
|
-
" lines, ",
|
|
38771
|
-
parseInfo.words,
|
|
38772
|
-
" words"
|
|
38773
|
-
] })
|
|
38774
|
-
] }),
|
|
38775
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
38776
|
-
TextField,
|
|
38777
|
-
{
|
|
38778
|
-
multiline: true,
|
|
38779
|
-
rows: 15,
|
|
38780
|
-
value: inputText,
|
|
38781
|
-
onChange: (e) => setInputText(e.target.value),
|
|
38782
|
-
placeholder: "Paste your lyrics here...\nEach line will become a segment\nWords will be separated by spaces",
|
|
38783
|
-
sx: {
|
|
38784
|
-
flexGrow: 1,
|
|
38785
|
-
"& .MuiInputBase-root": {
|
|
38786
|
-
height: "100%",
|
|
38787
|
-
alignItems: "flex-start"
|
|
38788
|
-
}
|
|
38789
|
-
}
|
|
38478
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { dividers: true, children: [
|
|
38479
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body1", sx: { mb: 3 }, children: "Choose how you want to edit the lyrics:" }),
|
|
38480
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 2 }, children: [
|
|
38481
|
+
hasExistingLyrics && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
38482
|
+
Paper,
|
|
38483
|
+
{
|
|
38484
|
+
sx: {
|
|
38485
|
+
p: 2,
|
|
38486
|
+
cursor: "pointer",
|
|
38487
|
+
border: 2,
|
|
38488
|
+
borderColor: "primary.main",
|
|
38489
|
+
"&:hover": {
|
|
38490
|
+
bgcolor: "action.hover",
|
|
38491
|
+
borderColor: "primary.dark"
|
|
38790
38492
|
}
|
|
38791
|
-
|
|
38792
|
-
|
|
38793
|
-
|
|
38794
|
-
{
|
|
38795
|
-
|
|
38796
|
-
|
|
38797
|
-
|
|
38798
|
-
|
|
38799
|
-
|
|
38493
|
+
},
|
|
38494
|
+
onClick: onSelectResync,
|
|
38495
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", alignItems: "flex-start", gap: 2 }, children: [
|
|
38496
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(SyncIcon, { color: "primary", sx: { fontSize: 40, mt: 0.5 } }),
|
|
38497
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { children: [
|
|
38498
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", color: "primary", children: "Re-sync Existing Lyrics" }),
|
|
38499
|
+
/* @__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." }),
|
|
38500
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "caption", color: "success.main", sx: { mt: 1, display: "block" }, children: "Recommended for fixing timing drift" })
|
|
38501
|
+
] })
|
|
38502
|
+
] })
|
|
38503
|
+
}
|
|
38504
|
+
),
|
|
38505
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
38506
|
+
Paper,
|
|
38507
|
+
{
|
|
38508
|
+
sx: {
|
|
38509
|
+
p: 2,
|
|
38510
|
+
cursor: "pointer",
|
|
38511
|
+
border: 1,
|
|
38512
|
+
borderColor: "divider",
|
|
38513
|
+
"&:hover": {
|
|
38514
|
+
bgcolor: "action.hover",
|
|
38515
|
+
borderColor: "text.secondary"
|
|
38800
38516
|
}
|
|
38801
|
-
|
|
38802
|
-
|
|
38803
|
-
|
|
38804
|
-
|
|
38805
|
-
|
|
38806
|
-
|
|
38807
|
-
|
|
38808
|
-
|
|
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."
|
|
38517
|
+
},
|
|
38518
|
+
onClick: onSelectReplace,
|
|
38519
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", alignItems: "flex-start", gap: 2 }, children: [
|
|
38520
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(ContentPasteIcon, { sx: { fontSize: 40, mt: 0.5, color: "text.secondary" } }),
|
|
38521
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { children: [
|
|
38522
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", children: "Replace All Lyrics" }),
|
|
38523
|
+
/* @__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." }),
|
|
38524
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "caption", color: "warning.main", sx: { mt: 1, display: "block" }, children: "All existing timing data will be lost" })
|
|
38814
38525
|
] })
|
|
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
38526
|
] })
|
|
38858
|
-
|
|
38527
|
+
}
|
|
38859
38528
|
)
|
|
38860
|
-
}
|
|
38861
|
-
),
|
|
38862
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(DialogActions, { children:
|
|
38863
|
-
EditActionBar,
|
|
38864
|
-
{
|
|
38865
|
-
onReset: handleReset,
|
|
38866
|
-
onClose: handleClose,
|
|
38867
|
-
onSave: handleSave,
|
|
38868
|
-
editedSegment: globalSegment,
|
|
38869
|
-
isGlobal: true
|
|
38870
|
-
}
|
|
38871
|
-
) })
|
|
38529
|
+
] })
|
|
38530
|
+
] }),
|
|
38531
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(DialogActions, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: onClose, color: "inherit", children: "Cancel" }) })
|
|
38872
38532
|
]
|
|
38873
38533
|
}
|
|
38874
38534
|
);
|
|
38875
38535
|
}
|
|
38876
|
-
const
|
|
38877
|
-
|
|
38878
|
-
|
|
38879
|
-
|
|
38880
|
-
|
|
38881
|
-
|
|
38882
|
-
|
|
38883
|
-
|
|
38884
|
-
|
|
38885
|
-
|
|
38886
|
-
|
|
38887
|
-
|
|
38888
|
-
|
|
38889
|
-
|
|
38890
|
-
|
|
38891
|
-
|
|
38892
|
-
|
|
38893
|
-
|
|
38894
|
-
|
|
38895
|
-
|
|
38896
|
-
|
|
38897
|
-
|
|
38898
|
-
|
|
38899
|
-
|
|
38900
|
-
|
|
38901
|
-
|
|
38902
|
-
|
|
38903
|
-
|
|
38904
|
-
|
|
38905
|
-
|
|
38906
|
-
|
|
38907
|
-
|
|
38908
|
-
|
|
38909
|
-
|
|
38910
|
-
|
|
38911
|
-
|
|
38912
|
-
|
|
38913
|
-
|
|
38914
|
-
|
|
38915
|
-
|
|
38916
|
-
|
|
38917
|
-
|
|
38918
|
-
|
|
38919
|
-
|
|
38920
|
-
|
|
38921
|
-
|
|
38922
|
-
|
|
38923
|
-
|
|
38924
|
-
|
|
38925
|
-
|
|
38926
|
-
|
|
38927
|
-
|
|
38928
|
-
|
|
38929
|
-
|
|
38930
|
-
|
|
38931
|
-
|
|
38932
|
-
|
|
38933
|
-
|
|
38934
|
-
|
|
38935
|
-
|
|
38936
|
-
|
|
38937
|
-
|
|
38938
|
-
|
|
38939
|
-
|
|
38940
|
-
|
|
38941
|
-
|
|
38942
|
-
|
|
38943
|
-
|
|
38944
|
-
|
|
38945
|
-
|
|
38946
|
-
|
|
38947
|
-
|
|
38948
|
-
|
|
38949
|
-
|
|
38950
|
-
|
|
38951
|
-
|
|
38952
|
-
|
|
38953
|
-
|
|
38954
|
-
|
|
38955
|
-
|
|
38956
|
-
|
|
38536
|
+
const TIME_BAR_HEIGHT = 28;
|
|
38537
|
+
const WORD_BLOCK_HEIGHT = 24;
|
|
38538
|
+
const WORD_LEVEL_SPACING = 50;
|
|
38539
|
+
const CANVAS_PADDING = 8;
|
|
38540
|
+
const TEXT_ABOVE_BLOCK = 14;
|
|
38541
|
+
const RESIZE_HANDLE_SIZE = 8;
|
|
38542
|
+
const RESIZE_HANDLE_HITAREA = 12;
|
|
38543
|
+
const PLAYHEAD_COLOR = "#ffffff";
|
|
38544
|
+
const WORD_BLOCK_COLOR = "#d32f2f";
|
|
38545
|
+
const WORD_BLOCK_SELECTED_COLOR = "#b71c1c";
|
|
38546
|
+
const WORD_BLOCK_CURRENT_COLOR = "#f44336";
|
|
38547
|
+
const WORD_TEXT_CURRENT_COLOR = "#d32f2f";
|
|
38548
|
+
const UPCOMING_WORD_BG = "#fff9c4";
|
|
38549
|
+
const UPCOMING_WORD_TEXT = "#000000";
|
|
38550
|
+
const TIME_BAR_BG = "#f5f5f5";
|
|
38551
|
+
const TIME_BAR_TEXT = "#666666";
|
|
38552
|
+
const TIMELINE_BG = "#e0e0e0";
|
|
38553
|
+
function buildWordToSegmentMap(segments) {
|
|
38554
|
+
const map = /* @__PURE__ */ new Map();
|
|
38555
|
+
segments.forEach((segment, idx) => {
|
|
38556
|
+
segment.words.forEach((word) => {
|
|
38557
|
+
map.set(word.id, idx);
|
|
38558
|
+
});
|
|
38559
|
+
});
|
|
38560
|
+
return map;
|
|
38561
|
+
}
|
|
38562
|
+
function calculateWordLevels(words, segments) {
|
|
38563
|
+
const levels = /* @__PURE__ */ new Map();
|
|
38564
|
+
const wordToSegment = buildWordToSegmentMap(segments);
|
|
38565
|
+
const segmentsWithTiming = segments.map((segment, idx) => {
|
|
38566
|
+
const timedWords = segment.words.filter((w) => w.start_time !== null);
|
|
38567
|
+
const minStart = timedWords.length > 0 ? Math.min(...timedWords.map((w) => w.start_time)) : Infinity;
|
|
38568
|
+
return { idx, minStart };
|
|
38569
|
+
}).filter((s) => s.minStart !== Infinity).sort((a, b) => a.minStart - b.minStart);
|
|
38570
|
+
const segmentLevels = /* @__PURE__ */ new Map();
|
|
38571
|
+
segmentsWithTiming.forEach(({ idx }, orderIndex) => {
|
|
38572
|
+
segmentLevels.set(idx, orderIndex % 2);
|
|
38573
|
+
});
|
|
38574
|
+
for (const word of words) {
|
|
38575
|
+
const segmentIdx = wordToSegment.get(word.id);
|
|
38576
|
+
if (segmentIdx !== void 0 && segmentLevels.has(segmentIdx)) {
|
|
38577
|
+
levels.set(word.id, segmentLevels.get(segmentIdx));
|
|
38578
|
+
} else {
|
|
38579
|
+
levels.set(word.id, 0);
|
|
38580
|
+
}
|
|
38581
|
+
}
|
|
38582
|
+
return levels;
|
|
38583
|
+
}
|
|
38584
|
+
function formatTime(seconds) {
|
|
38585
|
+
const mins = Math.floor(seconds / 60);
|
|
38586
|
+
const secs = Math.floor(seconds % 60);
|
|
38587
|
+
return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
|
|
38588
|
+
}
|
|
38589
|
+
const TimelineCanvas = reactExports.memo(function TimelineCanvas2({
|
|
38590
|
+
words,
|
|
38591
|
+
segments,
|
|
38592
|
+
visibleStartTime,
|
|
38593
|
+
visibleEndTime,
|
|
38594
|
+
currentTime,
|
|
38595
|
+
selectedWordIds,
|
|
38596
|
+
onWordClick,
|
|
38597
|
+
onBackgroundClick,
|
|
38598
|
+
onTimeBarClick,
|
|
38599
|
+
onSelectionComplete,
|
|
38600
|
+
onWordTimingChange,
|
|
38601
|
+
onWordsMove,
|
|
38602
|
+
syncWordIndex,
|
|
38603
|
+
isManualSyncing,
|
|
38604
|
+
onScrollChange,
|
|
38605
|
+
audioDuration,
|
|
38606
|
+
zoomSeconds,
|
|
38607
|
+
height: height2 = 200
|
|
38608
|
+
}) {
|
|
38609
|
+
const canvasRef = reactExports.useRef(null);
|
|
38610
|
+
const containerRef = reactExports.useRef(null);
|
|
38611
|
+
const [canvasWidth, setCanvasWidth] = reactExports.useState(800);
|
|
38612
|
+
const animationFrameRef = reactExports.useRef();
|
|
38613
|
+
const wordLevelsRef = reactExports.useRef(/* @__PURE__ */ new Map());
|
|
38614
|
+
const [dragMode, setDragMode] = reactExports.useState("none");
|
|
38615
|
+
const dragStartRef = reactExports.useRef(null);
|
|
38616
|
+
const dragWordIdRef = reactExports.useRef(null);
|
|
38617
|
+
const dragOriginalTimesRef = reactExports.useRef(/* @__PURE__ */ new Map());
|
|
38618
|
+
const [selectionRect, setSelectionRect] = reactExports.useState(null);
|
|
38619
|
+
const [hoveredWordId, setHoveredWordId] = reactExports.useState(null);
|
|
38620
|
+
const [cursorStyle, setCursorStyle] = reactExports.useState("default");
|
|
38621
|
+
reactExports.useEffect(() => {
|
|
38622
|
+
const updateWidth = () => {
|
|
38623
|
+
if (containerRef.current) {
|
|
38624
|
+
setCanvasWidth(containerRef.current.clientWidth);
|
|
38625
|
+
}
|
|
38626
|
+
};
|
|
38627
|
+
updateWidth();
|
|
38628
|
+
const resizeObserver = new ResizeObserver(updateWidth);
|
|
38629
|
+
if (containerRef.current) {
|
|
38630
|
+
resizeObserver.observe(containerRef.current);
|
|
38631
|
+
}
|
|
38632
|
+
return () => resizeObserver.disconnect();
|
|
38633
|
+
}, []);
|
|
38634
|
+
reactExports.useEffect(() => {
|
|
38635
|
+
wordLevelsRef.current = calculateWordLevels(words, segments);
|
|
38636
|
+
}, [words, segments]);
|
|
38637
|
+
const timeToX = reactExports.useCallback((time) => {
|
|
38638
|
+
const duration2 = visibleEndTime - visibleStartTime;
|
|
38639
|
+
if (duration2 <= 0) return 0;
|
|
38640
|
+
return CANVAS_PADDING + (time - visibleStartTime) / duration2 * (canvasWidth - CANVAS_PADDING * 2);
|
|
38641
|
+
}, [visibleStartTime, visibleEndTime, canvasWidth]);
|
|
38642
|
+
const xToTime = reactExports.useCallback((x) => {
|
|
38643
|
+
const duration2 = visibleEndTime - visibleStartTime;
|
|
38644
|
+
return visibleStartTime + (x - CANVAS_PADDING) / (canvasWidth - CANVAS_PADDING * 2) * duration2;
|
|
38645
|
+
}, [visibleStartTime, visibleEndTime, canvasWidth]);
|
|
38646
|
+
const getWordBounds = reactExports.useCallback((word) => {
|
|
38647
|
+
if (word.start_time === null || word.end_time === null) return null;
|
|
38648
|
+
const level = wordLevelsRef.current.get(word.id) || 0;
|
|
38649
|
+
const startX = timeToX(word.start_time);
|
|
38650
|
+
const endX = timeToX(word.end_time);
|
|
38651
|
+
const blockWidth = Math.max(endX - startX, 4);
|
|
38652
|
+
const y = TIME_BAR_HEIGHT + CANVAS_PADDING + TEXT_ABOVE_BLOCK + level * WORD_LEVEL_SPACING;
|
|
38653
|
+
return { startX, endX, blockWidth, y, level };
|
|
38654
|
+
}, [timeToX]);
|
|
38655
|
+
const isNearResizeHandlePos = reactExports.useCallback((word, x, y) => {
|
|
38656
|
+
const bounds = getWordBounds(word);
|
|
38657
|
+
if (!bounds) return false;
|
|
38658
|
+
const handleX = bounds.startX + bounds.blockWidth - RESIZE_HANDLE_SIZE / 2;
|
|
38659
|
+
const handleY = bounds.y + WORD_BLOCK_HEIGHT / 2;
|
|
38660
|
+
return Math.abs(x - handleX) < RESIZE_HANDLE_HITAREA / 2 && Math.abs(y - handleY) < RESIZE_HANDLE_HITAREA / 2;
|
|
38661
|
+
}, [getWordBounds]);
|
|
38662
|
+
const findWordAtPosition = reactExports.useCallback((x, y) => {
|
|
38663
|
+
for (const word of words) {
|
|
38664
|
+
const bounds = getWordBounds(word);
|
|
38665
|
+
if (!bounds) continue;
|
|
38666
|
+
if (x >= bounds.startX && x <= bounds.startX + bounds.blockWidth && y >= bounds.y && y <= bounds.y + WORD_BLOCK_HEIGHT) {
|
|
38667
|
+
return word;
|
|
38668
|
+
}
|
|
38669
|
+
}
|
|
38670
|
+
return null;
|
|
38671
|
+
}, [words, getWordBounds]);
|
|
38672
|
+
const findWordsInRect = reactExports.useCallback((rect) => {
|
|
38673
|
+
const rectLeft = Math.min(rect.startX, rect.endX);
|
|
38674
|
+
const rectRight = Math.max(rect.startX, rect.endX);
|
|
38675
|
+
const rectTop = Math.min(rect.startY, rect.endY);
|
|
38676
|
+
const rectBottom = Math.max(rect.startY, rect.endY);
|
|
38677
|
+
const selectedIds = [];
|
|
38678
|
+
for (const word of words) {
|
|
38679
|
+
const bounds = getWordBounds(word);
|
|
38680
|
+
if (!bounds) continue;
|
|
38681
|
+
if (bounds.startX + bounds.blockWidth >= rectLeft && bounds.startX <= rectRight && bounds.y + WORD_BLOCK_HEIGHT >= rectTop && bounds.y <= rectBottom) {
|
|
38682
|
+
selectedIds.push(word.id);
|
|
38683
|
+
}
|
|
38684
|
+
}
|
|
38685
|
+
return selectedIds;
|
|
38686
|
+
}, [words, getWordBounds]);
|
|
38687
|
+
const draw = reactExports.useCallback(() => {
|
|
38688
|
+
var _a;
|
|
38689
|
+
const canvas = canvasRef.current;
|
|
38690
|
+
if (!canvas) return;
|
|
38691
|
+
const ctx = canvas.getContext("2d");
|
|
38692
|
+
if (!ctx) return;
|
|
38693
|
+
const dpr = window.devicePixelRatio || 1;
|
|
38694
|
+
canvas.width = canvasWidth * dpr;
|
|
38695
|
+
canvas.height = height2 * dpr;
|
|
38696
|
+
ctx.scale(dpr, dpr);
|
|
38697
|
+
ctx.fillStyle = TIMELINE_BG;
|
|
38698
|
+
ctx.fillRect(0, 0, canvasWidth, height2);
|
|
38699
|
+
ctx.fillStyle = TIME_BAR_BG;
|
|
38700
|
+
ctx.fillRect(0, 0, canvasWidth, TIME_BAR_HEIGHT);
|
|
38701
|
+
const duration2 = visibleEndTime - visibleStartTime;
|
|
38702
|
+
const secondsPerTick = duration2 > 15 ? 2 : duration2 > 8 ? 1 : 0.5;
|
|
38703
|
+
const startSecond = Math.ceil(visibleStartTime / secondsPerTick) * secondsPerTick;
|
|
38704
|
+
ctx.fillStyle = TIME_BAR_TEXT;
|
|
38705
|
+
ctx.font = "11px system-ui, -apple-system, sans-serif";
|
|
38706
|
+
ctx.textAlign = "center";
|
|
38707
|
+
for (let t = startSecond; t <= visibleEndTime; t += secondsPerTick) {
|
|
38708
|
+
const x = timeToX(t);
|
|
38709
|
+
ctx.beginPath();
|
|
38710
|
+
ctx.strokeStyle = "#999999";
|
|
38711
|
+
ctx.lineWidth = 1;
|
|
38712
|
+
ctx.moveTo(x, TIME_BAR_HEIGHT - 6);
|
|
38713
|
+
ctx.lineTo(x, TIME_BAR_HEIGHT);
|
|
38714
|
+
ctx.stroke();
|
|
38715
|
+
if (t % 1 === 0) {
|
|
38716
|
+
ctx.fillText(formatTime(t), x, TIME_BAR_HEIGHT - 10);
|
|
38717
|
+
}
|
|
38718
|
+
}
|
|
38719
|
+
ctx.beginPath();
|
|
38720
|
+
ctx.strokeStyle = "#cccccc";
|
|
38721
|
+
ctx.lineWidth = 1;
|
|
38722
|
+
ctx.moveTo(0, TIME_BAR_HEIGHT);
|
|
38723
|
+
ctx.lineTo(canvasWidth, TIME_BAR_HEIGHT);
|
|
38724
|
+
ctx.stroke();
|
|
38725
|
+
const wordToSegment = buildWordToSegmentMap(segments);
|
|
38726
|
+
const syncedWords = words.filter((w) => w.start_time !== null && w.end_time !== null);
|
|
38727
|
+
const currentWordId = ((_a = syncedWords.find(
|
|
38728
|
+
(w) => currentTime >= w.start_time && currentTime <= w.end_time
|
|
38729
|
+
)) == null ? void 0 : _a.id) || null;
|
|
38730
|
+
for (const word of syncedWords) {
|
|
38731
|
+
const bounds = getWordBounds(word);
|
|
38732
|
+
if (!bounds) continue;
|
|
38733
|
+
const isSelected = selectedWordIds.has(word.id);
|
|
38734
|
+
const isCurrent = word.id === currentWordId;
|
|
38735
|
+
const isHovered = word.id === hoveredWordId;
|
|
38736
|
+
if (isSelected) {
|
|
38737
|
+
ctx.fillStyle = WORD_BLOCK_SELECTED_COLOR;
|
|
38738
|
+
} else if (isCurrent) {
|
|
38739
|
+
ctx.fillStyle = WORD_BLOCK_CURRENT_COLOR;
|
|
38740
|
+
} else {
|
|
38741
|
+
ctx.fillStyle = WORD_BLOCK_COLOR;
|
|
38742
|
+
}
|
|
38743
|
+
ctx.fillRect(bounds.startX, bounds.y, bounds.blockWidth, WORD_BLOCK_HEIGHT);
|
|
38744
|
+
if (isSelected) {
|
|
38745
|
+
ctx.strokeStyle = "#ffffff";
|
|
38746
|
+
ctx.lineWidth = 2;
|
|
38747
|
+
ctx.strokeRect(bounds.startX, bounds.y, bounds.blockWidth, WORD_BLOCK_HEIGHT);
|
|
38748
|
+
if (isHovered || selectedWordIds.size === 1) {
|
|
38749
|
+
const handleX = bounds.startX + bounds.blockWidth - RESIZE_HANDLE_SIZE / 2;
|
|
38750
|
+
const handleY = bounds.y + WORD_BLOCK_HEIGHT / 2;
|
|
38751
|
+
ctx.beginPath();
|
|
38752
|
+
ctx.fillStyle = "#ffffff";
|
|
38753
|
+
ctx.arc(handleX, handleY, RESIZE_HANDLE_SIZE / 2, 0, Math.PI * 2);
|
|
38754
|
+
ctx.fill();
|
|
38755
|
+
ctx.strokeStyle = "#666666";
|
|
38756
|
+
ctx.lineWidth = 1;
|
|
38757
|
+
ctx.stroke();
|
|
38758
|
+
}
|
|
38759
|
+
}
|
|
38760
|
+
}
|
|
38761
|
+
const wordsBySegment = /* @__PURE__ */ new Map();
|
|
38762
|
+
for (const word of syncedWords) {
|
|
38763
|
+
const segIdx = wordToSegment.get(word.id);
|
|
38764
|
+
if (segIdx !== void 0) {
|
|
38765
|
+
if (!wordsBySegment.has(segIdx)) {
|
|
38766
|
+
wordsBySegment.set(segIdx, []);
|
|
38767
|
+
}
|
|
38768
|
+
wordsBySegment.get(segIdx).push(word);
|
|
38769
|
+
}
|
|
38770
|
+
}
|
|
38771
|
+
ctx.font = "11px system-ui, -apple-system, sans-serif";
|
|
38772
|
+
ctx.textAlign = "left";
|
|
38773
|
+
for (const [, segmentWords] of wordsBySegment) {
|
|
38774
|
+
const sortedWords = [...segmentWords].sort(
|
|
38775
|
+
(a, b) => (a.start_time || 0) - (b.start_time || 0)
|
|
38957
38776
|
);
|
|
38958
|
-
|
|
38959
|
-
|
|
38777
|
+
if (sortedWords.length === 0) continue;
|
|
38778
|
+
const level = wordLevelsRef.current.get(sortedWords[0].id) || 0;
|
|
38779
|
+
const textY = TIME_BAR_HEIGHT + CANVAS_PADDING + TEXT_ABOVE_BLOCK + level * WORD_LEVEL_SPACING - 3;
|
|
38780
|
+
let rightmostTextEnd = -Infinity;
|
|
38781
|
+
for (const word of sortedWords) {
|
|
38782
|
+
const blockStartX = timeToX(word.start_time);
|
|
38783
|
+
const textWidth = ctx.measureText(word.text).width;
|
|
38784
|
+
const textStartX = Math.max(blockStartX, rightmostTextEnd + 3);
|
|
38785
|
+
if (textStartX < canvasWidth - 10) {
|
|
38786
|
+
const isCurrent = word.id === currentWordId;
|
|
38787
|
+
ctx.fillStyle = isCurrent ? WORD_TEXT_CURRENT_COLOR : "#333333";
|
|
38788
|
+
ctx.fillText(word.text, textStartX, textY);
|
|
38789
|
+
rightmostTextEnd = textStartX + textWidth;
|
|
38790
|
+
}
|
|
38791
|
+
}
|
|
38792
|
+
}
|
|
38793
|
+
if (isManualSyncing && syncWordIndex >= 0) {
|
|
38794
|
+
const upcomingWords = words.slice(syncWordIndex).filter((w) => w.start_time === null);
|
|
38795
|
+
const playheadX = timeToX(currentTime);
|
|
38796
|
+
let offsetX = playheadX + 10;
|
|
38797
|
+
ctx.font = "11px system-ui, -apple-system, sans-serif";
|
|
38798
|
+
for (let i = 0; i < Math.min(upcomingWords.length, 12); i++) {
|
|
38799
|
+
const word = upcomingWords[i];
|
|
38800
|
+
const textWidth = ctx.measureText(word.text).width + 10;
|
|
38801
|
+
ctx.fillStyle = UPCOMING_WORD_BG;
|
|
38802
|
+
ctx.fillRect(offsetX, TIME_BAR_HEIGHT + CANVAS_PADDING + WORD_LEVEL_SPACING + 60, textWidth, 20);
|
|
38803
|
+
ctx.fillStyle = UPCOMING_WORD_TEXT;
|
|
38804
|
+
ctx.textAlign = "left";
|
|
38805
|
+
ctx.fillText(word.text, offsetX + 5, TIME_BAR_HEIGHT + CANVAS_PADDING + WORD_LEVEL_SPACING + 74);
|
|
38806
|
+
offsetX += textWidth + 3;
|
|
38807
|
+
if (offsetX > canvasWidth - 20) break;
|
|
38808
|
+
}
|
|
38809
|
+
}
|
|
38810
|
+
if (currentTime >= visibleStartTime && currentTime <= visibleEndTime) {
|
|
38811
|
+
const playheadX = timeToX(currentTime);
|
|
38812
|
+
ctx.beginPath();
|
|
38813
|
+
ctx.fillStyle = PLAYHEAD_COLOR;
|
|
38814
|
+
ctx.strokeStyle = "#333333";
|
|
38815
|
+
ctx.lineWidth = 1;
|
|
38816
|
+
ctx.moveTo(playheadX - 6, 2);
|
|
38817
|
+
ctx.lineTo(playheadX + 6, 2);
|
|
38818
|
+
ctx.lineTo(playheadX, TIME_BAR_HEIGHT - 4);
|
|
38819
|
+
ctx.closePath();
|
|
38820
|
+
ctx.fill();
|
|
38821
|
+
ctx.stroke();
|
|
38822
|
+
ctx.beginPath();
|
|
38823
|
+
ctx.strokeStyle = PLAYHEAD_COLOR;
|
|
38824
|
+
ctx.lineWidth = 2;
|
|
38825
|
+
ctx.moveTo(playheadX, TIME_BAR_HEIGHT);
|
|
38826
|
+
ctx.lineTo(playheadX, height2);
|
|
38827
|
+
ctx.stroke();
|
|
38828
|
+
ctx.beginPath();
|
|
38829
|
+
ctx.strokeStyle = "rgba(0,0,0,0.4)";
|
|
38830
|
+
ctx.lineWidth = 1;
|
|
38831
|
+
ctx.moveTo(playheadX + 1, TIME_BAR_HEIGHT);
|
|
38832
|
+
ctx.lineTo(playheadX + 1, height2);
|
|
38833
|
+
ctx.stroke();
|
|
38834
|
+
}
|
|
38835
|
+
if (selectionRect) {
|
|
38836
|
+
ctx.fillStyle = "rgba(25, 118, 210, 0.2)";
|
|
38837
|
+
ctx.strokeStyle = "rgba(25, 118, 210, 0.8)";
|
|
38838
|
+
ctx.lineWidth = 1;
|
|
38839
|
+
const rectX = Math.min(selectionRect.startX, selectionRect.endX);
|
|
38840
|
+
const rectY = Math.min(selectionRect.startY, selectionRect.endY);
|
|
38841
|
+
const rectW = Math.abs(selectionRect.endX - selectionRect.startX);
|
|
38842
|
+
const rectH = Math.abs(selectionRect.endY - selectionRect.startY);
|
|
38843
|
+
ctx.fillRect(rectX, rectY, rectW, rectH);
|
|
38844
|
+
ctx.strokeRect(rectX, rectY, rectW, rectH);
|
|
38845
|
+
}
|
|
38846
|
+
}, [
|
|
38847
|
+
canvasWidth,
|
|
38848
|
+
height2,
|
|
38849
|
+
visibleStartTime,
|
|
38850
|
+
visibleEndTime,
|
|
38851
|
+
currentTime,
|
|
38852
|
+
words,
|
|
38853
|
+
segments,
|
|
38854
|
+
selectedWordIds,
|
|
38855
|
+
selectionRect,
|
|
38856
|
+
hoveredWordId,
|
|
38857
|
+
syncWordIndex,
|
|
38858
|
+
isManualSyncing,
|
|
38859
|
+
timeToX,
|
|
38860
|
+
getWordBounds
|
|
38861
|
+
]);
|
|
38862
|
+
reactExports.useEffect(() => {
|
|
38863
|
+
const animate = () => {
|
|
38864
|
+
draw();
|
|
38865
|
+
animationFrameRef.current = requestAnimationFrame(animate);
|
|
38866
|
+
};
|
|
38867
|
+
animate();
|
|
38868
|
+
return () => {
|
|
38869
|
+
if (animationFrameRef.current) {
|
|
38870
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
38871
|
+
}
|
|
38872
|
+
};
|
|
38873
|
+
}, [draw]);
|
|
38874
|
+
const handleMouseDown = reactExports.useCallback((e) => {
|
|
38875
|
+
var _a;
|
|
38876
|
+
const rect = (_a = canvasRef.current) == null ? void 0 : _a.getBoundingClientRect();
|
|
38877
|
+
if (!rect) return;
|
|
38878
|
+
const x = e.clientX - rect.left;
|
|
38879
|
+
const y = e.clientY - rect.top;
|
|
38880
|
+
const time = xToTime(x);
|
|
38881
|
+
if (y < TIME_BAR_HEIGHT) {
|
|
38882
|
+
onTimeBarClick(Math.max(0, time));
|
|
38883
|
+
return;
|
|
38884
|
+
}
|
|
38885
|
+
const clickedWord = findWordAtPosition(x, y);
|
|
38886
|
+
if (clickedWord && selectedWordIds.has(clickedWord.id)) {
|
|
38887
|
+
if (isNearResizeHandlePos(clickedWord, x, y)) {
|
|
38888
|
+
setDragMode("resize");
|
|
38889
|
+
dragStartRef.current = { x, y, time };
|
|
38890
|
+
dragWordIdRef.current = clickedWord.id;
|
|
38891
|
+
dragOriginalTimesRef.current = /* @__PURE__ */ new Map([[clickedWord.id, {
|
|
38892
|
+
start: clickedWord.start_time,
|
|
38893
|
+
end: clickedWord.end_time
|
|
38894
|
+
}]]);
|
|
38895
|
+
return;
|
|
38896
|
+
}
|
|
38897
|
+
setDragMode("move");
|
|
38898
|
+
dragStartRef.current = { x, y, time };
|
|
38899
|
+
dragWordIdRef.current = clickedWord.id;
|
|
38900
|
+
const originalTimes = /* @__PURE__ */ new Map();
|
|
38901
|
+
for (const wordId of selectedWordIds) {
|
|
38902
|
+
const word = words.find((w) => w.id === wordId);
|
|
38903
|
+
if (word && word.start_time !== null && word.end_time !== null) {
|
|
38904
|
+
originalTimes.set(wordId, { start: word.start_time, end: word.end_time });
|
|
38905
|
+
}
|
|
38906
|
+
}
|
|
38907
|
+
dragOriginalTimesRef.current = originalTimes;
|
|
38908
|
+
return;
|
|
38909
|
+
}
|
|
38910
|
+
if (clickedWord) {
|
|
38911
|
+
onWordClick(clickedWord.id, e);
|
|
38912
|
+
return;
|
|
38913
|
+
}
|
|
38914
|
+
setDragMode("selection");
|
|
38915
|
+
dragStartRef.current = { x, y, time };
|
|
38916
|
+
setSelectionRect({ startX: x, startY: y, endX: x, endY: y });
|
|
38917
|
+
}, [xToTime, onTimeBarClick, findWordAtPosition, selectedWordIds, isNearResizeHandlePos, onWordClick, words]);
|
|
38918
|
+
const handleMouseMove = reactExports.useCallback((e) => {
|
|
38919
|
+
var _a;
|
|
38920
|
+
const rect = (_a = canvasRef.current) == null ? void 0 : _a.getBoundingClientRect();
|
|
38921
|
+
if (!rect) return;
|
|
38922
|
+
const x = e.clientX - rect.left;
|
|
38923
|
+
const y = e.clientY - rect.top;
|
|
38924
|
+
const time = xToTime(x);
|
|
38925
|
+
if (dragMode === "none") {
|
|
38926
|
+
const hoveredWord = findWordAtPosition(x, y);
|
|
38927
|
+
setHoveredWordId((hoveredWord == null ? void 0 : hoveredWord.id) || null);
|
|
38928
|
+
if (hoveredWord && selectedWordIds.has(hoveredWord.id)) {
|
|
38929
|
+
const nearHandle = isNearResizeHandlePos(hoveredWord, x, y);
|
|
38930
|
+
setCursorStyle(nearHandle ? "ew-resize" : "grab");
|
|
38931
|
+
} else if (hoveredWord) {
|
|
38932
|
+
setCursorStyle("pointer");
|
|
38933
|
+
} else if (y < TIME_BAR_HEIGHT) {
|
|
38934
|
+
setCursorStyle("pointer");
|
|
38935
|
+
} else {
|
|
38936
|
+
setCursorStyle("default");
|
|
38937
|
+
}
|
|
38938
|
+
}
|
|
38939
|
+
if (!dragStartRef.current) return;
|
|
38940
|
+
if (dragMode === "selection") {
|
|
38941
|
+
setSelectionRect({
|
|
38942
|
+
startX: dragStartRef.current.x,
|
|
38943
|
+
startY: dragStartRef.current.y,
|
|
38944
|
+
endX: x,
|
|
38945
|
+
endY: y
|
|
38946
|
+
});
|
|
38947
|
+
} else if (dragMode === "resize" && dragWordIdRef.current) {
|
|
38948
|
+
const originalTimes = dragOriginalTimesRef.current.get(dragWordIdRef.current);
|
|
38949
|
+
if (originalTimes) {
|
|
38950
|
+
const deltaTime = time - dragStartRef.current.time;
|
|
38951
|
+
const newEndTime = Math.max(originalTimes.start + 0.05, originalTimes.end + deltaTime);
|
|
38952
|
+
onWordTimingChange(dragWordIdRef.current, originalTimes.start, newEndTime);
|
|
38953
|
+
}
|
|
38954
|
+
setCursorStyle("ew-resize");
|
|
38955
|
+
} else if (dragMode === "move") {
|
|
38956
|
+
const deltaTime = time - dragStartRef.current.time;
|
|
38957
|
+
const updates = [];
|
|
38958
|
+
for (const [wordId, originalTimes] of dragOriginalTimesRef.current) {
|
|
38959
|
+
const newStartTime = Math.max(0, originalTimes.start + deltaTime);
|
|
38960
|
+
const newEndTime = Math.max(newStartTime + 0.05, originalTimes.end + deltaTime);
|
|
38961
|
+
updates.push({
|
|
38962
|
+
wordId,
|
|
38963
|
+
newStartTime,
|
|
38964
|
+
newEndTime
|
|
38965
|
+
});
|
|
38966
|
+
}
|
|
38967
|
+
if (updates.length > 0) {
|
|
38968
|
+
onWordsMove(updates);
|
|
38969
|
+
}
|
|
38970
|
+
setCursorStyle("grabbing");
|
|
38971
|
+
}
|
|
38972
|
+
}, [dragMode, xToTime, findWordAtPosition, selectedWordIds, isNearResizeHandlePos, onWordTimingChange, onWordsMove]);
|
|
38973
|
+
const handleMouseUp = reactExports.useCallback((e) => {
|
|
38974
|
+
var _a;
|
|
38975
|
+
const rect = (_a = canvasRef.current) == null ? void 0 : _a.getBoundingClientRect();
|
|
38976
|
+
if (dragMode === "selection" && dragStartRef.current && rect) {
|
|
38977
|
+
const endX = e.clientX - rect.left;
|
|
38978
|
+
const endY = e.clientY - rect.top;
|
|
38979
|
+
const dragDistance = Math.sqrt(
|
|
38980
|
+
Math.pow(endX - dragStartRef.current.x, 2) + Math.pow(endY - dragStartRef.current.y, 2)
|
|
38981
|
+
);
|
|
38982
|
+
if (dragDistance < 5) {
|
|
38983
|
+
onBackgroundClick();
|
|
38984
|
+
} else {
|
|
38985
|
+
const finalRect = {
|
|
38986
|
+
startX: dragStartRef.current.x,
|
|
38987
|
+
startY: dragStartRef.current.y,
|
|
38988
|
+
endX,
|
|
38989
|
+
endY
|
|
38990
|
+
};
|
|
38991
|
+
const selectedIds = findWordsInRect(finalRect);
|
|
38992
|
+
if (selectedIds.length > 0) {
|
|
38993
|
+
onSelectionComplete(selectedIds);
|
|
38994
|
+
}
|
|
38995
|
+
}
|
|
38996
|
+
}
|
|
38997
|
+
setDragMode("none");
|
|
38998
|
+
dragStartRef.current = null;
|
|
38999
|
+
dragWordIdRef.current = null;
|
|
39000
|
+
dragOriginalTimesRef.current = /* @__PURE__ */ new Map();
|
|
39001
|
+
setSelectionRect(null);
|
|
39002
|
+
setCursorStyle("default");
|
|
39003
|
+
}, [dragMode, onBackgroundClick, findWordsInRect, onSelectionComplete]);
|
|
39004
|
+
const handleWheel = reactExports.useCallback((e) => {
|
|
39005
|
+
const delta = e.deltaX !== 0 ? e.deltaX : e.deltaY;
|
|
39006
|
+
const scrollAmount = delta / 100 * (zoomSeconds / 4);
|
|
39007
|
+
let newStart = Math.max(0, Math.min(audioDuration - zoomSeconds, visibleStartTime + scrollAmount));
|
|
39008
|
+
if (newStart !== visibleStartTime) {
|
|
39009
|
+
onScrollChange(newStart);
|
|
39010
|
+
}
|
|
39011
|
+
}, [visibleStartTime, zoomSeconds, audioDuration, onScrollChange]);
|
|
39012
|
+
const handleScrollLeft = reactExports.useCallback(() => {
|
|
39013
|
+
const newStart = Math.max(0, visibleStartTime - zoomSeconds * 0.25);
|
|
39014
|
+
onScrollChange(newStart);
|
|
39015
|
+
}, [visibleStartTime, zoomSeconds, onScrollChange]);
|
|
39016
|
+
const handleScrollRight = reactExports.useCallback(() => {
|
|
39017
|
+
const newStart = Math.min(audioDuration - zoomSeconds, visibleStartTime + zoomSeconds * 0.25);
|
|
39018
|
+
onScrollChange(Math.max(0, newStart));
|
|
39019
|
+
}, [visibleStartTime, zoomSeconds, audioDuration, onScrollChange]);
|
|
39020
|
+
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: [
|
|
39021
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Scroll Left", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39022
|
+
IconButton,
|
|
39023
|
+
{
|
|
39024
|
+
size: "small",
|
|
39025
|
+
onClick: handleScrollLeft,
|
|
39026
|
+
disabled: visibleStartTime <= 0,
|
|
39027
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowBackIcon, { fontSize: "small" })
|
|
39028
|
+
}
|
|
39029
|
+
) }),
|
|
39030
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39031
|
+
Box,
|
|
39032
|
+
{
|
|
39033
|
+
ref: containerRef,
|
|
39034
|
+
sx: {
|
|
39035
|
+
flexGrow: 1,
|
|
39036
|
+
height: height2,
|
|
39037
|
+
cursor: cursorStyle,
|
|
39038
|
+
borderRadius: 1,
|
|
39039
|
+
overflow: "hidden"
|
|
39040
|
+
},
|
|
39041
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39042
|
+
"canvas",
|
|
39043
|
+
{
|
|
39044
|
+
ref: canvasRef,
|
|
39045
|
+
style: {
|
|
39046
|
+
width: "100%",
|
|
39047
|
+
height: "100%",
|
|
39048
|
+
display: "block",
|
|
39049
|
+
cursor: cursorStyle
|
|
39050
|
+
},
|
|
39051
|
+
onMouseDown: handleMouseDown,
|
|
39052
|
+
onMouseMove: handleMouseMove,
|
|
39053
|
+
onMouseUp: handleMouseUp,
|
|
39054
|
+
onMouseLeave: handleMouseUp,
|
|
39055
|
+
onWheel: handleWheel
|
|
39056
|
+
}
|
|
39057
|
+
)
|
|
39058
|
+
}
|
|
39059
|
+
),
|
|
39060
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Scroll Right", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39061
|
+
IconButton,
|
|
39062
|
+
{
|
|
39063
|
+
size: "small",
|
|
39064
|
+
onClick: handleScrollRight,
|
|
39065
|
+
disabled: visibleStartTime >= audioDuration - zoomSeconds,
|
|
39066
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowForwardIcon, { fontSize: "small" })
|
|
39067
|
+
}
|
|
39068
|
+
) })
|
|
39069
|
+
] }) });
|
|
39070
|
+
});
|
|
39071
|
+
const UpcomingWordsBar = reactExports.memo(function UpcomingWordsBar2({
|
|
39072
|
+
words,
|
|
39073
|
+
syncWordIndex,
|
|
39074
|
+
isManualSyncing,
|
|
39075
|
+
maxWordsToShow = 20
|
|
39076
|
+
}) {
|
|
39077
|
+
const upcomingWords = reactExports.useMemo(() => {
|
|
39078
|
+
if (!isManualSyncing || syncWordIndex < 0) return [];
|
|
39079
|
+
return words.slice(syncWordIndex).filter((w) => w.start_time === null).slice(0, maxWordsToShow);
|
|
39080
|
+
}, [words, syncWordIndex, isManualSyncing, maxWordsToShow]);
|
|
39081
|
+
const totalRemaining = reactExports.useMemo(() => {
|
|
39082
|
+
if (!isManualSyncing || syncWordIndex < 0) return 0;
|
|
39083
|
+
return words.slice(syncWordIndex).filter((w) => w.start_time === null).length;
|
|
39084
|
+
}, [words, syncWordIndex, isManualSyncing]);
|
|
39085
|
+
if (upcomingWords.length === 0) {
|
|
39086
|
+
return null;
|
|
39087
|
+
}
|
|
39088
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: {
|
|
39089
|
+
height: 44,
|
|
39090
|
+
bgcolor: "grey.100",
|
|
39091
|
+
borderRadius: 1,
|
|
39092
|
+
display: "flex",
|
|
39093
|
+
alignItems: "center",
|
|
39094
|
+
px: 1,
|
|
39095
|
+
gap: 0.5,
|
|
39096
|
+
overflow: "hidden",
|
|
39097
|
+
boxSizing: "border-box"
|
|
39098
|
+
}, children: [
|
|
39099
|
+
upcomingWords.map((word, index) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39100
|
+
Box,
|
|
39101
|
+
{
|
|
39102
|
+
sx: {
|
|
39103
|
+
px: 1,
|
|
39104
|
+
py: 0.5,
|
|
39105
|
+
borderRadius: 0.5,
|
|
39106
|
+
bgcolor: index === 0 ? "error.main" : "grey.300",
|
|
39107
|
+
color: index === 0 ? "white" : "text.primary",
|
|
39108
|
+
fontWeight: index === 0 ? "bold" : "normal",
|
|
39109
|
+
fontSize: "13px",
|
|
39110
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
39111
|
+
whiteSpace: "nowrap",
|
|
39112
|
+
border: index === 0 ? "2px solid" : "none",
|
|
39113
|
+
borderColor: "error.dark"
|
|
39114
|
+
},
|
|
39115
|
+
children: word.text
|
|
39116
|
+
},
|
|
39117
|
+
word.id
|
|
39118
|
+
)),
|
|
39119
|
+
totalRemaining > maxWordsToShow && /* @__PURE__ */ jsxRuntimeExports.jsxs(Typography, { variant: "caption", color: "text.secondary", sx: { ml: 1 }, children: [
|
|
39120
|
+
"+",
|
|
39121
|
+
totalRemaining - maxWordsToShow,
|
|
39122
|
+
" more"
|
|
39123
|
+
] })
|
|
39124
|
+
] });
|
|
39125
|
+
});
|
|
39126
|
+
const SyncControls = reactExports.memo(function SyncControls2({
|
|
39127
|
+
isManualSyncing,
|
|
39128
|
+
isPaused,
|
|
39129
|
+
onStartSync,
|
|
39130
|
+
onPauseSync,
|
|
39131
|
+
onResumeSync,
|
|
39132
|
+
onClearSync,
|
|
39133
|
+
onEditLyrics,
|
|
39134
|
+
onPlay,
|
|
39135
|
+
onStop,
|
|
39136
|
+
isPlaying,
|
|
39137
|
+
hasSelectedWords,
|
|
39138
|
+
selectedWordCount,
|
|
39139
|
+
onUnsyncFromCursor,
|
|
39140
|
+
onEditSelectedWord,
|
|
39141
|
+
onDeleteSelected,
|
|
39142
|
+
canUnsyncFromCursor
|
|
39143
|
+
}) {
|
|
39144
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 1.5 }, children: [
|
|
39145
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", flexWrap: "wrap", children: [
|
|
39146
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39147
|
+
Button,
|
|
38960
39148
|
{
|
|
38961
|
-
|
|
38962
|
-
|
|
38963
|
-
|
|
39149
|
+
variant: "outlined",
|
|
39150
|
+
color: "primary",
|
|
39151
|
+
onClick: onPlay,
|
|
39152
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(PlayArrowIcon, {}),
|
|
39153
|
+
size: "small",
|
|
39154
|
+
disabled: isPlaying,
|
|
39155
|
+
children: "Play"
|
|
39156
|
+
}
|
|
39157
|
+
),
|
|
39158
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39159
|
+
Button,
|
|
39160
|
+
{
|
|
39161
|
+
variant: "outlined",
|
|
39162
|
+
color: "error",
|
|
39163
|
+
onClick: onStop,
|
|
39164
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(StopIcon, {}),
|
|
39165
|
+
size: "small",
|
|
39166
|
+
disabled: !isPlaying,
|
|
39167
|
+
children: "Stop"
|
|
39168
|
+
}
|
|
39169
|
+
),
|
|
39170
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Divider, { orientation: "vertical", flexItem: true, sx: { mx: 0.5 } }),
|
|
39171
|
+
isManualSyncing ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
39172
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39173
|
+
Button,
|
|
39174
|
+
{
|
|
39175
|
+
variant: "contained",
|
|
39176
|
+
color: "error",
|
|
39177
|
+
onClick: onStartSync,
|
|
39178
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(StopIcon, {}),
|
|
39179
|
+
size: "small",
|
|
39180
|
+
children: "Stop Sync"
|
|
39181
|
+
}
|
|
39182
|
+
),
|
|
39183
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39184
|
+
Button,
|
|
39185
|
+
{
|
|
39186
|
+
variant: "outlined",
|
|
39187
|
+
color: isPaused ? "success" : "warning",
|
|
39188
|
+
onClick: isPaused ? onResumeSync : onPauseSync,
|
|
39189
|
+
startIcon: isPaused ? /* @__PURE__ */ jsxRuntimeExports.jsx(PlayArrowIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(PauseIcon, {}),
|
|
39190
|
+
size: "small",
|
|
39191
|
+
children: isPaused ? "Resume" : "Pause"
|
|
39192
|
+
}
|
|
39193
|
+
)
|
|
39194
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39195
|
+
Button,
|
|
39196
|
+
{
|
|
39197
|
+
variant: "contained",
|
|
39198
|
+
color: "primary",
|
|
39199
|
+
onClick: onStartSync,
|
|
39200
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(PlayCircleOutlineIcon, {}),
|
|
39201
|
+
size: "small",
|
|
39202
|
+
children: "Start Sync"
|
|
39203
|
+
}
|
|
39204
|
+
)
|
|
39205
|
+
] }),
|
|
39206
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", flexWrap: "wrap", children: [
|
|
39207
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39208
|
+
Button,
|
|
39209
|
+
{
|
|
39210
|
+
variant: "outlined",
|
|
39211
|
+
color: "warning",
|
|
39212
|
+
onClick: onClearSync,
|
|
39213
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(ClearAllIcon, {}),
|
|
39214
|
+
size: "small",
|
|
39215
|
+
disabled: isManualSyncing && !isPaused,
|
|
39216
|
+
children: "Clear Sync"
|
|
39217
|
+
}
|
|
39218
|
+
),
|
|
39219
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39220
|
+
Button,
|
|
39221
|
+
{
|
|
39222
|
+
variant: "outlined",
|
|
39223
|
+
onClick: onEditLyrics,
|
|
39224
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(EditNoteIcon, {}),
|
|
39225
|
+
size: "small",
|
|
39226
|
+
disabled: isManualSyncing && !isPaused,
|
|
39227
|
+
children: "Edit Lyrics"
|
|
39228
|
+
}
|
|
39229
|
+
),
|
|
39230
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Divider, { orientation: "vertical", flexItem: true, sx: { mx: 0.5 } }),
|
|
39231
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39232
|
+
Button,
|
|
39233
|
+
{
|
|
39234
|
+
variant: "outlined",
|
|
39235
|
+
onClick: onUnsyncFromCursor,
|
|
39236
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(BlockIcon, {}),
|
|
39237
|
+
size: "small",
|
|
39238
|
+
disabled: !canUnsyncFromCursor || isManualSyncing && !isPaused,
|
|
39239
|
+
children: "Unsync from Cursor"
|
|
39240
|
+
}
|
|
39241
|
+
),
|
|
39242
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39243
|
+
Button,
|
|
39244
|
+
{
|
|
39245
|
+
variant: "outlined",
|
|
39246
|
+
onClick: onEditSelectedWord,
|
|
39247
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(EditIcon, {}),
|
|
39248
|
+
size: "small",
|
|
39249
|
+
disabled: !hasSelectedWords || selectedWordCount !== 1,
|
|
39250
|
+
children: "Edit Word"
|
|
39251
|
+
}
|
|
39252
|
+
),
|
|
39253
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
39254
|
+
Button,
|
|
39255
|
+
{
|
|
39256
|
+
variant: "outlined",
|
|
39257
|
+
color: "error",
|
|
39258
|
+
onClick: onDeleteSelected,
|
|
39259
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(DeleteIcon, {}),
|
|
39260
|
+
size: "small",
|
|
39261
|
+
disabled: !hasSelectedWords || isManualSyncing && !isPaused,
|
|
39262
|
+
children: [
|
|
39263
|
+
"Delete",
|
|
39264
|
+
hasSelectedWords && selectedWordCount > 0 ? ` (${selectedWordCount})` : ""
|
|
39265
|
+
]
|
|
39266
|
+
}
|
|
39267
|
+
)
|
|
39268
|
+
] })
|
|
39269
|
+
] });
|
|
39270
|
+
});
|
|
39271
|
+
const MIN_ZOOM_SECONDS = 4.5;
|
|
39272
|
+
const MAX_ZOOM_SECONDS = 24;
|
|
39273
|
+
const ZOOM_STEPS = 50;
|
|
39274
|
+
function getAllWords(segments) {
|
|
39275
|
+
return segments.flatMap((s) => s.words);
|
|
39276
|
+
}
|
|
39277
|
+
function cloneSegments(segments) {
|
|
39278
|
+
return JSON.parse(JSON.stringify(segments));
|
|
39279
|
+
}
|
|
39280
|
+
const LyricsSynchronizer = reactExports.memo(function LyricsSynchronizer2({
|
|
39281
|
+
segments: initialSegments,
|
|
39282
|
+
currentTime,
|
|
39283
|
+
onPlaySegment,
|
|
39284
|
+
onSave,
|
|
39285
|
+
onCancel,
|
|
39286
|
+
setModalSpacebarHandler
|
|
39287
|
+
}) {
|
|
39288
|
+
const [workingSegments, setWorkingSegments] = reactExports.useState(
|
|
39289
|
+
() => cloneSegments(initialSegments)
|
|
39290
|
+
);
|
|
39291
|
+
const allWords = reactExports.useMemo(() => getAllWords(workingSegments), [workingSegments]);
|
|
39292
|
+
const audioDuration = reactExports.useMemo(() => {
|
|
39293
|
+
if (typeof window.getAudioDuration === "function") {
|
|
39294
|
+
const duration2 = window.getAudioDuration();
|
|
39295
|
+
return duration2 > 0 ? duration2 : 300;
|
|
39296
|
+
}
|
|
39297
|
+
return 300;
|
|
39298
|
+
}, []);
|
|
39299
|
+
const [zoomSeconds, setZoomSeconds] = reactExports.useState(12);
|
|
39300
|
+
const [visibleStartTime, setVisibleStartTime] = reactExports.useState(0);
|
|
39301
|
+
const visibleEndTime = reactExports.useMemo(
|
|
39302
|
+
() => Math.min(visibleStartTime + zoomSeconds, audioDuration),
|
|
39303
|
+
[visibleStartTime, zoomSeconds, audioDuration]
|
|
39304
|
+
);
|
|
39305
|
+
const [isManualSyncing, setIsManualSyncing] = reactExports.useState(false);
|
|
39306
|
+
const [isPaused, setIsPaused] = reactExports.useState(false);
|
|
39307
|
+
const [syncWordIndex, setSyncWordIndex] = reactExports.useState(-1);
|
|
39308
|
+
const [isSpacebarPressed, setIsSpacebarPressed] = reactExports.useState(false);
|
|
39309
|
+
const wordStartTimeRef = reactExports.useRef(null);
|
|
39310
|
+
const spacebarPressTimeRef = reactExports.useRef(null);
|
|
39311
|
+
const currentTimeRef = reactExports.useRef(currentTime);
|
|
39312
|
+
const [selectedWordIds, setSelectedWordIds] = reactExports.useState(/* @__PURE__ */ new Set());
|
|
39313
|
+
const [showEditLyricsModal, setShowEditLyricsModal] = reactExports.useState(false);
|
|
39314
|
+
const [editLyricsText, setEditLyricsText] = reactExports.useState("");
|
|
39315
|
+
const [showEditWordModal, setShowEditWordModal] = reactExports.useState(false);
|
|
39316
|
+
const [editWordText, setEditWordText] = reactExports.useState("");
|
|
39317
|
+
const [editWordId, setEditWordId] = reactExports.useState(null);
|
|
39318
|
+
reactExports.useEffect(() => {
|
|
39319
|
+
currentTimeRef.current = currentTime;
|
|
39320
|
+
}, [currentTime]);
|
|
39321
|
+
reactExports.useEffect(() => {
|
|
39322
|
+
if (isManualSyncing && !isPaused && currentTime > 0) {
|
|
39323
|
+
if (currentTime > visibleEndTime - zoomSeconds * 0.1) {
|
|
39324
|
+
const newStart = Math.max(0, currentTime - zoomSeconds * 0.1);
|
|
39325
|
+
setVisibleStartTime(newStart);
|
|
39326
|
+
} else if (currentTime < visibleStartTime) {
|
|
39327
|
+
setVisibleStartTime(Math.max(0, currentTime - 1));
|
|
39328
|
+
}
|
|
39329
|
+
}
|
|
39330
|
+
}, [currentTime, isManualSyncing, isPaused, visibleStartTime, visibleEndTime, zoomSeconds]);
|
|
39331
|
+
const handleZoomChange = reactExports.useCallback((_, value) => {
|
|
39332
|
+
const zoomValue = value;
|
|
39333
|
+
const newZoomSeconds = MIN_ZOOM_SECONDS + zoomValue / ZOOM_STEPS * (MAX_ZOOM_SECONDS - MIN_ZOOM_SECONDS);
|
|
39334
|
+
setZoomSeconds(newZoomSeconds);
|
|
39335
|
+
}, []);
|
|
39336
|
+
const sliderValue = reactExports.useMemo(() => {
|
|
39337
|
+
return (zoomSeconds - MIN_ZOOM_SECONDS) / (MAX_ZOOM_SECONDS - MIN_ZOOM_SECONDS) * ZOOM_STEPS;
|
|
39338
|
+
}, [zoomSeconds]);
|
|
39339
|
+
const handleScrollChange = reactExports.useCallback((newStartTime) => {
|
|
39340
|
+
setVisibleStartTime(newStartTime);
|
|
39341
|
+
}, []);
|
|
39342
|
+
const updateWords = reactExports.useCallback((newWords) => {
|
|
39343
|
+
setWorkingSegments((prevSegments) => {
|
|
39344
|
+
const newSegments = cloneSegments(prevSegments);
|
|
39345
|
+
const wordMap = new Map(newWords.map((w) => [w.id, w]));
|
|
39346
|
+
for (const segment of newSegments) {
|
|
39347
|
+
segment.words = segment.words.map((w) => wordMap.get(w.id) || w);
|
|
39348
|
+
const timedWords = segment.words.filter(
|
|
39349
|
+
(w) => w.start_time !== null && w.end_time !== null
|
|
39350
|
+
);
|
|
39351
|
+
if (timedWords.length > 0) {
|
|
39352
|
+
segment.start_time = Math.min(...timedWords.map((w) => w.start_time));
|
|
39353
|
+
segment.end_time = Math.max(...timedWords.map((w) => w.end_time));
|
|
39354
|
+
} else {
|
|
39355
|
+
segment.start_time = null;
|
|
39356
|
+
segment.end_time = null;
|
|
39357
|
+
}
|
|
39358
|
+
}
|
|
39359
|
+
return newSegments;
|
|
39360
|
+
});
|
|
39361
|
+
}, []);
|
|
39362
|
+
const [isPlaying, setIsPlaying] = reactExports.useState(false);
|
|
39363
|
+
reactExports.useEffect(() => {
|
|
39364
|
+
const checkPlaying = () => {
|
|
39365
|
+
setIsPlaying(typeof window.isAudioPlaying === "boolean" ? window.isAudioPlaying : false);
|
|
39366
|
+
};
|
|
39367
|
+
checkPlaying();
|
|
39368
|
+
const interval = setInterval(checkPlaying, 100);
|
|
39369
|
+
return () => clearInterval(interval);
|
|
39370
|
+
}, []);
|
|
39371
|
+
const handlePlayAudio = reactExports.useCallback(() => {
|
|
39372
|
+
if (onPlaySegment) {
|
|
39373
|
+
onPlaySegment(currentTimeRef.current);
|
|
39374
|
+
}
|
|
39375
|
+
}, [onPlaySegment]);
|
|
39376
|
+
const handleStopAudio = reactExports.useCallback(() => {
|
|
39377
|
+
if (typeof window.toggleAudioPlayback === "function" && window.isAudioPlaying) {
|
|
39378
|
+
window.toggleAudioPlayback();
|
|
39379
|
+
}
|
|
39380
|
+
if (isManualSyncing) {
|
|
39381
|
+
setIsManualSyncing(false);
|
|
39382
|
+
setIsPaused(false);
|
|
39383
|
+
setIsSpacebarPressed(false);
|
|
39384
|
+
}
|
|
39385
|
+
}, [isManualSyncing]);
|
|
39386
|
+
const handleStartSync = reactExports.useCallback(() => {
|
|
39387
|
+
if (isManualSyncing) {
|
|
39388
|
+
setIsManualSyncing(false);
|
|
39389
|
+
setIsPaused(false);
|
|
39390
|
+
setSyncWordIndex(-1);
|
|
39391
|
+
setIsSpacebarPressed(false);
|
|
39392
|
+
handleStopAudio();
|
|
39393
|
+
return;
|
|
39394
|
+
}
|
|
39395
|
+
const firstUnsyncedIndex = allWords.findIndex(
|
|
39396
|
+
(w) => w.start_time === null || w.end_time === null
|
|
39397
|
+
);
|
|
39398
|
+
const startIndex = firstUnsyncedIndex !== -1 ? firstUnsyncedIndex : 0;
|
|
39399
|
+
setIsManualSyncing(true);
|
|
39400
|
+
setIsPaused(false);
|
|
39401
|
+
setSyncWordIndex(startIndex);
|
|
39402
|
+
setIsSpacebarPressed(false);
|
|
39403
|
+
if (onPlaySegment) {
|
|
39404
|
+
onPlaySegment(Math.max(0, currentTimeRef.current - 1));
|
|
39405
|
+
}
|
|
39406
|
+
}, [isManualSyncing, allWords, onPlaySegment, handleStopAudio]);
|
|
39407
|
+
const handlePauseSync = reactExports.useCallback(() => {
|
|
39408
|
+
setIsPaused(true);
|
|
39409
|
+
handleStopAudio();
|
|
39410
|
+
}, [handleStopAudio]);
|
|
39411
|
+
const handleResumeSync = reactExports.useCallback(() => {
|
|
39412
|
+
setIsPaused(false);
|
|
39413
|
+
const firstUnsyncedIndex = allWords.findIndex(
|
|
39414
|
+
(w) => w.start_time === null || w.end_time === null
|
|
39415
|
+
);
|
|
39416
|
+
if (firstUnsyncedIndex !== -1 && firstUnsyncedIndex !== syncWordIndex) {
|
|
39417
|
+
setSyncWordIndex(firstUnsyncedIndex);
|
|
39418
|
+
}
|
|
39419
|
+
if (onPlaySegment) {
|
|
39420
|
+
onPlaySegment(currentTimeRef.current);
|
|
39421
|
+
}
|
|
39422
|
+
}, [allWords, syncWordIndex, onPlaySegment]);
|
|
39423
|
+
const handleClearSync = reactExports.useCallback(() => {
|
|
39424
|
+
setWorkingSegments((prevSegments) => {
|
|
39425
|
+
const newSegments = cloneSegments(prevSegments);
|
|
39426
|
+
for (const segment of newSegments) {
|
|
39427
|
+
for (const word of segment.words) {
|
|
39428
|
+
word.start_time = null;
|
|
39429
|
+
word.end_time = null;
|
|
39430
|
+
}
|
|
39431
|
+
segment.start_time = null;
|
|
39432
|
+
segment.end_time = null;
|
|
39433
|
+
}
|
|
39434
|
+
return newSegments;
|
|
39435
|
+
});
|
|
39436
|
+
setSyncWordIndex(-1);
|
|
39437
|
+
}, []);
|
|
39438
|
+
const handleUnsyncFromCursor = reactExports.useCallback(() => {
|
|
39439
|
+
const cursorTime = currentTimeRef.current;
|
|
39440
|
+
setWorkingSegments((prevSegments) => {
|
|
39441
|
+
const newSegments = cloneSegments(prevSegments);
|
|
39442
|
+
for (const segment of newSegments) {
|
|
39443
|
+
for (const word of segment.words) {
|
|
39444
|
+
if (word.start_time !== null && word.start_time > cursorTime) {
|
|
39445
|
+
word.start_time = null;
|
|
39446
|
+
word.end_time = null;
|
|
39447
|
+
}
|
|
39448
|
+
}
|
|
39449
|
+
const timedWords = segment.words.filter(
|
|
39450
|
+
(w) => w.start_time !== null && w.end_time !== null
|
|
39451
|
+
);
|
|
39452
|
+
if (timedWords.length > 0) {
|
|
39453
|
+
segment.start_time = Math.min(...timedWords.map((w) => w.start_time));
|
|
39454
|
+
segment.end_time = Math.max(...timedWords.map((w) => w.end_time));
|
|
39455
|
+
} else {
|
|
39456
|
+
segment.start_time = null;
|
|
39457
|
+
segment.end_time = null;
|
|
39458
|
+
}
|
|
39459
|
+
}
|
|
39460
|
+
return newSegments;
|
|
39461
|
+
});
|
|
39462
|
+
}, []);
|
|
39463
|
+
const canUnsyncFromCursor = reactExports.useMemo(() => {
|
|
39464
|
+
const cursorTime = currentTimeRef.current;
|
|
39465
|
+
return allWords.some(
|
|
39466
|
+
(w) => w.start_time !== null && w.start_time > cursorTime
|
|
39467
|
+
);
|
|
39468
|
+
}, [allWords, currentTime]);
|
|
39469
|
+
const handleEditLyrics = reactExports.useCallback(() => {
|
|
39470
|
+
const text = workingSegments.map((s) => s.text).join("\n");
|
|
39471
|
+
setEditLyricsText(text);
|
|
39472
|
+
setShowEditLyricsModal(true);
|
|
39473
|
+
}, [workingSegments]);
|
|
39474
|
+
const handleSaveEditedLyrics = reactExports.useCallback(() => {
|
|
39475
|
+
const lines = editLyricsText.split("\n").filter((l) => l.trim());
|
|
39476
|
+
const newSegments = lines.map((line2, idx) => {
|
|
39477
|
+
const words = line2.trim().split(/\s+/).map((text, wIdx) => ({
|
|
39478
|
+
id: `word-${idx}-${wIdx}-${Date.now()}`,
|
|
39479
|
+
text,
|
|
39480
|
+
start_time: null,
|
|
39481
|
+
end_time: null,
|
|
39482
|
+
confidence: 1
|
|
39483
|
+
}));
|
|
39484
|
+
return {
|
|
39485
|
+
id: `segment-${idx}-${Date.now()}`,
|
|
39486
|
+
text: line2.trim(),
|
|
39487
|
+
words,
|
|
39488
|
+
start_time: null,
|
|
39489
|
+
end_time: null
|
|
39490
|
+
};
|
|
39491
|
+
});
|
|
39492
|
+
setWorkingSegments(newSegments);
|
|
39493
|
+
setShowEditLyricsModal(false);
|
|
39494
|
+
setSyncWordIndex(-1);
|
|
39495
|
+
}, [editLyricsText]);
|
|
39496
|
+
const handleEditSelectedWord = reactExports.useCallback(() => {
|
|
39497
|
+
if (selectedWordIds.size !== 1) return;
|
|
39498
|
+
const wordId = Array.from(selectedWordIds)[0];
|
|
39499
|
+
const word = allWords.find((w) => w.id === wordId);
|
|
39500
|
+
if (word) {
|
|
39501
|
+
setEditWordId(wordId);
|
|
39502
|
+
setEditWordText(word.text);
|
|
39503
|
+
setShowEditWordModal(true);
|
|
39504
|
+
}
|
|
39505
|
+
}, [selectedWordIds, allWords]);
|
|
39506
|
+
const handleSaveEditedWord = reactExports.useCallback(() => {
|
|
39507
|
+
if (!editWordId) return;
|
|
39508
|
+
const newText = editWordText.trim();
|
|
39509
|
+
if (!newText) return;
|
|
39510
|
+
const newWords = newText.split(/\s+/);
|
|
39511
|
+
if (newWords.length === 1) {
|
|
39512
|
+
const updatedWords = allWords.map(
|
|
39513
|
+
(w) => w.id === editWordId ? { ...w, text: newWords[0] } : w
|
|
39514
|
+
);
|
|
39515
|
+
updateWords(updatedWords);
|
|
39516
|
+
} else {
|
|
39517
|
+
const originalWord = allWords.find((w) => w.id === editWordId);
|
|
39518
|
+
if (!originalWord) return;
|
|
39519
|
+
setWorkingSegments((prevSegments) => {
|
|
39520
|
+
const newSegments = cloneSegments(prevSegments);
|
|
39521
|
+
for (const segment of newSegments) {
|
|
39522
|
+
const wordIndex = segment.words.findIndex((w) => w.id === editWordId);
|
|
39523
|
+
if (wordIndex !== -1) {
|
|
39524
|
+
const newWordObjects = newWords.map((text, idx) => ({
|
|
39525
|
+
id: idx === 0 ? editWordId : `${editWordId}-split-${idx}`,
|
|
39526
|
+
text,
|
|
39527
|
+
start_time: idx === 0 ? originalWord.start_time : null,
|
|
39528
|
+
end_time: idx === 0 ? originalWord.end_time : null,
|
|
39529
|
+
confidence: 1
|
|
39530
|
+
}));
|
|
39531
|
+
segment.words.splice(wordIndex, 1, ...newWordObjects);
|
|
39532
|
+
segment.text = segment.words.map((w) => w.text).join(" ");
|
|
39533
|
+
break;
|
|
39534
|
+
}
|
|
39535
|
+
}
|
|
39536
|
+
return newSegments;
|
|
39537
|
+
});
|
|
39538
|
+
}
|
|
39539
|
+
setShowEditWordModal(false);
|
|
39540
|
+
setEditWordId(null);
|
|
39541
|
+
setEditWordText("");
|
|
39542
|
+
setSelectedWordIds(/* @__PURE__ */ new Set());
|
|
39543
|
+
}, [editWordId, editWordText, allWords, updateWords]);
|
|
39544
|
+
const handleDeleteSelected = reactExports.useCallback(() => {
|
|
39545
|
+
if (selectedWordIds.size === 0) return;
|
|
39546
|
+
setWorkingSegments((prevSegments) => {
|
|
39547
|
+
const newSegments = cloneSegments(prevSegments);
|
|
39548
|
+
for (const segment of newSegments) {
|
|
39549
|
+
segment.words = segment.words.filter((w) => !selectedWordIds.has(w.id));
|
|
39550
|
+
segment.text = segment.words.map((w) => w.text).join(" ");
|
|
39551
|
+
const timedWords = segment.words.filter(
|
|
39552
|
+
(w) => w.start_time !== null && w.end_time !== null
|
|
39553
|
+
);
|
|
39554
|
+
if (timedWords.length > 0) {
|
|
39555
|
+
segment.start_time = Math.min(...timedWords.map((w) => w.start_time));
|
|
39556
|
+
segment.end_time = Math.max(...timedWords.map((w) => w.end_time));
|
|
39557
|
+
} else {
|
|
39558
|
+
segment.start_time = null;
|
|
39559
|
+
segment.end_time = null;
|
|
39560
|
+
}
|
|
39561
|
+
}
|
|
39562
|
+
return newSegments.filter((s) => s.words.length > 0);
|
|
39563
|
+
});
|
|
39564
|
+
setSelectedWordIds(/* @__PURE__ */ new Set());
|
|
39565
|
+
}, [selectedWordIds]);
|
|
39566
|
+
const handleWordClick = reactExports.useCallback((wordId, event) => {
|
|
39567
|
+
if (event.shiftKey || event.ctrlKey || event.metaKey) {
|
|
39568
|
+
setSelectedWordIds((prev2) => {
|
|
39569
|
+
const newSet = new Set(prev2);
|
|
39570
|
+
if (newSet.has(wordId)) {
|
|
39571
|
+
newSet.delete(wordId);
|
|
39572
|
+
} else {
|
|
39573
|
+
newSet.add(wordId);
|
|
39574
|
+
}
|
|
39575
|
+
return newSet;
|
|
39576
|
+
});
|
|
39577
|
+
} else {
|
|
39578
|
+
setSelectedWordIds(/* @__PURE__ */ new Set([wordId]));
|
|
39579
|
+
}
|
|
39580
|
+
}, []);
|
|
39581
|
+
const handleBackgroundClick = reactExports.useCallback(() => {
|
|
39582
|
+
setSelectedWordIds(/* @__PURE__ */ new Set());
|
|
39583
|
+
}, []);
|
|
39584
|
+
const handleWordTimingChange = reactExports.useCallback((wordId, newStartTime, newEndTime) => {
|
|
39585
|
+
setWorkingSegments((prevSegments) => {
|
|
39586
|
+
const newSegments = cloneSegments(prevSegments);
|
|
39587
|
+
for (const segment of newSegments) {
|
|
39588
|
+
const word = segment.words.find((w) => w.id === wordId);
|
|
39589
|
+
if (word) {
|
|
39590
|
+
word.start_time = Math.max(0, newStartTime);
|
|
39591
|
+
word.end_time = Math.max(word.start_time + 0.05, newEndTime);
|
|
39592
|
+
const timedWords = segment.words.filter(
|
|
39593
|
+
(w) => w.start_time !== null && w.end_time !== null
|
|
39594
|
+
);
|
|
39595
|
+
if (timedWords.length > 0) {
|
|
39596
|
+
segment.start_time = Math.min(...timedWords.map((w) => w.start_time));
|
|
39597
|
+
segment.end_time = Math.max(...timedWords.map((w) => w.end_time));
|
|
39598
|
+
}
|
|
39599
|
+
break;
|
|
39600
|
+
}
|
|
39601
|
+
}
|
|
39602
|
+
return newSegments;
|
|
39603
|
+
});
|
|
39604
|
+
}, []);
|
|
39605
|
+
const handleWordsMove = reactExports.useCallback((updates) => {
|
|
39606
|
+
setWorkingSegments((prevSegments) => {
|
|
39607
|
+
const newSegments = cloneSegments(prevSegments);
|
|
39608
|
+
const updateMap = new Map(updates.map((u) => [u.wordId, u]));
|
|
39609
|
+
for (const segment of newSegments) {
|
|
39610
|
+
for (const word of segment.words) {
|
|
39611
|
+
const update = updateMap.get(word.id);
|
|
39612
|
+
if (update) {
|
|
39613
|
+
word.start_time = update.newStartTime;
|
|
39614
|
+
word.end_time = update.newEndTime;
|
|
39615
|
+
}
|
|
39616
|
+
}
|
|
39617
|
+
const timedWords = segment.words.filter(
|
|
39618
|
+
(w) => w.start_time !== null && w.end_time !== null
|
|
39619
|
+
);
|
|
39620
|
+
if (timedWords.length > 0) {
|
|
39621
|
+
segment.start_time = Math.min(...timedWords.map((w) => w.start_time));
|
|
39622
|
+
segment.end_time = Math.max(...timedWords.map((w) => w.end_time));
|
|
39623
|
+
}
|
|
39624
|
+
}
|
|
39625
|
+
return newSegments;
|
|
39626
|
+
});
|
|
39627
|
+
}, []);
|
|
39628
|
+
const handleTimeBarClick = reactExports.useCallback((time) => {
|
|
39629
|
+
const newStart = Math.max(0, time - zoomSeconds / 2);
|
|
39630
|
+
setVisibleStartTime(Math.min(newStart, Math.max(0, audioDuration - zoomSeconds)));
|
|
39631
|
+
if (onPlaySegment) {
|
|
39632
|
+
onPlaySegment(time);
|
|
39633
|
+
setTimeout(() => {
|
|
39634
|
+
if (typeof window.toggleAudioPlayback === "function" && window.isAudioPlaying) {
|
|
39635
|
+
window.toggleAudioPlayback();
|
|
39636
|
+
}
|
|
39637
|
+
}, 50);
|
|
39638
|
+
}
|
|
39639
|
+
}, [zoomSeconds, audioDuration, onPlaySegment]);
|
|
39640
|
+
const handleSelectionComplete = reactExports.useCallback((wordIds) => {
|
|
39641
|
+
setSelectedWordIds(new Set(wordIds));
|
|
39642
|
+
}, []);
|
|
39643
|
+
const handleKeyDown = reactExports.useCallback((e) => {
|
|
39644
|
+
if (e.code !== "Space") return;
|
|
39645
|
+
if (!isManualSyncing || isPaused) return;
|
|
39646
|
+
if (syncWordIndex < 0 || syncWordIndex >= allWords.length) return;
|
|
39647
|
+
e.preventDefault();
|
|
39648
|
+
e.stopPropagation();
|
|
39649
|
+
if (isSpacebarPressed) return;
|
|
39650
|
+
setIsSpacebarPressed(true);
|
|
39651
|
+
wordStartTimeRef.current = currentTimeRef.current;
|
|
39652
|
+
spacebarPressTimeRef.current = Date.now();
|
|
39653
|
+
const newWords = [...allWords];
|
|
39654
|
+
const currentWord = newWords[syncWordIndex];
|
|
39655
|
+
currentWord.start_time = currentTimeRef.current;
|
|
39656
|
+
if (syncWordIndex > 0) {
|
|
39657
|
+
const prevWord = newWords[syncWordIndex - 1];
|
|
39658
|
+
if (prevWord.start_time !== null && prevWord.end_time === null) {
|
|
39659
|
+
const gap2 = currentTimeRef.current - prevWord.start_time;
|
|
39660
|
+
if (gap2 > 1) {
|
|
39661
|
+
prevWord.end_time = prevWord.start_time + 0.5;
|
|
39662
|
+
} else {
|
|
39663
|
+
prevWord.end_time = currentTimeRef.current - 5e-3;
|
|
39664
|
+
}
|
|
39665
|
+
}
|
|
39666
|
+
}
|
|
39667
|
+
updateWords(newWords);
|
|
39668
|
+
}, [isManualSyncing, isPaused, syncWordIndex, allWords, isSpacebarPressed, updateWords]);
|
|
39669
|
+
const handleKeyUp = reactExports.useCallback((e) => {
|
|
39670
|
+
if (e.code !== "Space") return;
|
|
39671
|
+
if (!isManualSyncing || isPaused) return;
|
|
39672
|
+
if (!isSpacebarPressed) return;
|
|
39673
|
+
e.preventDefault();
|
|
39674
|
+
e.stopPropagation();
|
|
39675
|
+
setIsSpacebarPressed(false);
|
|
39676
|
+
const pressDuration = spacebarPressTimeRef.current ? Date.now() - spacebarPressTimeRef.current : 0;
|
|
39677
|
+
const isTap = pressDuration < 200;
|
|
39678
|
+
const newWords = [...allWords];
|
|
39679
|
+
const currentWord = newWords[syncWordIndex];
|
|
39680
|
+
if (isTap) {
|
|
39681
|
+
currentWord.end_time = (wordStartTimeRef.current || currentTimeRef.current) + 0.5;
|
|
39682
|
+
} else {
|
|
39683
|
+
currentWord.end_time = currentTimeRef.current;
|
|
39684
|
+
}
|
|
39685
|
+
updateWords(newWords);
|
|
39686
|
+
if (syncWordIndex < allWords.length - 1) {
|
|
39687
|
+
setSyncWordIndex(syncWordIndex + 1);
|
|
39688
|
+
} else {
|
|
39689
|
+
setIsManualSyncing(false);
|
|
39690
|
+
setSyncWordIndex(-1);
|
|
39691
|
+
handleStopAudio();
|
|
39692
|
+
}
|
|
39693
|
+
wordStartTimeRef.current = null;
|
|
39694
|
+
spacebarPressTimeRef.current = null;
|
|
39695
|
+
}, [isManualSyncing, isPaused, isSpacebarPressed, syncWordIndex, allWords, updateWords, handleStopAudio]);
|
|
39696
|
+
const handleSpacebar = reactExports.useCallback((e) => {
|
|
39697
|
+
if (e.type === "keydown") {
|
|
39698
|
+
handleKeyDown(e);
|
|
39699
|
+
} else if (e.type === "keyup") {
|
|
39700
|
+
handleKeyUp(e);
|
|
39701
|
+
}
|
|
39702
|
+
}, [handleKeyDown, handleKeyUp]);
|
|
39703
|
+
const spacebarHandlerRef = reactExports.useRef(handleSpacebar);
|
|
39704
|
+
spacebarHandlerRef.current = handleSpacebar;
|
|
39705
|
+
reactExports.useEffect(() => {
|
|
39706
|
+
const handler = (e) => {
|
|
39707
|
+
if (e.code === "Space") {
|
|
39708
|
+
e.preventDefault();
|
|
39709
|
+
e.stopPropagation();
|
|
39710
|
+
spacebarHandlerRef.current(e);
|
|
39711
|
+
}
|
|
39712
|
+
};
|
|
39713
|
+
setModalSpacebarHandler(() => handler);
|
|
39714
|
+
return () => {
|
|
39715
|
+
setModalSpacebarHandler(void 0);
|
|
39716
|
+
};
|
|
39717
|
+
}, [setModalSpacebarHandler]);
|
|
39718
|
+
const handleSave = reactExports.useCallback(() => {
|
|
39719
|
+
onSave(workingSegments);
|
|
39720
|
+
}, [workingSegments, onSave]);
|
|
39721
|
+
const stats = reactExports.useMemo(() => {
|
|
39722
|
+
const total = allWords.length;
|
|
39723
|
+
const synced = allWords.filter(
|
|
39724
|
+
(w) => w.start_time !== null && w.end_time !== null
|
|
39725
|
+
).length;
|
|
39726
|
+
return { total, synced, remaining: total - synced };
|
|
39727
|
+
}, [allWords]);
|
|
39728
|
+
const getInstructionText = reactExports.useCallback(() => {
|
|
39729
|
+
if (isManualSyncing) {
|
|
39730
|
+
if (isSpacebarPressed) {
|
|
39731
|
+
return { primary: "⏱️ Holding... release when word ends", secondary: "Release spacebar when the word finishes" };
|
|
39732
|
+
}
|
|
39733
|
+
if (stats.remaining === 0) {
|
|
39734
|
+
return { primary: "✅ All words synced!", secondary: 'Click "Stop Sync" then "Apply" to save' };
|
|
39735
|
+
}
|
|
39736
|
+
return { primary: "👆 Press SPACEBAR when you hear each word", secondary: "Tap for short words, hold for longer words" };
|
|
39737
|
+
}
|
|
39738
|
+
if (stats.synced === 0) {
|
|
39739
|
+
return { primary: 'Click "Start Sync" to begin timing words', secondary: "Audio will play and you'll tap spacebar for each word" };
|
|
39740
|
+
}
|
|
39741
|
+
if (stats.remaining > 0) {
|
|
39742
|
+
return { primary: `${stats.remaining} words remaining to sync`, secondary: 'Click "Start Sync" to continue, or "Unsync from Cursor" to re-sync from a point' };
|
|
39743
|
+
}
|
|
39744
|
+
return { primary: "✅ All words synced!", secondary: 'Click "Apply" to save changes, or make adjustments first' };
|
|
39745
|
+
}, [isManualSyncing, isSpacebarPressed, stats.synced, stats.remaining]);
|
|
39746
|
+
const instruction = getInstructionText();
|
|
39747
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", flexDirection: "column", height: "100%", gap: 1 }, children: [
|
|
39748
|
+
/* @__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: [
|
|
39749
|
+
stats.synced,
|
|
39750
|
+
" / ",
|
|
39751
|
+
stats.total,
|
|
39752
|
+
" words synced",
|
|
39753
|
+
stats.remaining > 0 && ` (${stats.remaining} remaining)`
|
|
39754
|
+
] }) }),
|
|
39755
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39756
|
+
Box,
|
|
39757
|
+
{
|
|
39758
|
+
sx: {
|
|
39759
|
+
height: 56,
|
|
39760
|
+
flexShrink: 0
|
|
38964
39761
|
},
|
|
38965
|
-
|
|
38966
|
-
|
|
38967
|
-
|
|
39762
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
39763
|
+
Paper,
|
|
39764
|
+
{
|
|
39765
|
+
sx: {
|
|
39766
|
+
p: 1.5,
|
|
39767
|
+
height: "100%",
|
|
39768
|
+
bgcolor: isManualSyncing ? "info.main" : "grey.100",
|
|
39769
|
+
color: isManualSyncing ? "info.contrastText" : "text.primary",
|
|
39770
|
+
display: "flex",
|
|
39771
|
+
flexDirection: "column",
|
|
39772
|
+
justifyContent: "center",
|
|
39773
|
+
overflow: "hidden",
|
|
39774
|
+
boxSizing: "border-box"
|
|
39775
|
+
},
|
|
39776
|
+
children: [
|
|
39777
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body2", sx: { fontWeight: 500, lineHeight: 1.3 }, children: instruction.primary }),
|
|
39778
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "caption", sx: { opacity: 0.85, display: "block", lineHeight: 1.3 }, children: instruction.secondary })
|
|
39779
|
+
]
|
|
39780
|
+
}
|
|
39781
|
+
)
|
|
39782
|
+
}
|
|
39783
|
+
),
|
|
39784
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { height: 88, flexShrink: 0 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39785
|
+
SyncControls,
|
|
39786
|
+
{
|
|
39787
|
+
isManualSyncing,
|
|
39788
|
+
isPaused,
|
|
39789
|
+
onStartSync: handleStartSync,
|
|
39790
|
+
onPauseSync: handlePauseSync,
|
|
39791
|
+
onResumeSync: handleResumeSync,
|
|
39792
|
+
onClearSync: handleClearSync,
|
|
39793
|
+
onEditLyrics: handleEditLyrics,
|
|
39794
|
+
onPlay: handlePlayAudio,
|
|
39795
|
+
onStop: handleStopAudio,
|
|
39796
|
+
isPlaying,
|
|
39797
|
+
hasSelectedWords: selectedWordIds.size > 0,
|
|
39798
|
+
selectedWordCount: selectedWordIds.size,
|
|
39799
|
+
onUnsyncFromCursor: handleUnsyncFromCursor,
|
|
39800
|
+
onEditSelectedWord: handleEditSelectedWord,
|
|
39801
|
+
onDeleteSelected: handleDeleteSelected,
|
|
39802
|
+
canUnsyncFromCursor
|
|
39803
|
+
}
|
|
39804
|
+
) }),
|
|
39805
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { height: 44, flexShrink: 0 }, children: isManualSyncing && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39806
|
+
UpcomingWordsBar,
|
|
39807
|
+
{
|
|
39808
|
+
words: allWords,
|
|
39809
|
+
syncWordIndex,
|
|
39810
|
+
isManualSyncing
|
|
39811
|
+
}
|
|
39812
|
+
) }),
|
|
39813
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { flexGrow: 1, minHeight: 200 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39814
|
+
TimelineCanvas,
|
|
39815
|
+
{
|
|
39816
|
+
words: allWords,
|
|
39817
|
+
segments: workingSegments,
|
|
39818
|
+
visibleStartTime,
|
|
39819
|
+
visibleEndTime,
|
|
39820
|
+
currentTime,
|
|
39821
|
+
selectedWordIds,
|
|
39822
|
+
onWordClick: handleWordClick,
|
|
39823
|
+
onBackgroundClick: handleBackgroundClick,
|
|
39824
|
+
onTimeBarClick: handleTimeBarClick,
|
|
39825
|
+
onSelectionComplete: handleSelectionComplete,
|
|
39826
|
+
onWordTimingChange: handleWordTimingChange,
|
|
39827
|
+
onWordsMove: handleWordsMove,
|
|
39828
|
+
syncWordIndex,
|
|
39829
|
+
isManualSyncing,
|
|
39830
|
+
onScrollChange: handleScrollChange,
|
|
39831
|
+
audioDuration,
|
|
39832
|
+
zoomSeconds,
|
|
39833
|
+
height: 200
|
|
39834
|
+
}
|
|
39835
|
+
) }),
|
|
39836
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 2, px: 2 }, children: [
|
|
39837
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(ZoomInIcon, { color: "action", fontSize: "small" }),
|
|
39838
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39839
|
+
Slider,
|
|
39840
|
+
{
|
|
39841
|
+
value: sliderValue,
|
|
39842
|
+
onChange: handleZoomChange,
|
|
39843
|
+
min: 0,
|
|
39844
|
+
max: ZOOM_STEPS,
|
|
39845
|
+
step: 1,
|
|
39846
|
+
sx: { flexGrow: 1 },
|
|
39847
|
+
disabled: isManualSyncing && !isPaused
|
|
39848
|
+
}
|
|
39849
|
+
),
|
|
39850
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOutIcon, { color: "action", fontSize: "small" }),
|
|
39851
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Typography, { variant: "caption", color: "text.secondary", sx: { minWidth: 60 }, children: [
|
|
39852
|
+
zoomSeconds.toFixed(1),
|
|
39853
|
+
"s view"
|
|
39854
|
+
] })
|
|
39855
|
+
] }),
|
|
39856
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", justifyContent: "flex-end", gap: 2, pt: 2, borderTop: 1, borderColor: "divider" }, children: [
|
|
39857
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: onCancel, color: "inherit", children: "Cancel" }),
|
|
39858
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39859
|
+
Button,
|
|
39860
|
+
{
|
|
39861
|
+
onClick: handleSave,
|
|
39862
|
+
variant: "contained",
|
|
39863
|
+
color: "primary",
|
|
39864
|
+
disabled: isManualSyncing && !isPaused,
|
|
39865
|
+
children: "Apply"
|
|
39866
|
+
}
|
|
39867
|
+
)
|
|
39868
|
+
] }),
|
|
39869
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
39870
|
+
Dialog,
|
|
39871
|
+
{
|
|
39872
|
+
open: showEditLyricsModal,
|
|
39873
|
+
onClose: () => setShowEditLyricsModal(false),
|
|
39874
|
+
maxWidth: "md",
|
|
39875
|
+
fullWidth: true,
|
|
39876
|
+
children: [
|
|
39877
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(DialogTitle, { children: "Edit Lyrics" }),
|
|
39878
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { children: [
|
|
39879
|
+
/* @__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." }),
|
|
39880
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39881
|
+
TextField,
|
|
39882
|
+
{
|
|
39883
|
+
multiline: true,
|
|
39884
|
+
rows: 15,
|
|
39885
|
+
fullWidth: true,
|
|
39886
|
+
value: editLyricsText,
|
|
39887
|
+
onChange: (e) => setEditLyricsText(e.target.value),
|
|
39888
|
+
placeholder: "Enter lyrics, one line per segment..."
|
|
39889
|
+
}
|
|
39890
|
+
)
|
|
39891
|
+
] }),
|
|
39892
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogActions, { children: [
|
|
39893
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: () => setShowEditLyricsModal(false), children: "Cancel" }),
|
|
39894
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: handleSaveEditedLyrics, variant: "contained", color: "warning", children: "Save & Reset Timing" })
|
|
39895
|
+
] })
|
|
39896
|
+
]
|
|
39897
|
+
}
|
|
39898
|
+
),
|
|
39899
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
39900
|
+
Dialog,
|
|
39901
|
+
{
|
|
39902
|
+
open: showEditWordModal,
|
|
39903
|
+
onClose: () => setShowEditWordModal(false),
|
|
39904
|
+
maxWidth: "xs",
|
|
39905
|
+
fullWidth: true,
|
|
39906
|
+
children: [
|
|
39907
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(DialogTitle, { children: "Edit Word" }),
|
|
39908
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogContent, { children: [
|
|
39909
|
+
/* @__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." }),
|
|
39910
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39911
|
+
TextField,
|
|
39912
|
+
{
|
|
39913
|
+
fullWidth: true,
|
|
39914
|
+
value: editWordText,
|
|
39915
|
+
onChange: (e) => setEditWordText(e.target.value),
|
|
39916
|
+
autoFocus: true,
|
|
39917
|
+
onKeyDown: (e) => {
|
|
39918
|
+
if (e.key === "Enter") {
|
|
39919
|
+
handleSaveEditedWord();
|
|
39920
|
+
}
|
|
39921
|
+
}
|
|
39922
|
+
}
|
|
39923
|
+
)
|
|
39924
|
+
] }),
|
|
39925
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogActions, { children: [
|
|
39926
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: () => setShowEditWordModal(false), children: "Cancel" }),
|
|
39927
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: handleSaveEditedWord, variant: "contained", children: "Save" })
|
|
39928
|
+
] })
|
|
39929
|
+
]
|
|
39930
|
+
}
|
|
39931
|
+
)
|
|
38968
39932
|
] });
|
|
38969
39933
|
});
|
|
39934
|
+
function ReplaceAllLyricsModal({
|
|
39935
|
+
open,
|
|
39936
|
+
onClose,
|
|
39937
|
+
onSave,
|
|
39938
|
+
onPlaySegment,
|
|
39939
|
+
currentTime = 0,
|
|
39940
|
+
setModalSpacebarHandler,
|
|
39941
|
+
existingSegments = []
|
|
39942
|
+
}) {
|
|
39943
|
+
const [mode, setMode] = reactExports.useState("selection");
|
|
39944
|
+
const [inputText, setInputText] = reactExports.useState("");
|
|
39945
|
+
const [newSegments, setNewSegments] = reactExports.useState([]);
|
|
39946
|
+
reactExports.useEffect(() => {
|
|
39947
|
+
if (open) {
|
|
39948
|
+
setMode("selection");
|
|
39949
|
+
setInputText("");
|
|
39950
|
+
setNewSegments([]);
|
|
39951
|
+
}
|
|
39952
|
+
}, [open]);
|
|
39953
|
+
const parseInfo = reactExports.useMemo(() => {
|
|
39954
|
+
if (!inputText.trim()) return { lines: 0, words: 0 };
|
|
39955
|
+
const lines = inputText.trim().split("\n").filter((line2) => line2.trim().length > 0);
|
|
39956
|
+
const totalWords = lines.reduce((count, line2) => {
|
|
39957
|
+
return count + line2.trim().split(/\s+/).length;
|
|
39958
|
+
}, 0);
|
|
39959
|
+
return { lines: lines.length, words: totalWords };
|
|
39960
|
+
}, [inputText]);
|
|
39961
|
+
const processLyrics = reactExports.useCallback(() => {
|
|
39962
|
+
if (!inputText.trim()) return;
|
|
39963
|
+
const lines = inputText.trim().split("\n").filter((line2) => line2.trim().length > 0);
|
|
39964
|
+
const segments = [];
|
|
39965
|
+
lines.forEach((line2) => {
|
|
39966
|
+
const words = line2.trim().split(/\s+/).filter((word) => word.length > 0);
|
|
39967
|
+
const segmentWords = words.map((wordText) => ({
|
|
39968
|
+
id: nanoid(),
|
|
39969
|
+
text: wordText,
|
|
39970
|
+
start_time: null,
|
|
39971
|
+
end_time: null,
|
|
39972
|
+
confidence: 1,
|
|
39973
|
+
created_during_correction: true
|
|
39974
|
+
}));
|
|
39975
|
+
segments.push({
|
|
39976
|
+
id: nanoid(),
|
|
39977
|
+
text: line2.trim(),
|
|
39978
|
+
words: segmentWords,
|
|
39979
|
+
start_time: null,
|
|
39980
|
+
end_time: null
|
|
39981
|
+
});
|
|
39982
|
+
});
|
|
39983
|
+
setNewSegments(segments);
|
|
39984
|
+
setMode("resync");
|
|
39985
|
+
}, [inputText]);
|
|
39986
|
+
const handlePasteFromClipboard = reactExports.useCallback(async () => {
|
|
39987
|
+
try {
|
|
39988
|
+
const text = await navigator.clipboard.readText();
|
|
39989
|
+
setInputText(text);
|
|
39990
|
+
} catch (error) {
|
|
39991
|
+
console.error("Failed to read from clipboard:", error);
|
|
39992
|
+
alert("Failed to read from clipboard. Please paste manually.");
|
|
39993
|
+
}
|
|
39994
|
+
}, []);
|
|
39995
|
+
const handleClose = reactExports.useCallback(() => {
|
|
39996
|
+
setMode("selection");
|
|
39997
|
+
setInputText("");
|
|
39998
|
+
setNewSegments([]);
|
|
39999
|
+
onClose();
|
|
40000
|
+
}, [onClose]);
|
|
40001
|
+
const handleSave = reactExports.useCallback((segments) => {
|
|
40002
|
+
onSave(segments);
|
|
40003
|
+
handleClose();
|
|
40004
|
+
}, [onSave, handleClose]);
|
|
40005
|
+
const handleSelectReplace = reactExports.useCallback(() => {
|
|
40006
|
+
setMode("replace");
|
|
40007
|
+
}, []);
|
|
40008
|
+
const handleSelectResync = reactExports.useCallback(() => {
|
|
40009
|
+
setMode("resync");
|
|
40010
|
+
}, []);
|
|
40011
|
+
const handleBackToSelection = reactExports.useCallback(() => {
|
|
40012
|
+
setMode("selection");
|
|
40013
|
+
setInputText("");
|
|
40014
|
+
setNewSegments([]);
|
|
40015
|
+
}, []);
|
|
40016
|
+
const segmentsForSync = mode === "resync" && newSegments.length > 0 ? newSegments : existingSegments;
|
|
40017
|
+
const hasExistingLyrics = existingSegments.length > 0 && existingSegments.some((s) => s.words.length > 0);
|
|
40018
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
40019
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
40020
|
+
ModeSelectionModal,
|
|
40021
|
+
{
|
|
40022
|
+
open: open && mode === "selection",
|
|
40023
|
+
onClose: handleClose,
|
|
40024
|
+
onSelectReplace: handleSelectReplace,
|
|
40025
|
+
onSelectResync: handleSelectResync,
|
|
40026
|
+
hasExistingLyrics
|
|
40027
|
+
}
|
|
40028
|
+
),
|
|
40029
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
40030
|
+
Dialog,
|
|
40031
|
+
{
|
|
40032
|
+
open: open && mode === "replace",
|
|
40033
|
+
onClose: handleClose,
|
|
40034
|
+
maxWidth: "md",
|
|
40035
|
+
fullWidth: true,
|
|
40036
|
+
PaperProps: {
|
|
40037
|
+
sx: {
|
|
40038
|
+
height: "80vh",
|
|
40039
|
+
maxHeight: "80vh"
|
|
40040
|
+
}
|
|
40041
|
+
},
|
|
40042
|
+
children: [
|
|
40043
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogTitle, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
|
|
40044
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(IconButton, { onClick: handleBackToSelection, size: "small", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowBackIcon, {}) }),
|
|
40045
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { flex: 1 }, children: "Replace All Lyrics" }),
|
|
40046
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(IconButton, { onClick: handleClose, sx: { ml: "auto" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, {}) })
|
|
40047
|
+
] }),
|
|
40048
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
40049
|
+
DialogContent,
|
|
40050
|
+
{
|
|
40051
|
+
dividers: true,
|
|
40052
|
+
sx: {
|
|
40053
|
+
display: "flex",
|
|
40054
|
+
flexDirection: "column",
|
|
40055
|
+
flexGrow: 1,
|
|
40056
|
+
overflow: "hidden"
|
|
40057
|
+
},
|
|
40058
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 2, height: "100%" }, children: [
|
|
40059
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", gutterBottom: true, children: "Paste your new lyrics below:" }),
|
|
40060
|
+
/* @__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." }),
|
|
40061
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", gap: 2, mb: 2 }, children: [
|
|
40062
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
40063
|
+
Button,
|
|
40064
|
+
{
|
|
40065
|
+
variant: "outlined",
|
|
40066
|
+
onClick: handlePasteFromClipboard,
|
|
40067
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(ContentPasteIcon, {}),
|
|
40068
|
+
size: "small",
|
|
40069
|
+
children: "Paste from Clipboard"
|
|
40070
|
+
}
|
|
40071
|
+
),
|
|
40072
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Typography, { variant: "body2", sx: {
|
|
40073
|
+
alignSelf: "center",
|
|
40074
|
+
color: "text.secondary",
|
|
40075
|
+
fontWeight: "medium"
|
|
40076
|
+
}, children: [
|
|
40077
|
+
parseInfo.lines,
|
|
40078
|
+
" lines, ",
|
|
40079
|
+
parseInfo.words,
|
|
40080
|
+
" words"
|
|
40081
|
+
] })
|
|
40082
|
+
] }),
|
|
40083
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
40084
|
+
TextField,
|
|
40085
|
+
{
|
|
40086
|
+
multiline: true,
|
|
40087
|
+
rows: 15,
|
|
40088
|
+
value: inputText,
|
|
40089
|
+
onChange: (e) => setInputText(e.target.value),
|
|
40090
|
+
placeholder: "Paste your lyrics here...\nEach line will become a segment\nWords will be separated by spaces",
|
|
40091
|
+
sx: {
|
|
40092
|
+
flexGrow: 1,
|
|
40093
|
+
"& .MuiInputBase-root": {
|
|
40094
|
+
height: "100%",
|
|
40095
|
+
alignItems: "flex-start"
|
|
40096
|
+
}
|
|
40097
|
+
}
|
|
40098
|
+
}
|
|
40099
|
+
)
|
|
40100
|
+
] })
|
|
40101
|
+
}
|
|
40102
|
+
),
|
|
40103
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogActions, { children: [
|
|
40104
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { onClick: handleClose, color: "inherit", children: "Cancel" }),
|
|
40105
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
40106
|
+
Button,
|
|
40107
|
+
{
|
|
40108
|
+
variant: "contained",
|
|
40109
|
+
onClick: processLyrics,
|
|
40110
|
+
disabled: !inputText.trim(),
|
|
40111
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(AutoFixHighIcon, {}),
|
|
40112
|
+
children: "Continue to Sync"
|
|
40113
|
+
}
|
|
40114
|
+
)
|
|
40115
|
+
] })
|
|
40116
|
+
]
|
|
40117
|
+
}
|
|
40118
|
+
),
|
|
40119
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
40120
|
+
Dialog,
|
|
40121
|
+
{
|
|
40122
|
+
open: open && mode === "resync",
|
|
40123
|
+
onClose: handleClose,
|
|
40124
|
+
maxWidth: false,
|
|
40125
|
+
fullWidth: true,
|
|
40126
|
+
PaperProps: {
|
|
40127
|
+
sx: {
|
|
40128
|
+
height: "90vh",
|
|
40129
|
+
margin: "5vh 2vw",
|
|
40130
|
+
maxWidth: "calc(100vw - 4vw)",
|
|
40131
|
+
width: "calc(100vw - 4vw)"
|
|
40132
|
+
}
|
|
40133
|
+
},
|
|
40134
|
+
children: [
|
|
40135
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(DialogTitle, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
|
|
40136
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(IconButton, { onClick: handleBackToSelection, size: "small", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowBackIcon, {}) }),
|
|
40137
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { flex: 1 }, children: newSegments.length > 0 ? "Sync New Lyrics" : "Re-sync Existing Lyrics" }),
|
|
40138
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(IconButton, { onClick: handleClose, sx: { ml: "auto" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, {}) })
|
|
40139
|
+
] }),
|
|
40140
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
40141
|
+
DialogContent,
|
|
40142
|
+
{
|
|
40143
|
+
dividers: true,
|
|
40144
|
+
sx: {
|
|
40145
|
+
display: "flex",
|
|
40146
|
+
flexDirection: "column",
|
|
40147
|
+
flexGrow: 1,
|
|
40148
|
+
overflow: "hidden",
|
|
40149
|
+
p: 2
|
|
40150
|
+
},
|
|
40151
|
+
children: segmentsForSync.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
40152
|
+
LyricsSynchronizer,
|
|
40153
|
+
{
|
|
40154
|
+
segments: segmentsForSync,
|
|
40155
|
+
currentTime,
|
|
40156
|
+
onPlaySegment,
|
|
40157
|
+
onSave: handleSave,
|
|
40158
|
+
onCancel: handleClose,
|
|
40159
|
+
setModalSpacebarHandler
|
|
40160
|
+
}
|
|
40161
|
+
) : /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: {
|
|
40162
|
+
display: "flex",
|
|
40163
|
+
flexDirection: "column",
|
|
40164
|
+
alignItems: "center",
|
|
40165
|
+
justifyContent: "center",
|
|
40166
|
+
height: "100%",
|
|
40167
|
+
gap: 2
|
|
40168
|
+
}, children: [
|
|
40169
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", color: "text.secondary", children: "No lyrics to sync" }),
|
|
40170
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body2", color: "text.secondary", children: "Go back and paste new lyrics, or close this modal." }),
|
|
40171
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
40172
|
+
Button,
|
|
40173
|
+
{
|
|
40174
|
+
variant: "outlined",
|
|
40175
|
+
onClick: handleBackToSelection,
|
|
40176
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowBackIcon, {}),
|
|
40177
|
+
children: "Back to Selection"
|
|
40178
|
+
}
|
|
40179
|
+
)
|
|
40180
|
+
] })
|
|
40181
|
+
}
|
|
40182
|
+
)
|
|
40183
|
+
]
|
|
40184
|
+
}
|
|
40185
|
+
)
|
|
40186
|
+
] });
|
|
40187
|
+
}
|
|
38970
40188
|
const ANNOTATION_TYPES = [
|
|
38971
40189
|
{
|
|
38972
40190
|
value: "SOUND_ALIKE",
|
|
@@ -39866,7 +41084,7 @@ function AudioPlayer({ apiClient, onTimeUpdate, audioHash }) {
|
|
|
39866
41084
|
audioRef.current.currentTime = time;
|
|
39867
41085
|
setCurrentTime(time);
|
|
39868
41086
|
};
|
|
39869
|
-
const
|
|
41087
|
+
const formatTime2 = (seconds) => {
|
|
39870
41088
|
const mins = Math.floor(seconds / 60);
|
|
39871
41089
|
const secs = Math.floor(seconds % 60);
|
|
39872
41090
|
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
@@ -39918,7 +41136,7 @@ function AudioPlayer({ apiClient, onTimeUpdate, audioHash }) {
|
|
|
39918
41136
|
children: isPlaying ? /* @__PURE__ */ jsxRuntimeExports.jsx(PauseIcon, { fontSize: "small" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(PlayArrowIcon, { fontSize: "small" })
|
|
39919
41137
|
}
|
|
39920
41138
|
),
|
|
39921
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body2", sx: { minWidth: 32, fontSize: "0.75rem" }, children:
|
|
41139
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body2", sx: { minWidth: 32, fontSize: "0.75rem" }, children: formatTime2(currentTime) }),
|
|
39922
41140
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
39923
41141
|
Slider,
|
|
39924
41142
|
{
|
|
@@ -39940,7 +41158,7 @@ function AudioPlayer({ apiClient, onTimeUpdate, audioHash }) {
|
|
|
39940
41158
|
}
|
|
39941
41159
|
}
|
|
39942
41160
|
),
|
|
39943
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body2", sx: { minWidth: 32, fontSize: "0.75rem" }, children:
|
|
41161
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "body2", sx: { minWidth: 32, fontSize: "0.75rem" }, children: formatTime2(duration2) })
|
|
39944
41162
|
] });
|
|
39945
41163
|
}
|
|
39946
41164
|
function Header({
|
|
@@ -39964,7 +41182,9 @@ function Header({
|
|
|
39964
41182
|
onUndo,
|
|
39965
41183
|
onRedo,
|
|
39966
41184
|
canUndo,
|
|
39967
|
-
canRedo
|
|
41185
|
+
canRedo,
|
|
41186
|
+
annotationsEnabled = true,
|
|
41187
|
+
onAnnotationsToggle
|
|
39968
41188
|
}) {
|
|
39969
41189
|
var _a, _b, _c;
|
|
39970
41190
|
const theme2 = useTheme();
|
|
@@ -40011,17 +41231,34 @@ function Header({
|
|
|
40011
41231
|
mb: 1
|
|
40012
41232
|
}, children: [
|
|
40013
41233
|
/* @__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
|
-
|
|
41234
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
|
|
41235
|
+
!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(
|
|
41236
|
+
Chip,
|
|
41237
|
+
{
|
|
41238
|
+
icon: /* @__PURE__ */ jsxRuntimeExports.jsx(RateReviewIcon, {}),
|
|
41239
|
+
label: annotationsEnabled ? "Feedback On" : "Feedback Off",
|
|
41240
|
+
onClick: () => onAnnotationsToggle(!annotationsEnabled),
|
|
41241
|
+
color: annotationsEnabled ? "primary" : "default",
|
|
41242
|
+
variant: annotationsEnabled ? "filled" : "outlined",
|
|
41243
|
+
size: "small",
|
|
41244
|
+
sx: {
|
|
41245
|
+
cursor: "pointer",
|
|
41246
|
+
"& .MuiChip-icon": { fontSize: "1rem" }
|
|
41247
|
+
}
|
|
41248
|
+
}
|
|
41249
|
+
) }),
|
|
41250
|
+
isReadOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
41251
|
+
Button,
|
|
41252
|
+
{
|
|
41253
|
+
variant: "outlined",
|
|
41254
|
+
size: "small",
|
|
41255
|
+
startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(UploadFileIcon, {}),
|
|
41256
|
+
onClick: onFileLoad,
|
|
41257
|
+
fullWidth: isMobile,
|
|
41258
|
+
children: "Load File"
|
|
41259
|
+
}
|
|
41260
|
+
)
|
|
41261
|
+
] })
|
|
40025
41262
|
] }),
|
|
40026
41263
|
/* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: {
|
|
40027
41264
|
display: "flex",
|
|
@@ -40859,7 +42096,9 @@ const MemoizedHeader = reactExports.memo(function MemoizedHeader2({
|
|
|
40859
42096
|
onRedo,
|
|
40860
42097
|
canUndo,
|
|
40861
42098
|
canRedo,
|
|
40862
|
-
onUnCorrectAll
|
|
42099
|
+
onUnCorrectAll,
|
|
42100
|
+
annotationsEnabled,
|
|
42101
|
+
onAnnotationsToggle
|
|
40863
42102
|
}) {
|
|
40864
42103
|
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
40865
42104
|
Header,
|
|
@@ -40884,7 +42123,9 @@ const MemoizedHeader = reactExports.memo(function MemoizedHeader2({
|
|
|
40884
42123
|
onRedo,
|
|
40885
42124
|
canUndo,
|
|
40886
42125
|
canRedo,
|
|
40887
|
-
onUnCorrectAll
|
|
42126
|
+
onUnCorrectAll,
|
|
42127
|
+
annotationsEnabled,
|
|
42128
|
+
onAnnotationsToggle
|
|
40888
42129
|
}
|
|
40889
42130
|
);
|
|
40890
42131
|
});
|
|
@@ -40920,10 +42161,14 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
|
|
|
40920
42161
|
const [annotations, setAnnotations] = reactExports.useState([]);
|
|
40921
42162
|
const [isAnnotationModalOpen, setIsAnnotationModalOpen] = reactExports.useState(false);
|
|
40922
42163
|
const [pendingAnnotation, setPendingAnnotation] = reactExports.useState(null);
|
|
40923
|
-
const [annotationsEnabled] = reactExports.useState(() => {
|
|
42164
|
+
const [annotationsEnabled, setAnnotationsEnabled] = reactExports.useState(() => {
|
|
40924
42165
|
const saved = localStorage.getItem("annotationsEnabled");
|
|
40925
42166
|
return saved !== null ? saved === "true" : true;
|
|
40926
42167
|
});
|
|
42168
|
+
const handleAnnotationsToggle = reactExports.useCallback((enabled) => {
|
|
42169
|
+
setAnnotationsEnabled(enabled);
|
|
42170
|
+
localStorage.setItem("annotationsEnabled", String(enabled));
|
|
42171
|
+
}, []);
|
|
40927
42172
|
const [correctionDetailOpen, setCorrectionDetailOpen] = reactExports.useState(false);
|
|
40928
42173
|
const [selectedCorrection, setSelectedCorrection] = reactExports.useState(null);
|
|
40929
42174
|
const theme2 = useTheme();
|
|
@@ -41473,7 +42718,9 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
|
|
|
41473
42718
|
onRedo: handleRedo,
|
|
41474
42719
|
canUndo,
|
|
41475
42720
|
canRedo,
|
|
41476
|
-
onUnCorrectAll: handleUnCorrectAll
|
|
42721
|
+
onUnCorrectAll: handleUnCorrectAll,
|
|
42722
|
+
annotationsEnabled,
|
|
42723
|
+
onAnnotationsToggle: handleAnnotationsToggle
|
|
41477
42724
|
}
|
|
41478
42725
|
),
|
|
41479
42726
|
/* @__PURE__ */ jsxRuntimeExports.jsxs(Grid, { container: true, direction: isMobile ? "column" : "row", children: [
|
|
@@ -41620,7 +42867,8 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
|
|
|
41620
42867
|
onSave: handleSaveReplaceAllLyrics,
|
|
41621
42868
|
onPlaySegment: handlePlaySegment,
|
|
41622
42869
|
currentTime: currentAudioTime,
|
|
41623
|
-
setModalSpacebarHandler: handleSetModalSpacebarHandler
|
|
42870
|
+
setModalSpacebarHandler: handleSetModalSpacebarHandler,
|
|
42871
|
+
existingSegments: data.corrected_segments
|
|
41624
42872
|
}
|
|
41625
42873
|
),
|
|
41626
42874
|
pendingAnnotation && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -41690,22 +42938,23 @@ function App() {
|
|
|
41690
42938
|
const params = new URLSearchParams(window.location.search);
|
|
41691
42939
|
const encodedApiUrl = params.get("baseApiUrl");
|
|
41692
42940
|
const audioHashParam = params.get("audioHash");
|
|
42941
|
+
const reviewTokenParam = params.get("reviewToken");
|
|
41693
42942
|
if (encodedApiUrl) {
|
|
41694
42943
|
const baseApiUrl = decodeURIComponent(encodedApiUrl);
|
|
41695
|
-
setApiClient(new LiveApiClient(baseApiUrl));
|
|
42944
|
+
setApiClient(new LiveApiClient(baseApiUrl, reviewTokenParam || void 0));
|
|
41696
42945
|
setIsReadOnly(false);
|
|
41697
42946
|
if (audioHashParam) {
|
|
41698
42947
|
setAudioHash(audioHashParam);
|
|
41699
42948
|
}
|
|
41700
|
-
fetchData(baseApiUrl);
|
|
42949
|
+
fetchData(baseApiUrl, reviewTokenParam || void 0);
|
|
41701
42950
|
} else {
|
|
41702
42951
|
setApiClient(new FileOnlyClient());
|
|
41703
42952
|
setIsReadOnly(true);
|
|
41704
42953
|
}
|
|
41705
42954
|
}, []);
|
|
41706
|
-
const fetchData = async (baseUrl) => {
|
|
42955
|
+
const fetchData = async (baseUrl, reviewToken) => {
|
|
41707
42956
|
try {
|
|
41708
|
-
const client2 = new LiveApiClient(baseUrl);
|
|
42957
|
+
const client2 = new LiveApiClient(baseUrl, reviewToken);
|
|
41709
42958
|
const data2 = await client2.getCorrectionData();
|
|
41710
42959
|
setData(data2);
|
|
41711
42960
|
} catch (err) {
|
|
@@ -42025,7 +43274,7 @@ const theme = createTheme({
|
|
|
42025
43274
|
spacing: (factor) => `${0.6 * factor}rem`
|
|
42026
43275
|
// Further reduced from 0.8 * factor
|
|
42027
43276
|
});
|
|
42028
|
-
const version = "0.
|
|
43277
|
+
const version = "0.83.0";
|
|
42029
43278
|
const packageJson = {
|
|
42030
43279
|
version
|
|
42031
43280
|
};
|
|
@@ -42036,4 +43285,4 @@ ReactDOM$1.createRoot(document.getElementById("root")).render(
|
|
|
42036
43285
|
/* @__PURE__ */ jsxRuntimeExports.jsx(App, {})
|
|
42037
43286
|
] })
|
|
42038
43287
|
);
|
|
42039
|
-
//# sourceMappingURL=index-
|
|
43288
|
+
//# sourceMappingURL=index-BECn1o8Q.js.map
|