fix: refresh timeline scan regions when switching model tabs

tab_changed was only updating export count, not the timeline overlay.
Now calls _on_scan_regions_edited which refreshes both.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-19 16:16:12 +02:00
parent 7d6fee9df1
commit bd345abca2
+49 -42
View File
@@ -3155,7 +3155,7 @@ class MainWindow(QMainWindow):
self._scan_panel.export_requested.connect(self._on_scan_export)
self._scan_panel.negatives_requested.connect(self._on_scan_negatives)
self._scan_panel.negatives_removed.connect(self._on_scan_negatives_removed)
self._scan_panel.tab_changed.connect(self._update_scan_export_count)
self._scan_panel.tab_changed.connect(self._on_scan_regions_edited)
self._scan_panel.regions_edited.connect(self._on_scan_regions_edited)
self._sld_threshold.valueChanged.connect(self._on_threshold_changed)
@@ -3485,6 +3485,9 @@ class MainWindow(QMainWindow):
self._preview_timer.start()
# Unlock scrollbar after Qt finishes processing layout events from load.
# Recalculate vid folder & counter for the new video.
self._update_next_label()
# Run DB fuzzy match off the main thread — can be slow on large databases.
filename = os.path.basename(self._file_path)
self._db_worker = _DBWorker(self._db, filename, self._profile)
@@ -3564,15 +3567,18 @@ class MainWindow(QMainWindow):
self._lbl_time.setText(f"{format_time(next_pos)} / {format_time(self._mpv.get_duration())}")
self._update_next_label()
self._preview_timer.start()
self._show_status(f"Cursor → end of {os.path.basename(os.path.dirname(output_path))}", 3000)
stem = os.path.splitext(os.path.basename(output_path))[0]
group_label = stem.rsplit("_", 1)[0]
self._show_status(f"Cursor → end of {group_label}", 3000)
return
self._overwrite_path = output_path
self._overwrite_group = self._db.get_group(output_path)
n = len(self._overwrite_group)
group_dir = os.path.basename(os.path.dirname(output_path))
stem = os.path.splitext(os.path.basename(output_path))[0]
group_label = stem.rsplit("_", 1)[0]
if n > 1:
self._lbl_next.setText(f"{group_dir} ({n} clips)")
self._btn_delete.setText(f"Delete {group_dir} ({n})")
self._lbl_next.setText(f"{group_label} ({n} clips)")
self._btn_delete.setText(f"Delete {group_label} ({n})")
else:
self._lbl_next.setText(f"{os.path.basename(output_path)}")
self._btn_delete.setText(f"Delete {os.path.basename(output_path)}")
@@ -3609,7 +3615,7 @@ class MainWindow(QMainWindow):
if ratio != "Off":
self._mpv.set_crop_overlay(_RATIOS[ratio], self._crop_center)
self._show_status(
f"Overwrite mode: {group_dir} ({n} clip{'s' if n != 1 else ''}) — export to replace", 5000
f"Overwrite mode: {group_label} ({n} clip{'s' if n != 1 else ''}) — export to replace", 5000
)
def _on_marker_deselected(self) -> None:
@@ -3632,9 +3638,10 @@ class MainWindow(QMainWindow):
if not all_paths:
all_paths = [target]
n = len(all_paths)
group_dir = os.path.basename(os.path.dirname(all_paths[0]))
stem = os.path.splitext(os.path.basename(all_paths[0]))[0]
group_label = stem.rsplit("_", 1)[0]
if n > 1:
msg = f"Delete {n} clips in {group_dir} from disk and database?"
msg = f"Delete {n} clips in {group_label} from disk and database?"
else:
msg = f"Delete {os.path.basename(target)} from disk and database?"
reply = QMessageBox.question(
@@ -3654,13 +3661,6 @@ class MainWindow(QMainWindow):
elif os.path.exists(path):
os.remove(path)
remove_clip_annotation(folder, path)
# Remove empty group directory
parent = os.path.dirname(all_paths[0])
try:
if os.path.isdir(parent) and not os.listdir(parent):
os.rmdir(parent)
except OSError:
pass
# Remove all from DB
self._db.delete_group(target)
# Reset state
@@ -3674,7 +3674,7 @@ class MainWindow(QMainWindow):
self._update_next_label()
self._refresh_markers()
self._refresh_playlist_checks()
self._show_status(f"Deleted {n} clip{'s' if n != 1 else ''}: {group_dir}")
self._show_status(f"Deleted {n} clip{'s' if n != 1 else ''}: {group_label}")
def _on_portrait_ratio_changed(self, text: str) -> None:
ratio = None if text == "Off" else text
@@ -4537,26 +4537,24 @@ class MainWindow(QMainWindow):
fmt = self._cmb_format.currentText()
image_sequence = fmt == "WebP sequence"
ext = "" if image_sequence else ".mp4"
os.makedirs(folder, exist_ok=True)
vid_name = self._get_vid_folder(folder)
vid_folder = os.path.join(folder, vid_name)
os.makedirs(vid_folder, exist_ok=True)
# Find next counter following the normal order
counter = 1
while True:
group_dir = os.path.join(folder, f"{name}_{counter:03d}")
if not os.path.exists(group_dir):
break
# Find next counter within the vid folder
db_max = self._db.get_max_counter(vid_folder, name) if self._db else 0
counter = max(1, db_max + 1)
while os.path.exists(build_export_path(vid_folder, name, counter, sub=0)):
counter += 1
# One folder per area group, numbered sequentially
# Clips go flat inside vid folder, numbered sequentially
jobs = []
self._auto_export_positions = []
for area_idx, group in enumerate(groups):
group_name = f"{name}_{counter:03d}"
group_dir = os.path.join(folder, group_name)
os.makedirs(group_dir, exist_ok=True)
for sub, start_t in enumerate(group):
fname = f"{group_name}_a{area_idx + 1}_{sub}{ext}"
out = os.path.join(group_dir, fname)
out = os.path.join(vid_folder, fname)
jobs.append((start_t, out, None, 0.5))
self._auto_export_positions.append((start_t, out))
counter += 1
@@ -4668,26 +4666,35 @@ class MainWindow(QMainWindow):
def _reset_counter(self):
self._update_next_label()
def _get_vid_folder(self, folder: str) -> str:
"""Return vid_NNN folder name for the currently loaded video."""
if not self._file_path or not self._db:
return "vid_001"
return self._db.get_vid_folder(
os.path.basename(self._file_path), self._profile, folder,
)
def _update_next_label(self):
folder = self._txt_folder.text()
name = self._txt_name.text() or "clip"
is_seq = self._cmb_format.currentText() == "WebP sequence"
vid_name = self._get_vid_folder(folder)
vid_folder = os.path.join(folder, vid_name)
# Start from the highest counter the DB knows about, so we never
# reuse a counter if the folder is temporarily empty / unmounted.
db_max = self._db.get_max_counter(folder, name) if self._db else 0
db_max = self._db.get_max_counter(vid_folder, name) if self._db else 0
self._export_counter = max(1, db_max + 1)
# Then also skip any directories that exist on disk.
# Then also skip any files that exist on disk.
while True:
group_dir = os.path.join(folder, f"{name}_{self._export_counter:03d}")
if not os.path.exists(group_dir):
test_path = build_export_path(vid_folder, name, self._export_counter, sub=0)
if not os.path.exists(test_path):
break
self._export_counter += 1
n = self._spn_clips.value()
base = f"{name}_{self._export_counter:03d}"
if n == 1:
self._lbl_next.setText(f"{base}_0")
self._lbl_next.setText(f"{vid_name}/{base}_0")
else:
self._lbl_next.setText(f"{base}_0..{n - 1}")
self._lbl_next.setText(f"{vid_name}/{base}_0..{n - 1}")
def _on_export(self, _=None, folder_suffix: str = ""):
if not self._file_path:
@@ -4746,30 +4753,30 @@ class MainWindow(QMainWindow):
else:
name = self._txt_name.text() or "clip"
n_clips = self._spn_clips.value()
vid_name = self._get_vid_folder(folder)
vid_folder = os.path.join(folder, vid_name)
os.makedirs(vid_folder, exist_ok=True)
# For subprofile exports, calculate counter independently.
if folder_suffix:
db_max_sub = self._db.get_max_counter(folder, name) if self._db else 0
db_max_sub = self._db.get_max_counter(vid_folder, name) if self._db else 0
counter = max(1, db_max_sub + 1)
while True:
if image_sequence:
p = build_sequence_dir(folder, name, counter, sub=0)
p = build_sequence_dir(vid_folder, name, counter, sub=0)
else:
p = build_export_path(folder, name, counter, sub=0)
p = build_export_path(vid_folder, name, counter, sub=0)
if not os.path.exists(p):
break
counter += 1
else:
counter = self._export_counter
# Create the group subfolder
group_dir = os.path.join(folder, f"{name}_{counter:03d}")
os.makedirs(group_dir, exist_ok=True)
jobs = []
for sub in range(n_clips):
start = self._cursor + sub * spread
if image_sequence:
out = build_sequence_dir(folder, name, counter, sub=sub)
out = build_sequence_dir(vid_folder, name, counter, sub=sub)
else:
out = build_export_path(folder, name, counter, sub=sub)
out = build_export_path(vid_folder, name, counter, sub=sub)
jobs.append((start, out, base_ratio, base_center))
# Apply crop keyframes (or fall back to base state).