diff --git a/push_screen.py b/push_screen.py index 850f6e6..377f534 100644 --- a/push_screen.py +++ b/push_screen.py @@ -4,46 +4,118 @@ 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, QProgressBar, QFrame) -from PySide6.QtCore import QTimer, Qt, QThread, Signal -from PySide6.QtGui import QFont, QPalette, QColor + QStatusBar, QFrame, QDialog, QLineEdit, QFormLayout, + QDialogButtonBox, QFileDialog) +from PySide6.QtCore import Qt, QThread, Signal +from PySide6.QtGui import QFont, QAction logger.add(sys.stderr, format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}", level="INFO") -SERVER_IP = "192.168.1.100" -MEDIAMTX_PATH = r"D:\ScreenCast\mediamtx\mediamtx.exe" -FFMPEG_PATH = r"D:\ScreenCast\ffmpeg\bin\ffmpeg.exe" -STREAM_PATH = "screen" +CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "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", + "mediamtx_path": r"D:\ScreenCast\mediamtx\mediamtx.exe", + "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, 220) + + layout = QFormLayout(self) + + self.server_ip_edit = QLineEdit(config["server_ip"]) + self.mediamtx_path_edit = QLineEdit(config["mediamtx_path"]) + self.ffmpeg_path_edit = QLineEdit(config["ffmpeg_path"]) + self.stream_path_edit = QLineEdit(config["stream_path"]) + + self.mediamtx_btn = QPushButton("浏览...") + self.mediamtx_btn.clicked.connect(lambda: self.browse("mediamtx")) + self.ffmpeg_btn = QPushButton("浏览...") + self.ffmpeg_btn.clicked.connect(lambda: self.browse("ffmpeg")) + + path_layout1 = QGridLayout() + path_layout1.addWidget(self.mediamtx_path_edit, 0, 0) + path_layout1.addWidget(self.mediamtx_btn, 0, 1) + path_layout1.addWidget(self.ffmpeg_path_edit, 1, 0) + path_layout1.addWidget(self.ffmpeg_btn, 1, 1) + + layout.addRow("服务器IP:", self.server_ip_edit) + layout.addRow("MediaMTX路径:", self.mediamtx_path_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 == "mediamtx": + path = QFileDialog.getOpenFileName(self, "选择MediaMTX", "", "Executable (*.exe)")[0] + if path: + self.mediamtx_path_edit.setText(path) + elif 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(), + "mediamtx_path": self.mediamtx_path_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 = { - "mediamtx": self.check_mediamtx(), - "ffmpeg": self.check_ffmpeg(), - "server": self.check_server(), - "server_port": self.check_port(SERVER_IP, 8554), + "mediamtx": self.check_mediamtx(cfg["mediamtx_path"]), + "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 check_mediamtx(self): + def set_config(self, config): + self.config = config + + def check_mediamtx(self, path): for proc in psutil.process_iter(['name']): if proc.info['name'] == 'mediamtx.exe': return True return False - def check_ffmpeg(self): - return os.path.exists(FFMPEG_PATH) + def check_ffmpeg(self, path): + return os.path.exists(path) - def check_server(self): + def check_server(self, ip): try: - socket.create_connection((SERVER_IP, 8554), timeout=2) + socket.create_connection((ip, 8554), timeout=2) return True except: return False @@ -61,9 +133,12 @@ class ConnectionChecker(QThread): class MainWindow(QMainWindow): def __init__(self): super().__init__() + self.config = load_config() self.setWindowTitle("投屏源控制") self.setFixedSize(520, 420) + self.create_menu() + self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) @@ -106,16 +181,9 @@ class MainWindow(QMainWindow): config_group.setLayout(config_layout) main_layout.addWidget(config_group) - info_group = QGroupBox("流信息") - info_layout = QGridLayout() - info_layout.addWidget(QLabel("服务器IP:"), 0, 0) - info_layout.addWidget(QLabel(SERVER_IP), 0, 1) - info_layout.addWidget(QLabel("推流地址:"), 1, 0) - info_layout.addWidget(QLabel(f"rtsp://{SERVER_IP}:8554/{STREAM_PATH}"), 1, 1) - info_layout.addWidget(QLabel("访问地址:"), 2, 0) - info_layout.addWidget(QLabel(f"http://{SERVER_IP}:8889/webrtc.html?src=screen"), 2, 1) - info_group.setLayout(info_layout) - main_layout.addWidget(info_group) + self.info_group = QGroupBox("流信息") + self.update_info_group() + main_layout.addWidget(self.info_group) self.btn_push = QPushButton("🎬 开始全屏投屏") self.btn_push.setFixedHeight(50) @@ -130,44 +198,73 @@ class MainWindow(QMainWindow): 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 create_menu(self): + settings_action = QAction("⚙ 设置", self) + settings_action.triggered.connect(self.open_settings) + self.menuBar().addAction(settings_action) + + 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["mediamtx"]: self.mediamtx_indicator.setStyleSheet("color: green; font-size: 16px;") - self.mediamtx_label.setText(f"MediaMTX: ✓ 运行中") + self.mediamtx_label.setText("MediaMTX: ✓ 运行中") else: self.mediamtx_indicator.setStyleSheet("color: red; font-size: 16px;") - self.mediamtx_label.setText(f"MediaMTX: ✗ 未运行") + self.mediamtx_label.setText("MediaMTX: ✗ 未运行") if status["ffmpeg"]: self.ffmpeg_indicator.setStyleSheet("color: green; font-size: 16px;") - self.ffmpeg_label.setText(f"FFmpeg: ✓ 已安装") + self.ffmpeg_label.setText("FFmpeg: ✓ 已安装") else: self.ffmpeg_indicator.setStyleSheet("color: red; font-size: 16px;") - self.ffmpeg_label.setText(f"FFmpeg: ✗ 未找到") + self.ffmpeg_label.setText("FFmpeg: ✗ 未找到") if status["server"]: self.server_indicator.setStyleSheet("color: green; font-size: 16px;") - self.server_label.setText(f"服务器连接: ✓ 可连接") + self.server_label.setText("服务器连接: ✓ 可连接") else: self.server_indicator.setStyleSheet("color: orange; font-size: 16px;") - self.server_label.setText(f"服务器连接: ✗ 连接失败") + self.server_label.setText("服务器连接: ✗ 连接失败") if status["server_port"]: self.port_indicator.setStyleSheet("color: green; font-size: 16px;") - self.port_label.setText(f"RTSP端口: ✓ 端口开放") + self.port_label.setText("RTSP端口: ✓ 端口开放") else: self.port_indicator.setStyleSheet("color: orange; font-size: 16px;") - self.port_label.setText(f"RTSP端口: ✗ 端口关闭") + self.port_label.setText("RTSP端口: ✗ 端口关闭") def start_mediamtx(self): - if not self.checker.check_mediamtx(): + cfg = self.config + if not self.checker.check_mediamtx(cfg["mediamtx_path"]): logger.info("启动MediaMTX服务") - subprocess.Popen([MEDIAMTX_PATH], cwd=r"D:\ScreenCast\mediamtx", creationflags=subprocess.CREATE_NEW_CONSOLE) + subprocess.Popen([cfg["mediamtx_path"]], cwd=os.path.dirname(cfg["mediamtx_path"]), creationflags=subprocess.CREATE_NEW_CONSOLE) time.sleep(3) logger.info("MediaMTX服务已启动") self.log_label.setText("日志: MediaMTX服务已启动") @@ -176,25 +273,26 @@ class MainWindow(QMainWindow): self.log_label.setText("日志: MediaMTX已在运行") def push_full_screen(self): + cfg = self.config logger.info("开始全屏投屏") self.start_mediamtx() - if not os.path.exists(FFMPEG_PATH): + if not os.path.exists(cfg["ffmpeg_path"]): logger.error("FFmpeg未安装") QMessageBox.warning(self, "错误", "FFmpeg未安装,请检查配置!") return cmd = [ - FFMPEG_PATH, + cfg["ffmpeg_path"], "-f", "gdigrab", "-framerate", "30", "-i", "desktop", "-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency", - "-f", "rtsp", f"rtsp://{SERVER_IP}:8554/{STREAM_PATH}" + "-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://{SERVER_IP}:8889/webrtc.html?src=screen") + QMessageBox.information(self, "提示", f"全屏投屏已启动!\n\n接收端可打开浏览器访问:\nhttp://{cfg['server_ip']}:8889/webrtc.html?src={cfg['stream_path']}") def closeEvent(self, event): self.checker.terminate()