惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

大猫的无限游戏
大猫的无限游戏
博客园 - 【当耐特】
Cloudbric
Cloudbric
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Attack and Defense Labs
Attack and Defense Labs
爱范儿
爱范儿
The Cloudflare Blog
腾讯CDC
Security Archives - TechRepublic
Security Archives - TechRepublic
TaoSecurity Blog
TaoSecurity Blog
云风的 BLOG
云风的 BLOG
Recent Announcements
Recent Announcements
C
Check Point Blog
Schneier on Security
Schneier on Security
S
Schneier on Security
J
Java Code Geeks
B
Blog RSS Feed
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
Stack Overflow Blog
Stack Overflow Blog
博客园_首页
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
A
About on SuperTechFans
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Google DeepMind News
Google DeepMind News
阮一峰的网络日志
阮一峰的网络日志
罗磊的独立博客
A
Arctic Wolf
S
Secure Thoughts
P
Palo Alto Networks Blog
The Last Watchdog
The Last Watchdog
SecWiki News
SecWiki News
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
博客园 - 三生石上(FineUI控件)
D
Darknet – Hacking Tools, Hacker News & Cyber Security
量子位
U
Unit 42
I
InfoQ
D
DataBreaches.Net
P
Privacy International News Feed
T
Troy Hunt's Blog
博客园 - 叶小钗
T
Threatpost
博客园 - Franky
K
Kaspersky official blog
Hugging Face - Blog
Hugging Face - Blog
IT之家
IT之家
www.infosecurity-magazine.com
www.infosecurity-magazine.com
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
C
Cisco Blogs

Dr34m's Blog

I Expect You To Die(我希望你死) 1,2,3 完整中文攻略 Linux换国内源 && docker安装 && 换加速镜像 Vue3 + Element-Plus 极简速查 随笔 pyinstaller打包的程序执行报错Failed to extract xxxx decompression resulted in return code -1 taoSync排除项简易教程 apscheduler的cron配置项 如何在绿联NAS中使用TaoSync同步我的文件到各个网盘 GB28181抓包记录 JS计算字节大小,把字节转换为KB/MB/GB/TB等 编写bat脚本实现对vue项目构建并压缩 内网穿透工具frp快速使用 CentOS 7.9 安装基础开发环境jdk redis nacos nginx mysql Pyinstaller 逆向 VUE实现复制与粘贴_获取剪切板内容 Electron + xterm.js + node-pty + vue 实现本地终端 child_process exec 中文乱码 Windows&Linux 解决方法 yum 更新 gcc CentOS安装并使用conda 将fluid主题博客的静态资源由第三方改为本地存储 一个上手即用的通用公众号/小程序/h5/app框架 python批量转化doc到docx SpringBoot单元测试注入空指针 TensorFlow 预测出现NaN的一种可能以及解决方法 随笔 python归一化数据 windows下python安装sasl遇到的问题及解决 将对象List中的某个字段放到新的List中[转载] Python http.server 本地服务支持跨域 TensorFlow入门 Flink学习笔记 windows本地配置spark开发环境 dataX使用 Apache Hive 学习笔记 hadoop学习笔记 element-ui 自带事件添加自定义参数 windows 11安装安卓应用程序 centos7安装zookeeper centos7安装jdk8 用idea开发spring项目过程中热重启 docker学习笔记 CentOS7安装k8s集群 通过Github Actions部署静态网站到腾讯云COS,并自动刷新CDN 用Typora编写Hexo博客时图片的处理 Hexo + Github Actions 提交代码自动部署 云服务器 腾讯云COS github-pages 常用的Linux进程基本命令 python实现微信jsapi签名 VsCode开发Python常用配置 mysql获取当期日期是该年第几周 LocalDate获取当前周周一日期 删除node_modules重新安装 基于firewalld端口转发 pip 安装 tensorflow MemoryError Java过滤html标签 mysql清空表,并让自增从0开始 JavaScript获取当前周或下n周的周n的日期 java复制不同实体类中相同的字段 前端下载二进制文件 mysql获取最近一段时间数据 python - pip换源 svn提交后jenkins自动部署 常见nginx反向代理配置 h5实现一种自动滚动的告警列表 nginx代理网站子目录到本地目录 centos下hexo + svn + jenkins实现博客自动部署 centos中jenkins配置环境变量 centos安装jenkins centos安装svn服务器 Ubuntu 20.04 换国内源 Vs Code编写md文件实现实时预览 Python自动处理依赖 海滨校园助手api使用文档 hexo-yilia主题相册 Java实现文件重命名,Java文件追加写入,java读取图片尺寸 Hexo安装配置并托管至github 新起点,新征程 在ubuntu安装jdk并配置环境变量 一个不到300行的C语言消灭敌机游戏 搭建L(Linux)+A(Apache)+M(MySQL)+P(PHP)网站环境,并安装Discuz 搭建L(Linux)+N(Nginx)+M(MySQL)+P(PHP)网站环境,并安装Wordpress NSA工具包验证之RDP漏洞利用 NSA工具包验证之IIS6.0漏洞利用 NSA工具包验证之SMB漏洞利用 SEO Ultimate 7.6.5.9汉化版(中文版)免费下载 在腾讯云服务器搭建FBCTF平台 FBCTF汉化简体中文免费下载,FBCTF更新缓存代码 wordpress发送邮件设置以及常见问题解决 短网址 一个小白的自学建站史(菜鸟建站入门) 危山 水调歌头 水调歌头
在Python中使用onvif管理摄像头,包括设备发现,获取RTSP地址,获取设备信息,截图,云台控制与缩放,设置时间
Dr3@m · 2023-12-14 · via Dr34m's Blog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
import base64
import datetime
from itertools import groupby

import pytz
import requests
from onvif import ONVIFCamera
from requests.auth import HTTPDigestAuth
from wsdiscovery.discovery import ThreadedWSDiscovery as WSDiscovery
from zeep.helpers import serialize_object

def checkPwdAndGetCam(ip, port, usr, pwd):
try:
cam = ONVIFCamera(ip, port, usr, pwd)
media = cam.create_media_service()
profiles = media.GetProfiles()
except Exception as e:
if 'timed out' in str(e):
raise Exception("连接超时,请检查地址以及端口是否正确")
elif 'HTTPConnectionPool' in str(e):
raise Exception(
"连接失败,请检查地址以及端口是否正确。"
"<br/><br/><front style='color: #aaa;'>异常信息:%s</front>" % str(e))
else:
raise Exception(
"请检查账号密码是否正确。"
"<br/><br/><front style='color: #aaa;'>异常信息:%s</front>" % str(e))
return {
'cam': cam,
'media': media,
'profiles': profiles
}

class OnvifClient:
def __init__(self, ip, port: int, usr, pwd, token=None, sourceToken=None, nodeToken=False, needSnapImg=True, needPtz=True):
"""
初始化参数
:param ip:
:param port:
:param usr:
:param pwd:
:param token: 每个码流都会有一个独立的token,通常一个画面会有2个或更多码流,例如主码流&辅码流
:param sourceToken: 每个画面会有一个独立的sourceToken,通常一个摄像头只有一个画面,有些红外、双摄之类的摄像头会有多个画面
:param nodeToken: 用于获取PTZ控制信息的token,因为nodeToken有可能为None,所以用False表示没传
:param needSnapImg: 是否需要截图,选否会加快速度
:param needPtz: 是否需要PTZ控制,选否会加快速度,一般查摄像头列表时可以传False
"""
self.usr = usr
self.pwd = pwd
result = checkPwdAndGetCam(ip, port, usr, pwd)
self.profiles = result['profiles']
self.cam = result['cam']
self.media = result['media']
self.needSnapImg = needSnapImg
# 如果没传token,默认使用第一个token
self.token = token if token is not None else self.profiles[0].token
# 如果没传sourceToken,默认使用第一个sourceToken
self.sourceToken = sourceToken if sourceToken is not None \
else self.profiles[0].VideoSourceConfiguration.SourceToken
# 如果没传nodeToken,默认使用第一个nodeToken
PTZConfiguration = self.profiles[0].PTZConfiguration
self.nodeToken = nodeToken if nodeToken is not False \
else (PTZConfiguration.NodeToken if PTZConfiguration is not None else None)
if needPtz:
self.ptz = self.cam.create_ptz_service() if bool(self.cam.devicemgmt.GetCapabilities().PTZ) else None
self.imaging = self.cam.create_imaging_service()
self.ptzNode = self.ptz.GetNode({
'NodeToken': self.nodeToken
}) if self.ptz is not None and self.nodeToken is not None else None
self.MoveOption = self.imaging.GetMoveOptions({'VideoSourceToken': self.sourceToken})
else:
self.cam = self.ptz = self.imaging = self.ptzNode = self.MoveOption = None
if self.ptzNode is not None:
SupportedPTZSpaces = self.ptzNode.SupportedPTZSpaces
# PTZ云台移动速度峰值
if len(SupportedPTZSpaces.PanTiltSpeedSpace) > 0:
PanTiltSpeedSpace = SupportedPTZSpaces.PanTiltSpeedSpace
elif len(SupportedPTZSpaces.ContinuousPanTiltVelocitySpace) > 0:
PanTiltSpeedSpace = SupportedPTZSpaces.ContinuousPanTiltVelocitySpace
else:
PanTiltSpeedSpace = None
self.PanTiltSpeedMax = PanTiltSpeedSpace[0].XRange.Max if PanTiltSpeedSpace is not None else None
# PTZ缩放速度峰值
if len(SupportedPTZSpaces.ZoomSpeedSpace) > 0:
ZoomSpeedSpace = SupportedPTZSpaces.ZoomSpeedSpace
elif len(SupportedPTZSpaces.ContinuousZoomVelocitySpace) > 0:
ZoomSpeedSpace = SupportedPTZSpaces.ContinuousZoomVelocitySpace
else:
ZoomSpeedSpace = None
self.ZoomSpeedMax = ZoomSpeedSpace[0].XRange.Max if ZoomSpeedSpace is not None else None
else:
self.PanTiltSpeedMax = self.ZoomSpeedMax = None
# 聚焦移动速度峰值
self.MoveSpeedMax = self.MoveOption.Continuous.Speed.Max \
if self.MoveOption is not None else None

def get_rtsp(self):
"""
获取RTSP地址等
参考文档:https://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl#op.GetStreamUri
"""
result = []
StreamSetup = {'Stream': 'RTP-Unicast', 'Transport': {'Protocol': 'RTSP'}}
for profile in self.profiles:
obj = self.media.create_type('GetStreamUri')
obj.StreamSetup = StreamSetup
obj.ProfileToken = profile.token
res_uri = self.media.GetStreamUri(obj)['Uri']
if 'rtsp://' in res_uri and '@' not in res_uri:
res_uri = res_uri.replace('rtsp://', 'rtsp://%s:%s@' % (self.usr, self.pwd))
result.append({
'source': profile.VideoSourceConfiguration.SourceToken,
'node': profile.PTZConfiguration.NodeToken if profile.PTZConfiguration is not None else None,
'uri': res_uri,
'token': profile.token,
'videoEncoding': profile.VideoEncoderConfiguration.Encoding,
'Resolution': serialize_object(profile.VideoEncoderConfiguration.Resolution),
'img': self.snip_image(profile.token) if self.needSnapImg else None
})
sortedResult = sorted(result, key=lambda d: d['source'])
groupData = groupby(sortedResult, key=lambda x: x['source'])
return [{'source': key, 'data': [item for item in group]} for key, group in groupData]

def snip_image(self, token=None):
"""
截图,如果在浏览器上访问,可在img的src填入[data:image/jpeg;base64,%s],%s处填写return值
参考文档:https://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl#op.GetSnapshotUri
:param token:
:return: base64转码之后的图片
"""
token = token if token is not None else self.token
res = self.media.GetSnapshotUri({'ProfileToken': token})
auth = HTTPDigestAuth(self.usr, self.pwd)
rsp = requests.get(res.Uri, auth=auth)
return base64.b64encode(rsp.content).decode('utf-8')

def get_deviceInfo(self):
"""
获取设备信息
参考文档:https://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl#op.GetDeviceInformation
:return: 设备信息,包括名称-Model、厂家-Manufacturer、固件版本-FirmwareVersion、序列号-SerialNumber、硬件ID-HardwareId
"""
return serialize_object(self.cam.devicemgmt.GetDeviceInformation())

def ptz_move(self, Velocity=None, token=None):
"""
PTZ控制移动
参考文档:https://www.onvif.org/onvif/ver20/ptz/wsdl/ptz.wsdl#op.ContinuousMove
:param token: 移动设备的token
:param Velocity: 可选参数,不传表示停止移动
"""
token = token if token is not None else self.token
if self.ptz is None:
if Velocity is not None:
# 只在移动时展示不支持,以免频繁打扰
raise Exception("该设备不支持PTZ控制")
else:
if Velocity is None:
self.ptz.Stop({'ProfileToken': token})
else:
request = self.ptz.create_type('ContinuousMove')
request.ProfileToken = token
request.Velocity = Velocity
self.ptz.ContinuousMove(request)

def focus_move(self, speed=None, token=None):
"""
聚焦
参考文档:https://www.onvif.org/onvif/ver20/imaging/wsdl/imaging.wsdl#op.Move
:param token: VideoSourceToken
:param speed: 正数:聚焦+,拉近;负数:聚焦-,拉远;None:停止聚焦
"""
token = token if token is not None else self.sourceToken
if speed is not None:
request = self.imaging.create_type('Move')
request.VideoSourceToken = token
request.Focus = {'Continuous': {'Speed': speed}}
try:
self.imaging.Move(request)
except Exception as e:
raise Exception(
"该设备不支持该功能!"
"<br/><br/><front style='color: #aaa;'>异常信息:%s</front>" % str(e))
else:
self.imaging.Stop({'VideoSourceToken': token})

def set_cam_time(self, timeStamp=None):
"""
设置时间
参考文档:https://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl#op.SetSystemDateAndTime
:param timeStamp: 秒级时间戳,不传则使用当前时间
"""
if timeStamp is None:
timeNow = datetime.datetime.now()
else:
timeNow = datetime.datetime.fromtimestamp(int(timeStamp))
utc_now = timeNow.astimezone(pytz.utc)
self.cam.devicemgmt.SetSystemDateAndTime({
'DateTimeType': 'Manual',
'DaylightSavings': False,
'TimeZone': {
'TZ': 'CST-8'
},
'UTCDateTime': {
'Time': {
'Hour': utc_now.hour,
'Minute': utc_now.minute,
'Second': utc_now.second
},
'Date': {
'Year': utc_now.year,
'Month': utc_now.month,
'Day': utc_now.day
}
}
})

def ptzChangeByClient(client, codeStr, status, speed=50.0):
"""
PTZ控制
:param client: onvif客户端
:param speed: 相对速度,1-100
:param status: 状态,1-开始,0-停止
:param codeStr: 标志字符串
"""
ptzList = ['Up', 'Right', 'Down', 'Left', 'LeftUp', 'RightUp', 'LeftDown', 'RightDown', 'ZoomWide', 'ZoomTele']
focusList = ['FocusFar', 'FocusNear']
if codeStr in ptzList:
if client.ptz is None:
if status == 1:
raise Exception("当前设备不支持PTZ控制")
else:
return
if status == 0:
if 'Zoom' not in codeStr:
if client.PanTiltSpeedMax is None:
return
else:
if client.ZoomSpeedMax is None:
return
client.ptz_move()
else:
PanTiltSpeed = 0
ZoomSpeed = 0
if 'Zoom' not in codeStr:
if client.PanTiltSpeedMax is None:
raise Exception("当前设备不支持云台控制")
PanTiltSpeed = client.PanTiltSpeedMax * float(speed) / 100.0
speedTilt = PanTiltSpeed if 'Up' in codeStr else (
PanTiltSpeed * -1.0 if 'Down' in codeStr else 0)
speedPan = PanTiltSpeed if 'Right' in codeStr else (
PanTiltSpeed * -1.0 if 'Left' in codeStr else 0)
params = {
'PanTilt': {
'x': speedPan,
'y': speedTilt
}
}
else:
if client.ZoomSpeedMax is None:
raise Exception("当前设备不支持缩放控制")
ZoomSpeed = client.ZoomSpeedMax * float(speed) / 100.0
speedZoom = 0 if 'Zoom' not in codeStr else (
ZoomSpeed * -1.0 if 'Wide' in codeStr else ZoomSpeed)
params = {
'Zoom': speedZoom
}
client.ptz_move(params)
elif codeStr in focusList:
if client.MoveSpeedMax is None:
if status == 1:
raise Exception("当前设备不支持聚焦控制")
else:
return
if status == 0:
client.focus_move()
else:
MoveSpeed = client.MoveSpeedMax * float(speed) / 100.0
client.focus_move(MoveSpeed if 'FocusNear' == codeStr else MoveSpeed * -1.0)
else:
if status == 1:
raise Exception("该方式暂不支持")