video_monitor/app/utils/GlobalUtils.py

299 lines
12 KiB
Python
Raw Normal View History

2026-08-30 22:22:11 +08:00
import os
import time
import json
from datetime import datetime
import sys
from framework.settings import BASE_DIR, PROJECT_UA, PROJECT_BUILT, PROJECT_VERSION, PROJECT_FLAG, PROJECT_ADMIN_START_TIMESTAMP
from app.utils.ZLMediaKitApi import ZLMediaKitApi
from app.utils.Config import Config
from app.utils.Logger import CreateLogger
from app.utils.Database import Database
from app.utils.GB28181SipServer import GB28181SipServer
# ========== 应用名常量 ==========
APP_NAME_LIVE = "live"
APP_NAME_RTP = "rtp"
from app.utils.LanguageUtils import LANG_UI_DICT, LANG_VIEWS_USE_LANG_T
from app.models import *
# BASE_DIR 是项目根目录(扁平化后 settings.py 的父父目录即根目录)
# BASE_PARENT_DIR = str(BASE_DIR) # 扁平化后 BASE_PARENT_DIR 与 BASE_DIR 相同,均在根目录
g_filepath_settings_json = os.path.join(BASE_DIR, "settings.json")
g_filepath_config_json = os.path.join(BASE_DIR, "config.json")
g_config = Config(filepath=g_filepath_config_json)
__log_dir = os.path.join(BASE_DIR, "log")
if not os.path.exists(__log_dir):
os.makedirs(__log_dir)
__log_name = "%s%s.log" % ("monitor", datetime.now().strftime("%Y%m%d-%H%M%S"))
g_logger = CreateLogger(filepath=os.path.join(__log_dir, __log_name),
is_show_console=False,
log_debug=g_config.logDebug)
g_logger.info("%s v%s,%s" % (PROJECT_UA, PROJECT_VERSION, PROJECT_FLAG))
g_logger.info(PROJECT_BUILT)
g_logger.info("g_filepath_config_json=%s" % g_filepath_config_json)
g_logger.info("config.json:%s" % g_config.getStr())
g_logger.info("logDebug=%d" % g_config.logDebug)
__argv_extend = sys.argv[1] if len(sys.argv) >= 2 else None
g_logger.info("argv_extend=%s" % str(__argv_extend))
g_zlm = ZLMediaKitApi(logger=g_logger, config=g_config)
g_database = Database(logger=g_logger)
__config_sip_server = g_config.sipServer
# SIP 对象仅在此构造;绑定端口由显式 ServiceManager 负责。
g_gb28181SipServer = GB28181SipServer(
server_ip=__config_sip_server.get("sipServerIp"),
server_port=__config_sip_server.get("sipServerPort"),
server_id=__config_sip_server.get("sipServerId"),
realm=__config_sip_server.get("sipServerRealm"),
password=__config_sip_server.get("sipServerPass"),
sip_server_timeout=__config_sip_server.get("sipServerTimeout"),
sip_server_expiry=__config_sip_server.get("sipServerExpiry"),
sip_transfer_mode=__config_sip_server.get("sipTransferMode"),
rtp_transfer_mode=__config_sip_server.get("rtpTransferMode"),
rtp_transfer_audio_type=__config_sip_server.get("rtpTransferAudioType"),
auto_invite_after_rec_cate_log=__config_sip_server.get("autoInviteAfterRecCateLog"),
admin_host=g_config.adminHost,
zlm=g_zlm,
logger=g_logger
)
g_pull_stream_types = [
{"id": 1, "name": "RTSP"},
{"id": 2, "name": "RTMP"},
{"id": 3, "name": "FLV"},
{"id": 4, "name": "HLS"},
{"id": 21, "name": "GB28181"},
{"id": 31, "name": "cRTSP"},
{"id": 32, "name": "cRTMP"}
]
def get_audio_types(lang='zh'):
"""返回音频类型"""
T = LANG_UI_DICT.get(lang, {})
result = []
__audio_types = [
{"type": 0, "name": "静音", "name_key": "audio_pull_mute"},
{"type": 1, "name": "原始音频", "name_key": "audio_pull_original"}
]
for audio_type in __audio_types:
result.append({
"type": audio_type["type"],
"name": T.get(audio_type["name_key"], audio_type["name"])
})
return result
g_session_key_user = "user"
g_session_key_captcha = "captcha"
def _bool_cfg(v):
if v is None:
return False
if isinstance(v, bool):
return v
if isinstance(v, (int, float)):
return v != 0
return str(v).strip().lower() in ("1", "true", "yes", "on")
class GlobalUtils(object):
@staticmethod
def addStreamProxy(stream, lang=None):
"""开启流代理拉流到ZLM"""
__ret = False
__msg = LANG_VIEWS_USE_LANG_T(lang, "msg_unknown_error")
if stream.pull_stream_type in [1, 2, 3, 4]:
enable_rtmp = 1 if g_config.isEnableMediaProxyRtmp else 0
add_key, add_msg = g_zlm.addStreamProxy(app=stream.app,
name=stream.name,
origin_url=stream.pull_stream_url,
is_audio=stream.is_audio,
enable_rtmp=enable_rtmp)
if add_key:
__ret = True
__msg = LANG_VIEWS_USE_LANG_T(lang, "stream_forward_enabled_success")
else:
__msg = add_msg
elif stream.pull_stream_type == 21:
__ret, __msg = g_gb28181SipServer.request_invite(client_id=stream.camera_device_id, channel_id=stream.name)
if __ret:
__msg = LANG_VIEWS_USE_LANG_T(lang, "stream_forward_enabled_success")
elif stream.pull_stream_type in [31, 32]:
__msg = LANG_VIEWS_USE_LANG_T(lang, "media_push_stream_hint")
else:
__msg = LANG_VIEWS_USE_LANG_T(lang, "media_protocol_not_supported")
return __ret, __msg
@staticmethod
def addAllStreamProxy(lang=None):
"""开启所有流代理"""
ret = False
msg = LANG_VIEWS_USE_LANG_T(lang, "msg_unknown_error")
online_streams = g_zlm.getMediaList()
online_stream_dict = {}
if len(online_streams) == 0:
g_database.execute("update av_stream set forward_state=0")
else:
for d in online_streams:
an = "{app}_{name}".format(app=d["app"], name=d["name"])
online_stream_dict[an] = d
success_count = 0
error_count = 0
streams = StreamModel.objects.all()
for stream in streams:
stream_an = "{app}_{name}".format(app=stream.app, name=stream.name)
if online_stream_dict.get(stream_an):
success_count += 1
else:
__add_ret, __add_msg = GlobalUtils.addStreamProxy(stream, lang=lang)
if __add_ret:
stream.forward_state = 1
stream.save()
success_count += 1
else:
error_count += 1
ret = True
msg = LANG_VIEWS_USE_LANG_T(lang, "msg_batch_result") % (success_count, error_count)
return ret, msg
@staticmethod
def delStreamProxy(stream, lang=None):
"""关闭流代理"""
__ret = False
__msg = LANG_VIEWS_USE_LANG_T(lang, "msg_unknown_error")
if stream.pull_stream_type in [1, 2, 3, 4]:
del_flag, del_msg = g_zlm.delStreamProxy(app=stream.app, name=stream.name)
if del_flag:
__ret = True
__msg = LANG_VIEWS_USE_LANG_T(lang, "media_forward_stop_success")
else:
__msg = del_msg
elif stream.pull_stream_type == 21:
__ret, __msg = g_gb28181SipServer.request_bye(client_id=stream.camera_device_id, channel_id=stream.name)
if __ret:
__msg = LANG_VIEWS_USE_LANG_T(lang, "media_forward_stop_success")
elif stream.pull_stream_type == 31:
__ret, __msg = g_zlm.close_streams(schema="rtsp", app=stream.app, name=stream.name)
if __ret:
__msg = LANG_VIEWS_USE_LANG_T(lang, "media_forward_stop_success")
elif stream.pull_stream_type == 32:
__ret, __msg = g_zlm.close_streams(schema="rtmp", app=stream.app, name=stream.name)
if __ret:
__msg = LANG_VIEWS_USE_LANG_T(lang, "media_forward_stop_success")
else:
__msg = LANG_VIEWS_USE_LANG_T(lang, "media_protocol_not_supported")
return __ret, __msg
@staticmethod
def delAllStreamProxy(lang=None):
"""关闭所有流代理"""
online_streams = g_zlm.getMediaList()
for d in online_streams:
stream = StreamModel.objects.filter(app=d["app"], name=d["name"]).first()
if stream:
__ret, __msg = GlobalUtils.delStreamProxy(stream, lang=lang)
g_database.execute("update av_stream set forward_state=0")
@staticmethod
def apply_runtime_config(before_cfg):
"""保存 config.json 后热更新运行时组件(不重启 Django 进程)。"""
before_cfg = before_cfg or {}
after_cfg = g_config.to_dict(include_secrets=True)
notes = []
def _sip_snapshot(cfg):
sip = dict((cfg or {}).get("sipServer") or {})
return (
sip.get("sipServerIp"), sip.get("sipServerPort"), sip.get("sipServerId"),
sip.get("sipServerRealm"), sip.get("sipServerPass"), sip.get("sipServerTimeout"),
sip.get("sipServerExpiry"), sip.get("sipTransferMode"), sip.get("rtpTransferMode"),
sip.get("rtpTransferAudioType"), sip.get("autoInviteAfterRecCateLog"),
)
if _sip_snapshot(before_cfg) != _sip_snapshot(after_cfg):
try:
from app.services.lifecycle import is_service_leader
if is_service_leader():
GlobalUtils._reload_gb28181_sip()
g_logger.info("apply_runtime_config: GB28181 SIP reloaded")
else:
notes.append("gb28181")
except Exception as e:
g_logger.warning("apply_runtime_config: GB28181 reload failed: %s", e)
notes.append("gb28181")
before_rec = _bool_cfg(before_cfg.get("recordingEnabled"))
after_rec = bool(g_config.recordingEnabled)
if after_rec != before_rec:
try:
from app.services.lifecycle import get_service_manager
get_service_manager().reconcile()
except Exception as e:
g_logger.warning("apply_runtime_config: recording manager: %s", e)
if before_cfg.get("adminPort") != after_cfg.get("adminPort"):
notes.append("adminPort")
if _bool_cfg(before_cfg.get("logDebug")) != bool(g_config.logDebug):
notes.append("logDebug")
media_keys = (
"mediaHttpPort", "mediaRtspPort", "mediaRtmpPort",
"mediaStartPath", "mediaStartConfigPath", "mediaSecret",
)
if any(before_cfg.get(k) != after_cfg.get(k) for k in media_keys):
notes.append("zlm")
return notes
@staticmethod
def _reload_gb28181_sip():
global g_gb28181SipServer
try:
g_gb28181SipServer.stop()
except Exception:
pass
sip = dict(g_config.sipServer or {})
g_gb28181SipServer = GB28181SipServer(
server_ip=sip.get("sipServerIp"),
server_port=sip.get("sipServerPort"),
server_id=sip.get("sipServerId"),
realm=sip.get("sipServerRealm"),
password=sip.get("sipServerPass"),
sip_server_timeout=sip.get("sipServerTimeout"),
sip_server_expiry=sip.get("sipServerExpiry"),
sip_transfer_mode=sip.get("sipTransferMode"),
rtp_transfer_mode=sip.get("rtpTransferMode"),
rtp_transfer_audio_type=sip.get("rtpTransferAudioType"),
auto_invite_after_rec_cate_log=sip.get("autoInviteAfterRecCateLog"),
admin_host=g_config.adminHost,
zlm=g_zlm,
logger=g_logger,
)
g_gb28181SipServer.start()
class CheckServerUtils():
@staticmethod
def checkVersion(request_ip, peer_ip, peer_port, lang=None):
# Network and host information parameters are intentionally ignored.
from app.services.telemetry import check_update
return check_update(lang=lang)
@staticmethod
def reportHeart(report_count, lang=None):
from app.services.telemetry import send_heartbeat
return send_heartbeat(report_count)