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:
@@ -3155,7 +3155,7 @@ class MainWindow(QMainWindow):
|
|||||||
self._scan_panel.export_requested.connect(self._on_scan_export)
|
self._scan_panel.export_requested.connect(self._on_scan_export)
|
||||||
self._scan_panel.negatives_requested.connect(self._on_scan_negatives)
|
self._scan_panel.negatives_requested.connect(self._on_scan_negatives)
|
||||||
self._scan_panel.negatives_removed.connect(self._on_scan_negatives_removed)
|
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._scan_panel.regions_edited.connect(self._on_scan_regions_edited)
|
||||||
self._sld_threshold.valueChanged.connect(self._on_threshold_changed)
|
self._sld_threshold.valueChanged.connect(self._on_threshold_changed)
|
||||||
|
|
||||||
@@ -3485,6 +3485,9 @@ class MainWindow(QMainWindow):
|
|||||||
self._preview_timer.start()
|
self._preview_timer.start()
|
||||||
# Unlock scrollbar after Qt finishes processing layout events from load.
|
# 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.
|
# Run DB fuzzy match off the main thread — can be slow on large databases.
|
||||||
filename = os.path.basename(self._file_path)
|
filename = os.path.basename(self._file_path)
|
||||||
self._db_worker = _DBWorker(self._db, filename, self._profile)
|
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._lbl_time.setText(f"{format_time(next_pos)} / {format_time(self._mpv.get_duration())}")
|
||||||
self._update_next_label()
|
self._update_next_label()
|
||||||
self._preview_timer.start()
|
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
|
return
|
||||||
self._overwrite_path = output_path
|
self._overwrite_path = output_path
|
||||||
self._overwrite_group = self._db.get_group(output_path)
|
self._overwrite_group = self._db.get_group(output_path)
|
||||||
n = len(self._overwrite_group)
|
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:
|
if n > 1:
|
||||||
self._lbl_next.setText(f"↺ {group_dir} ({n} clips)")
|
self._lbl_next.setText(f"↺ {group_label} ({n} clips)")
|
||||||
self._btn_delete.setText(f"Delete {group_dir} ({n})")
|
self._btn_delete.setText(f"Delete {group_label} ({n})")
|
||||||
else:
|
else:
|
||||||
self._lbl_next.setText(f"↺ {os.path.basename(output_path)}")
|
self._lbl_next.setText(f"↺ {os.path.basename(output_path)}")
|
||||||
self._btn_delete.setText(f"Delete {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":
|
if ratio != "Off":
|
||||||
self._mpv.set_crop_overlay(_RATIOS[ratio], self._crop_center)
|
self._mpv.set_crop_overlay(_RATIOS[ratio], self._crop_center)
|
||||||
self._show_status(
|
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:
|
def _on_marker_deselected(self) -> None:
|
||||||
@@ -3632,9 +3638,10 @@ class MainWindow(QMainWindow):
|
|||||||
if not all_paths:
|
if not all_paths:
|
||||||
all_paths = [target]
|
all_paths = [target]
|
||||||
n = len(all_paths)
|
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:
|
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:
|
else:
|
||||||
msg = f"Delete {os.path.basename(target)} from disk and database?"
|
msg = f"Delete {os.path.basename(target)} from disk and database?"
|
||||||
reply = QMessageBox.question(
|
reply = QMessageBox.question(
|
||||||
@@ -3654,13 +3661,6 @@ class MainWindow(QMainWindow):
|
|||||||
elif os.path.exists(path):
|
elif os.path.exists(path):
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
remove_clip_annotation(folder, 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
|
# Remove all from DB
|
||||||
self._db.delete_group(target)
|
self._db.delete_group(target)
|
||||||
# Reset state
|
# Reset state
|
||||||
@@ -3674,7 +3674,7 @@ class MainWindow(QMainWindow):
|
|||||||
self._update_next_label()
|
self._update_next_label()
|
||||||
self._refresh_markers()
|
self._refresh_markers()
|
||||||
self._refresh_playlist_checks()
|
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:
|
def _on_portrait_ratio_changed(self, text: str) -> None:
|
||||||
ratio = None if text == "Off" else text
|
ratio = None if text == "Off" else text
|
||||||
@@ -4537,26 +4537,24 @@ class MainWindow(QMainWindow):
|
|||||||
fmt = self._cmb_format.currentText()
|
fmt = self._cmb_format.currentText()
|
||||||
image_sequence = fmt == "WebP sequence"
|
image_sequence = fmt == "WebP sequence"
|
||||||
ext = "" if image_sequence else ".mp4"
|
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
|
# Find next counter within the vid folder
|
||||||
counter = 1
|
db_max = self._db.get_max_counter(vid_folder, name) if self._db else 0
|
||||||
while True:
|
counter = max(1, db_max + 1)
|
||||||
group_dir = os.path.join(folder, f"{name}_{counter:03d}")
|
while os.path.exists(build_export_path(vid_folder, name, counter, sub=0)):
|
||||||
if not os.path.exists(group_dir):
|
|
||||||
break
|
|
||||||
counter += 1
|
counter += 1
|
||||||
|
|
||||||
# One folder per area group, numbered sequentially
|
# Clips go flat inside vid folder, numbered sequentially
|
||||||
jobs = []
|
jobs = []
|
||||||
self._auto_export_positions = []
|
self._auto_export_positions = []
|
||||||
for area_idx, group in enumerate(groups):
|
for area_idx, group in enumerate(groups):
|
||||||
group_name = f"{name}_{counter:03d}"
|
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):
|
for sub, start_t in enumerate(group):
|
||||||
fname = f"{group_name}_a{area_idx + 1}_{sub}{ext}"
|
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))
|
jobs.append((start_t, out, None, 0.5))
|
||||||
self._auto_export_positions.append((start_t, out))
|
self._auto_export_positions.append((start_t, out))
|
||||||
counter += 1
|
counter += 1
|
||||||
@@ -4668,26 +4666,35 @@ class MainWindow(QMainWindow):
|
|||||||
def _reset_counter(self):
|
def _reset_counter(self):
|
||||||
self._update_next_label()
|
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):
|
def _update_next_label(self):
|
||||||
folder = self._txt_folder.text()
|
folder = self._txt_folder.text()
|
||||||
name = self._txt_name.text() or "clip"
|
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
|
# Start from the highest counter the DB knows about, so we never
|
||||||
# reuse a counter if the folder is temporarily empty / unmounted.
|
# 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)
|
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:
|
while True:
|
||||||
group_dir = os.path.join(folder, f"{name}_{self._export_counter:03d}")
|
test_path = build_export_path(vid_folder, name, self._export_counter, sub=0)
|
||||||
if not os.path.exists(group_dir):
|
if not os.path.exists(test_path):
|
||||||
break
|
break
|
||||||
self._export_counter += 1
|
self._export_counter += 1
|
||||||
n = self._spn_clips.value()
|
n = self._spn_clips.value()
|
||||||
base = f"{name}_{self._export_counter:03d}"
|
base = f"{name}_{self._export_counter:03d}"
|
||||||
if n == 1:
|
if n == 1:
|
||||||
self._lbl_next.setText(f"→ {base}_0")
|
self._lbl_next.setText(f"→ {vid_name}/{base}_0")
|
||||||
else:
|
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 = ""):
|
def _on_export(self, _=None, folder_suffix: str = ""):
|
||||||
if not self._file_path:
|
if not self._file_path:
|
||||||
@@ -4746,30 +4753,30 @@ class MainWindow(QMainWindow):
|
|||||||
else:
|
else:
|
||||||
name = self._txt_name.text() or "clip"
|
name = self._txt_name.text() or "clip"
|
||||||
n_clips = self._spn_clips.value()
|
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.
|
# For subprofile exports, calculate counter independently.
|
||||||
if folder_suffix:
|
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)
|
counter = max(1, db_max_sub + 1)
|
||||||
while True:
|
while True:
|
||||||
if image_sequence:
|
if image_sequence:
|
||||||
p = build_sequence_dir(folder, name, counter, sub=0)
|
p = build_sequence_dir(vid_folder, name, counter, sub=0)
|
||||||
else:
|
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):
|
if not os.path.exists(p):
|
||||||
break
|
break
|
||||||
counter += 1
|
counter += 1
|
||||||
else:
|
else:
|
||||||
counter = self._export_counter
|
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 = []
|
jobs = []
|
||||||
for sub in range(n_clips):
|
for sub in range(n_clips):
|
||||||
start = self._cursor + sub * spread
|
start = self._cursor + sub * spread
|
||||||
if image_sequence:
|
if image_sequence:
|
||||||
out = build_sequence_dir(folder, name, counter, sub=sub)
|
out = build_sequence_dir(vid_folder, name, counter, sub=sub)
|
||||||
else:
|
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))
|
jobs.append((start, out, base_ratio, base_center))
|
||||||
|
|
||||||
# Apply crop keyframes (or fall back to base state).
|
# Apply crop keyframes (or fall back to base state).
|
||||||
|
|||||||
Reference in New Issue
Block a user