feat: re-export rework, delete profile, shared path protection

Re-export dialog now offers two modes: keep section length (adjust clip
count) or keep clip count (adjust section length). Files shared with
other profiles are preserved during re-export. Vid folder is resolved
before DB deletions to reuse existing folders. Add delete profile option
with confirmation dialog. Profile duplication now copies all tables
including processed exports.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-28 14:57:54 +02:00
parent cb805c5bda
commit e972c7a2ae
2 changed files with 159 additions and 27 deletions
+64 -6
View File
@@ -284,13 +284,31 @@ class ProcessedDB:
).fetchone()
return dict(row) if row else None
def delete_by_output_path(self, output_path: str) -> None:
def delete_by_output_path(self, output_path: str, profile: str = "") -> None:
if not self._enabled:
return
with self._lock:
self._con.execute("DELETE FROM processed WHERE output_path = ?", (output_path,))
if profile:
self._con.execute(
"DELETE FROM processed WHERE output_path = ? AND profile = ?",
(output_path, profile),
)
else:
self._con.execute(
"DELETE FROM processed WHERE output_path = ?", (output_path,),
)
self._con.commit()
def is_path_used_by_other_profiles(self, output_path: str, profile: str) -> bool:
"""Return True if *output_path* is referenced by any profile other than *profile*."""
if not self._enabled:
return False
row = self._con.execute(
"SELECT 1 FROM processed WHERE output_path = ? AND profile != ? LIMIT 1",
(output_path, profile),
).fetchone()
return row is not None
def get_group(self, output_path: str, profile: str = "") -> list[str]:
"""Return all output_paths sharing the same (filename, start_time, profile) as *output_path*."""
if not self._enabled:
@@ -416,16 +434,33 @@ class ProcessedDB:
return [r[0] for r in rows]
def duplicate_profile(self, src: str, dst: str) -> int:
"""Copy scan_results, hard_negatives, and hidden_files from *src* to *dst*.
"""Copy all profile data from *src* to *dst*.
Exports (processed) are NOT copied because their output_paths
reference files in the source profile's folder structure.
Returns total number of rows copied.
Copies processed (exports), scan_results, hard_negatives, and
hidden_files. Returns total number of rows copied.
"""
if not self._enabled or src == dst:
return 0
total = 0
with self._lock:
# processed (exports)
rows = self._con.execute(
"SELECT filename, start_time, output_path, label, category,"
" short_side, portrait_ratio, crop_center, format,"
" clip_count, spread, source_path, scan_export, processed_at"
" FROM processed WHERE profile = ?", (src,),
).fetchall()
for r in rows:
self._con.execute(
"INSERT INTO processed"
" (filename, start_time, output_path, label, category,"
" short_side, portrait_ratio, crop_center, format,"
" clip_count, spread, profile, source_path, scan_export,"
" processed_at)"
" VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(*r[:11], dst, *r[11:]),
)
total += len(rows)
# scan_results
rows = self._con.execute(
"SELECT filename, model, start_time, end_time, score,"
@@ -468,6 +503,29 @@ class ProcessedDB:
self._con.commit()
return total
def count_profile_rows(self, profile: str) -> int:
"""Return total number of rows across all tables for *profile*."""
if not self._enabled:
return 0
n = 0
for table in ("processed", "scan_results", "hard_negatives", "hidden_files"):
row = self._con.execute(
f"SELECT COUNT(*) FROM {table} WHERE profile = ?", (profile,),
).fetchone()
n += row[0] if row else 0
return n
def delete_profile(self, profile: str) -> None:
"""Delete all rows for *profile* from every table."""
if not self._enabled:
return
with self._lock:
for table in ("processed", "scan_results", "hard_negatives", "hidden_files"):
self._con.execute(
f"DELETE FROM {table} WHERE profile = ?", (profile,),
)
self._con.commit()
def get_all_export_paths(self, profile: str = "default") -> list[str]:
"""Return all unique output_path values for a given profile."""
if not self._enabled: