feat: logo-based icon set + accent aligned to brand palette
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@@ -30,6 +30,11 @@ mpv_dir = Path(os.environ.get("MPV_DIR", base))
|
||||
|
||||
datas = []
|
||||
|
||||
# Bundled assets (icons, logo) — must exist at runtime under sys._MEIPASS/assets
|
||||
assets_dir = base / "assets"
|
||||
if assets_dir.exists():
|
||||
datas.append((str(assets_dir), "assets"))
|
||||
|
||||
# YOLOv8 model (optional — large, skip if missing)
|
||||
yolo = base / "yolov8n.pt"
|
||||
if yolo.exists():
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<defs>
|
||||
<linearGradient id="g8" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#ffd230"/>
|
||||
<stop offset="100%" stop-color="#e6a800"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="64" height="64" rx="13" fill="#161616"/>
|
||||
<rect x="8" y="42" width="48" height="11" rx="2" fill="#2a2a2a" stroke="#333" stroke-width="1"/>
|
||||
<rect x="26" y="42" width="16" height="11" fill="#3c82dc" fill-opacity="0.45"/>
|
||||
<line x1="26" y1="38" x2="26" y2="55" stroke="#ffd230" stroke-width="2"/>
|
||||
<polygon points="22,38 30,38 26,44" fill="#ffd230"/>
|
||||
<text x="32" y="33" font-family="'Helvetica Neue',Helvetica,Arial,sans-serif" font-size="34" font-weight="bold" fill="url(#g8)" text-anchor="middle">8</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 790 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M7.5 10 V7.5 a4.5 4.5 0 0 1 9 0 V10" stroke="#ffd230" stroke-width="2"/>
|
||||
<rect x="5" y="10" width="14" height="10" rx="2" fill="#ffd230"/>
|
||||
<circle cx="12" cy="14.3" r="1.4" fill="#161616"/>
|
||||
<rect x="11.2" y="14.3" width="1.6" height="3.4" rx="0.8" fill="#161616"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 362 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M7.5 10 V7.5 a4.5 4.5 0 0 1 8.6 -1.8" stroke="#8a8a8a" stroke-width="2"/>
|
||||
<rect x="5" y="10" width="14" height="10" rx="2" fill="#8a8a8a"/>
|
||||
<circle cx="12" cy="14.3" r="1.4" fill="#1e1e1e"/>
|
||||
<rect x="11.2" y="14.3" width="1.6" height="3.4" rx="0.8" fill="#1e1e1e"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 363 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<rect x="6.5" y="5" width="4" height="14" rx="1.2" fill="#ffd230"/>
|
||||
<rect x="13.5" y="5" width="4" height="14" rx="1.2" fill="#ffd230"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 209 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M7 5 L19 12 L7 19 Z" fill="#ffd230" stroke="#ffd230" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 177 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#aad4ff" stroke-width="2" stroke-linecap="round">
|
||||
<circle cx="10.5" cy="10.5" r="6"/>
|
||||
<line x1="15" y1="15" x2="20" y2="20"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 217 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#ffd230" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="6.5" cy="6.5" r="2.6"/>
|
||||
<circle cx="6.5" cy="17.5" r="2.6"/>
|
||||
<line x1="8.8" y1="8" x2="20" y2="17"/>
|
||||
<line x1="8.8" y1="16" x2="20" y2="7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 322 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#ffd230" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="4,17 10,11 14,14 20,6"/>
|
||||
<polyline points="15,6 20,6 20,11"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 245 B |
@@ -21,7 +21,7 @@ from PyQt6.QtWidgets import (
|
||||
QGridLayout,
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QObject, QThread, QTimer, QRect, QSize, pyqtSignal, QSettings
|
||||
from PyQt6.QtGui import QPainter, QColor, QPen, QPixmap, QDragEnterEvent, QDropEvent, QCursor, QFont, QKeySequence, QShortcut
|
||||
from PyQt6.QtGui import QPainter, QColor, QPen, QPixmap, QDragEnterEvent, QDropEvent, QCursor, QFont, QKeySequence, QShortcut, QIcon
|
||||
if sys.platform == "win32":
|
||||
# Help ctypes find libmpv-2.dll next to main.py or in frozen bundle
|
||||
_dll_dir = Path(sys._MEIPASS) if getattr(sys, "frozen", False) else Path(__file__).parent
|
||||
@@ -40,6 +40,13 @@ from core.db import ProcessedDB
|
||||
from core.annotations import remove_clip_annotation, upsert_clip_annotation
|
||||
from core.tracking import track_centers_for_jobs
|
||||
|
||||
_ASSET_DIR = (Path(sys._MEIPASS) if getattr(sys, "frozen", False) else Path(__file__).parent) / "assets"
|
||||
|
||||
|
||||
def _icon(name: str) -> "QIcon":
|
||||
return QIcon(str(_ASSET_DIR / "icons" / name))
|
||||
|
||||
|
||||
_SELVA_CATEGORIES = ["", "Human", "Animal", "Vehicle", "Tool", "Music", "Nature", "Sport", "Other"]
|
||||
|
||||
|
||||
@@ -3828,6 +3835,7 @@ def main():
|
||||
QSurfaceFormat.setDefaultFormat(_fmt)
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
app.setWindowIcon(_icon("app.svg"))
|
||||
locale.setlocale(locale.LC_NUMERIC, "C") # QApplication resets locale; re-apply for libmpv
|
||||
_kf = _KeyFilter(app)
|
||||
app.installEventFilter(_kf)
|
||||
@@ -3841,13 +3849,13 @@ def main():
|
||||
QComboBox { background: #2a2a2a; border: 1px solid #555; padding: 3px 6px; border-radius: 3px; }
|
||||
QComboBox::drop-down { subcontrol-position: right center; width: 18px; border-left: 1px solid #444; }
|
||||
QComboBox::down-arrow { image: none; border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 5px solid #888; margin-right: 4px; }
|
||||
QComboBox QAbstractItemView { background: #2a2a2a; border: 1px solid #555; selection-background-color: #3a6ea8; }
|
||||
QComboBox QAbstractItemView { background: #2a2a2a; border: 1px solid #555; selection-background-color: #3c82dc; }
|
||||
QSpinBox, QDoubleSpinBox { background: #2a2a2a; border: 1px solid #555; padding: 3px; border-radius: 3px; }
|
||||
QCheckBox::indicator { width: 14px; height: 14px; }
|
||||
QListWidget { background: #252525; alternate-background-color: #2a2a2a; }
|
||||
QListWidget::item { padding: 4px; color: #ccc; }
|
||||
QListWidget::item:alternate { color: #ddd; }
|
||||
QListWidget::item:selected { background: #3a6ea8; color: #fff; }
|
||||
QListWidget::item:selected { background: #3c82dc; color: #fff; }
|
||||
QTabWidget::pane { border: 1px solid #444; border-radius: 3px; top: -1px; }
|
||||
QTabBar::tab { background: #2a2a2a; color: #bbb; padding: 5px 12px;
|
||||
border: 1px solid #444; border-bottom: none;
|
||||
@@ -3856,11 +3864,11 @@ def main():
|
||||
QPushButton:checked { background: #4a3000; border-color: #ffd230; color: #fff; }
|
||||
QStatusBar { background: #1a1a1a; color: #bbb; }
|
||||
QStatusBar::item { border: none; }
|
||||
QPushButton#primary { background: #3a6ea8; border-color: #4f86c6; color: #fff; }
|
||||
QPushButton#primary:hover { background: #4f86c6; }
|
||||
QMenuBar { background: #1e1e1e; } QMenuBar::item:selected { background: #3a6ea8; }
|
||||
QPushButton#primary { background: #3c82dc; border-color: #5a9bf0; color: #fff; }
|
||||
QPushButton#primary:hover { background: #5a9bf0; }
|
||||
QMenuBar { background: #1e1e1e; } QMenuBar::item:selected { background: #3c82dc; }
|
||||
QMenu { background: #2a2a2a; border: 1px solid #555; }
|
||||
QMenu::item:selected { background: #3a6ea8; }
|
||||
QMenu::item:selected { background: #3c82dc; }
|
||||
QWidget#group_sep { background: #3a3a3a; }
|
||||
""")
|
||||
win = MainWindow()
|
||||
@@ -3877,6 +3885,7 @@ class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("8-cut")
|
||||
self.setWindowIcon(_icon("app.svg"))
|
||||
self.resize(1100, 680)
|
||||
self.setAcceptDrops(True)
|
||||
|
||||
@@ -3991,12 +4000,14 @@ class MainWindow(QMainWindow):
|
||||
from PyQt6.QtWidgets import QSizePolicy
|
||||
self._lbl_file.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Preferred)
|
||||
|
||||
self._btn_play = QPushButton("▶ Play")
|
||||
self._btn_play = QPushButton("Play")
|
||||
self._btn_play.setIcon(_icon("play.svg"))
|
||||
self._btn_play.setEnabled(False)
|
||||
self._btn_play.setToolTip("Play selection loop (Space / P)")
|
||||
self._btn_play.clicked.connect(self._on_play)
|
||||
|
||||
self._btn_pause = QPushButton("⏸ Pause")
|
||||
self._btn_pause = QPushButton("Pause")
|
||||
self._btn_pause.setIcon(_icon("pause.svg"))
|
||||
self._btn_pause.setEnabled(False)
|
||||
self._btn_pause.setToolTip("Pause playback (Space / K)")
|
||||
self._btn_pause.clicked.connect(self._on_pause)
|
||||
@@ -4013,7 +4024,8 @@ class MainWindow(QMainWindow):
|
||||
self._btn_speed4.setToolTip("Playback at 4× speed")
|
||||
self._btn_speed4.clicked.connect(lambda: self._set_playback_speed(4.0))
|
||||
|
||||
self._btn_lock = QPushButton("🔒 Lock")
|
||||
self._btn_lock = QPushButton("Lock")
|
||||
self._btn_lock.setIcon(_icon("lock_open.svg"))
|
||||
self._btn_lock.setCheckable(True)
|
||||
self._btn_lock.setToolTip("Lock cursor — click/drag scrubs playback without moving the export point")
|
||||
self._btn_lock.toggled.connect(self._on_lock_toggled)
|
||||
@@ -4195,6 +4207,7 @@ class MainWindow(QMainWindow):
|
||||
self._hidden_subcats: set[str] = set()
|
||||
|
||||
self._btn_scan = QPushButton("Scan")
|
||||
self._btn_scan.setIcon(_icon("scan.svg"))
|
||||
self._btn_scan.setToolTip("Scan current video for audio segments matching reference clips")
|
||||
self._btn_scan.clicked.connect(self._start_scan)
|
||||
|
||||
@@ -4292,6 +4305,7 @@ class MainWindow(QMainWindow):
|
||||
self._update_next_label()
|
||||
|
||||
self._btn_export = QPushButton("Export")
|
||||
self._btn_export.setIcon(_icon("scissors.svg"))
|
||||
self._btn_export.setObjectName("primary")
|
||||
self._btn_export.setEnabled(False)
|
||||
self._btn_export.setToolTip("Export clips at cursor position (E)")
|
||||
@@ -4649,11 +4663,13 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# Scan
|
||||
m_scan = mb.addMenu("&Scan")
|
||||
m_scan.addAction("Scan current", self._start_scan)
|
||||
_act_scan_cur = m_scan.addAction("Scan current", self._start_scan)
|
||||
_act_scan_cur.setIcon(_icon("scan.svg"))
|
||||
m_scan.addAction("Auto-export", self._auto_export)
|
||||
m_scan.addSeparator()
|
||||
m_scan.addAction("Scan All…", self._btn_scan_all.click)
|
||||
m_scan.addAction("Train classifier…", self._btn_train.click)
|
||||
_act_train = m_scan.addAction("Train classifier…", self._btn_train.click)
|
||||
_act_train.setIcon(_icon("train.svg"))
|
||||
|
||||
# View
|
||||
m_view = mb.addMenu("&View")
|
||||
@@ -6052,7 +6068,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def _on_lock_toggled(self, locked: bool):
|
||||
self._timeline._locked = locked
|
||||
self._btn_lock.setText("🔒 Lock" if locked else "🔓 Lock")
|
||||
self._btn_lock.setIcon(_icon("lock.svg" if locked else "lock_open.svg"))
|
||||
if not locked:
|
||||
# Clear keyframes when unlocking.
|
||||
if self._crop_keyframes:
|
||||
|
||||