diff --git a/core/db.py b/core/db.py index 773d35d..7313d19 100644 --- a/core/db.py +++ b/core/db.py @@ -396,6 +396,44 @@ class ProcessedDB: return [] return self._get_markers_for(filename, profile, export_folder) + def get_other_folder_markers(self, filename: str, profile: str = "default", + export_folder: str = "" + ) -> dict[str, list[tuple[float, int, str, float]]]: + """Return {folder_name: [(start_time, num, path, span), ...]} for + markers NOT in export_folder, grouped by their base export folder.""" + if not self._enabled or not export_folder: + return {} + rows = self._con.execute( + "SELECT start_time, output_path, clip_duration, clip_count, spread" + " FROM processed" + " WHERE filename = ? AND profile = ? AND scan_export = 0" + " AND output_path NOT LIKE ?" + " ORDER BY start_time", + (filename, profile, export_folder.rstrip("/") + "/%"), + ).fetchall() + by_folder: dict[str, list] = {} + for t, p, dur, cnt, spr in rows: + parts = p.split("/") + for i, part in enumerate(parts): + if part.startswith("vid_"): + folder = "/".join(parts[:i]) + break + else: + folder = os.path.dirname(os.path.dirname(p)) + by_folder.setdefault(folder, []).append((t, p, dur, cnt, spr)) + result: dict[str, list[tuple[float, int, str, float]]] = {} + for folder, folder_rows in by_folder.items(): + seen: dict[float, tuple[float, int, str, float]] = {} + n = 0 + for t, p, dur, cnt, spr in folder_rows: + if t not in seen: + n += 1 + span = (dur or 8.0) + ((cnt or 1) - 1) * (spr or 3.0) + seen[t] = (t, n, p, span) + name = os.path.basename(folder) + result[name] = list(seen.values()) + return result + def get_manual_export_groups(self, filename: str, profile: str = "default" ) -> list[dict]: """Return manual (non-scan) export groups for *filename*. diff --git a/main.py b/main.py index 4471999..ede1351 100755 --- a/main.py +++ b/main.py @@ -1692,6 +1692,7 @@ class TimelineWidget(QWidget): self._locked = False # when True, clicks scrub playback, not cursor self._crop_keyframes: list[tuple[float, float, str | None, bool, bool]] = [] self._markers: list[tuple[float, int, str, float]] = [] + self._other_markers: list[tuple[str, list[tuple[float, int, str, float]]]] = [] # (start, end, score, orig_start, orig_end) self._scan_regions: list[tuple[float, float, float, float, float]] = [] self._scan_neg_times: set[float] = set() @@ -1743,6 +1744,7 @@ class TimelineWidget(QWidget): self._play_pos = None self._view_start = 0.0 self._view_span = 0.0 + self._other_markers = [] self.update() def set_waveform(self, peaks) -> None: @@ -1768,6 +1770,10 @@ class TimelineWidget(QWidget): self._markers = markers self.update() + def set_other_markers(self, groups: dict[str, list[tuple[float, int, str, float]]]) -> None: + self._other_markers = list(groups.items()) + self.update() + def set_scan_regions(self, regions: list, neg_times: set[float] | None = None) -> None: """regions: list of (start, end, score) or (start, end, score, orig_start, orig_end)""" normed: list[tuple[float, float, float, float, float]] = [] @@ -2060,6 +2066,33 @@ class TimelineWidget(QWidget): p.drawText(mx + 1, rh + 2, 13, 12, Qt.AlignmentFlag.AlignCenter, str(num)) + # ── other-folder markers (subprofile exports) ───────────────── + _OTHER_COLORS = [ + QColor(220, 190, 50), # yellow + QColor(60, 190, 100), # green + QColor(80, 160, 220), # blue + QColor(200, 120, 220), # purple + QColor(220, 140, 60), # orange + ] + for gi, (folder_name, group) in enumerate(self._other_markers): + color = _OTHER_COLORS[gi % len(_OTHER_COLORS)] + dim = QColor(color.red(), color.green(), color.blue(), 35) + pen = QPen(color, 1) + for (t, num, _path, span) in group: + mx = int(self._time_to_x(t)) + if mx < -20 or mx > w + 20: + continue + mx2 = int(self._time_to_x(min(t + span, self._duration))) + if mx2 > mx: + p.fillRect(mx, rh, mx2 - mx, th, dim) + p.setPen(pen) + p.drawLine(mx, rh, mx, h) + p.fillRect(mx, rh + 2, 14, 12, color) + p.setPen(QColor(0, 0, 0)) + p.setFont(self._marker_font) + p.drawText(mx + 1, rh + 2, 13, 12, + Qt.AlignmentFlag.AlignCenter, str(num)) + # ── scan mode cursor + playback line ───────────────────────── if self._scan_mode: # Export cursor (dim) @@ -4210,12 +4243,26 @@ class MainWindow(QMainWindow): else: self._lbl_status.clear() self._timeline.set_markers(markers) + self._refresh_other_markers() def _refresh_markers(self) -> None: filename = os.path.basename(self._file_path) - markers = self._db.get_markers(filename, self._profile, - self._txt_folder.text()) + folder = self._txt_folder.text() + markers = self._db.get_markers(filename, self._profile, folder) self._timeline.set_markers(markers) + others = self._db.get_other_folder_markers( + filename, self._profile, folder) + self._timeline.set_other_markers(others) + + def _refresh_other_markers(self) -> None: + if not self._file_path: + self._timeline.set_other_markers({}) + return + filename = os.path.basename(self._file_path) + folder = self._txt_folder.text() + others = self._db.get_other_folder_markers( + filename, self._profile, folder) + self._timeline.set_other_markers(others) def _refresh_playlist_checks(self) -> None: """Re-evaluate marks on every playlist item for the current profile."""