audio-tuner-gui 0.9.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- audio_tuner_gui/__init__.py +39 -0
- audio_tuner_gui/analysis.py +717 -0
- audio_tuner_gui/common.py +350 -0
- audio_tuner_gui/display.py +605 -0
- audio_tuner_gui/export.py +563 -0
- audio_tuner_gui/file_selector.py +435 -0
- audio_tuner_gui/icons/audio_tuner_icon.ico +0 -0
- audio_tuner_gui/icons/audio_tuner_icon.svg +215 -0
- audio_tuner_gui/icons/audio_tuner_icon_hires.svg +215 -0
- audio_tuner_gui/icons/audio_tuner_logo_dark.svg +563 -0
- audio_tuner_gui/icons/audio_tuner_logo_light.svg +563 -0
- audio_tuner_gui/icons/black-logo.svg +24 -0
- audio_tuner_gui/icons/folder.png +0 -0
- audio_tuner_gui/icons/gpl-v3-logo_red.svg +223 -0
- audio_tuner_gui/icons/gpl-v3-logo_white.svg +224 -0
- audio_tuner_gui/icons/preferences-other.png +0 -0
- audio_tuner_gui/icons/process-stop.png +0 -0
- audio_tuner_gui/icons/white-logo.svg +24 -0
- audio_tuner_gui/log_viewer.py +154 -0
- audio_tuner_gui/option_panel.py +683 -0
- audio_tuner_gui/player.py +757 -0
- audio_tuner_gui/scripts/__init__.py +0 -0
- audio_tuner_gui/scripts/tuner_gui.py +863 -0
- audio_tuner_gui-0.9.1.dist-info/METADATA +93 -0
- audio_tuner_gui-0.9.1.dist-info/RECORD +28 -0
- audio_tuner_gui-0.9.1.dist-info/WHEEL +4 -0
- audio_tuner_gui-0.9.1.dist-info/entry_points.txt +2 -0
- audio_tuner_gui-0.9.1.dist-info/licenses/COPYING +674 -0
|
@@ -0,0 +1,863 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# This file is part of Audio Tuner.
|
|
4
|
+
#
|
|
5
|
+
# Copyright 2025, 2026 Jessie Blue Cassell <bluesloth600@gmail.com>
|
|
6
|
+
#
|
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
# (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU General Public License
|
|
18
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
"""The GUI frontend for Audio Tuner."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__author__ = 'Jessie Blue Cassell'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
import sys
|
|
28
|
+
import os
|
|
29
|
+
from collections import deque
|
|
30
|
+
|
|
31
|
+
from PyQt6.QtCore import (
|
|
32
|
+
Qt,
|
|
33
|
+
pyqtSignal,
|
|
34
|
+
)
|
|
35
|
+
from PyQt6.QtWidgets import (
|
|
36
|
+
QApplication,
|
|
37
|
+
QWidget,
|
|
38
|
+
QPushButton,
|
|
39
|
+
QMessageBox,
|
|
40
|
+
QMainWindow,
|
|
41
|
+
QVBoxLayout,
|
|
42
|
+
QHBoxLayout,
|
|
43
|
+
QSplitter,
|
|
44
|
+
QToolButton,
|
|
45
|
+
QSizePolicy,
|
|
46
|
+
QProgressBar,
|
|
47
|
+
QLabel,
|
|
48
|
+
QStackedWidget,
|
|
49
|
+
QLCDNumber,
|
|
50
|
+
QSlider,
|
|
51
|
+
)
|
|
52
|
+
from PyQt6.QtGui import (
|
|
53
|
+
QIcon,
|
|
54
|
+
QAction,
|
|
55
|
+
QKeySequence,
|
|
56
|
+
QFont,
|
|
57
|
+
QPalette,
|
|
58
|
+
)
|
|
59
|
+
from PyQt6.QtSvgWidgets import QSvgWidget
|
|
60
|
+
|
|
61
|
+
import audio_tuner.error_handling as eh
|
|
62
|
+
import audio_tuner.common as com
|
|
63
|
+
import audio_tuner.argument_parser as ap
|
|
64
|
+
|
|
65
|
+
import audio_tuner_gui.common as gcom
|
|
66
|
+
import audio_tuner_gui.file_selector as fs
|
|
67
|
+
import audio_tuner_gui.option_panel as op
|
|
68
|
+
import audio_tuner_gui.analysis as ga
|
|
69
|
+
import audio_tuner_gui.log_viewer as lv
|
|
70
|
+
import audio_tuner_gui.display as disp
|
|
71
|
+
import audio_tuner_gui.player as pl
|
|
72
|
+
import audio_tuner_gui.export as ex
|
|
73
|
+
|
|
74
|
+
from audio_tuner_gui import VERSION
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
APP_TITLE = 'Audio Tuner'
|
|
78
|
+
QUIT_CONFIRMATION_MESSAGE = f'Quit {APP_TITLE}?'
|
|
79
|
+
INIT_STATUS_MESSAGE = f'Welcome to {APP_TITLE}'
|
|
80
|
+
|
|
81
|
+
DESCRIPTION = (f'This is the GUI for {APP_TITLE}. Command line arguments'
|
|
82
|
+
' can be used to run with different default settings.'
|
|
83
|
+
' Arguments not listed here will be passed to the'
|
|
84
|
+
' Qt GUI toolkit.')
|
|
85
|
+
|
|
86
|
+
ABOUT_TEXT = """
|
|
87
|
+
Copyright \N{COPYRIGHT SIGN} 2025, 2026 Jessie Blue Cassell.
|
|
88
|
+
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
|
|
89
|
+
This is free software: you are free to change and redistribute it.
|
|
90
|
+
There is NO WARRANTY, to the extent permitted by law.
|
|
91
|
+
|
|
92
|
+
Folder, Options, and Process Stop icons from the
|
|
93
|
+
Adwaita icon theme, by the GNOME Project.
|
|
94
|
+
<https://download.gnome.org/sources/adwaita-icon-theme/>
|
|
95
|
+
Copyright \N{COPYRIGHT SIGN} 2002-2014
|
|
96
|
+
License: CC-BY-SA-3.0 or LGPL-3
|
|
97
|
+
<https://creativecommons.org/licenses/by-sa/3.0/>
|
|
98
|
+
<https://gnu.org/licenses/lgpl-3.0.html>
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
VERSION_STRING = f'tuner-gui ({APP_TITLE}) {VERSION}\n{ABOUT_TEXT}'
|
|
102
|
+
|
|
103
|
+
DEBUG_BUTTON = False
|
|
104
|
+
|
|
105
|
+
class _AboutWindow(QWidget):
|
|
106
|
+
def __init__(self, parent):
|
|
107
|
+
super().__init__(parent)
|
|
108
|
+
|
|
109
|
+
self.setWindowFlag(Qt.WindowType.Window)
|
|
110
|
+
self.setWindowTitle('About')
|
|
111
|
+
|
|
112
|
+
self.vbox = QVBoxLayout(self)
|
|
113
|
+
|
|
114
|
+
self.text = QLabel(ABOUT_TEXT, self)
|
|
115
|
+
self.text.setCursor(Qt.CursorShape.IBeamCursor)
|
|
116
|
+
self.text.setTextInteractionFlags(
|
|
117
|
+
Qt.TextInteractionFlag.TextSelectableByMouse)
|
|
118
|
+
|
|
119
|
+
self.config_text = QLabel('Config file path:\n' + ap.CONFIG_PATH, self)
|
|
120
|
+
self.config_text.setCursor(Qt.CursorShape.IBeamCursor)
|
|
121
|
+
self.config_text.setTextInteractionFlags(
|
|
122
|
+
Qt.TextInteractionFlag.TextSelectableByMouse)
|
|
123
|
+
|
|
124
|
+
th = self.text.fontMetrics().height()
|
|
125
|
+
pal = QApplication.palette()
|
|
126
|
+
text_color = pal.color(QPalette.ColorRole.WindowText)
|
|
127
|
+
bg_color = pal.color(QPalette.ColorRole.Window)
|
|
128
|
+
lightmode = text_color.lightness() < bg_color.lightness()
|
|
129
|
+
|
|
130
|
+
if lightmode:
|
|
131
|
+
self.logo = QSvgWidget(gcom.LOGO_DARK)
|
|
132
|
+
else:
|
|
133
|
+
self.logo = QSvgWidget(gcom.LOGO_LIGHT)
|
|
134
|
+
self.logo.renderer().setAspectRatioMode(
|
|
135
|
+
Qt.AspectRatioMode.KeepAspectRatio)
|
|
136
|
+
self.logo.setFixedSize(int(35 * th), int(7 * th))
|
|
137
|
+
self.vbox.addWidget(self.logo)
|
|
138
|
+
|
|
139
|
+
self.ver = QLabel(f'Version {VERSION}', self)
|
|
140
|
+
self.ver.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
141
|
+
font = QFont()
|
|
142
|
+
font.setPointSize(16)
|
|
143
|
+
self.ver.setFont(font)
|
|
144
|
+
self.ver.setCursor(Qt.CursorShape.IBeamCursor)
|
|
145
|
+
self.ver.setTextInteractionFlags(
|
|
146
|
+
Qt.TextInteractionFlag.TextSelectableByMouse)
|
|
147
|
+
self.vbox.addWidget(self.ver)
|
|
148
|
+
|
|
149
|
+
self.vbox.addWidget(self.text)
|
|
150
|
+
|
|
151
|
+
self.vbox.addWidget(self.config_text)
|
|
152
|
+
|
|
153
|
+
self.hboxwidget = QWidget()
|
|
154
|
+
self.hbox = QHBoxLayout(self.hboxwidget)
|
|
155
|
+
self.hbox.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
|
156
|
+
|
|
157
|
+
if lightmode:
|
|
158
|
+
self.gpl = QSvgWidget(gcom.GPL_RED)
|
|
159
|
+
else:
|
|
160
|
+
self.gpl = QSvgWidget(gcom.GPL_WHITE)
|
|
161
|
+
self.gpl.renderer().setAspectRatioMode(
|
|
162
|
+
Qt.AspectRatioMode.KeepAspectRatio)
|
|
163
|
+
self.gpl.setFixedSize(int(7 * th), int(4 * th))
|
|
164
|
+
self.hbox.addWidget(self.gpl)
|
|
165
|
+
|
|
166
|
+
if lightmode:
|
|
167
|
+
self.brainmade = QSvgWidget(gcom.BRAINMADE_BLACK)
|
|
168
|
+
else:
|
|
169
|
+
self.brainmade = QSvgWidget(gcom.BRAINMADE_WHITE)
|
|
170
|
+
self.brainmade.renderer().setAspectRatioMode(
|
|
171
|
+
Qt.AspectRatioMode.KeepAspectRatio)
|
|
172
|
+
self.brainmade.setFixedSize(int(5 * th), int(1.5 * th))
|
|
173
|
+
self.hbox.addWidget(self.brainmade)
|
|
174
|
+
|
|
175
|
+
self.vbox.addWidget(self.hboxwidget)
|
|
176
|
+
|
|
177
|
+
self.button = QPushButton('Close')
|
|
178
|
+
self.button.clicked.connect(self.close)
|
|
179
|
+
self.vbox.addWidget(self.button)
|
|
180
|
+
|
|
181
|
+
def close(self):
|
|
182
|
+
"""Close the window."""
|
|
183
|
+
self.hide()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class _Slider(QSlider):
|
|
187
|
+
MouseReleased = pyqtSignal()
|
|
188
|
+
MousePressed = pyqtSignal()
|
|
189
|
+
|
|
190
|
+
def mousePressEvent(self, event):
|
|
191
|
+
super().mousePressEvent(event)
|
|
192
|
+
self.MousePressed.emit()
|
|
193
|
+
|
|
194
|
+
def mouseReleaseEvent(self, event):
|
|
195
|
+
super().mouseReleaseEvent(event)
|
|
196
|
+
self.MouseReleased.emit()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class _MainUI(QMainWindow):
|
|
200
|
+
def __init__(self, args, message_queue):
|
|
201
|
+
super().__init__()
|
|
202
|
+
|
|
203
|
+
self.args = args
|
|
204
|
+
self.message_queue = message_queue
|
|
205
|
+
self._slider_update_enabled = True
|
|
206
|
+
self.duration = None
|
|
207
|
+
self._exporting_in_progress = False
|
|
208
|
+
self._currently_playing_start = None
|
|
209
|
+
self._currently_playing_end = None
|
|
210
|
+
|
|
211
|
+
self.exit_act = QAction(QIcon.fromTheme(gcom.ICON_EXIT),
|
|
212
|
+
'Exit',
|
|
213
|
+
self)
|
|
214
|
+
self.exit_act.setShortcut(QKeySequence('Ctrl+Q'))
|
|
215
|
+
self.exit_act.setStatusTip('Exit application')
|
|
216
|
+
self.exit_act.triggered.connect(QApplication.instance().quit)
|
|
217
|
+
|
|
218
|
+
self.showhidden_act = QAction('Show &hidden files', self)
|
|
219
|
+
self.showhidden_act.setCheckable(True)
|
|
220
|
+
self.showhidden_act.setShortcut(QKeySequence('Ctrl+H'))
|
|
221
|
+
|
|
222
|
+
self.play_start_end_act = QAction(
|
|
223
|
+
f'Play between {gcom.OPTION_START} and {gcom.OPTION_END}')
|
|
224
|
+
self.play_start_end_act.setCheckable(True)
|
|
225
|
+
self.play_start_end_act.toggled.connect(self._play_start_end_toggled)
|
|
226
|
+
|
|
227
|
+
self.toggle_options_panel_act = QAction('Show &options panel', self)
|
|
228
|
+
self.toggle_options_panel_act.setCheckable(True)
|
|
229
|
+
self.toggle_options_panel_act.setChecked(False)
|
|
230
|
+
self.toggle_options_panel_act.setShortcut('F2')
|
|
231
|
+
self.toggle_options_panel_act.setStatusTip(
|
|
232
|
+
'Switch between file selector and options panel')
|
|
233
|
+
self.toggle_options_panel_act.triggered.connect(
|
|
234
|
+
self._toggle_options_panel)
|
|
235
|
+
|
|
236
|
+
self.toggle_toggle_options_panel_act = QAction(self)
|
|
237
|
+
self.toggle_toggle_options_panel_act.setIcon(QIcon(gcom.ICON_OPTIONS))
|
|
238
|
+
self.toggle_toggle_options_panel_act.setStatusTip(
|
|
239
|
+
'Switch between file selector and options panel')
|
|
240
|
+
self.toggle_toggle_options_panel_act.triggered.connect(
|
|
241
|
+
self._toggle_toggle_options_panel)
|
|
242
|
+
|
|
243
|
+
self.cancel_act = QAction(self)
|
|
244
|
+
self.cancel_act.setIcon(QIcon.fromTheme(gcom.ICON_CANCEL))
|
|
245
|
+
self.cancel_act.setShortcut('Escape')
|
|
246
|
+
self.cancel_act.setStatusTip('Cancel')
|
|
247
|
+
|
|
248
|
+
self.export_act = QAction('&Export Audio...')
|
|
249
|
+
self.export_act.triggered.connect(self._export)
|
|
250
|
+
if com.mpv_error is not None:
|
|
251
|
+
self.export_act.setEnabled(False)
|
|
252
|
+
|
|
253
|
+
self.about_act = QAction('&About ' + APP_TITLE, self)
|
|
254
|
+
self.about_act.setIcon(QIcon.fromTheme(gcom.ICON_ABOUT))
|
|
255
|
+
self.about_act.triggered.connect(self._about_show)
|
|
256
|
+
|
|
257
|
+
self.log_viewer_act = gcom.SplitAction('&Message log', self)
|
|
258
|
+
self.log_viewer_act.setShortcut(QKeySequence('Ctrl+E'))
|
|
259
|
+
self.log_viewer_act.setIcon(QIcon.fromTheme(gcom.ICON_MESSAGE_LOG))
|
|
260
|
+
self.log_viewer_act.setStatusTip('View message log')
|
|
261
|
+
self.log_viewer_act.triggered_connect(self._log_show)
|
|
262
|
+
|
|
263
|
+
self.device_window_act = QAction('Select audio output &device...')
|
|
264
|
+
self.device_window_act.setIcon(QIcon.fromTheme(gcom.ICON_AUDIO_DEVICE))
|
|
265
|
+
self.device_window_act.setStatusTip('Select audio output device')
|
|
266
|
+
self.device_window_act.triggered.connect(self._device_window_show)
|
|
267
|
+
|
|
268
|
+
self.show_log_plot_act = QAction('Show &dB plot')
|
|
269
|
+
self.show_log_plot_act.setStatusTip('Show a plot of dB vs frequency')
|
|
270
|
+
self.show_log_plot_act.triggered.connect(self._show_log_plot)
|
|
271
|
+
|
|
272
|
+
self.show_linear_plot_act = QAction('Show &power plot')
|
|
273
|
+
self.show_linear_plot_act.setStatusTip(
|
|
274
|
+
'Show a plot of power vs frequency')
|
|
275
|
+
self.show_linear_plot_act.triggered.connect(self._show_linear_plot)
|
|
276
|
+
|
|
277
|
+
self.debug_drag_act = QAction('Debug dragging')
|
|
278
|
+
self.debug_drag_act.setCheckable(True)
|
|
279
|
+
self.debug_drag_act.setChecked(False)
|
|
280
|
+
|
|
281
|
+
self.play_act = QAction(self)
|
|
282
|
+
self.play_act.setShortcuts([QKeySequence('Ctrl+P'),
|
|
283
|
+
Qt.Key.Key_MediaPlay])
|
|
284
|
+
self.play_act.setIcon(QIcon.fromTheme(gcom.ICON_PLAY))
|
|
285
|
+
self.play_act.setStatusTip('Play (Ctrl+P)')
|
|
286
|
+
self.play_act.triggered.connect(self._play)
|
|
287
|
+
|
|
288
|
+
self.pause_act = QAction(self)
|
|
289
|
+
self.pause_act.setShortcuts(['Space',
|
|
290
|
+
Qt.Key.Key_MediaPause])
|
|
291
|
+
self.pause_act.setIcon(QIcon.fromTheme(gcom.ICON_PAUSE))
|
|
292
|
+
self.pause_act.setStatusTip('Pause (Spacebar)')
|
|
293
|
+
self.pause_act.triggered.connect(self._pause)
|
|
294
|
+
|
|
295
|
+
self.stop_act = QAction(self)
|
|
296
|
+
self.stop_act.setShortcuts([QKeySequence('Ctrl+S'),
|
|
297
|
+
Qt.Key.Key_MediaStop])
|
|
298
|
+
self.stop_act.setIcon(QIcon.fromTheme(gcom.ICON_STOP))
|
|
299
|
+
self.stop_act.setStatusTip('Stop (Ctrl+S)')
|
|
300
|
+
self.stop_act.triggered.connect(self._stop)
|
|
301
|
+
|
|
302
|
+
if DEBUG_BUTTON:
|
|
303
|
+
self.debug_act = QAction(self)
|
|
304
|
+
#self.debug_act.setShortcut(QKeySequence('Ctrl+P'))
|
|
305
|
+
self.debug_act.setIcon(QIcon.fromTheme(gcom.ICON_ABOUT))
|
|
306
|
+
self.debug_act.setStatusTip('Print stuffs')
|
|
307
|
+
self.debug_act.triggered.connect(self._debug)
|
|
308
|
+
|
|
309
|
+
self.player_back_act = QAction(self)
|
|
310
|
+
self.player_back_act.setShortcut('Left')
|
|
311
|
+
self.player_back_act.setIcon(QIcon.fromTheme(gcom.ICON_PLAYER_BACK))
|
|
312
|
+
self.player_back_act.setStatusTip('Rewind 10 seconds (Left arrow)')
|
|
313
|
+
|
|
314
|
+
self.player_forward_act = QAction(self)
|
|
315
|
+
self.player_forward_act.setShortcut('Right')
|
|
316
|
+
self.player_forward_act.setIcon(QIcon.fromTheme(
|
|
317
|
+
gcom.ICON_PLAYER_FORWARD))
|
|
318
|
+
self.player_forward_act.setStatusTip('Jump ahead 10 seconds'
|
|
319
|
+
' (Right arrow)')
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
self._initUI()
|
|
323
|
+
|
|
324
|
+
self._nothing_selected()
|
|
325
|
+
|
|
326
|
+
self.setAcceptDrops(True)
|
|
327
|
+
|
|
328
|
+
for arg in args.file:
|
|
329
|
+
self.file_selector.handle_command_line_arg(arg)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def dragEnterEvent(self, event):
|
|
333
|
+
if self.debug_drag_act.isChecked():
|
|
334
|
+
s = (f'\ndragEnterEvent\n'
|
|
335
|
+
f'source: {event.source()}\n'
|
|
336
|
+
f'hasUrls: {event.mimeData().hasUrls()}\n'
|
|
337
|
+
f'possibleActions: {event.possibleActions()}\n'
|
|
338
|
+
f'proposedAction: {event.proposedAction()}')
|
|
339
|
+
self._log(s, lv.LOG_LEVEL_NORMAL)
|
|
340
|
+
possible = event.possibleActions()
|
|
341
|
+
acceptable = Qt.DropAction.CopyAction | Qt.DropAction.LinkAction
|
|
342
|
+
if (event.source() is None
|
|
343
|
+
and possible & acceptable
|
|
344
|
+
and event.mimeData().hasUrls()):
|
|
345
|
+
if event.proposedAction() & acceptable:
|
|
346
|
+
event.acceptProposedAction()
|
|
347
|
+
elif Qt.DropAction.LinkAction & possible:
|
|
348
|
+
event.setDropAction(Qt.DropAction.LinkAction)
|
|
349
|
+
event.accept()
|
|
350
|
+
elif Qt.DropAction.CopyAction & possible:
|
|
351
|
+
event.setDropAction(Qt.DropAction.CopyAction)
|
|
352
|
+
event.accept()
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def dropEvent(self, event):
|
|
356
|
+
possible = event.possibleActions()
|
|
357
|
+
acceptable = Qt.DropAction.CopyAction | Qt.DropAction.LinkAction
|
|
358
|
+
if event.proposedAction() & acceptable:
|
|
359
|
+
event.acceptProposedAction()
|
|
360
|
+
elif Qt.DropAction.LinkAction & possible:
|
|
361
|
+
event.setDropAction(Qt.DropAction.LinkAction)
|
|
362
|
+
event.accept()
|
|
363
|
+
elif Qt.DropAction.CopyAction & possible:
|
|
364
|
+
event.setDropAction(Qt.DropAction.CopyAction)
|
|
365
|
+
event.accept()
|
|
366
|
+
for url in event.mimeData().urls():
|
|
367
|
+
path = url.toLocalFile()
|
|
368
|
+
if not path:
|
|
369
|
+
path = url.toString()
|
|
370
|
+
self.file_selector.handle_drop(path)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def closeEvent(self, event):
|
|
374
|
+
reply = QMessageBox.question(
|
|
375
|
+
self,
|
|
376
|
+
'Confirm',
|
|
377
|
+
QUIT_CONFIRMATION_MESSAGE,
|
|
378
|
+
QMessageBox.StandardButton.Yes
|
|
379
|
+
| QMessageBox.StandardButton.No,
|
|
380
|
+
QMessageBox.StandardButton.No)
|
|
381
|
+
|
|
382
|
+
if reply == QMessageBox.StandardButton.Yes:
|
|
383
|
+
self.analyzed_audio.close()
|
|
384
|
+
self.export_window.quit_thread()
|
|
385
|
+
event.accept()
|
|
386
|
+
else:
|
|
387
|
+
event.ignore()
|
|
388
|
+
|
|
389
|
+
def _init_menubar(self):
|
|
390
|
+
menubar = self.menuBar()
|
|
391
|
+
|
|
392
|
+
file_menu = menubar.addMenu('&File')
|
|
393
|
+
file_menu.addAction(self.export_act)
|
|
394
|
+
file_menu.addSeparator()
|
|
395
|
+
file_menu.addAction(self.exit_act)
|
|
396
|
+
|
|
397
|
+
view_menu = menubar.addMenu('&View')
|
|
398
|
+
view_menu.addAction(self.show_log_plot_act)
|
|
399
|
+
view_menu.addAction(self.show_linear_plot_act)
|
|
400
|
+
view_menu.addSeparator()
|
|
401
|
+
view_menu.addAction(self.log_viewer_act.menu())
|
|
402
|
+
|
|
403
|
+
option_menu = menubar.addMenu('&Options')
|
|
404
|
+
option_menu.addAction(self.showhidden_act)
|
|
405
|
+
option_menu.addAction(self.play_start_end_act)
|
|
406
|
+
option_menu.addAction(self.toggle_options_panel_act)
|
|
407
|
+
option_menu.addSeparator()
|
|
408
|
+
option_menu.addAction(self.device_window_act)
|
|
409
|
+
option_menu.addSeparator()
|
|
410
|
+
option_menu.addAction(self.debug_drag_act)
|
|
411
|
+
|
|
412
|
+
go_menu = menubar.addMenu('&Go')
|
|
413
|
+
go_menu.addAction(self.file_selector.up_act.menu())
|
|
414
|
+
go_menu.addAction(self.file_selector.back_act.menu())
|
|
415
|
+
go_menu.addAction(self.file_selector.forward_act.menu())
|
|
416
|
+
go_menu.addAction(self.file_selector.home_act.menu())
|
|
417
|
+
|
|
418
|
+
help_menu = menubar.addMenu('&Help')
|
|
419
|
+
help_menu.addAction(self.about_act)
|
|
420
|
+
|
|
421
|
+
self.menubar = menubar
|
|
422
|
+
|
|
423
|
+
def _init_statusbar(self):
|
|
424
|
+
self.prog_bar = QProgressBar()
|
|
425
|
+
self.prog_bar.setRange(0, 100)
|
|
426
|
+
sp = QSizePolicy()
|
|
427
|
+
self.prog_bar.setSizePolicy(sp)
|
|
428
|
+
self.statusBar().addPermanentWidget(self.prog_bar, 2)
|
|
429
|
+
self.cancel_button = QToolButton()
|
|
430
|
+
self.cancel_button.setDefaultAction(self.cancel_act)
|
|
431
|
+
self.statusBar().addPermanentWidget(self.cancel_button)
|
|
432
|
+
self.prog_bar.hide()
|
|
433
|
+
self.cancel_button.hide()
|
|
434
|
+
self.cancel_act.setEnabled(False)
|
|
435
|
+
self.statusBar().showMessage(INIT_STATUS_MESSAGE)
|
|
436
|
+
|
|
437
|
+
def _update_progbar(self, progress):
|
|
438
|
+
if progress >= 0:
|
|
439
|
+
self.prog_bar.setValue(int(progress * 100))
|
|
440
|
+
else:
|
|
441
|
+
self.prog_bar.reset()
|
|
442
|
+
|
|
443
|
+
def _update_statusbar(self, status):
|
|
444
|
+
self.statusBar().showMessage(status)
|
|
445
|
+
|
|
446
|
+
def _init_toolbar(self):
|
|
447
|
+
self.toolbar = self.addToolBar('Toolbar')
|
|
448
|
+
|
|
449
|
+
self.toolbar.addAction(self.toggle_toggle_options_panel_act)
|
|
450
|
+
self.toolbar.addAction(self.log_viewer_act.button())
|
|
451
|
+
|
|
452
|
+
self.toolbar.addSeparator()
|
|
453
|
+
|
|
454
|
+
self.toolbar.addAction(self.play_act)
|
|
455
|
+
self.pause_button = QToolButton()
|
|
456
|
+
self.pause_button.setDefaultAction(self.pause_act)
|
|
457
|
+
self.toolbar.addWidget(self.pause_button)
|
|
458
|
+
self.toolbar.addAction(self.stop_act)
|
|
459
|
+
self.toolbar.addAction(self.player_back_act)
|
|
460
|
+
self.toolbar.addAction(self.player_forward_act)
|
|
461
|
+
if DEBUG_BUTTON:
|
|
462
|
+
self.toolbar.addAction(self.debug_act)
|
|
463
|
+
|
|
464
|
+
lcd_style = ('QLCDNumber {'
|
|
465
|
+
f' background-color: {disp.DISPLAY_BG_COLOR.name()};'
|
|
466
|
+
f' color: {disp.DISPLAY_DATA_COLOR.name()};'
|
|
467
|
+
' }')
|
|
468
|
+
self.lcd1 = QLCDNumber()
|
|
469
|
+
self.lcd1.setDigitCount(5)
|
|
470
|
+
self.lcd1.setStyleSheet(lcd_style)
|
|
471
|
+
self.toolbar.addWidget(self.lcd1)
|
|
472
|
+
self.lcd1.setSegmentStyle(QLCDNumber.SegmentStyle.Flat)
|
|
473
|
+
self.lcd1.display('--:--')
|
|
474
|
+
|
|
475
|
+
self.slider = _Slider(Qt.Orientation.Horizontal)
|
|
476
|
+
self.slider.setRange(0, 100)
|
|
477
|
+
self.slider.setMinimumWidth(100)
|
|
478
|
+
self.slider.setTickPosition(QSlider.TickPosition.TicksBothSides)
|
|
479
|
+
self.toolbar.addWidget(self.slider)
|
|
480
|
+
|
|
481
|
+
self.lcd2 = QLCDNumber()
|
|
482
|
+
self.lcd2.setDigitCount(6)
|
|
483
|
+
self.toolbar.addWidget(self.lcd2)
|
|
484
|
+
self.lcd2.setStyleSheet(lcd_style)
|
|
485
|
+
self.lcd2.setSegmentStyle(QLCDNumber.SegmentStyle.Flat)
|
|
486
|
+
self.lcd2.display('---:--')
|
|
487
|
+
|
|
488
|
+
self.now_playing = QLabel()
|
|
489
|
+
self.now_playing.setTextFormat(Qt.TextFormat.PlainText)
|
|
490
|
+
self.now_playing.setMinimumWidth(30)
|
|
491
|
+
self.now_playing.setSizePolicy(QSizePolicy.Policy.Minimum,
|
|
492
|
+
QSizePolicy.Policy.Preferred)
|
|
493
|
+
self.toolbar.addWidget(self.now_playing)
|
|
494
|
+
|
|
495
|
+
spacer = QWidget()
|
|
496
|
+
spacer.setSizePolicy(QSizePolicy.Policy.Expanding,
|
|
497
|
+
QSizePolicy.Policy.Preferred)
|
|
498
|
+
self.toolbar.addWidget(spacer)
|
|
499
|
+
self.toolbar.addAction(self.exit_act)
|
|
500
|
+
|
|
501
|
+
def _toggle_toggle_options_panel(self):
|
|
502
|
+
if self.toggle_options_panel_act.isChecked():
|
|
503
|
+
self.toggle_options_panel_act.setChecked(False)
|
|
504
|
+
self._toggle_options_panel(False)
|
|
505
|
+
else:
|
|
506
|
+
self.toggle_options_panel_act.setChecked(True)
|
|
507
|
+
self._toggle_options_panel(True)
|
|
508
|
+
|
|
509
|
+
def _show_options_panel(self):
|
|
510
|
+
if not self.toggle_options_panel_act.isChecked():
|
|
511
|
+
self.toggle_options_panel_act.setChecked(True)
|
|
512
|
+
self._toggle_options_panel(True)
|
|
513
|
+
|
|
514
|
+
def _toggle_options_panel(self, checked):
|
|
515
|
+
if checked:
|
|
516
|
+
self.stack.setCurrentWidget(self.option_panel)
|
|
517
|
+
self.toggle_toggle_options_panel_act.setIcon(
|
|
518
|
+
QIcon(gcom.ICON_FILES))
|
|
519
|
+
else:
|
|
520
|
+
self.stack.setCurrentWidget(self.file_selector)
|
|
521
|
+
self.toggle_toggle_options_panel_act.setIcon(
|
|
522
|
+
QIcon(gcom.ICON_OPTIONS))
|
|
523
|
+
|
|
524
|
+
def _starting_processing(self):
|
|
525
|
+
self.cancel_act.setEnabled(True)
|
|
526
|
+
self.prog_bar.show()
|
|
527
|
+
self.cancel_button.show()
|
|
528
|
+
|
|
529
|
+
def _finished_processing(self):
|
|
530
|
+
self._pitch_change(self.option_panel.widgets[gcom.OPTION_PITCH].get())
|
|
531
|
+
self.prog_bar.hide()
|
|
532
|
+
self.cancel_button.hide()
|
|
533
|
+
self.cancel_act.setEnabled(False)
|
|
534
|
+
|
|
535
|
+
def _export(self):
|
|
536
|
+
self.export_window.show(self.analyzed_audio.current_selection,
|
|
537
|
+
self.analyzed_audio.current_listed_title,
|
|
538
|
+
self.analyzed_audio.current_options,
|
|
539
|
+
self.analyzed_audio.current_metadata)
|
|
540
|
+
|
|
541
|
+
def _starting_exporting(self):
|
|
542
|
+
self._exporting_in_progress = True
|
|
543
|
+
self.export_act.setEnabled(False)
|
|
544
|
+
|
|
545
|
+
def _finished_exporting(self):
|
|
546
|
+
self._exporting_in_progress = False
|
|
547
|
+
if com.mpv_error is not None:
|
|
548
|
+
self.export_act.setEnabled(True)
|
|
549
|
+
|
|
550
|
+
def _about_show(self):
|
|
551
|
+
self.about_window.show()
|
|
552
|
+
|
|
553
|
+
def _log_show(self):
|
|
554
|
+
self.log_viewer.show()
|
|
555
|
+
self.log_viewer_act.setIcon(QIcon.fromTheme(gcom.ICON_MESSAGE_LOG))
|
|
556
|
+
|
|
557
|
+
def _device_window_show(self):
|
|
558
|
+
try:
|
|
559
|
+
self.player.show_device_window()
|
|
560
|
+
except OSError:
|
|
561
|
+
self.device_window_act.setEnabled(False)
|
|
562
|
+
|
|
563
|
+
def _show_log_plot(self):
|
|
564
|
+
self.analyzed_audio.show_plot('log')
|
|
565
|
+
|
|
566
|
+
def _show_linear_plot(self):
|
|
567
|
+
self.analyzed_audio.show_plot('linear')
|
|
568
|
+
|
|
569
|
+
def _log(self, message, level):
|
|
570
|
+
if level == lv.LOG_LEVEL_ERROR and not self.log_viewer.isVisible():
|
|
571
|
+
self.log_viewer_act.setIcon(QIcon.fromTheme(gcom.ICON_ALERT))
|
|
572
|
+
self.log_viewer.add_message(message + '\n', level)
|
|
573
|
+
|
|
574
|
+
def _handle_option_error(self, option):
|
|
575
|
+
self.option_panel.widgets[option].set_error()
|
|
576
|
+
self.option_panel.ensure_visible()
|
|
577
|
+
|
|
578
|
+
def _something_selected(self):
|
|
579
|
+
self._pitch_change(self.option_panel.widgets[gcom.OPTION_PITCH].get())
|
|
580
|
+
self.something_selected=True
|
|
581
|
+
self.option_panel.set_apply_enabled(True)
|
|
582
|
+
self.show_log_plot_act.setEnabled(True)
|
|
583
|
+
self.show_linear_plot_act.setEnabled(True)
|
|
584
|
+
if not self._exporting_in_progress and com.mpv_error is None:
|
|
585
|
+
self.export_act.setEnabled(True)
|
|
586
|
+
|
|
587
|
+
def _nothing_selected(self):
|
|
588
|
+
self.something_selected=False
|
|
589
|
+
self.option_panel.set_apply_enabled(False)
|
|
590
|
+
self.show_log_plot_act.setEnabled(False)
|
|
591
|
+
self.show_linear_plot_act.setEnabled(False)
|
|
592
|
+
self.export_act.setEnabled(False)
|
|
593
|
+
|
|
594
|
+
def _play_start_end_toggled(self, checked):
|
|
595
|
+
if checked:
|
|
596
|
+
start = self._currently_playing_start
|
|
597
|
+
end = self._currently_playing_end
|
|
598
|
+
else:
|
|
599
|
+
start = None
|
|
600
|
+
end = None
|
|
601
|
+
self.player.update_corrections(None, None, start, end)
|
|
602
|
+
|
|
603
|
+
def _play(self):
|
|
604
|
+
path = self.analyzed_audio.current_selection
|
|
605
|
+
if path:
|
|
606
|
+
if self.player.get_currently_playing() != path:
|
|
607
|
+
options = self.analyzed_audio.current_options
|
|
608
|
+
self.duration = self.analyzed_audio.current_duration
|
|
609
|
+
self._currently_playing_start = options[gcom.OPTION_START]
|
|
610
|
+
self._currently_playing_end = options[gcom.OPTION_END]
|
|
611
|
+
if self.play_start_end_act.isChecked():
|
|
612
|
+
start = options[gcom.OPTION_START]
|
|
613
|
+
end = options[gcom.OPTION_END]
|
|
614
|
+
else:
|
|
615
|
+
start = None
|
|
616
|
+
end = None
|
|
617
|
+
try:
|
|
618
|
+
self.player.play(path,
|
|
619
|
+
self.duration,
|
|
620
|
+
options[gcom.OPTION_PITCH],
|
|
621
|
+
options[gcom.OPTION_TEMPO],
|
|
622
|
+
start,
|
|
623
|
+
end,
|
|
624
|
+
)
|
|
625
|
+
except OSError:
|
|
626
|
+
self.play_act.setEnabled(False)
|
|
627
|
+
return
|
|
628
|
+
self.now_playing.setText(self.analyzed_audio.current_title)
|
|
629
|
+
if self.duration is not None:
|
|
630
|
+
self.slider.setTickInterval(
|
|
631
|
+
6000//max(int(self.duration), 1))
|
|
632
|
+
elif self.slider.value() >= 99:
|
|
633
|
+
self.player.set_percent(0)
|
|
634
|
+
self.option_panel.start_end_enable(True)
|
|
635
|
+
self.player.set_pause(False)
|
|
636
|
+
|
|
637
|
+
def _pause(self):
|
|
638
|
+
self.player.toggle_pause()
|
|
639
|
+
|
|
640
|
+
def _update_pause(self, pause):
|
|
641
|
+
self.pause_button.setDown(pause)
|
|
642
|
+
|
|
643
|
+
def _stop(self):
|
|
644
|
+
self.player.stop()
|
|
645
|
+
self.now_playing.clear()
|
|
646
|
+
self.option_panel.start_end_enable(False)
|
|
647
|
+
|
|
648
|
+
def _back(self):
|
|
649
|
+
self.player.back()
|
|
650
|
+
|
|
651
|
+
def _forward(self):
|
|
652
|
+
self.player.forward()
|
|
653
|
+
|
|
654
|
+
def _debug(self):
|
|
655
|
+
pass
|
|
656
|
+
|
|
657
|
+
def _update_time_pos(self, is_valid, time_pos):
|
|
658
|
+
if is_valid:
|
|
659
|
+
t = min(time_pos, 5999)
|
|
660
|
+
display = f'{t//60:0>2}:{t%60:0>2}'
|
|
661
|
+
else:
|
|
662
|
+
display = '--:--'
|
|
663
|
+
self.lcd1.display(display)
|
|
664
|
+
|
|
665
|
+
def _enable_slider_update(self):
|
|
666
|
+
self._slider_update_enabled = True
|
|
667
|
+
|
|
668
|
+
def _disable_slider_update(self):
|
|
669
|
+
self._slider_update_enabled = False
|
|
670
|
+
|
|
671
|
+
def _update_percent(self, percent):
|
|
672
|
+
if self._slider_update_enabled and not self.slider.isSliderDown():
|
|
673
|
+
self.slider.setValue(percent)
|
|
674
|
+
if self.duration is None:
|
|
675
|
+
self.duration = self.player.get_duration()
|
|
676
|
+
if self.duration is not None:
|
|
677
|
+
self.slider.setTickInterval(6000//max(int(self.duration), 1))
|
|
678
|
+
|
|
679
|
+
def _set_percent(self):
|
|
680
|
+
self.player.set_percent(self.slider.value())
|
|
681
|
+
self._enable_slider_update()
|
|
682
|
+
|
|
683
|
+
def _update_time_rem(self, is_valid, time_rem):
|
|
684
|
+
if is_valid:
|
|
685
|
+
t = min(time_rem, 5999)
|
|
686
|
+
display = f'-{t//60:0>2}:{t%60:0>2}'
|
|
687
|
+
else:
|
|
688
|
+
display = '---:--'
|
|
689
|
+
self.lcd2.display(display)
|
|
690
|
+
|
|
691
|
+
def _update_now_playing(self, title, result_rows=None):
|
|
692
|
+
path = self.analyzed_audio.current_selection
|
|
693
|
+
if path and title != ' ':
|
|
694
|
+
if self.player.get_currently_playing() == path:
|
|
695
|
+
self.now_playing.setText(title)
|
|
696
|
+
|
|
697
|
+
def _update_corrections(self, options, reread_requested):
|
|
698
|
+
if (self.analyzed_audio.current_selection
|
|
699
|
+
== self.player.get_currently_playing()):
|
|
700
|
+
self._currently_playing_start = options[gcom.OPTION_START]
|
|
701
|
+
self._currently_playing_end = options[gcom.OPTION_END]
|
|
702
|
+
if self.play_start_end_act.isChecked():
|
|
703
|
+
start = options[gcom.OPTION_START]
|
|
704
|
+
end = options[gcom.OPTION_END]
|
|
705
|
+
else:
|
|
706
|
+
start = None
|
|
707
|
+
end = None
|
|
708
|
+
self.player.update_corrections(options[gcom.OPTION_PITCH],
|
|
709
|
+
options[gcom.OPTION_TEMPO],
|
|
710
|
+
start,
|
|
711
|
+
end,
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
def _pitch_change(self, factor):
|
|
715
|
+
if self.something_selected:
|
|
716
|
+
options = self.analyzed_audio.current_options
|
|
717
|
+
relative_change = factor / options[gcom.OPTION_PITCH]
|
|
718
|
+
cents = com.ratio_to_cents(relative_change)
|
|
719
|
+
self.display.update_ghost_offset(cents)
|
|
720
|
+
|
|
721
|
+
def _set_start_now(self):
|
|
722
|
+
now = self.player.get_current_position()
|
|
723
|
+
self.option_panel.set_start(now)
|
|
724
|
+
|
|
725
|
+
def _set_end_now(self):
|
|
726
|
+
now = self.player.get_current_position()
|
|
727
|
+
self.option_panel.set_end(now)
|
|
728
|
+
|
|
729
|
+
def _initUI(self):
|
|
730
|
+
self.resize(800, 850)
|
|
731
|
+
self.setWindowTitle(APP_TITLE)
|
|
732
|
+
self.setWindowIcon(QIcon(gcom.APP_ICON))
|
|
733
|
+
|
|
734
|
+
option_panel = op.OptionPanel(self.args)
|
|
735
|
+
self.option_panel = option_panel
|
|
736
|
+
file_selector = fs.FileSelector(option_panel)
|
|
737
|
+
self.file_selector = file_selector
|
|
738
|
+
analyzed_audio = ga.AnalyzedAudio()
|
|
739
|
+
self.analyzed_audio = analyzed_audio
|
|
740
|
+
display = disp.Display()
|
|
741
|
+
self.display = display
|
|
742
|
+
|
|
743
|
+
self.player = pl.Player(self)
|
|
744
|
+
if com.mpv_error is not None:
|
|
745
|
+
self.play_act.setEnabled(False)
|
|
746
|
+
self.stop_act.setEnabled(False)
|
|
747
|
+
self.pause_act.setEnabled(False)
|
|
748
|
+
self.player_back_act.setEnabled(False)
|
|
749
|
+
self.player_forward_act.setEnabled(False)
|
|
750
|
+
self.device_window_act.setEnabled(False)
|
|
751
|
+
|
|
752
|
+
option_panel.set_default_options()
|
|
753
|
+
|
|
754
|
+
self.cancel_act.triggered.connect(analyzed_audio.cancel)
|
|
755
|
+
|
|
756
|
+
stack = QStackedWidget()
|
|
757
|
+
self.stack = stack
|
|
758
|
+
stack.addWidget(file_selector)
|
|
759
|
+
stack.addWidget(option_panel)
|
|
760
|
+
|
|
761
|
+
hsplitter = QSplitter(Qt.Orientation.Horizontal)
|
|
762
|
+
hsplitter.addWidget(analyzed_audio)
|
|
763
|
+
hsplitter.insertWidget(0, stack)
|
|
764
|
+
|
|
765
|
+
vsplitter = QSplitter(Qt.Orientation.Vertical)
|
|
766
|
+
vsplitter.addWidget(display)
|
|
767
|
+
vsplitter.addWidget(hsplitter)
|
|
768
|
+
|
|
769
|
+
self.setCentralWidget(vsplitter)
|
|
770
|
+
|
|
771
|
+
self._init_menubar()
|
|
772
|
+
self._init_statusbar()
|
|
773
|
+
self._init_toolbar()
|
|
774
|
+
|
|
775
|
+
self.export_window = ex.ExportWindow(self)
|
|
776
|
+
self.export_window.set_dir(self.file_selector.model.rootDirectory())
|
|
777
|
+
|
|
778
|
+
file_selector.SelectForAnalysis.connect(analyzed_audio.add_audio)
|
|
779
|
+
self.showhidden_act.toggled.connect(file_selector._set_show_hidden)
|
|
780
|
+
file_selector.UpdateStatusbar.connect(self._update_statusbar)
|
|
781
|
+
file_selector.model.rootPathChanged.connect(self.export_window.set_dir)
|
|
782
|
+
self.analyzed_audio.UpdateStatusbar.connect(self._update_statusbar)
|
|
783
|
+
self.analyzed_audio.UpdateProgbar.connect(self._update_progbar)
|
|
784
|
+
self.analyzed_audio.Starting.connect(self._starting_processing)
|
|
785
|
+
self.analyzed_audio.Finished.connect(self._finished_processing)
|
|
786
|
+
self.analyzed_audio.AddToLog.connect(self._log)
|
|
787
|
+
self.analyzed_audio.OptionError.connect(self._handle_option_error)
|
|
788
|
+
self.analyzed_audio.SomethingSelected.connect(self._something_selected)
|
|
789
|
+
self.analyzed_audio.NothingSelected.connect(self._nothing_selected)
|
|
790
|
+
self.option_panel.PushOptions.connect(analyzed_audio.change_options)
|
|
791
|
+
self.option_panel.PushOptions.connect(self._update_corrections)
|
|
792
|
+
self.option_panel.PitchChange.connect(self._pitch_change)
|
|
793
|
+
self.option_panel.widgets[gcom.OPTION_START].button_clicked.connect(
|
|
794
|
+
self._set_start_now)
|
|
795
|
+
self.option_panel.widgets[gcom.OPTION_END].button_clicked.connect(
|
|
796
|
+
self._set_end_now)
|
|
797
|
+
self.option_panel.PayAttentionToMe.connect(self._show_options_panel)
|
|
798
|
+
self.player.TickPos.connect(self._update_time_pos)
|
|
799
|
+
self.player.TickRem.connect(self._update_time_rem)
|
|
800
|
+
self.player.Percent.connect(self._update_percent)
|
|
801
|
+
self.player.PauseStatus.connect(self._update_pause)
|
|
802
|
+
self.player.AddToLog.connect(self._log)
|
|
803
|
+
self.slider.MousePressed.connect(self._disable_slider_update)
|
|
804
|
+
self.slider.MouseReleased.connect(self._set_percent)
|
|
805
|
+
self.player_back_act.triggered.connect(self._back)
|
|
806
|
+
self.player_forward_act.triggered.connect(self._forward)
|
|
807
|
+
self.export_window.AddToLog.connect(self._log)
|
|
808
|
+
self.export_window.Starting.connect(self._starting_exporting)
|
|
809
|
+
self.export_window.Finished.connect(self._finished_exporting)
|
|
810
|
+
|
|
811
|
+
analyzed_audio.DisplayResult.connect(display.update_data,
|
|
812
|
+
Qt.ConnectionType.QueuedConnection)
|
|
813
|
+
analyzed_audio.DisplayResult.connect(self._update_now_playing)
|
|
814
|
+
analyzed_audio.PushOptions.connect(option_panel.set_options)
|
|
815
|
+
|
|
816
|
+
self.about_window = _AboutWindow(self)
|
|
817
|
+
|
|
818
|
+
self.log_viewer = lv.LogViewer(self)
|
|
819
|
+
|
|
820
|
+
self.show()
|
|
821
|
+
|
|
822
|
+
hsplitter.moveSplitter(500, 1)
|
|
823
|
+
vsplitter.moveSplitter(342, 1)
|
|
824
|
+
|
|
825
|
+
while self.message_queue:
|
|
826
|
+
message = self.message_queue.popleft()
|
|
827
|
+
self._log(message.msg, message.level)
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
class _Message():
|
|
831
|
+
def __init__(self, msg, level):
|
|
832
|
+
self.msg = msg
|
|
833
|
+
self.level = level
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
class _MessageQueue(deque):
|
|
837
|
+
def add_msg(self, msg, level=eh.NORMAL):
|
|
838
|
+
self.append(_Message(msg, level))
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def main():
|
|
842
|
+
parser = ap.get_arg_parser(version=VERSION_STRING,
|
|
843
|
+
description=DESCRIPTION,
|
|
844
|
+
gui_mode=True)
|
|
845
|
+
cli_args, unparsed = parser.parse_known_args_gm()
|
|
846
|
+
message_queue = _MessageQueue()
|
|
847
|
+
args = ap.merge_args(cli_args, print_msg=message_queue.add_msg)
|
|
848
|
+
if not ap.validate(args):
|
|
849
|
+
return 2
|
|
850
|
+
|
|
851
|
+
app = QApplication(sys.argv[:1] + unparsed)
|
|
852
|
+
|
|
853
|
+
# Needed to make libmpv work
|
|
854
|
+
if os.name != 'nt':
|
|
855
|
+
import locale
|
|
856
|
+
locale.setlocale(locale.LC_NUMERIC, 'C')
|
|
857
|
+
|
|
858
|
+
ui = _MainUI(args, message_queue)
|
|
859
|
+
|
|
860
|
+
return app.exec()
|
|
861
|
+
|
|
862
|
+
if __name__ == '__main__':
|
|
863
|
+
sys.exit(main())
|