diff --git a/core/audio_scan.py b/core/audio_scan.py index 57d4152..b42e4b7 100644 --- a/core/audio_scan.py +++ b/core/audio_scan.py @@ -3,7 +3,6 @@ import hashlib import os import subprocess -import warnings import numpy as np from .paths import _bin, _log @@ -22,7 +21,10 @@ def _load_audio_ffmpeg(path: str, sr: int = _SR) -> np.ndarray: "-loglevel", "error", "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: raise RuntimeError(f"ffmpeg failed: {proc.stderr.decode().strip()}") 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 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() - for t in range(0, int(duration - _WINDOW), 4): - if neg_margin > 0 and min((abs(t - g) for g in all_gt), default=9999) > neg_margin: - neg_times.add(t) + if all_gt and neg_margin > 0: + for t in range(0, int(duration - _WINDOW), 4): + 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) # Filter out windows that go past the end diff --git a/core/db.py b/core/db.py index 4c1aa1b..659b74b 100644 --- a/core/db.py +++ b/core/db.py @@ -159,43 +159,47 @@ class ProcessedDB: self._con.execute("DELETE FROM processed WHERE output_path = ?", (output_path,)) self._con.commit() - def get_group(self, output_path: str) -> list[str]: - """Return all output_paths sharing the same (filename, start_time) as *output_path*.""" + 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: return [] 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,), ).fetchone() if not row: return [] + filename, start_time, row_profile = row + p = profile or row_profile rows = self._con.execute( "SELECT output_path FROM processed" - " WHERE filename = ? AND start_time = ? ORDER BY output_path", - (row[0], row[1]), + " WHERE filename = ? AND start_time = ? AND profile = ? ORDER BY output_path", + (filename, start_time, p), ).fetchall() return [r[0] for r in rows] - def delete_group(self, output_path: str) -> list[str]: - """Delete all rows sharing the same (filename, start_time) as *output_path*. + def delete_group(self, output_path: str, profile: str = "") -> list[str]: + """Delete all rows sharing the same (filename, start_time, profile) as *output_path*. Returns list of deleted output_paths.""" if not self._enabled: return [] with self._lock: 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,), ).fetchone() if not row: 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( - "SELECT output_path FROM processed WHERE filename = ? AND start_time = ?", - (filename, start_time), + "SELECT output_path FROM processed" + " WHERE filename = ? AND start_time = ? AND profile = ?", + (filename, start_time, p), ).fetchall()] self._con.execute( - "DELETE FROM processed WHERE filename = ? AND start_time = ?", - (filename, start_time), + "DELETE FROM processed WHERE filename = ? AND start_time = ? AND profile = ?", + (filename, start_time, p), ) self._con.commit() return paths diff --git a/main.py b/main.py index 4d53dfc..aafe290 100755 --- a/main.py +++ b/main.py @@ -3068,7 +3068,7 @@ class MainWindow(QMainWindow): filename, profile, model_label, regions) # If this is the currently loaded file, update the panel 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._scan_all_next() @@ -3303,11 +3303,13 @@ class MainWindow(QMainWindow): short_side = self._spn_resize.value() or None self._export_short_side = short_side self._export_portrait = "Off" + self._export_crop_center = 0.5 self._export_format = fmt self._export_clip_count = 1 self._export_spread = 0 self._export_folder = folder self._export_folder_suffix = "" + self._export_profile = self._profile hw_on = self._chk_hw.isChecked() and self._hw_encoders encoder = self._hw_encoders[0] if hw_on else "libx264" @@ -3354,7 +3356,7 @@ class MainWindow(QMainWindow): fmt=self._export_format, clip_count=1, spread=0, - profile=self._profile, + profile=self._export_profile, source_path=self._file_path, ) upsert_clip_annotation(self._export_folder, path, label) @@ -3544,11 +3546,13 @@ class MainWindow(QMainWindow): self._export_cursor = self._cursor self._export_short_side = short_side self._export_portrait = self._cmb_portrait.currentText() + self._export_crop_center = self._crop_center self._export_format = fmt self._export_clip_count = self._spn_clips.value() self._export_spread = self._spn_spread.value() self._export_folder = folder self._export_folder_suffix = folder_suffix + self._export_profile = self._profile self._btn_export.setEnabled(False) self._set_subprofile_btns_enabled(False) @@ -3595,11 +3599,11 @@ class MainWindow(QMainWindow): category=category, short_side=self._export_short_side, portrait_ratio=portrait, - crop_center=self._crop_center, + crop_center=self._export_crop_center, fmt=self._export_format, clip_count=self._export_clip_count, spread=self._export_spread, - profile=self._profile, + profile=self._export_profile, source_path=self._file_path, ) upsert_clip_annotation(self._export_folder, path, label)