新增拉流转推功能,并按业务重设计摄像头/拉流独立界面
支持 RTMP/SRT/HLS/FLV/WHEP 拉流到 YouTube;摄像头与拉流页采用独立工作台布局,更新 README 文档。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
54
web2.py
54
web2.py
@@ -70,6 +70,12 @@ from web2_relay_pro import (
|
||||
from web2_youtube_ini import parse_youtube_ini, serialize_youtube_ini, validate_youtube_keys
|
||||
from web2_host_caps import gpu_status_payload, machine_summary_payload
|
||||
from web2_camera import camera_config_ready, list_capture_devices, load_camera_config, save_camera_config
|
||||
from web2_pull_stream import (
|
||||
load_pull_stream_config,
|
||||
protocol_help,
|
||||
pull_stream_config_ready,
|
||||
save_pull_stream_config,
|
||||
)
|
||||
from web2_pocketbase import REQUIRE_PB, pb_auth_refresh_and_validate
|
||||
|
||||
|
||||
@@ -96,6 +102,7 @@ def _label_short(prefix: str) -> str:
|
||||
"tiktok": "TikTok",
|
||||
"youtube": "YouTube",
|
||||
"camera": "摄像头",
|
||||
"pull": "拉流",
|
||||
"obs": "OBS",
|
||||
"web": "Web 控制台",
|
||||
}.get(prefix, prefix)
|
||||
@@ -119,6 +126,10 @@ def discover_script_entries() -> tuple:
|
||||
if path.is_file() and path.resolve() != web_path:
|
||||
entries.append({"script": path.name, "label": _label_short("camera")})
|
||||
|
||||
for path in sorted(base.glob("pull_stream*.py")):
|
||||
if path.is_file() and path.resolve() != web_path:
|
||||
entries.append({"script": path.name, "label": _label_short("pull")})
|
||||
|
||||
for path in sorted(base.glob("obs*.sh")):
|
||||
if path.is_file():
|
||||
entries.append({"script": path.name, "label": _label_short("obs")})
|
||||
@@ -147,6 +158,10 @@ def is_camera_script(script: str) -> bool:
|
||||
return script.endswith(".py") and _stem(script).startswith("camera")
|
||||
|
||||
|
||||
def is_pull_stream_script(script: str) -> bool:
|
||||
return script.endswith(".py") and _stem(script).startswith("pull_stream")
|
||||
|
||||
|
||||
def is_youtube_script(script: str) -> bool:
|
||||
return script.endswith(".py") and _stem(script).startswith("youtube")
|
||||
|
||||
@@ -855,6 +870,16 @@ def _camera_ready_message(process: str) -> str | None:
|
||||
return camera_config_ready(CONFIG_DIR)
|
||||
|
||||
|
||||
def _pull_stream_ready_message(process: str) -> str | None:
|
||||
if not is_pull_stream_script(script_for_pm2(process) or ""):
|
||||
return None
|
||||
return pull_stream_config_ready(CONFIG_DIR)
|
||||
|
||||
|
||||
def _stream_worker_ready_message(process: str) -> str | None:
|
||||
return _camera_ready_message(process) or _pull_stream_ready_message(process)
|
||||
|
||||
|
||||
@app.get("/camera/devices")
|
||||
async def camera_devices():
|
||||
return list_capture_devices()
|
||||
@@ -880,6 +905,31 @@ async def camera_config_post(request: Request):
|
||||
return {"message": msg, "config": load_camera_config(CONFIG_DIR)}
|
||||
|
||||
|
||||
@app.get("/pull_stream/config")
|
||||
async def pull_stream_config_get():
|
||||
return {"config": load_pull_stream_config(CONFIG_DIR)}
|
||||
|
||||
|
||||
@app.get("/pull_stream/protocols")
|
||||
async def pull_stream_protocols():
|
||||
return {"protocols": protocol_help()}
|
||||
|
||||
|
||||
@app.post("/pull_stream/config")
|
||||
async def pull_stream_config_post(request: Request):
|
||||
try:
|
||||
data = await request.json()
|
||||
except Exception:
|
||||
return JSONResponse({"error": "无效的 JSON"}, status_code=400)
|
||||
if not isinstance(data, dict):
|
||||
return JSONResponse({"error": "无效数据"}, status_code=400)
|
||||
patch = data.get("config") if isinstance(data.get("config"), dict) else data
|
||||
ok, msg = save_pull_stream_config(CONFIG_DIR, patch)
|
||||
if not ok:
|
||||
return JSONResponse({"error": msg}, status_code=400)
|
||||
return {"message": msg, "config": load_pull_stream_config(CONFIG_DIR)}
|
||||
|
||||
|
||||
@app.get("/start")
|
||||
async def start(request: Request, process: str = Query(..., description="进程名")):
|
||||
if REQUIRE_PB:
|
||||
@@ -902,7 +952,7 @@ async def start(request: Request, process: str = Query(..., description="进程
|
||||
if not ok:
|
||||
return JSONResponse({"output": msg}, status_code=400)
|
||||
await _stop_legacy_youtube_if_online()
|
||||
cam_msg = _camera_ready_message(process)
|
||||
cam_msg = _stream_worker_ready_message(process)
|
||||
if cam_msg:
|
||||
return JSONResponse({"output": cam_msg}, status_code=400)
|
||||
output = await control_start(process)
|
||||
@@ -963,7 +1013,7 @@ async def restart(request: Request, process: str = Query(..., description="进
|
||||
if not ok:
|
||||
return JSONResponse({"output": f"{stop_output}\n{msg}"}, status_code=400)
|
||||
await _stop_legacy_youtube_if_online()
|
||||
cam_msg = _camera_ready_message(process)
|
||||
cam_msg = _stream_worker_ready_message(process)
|
||||
if cam_msg:
|
||||
return JSONResponse({"output": f"{stop_output}\n{cam_msg}"}, status_code=400)
|
||||
start_output = await control_start(process)
|
||||
|
||||
Reference in New Issue
Block a user