fix: 6 bugs — profile isolation, export stashing, auto-negative guard
- Stash profile and crop_center at export start for async safety - Scope get_group/delete_group by profile to prevent cross-profile leaks - Guard auto-negative sampling when no markers exist (prevents flood) - Wrap ffmpeg subprocess with clean timeout error message - Fix scan-all panel reload to use stashed profile, not live value - Remove dead warnings import Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+9
-6
@@ -3,7 +3,6 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import warnings
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from .paths import _bin, _log
|
from .paths import _bin, _log
|
||||||
@@ -22,7 +21,10 @@ def _load_audio_ffmpeg(path: str, sr: int = _SR) -> np.ndarray:
|
|||||||
"-loglevel", "error",
|
"-loglevel", "error",
|
||||||
"pipe:1",
|
"pipe:1",
|
||||||
]
|
]
|
||||||
proc = subprocess.run(cmd, capture_output=True, timeout=300)
|
try:
|
||||||
|
proc = subprocess.run(cmd, capture_output=True, timeout=300)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
raise RuntimeError(f"ffmpeg timed out (300s) on {os.path.basename(path)}")
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
raise RuntimeError(f"ffmpeg failed: {proc.stderr.decode().strip()}")
|
raise RuntimeError(f"ffmpeg failed: {proc.stderr.decode().strip()}")
|
||||||
return np.frombuffer(proc.stdout, dtype=np.float32)
|
return np.frombuffer(proc.stdout, dtype=np.float32)
|
||||||
@@ -240,11 +242,12 @@ def _extract_w2v_targeted(y: np.ndarray, sr: int, gt_intense: list[float],
|
|||||||
# Don't let manual negatives overlap with positives
|
# Don't let manual negatives overlap with positives
|
||||||
manual_neg_times -= pos_times
|
manual_neg_times -= pos_times
|
||||||
|
|
||||||
# Auto negative windows: every 4s, far from any marker (skip if margin <= 0)
|
# Auto negative windows: every 4s, far from any marker (skip if margin <= 0 or no markers)
|
||||||
neg_times = set()
|
neg_times = set()
|
||||||
for t in range(0, int(duration - _WINDOW), 4):
|
if all_gt and neg_margin > 0:
|
||||||
if neg_margin > 0 and min((abs(t - g) for g in all_gt), default=9999) > neg_margin:
|
for t in range(0, int(duration - _WINDOW), 4):
|
||||||
neg_times.add(t)
|
if min(abs(t - g) for g in all_gt) > neg_margin:
|
||||||
|
neg_times.add(t)
|
||||||
|
|
||||||
all_times = sorted(pos_times | neg_times | manual_neg_times)
|
all_times = sorted(pos_times | neg_times | manual_neg_times)
|
||||||
# Filter out windows that go past the end
|
# Filter out windows that go past the end
|
||||||
|
|||||||
+17
-13
@@ -159,43 +159,47 @@ class ProcessedDB:
|
|||||||
self._con.execute("DELETE FROM processed WHERE output_path = ?", (output_path,))
|
self._con.execute("DELETE FROM processed WHERE output_path = ?", (output_path,))
|
||||||
self._con.commit()
|
self._con.commit()
|
||||||
|
|
||||||
def get_group(self, output_path: str) -> list[str]:
|
def get_group(self, output_path: str, profile: str = "") -> list[str]:
|
||||||
"""Return all output_paths sharing the same (filename, start_time) as *output_path*."""
|
"""Return all output_paths sharing the same (filename, start_time, profile) as *output_path*."""
|
||||||
if not self._enabled:
|
if not self._enabled:
|
||||||
return []
|
return []
|
||||||
row = self._con.execute(
|
row = self._con.execute(
|
||||||
"SELECT filename, start_time FROM processed WHERE output_path = ?",
|
"SELECT filename, start_time, profile FROM processed WHERE output_path = ?",
|
||||||
(output_path,),
|
(output_path,),
|
||||||
).fetchone()
|
).fetchone()
|
||||||
if not row:
|
if not row:
|
||||||
return []
|
return []
|
||||||
|
filename, start_time, row_profile = row
|
||||||
|
p = profile or row_profile
|
||||||
rows = self._con.execute(
|
rows = self._con.execute(
|
||||||
"SELECT output_path FROM processed"
|
"SELECT output_path FROM processed"
|
||||||
" WHERE filename = ? AND start_time = ? ORDER BY output_path",
|
" WHERE filename = ? AND start_time = ? AND profile = ? ORDER BY output_path",
|
||||||
(row[0], row[1]),
|
(filename, start_time, p),
|
||||||
).fetchall()
|
).fetchall()
|
||||||
return [r[0] for r in rows]
|
return [r[0] for r in rows]
|
||||||
|
|
||||||
def delete_group(self, output_path: str) -> list[str]:
|
def delete_group(self, output_path: str, profile: str = "") -> list[str]:
|
||||||
"""Delete all rows sharing the same (filename, start_time) as *output_path*.
|
"""Delete all rows sharing the same (filename, start_time, profile) as *output_path*.
|
||||||
Returns list of deleted output_paths."""
|
Returns list of deleted output_paths."""
|
||||||
if not self._enabled:
|
if not self._enabled:
|
||||||
return []
|
return []
|
||||||
with self._lock:
|
with self._lock:
|
||||||
row = self._con.execute(
|
row = self._con.execute(
|
||||||
"SELECT filename, start_time FROM processed WHERE output_path = ?",
|
"SELECT filename, start_time, profile FROM processed WHERE output_path = ?",
|
||||||
(output_path,),
|
(output_path,),
|
||||||
).fetchone()
|
).fetchone()
|
||||||
if not row:
|
if not row:
|
||||||
return []
|
return []
|
||||||
filename, start_time = row
|
filename, start_time, row_profile = row
|
||||||
|
p = profile or row_profile
|
||||||
paths = [r[0] for r in self._con.execute(
|
paths = [r[0] for r in self._con.execute(
|
||||||
"SELECT output_path FROM processed WHERE filename = ? AND start_time = ?",
|
"SELECT output_path FROM processed"
|
||||||
(filename, start_time),
|
" WHERE filename = ? AND start_time = ? AND profile = ?",
|
||||||
|
(filename, start_time, p),
|
||||||
).fetchall()]
|
).fetchall()]
|
||||||
self._con.execute(
|
self._con.execute(
|
||||||
"DELETE FROM processed WHERE filename = ? AND start_time = ?",
|
"DELETE FROM processed WHERE filename = ? AND start_time = ? AND profile = ?",
|
||||||
(filename, start_time),
|
(filename, start_time, p),
|
||||||
)
|
)
|
||||||
self._con.commit()
|
self._con.commit()
|
||||||
return paths
|
return paths
|
||||||
|
|||||||
@@ -3068,7 +3068,7 @@ class MainWindow(QMainWindow):
|
|||||||
filename, profile, model_label, regions)
|
filename, profile, model_label, regions)
|
||||||
# If this is the currently loaded file, update the panel
|
# If this is the currently loaded file, update the panel
|
||||||
if self._file_path and os.path.basename(self._file_path) == filename:
|
if self._file_path and os.path.basename(self._file_path) == filename:
|
||||||
self._scan_panel.load_for_file(filename, self._profile)
|
self._scan_panel.load_for_file(filename, profile)
|
||||||
self._timeline.set_scan_regions(regions)
|
self._timeline.set_scan_regions(regions)
|
||||||
self._scan_all_next()
|
self._scan_all_next()
|
||||||
|
|
||||||
@@ -3303,11 +3303,13 @@ class MainWindow(QMainWindow):
|
|||||||
short_side = self._spn_resize.value() or None
|
short_side = self._spn_resize.value() or None
|
||||||
self._export_short_side = short_side
|
self._export_short_side = short_side
|
||||||
self._export_portrait = "Off"
|
self._export_portrait = "Off"
|
||||||
|
self._export_crop_center = 0.5
|
||||||
self._export_format = fmt
|
self._export_format = fmt
|
||||||
self._export_clip_count = 1
|
self._export_clip_count = 1
|
||||||
self._export_spread = 0
|
self._export_spread = 0
|
||||||
self._export_folder = folder
|
self._export_folder = folder
|
||||||
self._export_folder_suffix = ""
|
self._export_folder_suffix = ""
|
||||||
|
self._export_profile = self._profile
|
||||||
|
|
||||||
hw_on = self._chk_hw.isChecked() and self._hw_encoders
|
hw_on = self._chk_hw.isChecked() and self._hw_encoders
|
||||||
encoder = self._hw_encoders[0] if hw_on else "libx264"
|
encoder = self._hw_encoders[0] if hw_on else "libx264"
|
||||||
@@ -3354,7 +3356,7 @@ class MainWindow(QMainWindow):
|
|||||||
fmt=self._export_format,
|
fmt=self._export_format,
|
||||||
clip_count=1,
|
clip_count=1,
|
||||||
spread=0,
|
spread=0,
|
||||||
profile=self._profile,
|
profile=self._export_profile,
|
||||||
source_path=self._file_path,
|
source_path=self._file_path,
|
||||||
)
|
)
|
||||||
upsert_clip_annotation(self._export_folder, path, label)
|
upsert_clip_annotation(self._export_folder, path, label)
|
||||||
@@ -3544,11 +3546,13 @@ class MainWindow(QMainWindow):
|
|||||||
self._export_cursor = self._cursor
|
self._export_cursor = self._cursor
|
||||||
self._export_short_side = short_side
|
self._export_short_side = short_side
|
||||||
self._export_portrait = self._cmb_portrait.currentText()
|
self._export_portrait = self._cmb_portrait.currentText()
|
||||||
|
self._export_crop_center = self._crop_center
|
||||||
self._export_format = fmt
|
self._export_format = fmt
|
||||||
self._export_clip_count = self._spn_clips.value()
|
self._export_clip_count = self._spn_clips.value()
|
||||||
self._export_spread = self._spn_spread.value()
|
self._export_spread = self._spn_spread.value()
|
||||||
self._export_folder = folder
|
self._export_folder = folder
|
||||||
self._export_folder_suffix = folder_suffix
|
self._export_folder_suffix = folder_suffix
|
||||||
|
self._export_profile = self._profile
|
||||||
|
|
||||||
self._btn_export.setEnabled(False)
|
self._btn_export.setEnabled(False)
|
||||||
self._set_subprofile_btns_enabled(False)
|
self._set_subprofile_btns_enabled(False)
|
||||||
@@ -3595,11 +3599,11 @@ class MainWindow(QMainWindow):
|
|||||||
category=category,
|
category=category,
|
||||||
short_side=self._export_short_side,
|
short_side=self._export_short_side,
|
||||||
portrait_ratio=portrait,
|
portrait_ratio=portrait,
|
||||||
crop_center=self._crop_center,
|
crop_center=self._export_crop_center,
|
||||||
fmt=self._export_format,
|
fmt=self._export_format,
|
||||||
clip_count=self._export_clip_count,
|
clip_count=self._export_clip_count,
|
||||||
spread=self._export_spread,
|
spread=self._export_spread,
|
||||||
profile=self._profile,
|
profile=self._export_profile,
|
||||||
source_path=self._file_path,
|
source_path=self._file_path,
|
||||||
)
|
)
|
||||||
upsert_clip_annotation(self._export_folder, path, label)
|
upsert_clip_annotation(self._export_folder, path, label)
|
||||||
|
|||||||
Reference in New Issue
Block a user