加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
DD监控室.py 61.15 KB
一键复制 编辑 原始数据 按行查看 历史
执明神君 提交于 2022-01-28 21:20 . 改为pyside2框架
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340
"""
DD监控室主界面进程 包含对所有子页面的初始化、排版管理
同时卡片和播放窗口的交互需要通过主界面线程通信
以及软件启动和退出后的一些操作
新增全局鼠标坐标跟踪 用于刷新鼠标交互效果
"""
import log
# 找不到 dll
# https://stackoverflow.com/questions/54110504/dynlib-dll-was-no-found-when-the-application-was-frozen-when-i-make-a-exe-fil
import ctypes
import os
import sys
import json
import time
import shutil
import logging
import platform
import threading
from PySide2.QtWidgets import * # QAction,QFileDialog
from PySide2.QtGui import * # QIcon,QPixmap
from PySide2.QtCore import * # QSize
from LayoutPanel import LayoutSettingPanel
# from VideoWidget import PushButton, Slider, VideoWidget # 已弃用
from VideoWidget_vlc import PushButton, Slider, VideoWidget
from LiverSelect import LiverPanel
from pay import pay
import codecs
import dns.resolver
from ReportException import thraedingExceptionHandler, uncaughtExceptionHandler,\
unraisableExceptionHandler, loggingSystemInfo
from danmu import TextOpation, ToolButton
# 程序所在路径
application_path = ""
def _translate(context, text, disambig):
return QApplication.translate(context, text, disambig)
class ControlWidget(QWidget):
heightValue = Signal(int)
def __init__(self):
super(ControlWidget, self).__init__()
def resizeEvent(self, QResizeEvent):
self.heightValue.emit(self.height())
class ScrollArea(QScrollArea):
multipleTimes = Signal(int)
addLiver = Signal()
clearAll = Signal()
def __init__(self):
super(ScrollArea, self).__init__()
self.multiple = self.width() // 169
self.horizontalScrollBar().setVisible(False)
def sizeHint(self):
return QSize(100, 90)
def mouseReleaseEvent(self, QMouseEvent):
if QMouseEvent.button() == Qt.RightButton:
menu = QMenu()
addLiver = menu.addAction('添加直播间')
menu.addSeparator() # 添加分割线,防止误操作
clearAll = menu.addAction('清空')
action = menu.exec_(self.mapToGlobal(QMouseEvent.pos()))
if action == addLiver:
self.addLiver.emit()
elif action == clearAll:
self.clearAll.emit()
def wheelEvent(self, QEvent):
if QEvent.angleDelta().y() < 0:
value = self.verticalScrollBar().value()
self.verticalScrollBar().setValue(value + 80)
elif QEvent.angleDelta().y() > 0:
value = self.verticalScrollBar().value()
self.verticalScrollBar().setValue(value - 80)
def resizeEvent(self, QResizeEvent):
multiple = self.width() // 169
if multiple and multiple != self.multiple: # 按卡片长度的倍数调整且不为0
self.multiple = multiple
self.multipleTimes.emit(multiple)
class DockWidget(QDockWidget):
def __init__(self, title):
super(DockWidget, self).__init__()
self.setWindowTitle(title)
self.setObjectName(f'dock-{title}')
self.setFloating(False)
self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea)
class StartLiveWindow(QWidget):
"""开播提醒弹窗"""
def __init__(self):
super(StartLiveWindow, self).__init__()
self.setWindowTitle('开播提醒')
self.setWindowFlag(Qt.WindowStaysOnTopHint)
self.resize(240, 70)
self.tipLabel = QLabel()
self.tipLabel.setStyleSheet('color:#293038;background-color:#eeeeee')
self.tipLabel.setFont(QFont('微软雅黑', 15, QFont.Bold))
layout = QGridLayout(self)
layout.setContentsMargins(3, 3, 3, 3)
layout.addWidget(self.tipLabel)
self.hideTimer = QTimer()
self.hideTimer.setInterval(10000)
self.hideTimer.timeout.connect(self.hide) # 10秒倒计时结束隐藏
def mousePressEvent(self, QMouseEvent): # 点击的话就停止倒计时
self.hideTimer.stop()
class CacheSetting(QWidget):
"""缓存设置窗口"""
setting = Signal(list)
def __init__(self):
super(CacheSetting, self).__init__()
self.resize(400, 200)
self.setWindowTitle('缓存设置')
layout = QGridLayout(self)
layout.addWidget(QLabel('最大缓存(GB)'), 0, 0, 1, 1)
self.maxCacheEdit = QLineEdit()
self.maxCacheEdit.setValidator(QIntValidator(1, 9))
layout.addWidget(self.maxCacheEdit, 0, 1, 1, 3)
layout.addWidget(QLabel('缓存自动备份至以上路径 (若不填则默认删除)'), 2, 0, 1, 3)
selectButton = QPushButton('备份路径')
selectButton.setStyleSheet('background-color:#31363b;border-width:1px')
selectButton.clicked.connect(self.selectCopyPath)
layout.addWidget(selectButton, 1, 0, 1, 1)
self.savePathEdit = QLineEdit()
layout.addWidget(self.savePathEdit, 1, 1, 1, 3)
okButton = QPushButton('OK')
okButton.setStyleSheet('background-color:#3daee9;border-width:1px')
okButton.clicked.connect(self.sendSetting)
layout.addWidget(okButton, 2, 3, 1, 1)
def selectCopyPath(self):
savePath = QFileDialog.getExistingDirectory(self, "选择备份缓存路径", None, QFileDialog.ShowDirsOnly)
if savePath:
self.savePathEdit.setText(savePath)
def sendSetting(self):
self.setting.emit([self.maxCacheEdit.text(), self.savePathEdit.text()])
self.hide()
class Version(QWidget):
"""版本说明窗口"""
def __init__(self):
super(Version, self).__init__()
self.resize(350, 150)
self.setWindowTitle('当前版本')
layout = QGridLayout(self)
layout.addWidget(QLabel('DD监控室 v2.9正式版'), 0, 0, 1, 2)
layout.addWidget(QLabel('by 神君Channel'), 1, 0, 1, 2)
layout.addWidget(QLabel('特别鸣谢:大锅饭 美东矿业 inkydragon 聪_哥'), 2, 0, 1, 2)
releases_url = QLabel('')
releases_url.setOpenExternalLinks(True)
releases_url.setText(_translate("MainWindow", "<html><head/><body><p><a href=\"https://space.bilibili.com/637783\">\
<span style=\" text-decoration: underline; color:#cccccc;\">https://space.bilibili.com/637783</span></a></p></body></html>", None))
layout.addWidget(releases_url, 1, 1, 1, 2, Qt.AlignRight)
checkButton = QPushButton('检查更新')
checkButton.setFixedHeight(40)
checkButton.clicked.connect(self.checkUpdate)
layout.addWidget(checkButton, 0, 2, 1, 1)
def checkUpdate(self):
QDesktopServices.openUrl(QUrl(r'https://github.com/zhimingshenjun/DD_Monitor/releases/tag/DD_Monitor'))
class HotKey(QWidget):
"""热键说明窗口"""
def __init__(self):
super(HotKey, self).__init__()
self.resize(350, 150)
self.setWindowTitle('快捷键')
layout = QGridLayout(self)
layout.addWidget(QLabel('F、f —— 全屏'), 0, 0)
layout.addWidget(QLabel('H、h —— 隐藏控制条'), 1, 0)
layout.addWidget(QLabel('M、m、S、s —— 除当前鼠标悬停窗口外全部静音'), 2, 0)
class DumpConfig(QThread):
"""导出配置"""
def __init__(self, config):
super(DumpConfig, self).__init__()
self.config = config
self.backupNumber = 1
def run(self):
try:
configJSONPath = os.path.join(application_path, r'utils/config.json')
with codecs.open(configJSONPath, 'w', encoding='utf-8', errors='ignore') as f:
f.write(json.dumps(self.config, ensure_ascii=False))
except Exception as e:
logging.error(str(e))
logging.exception('config.json 写入失败')
try: # 备份 防止存储config时崩溃
configJSONPath = os.path.join(application_path, r'utils/config_备份%d.json' % self.backupNumber)
self.backupNumber += 1
if self.backupNumber == 4:
self.backupNumber = 1
# with open(configJSONPath, 'w') as f:
# f.write(json.dumps(self.config, ensure_ascii=False))
with codecs.open(configJSONPath, 'w', encoding='utf-8', errors='ignore') as f:
f.write(json.dumps(self.config, ensure_ascii=False))
except Exception as e:
logging.error(str(e))
logging.exception('config_备份.json 备份配置文件写入失败')
class CheckDanmmuProvider(QThread):
"""检查弹幕服务器域名解析状态"""
def __init__(self):
super(CheckDanmmuProvider,self).__init__()
def run(self):
try:
anwsers = dns.resolver.resolve('broadcastlv.chat.bilibili.com', 'A')
danmu_ip = anwsers[0].to_text()
logging.info("弹幕IP: %s" % danmu_ip)
except Exception as e:
logging.error("解析弹幕域名失败")
logging.error(str(e))
class MainWindow(QMainWindow):
"""主窗口"""
def __init__(self, cacheFolder, progressBar, progressText):
super(MainWindow, self).__init__()
self.setWindowTitle('DD监控室')
self.resize(1600, 900)
self.maximumToken = True
self.soloToken = False # 记录静音除鼠标悬停窗口以外的其他所有窗口的标志位 True就是恢复所有房间声音
self.cacheFolder = cacheFolder
# ---- json 配置文件加载 ----
self.configJSONPath = os.path.join(
application_path, r'utils/config.json')
self.config = {}
# 读取默认的 config
if os.path.exists(self.configJSONPath):
if os.path.getsize(self.configJSONPath):
try:
with codecs.open(self.configJSONPath, 'r', encoding='utf-8', errors='ignore') as f:
self.config = json.loads(f.read())
# self.config = json.loads(open(self.configJSONPath).read())
except Exception as e:
logging.error(str(e))
logging.exception('json 配置读取失败')
self.config = {}
# 读取config失败 尝试读取备份
if not self.config:
for backupNumber in [1, 2, 3]: # 备份预设123
self.configJSONPath = os.path.join(
application_path, r'utils/config_备份%d.json' % backupNumber)
if os.path.exists(self.configJSONPath): # 如果备份文件存在
if os.path.getsize(self.configJSONPath): # 如过备份文件有效
try:
self.config = json.loads(
codecs.open(self.configJSONPath, 'r', encoding='utf-8', errors='ignore').read())
break
except Exception as e:
logging.error(str(e))
logging.exception('json 备份配置读取失败')
self.config = {}
# 如果能成功读取到config文件
if self.config:
while len(self.config['player']) < 16:
self.config['player'].append('0')
while len(self.config['volume']) < 16:
self.config['volume'].append(0)
while len(self.config['danmu']) < 16:
self.config['danmu'].append([True, 50, 1, 7, 0, "【 [ {", 10])
while len(self.config['muted']) < 16:
self.config['muted'].append(1)
while len(self.config['quality']) < 16:
self.config['quality'].append(80)
while len(self.config['audioChannel']) < 16:
self.config['audioChannel'].append(0)
self.config['player'] = list(map(str, self.config['player']))
if type(self.config['roomid']) == list:
roomIDList = self.config['roomid']
self.config['roomid'] = {}
for roomID in roomIDList:
self.config['roomid'][roomID] = False
if '0' in self.config['roomid']: # 过滤0房间号
del self.config['roomid']['0']
if 'quality' not in self.config:
self.config['quality'] = [80] * 16
if 'audioChannel' not in self.config:
self.config['audioChannel'] = [0] * 16
if 'translator' not in self.config:
self.config['translator'] = [True] * 16
for index, textSetting in enumerate(self.config['danmu']):
if type(textSetting) == bool:
self.config['danmu'][index] = [
textSetting, 20, 1, 7, 0, '【 [ {']
if 'hardwareDecode' not in self.config:
self.config['hardwareDecode'] = True
if 'maxCacheSize' not in self.config:
self.config['maxCacheSize'] = 2048000
logging.warning('最大缓存没有被设置,使用默认1G')
if 'saveCachePath' not in self.config:
self.config['saveCachePath'] = ''
logging.warning('默认缓存备份路径为空 即自动清空')
if 'startWithDanmu' not in self.config:
self.config['startWithDanmu'] = True
logging.warning('启动时加载弹幕没有被设置,默认加载')
if 'showStartLive' not in self.config:
self.config['showStartLive'] = True
for danmuConfig in self.config['danmu']:
if len(danmuConfig) == 6:
danmuConfig.append(10)
else: # 默认和备份 json 配置均读取失败
self.config = {
# 置顶显示
'roomid': {'21396545': False, '21402309': False, '22384516': False, '8792912': False},
'layout': [(0, 0, 1, 1), (0, 1, 1, 1), (1, 0, 1, 1), (1, 1, 1, 1)],
'player': ['0'] * 16,
'quality': [80] * 16,
'audioChannel': [0] * 16,
'muted': [1] * 16,
'volume': [50] * 16,
# 显示,透明,横向,纵向,类型,同传字符,字体大小
'danmu': [[True, 50, 1, 7, 0, '【 [ {', 10]] * 16,
'globalVolume': 30,
'control': True,
'hardwareDecode': True,
'maxCacheSize': 2048000,
'saveCachePath': '',
'startWithDanmu': True,
'showStartLive': True,
}
self.dumpConfig = DumpConfig(self.config)
# ---- 主窗体控件 ----
mainWidget = QWidget()
self.setCentralWidget(mainWidget)
# Grid 布局
self.mainLayout = QGridLayout(mainWidget)
self.mainLayout.setSpacing(0)
self.mainLayout.setContentsMargins(0, 0, 0, 0)
self.layoutSettingPanel = LayoutSettingPanel()
self.layoutSettingPanel.layoutConfig.connect(self.changeLayout)
self.version = Version()
self.cacheSetting = CacheSetting()
self.cacheSetting.maxCacheEdit.setText(
str(self.config['maxCacheSize'] // 1024000))
self.cacheSetting.savePathEdit.setText(self.config['saveCachePath'])
self.cacheSetting.setting.connect(self.setCache)
self.hotKey = HotKey()
self.pay = pay()
self.startLiveWindow = StartLiveWindow()
# ---- 内嵌/弹出播放器初始化 ----
self.videoWidgetList = []
self.popVideoWidgetList = []
vlcProgressCounter = 1
for i in range(16):
if len(self.config['danmu'][i]) < 8:
self.config['danmu'][i].append(3)
volume = self.config['volume'][i]
progressText.setText('设置第%s个主层播放器...' % str(i + 1))
self.videoWidgetList.append(VideoWidget(i, volume, cacheFolder, textSetting=self.config['danmu'][i],
maxCacheSize=self.config['maxCacheSize'],
saveCachePath=self.config['saveCachePath'],
startWithDanmu=self.config['startWithDanmu'],
hardwareDecode=self.config['hardwareDecode']))
vlcProgressCounter += 1
progressBar.setValue(vlcProgressCounter)
self.videoWidgetList[i].mutedChanged.connect(self.mutedChanged)
self.videoWidgetList[i].volumeChanged.connect(self.volumeChanged)
self.videoWidgetList[i].addMedia.connect(self.addMedia)
self.videoWidgetList[i].deleteMedia.connect(self.deleteMedia)
self.videoWidgetList[i].exchangeMedia.connect(self.exchangeMedia)
# self.videoWidgetList[i].setDanmu.connect(self.setDanmu) # 硬盘io过高 屏蔽掉 退出的时候统一保存
# self.videoWidgetList[i].setTranslator.connect(self.setTranslator) # 已废弃
self.videoWidgetList[i].changeQuality.connect(self.setQuality)
# self.videoWidgetList[i].changeAudioChannel.connect(self.setAudioChannel) # 硬盘io过高 屏蔽掉 退出的时候统一保存
self.videoWidgetList[i].popWindow.connect(self.popWindow)
self.videoWidgetList[i].hideBarKey.connect(self.openControlPanel)
self.videoWidgetList[i].fullScreenKey.connect(self.fullScreen)
self.videoWidgetList[i].muteExceptKey.connect(self.muteExcept)
self.videoWidgetList[i].mediaMute(
self.config['muted'][i], emit=False)
self.videoWidgetList[i].slider.setValue(self.config['volume'][i])
self.videoWidgetList[i].quality = self.config['quality'][i]
self.videoWidgetList[i].audioChannel = self.config['audioChannel'][i]
self.popVideoWidgetList.append(VideoWidget(i + 16, volume, cacheFolder, True, '悬浮窗', [1280, 720],
maxCacheSize=self.config['maxCacheSize'],
saveCachePath=self.config['saveCachePath'],
startWithDanmu=self.config['startWithDanmu'],
hardwareDecode=self.config['hardwareDecode']))
self.popVideoWidgetList[i].closePopWindow.connect(
self.closePopWindow)
vlcProgressCounter += 1
progressBar.setValue(vlcProgressCounter)
progressText.setText('设置第%s个悬浮窗播放器...' % str(i + 1))
app.processEvents()
logging.info("VLC设置完毕 %s / 16" % str(i + 1))
# 设置所有播放器布局
self.setPlayer()
self.controlDock = DockWidget('控制条')
self.controlDock.setFixedWidth(178)
self.addDockWidget(Qt.TopDockWidgetArea, self.controlDock)
self.controlWidget = ControlWidget()
self.controlWidget.heightValue.connect(self.showAddButton)
self.controlDock.setWidget(self.controlWidget)
self.controlBarLayout = QGridLayout(self.controlWidget)
self.globalPlayToken = True
self.play = PushButton(self.style().standardIcon(QStyle.SP_MediaPause))
self.play.clicked.connect(self.globalMediaPlay)
self.controlBarLayout.addWidget(self.play, 0, 0, 1, 1)
self.reload = PushButton(
self.style().standardIcon(QStyle.SP_BrowserReload))
self.reload.clicked.connect(self.globalMediaReload)
self.controlBarLayout.addWidget(self.reload, 0, 1, 1, 1)
self.stop = PushButton(self.style().standardIcon(
QStyle.SP_DialogCancelButton))
self.stop.clicked.connect(self.globalMediaStop)
self.controlBarLayout.addWidget(self.stop, 0, 2, 1, 1)
# 全局弹幕设置
self.danmuOption = TextOpation()
self.danmuOption.setWindowTitle('全局弹幕窗设置')
self.danmuOption.opacitySlider.value.connect(
self.setGlobalDanmuOpacity)
self.danmuOption.horizontalCombobox.currentIndexChanged.connect(
self.setGlobalHorizontalPercent)
self.danmuOption.verticalCombobox.currentIndexChanged.connect(
self.setGlobalVerticalPercent)
self.danmuOption.translateCombobox.currentIndexChanged.connect(
self.setGlobalTranslateBrowser)
self.danmuOption.showEnterRoom.currentIndexChanged.connect(
self.setGlobalShowEnterRoom)
self.danmuOption.translateFitler.textChanged.connect(
self.setGlobalTranslateFilter)
self.danmuOption.fontSizeCombox.currentIndexChanged.connect(
self.setGlobalFontSize)
# self.danmuButton = ToolButton(self.style().standardIcon(QStyle.SP_FileDialogDetailedView))
icon = QIcon()
icon.addFile(os.path.join(application_path, 'utils/danmu.png'))
self.danmuButton = PushButton(icon)
self.danmuButton.clicked.connect(self.danmuOption.show)
# self.danmuButton = PushButton(text='弹')
# self.globalDanmuToken = True
# self.danmuButton.clicked.connect(self.globalDanmuShow)
self.controlBarLayout.addWidget(self.danmuButton, 0, 3, 1, 1)
# 全局静音
self.globalMuteToken = False
self.volumeButton = PushButton(
self.style().standardIcon(QStyle.SP_MediaVolume))
self.volumeButton.clicked.connect(self.globalMediaMute)
self.controlBarLayout.addWidget(self.volumeButton, 1, 0, 1, 1)
# 全局音量滑条
self.slider = Slider()
self.slider.setValue(self.config['globalVolume'])
self.slider.value.connect(self.globalSetVolume)
self.controlBarLayout.addWidget(self.slider, 1, 1, 1, 3)
progressText.setText('设置播放器控制...')
# 添加主播按钮
self.addButton = QPushButton('+')
self.addButton.setFixedSize(160, 90)
self.addButton.setStyleSheet('border:3px dotted #EEEEEE')
self.addButton.setFont(QFont('Arial', 24, QFont.Bold))
progressText.setText('设置添加控制...')
self.controlBarLayout.addWidget(self.addButton, 2, 0, 1, 4)
progressText.setText('设置全局控制...')
self.scrollArea = ScrollArea()
self.scrollArea.setStyleSheet('border-width:0px')
# self.scrollArea.setMinimumHeight(111)
self.cardDock = DockWidget('卡片槽')
self.cardDock.setWidget(self.scrollArea)
self.addDockWidget(Qt.TopDockWidgetArea, self.cardDock)
# self.controlBarLayout.addWidget(self.scrollArea, 3, 0, 1, 5)
# 主播添加窗口
self.liverPanel = LiverPanel(self.config['roomid'], application_path)
self.liverPanel.addLiverRoomWidget.getHotLiver.start()
self.liverPanel.addToWindow.connect(self.addCoverToPlayer)
self.liverPanel.dumpConfig.connect(self.dumpConfig.start) # 保存config
self.liverPanel.refreshIDList.connect(
self.refreshPlayerStatus) # 刷新播放器
self.liverPanel.startLiveList.connect(self.startLiveTip) # 开播提醒
self.scrollArea.setWidget(self.liverPanel)
self.scrollArea.multipleTimes.connect(self.changeLiverPanelLayout)
self.scrollArea.addLiver.connect(self.liverPanel.openLiverRoomPanel)
self.scrollArea.clearAll.connect(self.clearLiverPanel)
self.addButton.clicked.connect(self.liverPanel.openLiverRoomPanel)
self.liverPanel.updatePlayingStatus(self.config['player'])
progressText.setText('设置主播选择控制...')
# ---- 菜单设置 ----
self.optionMenu = self.menuBar().addMenu('设置')
self.controlBarLayoutToken = self.config['control']
layoutConfigAction = QAction(
'布局方式', self, triggered=self.openLayoutSetting)
self.optionMenu.addAction(layoutConfigAction)
globalQualityMenu = self.optionMenu.addMenu('全局画质 ►')
originQualityAction = QAction(
'原画', self, triggered=lambda: self.globalQuality(10000))
globalQualityMenu.addAction(originQualityAction)
bluerayQualityAction = QAction(
'蓝光', self, triggered=lambda: self.globalQuality(400))
globalQualityMenu.addAction(bluerayQualityAction)
highQualityAction = QAction(
'超清', self, triggered=lambda: self.globalQuality(250))
globalQualityMenu.addAction(highQualityAction)
lowQualityAction = QAction(
'流畅', self, triggered=lambda: self.globalQuality(80))
globalQualityMenu.addAction(lowQualityAction)
onlyAudio = QAction(
'仅播声音', self, triggered=lambda: self.globalQuality(-1))
globalQualityMenu.addAction(onlyAudio)
globalAudioMenu = self.optionMenu.addMenu('全局音效 ►')
audioOriginAction = QAction(
'原始音效', self, triggered=lambda: self.globalAudioChannel(0))
globalAudioMenu.addAction(audioOriginAction)
audioDolbysAction = QAction(
'杜比音效', self, triggered=lambda: self.globalAudioChannel(5))
globalAudioMenu.addAction(audioDolbysAction)
hardDecodeMenu = self.optionMenu.addMenu('解码方案 ►')
hardDecodeAction = QAction(
'硬解', self, triggered=lambda: self.setDecode(True))
hardDecodeMenu.addAction(hardDecodeAction)
softDecodeAction = QAction(
'软解', self, triggered=lambda: self.setDecode(False))
hardDecodeMenu.addAction(softDecodeAction)
startLiveSetting = self.optionMenu.addMenu('开播提醒 ►')
enableStartLive = QAction(
'打开', self, triggered=lambda: self.setStartLive(True))
startLiveSetting.addAction(enableStartLive)
disableStartLive = QAction(
'关闭', self, triggered=lambda: self.setStartLive(False))
startLiveSetting.addAction(disableStartLive)
cacheSizeSetting = QAction(
'缓存设置', self, triggered=self.openCacheSetting)
self.optionMenu.addAction(cacheSizeSetting)
startWithDanmuSetting = QAction(
'自动加载弹幕设置', self, triggered=self.openStartWithDanmuSetting)
self.optionMenu.addAction(startWithDanmuSetting)
controlPanelAction = QAction(
'显示 / 隐藏控制条(H)', self, triggered=self.openControlPanel)
self.optionMenu.addAction(controlPanelAction)
self.fullScreenAction = QAction(
'全屏(F) / 退出(Esc)', self, triggered=self.fullScreen)
self.optionMenu.addAction(self.fullScreenAction)
exportConfig = QAction('导出预设', self, triggered=self.exportConfig)
self.optionMenu.addAction(exportConfig)
importConfig = QAction('导入预设', self, triggered=self.importConfig)
self.optionMenu.addAction(importConfig)
progressText.setText('设置选项菜单...')
self.versionMenu = self.menuBar().addMenu('帮助')
bilibiliAction = QAction('B站视频', self, triggered=self.openBilibili)
self.versionMenu.addAction(bilibiliAction)
hotKeyAction = QAction('快捷键', self, triggered=self.openHotKey)
self.versionMenu.addAction(hotKeyAction)
versionAction = QAction('检查版本', self, triggered=self.openVersion)
self.versionMenu.addAction(versionAction)
otherDDMenu = self.versionMenu.addMenu('其他DD系列工具 ►')
DDSubtitleAction = QAction(
'DD烤肉机', self, triggered=self.openDDSubtitle)
otherDDMenu.addAction(DDSubtitleAction)
DDThanksAction = QAction('DD答谢机', self, triggered=self.openDDThanks)
otherDDMenu.addAction(DDThanksAction)
progressText.setText('设置帮助菜单...')
self.payMenu = self.menuBar().addMenu('开源和投喂')
githubAction = QAction('GitHub', self, triggered=self.openGithub)
self.payMenu.addAction(githubAction)
feedAction = QAction('投喂作者', self, triggered=self.openFeed)
self.payMenu.addAction(feedAction)
# killAction = QAction('自尽(测试)', self, triggered=lambda a: 0 / 0)
# self.payMenu.addAction(killAction)
progressText.setText('设置关于菜单...')
# 鼠标和计时器
self.oldMousePos = QPoint(0, 0) # 初始化鼠标坐标
self.hideMouseCnt = 90
self.mouseTrackTimer = QTimer()
self.mouseTrackTimer.timeout.connect(self.checkMousePos)
self.mouseTrackTimer.start(100) # 0.1s检测一次
progressText.setText('设置UI...')
self.checkDanmmuProvider = CheckDanmmuProvider()
self.checkDanmmuProvider.start()
self.loadDockLayout()
logging.info('UI构造完毕')
def setPlayer(self):
for index, layoutConfig in enumerate(self.config['layout']):
roomID = self.config['player'][index]
videoWidget = self.videoWidgetList[index]
videoWidget.roomID = str(roomID) # 转一下防止格式出错
y, x, h, w = layoutConfig
self.mainLayout.addWidget(videoWidget, y, x, h, w)
self.videoWidgetList[index].show()
self.videoIndex = 0
self.setMediaTimer = QTimer()
self.setMediaTimer.timeout.connect(self.setMedia)
# self.setMediaTimer.start(500) # QMediaPlayer加载慢一点 否则容易崩
self.setMediaTimer.start(10) # vlc
def setMedia(self):
if self.videoIndex == 16:
self.setMediaTimer.stop()
elif self.videoIndex < len(self.config['layout']):
# pass
self.videoWidgetList[self.videoIndex].mediaReload()
else:
self.videoWidgetList[self.videoIndex].playerRestart()
self.videoIndex += 1
def addMedia(self, info): # 窗口 房号
id, roomID = info
self.config['player'][id] = roomID
self.liverPanel.updatePlayingStatus(self.config['player'])
self.dumpConfig.start()
def deleteMedia(self, id):
self.config['player'][id] = 0
self.liverPanel.updatePlayingStatus(self.config['player'])
self.dumpConfig.start()
def exchangeMedia(self, info): # 交换播放窗口的函数
fromID, fromRoomID, toID, toRoomID = info # 交换数据
# 待交换的两个控件
fromVideo, toVideo = self.videoWidgetList[fromID], self.videoWidgetList[toID]
fromVideo.id, toVideo.id = toID, fromID # 交换id
fromVideo.topLabel.setText(fromVideo.topLabel.text().replace(
'窗口%s' % (fromID + 1), '窗口%s' % (toID + 1)))
toVideo.topLabel.setText(toVideo.topLabel.text().replace(
'窗口%s' % (toID + 1), '窗口%s' % (fromID + 1)))
fromWidth, fromHeight = fromVideo.width(), fromVideo.height()
toWidth, toHeight = toVideo.width(), toVideo.height()
if 3 < abs(fromWidth - toWidth) or 3 < abs(fromHeight - toHeight): # 有主次关系的播放窗交换同时交换音量和弹幕设置
fromMuted = 2 if fromVideo.player.audio_get_mute() else 1
toMuted = 2 if toVideo.player.audio_get_mute() else 1
fromVolume, toVolume = fromVideo.player.audio_get_volume(
), toVideo.player.audio_get_volume() # 音量值
fromVideo.mediaMute(toMuted) # 交换静音设置
fromVideo.setVolume(toVolume) # 交换音量
toVideo.mediaMute(fromMuted)
toVideo.setVolume(fromVolume)
fromVideo.textSetting, toVideo.textSetting = toVideo.textSetting, fromVideo.textSetting # 交换弹幕设置
for videoWidget in [fromVideo, toVideo]:
color = str(
hex(int(videoWidget.textSetting[1] / 101 * 256)))[2:] + '000000'
videoWidget.textBrowser.textBrowser.setStyleSheet(
'background-color:#%s' % color) # 设置透明度
videoWidget.textBrowser.transBrowser.setStyleSheet(
'background-color:#%s' % color)
videoWidget.textBrowser.msgsBrowser.setStyleSheet(
'background-color:#%s' % color)
videoWidget.horiPercent = [
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0][videoWidget.textSetting[2]]
videoWidget.vertPercent = [
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0][videoWidget.textSetting[3]]
if videoWidget.textSetting[4] == 0: # 显示弹幕和同传
videoWidget.textBrowser.textBrowser.show()
videoWidget.textBrowser.transBrowser.show()
elif videoWidget.textSetting[4] == 1: # 只显示弹幕
videoWidget.textBrowser.transBrowser.hide()
videoWidget.textBrowser.textBrowser.show()
elif videoWidget.textSetting[4] == 2: # 只显示同传
videoWidget.textBrowser.textBrowser.hide()
videoWidget.textBrowser.transBrowser.show()
videoWidget.filters = videoWidget.textSetting[5].split(' ')
if videoWidget.textSetting[7] < 3:
videoWidget.textBrowser.msgsBrowser.show()
elif videoWidget.textSetting[7] == 3:
videoWidget.textBrowser.msgsBrowser.hide()
size = videoWidget.textSetting[6]
videoWidget.textBrowser.textBrowser.setFont(
QFont('Microsoft JhengHei', size + 5, QFont.Bold))
videoWidget.textBrowser.transBrowser.setFont(
QFont('Microsoft JhengHei', size + 5, QFont.Bold))
videoWidget.textBrowser.msgsBrowser.setFont(
QFont('Microsoft JhengHei', size + 5, QFont.Bold))
# 交换控件列表
self.videoWidgetList[fromID], self.videoWidgetList[toID] = toVideo, fromVideo
self.config['player'][toID] = fromRoomID # 记录config
self.config['player'][fromID] = toRoomID
self.dumpConfig.start()
# self.changeLayout(self.config['layout']) # 刷新layout
# 用新的方法直接交换两个窗口
fromLayout, toLayout = self.config['layout'][fromID], self.config['layout'][toID]
y, x, h, w = fromLayout
self.mainLayout.addWidget(toVideo, y, x, h, w)
y, x, h, w = toLayout
self.mainLayout.addWidget(fromVideo, y, x, h, w)
# TODO: 改崩溃了 不想改了 怎么改都没法按比例调整弹幕窗坐标
# fromVideoPos = fromVideo.mapToGlobal(fromVideo.pos()) # 保持弹幕框相对位置
# toVideoPos = toVideo.mapToGlobal(toVideo.pos())
# fromVideo.textBrowser.move(toVideoPos + QPoint(toWidth * fromVideo.deltaX, toHeight * fromVideo.deltaY))
# toVideo.textBrowser.move(fromVideoPos + QPoint(fromWidth * toVideo.deltaX, fromHeight * toVideo.deltaY))
def clearLiverPanel(self): # 清空卡片槽
reply = QMessageBox.information(
self, '清空卡片槽', '注意:是否要清空卡片槽?', QMessageBox.Yes | QMessageBox.No)
if reply == QMessageBox.Yes: # 确认用户操作
self.liverPanel.deleteAll()
def setDanmu(self):
self.dumpConfig.start()
def showAddButton(self, height):
if height < 181:
self.addButton.hide()
else:
self.addButton.show()
def setTranslator(self, info):
id, token = info # 窗口 同传显示布尔值
self.config['translator'][id] = token
self.dumpConfig.start()
def setQuality(self, info):
id, quality = info # 窗口 画质
self.config['quality'][id] = quality
self.dumpConfig.start()
def setAudioChannel(self, info):
id, audioChannel = info # 窗口 音效
self.config['audioChannel'][id] = audioChannel
self.dumpConfig.start()
def popWindow(self, info): # 悬浮窗播放
id, roomID, quality, showMax, startWithDanmu = info
logging.info("%s 进入悬浮窗模式, 弹幕?: %s" % (roomID, startWithDanmu))
self.popVideoWidgetList[id].roomID = roomID
self.popVideoWidgetList[id].quality = quality
self.popVideoWidgetList[id].resize(1280, 720)
self.popVideoWidgetList[id].show()
if startWithDanmu:
self.popVideoWidgetList[id].showDanmu()
self.popVideoWidgetList[id].textBrowser.show()
if showMax:
self.popVideoWidgetList[id].showMaximized()
self.popVideoWidgetList[id].mediaReload()
def mutedChanged(self, mutedInfo):
id, muted = mutedInfo
token = 2 if muted else 1
self.config['muted'][id] = token
# self.dumpConfig.start()
def volumeChanged(self, volumeInfo):
id, value = volumeInfo
self.config['volume'][id] = value
# self.dumpConfig.start()
def globalMediaPlay(self):
if self.globalPlayToken:
force = 1
self.play.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
else:
force = 2
self.play.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
self.globalPlayToken = not self.globalPlayToken
for videoWidget in self.videoWidgetList:
videoWidget.mediaPlay(force, setUserPause=True)
def globalMediaReload(self):
for videoWidget in self.videoWidgetList:
if not videoWidget.isHidden():
videoWidget.mediaReload()
def globalMediaMute(self):
if self.globalMuteToken:
force = 1
self.volumeButton.setIcon(
self.style().standardIcon(QStyle.SP_MediaVolume))
else:
force = 2
self.volumeButton.setIcon(
self.style().standardIcon(QStyle.SP_MediaVolumeMuted))
self.globalMuteToken = not self.globalMuteToken
for videoWidget in self.videoWidgetList:
videoWidget.mediaMute(force)
self.config['muted'] = [force] * 16
# self.dumpConfig.start()
def globalSetVolume(self, value):
for videoWidget in self.videoWidgetList:
videoWidget.player.audio_set_volume(
int(value * videoWidget.volumeAmplify))
videoWidget.volume = value
videoWidget.slider.setValue(value)
self.config['volume'] = [value] * 16
self.config['globalVolume'] = value
# self.dumpConfig.start()
def globalMediaStop(self):
for videoWidget in self.videoWidgetList:
videoWidget.mediaStop()
# def globalDanmuShow(self): # 已弃用
# self.globalDanmuToken = not self.globalDanmuToken
# for videoWidget in self.videoWidgetList:
# if not videoWidget.isHidden():
# videoWidget.textBrowser.show() if self.globalDanmuToken else videoWidget.textBrowser.hide()
# for danmuConfig in self.config['danmu']:
# danmuConfig[0] = self.globalDanmuToken
def setGlobalDanmuOpacity(self, value):
if value < 7:
value = 7 # 最小透明度
opacity = int(value / 101 * 256)
color = str(hex(opacity))[2:] + '000000'
for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
videoWidget.textSetting[1] = value # 记录设置
videoWidget.textBrowser.textBrowser.setStyleSheet(
'background-color:#%s' % color)
videoWidget.textBrowser.transBrowser.setStyleSheet(
'background-color:#%s' % color)
videoWidget.textBrowser.msgsBrowser.setStyleSheet(
'background-color:#%s' % color)
def setGlobalHorizontalPercent(self, index): # 设置弹幕框水平宽度
for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
videoWidget.textSetting[2] = index
videoWidget.horiPercent = [
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0][index] # 记录横向占比
width = videoWidget.width() * videoWidget.horiPercent
videoWidget.textBrowser.resize(
width, videoWidget.textBrowser.height())
videoWidget.textBrowser.textBrowser.verticalScrollBar().setValue(100000000)
videoWidget.textBrowser.transBrowser.verticalScrollBar().setValue(100000000)
videoWidget.textBrowser.msgsBrowser.verticalScrollBar().setValue(100000000)
def setGlobalVerticalPercent(self, index): # 设置弹幕框垂直高度
for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
videoWidget.textSetting[3] = index
videoWidget.vertPercent = [
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0][index] # 记录纵向占比
height = videoWidget.height() * videoWidget.vertPercent
videoWidget.textBrowser.resize(
videoWidget.textBrowser.width(), height)
videoWidget.textBrowser.textBrowser.verticalScrollBar().setValue(100000000)
videoWidget.textBrowser.transBrowser.verticalScrollBar().setValue(100000000)
videoWidget.textBrowser.msgsBrowser.verticalScrollBar().setValue(100000000)
def setGlobalTranslateBrowser(self, index):
for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
videoWidget.textSetting[4] = index
if index == 0: # 显示弹幕和同传
videoWidget.textBrowser.textBrowser.show()
videoWidget.textBrowser.transBrowser.show()
elif index == 1: # 只显示弹幕
videoWidget.textBrowser.transBrowser.hide()
videoWidget.textBrowser.textBrowser.show()
elif index == 2: # 只显示同传
videoWidget.textBrowser.textBrowser.hide()
videoWidget.textBrowser.transBrowser.show()
width = videoWidget.width() * videoWidget.horiPercent
height = videoWidget.height() * videoWidget.vertPercent
videoWidget.textBrowser.resize(width, height)
def setGlobalShowEnterRoom(self, index):
for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
videoWidget.textSetting[7] = index
if index < 3: # 显示礼物和进入信息]
videoWidget.textBrowser.msgsBrowser.show()
elif index == 3: # 隐藏窗口
videoWidget.textBrowser.msgsBrowser.hide()
width = videoWidget.width() * videoWidget.horiPercent
height = videoWidget.height() * videoWidget.vertPercent
videoWidget.textBrowser.resize(width, height)
def setGlobalTranslateFilter(self, filterWords):
for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
videoWidget.textSetting[5] = filterWords
videoWidget.filters = filterWords.split(' ')
def setGlobalFontSize(self, index):
for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
videoWidget.textSetting[6] = index
videoWidget.textBrowser.textBrowser.setFont(
QFont('Microsoft JhengHei', index + 5, QFont.Bold))
videoWidget.textBrowser.transBrowser.setFont(
QFont('Microsoft JhengHei', index + 5, QFont.Bold))
videoWidget.textBrowser.msgsBrowser.setFont(
QFont('Microsoft JhengHei', index + 5, QFont.Bold))
def globalQuality(self, quality):
for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
if not videoWidget.isHidden(): # 窗口没有被隐藏
videoWidget.quality = quality
videoWidget.mediaReload()
self.config['quality'] = [quality] * 16
self.dumpConfig.start()
def globalAudioChannel(self, audioChannel):
for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
videoWidget.audioChannel = audioChannel
videoWidget.player.audio_set_channel(audioChannel)
self.config['audioChannel'] = [audioChannel] * 16
# self.dumpConfig.start()
def setDecode(self, hardwareDecodeToken):
for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
videoWidget.hardwareDecode = hardwareDecodeToken
self.globalMediaReload()
self.config['hardwareDecode'] = hardwareDecodeToken
def setStartLive(self, token):
self.config['showStartLive'] = token
def openControlPanel(self):
if self.controlDock.isHidden() and self.cardDock.isHidden():
self.controlDock.show()
self.cardDock.show()
self.optionMenu.menuAction().setVisible(True)
self.versionMenu.menuAction().setVisible(True)
self.payMenu.menuAction().setVisible(True)
else:
self.controlDock.hide()
self.cardDock.hide()
self.optionMenu.menuAction().setVisible(False)
self.versionMenu.menuAction().setVisible(False)
self.payMenu.menuAction().setVisible(False)
self.controlBarLayoutToken = self.controlDock.isHidden()
def openVersion(self):
self.version.hide()
self.version.show()
def openGithub(self):
QDesktopServices.openUrl(
QUrl(r'https://github.com/zhimingshenjun/DD_Monitor'))
def openBilibili(self):
QDesktopServices.openUrl(
QUrl(r'https://www.bilibili.com/video/BV14v411s7WE'))
def openDDSubtitle(self):
QDesktopServices.openUrl(
QUrl(r'https://www.bilibili.com/video/BV1p5411b7o7'))
def openDDThanks(self):
QDesktopServices.openUrl(
QUrl(r'https://www.bilibili.com/video/BV1Di4y1L7T2'))
def openCacheSetting(self):
self.cacheSetting.hide()
self.cacheSetting.show()
def setCache(self, setting):
maxCache, savePath = setting
intergerMaxCache = int(maxCache)
if intergerMaxCache <= 0:
QMessageBox.warning(self, '大小错误', '缓存大小不能小于为0GB!', QMessageBox.Ok)
return
self.config['maxCacheSize'] = intergerMaxCache * 1024000
self.config['saveCachePath'] = savePath
self.dumpConfig.start()
QMessageBox.information(
self, '缓存设置更改', '设置成功 重启监控室后生效', QMessageBox.Ok)
def openStartWithDanmuSetting(self):
items = ('加载(推荐,默认。但可能增加网络压力,可能会被限流。)', '不加载')
defulatSelection = 0
if not self.config['startWithDanmu']:
defulatSelection = 1
selection, okPressed = QInputDialog.getItem(
self, "设置启动时是否加载弹幕", "加载选项", items, defulatSelection, False)
if okPressed:
trueDanmu = (selection == items[0])
self.config['startWithDanmu'] = trueDanmu
self.dumpConfig.start()
def openHotKey(self):
self.hotKey.hide()
self.hotKey.show()
def openFeed(self):
self.pay.hide()
self.pay.show()
self.pay.thankToBoss.start()
def checkMousePos(self):
# for videoWidget in self.videoWidgetList: # vlc的播放会直接音量最大化 实在没地方放了 写在这里实时强制修改它的音量
# videoWidget.player.audio_set_volume(int(videoWidget.volume * videoWidget.volumeAmplify))
newMousePos = QCursor.pos()
if newMousePos != self.oldMousePos:
self.setCursor(Qt.ArrowCursor) # 鼠标动起来就显示
self.oldMousePos = newMousePos
self.hideMouseCnt = 20 # 刷新隐藏鼠标的间隔
if self.hideMouseCnt > 0:
self.hideMouseCnt -= 1
else:
self.setCursor(Qt.BlankCursor) # 计数归零隐藏鼠标
for videoWidget in self.videoWidgetList:
videoWidget.topLabel.hide() # 隐藏播放窗口的控制条
videoWidget.frame.hide()
for videoWidget in self.popVideoWidgetList:
videoWidget.topLabel.hide() # 隐藏悬浮窗口的控制条
videoWidget.frame.hide()
def moveEvent(self, QMoveEvent): # 捕获主窗口moveEvent来实时同步弹幕机位置
for videoWidget in self.videoWidgetList:
videoPos = videoWidget.mapToGlobal(
videoWidget.videoFrame.pos()) # videoFrame的坐标要转成globalPos
videoWidget.textBrowser.move(videoPos + videoWidget.textPosDelta)
videoWidget.textPosDelta = videoWidget.textBrowser.pos() - videoPos
def hideEvent(self, e: QHideEvent) -> None:
"""主窗口隐藏:关闭、最小化
隐藏所有弹幕机
"""
logging.debug(f"主窗口已隐藏")
for videoWidget in self.videoWidgetList:
videoWidget.textBrowser.hide()
def showEvent(self, e: QShowEvent) -> None:
"""主窗口显示:打开、最大化
显示开启的弹幕机
"""
logging.debug(f"主窗口已显示")
for index, videoWidget in enumerate(self.videoWidgetList):
if self.config['danmu'][index][0] and not videoWidget.isHidden():
videoWidget.textBrowser.show()
def closeEvent(self, QCloseEvent):
self.hide()
self.layoutSettingPanel.close()
self.liverPanel.addLiverRoomWidget.close()
for videoWidget in self.videoWidgetList + self.popVideoWidgetList:
videoWidget.getMediaURL.recordToken = False # 关闭缓存并清除
videoWidget.getMediaURL.checkTimer.stop()
videoWidget.checkPlaying.stop()
videoWidget.mediaStop(deleteMedia=False) # 不要清除播放窗记录
videoWidget.close()
self.saveDockLayout()
self.dumpConfig.start()
def openLayoutSetting(self):
self.layoutSettingPanel.hide()
self.layoutSettingPanel.show()
def changeLayout(self, layoutConfig):
for videoWidget in self.videoWidgetList:
videoWidget.mediaPlay(1) # 全部暂停
for index, _ in enumerate(self.config['layout']):
self.videoWidgetList[index].textBrowser.hide()
self.mainLayout.itemAt(0).widget().hide()
self.mainLayout.removeWidget(self.mainLayout.itemAt(0).widget())
for index, layout in enumerate(layoutConfig):
y, x, h, w = layout
videoWidget = self.videoWidgetList[index]
videoWidget.show()
if videoWidget.textSetting[0]: # 显示弹幕
videoWidget.textBrowser.show()
self.mainLayout.addWidget(videoWidget, y, x, h, w)
if videoWidget.roomID != '0':
videoWidget.mediaPlay(2) # 显示的窗口播放
for videoWidget in self.videoWidgetList[index + 1:]: # 被隐藏起来的窗口
videoWidget.getMediaURL.recordToken = False # 关闭缓存并清除
videoWidget.getMediaURL.checkTimer.stop()
videoWidget.checkPlaying.stop()
self.config['layout'] = layoutConfig
self.dumpConfig.start()
def changeLiverPanelLayout(self, multiple):
self.liverPanel.multiple = multiple
self.liverPanel.refreshPanel()
def fullScreen(self):
if self.isFullScreen(): # 退出全屏
if self.maximumToken:
self.showMaximized()
else:
self.showNormal()
self.optionMenu.menuAction().setVisible(True)
self.versionMenu.menuAction().setVisible(True)
self.payMenu.menuAction().setVisible(True)
if self.controlBarLayoutToken:
self.controlDock.show()
self.cardDock.show()
else: # 全屏
for videoWidget in self.videoWidgetList:
videoWidget.fullScreen = True
self.maximumToken = self.isMaximized()
self.optionMenu.menuAction().setVisible(False)
self.versionMenu.menuAction().setVisible(False)
self.payMenu.menuAction().setVisible(False)
if self.controlBarLayoutToken:
self.controlDock.hide()
self.cardDock.hide()
for videoWidget in self.videoWidgetList:
videoWidget.fullScreen = True
self.showFullScreen()
def saveDockLayout(self):
self.config['geometry'] = str(self.saveGeometry().toBase64(), 'ASCII')
self.config['windowState'] = str(self.saveState().toBase64(), 'ASCII')
logging.info(f'save Window layout.')
def loadDockLayout(self):
if 'geometry' in self.config:
geometry = QByteArray().fromBase64(
self.config['geometry'].encode('ASCII'))
self.restoreGeometry(geometry)
if 'windowState' in self.config:
windowState = QByteArray().fromBase64(
self.config['windowState'].encode('ASCII'))
self.restoreState(windowState)
logging.info(f'restore Window layout.')
def exportConfig(self):
self.savePath = QFileDialog.getSaveFileName(
self, "选择保存路径", 'DD监控室预设', "*.json")[0]
if self.savePath: # 保存路径有效
try:
with codecs.open(self.savePath, 'w', encoding='utf-8') as f:
f.write(json.dumps(self.config, ensure_ascii=False))
QMessageBox.information(self, '导出预设', '导出完成', QMessageBox.Ok)
except:
logging.exception('json 配置导出失败')
def importConfig(self):
jsonPath = QFileDialog.getOpenFileName(self, "选择预设", None, "*.json")[0]
if jsonPath:
if os.path.getsize(jsonPath):
config = {}
try:
with codecs.open(jsonPath, 'r', encoding='utf-8') as f:
config = json.loads(f.read())
except UnicodeDecodeError:
try:
with codecs.open(jsonPath, 'r', encoding='gbk') as f:
config = json.loads(f.read())
except:
logging.exception('json 配置导入失败')
config = {}
except:
logging.exception('json 配置导入失败')
config = {}
if config: # 如果能成功读取到config文件
config['layout'] = self.config['layout'] # 保持最新layout
self.config = config
while len(self.config['player']) < 16:
self.config['player'].append('0')
self.config['player'] = list(
map(str, self.config['player']))
if type(self.config['roomid']) == list:
roomIDList = self.config['roomid']
self.config['roomid'] = {}
for roomID in roomIDList:
self.config['roomid'][roomID] = False
if '0' in self.config['roomid']: # 过滤0房间号
del self.config['roomid']['0']
if 'quality' not in self.config:
self.config['quality'] = [80] * 16
if 'audioChannel' not in self.config:
self.config['audioChannel'] = [0] * 16
if 'translator' not in self.config:
self.config['translator'] = [True] * 16
for index, textSetting in enumerate(self.config['danmu']):
if type(textSetting) == bool:
self.config['danmu'][index] = [
textSetting, 20, 1, 7, 0, '【 [ {']
if 'hardwareDecode' not in self.config:
self.config['hardwareDecode'] = True
if 'maxCacheSize' not in self.config:
self.config['maxCacheSize'] = 2048000
logging.warning('最大缓存没有被设置,使用默认1G')
if 'saveCachePath' not in self.config:
self.config['saveCachePath'] = ''
logging.warning('默认缓存备份路径为空 即自动清空')
if 'startWithDanmu' not in self.config:
self.config['startWithDanmu'] = True
logging.warning('启动时加载弹幕没有被设置,默认加载')
if 'showStartLive' not in self.config:
self.config['showStartLive'] = True
for danmuConfig in self.config['danmu']:
if len(danmuConfig) == 6:
danmuConfig.append(10)
self.liverPanel.addLiverRoomList(self.config['roomid'])
QMessageBox.information(
self, '导入预设', '导入完成', QMessageBox.Ok)
def muteExcept(self):
if not self.soloToken:
for videoWidget in self.videoWidgetList:
if not videoWidget.isHidden() and videoWidget.hoverToken:
videoWidget.mediaMute(1) # 取消静音
else:
videoWidget.mediaMute(2) # 静音
else: # 恢复所有直播间声音
for videoWidget in self.videoWidgetList:
if not videoWidget.isHidden():
videoWidget.mediaMute(1) # 取消静音
self.soloToken = not self.soloToken
def closePopWindow(self, info):
id, roomID = info
# 房间号有效
if not self.videoWidgetList[id - 16].isHidden() and roomID != '0' and roomID:
self.videoWidgetList[id - 16].roomID = roomID
self.videoWidgetList[id - 16].mediaReload()
self.config['player'][id - 16] = roomID
self.liverPanel.updatePlayingStatus(self.config['player'])
self.dumpConfig.start()
def keyPressEvent(self, QKeyEvent):
if QKeyEvent.key() == Qt.Key_Escape or QKeyEvent.key() == Qt.Key_F:
self.fullScreen() # 自动判断全屏状态并退出
elif QKeyEvent.key() == Qt.Key_H:
self.openControlPanel()
elif QKeyEvent.key() == Qt.Key_M or QKeyEvent.key() == Qt.Key_S:
self.muteExcept()
def addCoverToPlayer(self, info): # 窗口 房号
self.addMedia(info)
self.videoWidgetList[info[0]].roomID = info[1] # 修改房号
self.videoWidgetList[info[0]].mediaReload() # 重载视频
def refreshPlayerStatus(self, refreshIDList): # 刷新直播状态发生变化的播放器
for videoWidget in self.videoWidgetList:
for roomID in refreshIDList:
if roomID == videoWidget.roomID:
videoWidget.mediaReload()
break
def startLiveTip(self, startLiveList): # 开播提醒
if self.config['showStartLive']:
self.startLiveWindow.resize(240, 70)
self.startLiveWindow.move(self.pos() + QPoint(50, 50))
startLivers = ''
for liver in startLiveList:
startLivers += ' %s 开播啦!~ \n' % liver
self.startLiveWindow.tipLabel.setText(startLivers)
self.startLiveWindow.show()
self.startLiveWindow.hideTimer.start()
# 程序入口点
if __name__ == '__main__':
# 平台相关 patch
if platform.system() == 'Windows':
ctypes.windll.kernel32.SetDllDirectoryW(None)
if getattr(sys, 'frozen', False):
application_path = os.path.dirname(sys.executable)
elif __file__:
application_path = os.path.dirname(__file__)
# 缓存、日志文件夹初始化
cachePath = os.path.join(application_path, 'cache')
logsPath = os.path.join(application_path, 'logs')
if not os.path.exists(cachePath): # 启动前初始化cache文件夹
os.mkdir(cachePath)
if not os.path.exists(logsPath): # 启动前初始化logs文件夹
os.mkdir(logsPath)
try: # 尝试清除上次缓存 如果失败则跳过
for cacheFolder in os.listdir(cachePath):
shutil.rmtree(os.path.join(
application_path, 'cache/%s' % cacheFolder))
except:
logging.exception('清除缓存失败')
cacheFolder = os.path.join(
application_path, 'cache/%d' % time.time()) # 初始化缓存文件夹
os.mkdir(cacheFolder)
# 应用qss
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
app = QApplication(sys.argv)
# with open(os.path.join(application_path, 'utils/qdark.qss'), 'r') as f:
# qss = f.read()
# app.setStyleSheet(qss)
import qdarktheme
app.setStyleSheet(qdarktheme.load_stylesheet())
font = QFont('', 14, QFont.Bold)
app.setFont(font)
# app.setFont(QFont('微软雅黑', 9))
# 日志采集初始化
log.init_log(application_path)
sys.excepthook = uncaughtExceptionHandler
sys.unraisablehook = unraisableExceptionHandler
threading.excepthook = thraedingExceptionHandler
loggingSystemInfo()
# vlc 版本信息log
import vlc
vlc_libvlc_env = os.getenv('PYTHON_VLC_LIB_PATH', '')
vlc_plugin_env = os.getenv('PYTHON_VLC_MODULE_PATH', '')
logging.info(f"libvlc env: PYTHON_VLC_LIB_PATH={vlc_libvlc_env}")
logging.info(f"plugin env: PYTHON_VLC_MODULE_PATH={vlc_plugin_env}")
logging.info(f"libvlc path: {vlc.dll._name}")
logging.info(f"vlc version: {vlc.libvlc_get_version()}")
# 欢迎页面
splash = QSplashScreen(QPixmap(os.path.join(
application_path, 'utils/splash.jpg')), Qt.WindowStaysOnTopHint)
progressBar = QProgressBar(splash)
progressBar.setMaximum(32) # 16 * 2个播放器, 0 - 17 index
progressBar.setGeometry(0, splash.height() - 20, splash.width(), 20)
progressText = QLabel(splash)
progressText.setText("加载中...")
progressText.setGeometry(0, 0, 170, 20)
splash.show()
# 主页面入口
mainWindow = MainWindow(cacheFolder, progressBar, progressText)
mainWindow.showMaximized()
mainWindow.show()
splash.hide()
sys.exit(app.exec_())
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化