diff --git a/8cut.spec b/8cut.spec
index 26fc73a..babc605 100644
--- a/8cut.spec
+++ b/8cut.spec
@@ -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():
diff --git a/assets/icons/app.svg b/assets/icons/app.svg
new file mode 100644
index 0000000..370f31d
--- /dev/null
+++ b/assets/icons/app.svg
@@ -0,0 +1,14 @@
+
diff --git a/assets/icons/lock.svg b/assets/icons/lock.svg
new file mode 100644
index 0000000..83ff63b
--- /dev/null
+++ b/assets/icons/lock.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/icons/lock_open.svg b/assets/icons/lock_open.svg
new file mode 100644
index 0000000..3b0cfcc
--- /dev/null
+++ b/assets/icons/lock_open.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/icons/pause.svg b/assets/icons/pause.svg
new file mode 100644
index 0000000..c68eb18
--- /dev/null
+++ b/assets/icons/pause.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/play.svg b/assets/icons/play.svg
new file mode 100644
index 0000000..613be92
--- /dev/null
+++ b/assets/icons/play.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/scan.svg b/assets/icons/scan.svg
new file mode 100644
index 0000000..ca9d0a7
--- /dev/null
+++ b/assets/icons/scan.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/icons/scissors.svg b/assets/icons/scissors.svg
new file mode 100644
index 0000000..9bc4a3f
--- /dev/null
+++ b/assets/icons/scissors.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/icons/train.svg b/assets/icons/train.svg
new file mode 100644
index 0000000..ff6517b
--- /dev/null
+++ b/assets/icons/train.svg
@@ -0,0 +1,4 @@
+
diff --git a/main.py b/main.py
index a80458c..1b62743 100755
--- a/main.py
+++ b/main.py
@@ -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: