Files
meetingroom-netscreen/push_screen.py
2026-03-27 17:56:20 +08:00

285 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import subprocess
import psutil
import os
import sys
import socket
import time
import json
from loguru import logger
from PySide6.QtWidgets import (QApplication, QMainWindow, QPushButton, QMessageBox,
QVBoxLayout, QWidget, QLabel, QGroupBox, QGridLayout,
QStatusBar, QFrame, QDialog, QLineEdit, QFormLayout,
QDialogButtonBox, QFileDialog, QHBoxLayout)
from PySide6.QtCore import Qt, QThread, Signal
from PySide6.QtGui import QFont
logger.remove()
APP_DIR = os.path.dirname(os.path.abspath(sys.executable if getattr(sys, 'frozen', False) else __file__))
log_file = os.path.join(APP_DIR, "push_screen.log")
logger.add(log_file, format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}", level="INFO", encoding="utf-8")
CONFIG_FILE = os.path.join(APP_DIR, "config.json")
def load_config():
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
return {
"server_ip": "192.168.1.100",
"ffmpeg_path": r"D:\ScreenCast\ffmpeg\bin\ffmpeg.exe",
"stream_path": "screen"
}
def save_config(config):
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
json.dump(config, f, ensure_ascii=False, indent=4)
logger.info(f"配置已保存到 {CONFIG_FILE}")
class ConfigDialog(QDialog):
def __init__(self, config, parent=None):
super().__init__(parent)
self.config = config
self.setWindowTitle("设置")
self.setFixedSize(450, 180)
layout = QFormLayout(self)
self.server_ip_edit = QLineEdit(config["server_ip"])
self.ffmpeg_path_edit = QLineEdit(config["ffmpeg_path"])
self.stream_path_edit = QLineEdit(config["stream_path"])
self.ffmpeg_btn = QPushButton("浏览...")
self.ffmpeg_btn.clicked.connect(lambda: self.browse("ffmpeg"))
path_layout1 = QGridLayout()
path_layout1.addWidget(self.ffmpeg_path_edit, 0, 0)
path_layout1.addWidget(self.ffmpeg_btn, 0, 1)
layout.addRow("服务器IP:", self.server_ip_edit)
layout.addRow("FFmpeg路径:", self.ffmpeg_path_edit)
layout.addRow("推流路径:", self.stream_path_edit)
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addRow(buttons)
def browse(self, field):
if field == "ffmpeg":
path = QFileDialog.getOpenFileName(self, "选择FFmpeg", "", "Executable (*.exe)")[0]
if path:
self.ffmpeg_path_edit.setText(path)
def get_config(self):
return {
"server_ip": self.server_ip_edit.text().strip(),
"ffmpeg_path": self.ffmpeg_path_edit.text().strip(),
"stream_path": self.stream_path_edit.text().strip()
}
class ConnectionChecker(QThread):
status_update = Signal(dict)
def run(self):
while True:
cfg = self.config
status = {
"ffmpeg": self.check_ffmpeg(cfg["ffmpeg_path"]),
"server": self.check_server(cfg["server_ip"]),
"server_port": self.check_port(cfg["server_ip"], 8554),
}
self.status_update.emit(status)
time.sleep(3)
def set_config(self, config):
self.config = config
def check_ffmpeg(self, path):
return os.path.exists(path)
def check_server(self, ip):
try:
socket.create_connection((ip, 8554), timeout=2)
return True
except:
return False
def check_port(self, ip, port):
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(2)
result = sock.connect_ex((ip, port))
sock.close()
return result == 0
except:
return False
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.config = load_config()
self.setWindowTitle("投屏源控制")
self.setFixedSize(520, 420)
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
title_layout = QHBoxLayout()
title_layout.addStretch()
self.btn_settings = QPushButton("⚙ 设置")
self.btn_settings.setFixedSize(70, 30)
self.btn_settings.clicked.connect(self.open_settings)
title_layout.addWidget(self.btn_settings)
title_label = QLabel("会议投屏系统 - 主播端")
title_font = QFont("Microsoft YaHei", 14, QFont.Bold)
title_label.setFont(title_font)
title_label.setAlignment(Qt.AlignCenter)
title_main_layout = QVBoxLayout()
title_main_layout.addLayout(title_layout)
title_main_layout.addWidget(title_label)
main_layout.addLayout(title_main_layout)
config_group = QGroupBox("本地配置")
config_layout = QGridLayout()
self.ffmpeg_label = QLabel("FFmpeg: 未检测")
self.server_label = QLabel("服务器连接: 未检测")
self.port_label = QLabel("RTSP端口: 未检测")
self.ffmpeg_indicator = QLabel("")
self.ffmpeg_indicator.setFixedWidth(20)
self.server_indicator = QLabel("")
self.server_indicator.setFixedWidth(20)
self.port_indicator = QLabel("")
self.port_indicator.setFixedWidth(20)
config_layout.addWidget(self.ffmpeg_indicator, 0, 0)
config_layout.addWidget(self.ffmpeg_label, 0, 1)
config_layout.addWidget(self.server_indicator, 1, 0)
config_layout.addWidget(self.server_label, 1, 1)
config_layout.addWidget(self.port_indicator, 2, 0)
config_layout.addWidget(self.port_label, 2, 1)
config_group.setLayout(config_layout)
main_layout.addWidget(config_group)
self.info_group = QGroupBox("流信息")
self.update_info_group()
main_layout.addWidget(self.info_group)
self.btn_push = QPushButton("🎬 开始全屏投屏")
self.btn_push.setFixedHeight(50)
self.btn_push.setFont(QFont("Microsoft YaHei", 11))
self.btn_push.setStyleSheet("""
QPushButton {
background-color: #2E7D32;
color: white;
border: none;
border-radius: 8px;
font-weight: bold;
}
QPushButton:hover {
background-color: #388E3C;
}
QPushButton:pressed {
background-color: #1B5E20;
}
""")
self.btn_push.clicked.connect(self.push_full_screen)
main_layout.addWidget(self.btn_push)
self.log_label = QLabel("日志: 等待启动...")
self.log_label.setWordWrap(True)
self.log_label.setFrameShape(QFrame.Shape.StyledPanel)
self.log_label.setFixedHeight(40)
main_layout.addWidget(self.log_label)
self.checker = ConnectionChecker()
self.checker.set_config(self.config)
self.checker.status_update.connect(self.update_status)
self.checker.start()
logger.info("投屏源控制界面已初始化")
def update_info_group(self):
cfg = self.config
if self.info_group.layout():
QWidget().setLayout(self.info_group.layout())
info_layout = QGridLayout()
info_layout.addWidget(QLabel("服务器IP:"), 0, 0)
info_layout.addWidget(QLabel(cfg["server_ip"]), 0, 1)
info_layout.addWidget(QLabel("推流地址:"), 1, 0)
info_layout.addWidget(QLabel(f"rtsp://{cfg['server_ip']}:8554/{cfg['stream_path']}"), 1, 1)
info_layout.addWidget(QLabel("访问地址:"), 2, 0)
info_layout.addWidget(QLabel(f"http://{cfg['server_ip']}:8889/webrtc.html?src={cfg['stream_path']}"), 2, 1)
self.info_group.setLayout(info_layout)
def open_settings(self):
dialog = ConfigDialog(self.config, self)
if dialog.exec():
self.config = dialog.get_config()
save_config(self.config)
self.checker.set_config(self.config)
self.update_info_group()
self.log_label.setText("日志: 配置已更新")
def update_status(self, status):
if status["ffmpeg"]:
self.ffmpeg_indicator.setStyleSheet("color: green; font-size: 16px;")
self.ffmpeg_label.setText("FFmpeg: ✓ 已安装")
else:
self.ffmpeg_indicator.setStyleSheet("color: red; font-size: 16px;")
self.ffmpeg_label.setText("FFmpeg: ✗ 未找到")
if status["server"]:
self.server_indicator.setStyleSheet("color: green; font-size: 16px;")
self.server_label.setText("服务器连接: ✓ 可连接")
else:
self.server_indicator.setStyleSheet("color: orange; font-size: 16px;")
self.server_label.setText("服务器连接: ✗ 连接失败")
if status["server_port"]:
self.port_indicator.setStyleSheet("color: green; font-size: 16px;")
self.port_label.setText("RTSP端口: ✓ 端口开放")
else:
self.port_indicator.setStyleSheet("color: orange; font-size: 16px;")
self.port_label.setText("RTSP端口: ✗ 端口关闭")
def push_full_screen(self):
cfg = self.config
logger.info("开始全屏投屏")
if not os.path.exists(cfg["ffmpeg_path"]):
logger.error("FFmpeg未安装")
QMessageBox.warning(self, "错误", "FFmpeg未安装请检查配置")
return
cmd = [
cfg["ffmpeg_path"],
"-f", "gdigrab", "-framerate", "30", "-i", "desktop",
"-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency",
"-f", "rtsp", f"rtsp://{cfg['server_ip']}:8554/{cfg['stream_path']}"
]
logger.info(f"执行FFmpeg命令: {' '.join(cmd)}")
subprocess.Popen(cmd, creationflags=subprocess.CREATE_NEW_CONSOLE)
logger.info("FFmpeg推流已启动")
self.log_label.setText("日志: FFmpeg推流已启动正在向服务器推送...")
QMessageBox.information(self, "提示", f"全屏投屏已启动!\n\n接收端可打开浏览器访问:\nhttp://{cfg['server_ip']}:8889/webrtc.html?src={cfg['stream_path']}")
def closeEvent(self, event):
self.checker.terminate()
event.accept()
if __name__ == "__main__":
logger.info("应用程序启动")
app = QApplication(sys.argv)
app.setStyle("Fusion")
window = MainWindow()
window.show()
sys.exit(app.exec())