添加配置文件和设置对话框
This commit is contained in:
178
push_screen.py
178
push_screen.py
@@ -4,46 +4,118 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
|
import json
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtWidgets import (QApplication, QMainWindow, QPushButton, QMessageBox,
|
from PySide6.QtWidgets import (QApplication, QMainWindow, QPushButton, QMessageBox,
|
||||||
QVBoxLayout, QWidget, QLabel, QGroupBox, QGridLayout,
|
QVBoxLayout, QWidget, QLabel, QGroupBox, QGridLayout,
|
||||||
QStatusBar, QProgressBar, QFrame)
|
QStatusBar, QFrame, QDialog, QLineEdit, QFormLayout,
|
||||||
from PySide6.QtCore import QTimer, Qt, QThread, Signal
|
QDialogButtonBox, QFileDialog)
|
||||||
from PySide6.QtGui import QFont, QPalette, QColor
|
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")
|
logger.add(sys.stderr, format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}", level="INFO")
|
||||||
|
|
||||||
SERVER_IP = "192.168.1.100"
|
CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json")
|
||||||
MEDIAMTX_PATH = r"D:\ScreenCast\mediamtx\mediamtx.exe"
|
|
||||||
FFMPEG_PATH = r"D:\ScreenCast\ffmpeg\bin\ffmpeg.exe"
|
def load_config():
|
||||||
STREAM_PATH = "screen"
|
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):
|
class ConnectionChecker(QThread):
|
||||||
status_update = Signal(dict)
|
status_update = Signal(dict)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while True:
|
while True:
|
||||||
|
cfg = self.config
|
||||||
status = {
|
status = {
|
||||||
"mediamtx": self.check_mediamtx(),
|
"mediamtx": self.check_mediamtx(cfg["mediamtx_path"]),
|
||||||
"ffmpeg": self.check_ffmpeg(),
|
"ffmpeg": self.check_ffmpeg(cfg["ffmpeg_path"]),
|
||||||
"server": self.check_server(),
|
"server": self.check_server(cfg["server_ip"]),
|
||||||
"server_port": self.check_port(SERVER_IP, 8554),
|
"server_port": self.check_port(cfg["server_ip"], 8554),
|
||||||
}
|
}
|
||||||
self.status_update.emit(status)
|
self.status_update.emit(status)
|
||||||
time.sleep(3)
|
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']):
|
for proc in psutil.process_iter(['name']):
|
||||||
if proc.info['name'] == 'mediamtx.exe':
|
if proc.info['name'] == 'mediamtx.exe':
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_ffmpeg(self):
|
def check_ffmpeg(self, path):
|
||||||
return os.path.exists(FFMPEG_PATH)
|
return os.path.exists(path)
|
||||||
|
|
||||||
def check_server(self):
|
def check_server(self, ip):
|
||||||
try:
|
try:
|
||||||
socket.create_connection((SERVER_IP, 8554), timeout=2)
|
socket.create_connection((ip, 8554), timeout=2)
|
||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
@@ -61,9 +133,12 @@ class ConnectionChecker(QThread):
|
|||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.config = load_config()
|
||||||
self.setWindowTitle("投屏源控制")
|
self.setWindowTitle("投屏源控制")
|
||||||
self.setFixedSize(520, 420)
|
self.setFixedSize(520, 420)
|
||||||
|
|
||||||
|
self.create_menu()
|
||||||
|
|
||||||
self.status_bar = QStatusBar()
|
self.status_bar = QStatusBar()
|
||||||
self.setStatusBar(self.status_bar)
|
self.setStatusBar(self.status_bar)
|
||||||
|
|
||||||
@@ -106,16 +181,9 @@ class MainWindow(QMainWindow):
|
|||||||
config_group.setLayout(config_layout)
|
config_group.setLayout(config_layout)
|
||||||
main_layout.addWidget(config_group)
|
main_layout.addWidget(config_group)
|
||||||
|
|
||||||
info_group = QGroupBox("流信息")
|
self.info_group = QGroupBox("流信息")
|
||||||
info_layout = QGridLayout()
|
self.update_info_group()
|
||||||
info_layout.addWidget(QLabel("服务器IP:"), 0, 0)
|
main_layout.addWidget(self.info_group)
|
||||||
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.btn_push = QPushButton("🎬 开始全屏投屏")
|
self.btn_push = QPushButton("🎬 开始全屏投屏")
|
||||||
self.btn_push.setFixedHeight(50)
|
self.btn_push.setFixedHeight(50)
|
||||||
@@ -130,44 +198,73 @@ class MainWindow(QMainWindow):
|
|||||||
main_layout.addWidget(self.log_label)
|
main_layout.addWidget(self.log_label)
|
||||||
|
|
||||||
self.checker = ConnectionChecker()
|
self.checker = ConnectionChecker()
|
||||||
|
self.checker.set_config(self.config)
|
||||||
self.checker.status_update.connect(self.update_status)
|
self.checker.status_update.connect(self.update_status)
|
||||||
self.checker.start()
|
self.checker.start()
|
||||||
|
|
||||||
logger.info("投屏源控制界面已初始化")
|
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):
|
def update_status(self, status):
|
||||||
if status["mediamtx"]:
|
if status["mediamtx"]:
|
||||||
self.mediamtx_indicator.setStyleSheet("color: green; font-size: 16px;")
|
self.mediamtx_indicator.setStyleSheet("color: green; font-size: 16px;")
|
||||||
self.mediamtx_label.setText(f"MediaMTX: ✓ 运行中")
|
self.mediamtx_label.setText("MediaMTX: ✓ 运行中")
|
||||||
else:
|
else:
|
||||||
self.mediamtx_indicator.setStyleSheet("color: red; font-size: 16px;")
|
self.mediamtx_indicator.setStyleSheet("color: red; font-size: 16px;")
|
||||||
self.mediamtx_label.setText(f"MediaMTX: ✗ 未运行")
|
self.mediamtx_label.setText("MediaMTX: ✗ 未运行")
|
||||||
|
|
||||||
if status["ffmpeg"]:
|
if status["ffmpeg"]:
|
||||||
self.ffmpeg_indicator.setStyleSheet("color: green; font-size: 16px;")
|
self.ffmpeg_indicator.setStyleSheet("color: green; font-size: 16px;")
|
||||||
self.ffmpeg_label.setText(f"FFmpeg: ✓ 已安装")
|
self.ffmpeg_label.setText("FFmpeg: ✓ 已安装")
|
||||||
else:
|
else:
|
||||||
self.ffmpeg_indicator.setStyleSheet("color: red; font-size: 16px;")
|
self.ffmpeg_indicator.setStyleSheet("color: red; font-size: 16px;")
|
||||||
self.ffmpeg_label.setText(f"FFmpeg: ✗ 未找到")
|
self.ffmpeg_label.setText("FFmpeg: ✗ 未找到")
|
||||||
|
|
||||||
if status["server"]:
|
if status["server"]:
|
||||||
self.server_indicator.setStyleSheet("color: green; font-size: 16px;")
|
self.server_indicator.setStyleSheet("color: green; font-size: 16px;")
|
||||||
self.server_label.setText(f"服务器连接: ✓ 可连接")
|
self.server_label.setText("服务器连接: ✓ 可连接")
|
||||||
else:
|
else:
|
||||||
self.server_indicator.setStyleSheet("color: orange; font-size: 16px;")
|
self.server_indicator.setStyleSheet("color: orange; font-size: 16px;")
|
||||||
self.server_label.setText(f"服务器连接: ✗ 连接失败")
|
self.server_label.setText("服务器连接: ✗ 连接失败")
|
||||||
|
|
||||||
if status["server_port"]:
|
if status["server_port"]:
|
||||||
self.port_indicator.setStyleSheet("color: green; font-size: 16px;")
|
self.port_indicator.setStyleSheet("color: green; font-size: 16px;")
|
||||||
self.port_label.setText(f"RTSP端口: ✓ 端口开放")
|
self.port_label.setText("RTSP端口: ✓ 端口开放")
|
||||||
else:
|
else:
|
||||||
self.port_indicator.setStyleSheet("color: orange; font-size: 16px;")
|
self.port_indicator.setStyleSheet("color: orange; font-size: 16px;")
|
||||||
self.port_label.setText(f"RTSP端口: ✗ 端口关闭")
|
self.port_label.setText("RTSP端口: ✗ 端口关闭")
|
||||||
|
|
||||||
def start_mediamtx(self):
|
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服务")
|
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)
|
time.sleep(3)
|
||||||
logger.info("MediaMTX服务已启动")
|
logger.info("MediaMTX服务已启动")
|
||||||
self.log_label.setText("日志: MediaMTX服务已启动")
|
self.log_label.setText("日志: MediaMTX服务已启动")
|
||||||
@@ -176,25 +273,26 @@ class MainWindow(QMainWindow):
|
|||||||
self.log_label.setText("日志: MediaMTX已在运行")
|
self.log_label.setText("日志: MediaMTX已在运行")
|
||||||
|
|
||||||
def push_full_screen(self):
|
def push_full_screen(self):
|
||||||
|
cfg = self.config
|
||||||
logger.info("开始全屏投屏")
|
logger.info("开始全屏投屏")
|
||||||
self.start_mediamtx()
|
self.start_mediamtx()
|
||||||
|
|
||||||
if not os.path.exists(FFMPEG_PATH):
|
if not os.path.exists(cfg["ffmpeg_path"]):
|
||||||
logger.error("FFmpeg未安装")
|
logger.error("FFmpeg未安装")
|
||||||
QMessageBox.warning(self, "错误", "FFmpeg未安装,请检查配置!")
|
QMessageBox.warning(self, "错误", "FFmpeg未安装,请检查配置!")
|
||||||
return
|
return
|
||||||
|
|
||||||
cmd = [
|
cmd = [
|
||||||
FFMPEG_PATH,
|
cfg["ffmpeg_path"],
|
||||||
"-f", "gdigrab", "-framerate", "30", "-i", "desktop",
|
"-f", "gdigrab", "-framerate", "30", "-i", "desktop",
|
||||||
"-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency",
|
"-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)}")
|
logger.info(f"执行FFmpeg命令: {' '.join(cmd)}")
|
||||||
subprocess.Popen(cmd, creationflags=subprocess.CREATE_NEW_CONSOLE)
|
subprocess.Popen(cmd, creationflags=subprocess.CREATE_NEW_CONSOLE)
|
||||||
logger.info("FFmpeg推流已启动")
|
logger.info("FFmpeg推流已启动")
|
||||||
self.log_label.setText("日志: 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):
|
def closeEvent(self, event):
|
||||||
self.checker.terminate()
|
self.checker.terminate()
|
||||||
|
|||||||
Reference in New Issue
Block a user