-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmain.py
More file actions
269 lines (223 loc) · 8.44 KB
/
main.py
File metadata and controls
269 lines (223 loc) · 8.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
import sys
import os
import logging
import logging.handlers
import platform
import traceback
import warnings
import ctypes
from pathlib import Path
# Set Windows AppUserModelID so the taskbar shows our icon, not Python's.
# Must be called before QApplication is created. Uses explicit arg/res types
# to ensure the wide string is passed correctly.
try:
_SetAppID = ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID
_SetAppID.argtypes = [ctypes.c_wchar_p]
_SetAppID.restype = ctypes.HRESULT
_SetAppID("TalkTrack.TalkTrack.1")
except Exception:
pass
# --- Logging setup (before anything else) ---
LOG_DIR = Path.home() / ".talktrack"
LOG_DIR.mkdir(parents=True, exist_ok=True)
LOG_FILE = LOG_DIR / "talktrack.log"
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
handlers=[
logging.handlers.RotatingFileHandler(
LOG_FILE, maxBytes=2 * 1024 * 1024, backupCount=3, encoding="utf-8"
),
],
)
logger = logging.getLogger("talktrack")
logger.info("TalkTrack starting — Python %s on %s", sys.version, platform.platform())
# Redirect stderr to log file so uncaught tracebacks are captured
class _StderrToLog:
def __init__(self, logger):
self._logger = logger
self._buf = ""
def write(self, msg):
if msg and msg.strip():
self._logger.error(msg.rstrip())
def flush(self):
pass
sys.stderr = _StderrToLog(logger)
# Suppress noisy torchcodec warnings (we use soundfile for audio loading).
warnings.filterwarnings("ignore", module=r"pyannote\.audio\.core\.io")
warnings.filterwarnings("ignore", message=".*std\\(\\).*degrees of freedom.*")
# Fix DLL search path for PyTorch before QApplication init.
try:
import torch as _torch
_torch_lib = os.path.join(os.path.dirname(_torch.__file__), "lib")
if os.path.isdir(_torch_lib):
os.add_dll_directory(_torch_lib)
del _torch, _torch_lib
except ImportError:
pass
from PyQt6.QtWidgets import QApplication, QMessageBox
from PyQt6.QtGui import QIcon
def get_log_file():
"""Return the path to the log file."""
return LOG_FILE
def get_log_tail(lines=30):
"""Return the last N lines of the log file."""
try:
text = LOG_FILE.read_text(encoding="utf-8", errors="replace")
return "\n".join(text.splitlines()[-lines:])
except OSError:
return "(could not read log file)"
def build_bug_report_url(error_text=""):
"""Build a GitHub issue URL pre-filled with system info and error details."""
import urllib.parse
body_parts = [
"## Description",
"(Describe what you were doing when the problem occurred)",
"",
"## System Info",
f"- **OS:** {platform.platform()}",
f"- **Python:** {sys.version.split()[0]}",
]
try:
import torch
body_parts.append(f"- **PyTorch:** {torch.__version__}")
body_parts.append(f"- **CUDA available:** {torch.cuda.is_available()}")
except ImportError:
body_parts.append("- **PyTorch:** not installed")
if error_text:
body_parts.extend([
"",
"## Error",
"```",
error_text[-1500:], # Trim to avoid URL length limits
"```",
])
body_parts.extend([
"",
"## Recent Log",
"```",
get_log_tail(15),
"```",
])
body = "\n".join(body_parts)
params = urllib.parse.urlencode({
"title": "[Bug] ",
"body": body,
"labels": "bug",
})
return f"https://github.com/ObscureAintSecure/TalkTrack/issues/new?{params}"
def _exception_handler(exc_type, exc_value, exc_tb):
"""Global exception handler — log the error and show a crash dialog."""
if exc_type == KeyboardInterrupt:
sys.exit(0)
error_text = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
logger.critical("Uncaught exception:\n%s", error_text)
try:
import webbrowser
msg = QMessageBox()
msg.setWindowTitle("TalkTrack — Unexpected Error")
msg.setIcon(QMessageBox.Icon.Critical)
msg.setText("TalkTrack encountered an unexpected error.")
msg.setInformativeText(str(exc_value))
msg.setDetailedText(error_text)
report_btn = msg.addButton("Report Bug", QMessageBox.ButtonRole.ActionRole)
open_log_btn = msg.addButton("Open Log", QMessageBox.ButtonRole.HelpRole)
msg.addButton(QMessageBox.StandardButton.Close)
msg.exec()
clicked = msg.clickedButton()
if clicked == report_btn:
webbrowser.open(build_bug_report_url(error_text))
elif clicked == open_log_btn:
os.startfile(str(LOG_FILE))
except Exception:
pass
def load_stylesheet():
style_path = Path(__file__).parent / "resources" / "style.qss"
if style_path.exists():
return style_path.read_text()
return ""
def main():
app = QApplication(sys.argv)
app.setApplicationName("TalkTrack")
app.setOrganizationName("TalkTrack")
# Set app icon
from PyQt6.QtGui import QIcon
icon_path = Path(__file__).parent / "resources" / "talktrack.ico"
if icon_path.exists():
app.setWindowIcon(QIcon(str(icon_path)))
# Install global exception handler
sys.excepthook = _exception_handler
# Apply dark theme stylesheet
stylesheet = load_stylesheet()
if stylesheet:
app.setStyleSheet(stylesheet)
# Show splash screen while heavy modules load
from PyQt6.QtWidgets import QSplashScreen
from PyQt6.QtGui import QPixmap, QPainter, QColor, QFont
from PyQt6.QtCore import Qt
splash_pixmap = QPixmap(340, 120)
splash_pixmap.fill(QColor("#1e1e2e"))
painter = QPainter(splash_pixmap)
painter.setPen(QColor("#89b4fa"))
painter.setFont(QFont("Segoe UI", 18, QFont.Weight.Bold))
painter.drawText(splash_pixmap.rect(), Qt.AlignmentFlag.AlignCenter, "TalkTrack")
painter.setPen(QColor("#a6adc8"))
painter.setFont(QFont("Segoe UI", 10))
r = splash_pixmap.rect()
r.setTop(r.center().y() + 10)
painter.drawText(r, Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop, "Loading...")
painter.end()
splash = QSplashScreen(splash_pixmap)
splash.show()
app.processEvents()
from app.main_window import MainWindow
window = MainWindow()
if icon_path.exists():
window.setWindowIcon(QIcon(str(icon_path)))
window.show()
splash.finish(window)
# Force taskbar icon via Win32 API (needed for Microsoft Store Python)
if icon_path.exists():
try:
WM_SETICON = 0x0080
IMAGE_ICON = 1
LR_LOADFROMFILE = 0x0010
hwnd = int(window.winId())
hicon_big = ctypes.windll.user32.LoadImageW(
None, str(icon_path), IMAGE_ICON, 48, 48,
LR_LOADFROMFILE,
)
hicon_small = ctypes.windll.user32.LoadImageW(
None, str(icon_path), IMAGE_ICON, 16, 16,
LR_LOADFROMFILE,
)
if hicon_big:
ctypes.windll.user32.SendMessageW(hwnd, WM_SETICON, 1, hicon_big)
if hicon_small:
ctypes.windll.user32.SendMessageW(hwnd, WM_SETICON, 0, hicon_small)
except Exception:
pass
# Set AppUserModelID on the window itself (not just the process).
# MS Store Python's AppX manifest can override the process-level ID,
# but per-window IDs via SHGetPropertyStoreForWindow take precedence.
try:
from comtypes import GUID
hwnd = int(window.winId())
IID_IPropertyStore = GUID("{886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99}")
SHGetPropertyStoreForWindow = ctypes.windll.shell32.SHGetPropertyStoreForWindow
SHGetPropertyStoreForWindow.argtypes = [
ctypes.c_void_p, ctypes.POINTER(GUID), ctypes.POINTER(ctypes.c_void_p)
]
SHGetPropertyStoreForWindow.restype = ctypes.HRESULT
ppv = ctypes.c_void_p()
hr = SHGetPropertyStoreForWindow(hwnd, ctypes.byref(IID_IPropertyStore), ctypes.byref(ppv))
if hr == 0 and ppv.value:
from app.utils.start_menu import _property_store_set_string
_property_store_set_string(ppv.value, "TalkTrack.TalkTrack.1")
logger.info("Set per-window AppUserModelID")
except Exception as e:
logger.debug("Could not set per-window AppUserModelID: %s", e)
logger.info("TalkTrack UI ready")
sys.exit(app.exec())
if __name__ == "__main__":
main()