video_monitor/app/views/StreamView.py

1232 lines
51 KiB
Python
Raw Normal View History

2026-08-30 22:22:11 +08:00
import json
from app.views.ViewsBase import *
from app.models import *
from django.shortcuts import render, redirect
from app.utils.Utils import buildPageLabels, group_by_field, GB28181CodeUtils
from app.utils.UploadUtils import UploadUtils
from app.utils.LogUtils import LogUtils
from app.utils.Credentials import encrypt_credential
def _hide_stream_credentials(stream):
if not isinstance(stream, dict):
return stream
stream["has_pull_stream_username"] = bool(stream.get("pull_stream_username"))
stream["has_pull_stream_password"] = bool(stream.get("pull_stream_password"))
stream["pull_stream_username"] = ""
stream["pull_stream_password"] = ""
return stream
def online(request):
context = {}
# data = Camera.objects.all().order_by("-sort")
return render(request, 'app/stream/online.html', context)
def index(request):
context = {}
params = f_parseGetParams(request)
stream_code = params.get("code","").strip()
context["search_text"] = stream_code # v4.716新增
return render(request, 'app/stream/index.html', context)
def api_openAddContext(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
request_ip = f_parseRequestIp(request) # v4.725新增) 获取实时请求ip
stream_info = {}
if "GET" == request.method:
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parseGetParams(request)
g_logger.info("StreamView.openAddContext() request_ip:%s,params:%s" % (request_ip,str(params)))
code = GB28181CodeUtils().generate_by_time()
app = g_zlm.default_stream_app
name = code
camera_device_id = "default"
stream_info = {
"code": code,
"app": app,
"name": name,
"camera_device_id": camera_device_id,
"is_audio": 0,
"pull_stream_username": "",
"pull_stream_password": "",
"rtspUrl": g_zlm.get_rtspUrl(app=app,name=name,request_ip=request_ip),
"hlsUrl": g_zlm.get_hlsUrl(app=app,name=name,request_ip=request_ip),
"httpMp4Url": g_zlm.get_httpMp4Url(app=app,name=name,request_ip=request_ip),
"wsMp4Url": g_zlm.get_wsMp4Url(app=app,name=name,request_ip=request_ip),
"record_enable": 0,
}
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg,
"stream": stream_info,
"pull_stream_types": g_pull_stream_types,
"audio_types": get_audio_types(f_parseRequestLang(request))
}
g_logger.info("StreamView.openAddContext() res:%s" % str(res))
return f_responseJson(res)
def api_openAdd(request):
__ret = False
__msg = LANG_VIEWS_T(request, "msg_unknown_error")
if "POST" == request.method:
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
try:
params = f_parsePostParams(request)
lang = f_parseRequestLang(request)
g_logger.info("StreamView.openAdd() params:%s" % str(params))
code = params.get("code", "").strip()
# app = params.get("app", "live").strip()
# name = params.get("name", "").strip()
nickname = params.get("nickname", "").strip()
pull_stream_type = int(params.get("pull_stream_type", 0))
pull_stream_url = params.get("pull_stream_url", "").strip()
pull_stream_ip = params.get("pull_stream_ip", "").strip()
pull_stream_port = int(params.get("pull_stream_port"))
camera_name = params.get("camera_name", "").strip()
camera_manufacturer = params.get("camera_manufacturer", "").strip()
camera_device_id = params.get("camera_device_id", "").strip()
remark = params.get("remark", "").strip()
onvif_username = params.get("onvif_username", "").strip()
onvif_password = params.get("onvif_password", "").strip()
is_audio = int(params.get("is_audio", 0))
record_enable = int(params.get("record_enable", 0))
if code == "":
raise Exception(LANG_VIEWS_T(request, "stream_code_invalid"))
stream = StreamModel.objects.filter(code=code).filter()
if stream:
raise Exception(LANG_VIEWS_T(request, "msg_code_already_exists"))
# if app == "":
# raise Exception(LANG_VIEWS_T(request, "stream_app_invalid"))
# if name == "":
# raise Exception(LANG_VIEWS_T(request, "stream_name_invalid"))
if nickname == "":
raise Exception(LANG_VIEWS_T(request, "stream_name_incorrect"))
else:
nickname_len = len(nickname)
if len(nickname) > 50:
raise Exception(LANG_VIEWS_T(request, "stream_name_max_length") % nickname_len)
if pull_stream_type == 21:
raise Exception(LANG_VIEWS_T(request, "stream_gb28181_hint"))
else:
if pull_stream_type == 1 or pull_stream_type == 2 or pull_stream_type == 3 or pull_stream_type == 4:
pass
else:
raise Exception(LANG_VIEWS_T(request, "stream_protocol_not_supported"))
if pull_stream_url == "":
raise Exception(LANG_VIEWS_T(request, "stream_url_incorrect"))
if camera_device_id == "":
camera_device_id = code
user_id = f_sessionReadUserId(request)
now_date = datetime.now()
stream = StreamModel()
stream.user_id = user_id
stream.sort = 0
stream.code = code
stream.app = APP_NAME_LIVE
stream.name = code
stream.pull_stream_url = pull_stream_url
stream.pull_stream_type = pull_stream_type # v4.405新增
stream.pull_stream_transfer_mode = 0 # v4.405新增
stream.pull_stream_ip = pull_stream_ip # v4.405新增
stream.pull_stream_port = pull_stream_port # v4.405新增
stream.pull_stream_username = encrypt_credential(onvif_username)
stream.pull_stream_password = encrypt_credential(onvif_password)
stream.nickname = nickname
stream.remark = remark
stream.forward_state = 0 # 默认未开启转发
stream.is_audio = is_audio # v4.405新增
stream.camera_name = camera_name
stream.camera_manufacturer = camera_manufacturer
stream.camera_device_id = camera_device_id
stream.record_enable = record_enable
stream.create_time = now_date
stream.last_update_time = now_date
stream.add_type = 0
stream.state = 0
stream.save()
# 添加日志
user_id = f_sessionReadUserId(request)
lang = f_parseRequestLang(request)
LogUtils.add_stream_log(user_id=user_id, stream_code=code,
log_type=LogUtils.LOG_TYPE_ADD, lang=lang)
__msg = LANG_VIEWS_T(request, "msg_add_success")
__ret = True
except Exception as e:
__msg = str(e)
else:
__msg = __check_msg
else:
__msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if __ret else 0,
"msg": __msg
}
g_logger.info("StreamView.openAdd() res:%s" % str(res))
return f_responseJson(res)
def api_openEditContext(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
request_ip = f_parseRequestIp(request) # v4.725新增) 获取实时请求ip
stream_info = {}
if "GET" == request.method:
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parseGetParams(request)
g_logger.info("StreamView.openEditContext() request_ip:%s,params:%s" % (request_ip,str(params)))
stream_code = params.get("code", "").strip()
stream = g_database.select("select * from av_stream where code=%s limit 1", [stream_code])
if len(stream) > 0:
stream_info = _hide_stream_credentials(stream[0])
camera_device_id = stream_info["camera_device_id"]
if camera_device_id is None or camera_device_id == "":
stream_info["camera_device_id"] = stream_code
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
else:
msg = LANG_VIEWS_T(request, "stream_not_found")
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg,
"stream": stream_info,
"pull_stream_types": g_pull_stream_types,
"audio_types": get_audio_types(f_parseRequestLang(request))
}
g_logger.info("StreamView.openEditContext() res:%s" % str(res))
return f_responseJson(res)
def api_openStreamByAppAndName(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
data = {}
if request.method == 'GET':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parseGetParams(request)
g_logger.info("StreamView.openStreamByAppAndName() params:%s" % str(params))
app = params.get("app", "").strip()
name = params.get("name", "").strip()
if app and name:
streams = g_database.select(
"select * from av_stream where app=%s and name=%s limit 1", [app, name])
if len(streams) > 0:
data = _hide_stream_credentials(streams[0])
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
else:
msg = LANG_VIEWS_T(request, "stream_not_found")
else:
msg = LANG_VIEWS_T(request, "stream_app_name_required")
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg,
"data": data
}
g_logger.info("StreamView.openStreamByAppAndName() res:%s" % str(res))
return f_responseJson(res)
def api_openEdit(request):
__ret = False
__msg = LANG_VIEWS_T(request, "msg_unknown_error")
if "POST" == request.method:
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
try:
params = f_parsePostParams(request)
g_logger.info("StreamView.openEdit() params:%s" % str(params))
lang = f_parseRequestLang(request)
code = params.get("code", "").strip()
nickname = params.get("nickname", "").strip()
pull_stream_url = params.get("pull_stream_url", "").strip()
pull_stream_ip = params.get("pull_stream_ip", "").strip()
pull_stream_port = int(params.get("pull_stream_port", 0))
camera_name = params.get("camera_name", "").strip()
camera_manufacturer = params.get("camera_manufacturer", "").strip()
camera_device_id = params.get("camera_device_id", "").strip()
remark = params.get("remark", "").strip()
is_audio = int(params.get("is_audio", 0))
record_enable = int(params.get("record_enable", 0))
if code == "":
raise Exception(LANG_VIEWS_T(request, "stream_code_invalid"))
if nickname == "":
raise Exception(LANG_VIEWS_T(request, "stream_name_incorrect"))
else:
nickname_len = len(nickname)
if len(nickname) > 50:
raise Exception(LANG_VIEWS_T(request, "stream_name_max_length") % nickname_len)
stream = StreamModel.objects.filter(code=code).first()
if stream:
if (stream.pull_stream_type == 1 or stream.pull_stream_type == 2 or
stream.pull_stream_type == 3 or stream.pull_stream_type == 4):
if pull_stream_url == "":
raise Exception(LANG_VIEWS_T(request, "stream_url_required"))
# 检测是否修改摄像头视频地址 start
if stream.pull_stream_url != pull_stream_url:
# 拉流地址不一致,则首先需要停止拉流代理
__del_ret, __del_msg = GlobalUtils.delStreamProxy(stream, lang=lang)
stream.forward_state = 0
stream.pull_stream_url = pull_stream_url
# 检测是否修改摄像头视频地址 end
if stream.pull_stream_type != 21:
# 非gb28181接入需要设置分组编号
stream.camera_device_id = camera_device_id
stream.pull_stream_ip = pull_stream_ip # v4.405新增
stream.pull_stream_port = pull_stream_port # v4.405新增
stream.is_audio = is_audio
stream.nickname = nickname
stream.remark = remark
stream.camera_name = camera_name
stream.camera_manufacturer = camera_manufacturer
old_record = stream.record_enable
stream.record_enable = record_enable
stream.last_update_time = datetime.now()
stream.save()
from app.views.NvrView import _sync_stream_recording
_sync_stream_recording(stream)
# 添加日志
user_id = f_sessionReadUserId(request)
lang = f_parseRequestLang(request)
LogUtils.add_stream_log(user_id=user_id, stream_code=code,
log_type=LogUtils.LOG_TYPE_EDIT, lang=lang)
__msg = LANG_VIEWS_T(request, "msg_edit_success")
__ret = True
else:
__msg = LANG_VIEWS_T(request, "stream_not_found")
except Exception as e:
__msg = str(e)
else:
__msg = __check_msg
else:
__msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if __ret else 0,
"msg": __msg
}
g_logger.info("StreamView.openEdit() res:%s" % str(res))
return f_responseJson(res)
def api_openPlayer(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
info = {}
if request.method == 'GET':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parseGetParams(request)
g_logger.info("StreamView.openPlayer() params:%s" % str(params))
request_ip = f_parseRequestIp(request) # v4.725新增) 获取实时请求ip
app = params.get("app","").strip()
name = params.get("name","").strip()
try:
if app and name:
media_info = g_zlm.getMediaInfo(app=app, name=name)
media_is_online = 1 if media_info else 0
# v4.706确认,返回结构需要保持当前现状
stream_info = {
"is_online": media_is_online,
"app": app,
# "produce_speed": produce_speed,
# "video": video_str,
"aliveSecond": media_info.get("aliveSecond", 0),
"totalReaderCount": media_info.get("totalReaderCount", 0),
"video_codec_name": media_info.get("video_codec_name", ""),
"video_width": media_info.get("video_width", 0),
"video_height": media_info.get("video_height", 0),
# "audio": audio_str,
# "originUrl": d.get("originUrl"), # 推流地址
# "originType": d.get("originType"), # 推流地址采用的推流协议类型
# "originTypeStr": d.get("originTypeStr"), # 推流地址采用的推流协议类型(字符串)
# "clients": d.get("totalReaderCount"), # 客户端总数量
# "schemas_clients": schemas_clients,
"videoUrl": g_zlm.get_wsMp4Url(app=app, name=name, request_ip=request_ip),
"wsHost": g_zlm.get_wsHost(request_ip=request_ip),
"wsMp4Url": g_zlm.get_wsMp4Url(app=app, name=name, request_ip=request_ip),
"wsFlvUrl": g_zlm.get_wsFlvUrl(app=app, name=name, request_ip=request_ip),
"httpMp4Url": g_zlm.get_httpMp4Url(app=app, name=name, request_ip=request_ip),
"httpFlvUrl": g_zlm.get_httpFlvUrl(app=app, name=name, request_ip=request_ip),
"rtspUrl": g_zlm.get_rtspUrl(app=app, name=name, request_ip=request_ip),
"rtmpUrl": g_zlm.get_rtmpUrl(app=app, name=name, request_ip=request_ip)
}
stream = StreamModel.objects.filter(app=app, name=name).first()
if stream:
stream_info["code"] = stream.code
stream_info["pull_stream_type"] = stream.pull_stream_type
else:
stream_info["code"] = name
stream_info["pull_stream_type"] = 0
else:
stream_info = {
"pull_stream_type": 0,
"is_online": 0,
}
except Exception as e:
g_logger.error("StreamView.openPlayer() e:%s" % str(e))
stream_info = {
"pull_stream_type": 0,
"is_online": 0,
}
info["stream"] = stream_info
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg,
"info": info
}
g_logger.info("StreamView.openPlayer() res:%s" % str(res))
return f_responseJson(res)
def player(request):
context = {}
request_ip = f_parseRequestIp(request) # v4.725新增) 获取实时请求ip
params = f_parseGetParams(request)
g_logger.info("StreamView.player() request_ip:%s,params:%s" % (request_ip,str(params)))
app = params.get("app","").strip()
name = params.get("name","").strip()
try:
if app and name:
media_info = g_zlm.getMediaInfo(app=app, name=name)
media_is_online = 1 if media_info else 0
# v4.706确认,返回结构需要保持当前现状
stream_info = {
"is_online": media_is_online,
"app": app,
"name": name,
"aliveSecond": media_info.get("aliveSecond", 0),
"totalReaderCount": media_info.get("totalReaderCount", 0),
"video_codec_name": media_info.get("video_codec_name", ""),
"video_width": media_info.get("video_width", 0),
"video_height": media_info.get("video_height", 0),
"videoUrl": g_zlm.get_wsMp4Url(app=app, name=name, request_ip=request_ip),
"wsHost": g_zlm.get_wsHost(request_ip=request_ip),
"wsMp4Url": g_zlm.get_wsMp4Url(app=app, name=name, request_ip=request_ip),
"wsFlvUrl": g_zlm.get_wsFlvUrl(app=app, name=name, request_ip=request_ip),
"httpMp4Url": g_zlm.get_httpMp4Url(app=app, name=name, request_ip=request_ip),
"httpFlvUrl": g_zlm.get_httpFlvUrl(app=app, name=name, request_ip=request_ip),
"rtspUrl": g_zlm.get_rtspUrl(app=app, name=name, request_ip=request_ip),
"rtmpUrl": g_zlm.get_rtmpUrl(app=app, name=name, request_ip=request_ip)
}
# 优先:直接按 app+name 查找live 原始流、rtp 等内部流)
stream = StreamModel.objects.filter(app=app, name=name).first()
if stream:
stream_info["code"] = stream.code
stream_info["pull_stream_type"] = stream.pull_stream_type
else:
stream_info["code"] = name
stream_info["pull_stream_type"] = 0
else:
stream_info = {
"pull_stream_type": 0,
"is_online": 0,
}
except Exception as e:
g_logger.error("StreamView.player() e:%s" % str(e))
stream_info = {
"pull_stream_type": 0,
"is_online": 0,
}
# 用 json.dumps 序列化为合法 JSON 字符串True/False -> true/false
# 避免 Django 模板 {{ stream|safe }} 把 Python bool 渲染成 JS 标识符导致 "True/False is not defined"
context["stream_json"] = json.dumps(stream_info, ensure_ascii=False)
g_logger.info("StreamView.player() stream_info:%s" % str(stream_info))
return render(request, 'app/stream/player.html', context)
def api_openIndex(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
data = []
pageData = {}
if request.method == 'GET':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parseGetParams(request)
# g_logger.info("StreamView.openIndex() params:%s" % str(params))
# 同步数据库和在线流状态 start
medias = g_zlm.getMediaList()
if len(medias) == 0:
# 流媒体服务不在线,全部更新下线状态
g_database.execute("update av_stream set forward_state=0")
else:
media_dict = {}
for m in medias:
media_dict[m["an"]] = m
db_streams = f_dbReadStreamData()
for d in db_streams:
d_id = int(d.get("id", 0))
d_forward_state = d.get("forward_state", 0)
app_name = "{app}_{name}".format(app=d["app"], name=d["name"])
if media_dict.get(app_name):
if d_forward_state != 1:
g_database.execute("update av_stream set forward_state=1 where id=%s", [d_id])
else:
if d_forward_state != 0:
g_database.execute("update av_stream set forward_state=0 where id=%s", [d_id])
# 同步数据库和在线流状态 end
page = params.get('p', 1)
page_size = params.get('ps', 10)
search_text = str(params.get('search_text', "")).strip() # v4.638新增
search_status = int(params.get('search_status', -1)) # v4.716新增 转发状态筛选:-1全部 0未转发 1转发中
try:
page = int(page)
except:
page = 1
try:
page_size = int(page_size)
if page_size < 1:
page_size = 1
except:
page_size = 10
# 构建查询条件
conditions = []
query_params = []
if search_text:
conditions.append("(nickname like %s or code = %s)")
query_params.extend(["%%%s%%" % search_text, search_text])
if search_status != -1:
conditions.append("forward_state = %s")
query_params.append(search_status)
if search_text and search_status != -1:
sql = ("select * from av_stream where (nickname like %s or code = %s) "
"and forward_state = %s order by id desc")
elif search_text:
sql = "select * from av_stream where (nickname like %s or code = %s) order by id desc"
elif search_status != -1:
sql = "select * from av_stream where forward_state = %s order by id desc"
else:
sql = "select * from av_stream order by id desc"
skip = (page - 1) * page_size
data = g_database.select(sql, query_params)
rec_ids = set()
try:
from app.recording.manager import get_recording_manager
rec_ids = set(get_recording_manager().list_recording())
except Exception:
pass
for d in data:
_hide_stream_credentials(d)
camera_device_id = d.get("camera_device_id")
if camera_device_id is None or camera_device_id == "":
camera_device_id = d["code"]
d["camera_device_id"] = camera_device_id
sid = int(d.get("id", 0) or 0)
d["record_enable"] = int(d.get("record_enable", 0) or 0)
d["is_recording"] = 1 if sid in rec_ids else 0
grouped_data = group_by_field(data, field="camera_device_id")
count = len(grouped_data)
if count > 0:
grouped_data = grouped_data[skip:skip + page_size]
for i, group in enumerate(grouped_data):
# print(f"组 {i + 1}: {group}")
for d in group:
camera_name = d.get("camera_name","unknown name")
d["camera_name"] = camera_name
camera_manufacturer = d.get("camera_manufacturer","unknown manufacturer")
d["camera_manufacturer"] = camera_manufacturer
d["last_update_time"] = d.get("last_update_time").strftime("%Y/%m/%d %H:%M")
data = grouped_data
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
else:
data = []
msg = LANG_VIEWS_T(request, "msg_empty")
page_num = int(count / page_size) # 总页数
if count % page_size > 0:
page_num += 1
pageData = {
"page": page,
"page_size": page_size,
"page_num": page_num,
"count": count,
"pageLabels": buildPageLabels(page=page, page_num=page_num, lang=f_parseRequestLang(request))
}
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg,
"data": data,
"pageData": pageData,
"extra": {
"audioTypes": get_audio_types(f_parseRequestLang(request)),
"pullStreamTypes": g_pull_stream_types
}
}
return f_responseJson(res)
def api_openExportFile(request):
"""导出所有摄像头数据为Excel文件与导入模板格式一致"""
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
if request.method == 'GET':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
g_logger.info("StreamView.openExportFile()")
import io
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
# lang = f_parseRequestLang(request)
try:
# 查询所有摄像头数据
streams = StreamModel.objects.all().order_by("id")
# 创建工作簿
wb = Workbook()
ws = wb.active
ws.title = "摄像头数据"
# 定义表头(与导入模板完全一致)
headers = [
"摄像头编号", "摄像头昵称", "拉流地址", "IP地址", "端口",
"用户名", "密码", "备注", "是否音频", "分组编号"
]
# 表头样式
header_font = Font(name='微软雅黑', size=11, bold=True, color='FFFFFF')
header_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
header_align = Alignment(horizontal='center', vertical='center', wrap_text=True)
thin_border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# 写入表头
for col_idx, header in enumerate(headers, 1):
cell = ws.cell(row=1, column=col_idx, value=header)
cell.font = header_font
cell.fill = header_fill
cell.alignment = header_align
cell.border = thin_border
# 数据样式
data_font = Font(name='微软雅黑', size=10)
data_align = Alignment(horizontal='left', vertical='center', wrap_text=True)
# 写入数据行
for row_idx, stream in enumerate(streams, 2):
# 设备ID处理如果为空则使用code
camera_device_id = stream.camera_device_id if stream.camera_device_id else stream.code
row_data = [
stream.code or "",
stream.nickname or "",
stream.pull_stream_url or "",
stream.pull_stream_ip or "",
stream.pull_stream_port or 554,
"[REDACTED]" if stream.pull_stream_username else "",
"[REDACTED]" if stream.pull_stream_password else "",
stream.remark or "",
stream.is_audio or 0,
camera_device_id
]
for col_idx, value in enumerate(row_data, 1):
cell = ws.cell(row=row_idx, column=col_idx, value=value)
cell.font = data_font
cell.alignment = data_align
cell.border = thin_border
# 设置列宽
col_widths = [20, 20, 40, 18, 10, 18, 18, 30, 10, 20]
for col_idx, width in enumerate(col_widths, 1):
ws.column_dimensions[ws.cell(row=1, column=col_idx).column_letter].width = width
# 设置行高(表头)
ws.row_dimensions[1].height = 25
# 生成响应
buffer = io.BytesIO()
wb.save(buffer)
buffer.seek(0)
response = HttpResponse(buffer.getvalue(),
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
filename = "cameras_export_%s.xlsx" % datetime.now().strftime("%Y%m%d_%H%M%S")
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
return response
except Exception as e:
msg = str(e)
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg
}
g_logger.info("StreamView.openExportFile() res:%s" % str(res))
return f_responseJson(res)
def api_openImportFile(request):
__ret = False
__msg = LANG_VIEWS_T(request, "msg_unknown_error")
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
lang = f_parseRequestLang(request)
g_logger.info("StreamView.openImportFile() params:%s" % str(params))
try:
file = request.FILES.get("file")
importRemark = str(params.get("importRemark", "")).strip()
upload_utils = UploadUtils()
__upload_ret, __upload_msg, __upload_data = upload_utils.upload_camera_xlsx(file=file,
upload_dir=g_config.storageTempDir,
lang=lang)
if __upload_ret:
user_id = f_sessionReadUserId(request)
success_count = 0
error_count = 0
for d in __upload_data:
try:
stream_code = d["code"]
if not stream_code: # 未填写编号,则自动生成编号
stream_code = GB28181CodeUtils().generate_by_time()
nickname = d["nickname"]
pull_stream_url = d["pull_stream_url"]
pull_stream_ip = d["pull_stream_ip"]
pull_stream_port = d["pull_stream_port"]
username = d["username"]
password = d["password"]
camera_device_id = str(d.get("camera_device_id", "")).strip()
is_audio = int(d.get("is_audio", 0))
remark = str(d.get("remark", "")).strip()
remark = "remark=%s,importRemark=%s" % (remark, importRemark)
if camera_device_id is None or camera_device_id == "":
camera_device_id = stream_code
if pull_stream_url.startswith("rtsp://") or pull_stream_url.startswith("rtsps://"):
pull_stream_type = 1
elif pull_stream_url.startswith("rtmp://") or pull_stream_url.startswith("rtmps://"):
pull_stream_type = 2
elif pull_stream_url.startswith("http://") or pull_stream_url.startswith("https://"):
pull_stream_type = 3
else:
raise Exception(LANG_VIEWS_T(request, "stream_url_format_error"))
stream = StreamModel.objects.filter(code=stream_code).first()
if stream:
raise Exception(LANG_VIEWS_T(request, "stream_code_exists"))
else:
stream = StreamModel()
stream.user_id = user_id
stream.sort = 0
stream.code = stream_code
stream.app = g_zlm.default_stream_app
stream.name = stream_code
stream.pull_stream_url = pull_stream_url
stream.pull_stream_type = pull_stream_type # v4.405新增
stream.pull_stream_transfer_mode = 0 # v4.405新增
stream.pull_stream_ip = pull_stream_ip # v4.405新增
stream.pull_stream_port = pull_stream_port # v4.405新增
stream.pull_stream_username = encrypt_credential(username)
stream.pull_stream_password = encrypt_credential(password)
stream.nickname = nickname
stream.remark = remark
stream.forward_state = 0 # 默认未开启转发
stream.camera_device_id = camera_device_id # v4.638新增
stream.create_time = datetime.now()
stream.last_update_time = datetime.now()
stream.add_type = 1
stream.state = 0
stream.is_audio = is_audio # v4.405新增
stream.save()
success_count += 1
except Exception as e:
g_logger.error("StreamView.openImportFile() error: %s, d=%s" % (str(e),str(d)))
error_count += 1
__ret = True
__msg = LANG_VIEWS_T(request, "msg_batch_result") % (success_count, error_count)
else:
__msg = __upload_msg
except Exception as e:
__msg = str(e)
else:
__msg = __check_msg
else:
__msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if __ret else 0,
"msg": __msg
}
g_logger.info("StreamView.openImportFile() res:%s" % str(res))
return f_responseJson(res)
def api_openDel(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
lang = f_parseRequestLang(request)
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
g_logger.info("StreamView.openDel() params:%s" % str(params))
handle = params.get("handle") # one删除一个视频流all删除全部视频流
stream_code = params.get("code")
if handle == "one":
stream = StreamModel.objects.filter(code=stream_code).first()
if stream:
__ret, __msg = GlobalUtils.delStreamProxy(stream, lang=lang)
if stream.delete():
2026-09-04 18:16:14 +08:00
# 同步清理内存里的该通道,避免 Catalog 刷新后自动恢复
g_gb28181SipServer.remove_channel(
stream_code,
device_id=stream.camera_device_id or None,
)
2026-08-30 22:22:11 +08:00
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
else:
msg = LANG_VIEWS_T(request, "stream_delete_failed")
else:
msg = LANG_VIEWS_T(request, "stream_not_found")
elif handle == "all":
success_count = 0
error_count = 0
streams = StreamModel.objects.all()
for stream in streams:
__ret, __msg = GlobalUtils.delStreamProxy(stream, lang=lang)
if stream.delete():
2026-09-04 18:16:14 +08:00
g_gb28181SipServer.remove_channel(stream.code, device_id=stream.camera_device_id or None)
2026-08-30 22:22:11 +08:00
success_count += 1
else:
error_count += 1
ret = True
msg = LANG_VIEWS_T(request, "msg_batch_result") % (success_count, error_count)
else:
msg = LANG_VIEWS_T(request, "msg_invalid_parameter")
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg
}
g_logger.info("StreamView.openDel() res:%s" % str(res))
return f_responseJson(res)
def api_openHandleAllStreamProxy(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
lang = f_parseRequestLang(request)
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
g_logger.info("StreamView.openHandleAllStreamProxy() params:%s" % str(params))
handle = params.get("handle") # add,del (add 开启流del 关闭流)
if handle == "add": # 全部开启转发
ret,msg = GlobalUtils.addAllStreamProxy(lang=lang)
elif handle == "del": # 全部关闭转发
GlobalUtils.delAllStreamProxy(lang=lang)
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
else:
msg = LANG_VIEWS_T(request, "msg_invalid_parameter")
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg
}
g_logger.info("StreamView.openHandleAllStreamProxy() res:%s" % str(res))
return f_responseJson(res)
def api_openAddStreamProxy(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
lang = f_parseRequestLang(request)
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
g_logger.info("StreamView.openAddStreamProxy() params:%s" % str(params))
stream_code = params.get("code", "").strip()
stream = StreamModel.objects.filter(code=stream_code).first()
if stream:
if stream.forward_state == 1:
ret = True
msg = LANG_VIEWS_T(request, "stream_forward_already_enabled")
else:
ret, msg = GlobalUtils.addStreamProxy(stream, lang=lang)
if ret:
stream.forward_state = 1
stream.save()
from app.views.NvrView import _sync_stream_recording
_sync_stream_recording(stream)
ret = True
msg = LANG_VIEWS_T(request, "stream_forward_enabled_success")
else:
msg = LANG_VIEWS_T(request, "stream_not_found")
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg
}
g_logger.info("StreamView.openAddStreamProxy() res:%s" % str(res))
return f_responseJson(res)
def api_openDelStreamProxy(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
lang = f_parseRequestLang(request)
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
g_logger.info("StreamView.openDelStreamProxy() params:%s" % str(params))
stream_code = params.get("code", "").strip()
stream = StreamModel.objects.filter(code=stream_code).first()
if stream:
__ret, __msg = GlobalUtils.delStreamProxy(stream, lang=lang)
stream.forward_state = 0
stream.save()
from app.views.NvrView import _sync_stream_recording
_sync_stream_recording(stream)
ret = __ret
msg = __msg
else:
msg = LANG_VIEWS_T(request, "stream_not_found")
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg
}
g_logger.info("StreamView.openDelStreamProxy() res:%s" % str(res))
return f_responseJson(res)
def api_openPtz(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
lang = f_parseRequestLang(request)
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
g_logger.info("StreamView.openPtz() params:%s" % str(params))
code = params.get("code", "").strip()
2026-09-04 18:16:14 +08:00
try:
ptzType = int(params.get("ptzType", 0))
except (TypeError, ValueError):
ptzType = -1
# 优先读取前端实际参数名 ptzSpeed兼容旧字段 val
try:
val = int(params.get("ptzSpeed", params.get("val", 0)) or 0)
except (TypeError, ValueError):
val = 0
2026-08-30 22:22:11 +08:00
2026-09-04 18:16:14 +08:00
if ptzType < 0 or ptzType > 14:
msg = LANG_VIEWS_T(request, "ptz_type_range_error")
else:
# 除停止外均需非零速度才会动作越界值钳制到0-255
if ptzType != 0 and val < 1:
val = 32
val = min(max(val, 0), 255)
2026-08-30 22:22:11 +08:00
2026-09-04 18:16:14 +08:00
stream = StreamModel.objects.filter(code=code).first()
if stream:
if stream.pull_stream_type == 21:
ret, msg = g_gb28181SipServer.request_ptz(
client_id=stream.camera_device_id,
channel_id=stream.code,
ptz_type=ptzType,
val=val,
# 分进程模式web进程无实时SIP注册表从库中取设备地址外发
device_ip=stream.pull_stream_ip or None,
device_port=stream.pull_stream_port or 0)
else:
msg = LANG_VIEWS_T(request, "stream_pull_type_not_supported")
2026-08-30 22:22:11 +08:00
else:
2026-09-04 18:16:14 +08:00
msg = LANG_VIEWS_T(request, "stream_not_found")
2026-08-30 22:22:11 +08:00
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg
}
g_logger.info("StreamView.openPtz() res:%s" % str(res))
return f_responseJson(res)
def api_openGetAllStreamData(request):
"""在线预览页面获取所有在线流数据(原 OpenView 中的同名接口迁移至此)"""
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
data = []
request_ip = f_parseRequestIp(request)
if request.method == 'GET':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parseGetParams(request)
handle = params.get("handle", "").strip()
g_logger.info("StreamView.openGetAllStreamData() request_ip:%s,params:%s" % (request_ip, str(params)))
# 获取所有 ZLM 在线流
medias = g_zlm.getMediaList(request_ip=request_ip)
# 构建 app_name -> 摄像头信息 映射(从数据库)
db_streams = g_database.select("select id,app,name,code,nickname from av_stream")
db_dict = {}
for s in db_streams:
key = "{app}_{name}".format(app=s["app"], name=s["name"])
db_dict[key] = s
for m in medias:
app = m.get("app", "")
name = m.get("name", "")
app_name = m.get("app_name", "{0}_{1}".format(app, name))
nickname = name
code = app_name
db_info = db_dict.get(app_name)
if db_info:
code = db_info.get("code") or app_name
nickname = db_info.get("nickname") or name
m["id"] = db_info.get("id")
m["code"] = code
m["nickname"] = nickname
data.append(m)
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg,
"data": data
}
g_logger.info("StreamView.openGetAllStreamData() res.data length:%d" % len(data))
return f_responseJson(res)
def api_openGetStatisticsStream(request):
"""首页流媒体统计(原 OpenView 接口迁移至此)"""
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
info = {"groupCount": 0, "count": 0, "forwardCount": 0}
request_ip = f_parseRequestIp(request)
if request.method == 'GET':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
g_logger.info("StreamView.openGetStatisticsStream() request_ip:%s" % request_ip)
try:
# 接入数量 = 数据库中摄像头总数
db_rows = g_database.select("select app,name from av_stream")
info["count"] = len(db_rows)
# 在线数量 = ZLM 中实际存在的流数量
medias = g_zlm.getMediaList(request_ip=request_ip)
info["forwardCount"] = len(medias)
# 分组数量 = 不同 app 的数量
apps = set()
for r in db_rows:
if r.get("app"):
apps.add(r["app"])
info["groupCount"] = len(apps)
except Exception as e:
g_logger.error("StreamView.openGetStatisticsStream() e:%s" % str(e))
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg,
"info": info
}
g_logger.info("StreamView.openGetStatisticsStream() res:%s" % str(res))
return f_responseJson(res)
def api_openOnvifDiscover(request):
"""WS-Discovery 发现局域网 ONVIF 设备"""
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
data = []
if request.method == 'GET':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
try:
from app.services.onvif_discovery import discover_onvif, get_rtsp_url_from_onvif
params = f_parseGetParams(request)
timeout = float(params.get("timeout", 3))
username = (params.get("username") or "").strip()
password = (params.get("password") or "").strip()
resolve = str(params.get("resolve_rtsp", "0")).strip() in ("1", "true", "yes")
devices = discover_onvif(timeout=timeout)
for d in devices:
item = dict(d)
if resolve and d.get("xaddr"):
rtsp = get_rtsp_url_from_onvif(d["xaddr"], username, password)
if rtsp:
item["rtsp_url"] = rtsp
data.append(item)
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
except Exception as e:
msg = str(e)
g_logger.exception("openOnvifDiscover: %s", e)
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
return f_responseJson({"code": 1000 if ret else 0, "msg": msg, "data": data})