Initial commit: RecorderStudio:PyQt5 高保真录音软件,无损 WAV / WASAPI 独占、BS.1770 响度与 ffmpeg 交叉验证
This commit is contained in:
+173
@@ -0,0 +1,173 @@
|
||||
"""图形界面入口:``python -m recorder.gui``(或双击 启动录音机.bat)。
|
||||
|
||||
负责三件事:High-DPI 适配、深色主题样式表、以及缺依赖时的友好提示。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
QSS = """
|
||||
* { font-family: "Microsoft YaHei UI", "Microsoft YaHei", "Segoe UI", sans-serif; }
|
||||
QWidget { background: #0F1218; color: #E8ECF4; font-size: 12px; }
|
||||
QMainWindow, QDialog { background: #0F1218; }
|
||||
QMenuBar { background: #12161D; border-bottom: 1px solid #2A313D; }
|
||||
QMenuBar::item { padding: 5px 12px; background: transparent; }
|
||||
QMenuBar::item:selected { background: #1D222C; border-radius: 4px; }
|
||||
QMenu { background: #161A22; border: 1px solid #2A313D; padding: 4px; }
|
||||
QMenu::item { padding: 6px 22px; border-radius: 4px; }
|
||||
QMenu::item:selected { background: #23303F; }
|
||||
QGroupBox {
|
||||
background: #161A22;
|
||||
border: 1px solid #2A313D;
|
||||
border-radius: 8px;
|
||||
margin-top: 12px;
|
||||
padding-top: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
QGroupBox::title {
|
||||
subcontrol-origin: margin;
|
||||
left: 12px;
|
||||
padding: 0 6px;
|
||||
color: #9FB0CB;
|
||||
}
|
||||
QFrame#card {
|
||||
background: #161A22;
|
||||
border: 1px solid #2A313D;
|
||||
border-radius: 10px;
|
||||
}
|
||||
QLabel { background: transparent; }
|
||||
QPushButton {
|
||||
background: #232A36;
|
||||
border: 1px solid #323A48;
|
||||
border-radius: 6px;
|
||||
padding: 6px 12px;
|
||||
color: #E8ECF4;
|
||||
}
|
||||
QPushButton:hover { background: #2A3341; border-color: #3E4A5C; }
|
||||
QPushButton:pressed { background: #1B2129; }
|
||||
QPushButton:disabled { color: #5C6577; background: #1A1F27; border-color: #262D38; }
|
||||
QComboBox, QLineEdit, QSpinBox, QDoubleSpinBox, QPlainTextEdit {
|
||||
background: #1B2029;
|
||||
border: 1px solid #2E3644;
|
||||
border-radius: 6px;
|
||||
padding: 5px 8px;
|
||||
selection-background-color: #2C5F9E;
|
||||
}
|
||||
QComboBox:hover, QLineEdit:hover, QSpinBox:hover, QDoubleSpinBox:hover {
|
||||
border-color: #3E4A5C;
|
||||
}
|
||||
QComboBox::drop-down { border: none; width: 18px; }
|
||||
QComboBox QAbstractItemView {
|
||||
background: #1B2029;
|
||||
border: 1px solid #2E3644;
|
||||
selection-background-color: #23303F;
|
||||
outline: none;
|
||||
}
|
||||
QComboBox::down-arrow {
|
||||
image: none;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 5px solid #8B94A7;
|
||||
margin-right: 6px;
|
||||
}
|
||||
QCheckBox { spacing: 7px; }
|
||||
QCheckBox::indicator {
|
||||
width: 15px; height: 15px;
|
||||
border: 1px solid #3A4352; border-radius: 4px;
|
||||
background: #1B2029;
|
||||
}
|
||||
QCheckBox::indicator:checked {
|
||||
background: #4EA1FF; border-color: #4EA1FF;
|
||||
}
|
||||
QSlider::groove:horizontal {
|
||||
height: 4px; background: #2A313D; border-radius: 2px;
|
||||
}
|
||||
QSlider::sub-page:horizontal { background: #4EA1FF; border-radius: 2px; }
|
||||
QSlider::handle:horizontal {
|
||||
background: #E8ECF4; width: 14px; margin: -6px 0; border-radius: 7px;
|
||||
}
|
||||
QScrollArea { background: transparent; border: none; }
|
||||
QScrollBar:vertical { background: #12161D; width: 10px; border-radius: 5px; }
|
||||
QScrollBar::handle:vertical { background: #333B49; border-radius: 5px; min-height: 30px; }
|
||||
QScrollBar::handle:vertical:hover { background: #414B5C; }
|
||||
QScrollBar::add-line, QScrollBar::sub-line { height: 0; }
|
||||
QStatusBar { background: #12161D; border-top: 1px solid #2A313D; color: #8B94A7; }
|
||||
QStatusBar QLabel { color: #8B94A7; }
|
||||
QSplitter::handle { background: transparent; width: 6px; }
|
||||
QProgressBar {
|
||||
background: #1B2029; border: 1px solid #2E3644; border-radius: 6px;
|
||||
text-align: center; color: #E8ECF4; height: 16px;
|
||||
}
|
||||
QProgressBar::chunk { background: #4EA1FF; border-radius: 5px; }
|
||||
QToolTip {
|
||||
background: #1B2029; color: #E8ECF4; border: 1px solid #3E4A5C;
|
||||
padding: 4px 6px; border-radius: 4px;
|
||||
}
|
||||
QTabBar::tab {
|
||||
background: #1B2029; padding: 6px 14px; border: 1px solid #2E3644;
|
||||
border-bottom: none; border-top-left-radius: 6px; border-top-right-radius: 6px;
|
||||
}
|
||||
QTabBar::tab:selected { background: #232A36; }
|
||||
"""
|
||||
|
||||
|
||||
def _check_dependencies() -> str | None:
|
||||
"""返回缺失依赖的说明,全部就绪时返回 None。"""
|
||||
missing = []
|
||||
try:
|
||||
import numpy # noqa: F401 (仅用于探测依赖是否就绪)
|
||||
except Exception:
|
||||
missing.append("numpy(数值运算)")
|
||||
try:
|
||||
import PyQt5 # noqa: F401 (仅用于探测依赖是否就绪)
|
||||
except Exception:
|
||||
missing.append("PyQt5(图形界面)")
|
||||
from . import engine
|
||||
if engine.sd is None:
|
||||
missing.append("sounddevice(PortAudio 音频后端)")
|
||||
if not missing:
|
||||
return None
|
||||
return ("缺少以下依赖,无法启动图形界面:\n\n · " + "\n · ".join(missing)
|
||||
+ "\n\n请运行:python install_deps.py\n"
|
||||
"(在项目目录下执行,会把依赖装到项目内的 _vendor 目录)")
|
||||
|
||||
|
||||
def run(argv: list[str] | None = None) -> int:
|
||||
argv = list(sys.argv if argv is None else argv)
|
||||
|
||||
problem = _check_dependencies()
|
||||
if problem:
|
||||
print(problem, file=sys.stderr)
|
||||
try:
|
||||
from PyQt5.QtWidgets import QApplication as _QA, QMessageBox
|
||||
except Exception:
|
||||
return 2
|
||||
_app = _QA(argv)
|
||||
QMessageBox.critical(None, "缺少依赖", problem)
|
||||
return 2
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
# High-DPI 相关属性必须在 QApplication 实例化之前设置
|
||||
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
|
||||
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
|
||||
|
||||
from .window import MainWindow
|
||||
|
||||
app = QApplication(argv)
|
||||
app.setApplicationName("RecorderStudio")
|
||||
app.setOrganizationName("RecorderStudio")
|
||||
app.setStyleSheet(QSS)
|
||||
app.setFont(QFont("Microsoft YaHei UI", 9))
|
||||
|
||||
win = MainWindow()
|
||||
win.show()
|
||||
return app.exec_()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run())
|
||||
Reference in New Issue
Block a user