feat: logo-based icon set + accent aligned to brand palette

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 16:56:54 +02:00
parent 8a7d761815
commit cbbdfeadb1
10 changed files with 81 additions and 13 deletions
+5
View File
@@ -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():
+14
View File
@@ -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

+6
View File
@@ -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

+6
View File
@@ -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

+4
View File
@@ -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

+3
View File
@@ -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

+4
View File
@@ -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

+6
View File
@@ -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

+4
View File
@@ -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

+29 -13
View File
@@ -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: