Fix multiple bugs from code analysis rounds

- Fix RIFE warp() grid cache: move backwarp_tenGrid to module level so it persists across calls
- Fix FILM partial download: download to .tmp file then atomically rename, validate file size
- Fix fid_to_pos orphan fallthrough: use .get(fid) with None check instead of defaulting to 0
- Fix commonpath ValueError: guard against mixed-drive paths on Windows
- Fix ON CONFLICT clause in direct_transition_settings to match UNIQUE constraint
- Remove dead save_folder_type_override method from database
- Fix subprocess resource leaks in video.py: add try/except/kill around proc loops
- Fix concat file handle leak: wrap writes in try/finally
- Fix format conversion in transition export: normalize extension comparison
- Fix session restore loop: blockSignals during destination path changes
- Fix missing folder handling: keep as placeholders instead of skipping
- Fix fid=0 skip guard in _restore_files_from_session

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 21:31:27 +01:00
parent a6461a2ce8
commit 546e93ceb1
5 changed files with 149 additions and 105 deletions
+82 -27
View File
@@ -1597,7 +1597,9 @@ class SequenceLinkerUI(QWidget):
fid_to_pos = {fid: i for i, fid in enumerate(self._folder_ids)}
files_by_idx: dict[int, list[str]] = {}
for source_dir, filename, folder_idx, file_idx, fid in files:
pi = fid_to_pos.get(fid, 0)
pi = fid_to_pos.get(fid)
if pi is None:
continue
if pi not in files_by_idx:
files_by_idx[pi] = []
files_by_idx[pi].append(filename)
@@ -2438,7 +2440,10 @@ class SequenceLinkerUI(QWidget):
if len(self.source_folders) > 1:
paths = [str(f) for f in self.source_folders]
# Find common prefix
common_prefix = os.path.commonpath(paths) if paths else ""
try:
common_prefix = os.path.commonpath(paths) if paths else ""
except ValueError:
common_prefix = ""
# Only use prefix if it's a meaningful directory (not just "/")
if len(common_prefix) <= 1:
common_prefix = ""
@@ -2805,7 +2810,10 @@ class SequenceLinkerUI(QWidget):
fid = item.data(0, Qt.ItemDataRole.UserRole + 2) or 0
removed_by_fid.setdefault(fid, set()).add(filename)
# For each fid, convert edge removals into trim adjustments
# For each fid, convert edge removals into trim adjustments.
# Trim operates on the full (untrimmed) file list, so we must count
# how many trimmed-list positions to consume from each edge,
# including any already-removed files that fall within that span.
for fid, filenames in removed_by_fid.items():
idx = self._folder_ids.index(fid) if fid in self._folder_ids else -1
if idx < 0:
@@ -2819,37 +2827,69 @@ class SequenceLinkerUI(QWidget):
if not effective:
continue
# Count contiguous removals from start
start_bump = 0
# Count contiguous removals from start of effective list
start_count = 0
for f in effective:
if f in filenames:
start_bump += 1
start_count += 1
else:
break
# Count contiguous removals from end
end_bump = 0
# Count contiguous removals from end of effective list
end_count = 0
for f in reversed(effective):
if f in filenames:
end_bump += 1
end_count += 1
else:
break
# Avoid double-counting if all files are removed
if start_bump + end_bump > len(effective):
start_bump = len(effective)
end_bump = 0
if start_count + end_count > len(effective):
start_count = len(effective)
end_count = 0
edge_files = set()
if start_bump > 0:
edge_files.update(effective[:start_bump])
if end_bump > 0:
edge_files.update(effective[-end_bump:])
start_trim_bump = 0
end_trim_bump = 0
if start_count > 0:
edge_files.update(effective[:start_count])
# Walk the trimmed list from the start, counting positions
# until we've covered all start edge files (including
# already-removed files that fall within that span)
covered = 0
for f in trimmed:
start_trim_bump += 1
if f in edge_files:
covered += 1
if covered == start_count:
break
# Also clear already-removed files that now fall within trim
for f in trimmed[:start_trim_bump]:
if f in already_removed:
already_removed.discard(f)
if end_count > 0:
edge_files.update(effective[-end_count:])
covered = 0
for f in reversed(trimmed):
end_trim_bump += 1
if f in edge_files:
covered += 1
if covered == end_count:
break
for f in trimmed[len(trimmed) - end_trim_bump:]:
if f in already_removed:
already_removed.discard(f)
# Adjust trim settings for edge removals
if start_bump > 0 or end_bump > 0:
if start_trim_bump > 0 or end_trim_bump > 0:
trim_start, trim_end = self._folder_trim_settings.get(fid, (0, 0))
self._folder_trim_settings[fid] = (trim_start + start_bump, trim_end + end_bump)
self._folder_trim_settings[fid] = (trim_start + start_trim_bump, trim_end + end_trim_bump)
# Clean up empty removed sets
if fid in self._removed_files and not self._removed_files[fid]:
del self._removed_files[fid]
# Remaining removals (middle) go to _removed_files
middle_files = filenames - edge_files
@@ -2947,7 +2987,9 @@ class SequenceLinkerUI(QWidget):
self, "Select Destination Folder", start_dir
)
if path:
self.dst_path.blockSignals(True)
self._add_to_path_history(self.dst_path, path)
self.dst_path.blockSignals(False)
self.last_directory = str(Path(path).parent)
self._try_resume_session(path)
@@ -3092,10 +3134,10 @@ class SequenceLinkerUI(QWidget):
seen_resolved: set[str] = set()
for position_idx, (folder_str, folder_type, trim_start, trim_end) in enumerate(ordered_folders):
folder_path = Path(folder_str)
# Resolve symlinks for consistent path matching
if not folder_path.exists():
continue
folder_path = folder_path.resolve()
# Resolve symlinks for consistent path matching;
# keep missing folders so they appear as placeholders
if folder_path.exists():
folder_path = folder_path.resolve()
resolved_str = str(folder_path)
fid = self._allocate_folder_id()
self.source_folders.append(folder_path)
@@ -3234,6 +3276,8 @@ class SequenceLinkerUI(QWidget):
for i, folder_path in enumerate(self.source_folders):
fid = self._folder_ids[i]
if not folder_path.is_dir():
continue
folder_str = str(folder_path)
if folder_str not in exported_by_folder:
continue
@@ -3585,8 +3629,13 @@ class SequenceLinkerUI(QWidget):
if session is None:
return
# Set destination path to match the session
# Set destination path to match the session — block signals to
# prevent _on_destination_changed from triggering a redundant
# _try_resume_session (which would load the *latest* session for
# this dest, not the one the user picked).
self.dst_path.blockSignals(True)
self._add_to_path_history(self.dst_path, session.destination)
self.dst_path.blockSignals(False)
self._restore_session_by_id(session)
@@ -4667,6 +4716,8 @@ class SequenceLinkerUI(QWidget):
if sf == folder_path:
fid = self._folder_ids[i]
break
if fid == 0:
continue # Folder not in current source_folders, skip
# Sort files by their sequence index
sorted_files = sorted(file_list, key=lambda x: x[0])
@@ -5751,7 +5802,9 @@ class SequenceLinkerUI(QWidget):
fid_to_pos = {fid: i for i, fid in enumerate(self._folder_ids)}
files_by_idx: dict[int, list[str]] = {}
for source_dir, filename, folder_idx, file_idx, fid in files:
pi = fid_to_pos.get(fid, 0)
pi = fid_to_pos.get(fid)
if pi is None:
continue
if pi not in files_by_idx:
files_by_idx[pi] = []
files_by_idx[pi].append(filename)
@@ -5956,11 +6009,13 @@ class SequenceLinkerUI(QWidget):
if in_range:
out_fmt = settings.output_format.lower()
src_ext = source_path.suffix.lower()
needs_convert = src_ext != f".{out_fmt}" and src_ext not in (
'.jpg' if out_fmt == 'jpeg' else '', '.jpeg' if out_fmt == 'jpg' else ''
needs_convert = not (
src_ext == f".{out_fmt}"
or (out_fmt == 'jpeg' and src_ext == '.jpg')
or (out_fmt == 'jpg' and src_ext == '.jpeg')
)
ext = f".{out_fmt}" if needs_convert else source_path.suffix
ext = f".{out_fmt}" if needs_convert else source_path.suffix.lower()
link_name = f"seq_{output_seq:05d}{ext}"
link_path = trans_dest / link_name
planned_names.add(link_name)