lyrics-transcriber 0.68.0__py3-none-any.whl → 0.69.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33824,9 +33824,31 @@ function ReferenceView({
33824
33824
  const copyToClipboard = (text) => {
33825
33825
  navigator.clipboard.writeText(text);
33826
33826
  };
33827
+ const copyAllReferenceText = () => {
33828
+ const allText = currentSourceSegments.map((segment) => segment.words.map((w) => w.text).join(" ")).join("\n");
33829
+ copyToClipboard(allText);
33830
+ };
33827
33831
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Paper, { sx: { p: 0.8, position: "relative" }, children: [
33828
33832
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 0.5 }, children: [
33829
- /* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", sx: { fontSize: "0.9rem", mb: 0 }, children: "Reference Lyrics" }),
33833
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
33834
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", sx: { fontSize: "0.9rem", mb: 0 }, children: "Reference Lyrics" }),
33835
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
33836
+ IconButton,
33837
+ {
33838
+ size: "small",
33839
+ onClick: copyAllReferenceText,
33840
+ sx: {
33841
+ padding: "2px",
33842
+ height: "20px",
33843
+ width: "20px",
33844
+ minHeight: "20px",
33845
+ minWidth: "20px"
33846
+ },
33847
+ title: "Copy all reference lyrics",
33848
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(ContentCopyIcon, { sx: { fontSize: "1rem" } })
33849
+ }
33850
+ )
33851
+ ] }),
33830
33852
  /* @__PURE__ */ jsxRuntimeExports.jsx(
33831
33853
  SourceSelector,
33832
33854
  {
@@ -34357,7 +34379,7 @@ const StopIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
34357
34379
  d: "M6 6h12v12H6z"
34358
34380
  }), "Stop");
34359
34381
  const TAP_THRESHOLD_MS = 200;
34360
- const DEFAULT_WORD_DURATION = 1;
34382
+ const DEFAULT_WORD_DURATION = 0.5;
34361
34383
  const OVERLAP_BUFFER = 0.01;
34362
34384
  function useManualSync({
34363
34385
  editedSegment,
@@ -34366,12 +34388,14 @@ function useManualSync({
34366
34388
  updateSegment: updateSegment2
34367
34389
  }) {
34368
34390
  const [isManualSyncing, setIsManualSyncing] = reactExports.useState(false);
34391
+ const [isPaused, setIsPaused] = reactExports.useState(false);
34369
34392
  const [syncWordIndex, setSyncWordIndex] = reactExports.useState(-1);
34370
34393
  const currentTimeRef = reactExports.useRef(currentTime);
34371
34394
  const [isSpacebarPressed, setIsSpacebarPressed] = reactExports.useState(false);
34372
34395
  const wordStartTimeRef = reactExports.useRef(null);
34373
34396
  const wordsRef = reactExports.useRef([]);
34374
34397
  const spacebarPressTimeRef = reactExports.useRef(null);
34398
+ const needsSegmentUpdateRef = reactExports.useRef(false);
34375
34399
  reactExports.useEffect(() => {
34376
34400
  currentTimeRef.current = currentTime;
34377
34401
  }, [currentTime]);
@@ -34380,13 +34404,58 @@ function useManualSync({
34380
34404
  wordsRef.current = [...editedSegment.words];
34381
34405
  }
34382
34406
  }, [editedSegment]);
34407
+ reactExports.useEffect(() => {
34408
+ if (needsSegmentUpdateRef.current) {
34409
+ needsSegmentUpdateRef.current = false;
34410
+ updateSegment2(wordsRef.current);
34411
+ }
34412
+ }, [updateSegment2, syncWordIndex]);
34383
34413
  const cleanupManualSync = reactExports.useCallback(() => {
34384
34414
  setIsManualSyncing(false);
34415
+ setIsPaused(false);
34385
34416
  setSyncWordIndex(-1);
34386
34417
  setIsSpacebarPressed(false);
34387
34418
  wordStartTimeRef.current = null;
34388
34419
  spacebarPressTimeRef.current = null;
34420
+ needsSegmentUpdateRef.current = false;
34421
+ if (window.toggleAudioPlayback && window.isAudioPlaying) {
34422
+ window.toggleAudioPlayback();
34423
+ }
34389
34424
  }, []);
34425
+ const pauseManualSync = reactExports.useCallback(() => {
34426
+ if (isManualSyncing && !isPaused) {
34427
+ console.log("useManualSync - Pausing manual sync");
34428
+ setIsPaused(true);
34429
+ if (window.toggleAudioPlayback && window.isAudioPlaying) {
34430
+ window.toggleAudioPlayback();
34431
+ }
34432
+ }
34433
+ }, [isManualSyncing, isPaused]);
34434
+ const resumeManualSync = reactExports.useCallback(() => {
34435
+ var _a;
34436
+ if (isManualSyncing && isPaused) {
34437
+ console.log("useManualSync - Resuming manual sync");
34438
+ setIsPaused(false);
34439
+ if (editedSegment) {
34440
+ const firstUnsyncedIndex = editedSegment.words.findIndex(
34441
+ (word) => word.start_time === null || word.end_time === null
34442
+ );
34443
+ if (firstUnsyncedIndex !== -1 && firstUnsyncedIndex !== syncWordIndex) {
34444
+ console.log("useManualSync - Resuming from first unsynced word", {
34445
+ previousIndex: syncWordIndex,
34446
+ newIndex: firstUnsyncedIndex,
34447
+ wordText: (_a = editedSegment.words[firstUnsyncedIndex]) == null ? void 0 : _a.text
34448
+ });
34449
+ setSyncWordIndex(firstUnsyncedIndex);
34450
+ } else {
34451
+ console.log("useManualSync - Resuming from current position", { syncWordIndex });
34452
+ }
34453
+ }
34454
+ if (onPlaySegment && currentTimeRef.current !== void 0) {
34455
+ onPlaySegment(currentTimeRef.current);
34456
+ }
34457
+ }
34458
+ }, [isManualSyncing, isPaused, onPlaySegment, editedSegment, syncWordIndex]);
34390
34459
  const handleKeyDown = reactExports.useCallback((e) => {
34391
34460
  if (e.code !== "Space") return;
34392
34461
  console.log("useManualSync - Spacebar pressed down", {
@@ -34397,25 +34466,71 @@ function useManualSync({
34397
34466
  });
34398
34467
  e.preventDefault();
34399
34468
  e.stopPropagation();
34400
- if (isManualSyncing && editedSegment && !isSpacebarPressed) {
34401
- const currentWord = syncWordIndex < editedSegment.words.length ? editedSegment.words[syncWordIndex] : null;
34402
- console.log("useManualSync - Recording word start time", {
34403
- wordIndex: syncWordIndex,
34404
- wordText: currentWord == null ? void 0 : currentWord.text,
34405
- time: currentTimeRef.current
34406
- });
34469
+ if (isManualSyncing && editedSegment && !isSpacebarPressed && !isPaused) {
34407
34470
  setIsSpacebarPressed(true);
34408
34471
  wordStartTimeRef.current = currentTimeRef.current;
34409
34472
  spacebarPressTimeRef.current = Date.now();
34410
34473
  if (syncWordIndex < editedSegment.words.length) {
34411
34474
  const newWords = [...wordsRef.current];
34412
- const currentWord2 = newWords[syncWordIndex];
34413
- currentWord2.start_time = currentTimeRef.current;
34475
+ const currentWord = newWords[syncWordIndex];
34476
+ const currentStartTime = currentTimeRef.current;
34477
+ currentWord.start_time = currentStartTime;
34478
+ if (syncWordIndex > 0) {
34479
+ const previousWord = newWords[syncWordIndex - 1];
34480
+ if (previousWord.start_time !== null) {
34481
+ const timeSincePreviousStart = currentStartTime - previousWord.start_time;
34482
+ const needsAdjustment = previousWord.end_time === null || previousWord.end_time !== null && previousWord.end_time > currentStartTime;
34483
+ if (needsAdjustment) {
34484
+ if (timeSincePreviousStart > 1) {
34485
+ previousWord.end_time = previousWord.start_time + 0.5;
34486
+ console.log("useManualSync - Gap detected, setting previous word end time to +500ms", {
34487
+ previousWordIndex: syncWordIndex - 1,
34488
+ previousWordText: previousWord.text,
34489
+ previousStartTime: previousWord.start_time,
34490
+ previousEndTime: previousWord.end_time,
34491
+ gap: timeSincePreviousStart.toFixed(2) + "s",
34492
+ reason: "gap > 1s"
34493
+ });
34494
+ } else {
34495
+ previousWord.end_time = currentStartTime - 5e-3;
34496
+ console.log("useManualSync - Setting previous word end time to current start - 5ms", {
34497
+ previousWordIndex: syncWordIndex - 1,
34498
+ previousWordText: previousWord.text,
34499
+ previousEndTime: previousWord.end_time,
34500
+ currentStartTime,
34501
+ gap: timeSincePreviousStart.toFixed(2) + "s",
34502
+ reason: "normal flow"
34503
+ });
34504
+ }
34505
+ } else {
34506
+ console.log("useManualSync - Preserving previous word timing (manually set)", {
34507
+ previousWordIndex: syncWordIndex - 1,
34508
+ previousWordText: previousWord.text,
34509
+ previousStartTime: previousWord.start_time,
34510
+ previousEndTime: previousWord.end_time,
34511
+ preservedDuration: previousWord.end_time !== null ? (previousWord.end_time - previousWord.start_time).toFixed(2) + "s" : "N/A",
34512
+ reason: "already timed correctly"
34513
+ });
34514
+ }
34515
+ }
34516
+ }
34517
+ console.log("useManualSync - Recording word start time", {
34518
+ wordIndex: syncWordIndex,
34519
+ wordText: currentWord == null ? void 0 : currentWord.text,
34520
+ time: currentStartTime
34521
+ });
34414
34522
  wordsRef.current = newWords;
34415
- updateSegment2(newWords);
34523
+ needsSegmentUpdateRef.current = true;
34416
34524
  }
34417
34525
  } else if (!isManualSyncing && editedSegment && onPlaySegment) {
34418
- console.log("useManualSync - Handling segment playback");
34526
+ console.log("useManualSync - Handling segment playback", {
34527
+ editedSegmentId: editedSegment.id,
34528
+ isGlobalReplacement: editedSegment.id === "global-replacement"
34529
+ });
34530
+ if (editedSegment.id === "global-replacement") {
34531
+ console.log("useManualSync - Ignoring playback for global replacement - please use Manual Sync");
34532
+ return;
34533
+ }
34419
34534
  const startTime = editedSegment.start_time ?? 0;
34420
34535
  const endTime = editedSegment.end_time ?? 0;
34421
34536
  if (currentTimeRef.current >= startTime && currentTimeRef.current <= endTime) {
@@ -34426,7 +34541,7 @@ function useManualSync({
34426
34541
  onPlaySegment(startTime);
34427
34542
  }
34428
34543
  }
34429
- }, [isManualSyncing, editedSegment, syncWordIndex, onPlaySegment, updateSegment2, isSpacebarPressed]);
34544
+ }, [isManualSyncing, editedSegment, syncWordIndex, onPlaySegment, isSpacebarPressed, isPaused]);
34430
34545
  const handleKeyUp = reactExports.useCallback((e) => {
34431
34546
  if (e.code !== "Space") return;
34432
34547
  console.log("useManualSync - Spacebar released", {
@@ -34438,7 +34553,7 @@ function useManualSync({
34438
34553
  });
34439
34554
  e.preventDefault();
34440
34555
  e.stopPropagation();
34441
- if (isManualSyncing && editedSegment && isSpacebarPressed) {
34556
+ if (isManualSyncing && editedSegment && isSpacebarPressed && !isPaused) {
34442
34557
  const currentWord = syncWordIndex < editedSegment.words.length ? editedSegment.words[syncWordIndex] : null;
34443
34558
  const pressDuration = spacebarPressTimeRef.current ? Date.now() - spacebarPressTimeRef.current : 0;
34444
34559
  const isTap = pressDuration < TAP_THRESHOLD_MS;
@@ -34449,6 +34564,7 @@ function useManualSync({
34449
34564
  endTime: currentTimeRef.current,
34450
34565
  pressDuration: `${pressDuration}ms`,
34451
34566
  isTap,
34567
+ tapThreshold: TAP_THRESHOLD_MS,
34452
34568
  duration: currentWord ? (currentTimeRef.current - (wordStartTimeRef.current || 0)).toFixed(2) + "s" : "N/A"
34453
34569
  });
34454
34570
  setIsSpacebarPressed(false);
@@ -34459,11 +34575,19 @@ function useManualSync({
34459
34575
  const defaultEndTime = (wordStartTimeRef.current || currentTimeRef.current) + DEFAULT_WORD_DURATION;
34460
34576
  currentWord2.end_time = defaultEndTime;
34461
34577
  console.log("useManualSync - Tap detected, setting default duration", {
34578
+ wordText: currentWord2.text,
34579
+ startTime: wordStartTimeRef.current,
34462
34580
  defaultEndTime,
34463
34581
  duration: DEFAULT_WORD_DURATION
34464
34582
  });
34465
34583
  } else {
34466
34584
  currentWord2.end_time = currentTimeRef.current;
34585
+ console.log("useManualSync - Hold detected, using actual timing", {
34586
+ wordText: currentWord2.text,
34587
+ startTime: wordStartTimeRef.current,
34588
+ endTime: currentTimeRef.current,
34589
+ actualDuration: (currentTimeRef.current - (wordStartTimeRef.current || 0)).toFixed(2) + "s"
34590
+ });
34467
34591
  }
34468
34592
  wordsRef.current = newWords;
34469
34593
  if (syncWordIndex === editedSegment.words.length - 1) {
@@ -34480,10 +34604,10 @@ function useManualSync({
34480
34604
  });
34481
34605
  setSyncWordIndex(syncWordIndex + 1);
34482
34606
  }
34483
- updateSegment2(newWords);
34607
+ needsSegmentUpdateRef.current = true;
34484
34608
  }
34485
34609
  }
34486
- }, [isManualSyncing, editedSegment, syncWordIndex, updateSegment2, isSpacebarPressed]);
34610
+ }, [isManualSyncing, editedSegment, syncWordIndex, isSpacebarPressed, isPaused]);
34487
34611
  reactExports.useEffect(() => {
34488
34612
  if (isManualSyncing && editedSegment && syncWordIndex > 0) {
34489
34613
  const newWords = [...wordsRef.current];
@@ -34499,10 +34623,10 @@ function useManualSync({
34499
34623
  });
34500
34624
  prevWord.end_time = currentWord.start_time - OVERLAP_BUFFER;
34501
34625
  wordsRef.current = newWords;
34502
- updateSegment2(newWords);
34626
+ needsSegmentUpdateRef.current = true;
34503
34627
  }
34504
34628
  }
34505
- }, [syncWordIndex, isManualSyncing, editedSegment, updateSegment2]);
34629
+ }, [syncWordIndex, isManualSyncing, editedSegment]);
34506
34630
  const handleSpacebar = reactExports.useCallback((e) => {
34507
34631
  if (e.type === "keydown") {
34508
34632
  handleKeyDown(e);
@@ -34511,33 +34635,62 @@ function useManualSync({
34511
34635
  }
34512
34636
  }, [handleKeyDown, handleKeyUp]);
34513
34637
  const startManualSync = reactExports.useCallback(() => {
34638
+ var _a;
34514
34639
  if (isManualSyncing) {
34515
34640
  cleanupManualSync();
34516
34641
  return;
34517
34642
  }
34518
34643
  if (!editedSegment || !onPlaySegment) return;
34519
34644
  wordsRef.current = [...editedSegment.words];
34645
+ const firstUnsyncedIndex = editedSegment.words.findIndex(
34646
+ (word) => word.start_time === null || word.end_time === null
34647
+ );
34648
+ const startIndex = firstUnsyncedIndex !== -1 ? firstUnsyncedIndex : 0;
34649
+ console.log("useManualSync - Starting manual sync", {
34650
+ totalWords: editedSegment.words.length,
34651
+ startingFromIndex: startIndex,
34652
+ startingWord: (_a = editedSegment.words[startIndex]) == null ? void 0 : _a.text
34653
+ });
34520
34654
  setIsManualSyncing(true);
34521
- setSyncWordIndex(0);
34655
+ setSyncWordIndex(startIndex);
34522
34656
  setIsSpacebarPressed(false);
34523
34657
  wordStartTimeRef.current = null;
34524
34658
  spacebarPressTimeRef.current = null;
34659
+ needsSegmentUpdateRef.current = false;
34525
34660
  onPlaySegment((editedSegment.start_time ?? 0) - 3);
34526
34661
  }, [isManualSyncing, editedSegment, onPlaySegment, cleanupManualSync]);
34527
34662
  reactExports.useEffect(() => {
34528
- var _a;
34529
- if (!editedSegment) return;
34530
- const endTime = editedSegment.end_time ?? 0;
34531
- if (window.isAudioPlaying && currentTimeRef.current > endTime) {
34532
- console.log("Stopping playback: current time exceeded end time");
34533
- (_a = window.toggleAudioPlayback) == null ? void 0 : _a.call(window);
34534
- cleanupManualSync();
34663
+ if (!editedSegment || !isManualSyncing) return;
34664
+ if (editedSegment.id === "global-replacement") {
34665
+ console.log("useManualSync - Skipping auto-stop for global replacement segment");
34666
+ return;
34535
34667
  }
34536
- }, [isManualSyncing, editedSegment, currentTimeRef, cleanupManualSync]);
34668
+ const checkAutoStop = () => {
34669
+ var _a;
34670
+ const endTime = editedSegment.end_time ?? 0;
34671
+ if (window.isAudioPlaying && currentTimeRef.current > endTime) {
34672
+ console.log("useManualSync - Auto-stopping: current time exceeded end time", {
34673
+ currentTime: currentTimeRef.current,
34674
+ endTime,
34675
+ segmentId: editedSegment.id
34676
+ });
34677
+ (_a = window.toggleAudioPlayback) == null ? void 0 : _a.call(window);
34678
+ cleanupManualSync();
34679
+ }
34680
+ };
34681
+ checkAutoStop();
34682
+ const intervalId = setInterval(checkAutoStop, 100);
34683
+ return () => {
34684
+ clearInterval(intervalId);
34685
+ };
34686
+ }, [isManualSyncing, editedSegment, cleanupManualSync]);
34537
34687
  return {
34538
34688
  isManualSyncing,
34689
+ isPaused,
34539
34690
  syncWordIndex,
34540
34691
  startManualSync,
34692
+ pauseManualSync,
34693
+ resumeManualSync,
34541
34694
  cleanupManualSync,
34542
34695
  handleSpacebar,
34543
34696
  isSpacebarPressed
@@ -34566,6 +34719,9 @@ const AutorenewIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path"
34566
34719
  const PauseCircleOutlineIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
34567
34720
  d: "M9 16h2V8H9zm3-14C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8m1-4h2V8h-2z"
34568
34721
  }), "PauseCircleOutline");
34722
+ const PlayArrowIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
34723
+ d: "M8 5v14l11-7z"
34724
+ }), "PlayArrow");
34569
34725
  const CenterFocusStrongIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
34570
34726
  d: "M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4m-7 7H3v4c0 1.1.9 2 2 2h4v-2H5zM5 5h4V3H5c-1.1 0-2 .9-2 2v4h2zm14-2h-4v2h4v4h2V5c0-1.1-.9-2-2-2m0 16h-4v2h4c1.1 0 2-.9 2-2v-4h-2z"
34571
34727
  }), "CenterFocusStrong");
@@ -34668,7 +34824,7 @@ const TimelineCursor = styled(Box)(({ theme: theme2 }) => ({
34668
34824
  zIndex: 1
34669
34825
  // Ensure it's above other elements
34670
34826
  }));
34671
- function TimelineEditor({ words, startTime, endTime, onWordUpdate, currentTime = 0, onPlaySegment, showPlaybackIndicator = true }) {
34827
+ function TimelineEditor({ words, startTime, endTime, onWordUpdate, onUnsyncWord, currentTime = 0, onPlaySegment, showPlaybackIndicator = true }) {
34672
34828
  const containerRef = reactExports.useRef(null);
34673
34829
  const [dragState, setDragState] = reactExports.useState(null);
34674
34830
  const MIN_DURATION = 0.1;
@@ -34694,21 +34850,14 @@ function TimelineEditor({ words, startTime, endTime, onWordUpdate, currentTime =
34694
34850
  const marks = [];
34695
34851
  const startSecond = Math.floor(startTime);
34696
34852
  const endSecond = Math.ceil(endTime);
34697
- for (let time = startSecond; time <= endSecond; time += 0.1) {
34853
+ for (let time = startSecond; time <= endSecond; time++) {
34698
34854
  if (time >= startTime && time <= endTime) {
34699
34855
  const position2 = timeToPosition(time);
34700
- const isFullSecond = Math.abs(time - Math.round(time)) < 1e-3;
34701
34856
  marks.push(
34702
34857
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { children: [
34703
- /* @__PURE__ */ jsxRuntimeExports.jsx(
34704
- TimelineMark,
34705
- {
34706
- className: isFullSecond ? "" : "subsecond",
34707
- sx: { left: `${position2}%` }
34708
- }
34709
- ),
34710
- isFullSecond && /* @__PURE__ */ jsxRuntimeExports.jsxs(TimelineLabel, { sx: { left: `${position2}%` }, children: [
34711
- Math.round(time),
34858
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TimelineMark, { sx: { left: `${position2}%` } }),
34859
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(TimelineLabel, { sx: { left: `${position2}%` }, children: [
34860
+ time,
34712
34861
  "s"
34713
34862
  ] })
34714
34863
  ] }, time)
@@ -34788,6 +34937,19 @@ function TimelineEditor({ words, startTime, endTime, onWordUpdate, currentTime =
34788
34937
  const handleMouseUp = () => {
34789
34938
  setDragState(null);
34790
34939
  };
34940
+ const handleContextMenu = (e, wordIndex) => {
34941
+ e.preventDefault();
34942
+ e.stopPropagation();
34943
+ const word = words[wordIndex];
34944
+ if (word.start_time === null || word.end_time === null) return;
34945
+ if (onUnsyncWord) {
34946
+ console.log("TimelineEditor - Right-click unsync word", {
34947
+ wordIndex,
34948
+ wordText: word.text
34949
+ });
34950
+ onUnsyncWord(wordIndex);
34951
+ }
34952
+ };
34791
34953
  const isWordHighlighted = (word) => {
34792
34954
  if (!currentTime || word.start_time === null || word.end_time === null) return false;
34793
34955
  return currentTime >= word.start_time && currentTime <= word.end_time;
@@ -34842,6 +35004,7 @@ function TimelineEditor({ words, startTime, endTime, onWordUpdate, currentTime =
34842
35004
  e.stopPropagation();
34843
35005
  handleMouseDown(e, index, "move");
34844
35006
  },
35007
+ onContextMenu: (e) => handleContextMenu(e, index),
34845
35008
  children: [
34846
35009
  /* @__PURE__ */ jsxRuntimeExports.jsx(
34847
35010
  ResizeHandle,
@@ -34873,6 +35036,125 @@ function TimelineEditor({ words, startTime, endTime, onWordUpdate, currentTime =
34873
35036
  }
34874
35037
  );
34875
35038
  }
35039
+ const TimelineControls = reactExports.memo(({
35040
+ isGlobal,
35041
+ visibleStartTime,
35042
+ visibleEndTime,
35043
+ startTime,
35044
+ endTime,
35045
+ zoomLevel,
35046
+ autoScrollEnabled,
35047
+ currentTime,
35048
+ isManualSyncing,
35049
+ isReplaceAllMode,
35050
+ isPaused,
35051
+ onScrollLeft,
35052
+ onZoomOut,
35053
+ onZoomIn,
35054
+ onScrollRight,
35055
+ onToggleAutoScroll,
35056
+ onJumpToCurrentTime,
35057
+ onStartManualSync,
35058
+ onPauseResume,
35059
+ onStopAudio
35060
+ }) => {
35061
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [
35062
+ isGlobal && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
35063
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Scroll Left", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35064
+ IconButton,
35065
+ {
35066
+ onClick: onScrollLeft,
35067
+ disabled: visibleStartTime <= startTime,
35068
+ size: "small",
35069
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowBack, {})
35070
+ }
35071
+ ) }),
35072
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Zoom Out (Show More Time)", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35073
+ IconButton,
35074
+ {
35075
+ onClick: onZoomOut,
35076
+ disabled: zoomLevel >= endTime - startTime || isReplaceAllMode && isManualSyncing && !isPaused,
35077
+ size: "small",
35078
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOutIcon, {})
35079
+ }
35080
+ ) }),
35081
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Zoom In (Show Less Time)", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35082
+ IconButton,
35083
+ {
35084
+ onClick: onZoomIn,
35085
+ disabled: zoomLevel <= 2 || isReplaceAllMode && isManualSyncing && !isPaused,
35086
+ size: "small",
35087
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomInIcon, {})
35088
+ }
35089
+ ) }),
35090
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Scroll Right", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35091
+ IconButton,
35092
+ {
35093
+ onClick: onScrollRight,
35094
+ disabled: visibleEndTime >= endTime,
35095
+ size: "small",
35096
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowForwardIcon, {})
35097
+ }
35098
+ ) }),
35099
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35100
+ Tooltip,
35101
+ {
35102
+ title: autoScrollEnabled ? "Disable Auto-Page Turn During Playback" : "Enable Auto-Page Turn During Playback",
35103
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35104
+ IconButton,
35105
+ {
35106
+ onClick: onToggleAutoScroll,
35107
+ color: autoScrollEnabled ? "primary" : "default",
35108
+ size: "small",
35109
+ children: autoScrollEnabled ? /* @__PURE__ */ jsxRuntimeExports.jsx(AutorenewIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(PauseCircleOutlineIcon, {})
35110
+ }
35111
+ )
35112
+ }
35113
+ ),
35114
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Jump to Current Playback Position", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35115
+ IconButton,
35116
+ {
35117
+ onClick: onJumpToCurrentTime,
35118
+ disabled: !currentTime,
35119
+ size: "small",
35120
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(CenterFocusStrongIcon, {})
35121
+ }
35122
+ ) })
35123
+ ] }),
35124
+ isReplaceAllMode && onStopAudio && /* @__PURE__ */ jsxRuntimeExports.jsx(
35125
+ Button,
35126
+ {
35127
+ variant: "outlined",
35128
+ onClick: onStopAudio,
35129
+ startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(StopIcon, {}),
35130
+ color: "error",
35131
+ size: "small",
35132
+ children: "Stop Audio"
35133
+ }
35134
+ ),
35135
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35136
+ Button,
35137
+ {
35138
+ variant: isManualSyncing ? "outlined" : "contained",
35139
+ onClick: onStartManualSync,
35140
+ startIcon: isManualSyncing ? /* @__PURE__ */ jsxRuntimeExports.jsx(CancelIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(PlayCircleOutlineIcon, {}),
35141
+ color: isManualSyncing ? "error" : "primary",
35142
+ children: isManualSyncing ? "Cancel Sync" : "Manual Sync"
35143
+ }
35144
+ ),
35145
+ isManualSyncing && isReplaceAllMode && /* @__PURE__ */ jsxRuntimeExports.jsx(
35146
+ Button,
35147
+ {
35148
+ variant: "outlined",
35149
+ onClick: onPauseResume,
35150
+ startIcon: isPaused ? /* @__PURE__ */ jsxRuntimeExports.jsx(PlayArrowIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(PauseCircleOutlineIcon, {}),
35151
+ color: isPaused ? "success" : "warning",
35152
+ size: "small",
35153
+ children: isPaused ? "Resume" : "Pause"
35154
+ }
35155
+ )
35156
+ ] });
35157
+ });
34876
35158
  function EditTimelineSection({
34877
35159
  words,
34878
35160
  startTime,
@@ -34886,16 +35168,32 @@ function EditTimelineSection({
34886
35168
  syncWordIndex,
34887
35169
  isSpacebarPressed,
34888
35170
  onWordUpdate,
35171
+ onUnsyncWord,
34889
35172
  onPlaySegment,
35173
+ onStopAudio,
34890
35174
  startManualSync,
34891
- isGlobal = false
35175
+ pauseManualSync,
35176
+ resumeManualSync,
35177
+ isPaused = false,
35178
+ isGlobal = false,
35179
+ defaultZoomLevel = 10,
35180
+ isReplaceAllMode = false
34892
35181
  }) {
34893
- var _a;
34894
- const [zoomLevel, setZoomLevel] = reactExports.useState(10);
35182
+ const [zoomLevel, setZoomLevel] = reactExports.useState(defaultZoomLevel);
34895
35183
  const [visibleStartTime, setVisibleStartTime] = reactExports.useState(startTime);
34896
35184
  const [visibleEndTime, setVisibleEndTime] = reactExports.useState(Math.min(startTime + zoomLevel, endTime));
34897
35185
  const [autoScrollEnabled, setAutoScrollEnabled] = reactExports.useState(true);
34898
35186
  const timelineRef = reactExports.useRef(null);
35187
+ const effectiveTimeRange = reactExports.useMemo(() => ({
35188
+ start: isGlobal ? visibleStartTime : startTime,
35189
+ end: isGlobal ? visibleEndTime : endTime
35190
+ }), [isGlobal, visibleStartTime, visibleEndTime, startTime, endTime]);
35191
+ reactExports.useEffect(() => {
35192
+ if (isManualSyncing && !isPaused) {
35193
+ console.log("EditTimelineSection - Auto-enabling auto-scroll for manual sync");
35194
+ setAutoScrollEnabled(true);
35195
+ }
35196
+ }, [isManualSyncing, isPaused]);
34899
35197
  reactExports.useEffect(() => {
34900
35198
  if (isGlobal) {
34901
35199
  setVisibleStartTime(startTime);
@@ -34905,8 +35203,13 @@ function EditTimelineSection({
34905
35203
  setVisibleEndTime(endTime);
34906
35204
  }
34907
35205
  }, [startTime, endTime, zoomLevel, isGlobal]);
35206
+ const lastScrollUpdateRef = reactExports.useRef(0);
35207
+ const SCROLL_THROTTLE_MS = 100;
34908
35208
  reactExports.useEffect(() => {
34909
35209
  if (!isGlobal || !currentTime || !autoScrollEnabled) return;
35210
+ const now = Date.now();
35211
+ if (now - lastScrollUpdateRef.current < SCROLL_THROTTLE_MS) return;
35212
+ lastScrollUpdateRef.current = now;
34910
35213
  if (currentTime < visibleStartTime) {
34911
35214
  const newStart = Math.max(startTime, currentTime);
34912
35215
  const newEnd = Math.min(endTime, newStart + zoomLevel);
@@ -34935,9 +35238,21 @@ function EditTimelineSection({
34935
35238
  setVisibleEndTime(endTime);
34936
35239
  }
34937
35240
  }, [zoomLevel, startTime, endTime, isGlobal, visibleStartTime]);
34938
- const toggleAutoScroll = () => {
35241
+ const handleZoomIn = reactExports.useCallback(() => {
35242
+ if (isReplaceAllMode && isManualSyncing && !isPaused) return;
35243
+ if (zoomLevel > 2) {
35244
+ setZoomLevel(zoomLevel - 2);
35245
+ }
35246
+ }, [isReplaceAllMode, isManualSyncing, isPaused, zoomLevel]);
35247
+ const handleZoomOut = reactExports.useCallback(() => {
35248
+ if (isReplaceAllMode && isManualSyncing && !isPaused) return;
35249
+ if (zoomLevel < endTime - startTime) {
35250
+ setZoomLevel(zoomLevel + 2);
35251
+ }
35252
+ }, [isReplaceAllMode, isManualSyncing, isPaused, zoomLevel, endTime, startTime]);
35253
+ const toggleAutoScroll = reactExports.useCallback(() => {
34939
35254
  setAutoScrollEnabled(!autoScrollEnabled);
34940
- };
35255
+ }, [autoScrollEnabled]);
34941
35256
  const jumpToCurrentTime = reactExports.useCallback(() => {
34942
35257
  if (!isGlobal || !currentTime) return;
34943
35258
  const halfZoom = zoomLevel / 2;
@@ -34949,34 +35264,6 @@ function EditTimelineSection({
34949
35264
  setVisibleStartTime(newStart);
34950
35265
  setVisibleEndTime(newEnd);
34951
35266
  }, [currentTime, zoomLevel, startTime, endTime, isGlobal]);
34952
- reactExports.useEffect(() => {
34953
- const handleKeyDown = (e) => {
34954
- if (isGlobal) {
34955
- if (e.altKey && e.key === "a") {
34956
- e.preventDefault();
34957
- toggleAutoScroll();
34958
- }
34959
- if (e.altKey && e.key === "j") {
34960
- e.preventDefault();
34961
- jumpToCurrentTime();
34962
- }
34963
- }
34964
- };
34965
- window.addEventListener("keydown", handleKeyDown);
34966
- return () => {
34967
- window.removeEventListener("keydown", handleKeyDown);
34968
- };
34969
- }, [isGlobal, toggleAutoScroll, jumpToCurrentTime]);
34970
- const handleZoomIn = () => {
34971
- if (zoomLevel > 2) {
34972
- setZoomLevel(zoomLevel - 2);
34973
- }
34974
- };
34975
- const handleZoomOut = () => {
34976
- if (zoomLevel < endTime - startTime) {
34977
- setZoomLevel(zoomLevel + 2);
34978
- }
34979
- };
34980
35267
  const handleScroll = reactExports.useCallback((event) => {
34981
35268
  if (isGlobal && event.deltaX !== 0) {
34982
35269
  event.preventDefault();
@@ -34996,7 +35283,7 @@ function EditTimelineSection({
34996
35283
  setVisibleEndTime(newEnd);
34997
35284
  }
34998
35285
  }, [isGlobal, visibleStartTime, visibleEndTime, startTime, endTime, zoomLevel]);
34999
- const handleScrollLeft = () => {
35286
+ const handleScrollLeft = reactExports.useCallback(() => {
35000
35287
  if (!isGlobal) return;
35001
35288
  setAutoScrollEnabled(false);
35002
35289
  const scrollAmount = zoomLevel * 0.25;
@@ -35004,8 +35291,8 @@ function EditTimelineSection({
35004
35291
  const newEnd = newStart + zoomLevel;
35005
35292
  setVisibleStartTime(newStart);
35006
35293
  setVisibleEndTime(newEnd);
35007
- };
35008
- const handleScrollRight = () => {
35294
+ }, [isGlobal, zoomLevel, startTime, visibleStartTime]);
35295
+ const handleScrollRight = reactExports.useCallback(() => {
35009
35296
  if (!isGlobal) return;
35010
35297
  setAutoScrollEnabled(false);
35011
35298
  const scrollAmount = zoomLevel * 0.25;
@@ -35019,9 +35306,25 @@ function EditTimelineSection({
35019
35306
  setVisibleEndTime(newEnd);
35020
35307
  }
35021
35308
  setVisibleStartTime(newStart);
35022
- };
35023
- const effectiveStartTime = isGlobal ? visibleStartTime : startTime;
35024
- const effectiveEndTime = isGlobal ? visibleEndTime : endTime;
35309
+ }, [isGlobal, zoomLevel, endTime, visibleEndTime, startTime]);
35310
+ const handlePauseResume = reactExports.useCallback(() => {
35311
+ if (isPaused && resumeManualSync) {
35312
+ resumeManualSync();
35313
+ } else if (!isPaused && pauseManualSync) {
35314
+ pauseManualSync();
35315
+ }
35316
+ }, [isPaused, resumeManualSync, pauseManualSync]);
35317
+ const currentWordInfo = reactExports.useMemo(() => {
35318
+ var _a;
35319
+ if (!isManualSyncing || syncWordIndex < 0 || syncWordIndex >= words.length) {
35320
+ return null;
35321
+ }
35322
+ return {
35323
+ index: syncWordIndex + 1,
35324
+ total: words.length,
35325
+ text: ((_a = words[syncWordIndex]) == null ? void 0 : _a.text) || ""
35326
+ };
35327
+ }, [isManualSyncing, syncWordIndex, words]);
35025
35328
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
35026
35329
  /* @__PURE__ */ jsxRuntimeExports.jsx(
35027
35330
  Box,
@@ -35033,11 +35336,12 @@ function EditTimelineSection({
35033
35336
  TimelineEditor,
35034
35337
  {
35035
35338
  words,
35036
- startTime: effectiveStartTime,
35037
- endTime: effectiveEndTime,
35339
+ startTime: effectiveTimeRange.start,
35340
+ endTime: effectiveTimeRange.end,
35038
35341
  onWordUpdate,
35039
35342
  currentTime,
35040
- onPlaySegment
35343
+ onPlaySegment,
35344
+ onUnsyncWord
35041
35345
  }
35042
35346
  )
35043
35347
  }
@@ -35054,88 +35358,40 @@ function EditTimelineSection({
35054
35358
  " - ",
35055
35359
  (currentEndTime == null ? void 0 : currentEndTime.toFixed(2)) ?? "N/A"
35056
35360
  ] }),
35057
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [
35058
- isGlobal && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
35059
- /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Scroll Left", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35060
- IconButton,
35061
- {
35062
- onClick: handleScrollLeft,
35063
- disabled: visibleStartTime <= startTime,
35064
- size: "small",
35065
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowBack, {})
35066
- }
35067
- ) }),
35068
- /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Zoom Out (Show More Time)", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35069
- IconButton,
35070
- {
35071
- onClick: handleZoomOut,
35072
- disabled: zoomLevel >= endTime - startTime,
35073
- size: "small",
35074
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOutIcon, {})
35075
- }
35076
- ) }),
35077
- /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Zoom In (Show Less Time)", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35078
- IconButton,
35079
- {
35080
- onClick: handleZoomIn,
35081
- disabled: zoomLevel <= 2,
35082
- size: "small",
35083
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomInIcon, {})
35084
- }
35085
- ) }),
35086
- /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Scroll Right", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35087
- IconButton,
35088
- {
35089
- onClick: handleScrollRight,
35090
- disabled: visibleEndTime >= endTime,
35091
- size: "small",
35092
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowForwardIcon, {})
35093
- }
35094
- ) }),
35095
- /* @__PURE__ */ jsxRuntimeExports.jsx(
35096
- Tooltip,
35097
- {
35098
- title: autoScrollEnabled ? "Disable Auto-Page Turn During Playback (Alt+A)" : "Enable Auto-Page Turn During Playback (Alt+A)",
35099
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35100
- IconButton,
35101
- {
35102
- onClick: toggleAutoScroll,
35103
- color: autoScrollEnabled ? "primary" : "default",
35104
- size: "small",
35105
- children: autoScrollEnabled ? /* @__PURE__ */ jsxRuntimeExports.jsx(AutorenewIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(PauseCircleOutlineIcon, {})
35106
- }
35107
- )
35108
- }
35109
- ),
35110
- /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Jump to Current Playback Position (Alt+J)", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35111
- IconButton,
35112
- {
35113
- onClick: jumpToCurrentTime,
35114
- disabled: !currentTime,
35115
- size: "small",
35116
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(CenterFocusStrongIcon, {})
35117
- }
35118
- ) })
35119
- ] }),
35361
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 2 }, children: [
35120
35362
  /* @__PURE__ */ jsxRuntimeExports.jsx(
35121
- Button,
35363
+ TimelineControls,
35122
35364
  {
35123
- variant: isManualSyncing ? "outlined" : "contained",
35124
- onClick: startManualSync,
35125
- disabled: !onPlaySegment,
35126
- startIcon: isManualSyncing ? /* @__PURE__ */ jsxRuntimeExports.jsx(CancelIcon, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(PlayCircleOutlineIcon, {}),
35127
- color: isManualSyncing ? "error" : "primary",
35128
- children: isManualSyncing ? "Cancel Sync" : "Manual Sync"
35365
+ isGlobal,
35366
+ visibleStartTime,
35367
+ visibleEndTime,
35368
+ startTime,
35369
+ endTime,
35370
+ zoomLevel,
35371
+ autoScrollEnabled,
35372
+ currentTime,
35373
+ isManualSyncing,
35374
+ isReplaceAllMode,
35375
+ isPaused,
35376
+ onScrollLeft: handleScrollLeft,
35377
+ onZoomOut: handleZoomOut,
35378
+ onZoomIn: handleZoomIn,
35379
+ onScrollRight: handleScrollRight,
35380
+ onToggleAutoScroll: toggleAutoScroll,
35381
+ onJumpToCurrentTime: jumpToCurrentTime,
35382
+ onStartManualSync: startManualSync,
35383
+ onPauseResume: handlePauseResume,
35384
+ onStopAudio
35129
35385
  }
35130
35386
  ),
35131
- isManualSyncing && /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { children: [
35387
+ currentWordInfo && /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { children: [
35132
35388
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Typography, { variant: "body2", children: [
35133
35389
  "Word ",
35134
- syncWordIndex + 1,
35390
+ currentWordInfo.index,
35135
35391
  " of ",
35136
- words.length,
35392
+ currentWordInfo.total,
35137
35393
  ": ",
35138
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: ((_a = words[syncWordIndex]) == null ? void 0 : _a.text) || "" })
35394
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: currentWordInfo.text })
35139
35395
  ] }),
35140
35396
  /* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "caption", color: "text.secondary", children: isSpacebarPressed ? "Holding spacebar... Release when word ends" : "Press spacebar when word starts (tap for short words, hold for long words)" })
35141
35397
  ] })
@@ -36317,6 +36573,9 @@ function PreviewVideoSection({
36317
36573
  const CloudUpload = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
36318
36574
  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"
36319
36575
  }), "CloudUpload");
36576
+ const ContentPasteIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
36577
+ d: "M19 2h-4.18C14.4.84 13.3 0 12 0S9.6.84 9.18 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2m-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1m7 18H5V4h2v3h10V4h2z"
36578
+ }), "ContentPaste");
36320
36579
  const EditIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
36321
36580
  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"
36322
36581
  }), "Edit");
@@ -36335,9 +36594,6 @@ const OndemandVideo = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path"
36335
36594
  const PauseIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
36336
36595
  d: "M6 19h4V5H6zm8-14v14h4V5z"
36337
36596
  }), "Pause");
36338
- const PlayArrowIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
36339
- d: "M8 5v14l11-7z"
36340
- }), "PlayArrow");
36341
36597
  const RedoIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
36342
36598
  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"
36343
36599
  }), "Redo");
@@ -36621,6 +36877,545 @@ function ReviewChangesModal({
36621
36877
  }
36622
36878
  );
36623
36879
  }
36880
+ function ReplaceAllLyricsModal({
36881
+ open,
36882
+ onClose,
36883
+ onSave,
36884
+ onPlaySegment,
36885
+ currentTime = 0,
36886
+ setModalSpacebarHandler
36887
+ }) {
36888
+ const [inputText, setInputText] = reactExports.useState("");
36889
+ const [isReplaced, setIsReplaced] = reactExports.useState(false);
36890
+ const [globalSegment, setGlobalSegment] = reactExports.useState(null);
36891
+ const [originalSegments, setOriginalSegments] = reactExports.useState([]);
36892
+ const [currentSegments, setCurrentSegments] = reactExports.useState([]);
36893
+ const getAudioDuration = reactExports.useCallback(() => {
36894
+ if (window.getAudioDuration) {
36895
+ const duration2 = window.getAudioDuration();
36896
+ return duration2 > 0 ? duration2 : 600;
36897
+ }
36898
+ return 600;
36899
+ }, []);
36900
+ const parseInfo = reactExports.useMemo(() => {
36901
+ if (!inputText.trim()) return { lines: 0, words: 0 };
36902
+ const lines = inputText.trim().split("\n").filter((line2) => line2.trim().length > 0);
36903
+ const totalWords = lines.reduce((count, line2) => {
36904
+ return count + line2.trim().split(/\s+/).length;
36905
+ }, 0);
36906
+ return { lines: lines.length, words: totalWords };
36907
+ }, [inputText]);
36908
+ const processLyrics = reactExports.useCallback(() => {
36909
+ if (!inputText.trim()) return;
36910
+ const lines = inputText.trim().split("\n").filter((line2) => line2.trim().length > 0);
36911
+ const newSegments = [];
36912
+ const allWords = [];
36913
+ lines.forEach((line2) => {
36914
+ const words = line2.trim().split(/\s+/).filter((word) => word.length > 0);
36915
+ const segmentWords = [];
36916
+ words.forEach((wordText) => {
36917
+ const word = {
36918
+ id: nanoid(),
36919
+ text: wordText,
36920
+ start_time: null,
36921
+ end_time: null,
36922
+ confidence: 1,
36923
+ created_during_correction: true
36924
+ };
36925
+ segmentWords.push(word);
36926
+ allWords.push(word);
36927
+ });
36928
+ const segment = {
36929
+ id: nanoid(),
36930
+ text: line2.trim(),
36931
+ words: segmentWords,
36932
+ start_time: null,
36933
+ end_time: null
36934
+ };
36935
+ newSegments.push(segment);
36936
+ });
36937
+ const audioDuration = getAudioDuration();
36938
+ const endTime = Math.max(audioDuration, 3600);
36939
+ console.log("ReplaceAllLyricsModal - Creating global segment", {
36940
+ audioDuration,
36941
+ endTime,
36942
+ wordCount: allWords.length
36943
+ });
36944
+ const globalSegment2 = {
36945
+ id: "global-replacement",
36946
+ text: allWords.map((w) => w.text).join(" "),
36947
+ words: allWords,
36948
+ start_time: 0,
36949
+ end_time: endTime
36950
+ };
36951
+ setCurrentSegments(newSegments);
36952
+ setOriginalSegments(JSON.parse(JSON.stringify(newSegments)));
36953
+ setGlobalSegment(globalSegment2);
36954
+ setIsReplaced(true);
36955
+ }, [inputText, getAudioDuration]);
36956
+ const handlePasteFromClipboard = reactExports.useCallback(async () => {
36957
+ try {
36958
+ const text = await navigator.clipboard.readText();
36959
+ setInputText(text);
36960
+ } catch (error) {
36961
+ console.error("Failed to read from clipboard:", error);
36962
+ alert("Failed to read from clipboard. Please paste manually.");
36963
+ }
36964
+ }, []);
36965
+ const updateSegment2 = reactExports.useCallback((newWords) => {
36966
+ if (!globalSegment) return;
36967
+ const validStartTimes = newWords.map((w) => w.start_time).filter((t) => t !== null);
36968
+ const validEndTimes = newWords.map((w) => w.end_time).filter((t) => t !== null);
36969
+ const segmentStartTime = validStartTimes.length > 0 ? Math.min(...validStartTimes) : null;
36970
+ const segmentEndTime = validEndTimes.length > 0 ? Math.max(...validEndTimes) : null;
36971
+ const updatedGlobalSegment = {
36972
+ ...globalSegment,
36973
+ words: newWords,
36974
+ text: newWords.map((w) => w.text).join(" "),
36975
+ start_time: segmentStartTime,
36976
+ end_time: segmentEndTime
36977
+ };
36978
+ setGlobalSegment(updatedGlobalSegment);
36979
+ const updatedSegments = currentSegments.map((segment) => {
36980
+ const segmentWordsWithTiming = segment.words.map((segmentWord) => {
36981
+ const globalWord = newWords.find((w) => w.id === segmentWord.id);
36982
+ return globalWord || segmentWord;
36983
+ });
36984
+ const wordsWithTiming = segmentWordsWithTiming.filter(
36985
+ (w) => w.start_time !== null && w.end_time !== null
36986
+ );
36987
+ if (wordsWithTiming.length === segmentWordsWithTiming.length && wordsWithTiming.length > 0) {
36988
+ const segmentStart = Math.min(...wordsWithTiming.map((w) => w.start_time));
36989
+ const segmentEnd = Math.max(...wordsWithTiming.map((w) => w.end_time));
36990
+ return {
36991
+ ...segment,
36992
+ words: segmentWordsWithTiming,
36993
+ start_time: segmentStart,
36994
+ end_time: segmentEnd
36995
+ };
36996
+ } else {
36997
+ return {
36998
+ ...segment,
36999
+ words: segmentWordsWithTiming
37000
+ };
37001
+ }
37002
+ });
37003
+ setCurrentSegments(updatedSegments);
37004
+ }, [globalSegment, currentSegments]);
37005
+ const {
37006
+ isManualSyncing,
37007
+ isPaused,
37008
+ syncWordIndex,
37009
+ startManualSync,
37010
+ pauseManualSync,
37011
+ resumeManualSync,
37012
+ cleanupManualSync,
37013
+ handleSpacebar,
37014
+ isSpacebarPressed
37015
+ } = useManualSync({
37016
+ editedSegment: globalSegment,
37017
+ currentTime,
37018
+ onPlaySegment,
37019
+ updateSegment: updateSegment2
37020
+ });
37021
+ const handleWordUpdate = reactExports.useCallback((wordIndex, updates) => {
37022
+ var _a;
37023
+ if (!globalSegment) return;
37024
+ if (isManualSyncing && !isPaused) {
37025
+ console.log("ReplaceAllLyricsModal - Ignoring word update during active manual sync");
37026
+ return;
37027
+ }
37028
+ console.log("ReplaceAllLyricsModal - Manual word update", {
37029
+ wordIndex,
37030
+ wordText: (_a = globalSegment.words[wordIndex]) == null ? void 0 : _a.text,
37031
+ updates,
37032
+ isManualSyncing,
37033
+ isPaused
37034
+ });
37035
+ const newWords = [...globalSegment.words];
37036
+ newWords[wordIndex] = {
37037
+ ...newWords[wordIndex],
37038
+ ...updates
37039
+ };
37040
+ updateSegment2(newWords);
37041
+ }, [globalSegment, updateSegment2, isManualSyncing, isPaused]);
37042
+ const handleUnsyncWord = reactExports.useCallback((wordIndex) => {
37043
+ var _a;
37044
+ if (!globalSegment) return;
37045
+ console.log("ReplaceAllLyricsModal - Un-syncing word", {
37046
+ wordIndex,
37047
+ wordText: (_a = globalSegment.words[wordIndex]) == null ? void 0 : _a.text
37048
+ });
37049
+ const newWords = [...globalSegment.words];
37050
+ newWords[wordIndex] = {
37051
+ ...newWords[wordIndex],
37052
+ start_time: null,
37053
+ end_time: null
37054
+ };
37055
+ updateSegment2(newWords);
37056
+ }, [globalSegment, updateSegment2]);
37057
+ const handleClose = reactExports.useCallback(() => {
37058
+ cleanupManualSync();
37059
+ setInputText("");
37060
+ setIsReplaced(false);
37061
+ setGlobalSegment(null);
37062
+ setOriginalSegments([]);
37063
+ setCurrentSegments([]);
37064
+ onClose();
37065
+ }, [onClose, cleanupManualSync]);
37066
+ const handleSave = reactExports.useCallback(() => {
37067
+ if (!globalSegment || !currentSegments.length) return;
37068
+ const finalSegments = [];
37069
+ let wordIndex = 0;
37070
+ currentSegments.forEach((segment) => {
37071
+ const originalWordCount = segment.words.length;
37072
+ const segmentWords = globalSegment.words.slice(wordIndex, wordIndex + originalWordCount);
37073
+ wordIndex += originalWordCount;
37074
+ if (segmentWords.length > 0) {
37075
+ const validStartTimes = segmentWords.map((w) => w.start_time).filter((t) => t !== null);
37076
+ const validEndTimes = segmentWords.map((w) => w.end_time).filter((t) => t !== null);
37077
+ const segmentStartTime = validStartTimes.length > 0 ? Math.min(...validStartTimes) : null;
37078
+ const segmentEndTime = validEndTimes.length > 0 ? Math.max(...validEndTimes) : null;
37079
+ finalSegments.push({
37080
+ ...segment,
37081
+ words: segmentWords,
37082
+ text: segmentWords.map((w) => w.text).join(" "),
37083
+ start_time: segmentStartTime,
37084
+ end_time: segmentEndTime
37085
+ });
37086
+ }
37087
+ });
37088
+ console.log("ReplaceAllLyricsModal - Saving new segments:", {
37089
+ originalSegmentCount: currentSegments.length,
37090
+ finalSegmentCount: finalSegments.length,
37091
+ totalWords: finalSegments.reduce((count, seg) => count + seg.words.length, 0)
37092
+ });
37093
+ onSave(finalSegments);
37094
+ handleClose();
37095
+ }, [globalSegment, currentSegments, onSave, handleClose]);
37096
+ const handleReset = reactExports.useCallback(() => {
37097
+ if (!originalSegments.length) return;
37098
+ console.log("ReplaceAllLyricsModal - Resetting to original state");
37099
+ const resetWords = originalSegments.flatMap(
37100
+ (segment) => segment.words.map((word) => ({
37101
+ ...word,
37102
+ start_time: null,
37103
+ end_time: null
37104
+ }))
37105
+ );
37106
+ const audioDuration = getAudioDuration();
37107
+ const resetGlobalSegment = {
37108
+ id: "global-replacement",
37109
+ text: resetWords.map((w) => w.text).join(" "),
37110
+ words: resetWords,
37111
+ start_time: 0,
37112
+ end_time: Math.max(audioDuration, 3600)
37113
+ // At least 1 hour to prevent auto-stop
37114
+ };
37115
+ const resetCurrentSegments = originalSegments.map((segment) => ({
37116
+ ...segment,
37117
+ words: segment.words.map((word) => ({
37118
+ ...word,
37119
+ start_time: null,
37120
+ end_time: null
37121
+ })),
37122
+ start_time: null,
37123
+ end_time: null
37124
+ }));
37125
+ setGlobalSegment(resetGlobalSegment);
37126
+ setCurrentSegments(resetCurrentSegments);
37127
+ }, [originalSegments, getAudioDuration]);
37128
+ const spacebarHandlerRef = reactExports.useRef(handleSpacebar);
37129
+ spacebarHandlerRef.current = handleSpacebar;
37130
+ reactExports.useEffect(() => {
37131
+ if (open && isReplaced) {
37132
+ console.log("ReplaceAllLyricsModal - Setting up spacebar handler");
37133
+ const handleKeyEvent = (e) => {
37134
+ if (e.code === "Space") {
37135
+ console.log("ReplaceAllLyricsModal - Spacebar captured in modal");
37136
+ e.preventDefault();
37137
+ e.stopPropagation();
37138
+ spacebarHandlerRef.current(e);
37139
+ }
37140
+ };
37141
+ setModalSpacebarHandler(() => handleKeyEvent);
37142
+ return () => {
37143
+ if (!open) {
37144
+ console.log("ReplaceAllLyricsModal - Clearing spacebar handler");
37145
+ setModalSpacebarHandler(void 0);
37146
+ }
37147
+ };
37148
+ } else if (open) {
37149
+ setModalSpacebarHandler(void 0);
37150
+ }
37151
+ }, [open, isReplaced, setModalSpacebarHandler]);
37152
+ const timeRange = reactExports.useMemo(() => {
37153
+ const audioDuration = getAudioDuration();
37154
+ return { start: 0, end: audioDuration };
37155
+ }, [getAudioDuration]);
37156
+ const segmentProgressProps = reactExports.useMemo(() => ({
37157
+ currentSegments,
37158
+ globalSegment,
37159
+ syncWordIndex
37160
+ }), [currentSegments, globalSegment, syncWordIndex]);
37161
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
37162
+ Dialog,
37163
+ {
37164
+ open,
37165
+ onClose: handleClose,
37166
+ maxWidth: false,
37167
+ fullWidth: true,
37168
+ onKeyDown: (e) => {
37169
+ if (e.key === "Enter" && !e.shiftKey && isReplaced) {
37170
+ e.preventDefault();
37171
+ handleSave();
37172
+ }
37173
+ },
37174
+ PaperProps: {
37175
+ sx: {
37176
+ height: "90vh",
37177
+ margin: "5vh 2vh",
37178
+ maxWidth: "calc(100vw - 4vh)",
37179
+ width: "calc(100vw - 4vh)"
37180
+ }
37181
+ },
37182
+ children: [
37183
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogTitle, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
37184
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { flex: 1 }, children: "Replace All Lyrics" }),
37185
+ /* @__PURE__ */ jsxRuntimeExports.jsx(IconButton, { onClick: handleClose, sx: { ml: "auto" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(CloseIcon, {}) })
37186
+ ] }),
37187
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
37188
+ DialogContent,
37189
+ {
37190
+ dividers: true,
37191
+ sx: {
37192
+ display: "flex",
37193
+ flexDirection: "column",
37194
+ flexGrow: 1,
37195
+ overflow: "hidden"
37196
+ },
37197
+ children: !isReplaced ? (
37198
+ // Step 1: Input new lyrics
37199
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 2, height: "100%" }, children: [
37200
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", gutterBottom: true, children: "Paste your new lyrics below:" }),
37201
+ /* @__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." }),
37202
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", gap: 2, mb: 2 }, children: [
37203
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
37204
+ Button,
37205
+ {
37206
+ variant: "outlined",
37207
+ onClick: handlePasteFromClipboard,
37208
+ startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(ContentPasteIcon, {}),
37209
+ size: "small",
37210
+ children: "Paste from Clipboard"
37211
+ }
37212
+ ),
37213
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Typography, { variant: "body2", sx: {
37214
+ alignSelf: "center",
37215
+ color: "text.secondary",
37216
+ fontWeight: "medium"
37217
+ }, children: [
37218
+ parseInfo.lines,
37219
+ " lines, ",
37220
+ parseInfo.words,
37221
+ " words"
37222
+ ] })
37223
+ ] }),
37224
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
37225
+ TextField,
37226
+ {
37227
+ multiline: true,
37228
+ rows: 15,
37229
+ value: inputText,
37230
+ onChange: (e) => setInputText(e.target.value),
37231
+ placeholder: "Paste your lyrics here...\nEach line will become a segment\nWords will be separated by spaces",
37232
+ sx: {
37233
+ flexGrow: 1,
37234
+ "& .MuiInputBase-root": {
37235
+ height: "100%",
37236
+ alignItems: "flex-start"
37237
+ }
37238
+ }
37239
+ }
37240
+ ),
37241
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { display: "flex", justifyContent: "flex-end", gap: 2 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
37242
+ Button,
37243
+ {
37244
+ variant: "contained",
37245
+ onClick: processLyrics,
37246
+ disabled: !inputText.trim(),
37247
+ startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(AutoFixHighIcon, {}),
37248
+ children: "Replace All Lyrics"
37249
+ }
37250
+ ) })
37251
+ ] })
37252
+ ) : (
37253
+ // Step 2: Manual sync interface
37254
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", flexDirection: "column", height: "100%", gap: 2 }, children: [
37255
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Paper, { sx: { p: 2, bgcolor: "background.paper" }, children: [
37256
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", gutterBottom: true, children: "Lyrics Replaced Successfully" }),
37257
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Typography, { variant: "body2", color: "text.secondary", children: [
37258
+ "Created ",
37259
+ currentSegments.length,
37260
+ " segments with ",
37261
+ globalSegment == null ? void 0 : globalSegment.words.length,
37262
+ " words total. Use Manual Sync to set timing for all words."
37263
+ ] })
37264
+ ] }),
37265
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Divider, {}),
37266
+ globalSegment && /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", gap: 2, flexGrow: 1, minHeight: 0 }, children: [
37267
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { flex: 2, display: "flex", flexDirection: "column", minHeight: 0 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
37268
+ EditTimelineSection,
37269
+ {
37270
+ words: globalSegment.words,
37271
+ startTime: timeRange.start,
37272
+ endTime: timeRange.end,
37273
+ originalStartTime: 0,
37274
+ originalEndTime: getAudioDuration(),
37275
+ currentStartTime: globalSegment.start_time,
37276
+ currentEndTime: globalSegment.end_time,
37277
+ currentTime,
37278
+ isManualSyncing,
37279
+ syncWordIndex,
37280
+ isSpacebarPressed,
37281
+ onWordUpdate: handleWordUpdate,
37282
+ onUnsyncWord: handleUnsyncWord,
37283
+ onPlaySegment,
37284
+ onStopAudio: () => {
37285
+ if (window.toggleAudioPlayback && window.isAudioPlaying) {
37286
+ window.toggleAudioPlayback();
37287
+ }
37288
+ },
37289
+ startManualSync,
37290
+ pauseManualSync,
37291
+ resumeManualSync,
37292
+ isPaused,
37293
+ isGlobal: true,
37294
+ defaultZoomLevel: 10,
37295
+ isReplaceAllMode: true
37296
+ }
37297
+ ) }),
37298
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
37299
+ SegmentProgressPanel,
37300
+ {
37301
+ currentSegments: segmentProgressProps.currentSegments,
37302
+ globalSegment: segmentProgressProps.globalSegment,
37303
+ syncWordIndex: segmentProgressProps.syncWordIndex
37304
+ }
37305
+ )
37306
+ ] })
37307
+ ] })
37308
+ )
37309
+ }
37310
+ ),
37311
+ /* @__PURE__ */ jsxRuntimeExports.jsx(DialogActions, { children: isReplaced && /* @__PURE__ */ jsxRuntimeExports.jsx(
37312
+ EditActionBar,
37313
+ {
37314
+ onReset: handleReset,
37315
+ onClose: handleClose,
37316
+ onSave: handleSave,
37317
+ editedSegment: globalSegment,
37318
+ isGlobal: true
37319
+ }
37320
+ ) })
37321
+ ]
37322
+ }
37323
+ );
37324
+ }
37325
+ const SegmentProgressItem = reactExports.memo(({
37326
+ segment,
37327
+ index,
37328
+ isActive
37329
+ }) => {
37330
+ const wordsWithTiming = segment.words.filter(
37331
+ (w) => w.start_time !== null && w.end_time !== null
37332
+ ).length;
37333
+ const totalWords = segment.words.length;
37334
+ const isComplete = wordsWithTiming === totalWords;
37335
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
37336
+ Paper,
37337
+ {
37338
+ ref: isActive ? (el) => {
37339
+ if (el) {
37340
+ el.scrollIntoView({
37341
+ behavior: "smooth",
37342
+ block: "center"
37343
+ });
37344
+ }
37345
+ } : void 0,
37346
+ sx: {
37347
+ p: 1,
37348
+ mb: 1,
37349
+ bgcolor: isActive ? "primary.light" : isComplete ? "success.light" : "background.paper",
37350
+ border: isActive ? 2 : 1,
37351
+ borderColor: isActive ? "primary.main" : "divider"
37352
+ },
37353
+ children: [
37354
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
37355
+ Typography,
37356
+ {
37357
+ variant: "body2",
37358
+ sx: {
37359
+ fontWeight: isActive ? "bold" : "normal",
37360
+ mb: 0.5
37361
+ },
37362
+ children: [
37363
+ "Segment ",
37364
+ index + 1,
37365
+ ": ",
37366
+ segment.text.slice(0, 50),
37367
+ segment.text.length > 50 ? "..." : ""
37368
+ ]
37369
+ }
37370
+ ),
37371
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Typography, { variant: "caption", color: "text.secondary", children: [
37372
+ wordsWithTiming,
37373
+ "/",
37374
+ totalWords,
37375
+ " words synced",
37376
+ isComplete && segment.start_time !== null && segment.end_time !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
37377
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
37378
+ segment.start_time.toFixed(2),
37379
+ "s - ",
37380
+ segment.end_time.toFixed(2),
37381
+ "s"
37382
+ ] })
37383
+ ] })
37384
+ ]
37385
+ },
37386
+ segment.id
37387
+ );
37388
+ });
37389
+ const SegmentProgressPanel = reactExports.memo(({
37390
+ currentSegments,
37391
+ globalSegment,
37392
+ syncWordIndex
37393
+ }) => {
37394
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { flex: 1, display: "flex", flexDirection: "column", minHeight: 0 }, children: [
37395
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", gutterBottom: true, children: "Segment Progress" }),
37396
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: {
37397
+ overflow: "auto",
37398
+ flexGrow: 1,
37399
+ border: 1,
37400
+ borderColor: "divider",
37401
+ borderRadius: 1,
37402
+ p: 1
37403
+ }, children: currentSegments.map((segment, index) => {
37404
+ const isActive = Boolean(
37405
+ globalSegment && syncWordIndex >= 0 && syncWordIndex < globalSegment.words.length && globalSegment.words[syncWordIndex] && segment.words.some((w) => w.id === globalSegment.words[syncWordIndex].id)
37406
+ );
37407
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
37408
+ SegmentProgressItem,
37409
+ {
37410
+ segment,
37411
+ index,
37412
+ isActive
37413
+ },
37414
+ segment.id
37415
+ );
37416
+ }) })
37417
+ ] });
37418
+ });
36624
37419
  const generateStorageKey = (data) => {
36625
37420
  var _a;
36626
37421
  const text = ((_a = data.original_segments[0]) == null ? void 0 : _a.text) || "";
@@ -36888,11 +37683,13 @@ function AudioPlayer({ apiClient, onTimeUpdate, audioHash }) {
36888
37683
  const win = window;
36889
37684
  win.seekAndPlayAudio = seekAndPlay;
36890
37685
  win.toggleAudioPlayback = togglePlayback;
37686
+ win.getAudioDuration = () => duration2;
36891
37687
  return () => {
36892
37688
  delete win.seekAndPlayAudio;
36893
37689
  delete win.toggleAudioPlayback;
37690
+ delete win.getAudioDuration;
36894
37691
  };
36895
- }, [apiClient, togglePlayback]);
37692
+ }, [apiClient, togglePlayback, duration2]);
36896
37693
  if (!apiClient) return null;
36897
37694
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: {
36898
37695
  display: "flex",
@@ -37873,11 +38670,7 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37873
38670
  const [isShiftPressed, setIsShiftPressed] = reactExports.useState(false);
37874
38671
  const [isCtrlPressed, setIsCtrlPressed] = reactExports.useState(false);
37875
38672
  const [editModalSegment, setEditModalSegment] = reactExports.useState(null);
37876
- const [isEditAllModalOpen, setIsEditAllModalOpen] = reactExports.useState(false);
37877
- const [globalEditSegment, setGlobalEditSegment] = reactExports.useState(null);
37878
- const [originalGlobalSegment, setOriginalGlobalSegment] = reactExports.useState(null);
37879
- const [originalTranscribedGlobalSegment, setOriginalTranscribedGlobalSegment] = reactExports.useState(null);
37880
- const [isLoadingGlobalEdit, setIsLoadingGlobalEdit] = reactExports.useState(false);
38673
+ const [isReplaceAllLyricsModalOpen, setIsReplaceAllLyricsModalOpen] = reactExports.useState(false);
37881
38674
  const [isReviewModalOpen, setIsReviewModalOpen] = reactExports.useState(false);
37882
38675
  const [currentAudioTime, setCurrentAudioTime] = reactExports.useState(0);
37883
38676
  const [isUpdatingHandlers, setIsUpdatingHandlers] = reactExports.useState(false);
@@ -37938,10 +38731,10 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37938
38731
  }, [setIsShiftPressed, setIsCtrlPressed, isAnyModalOpen]);
37939
38732
  reactExports.useEffect(() => {
37940
38733
  const modalOpen = Boolean(
37941
- modalContent || editModalSegment || isReviewModalOpen || isAddLyricsModalOpen || isFindReplaceModalOpen || isEditAllModalOpen || isTimingOffsetModalOpen
38734
+ modalContent || editModalSegment || isReviewModalOpen || isAddLyricsModalOpen || isFindReplaceModalOpen || isReplaceAllLyricsModalOpen || isTimingOffsetModalOpen
37942
38735
  );
37943
38736
  setIsAnyModalOpen(modalOpen);
37944
- }, [modalContent, editModalSegment, isReviewModalOpen, isAddLyricsModalOpen, isFindReplaceModalOpen, isEditAllModalOpen, isTimingOffsetModalOpen]);
38737
+ }, [modalContent, editModalSegment, isReviewModalOpen, isAddLyricsModalOpen, isFindReplaceModalOpen, isReplaceAllLyricsModalOpen, isTimingOffsetModalOpen]);
37945
38738
  const effectiveMode = isCtrlPressed ? "delete_word" : isShiftPressed ? "highlight" : interactionMode;
37946
38739
  const handleFlash = reactExports.useCallback((type, info) => {
37947
38740
  setFlashingType(null);
@@ -38201,144 +38994,21 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
38201
38994
  const newData = findAndReplace(data, findText, replaceText, options);
38202
38995
  updateDataWithHistory(newData, "find/replace");
38203
38996
  };
38204
- const handleEditAll = reactExports.useCallback(() => {
38205
- console.log("EditAll - Starting process");
38206
- const placeholderSegment = {
38207
- id: "loading-placeholder",
38208
- words: [],
38209
- text: "",
38210
- start_time: 0,
38211
- end_time: 1
38212
- };
38213
- setGlobalEditSegment(placeholderSegment);
38214
- setOriginalGlobalSegment(placeholderSegment);
38215
- setIsLoadingGlobalEdit(true);
38216
- console.log("EditAll - Set loading state to true");
38217
- setIsEditAllModalOpen(true);
38218
- console.log("EditAll - Set modal open to true");
38219
- requestAnimationFrame(() => {
38220
- console.log("EditAll - Inside requestAnimationFrame");
38221
- setTimeout(() => {
38222
- var _a, _b, _c, _d;
38223
- console.log("EditAll - Inside setTimeout, starting data processing");
38224
- try {
38225
- console.time("EditAll - Data processing");
38226
- const allWords = data.corrected_segments.flatMap((segment) => segment.words);
38227
- console.log(`EditAll - Collected ${allWords.length} words from all segments`);
38228
- const sortedWords = [...allWords].sort((a, b) => {
38229
- const aTime = a.start_time ?? 0;
38230
- const bTime = b.start_time ?? 0;
38231
- return aTime - bTime;
38232
- });
38233
- console.log("EditAll - Sorted words by start time");
38234
- const globalSegment = {
38235
- id: "global-edit",
38236
- words: sortedWords,
38237
- text: sortedWords.map((w) => w.text).join(" "),
38238
- start_time: ((_a = sortedWords[0]) == null ? void 0 : _a.start_time) ?? null,
38239
- end_time: ((_b = sortedWords[sortedWords.length - 1]) == null ? void 0 : _b.end_time) ?? null
38240
- };
38241
- console.log("EditAll - Created global segment");
38242
- setGlobalEditSegment(globalSegment);
38243
- console.log("EditAll - Set global edit segment");
38244
- setOriginalGlobalSegment(JSON.parse(JSON.stringify(globalSegment)));
38245
- console.log("EditAll - Set original global segment");
38246
- if (originalData.original_segments) {
38247
- console.log("EditAll - Processing original segments for Un-Correct functionality");
38248
- const originalWords = originalData.original_segments.flatMap((segment) => segment.words);
38249
- console.log(`EditAll - Collected ${originalWords.length} words from original segments`);
38250
- const sortedOriginalWords = [...originalWords].sort((a, b) => {
38251
- const aTime = a.start_time ?? 0;
38252
- const bTime = b.start_time ?? 0;
38253
- return aTime - bTime;
38254
- });
38255
- console.log("EditAll - Sorted original words by start time");
38256
- const originalTranscribedGlobal = {
38257
- id: "original-transcribed-global",
38258
- words: sortedOriginalWords,
38259
- text: sortedOriginalWords.map((w) => w.text).join(" "),
38260
- start_time: ((_c = sortedOriginalWords[0]) == null ? void 0 : _c.start_time) ?? null,
38261
- end_time: ((_d = sortedOriginalWords[sortedOriginalWords.length - 1]) == null ? void 0 : _d.end_time) ?? null
38262
- };
38263
- console.log("EditAll - Created original transcribed global segment");
38264
- setOriginalTranscribedGlobalSegment(originalTranscribedGlobal);
38265
- console.log("EditAll - Set original transcribed global segment");
38266
- } else {
38267
- setOriginalTranscribedGlobalSegment(null);
38268
- console.log("EditAll - No original segments found, set original transcribed global segment to null");
38269
- }
38270
- console.timeEnd("EditAll - Data processing");
38271
- } catch (error) {
38272
- console.error("Error preparing global edit data:", error);
38273
- } finally {
38274
- console.log("EditAll - Finished processing, setting loading state to false");
38275
- setIsLoadingGlobalEdit(false);
38276
- }
38277
- }, 100);
38278
- });
38279
- }, [data.corrected_segments, originalData.original_segments]);
38280
- const handleSaveGlobalEdit = reactExports.useCallback((updatedSegment) => {
38281
- var _a;
38282
- console.log("Global Edit - Saving with new approach:", {
38283
- updatedSegmentId: updatedSegment.id,
38284
- wordCount: updatedSegment.words.length,
38285
- originalSegmentCount: data.corrected_segments.length,
38286
- originalTotalWordCount: data.corrected_segments.reduce((count, segment) => count + segment.words.length, 0)
38287
- });
38288
- const updatedWords = updatedSegment.words;
38289
- const updatedSegments = [];
38290
- let wordIndex = 0;
38291
- for (const segment of data.corrected_segments) {
38292
- const originalWordCount = segment.words.length;
38293
- const segmentWords = [];
38294
- const endIndex = Math.min(wordIndex + originalWordCount, updatedWords.length);
38295
- for (let i = wordIndex; i < endIndex; i++) {
38296
- segmentWords.push(updatedWords[i]);
38297
- }
38298
- wordIndex = endIndex;
38299
- if (segmentWords.length > 0) {
38300
- const validStartTimes = segmentWords.map((w) => w.start_time).filter((t) => t !== null);
38301
- const validEndTimes = segmentWords.map((w) => w.end_time).filter((t) => t !== null);
38302
- const segmentStartTime = validStartTimes.length > 0 ? Math.min(...validStartTimes) : null;
38303
- const segmentEndTime = validEndTimes.length > 0 ? Math.max(...validEndTimes) : null;
38304
- updatedSegments.push({
38305
- ...segment,
38306
- words: segmentWords,
38307
- text: segmentWords.map((w) => w.text).join(" "),
38308
- start_time: segmentStartTime,
38309
- end_time: segmentEndTime
38310
- });
38311
- }
38312
- }
38313
- if (wordIndex < updatedWords.length) {
38314
- const remainingWords = updatedWords.slice(wordIndex);
38315
- const lastSegment = updatedSegments[updatedSegments.length - 1];
38316
- const combinedWords = [...lastSegment.words, ...remainingWords];
38317
- const validStartTimes = combinedWords.map((w) => w.start_time).filter((t) => t !== null);
38318
- const validEndTimes = combinedWords.map((w) => w.end_time).filter((t) => t !== null);
38319
- const segmentStartTime = validStartTimes.length > 0 ? Math.min(...validStartTimes) : null;
38320
- const segmentEndTime = validEndTimes.length > 0 ? Math.max(...validEndTimes) : null;
38321
- updatedSegments[updatedSegments.length - 1] = {
38322
- ...lastSegment,
38323
- words: combinedWords,
38324
- text: combinedWords.map((w) => w.text).join(" "),
38325
- start_time: segmentStartTime,
38326
- end_time: segmentEndTime
38327
- };
38328
- }
38329
- console.log("Global Edit - Updated Segments with new approach:", {
38330
- segmentCount: updatedSegments.length,
38331
- firstSegmentWordCount: (_a = updatedSegments[0]) == null ? void 0 : _a.words.length,
38332
- totalWordCount: updatedSegments.reduce((count, segment) => count + segment.words.length, 0),
38333
- originalTotalWordCount: data.corrected_segments.reduce((count, segment) => count + segment.words.length, 0)
38997
+ const handleReplaceAllLyrics = reactExports.useCallback(() => {
38998
+ console.log("ReplaceAllLyrics - Opening modal");
38999
+ setIsReplaceAllLyricsModalOpen(true);
39000
+ }, []);
39001
+ const handleSaveReplaceAllLyrics = reactExports.useCallback((newSegments) => {
39002
+ console.log("ReplaceAllLyrics - Saving new segments:", {
39003
+ segmentCount: newSegments.length,
39004
+ totalWords: newSegments.reduce((count, segment) => count + segment.words.length, 0)
38334
39005
  });
38335
39006
  const newData = {
38336
39007
  ...data,
38337
- corrected_segments: updatedSegments
39008
+ corrected_segments: newSegments
38338
39009
  };
38339
- updateDataWithHistory(newData, "edit all");
38340
- setIsEditAllModalOpen(false);
38341
- setGlobalEditSegment(null);
39010
+ updateDataWithHistory(newData, "replace all lyrics");
39011
+ setIsReplaceAllLyricsModalOpen(false);
38342
39012
  }, [data, updateDataWithHistory]);
38343
39013
  const handleUndo = reactExports.useCallback(() => {
38344
39014
  if (historyIndex > 0) {
@@ -38399,7 +39069,7 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
38399
39069
  isUpdatingHandlers,
38400
39070
  onHandlerClick: handleHandlerClick,
38401
39071
  onFindReplace: () => setIsFindReplaceModalOpen(true),
38402
- onEditAll: handleEditAll,
39072
+ onEditAll: handleReplaceAllLyrics,
38403
39073
  onTimingOffset: handleOpenTimingOffsetModal,
38404
39074
  timingOffsetMs,
38405
39075
  onUndo: handleUndo,
@@ -38477,29 +39147,6 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
38477
39147
  }
38478
39148
  ) })
38479
39149
  ] }),
38480
- /* @__PURE__ */ jsxRuntimeExports.jsx(
38481
- EditModal,
38482
- {
38483
- open: isEditAllModalOpen,
38484
- onClose: () => {
38485
- setIsEditAllModalOpen(false);
38486
- setGlobalEditSegment(null);
38487
- setOriginalGlobalSegment(null);
38488
- setOriginalTranscribedGlobalSegment(null);
38489
- handleSetModalSpacebarHandler(void 0);
38490
- },
38491
- segment: globalEditSegment ? timingOffsetMs !== 0 ? applyOffsetToSegment(globalEditSegment, timingOffsetMs) : globalEditSegment : null,
38492
- segmentIndex: null,
38493
- originalSegment: originalGlobalSegment ? timingOffsetMs !== 0 ? applyOffsetToSegment(originalGlobalSegment, timingOffsetMs) : originalGlobalSegment : null,
38494
- onSave: handleSaveGlobalEdit,
38495
- onPlaySegment: handlePlaySegment,
38496
- currentTime: currentAudioTime,
38497
- setModalSpacebarHandler: handleSetModalSpacebarHandler,
38498
- originalTranscribedSegment: originalTranscribedGlobalSegment ? timingOffsetMs !== 0 ? applyOffsetToSegment(originalTranscribedGlobalSegment, timingOffsetMs) : originalTranscribedGlobalSegment : null,
38499
- isGlobal: true,
38500
- isLoading: isLoadingGlobalEdit
38501
- }
38502
- ),
38503
39150
  /* @__PURE__ */ jsxRuntimeExports.jsx(
38504
39151
  EditModal,
38505
39152
  {
@@ -38566,6 +39213,17 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
38566
39213
  currentOffset: timingOffsetMs,
38567
39214
  onApply: handleApplyTimingOffset
38568
39215
  }
39216
+ ),
39217
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
39218
+ ReplaceAllLyricsModal,
39219
+ {
39220
+ open: isReplaceAllLyricsModalOpen,
39221
+ onClose: () => setIsReplaceAllLyricsModalOpen(false),
39222
+ onSave: handleSaveReplaceAllLyrics,
39223
+ onPlaySegment: handlePlaySegment,
39224
+ currentTime: currentAudioTime,
39225
+ setModalSpacebarHandler: handleSetModalSpacebarHandler
39226
+ }
38569
39227
  )
38570
39228
  ] });
38571
39229
  }
@@ -38915,7 +39573,7 @@ const theme = createTheme({
38915
39573
  spacing: (factor) => `${0.6 * factor}rem`
38916
39574
  // Further reduced from 0.8 * factor
38917
39575
  });
38918
- const version = "0.68.0";
39576
+ const version = "0.69.0";
38919
39577
  const packageJson = {
38920
39578
  version
38921
39579
  };
@@ -38926,4 +39584,4 @@ ReactDOM$1.createRoot(document.getElementById("root")).render(
38926
39584
  /* @__PURE__ */ jsxRuntimeExports.jsx(App, {})
38927
39585
  ] })
38928
39586
  );
38929
- //# sourceMappingURL=index-D7BQUJXK.js.map
39587
+ //# sourceMappingURL=index-izP9z1oB.js.map