fix: three audio scan bugs — signal shadow, re-entrancy, S-key jump
1. Rename ScanWorker.finished → scan_done to stop shadowing QThread.finished. Previously, cancelled scans leaked the QThread because the custom signal was never emitted. 2. Block signals on combobox reset in _on_scan_ref_changed to prevent re-entrant call when user cancels folder dialog. 3. Merge overlapping scan regions into clusters before S-key navigation so it jumps to the next distinct match, not 1s forward through overlapping windows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -187,7 +187,7 @@ class FrameGrabber(QThread):
|
|||||||
|
|
||||||
class ScanWorker(QThread):
|
class ScanWorker(QThread):
|
||||||
"""Runs audio similarity scan off the main thread."""
|
"""Runs audio similarity scan off the main thread."""
|
||||||
finished = pyqtSignal(list) # emits list of (start, end, score)
|
scan_done = pyqtSignal(list) # emits list of (start, end, score)
|
||||||
error = pyqtSignal(str)
|
error = pyqtSignal(str)
|
||||||
progress = pyqtSignal(str) # status message
|
progress = pyqtSignal(str) # status message
|
||||||
|
|
||||||
@@ -220,7 +220,7 @@ class ScanWorker(QThread):
|
|||||||
cancel_flag=self,
|
cancel_flag=self,
|
||||||
)
|
)
|
||||||
if not self._cancel:
|
if not self._cancel:
|
||||||
self.finished.emit(regions)
|
self.scan_done.emit(regions)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if not self._cancel:
|
if not self._cancel:
|
||||||
self.error.emit(str(e))
|
self.error.emit(str(e))
|
||||||
@@ -2508,19 +2508,21 @@ class MainWindow(QMainWindow):
|
|||||||
if folder:
|
if folder:
|
||||||
self._scan_folder = folder
|
self._scan_folder = folder
|
||||||
else:
|
else:
|
||||||
|
self._cmb_scan_ref.blockSignals(True)
|
||||||
self._cmb_scan_ref.setCurrentIndex(0)
|
self._cmb_scan_ref.setCurrentIndex(0)
|
||||||
|
self._cmb_scan_ref.blockSignals(False)
|
||||||
|
|
||||||
def _cleanup_scan_worker(self) -> None:
|
def _cleanup_scan_worker(self) -> None:
|
||||||
"""Disconnect signals and schedule deletion of old scan worker."""
|
"""Disconnect signals and schedule deletion of old scan worker."""
|
||||||
if self._scan_worker is not None:
|
if self._scan_worker is not None:
|
||||||
try:
|
try:
|
||||||
self._scan_worker.finished.disconnect()
|
self._scan_worker.scan_done.disconnect()
|
||||||
self._scan_worker.error.disconnect()
|
self._scan_worker.error.disconnect()
|
||||||
self._scan_worker.progress.disconnect()
|
self._scan_worker.progress.disconnect()
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass # already disconnected
|
pass # already disconnected
|
||||||
if self._scan_worker.isRunning():
|
if self._scan_worker.isRunning():
|
||||||
# Let the thread finish naturally; deleteLater when done
|
# QThread.finished fires when run() returns, even on cancel
|
||||||
self._scan_worker.finished.connect(self._scan_worker.deleteLater)
|
self._scan_worker.finished.connect(self._scan_worker.deleteLater)
|
||||||
else:
|
else:
|
||||||
self._scan_worker.deleteLater()
|
self._scan_worker.deleteLater()
|
||||||
@@ -2566,7 +2568,7 @@ class MainWindow(QMainWindow):
|
|||||||
self._show_status(f"Scanning with {len(clip_paths)} reference clips...")
|
self._show_status(f"Scanning with {len(clip_paths)} reference clips...")
|
||||||
|
|
||||||
self._scan_worker = ScanWorker(self._file_path, clip_paths, mode, threshold)
|
self._scan_worker = ScanWorker(self._file_path, clip_paths, mode, threshold)
|
||||||
self._scan_worker.finished.connect(self._on_scan_done)
|
self._scan_worker.scan_done.connect(self._on_scan_done)
|
||||||
self._scan_worker.error.connect(self._on_scan_error)
|
self._scan_worker.error.connect(self._on_scan_error)
|
||||||
self._scan_worker.progress.connect(self._show_status)
|
self._scan_worker.progress.connect(self._show_status)
|
||||||
self._scan_worker.start()
|
self._scan_worker.start()
|
||||||
@@ -2587,12 +2589,20 @@ class MainWindow(QMainWindow):
|
|||||||
regions = sorted(self._timeline._scan_regions, key=lambda r: r[0])
|
regions = sorted(self._timeline._scan_regions, key=lambda r: r[0])
|
||||||
if not regions:
|
if not regions:
|
||||||
return
|
return
|
||||||
for (start, _end, _score) in regions:
|
# Merge overlapping regions into clusters so S jumps past each group
|
||||||
|
clusters: list[tuple[float, float]] = []
|
||||||
|
for (start, end, _score) in regions:
|
||||||
|
if clusters and start <= clusters[-1][1]:
|
||||||
|
clusters[-1] = (clusters[-1][0], max(clusters[-1][1], end))
|
||||||
|
else:
|
||||||
|
clusters.append((start, end))
|
||||||
|
# Jump to the start of the next cluster after cursor
|
||||||
|
for (start, _end) in clusters:
|
||||||
if start > self._cursor + 0.1:
|
if start > self._cursor + 0.1:
|
||||||
self._step_cursor(start - self._cursor)
|
self._step_cursor(start - self._cursor)
|
||||||
return
|
return
|
||||||
# Wrap to first region
|
# Wrap to first cluster
|
||||||
self._step_cursor(regions[0][0] - self._cursor)
|
self._step_cursor(clusters[0][0] - self._cursor)
|
||||||
|
|
||||||
# --- Export ---
|
# --- Export ---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user