feat: manual exports use vid number with m{N} tag

Manual clips now follow the same pattern as scan exports:
clip_003_m1_0.mp4 (manual) vs clip_003_a1_0.mp4 (auto-scan).
The clip number matches the vid folder number.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-20 09:42:48 +02:00
parent aaf405dd3d
commit b249705506
2 changed files with 25 additions and 18 deletions
+8 -2
View File
@@ -24,17 +24,23 @@ def _log(*args) -> None:
print(f"[8-cut {ts}]", *args, file=sys.stderr) print(f"[8-cut {ts}]", *args, file=sys.stderr)
def build_export_path(folder: str, basename: str, counter: int, sub: int | None = None) -> str: def build_export_path(folder: str, basename: str, counter: int,
sub: int | None = None, tag: str | None = None) -> str:
"""Build clip output path. *folder* should be the vid folder (e.g. .../mp4/vid_001).""" """Build clip output path. *folder* should be the vid folder (e.g. .../mp4/vid_001)."""
name = f"{basename}_{counter:03d}" name = f"{basename}_{counter:03d}"
if tag is not None:
name = f"{name}_{tag}"
if sub is not None: if sub is not None:
name = f"{name}_{sub}" name = f"{name}_{sub}"
return os.path.join(folder, name + ".mp4") return os.path.join(folder, name + ".mp4")
def build_sequence_dir(folder: str, basename: str, counter: int, sub: int | None = None) -> str: def build_sequence_dir(folder: str, basename: str, counter: int,
sub: int | None = None, tag: str | None = None) -> str:
"""Build WebP sequence output dir. *folder* should be the vid folder.""" """Build WebP sequence output dir. *folder* should be the vid folder."""
name = f"{basename}_{counter:03d}" name = f"{basename}_{counter:03d}"
if tag is not None:
name = f"{name}_{tag}"
if sub is not None: if sub is not None:
name = f"{name}_{sub}" name = f"{name}_{sub}"
return os.path.join(folder, name) return os.path.join(folder, name)
+17 -16
View File
@@ -4743,18 +4743,17 @@ class MainWindow(QMainWindow):
name = self._txt_name.text() or "clip" name = self._txt_name.text() or "clip"
vid_name = self._get_vid_folder(folder) vid_name = self._get_vid_folder(folder)
vid_folder = os.path.join(folder, vid_name) vid_folder = os.path.join(folder, vid_name)
# Start from the highest counter the DB knows about, so we never vid_num = int(vid_name.split("_")[-1])
# reuse a counter if the folder is temporarily empty / unmounted. # Find next manual export number (m1, m2, ...)
db_max = self._db.get_max_counter(vid_folder, name) if self._db else 0 self._export_counter = 1
self._export_counter = max(1, db_max + 1)
# Then also skip any files that exist on disk.
while True: while True:
test_path = build_export_path(vid_folder, name, self._export_counter, sub=0) tag = f"m{self._export_counter}"
test_path = build_export_path(vid_folder, name, vid_num, sub=0, tag=tag)
if not os.path.exists(test_path): 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}_{vid_num:03d}_m{self._export_counter}"
if n == 1: if n == 1:
self._lbl_next.setText(f"{vid_name}/{base}_0") self._lbl_next.setText(f"{vid_name}/{base}_0")
else: else:
@@ -4820,27 +4819,29 @@ class MainWindow(QMainWindow):
vid_name = self._get_vid_folder(folder) vid_name = self._get_vid_folder(folder)
vid_folder = os.path.join(folder, vid_name) vid_folder = os.path.join(folder, vid_name)
os.makedirs(vid_folder, exist_ok=True) os.makedirs(vid_folder, exist_ok=True)
# For subprofile exports, calculate counter independently. vid_num = int(vid_name.split("_")[-1])
# For subprofile exports, calculate manual counter independently.
if folder_suffix: if folder_suffix:
db_max_sub = self._db.get_max_counter(vid_folder, name) if self._db else 0 manual_n = 1
counter = max(1, db_max_sub + 1)
while True: while True:
tag = f"m{manual_n}"
if image_sequence: if image_sequence:
p = build_sequence_dir(vid_folder, name, counter, sub=0) p = build_sequence_dir(vid_folder, name, vid_num, sub=0, tag=tag)
else: else:
p = build_export_path(vid_folder, name, counter, sub=0) p = build_export_path(vid_folder, name, vid_num, sub=0, tag=tag)
if not os.path.exists(p): if not os.path.exists(p):
break break
counter += 1 manual_n += 1
else: else:
counter = self._export_counter manual_n = self._export_counter
tag = f"m{manual_n}"
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(vid_folder, name, counter, sub=sub) out = build_sequence_dir(vid_folder, name, vid_num, sub=sub, tag=tag)
else: else:
out = build_export_path(vid_folder, name, counter, sub=sub) out = build_export_path(vid_folder, name, vid_num, sub=sub, tag=tag)
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).