OpenIPC/python-dvr
GitHub: OpenIPC/python-dvr
基于 Python 的 IP 摄像头配置库,兼容 NETSurveillance 与 Xiongmai 协议,帮助用户管理海康等品牌的 IPC 设备。
Stars: 59 | Forks: 12
# python-dvr
用于配置使用 NETsurveillance ActiveX 插件的一系列 IP 摄像头的 Python 库
XMeye SDK

## DeviceManager.py
DeviceManager.py 是一个独立的 Tkinter 和控制台界面程序,类似于原始的 DeviceManager.exe
在没有 Tkinter 的情况下也能在两种系统上运行,如果系统不支持 Tkinter 则会启动控制台界面
## DVR-IP、NetSurveillance 或 "Sofia" 协议
NETSurveillance ActiveX 插件使用一种基于 TCP 的协议,被“杭州迈麦信息技术有限公司”简称为“数字视频记录接口协议”
除制造商提供的工具外,几乎没有其他软件支持或文档,这使得许多配置选项无法访问
- [命令与响应代码](https://gist.github.com/ekwoodrich/a6d7b8db8f82adf107c3c366e61fd36f)
- [Xiongmai DVR API v1.0,俄语](https://github.com/OpenIPC/python-dvr/blob/master/doc/Соглашение%20о%20интерфейсе%20цифрового%20видеорегистратора%20XiongmaiV1.0.doc)
- [Xiongmai DVR API,2013-01-11,中文](doc/雄迈数字视频录像机接口协议_V1.0.0.pdf)
- [DVR API 简述,中文](doc/配置交换格式V2.0.pdf)
- [NETIP 视频/音频负载协议,中文](doc/码流帧格式文档.pdf)
### 类似项目
- [sofiactl](https://github.com/667bdrm/sofiactl)
- [DVRIP 库与工具](https://github.com/alexshpilkin/dvrip)
- [numenworld-ipcam](https://github.com/johndoe31415/numenworld-ipcam)
### 服务器实现
* [OpenIPC](https://openipc.org/firmware/)
## 基本用法
```
from dvrip import DVRIPCam
from time import sleep
host_ip = '192.168.1.10'
cam = DVRIPCam(host_ip, user='admin', password='')
if cam.login():
print("Success! Connected to " + host_ip)
else:
print("Failure. Could not connect.")
print("Camera time:", cam.get_time())
# 重启相机
cam.reboot()
sleep(60) # wait while camera starts
# 再次登录
cam.login()
# 同步相机时间与PC时间
cam.set_time()
# 断开连接
cam.close()
```
## 异步用法
```
from asyncio_dvrip import DVRIPCam
import asyncio
import traceback
def stop(loop):
tasks = asyncio.gather(*asyncio.Task.all_tasks(loop=loop), loop=loop, return_exceptions=True)
tasks.add_done_callback(lambda t: loop.stop())
tasks.cancel()
loop = asyncio.get_event_loop()
def onAlert(event, sequence_number):
print(event, sequence_number)
async def some_test_worker():
while True:
print("do some important work...")
await asyncio.sleep(3)
async def main(loop):
host_ip = '192.168.1.10'
cam = DVRIPCam(host_ip, user='admin', password='')
try:
if not await cam.login():
raise Exception("Failure. Could not connect.")
# -------------------------------
# take snapshot
image = await cam.snapshot()
# save it
with open("snap.jpeg", "wb") as fp:
fp.write(image)
# -------------------------------
# write video
with open("datastream.h265", "wb") as f:
await cam.start_monitor(lambda frame, meta, user: f.write(frame))
# -------------------------------
# or get alarms
cam.setAlarm(onAlert)
# will create new task
await cam.alarmStart(loop)
# so just wait or something else
while True:
await asyncio.sleep(1)
# -------------------------------
except:
pass
finally:
cam.close()
try:
loop.create_task(main(loop))
loop.create_task(some_test_worker())
loop.run_forever()
except Exception as err:
msg = ''.join(traceback.format_tb(err.__traceback__) + [str(err)])
print(msg)
finally:
cam.close()
stop(loop)
```
## 摄像头设置
```
params = cam.get_general_info()
```
返回通用摄像头信息(时区、格式、自动重启策略、安全选项):
```
{
"AppBindFlag": {
"BeBinded": false
},
"AutoMaintain": {
"AutoDeleteFilesDays": 0,
"AutoRebootDay": "Tuesday",
"AutoRebootHour": 3
},
"DSTState": {
"InNormalState": true
},
"General": {
"AutoLogout": 0,
"FontSize": 24,
"IranCalendarEnable": 0,
"LocalNo": 0,
"MachineName": "LocalHost",
"OverWrite": "OverWrite",
"ScreenAutoShutdown": 10,
"ScreenSaveTime": 0,
"VideoOutPut": "Auto"
},
"Location": {
"DSTEnd": {
"Day": 1,
"Hour": 1,
"Minute": 1,
"Month": 10,
"Week": 0,
"Year": 2021
},
"DSTRule": "Off",
"DSTStart": {
"Day": 1,
"Hour": 1,
"Minute": 1,
"Month": 5,
"Week": 0,
"Year": 2021
},
"DateFormat": "YYMMDD",
"DateSeparator": "-",
"IranCalendar": 0,
"Language": "Russian",
"TimeFormat": "24",
"VideoFormat": "PAL",
"Week": null,
"WorkDay": 62
},
"OneKeyMaskVideo": null,
"PwdSafety": {
"PwdReset": [
{
"QuestionAnswer": "",
"QuestionIndex": 0
},
{
"QuestionAnswer": "",
"QuestionIndex": 0
},
{
"QuestionAnswer": "",
"QuestionIndex": 0
},
{
"QuestionAnswer": "",
"QuestionIndex": 0
}
],
"SecurityEmail": "",
"TipPageHide": false
},
"ResumePtzState": null,
"TimingSleep": null
}
```
```
params = cam.get_system_info()
```
返回硬件特定设置、摄像头序列号、当前软件版本和固件类型:
```
{
"AlarmInChannel": 2,
"AlarmOutChannel": 1,
"AudioInChannel": 1,
"BuildTime": "2020-01-08 11:05:18",
"CombineSwitch": 0,
"DeviceModel": "HI3516EV300_85H50AI",
"DeviceRunTime": "0x0001f532",
"DigChannel": 0,
"EncryptVersion": "Unknown",
"ExtraChannel": 0,
"HardWare": "HI3516EV300_85H50AI",
"HardWareVersion": "Unknown",
"SerialNo": "a166379674a3b447",
"SoftWareVersion": "V5.00.R02.000529B2.10010.040600.0020000",
"TalkInChannel": 1,
"TalkOutChannel": 1,
"UpdataTime": "",
"UpdataType": "0x00000000",
"VideoInChannel": 1,
"VideoOutChannel": 1
}
```
```
params = cam.get_system_capabilities()
```
返回摄像头软件功能(告警与检测、通信协议和硬件特定功能):
```
{
"AlarmFunction": {
"AlarmConfig": true,
"BlindDetect": true,
"HumanDection": true,
"HumanPedDetection": true,
"LossDetect": true,
"MotionDetect": true,
"NetAbort": true,
"NetAlarm": true,
"NetIpConflict": true,
"NewVideoAnalyze": false,
"PEAInHumanPed": true,
"StorageFailure": true,
"StorageLowSpace": true,
"StorageNotExist": true,
"VideoAnalyze": false
},
"CommFunction": {
"CommRS232": true,
"CommRS485": true
},
"EncodeFunction": {
"DoubleStream": true,
"SmartH264": true,
"SmartH264V2": false,
"SnapStream": true
},
"NetServerFunction": {
"IPAdaptive": true,
"Net3G": false,
"Net4GSignalLevel": false,
"NetAlarmCenter": true,
"NetDAS": false,
"NetDDNS": false,
"NetDHCP": true,
"NetDNS": true,
"NetEmail": true,
"NetFTP": true,
"NetIPFilter": true,
"NetMutlicast": false,
"NetNTP": true,
"NetNat": true,
"NetPMS": true,
"NetPMSV2": true,
"NetPPPoE": false,
"NetRTSP": true,
"NetSPVMN": false,
"NetUPNP": true,
"NetWifi": false,
"OnvifPwdCheckout": true,
"RTMP": false,
"WifiModeSwitch": false,
"WifiRouteSignalLevel": true
},
"OtherFunction": {
"NOHDDRECORD": false,
"NoSupportSafetyQuestion": false,
"NotSupportAutoAndIntelligent": false,
"SupportAdminContactInfo": true,
"SupportAlarmRemoteCall": false,
"SupportAlarmVoiceTipInterval": true,
"SupportAlarmVoiceTips": true,
"SupportAlarmVoiceTipsType": true,
"SupportAppBindFlag": true,
"SupportBT": true,
"SupportBallTelescopic": false,
"SupportBoxCameraBulb": false,
"SupportCamareStyle": true,
"SupportCameraWhiteLight": false,
"SupportCfgCloudupgrade": true,
"SupportChangeLanguageNoReboot": true,
"SupportCloseVoiceTip": false,
"SupportCloudUpgrade": true,
"SupportCommDataUpload": true,
"SupportCorridorMode": false,
"SupportCustomizeLpRect": false,
"SupportDNChangeByImage": false,
"SupportDimenCode": true,
"SupportDoubleLightBoxCamera": false,
"SupportDoubleLightBulb": false,
"SupportElectronicPTZ": false,
"SupportFTPTest": true,
"SupportFaceDetectV2": false,
"SupportFaceRecognition": false,
"SupportMailTest": true,
"SupportMusicBulb433Pair": false,
"SupportMusicLightBulb": false,
"SupportNetWorkMode": false,
"SupportOSDInfo": false,
"SupportOneKeyMaskVideo": false,
"SupportPCSetDoubleLight": true,
"SupportPTZDirectionControl": false,
"SupportPTZTour": false,
"SupportPWDSafety": true,
"SupportParkingGuide": false,
"SupportPtz360Spin": false,
"SupportRPSVideo": false,
"SupportSetBrightness": false,
"SupportSetDetectTrackWatchPoint": false,
"SupportSetHardwareAbility": false,
"SupportSetPTZPresetAttribute": false,
"SupportSetVolume": true,
"SupportShowH265X": true,
"SupportSnapCfg": false,
"SupportSnapV2Stream": true,
"SupportSnapshotConfigV2": false,
"SupportSoftPhotosensitive": true,
"SupportStatusLed": false,
"SupportTextPassword": true,
"SupportTimeZone": true,
"SupportTimingSleep": false,
"SupportWebRTCModule": false,
"SupportWriteLog": true,
"SuppportChangeOnvifPort": true
},
"PreviewFunction": {
"Talk": true,
"Tour": false
},
"TipShow": {
"NoBeepTipShow": true
}
}
```
## 摄像头视频设置/模式
```
params = cam.get_info("Camera")
# 返回如下数据:
# {'ClearFog': [{'enable': 0, 'level': 50}], 'DistortionCorrect': {'Lenstype': 0, 'Version': 0},
# 'FishLensParam': [{'CenterOffsetX': 300, 'CenterOffsetY': 300, 'ImageHeight': 720,
# 'ImageWidth': 1280, 'LensType': 0, 'PCMac': '000000000000', 'Radius': 300, 'Version': 1,
# 'ViewAngle': 0, 'ViewMode': 0, 'Zoom': 100}], 'FishViCut': [{'ImgHeight': 0, 'ImgWidth': 0,
# 'Xoffset': 0, 'Yoffset': 0}], 'Param': [{'AeSensitivity': 5, 'ApertureMode': '0x00000000',
# 'BLCMode': '0x00000000', 'DayNightColor': '0x00000000', 'Day_nfLevel': 3, 'DncThr': 30,
# 'ElecLevel': 50, 'EsShutter': '0x00000002', 'ExposureParam': {'LeastTime': '0x00000100',
# 'Level': 0, 'MostTime': '0x00010000'}, 'GainParam': {'AutoGain': 1, 'Gain': 50},
# 'IRCUTMode': 0, 'IrcutSwap': 0, 'Night_nfLevel': 3, 'PictureFlip': '0x00000000',
# 'PictureMirror': '0x00000000', 'RejectFlicker': '0x00000000', 'WhiteBalance': '0x00000000'}],
# 'ParamEx': [{'AutomaticAdjustment': 3, 'BroadTrends': {'AutoGain': 0, 'Gain': 50},
# 'CorridorMode': 0, 'ExposureTime': '0x100', 'LightRestrainLevel': 16, 'LowLuxMode': 0,
# 'PreventOverExpo': 0, 'SoftPhotosensitivecontrol': 0, 'Style': 'type1'}], 'WhiteLight':
# {'MoveTrigLight': {'Duration': 60, 'Level': 3}, 'WorkMode': 'Auto', 'WorkPeriod':
# {'EHour': 6, 'EMinute': 0, 'Enable': 1, 'SHour': 18, 'SMinute': 0}}
# 获取当前编码设置
enc_info = cam.get_info("Simplify.Encode")
# 返回如下数据:
# [{'ExtraFormat': {'AudioEnable': False, 'Video': {'BitRate': 552, 'BitRateControl': 'VBR',
# 'Compression': 'H.265', 'FPS': 20, 'GOP': 2, 'Quality': 3, 'Resolution': 'D1'},
# 'VideoEnable': True}, 'MainFormat': {'AudioEnable': False, 'Video': {'BitRate': 2662,
# 'BitRateControl': 'VBR', 'Compression': 'H.265', 'FPS': 25, 'GOP': 2, 'Quality': 4,
# 'Resolution': '1080P'}, 'VideoEnable': True}}]
# 更改比特率
NewBitrate = 7000
enc_info[0]['MainFormat']['Video']['BitRate'] = NewBitrate
cam.set_info("Simplify.Encode", enc_info)
# 获取视频通道颜色参数
colors = cam.get_info("AVEnc.VideoColor.[0]")
# 返回如下数据:
# [{'Enable': True, 'TimeSection': '0 00:00:00-24:00:00', 'VideoColorParam': {'Acutance': 3848,
# 'Brightness': 50, 'Contrast': 50, 'Gain': 0, 'Hue': 50, 'Saturation': 50, 'Whitebalance': 128}},
# {'Enable': False, 'TimeSection': '0 00:00:00-24:00:00', 'VideoColorParam': {'Acutance': 3848,
# 'Brightness': 50, 'Contrast': 50, 'Gain': 0, 'Hue': 50, 'Saturation': 50, 'Whitebalance': 128}}]
# 更改IR切割
cam.set_info("Camera.Param.[0]", { "IrcutSwap" : 0 })
# 更改WDR设置
WDR_mode = True
cam.set_info("Camera.ParamEx.[0]", { "BroadTrends" : { "AutoGain" : int(WDR_mode) } })
# 获取网络设置
net = cam.get_info("NetWork.NetCommon")
# 开启自适应IP模式
cam.set_info("NetWork.IPAdaptive", { "IPAdaptive": True })
# 设置相机主机名
cam.set_info("NetWork.NetCommon.HostName", "IVG-85HG50PYA-S")
# 设置DHCP模式(在此情况下开启)
dhcpst = cam.get_info("NetWork.NetDHCP")
dhcpst[0]['Enable'] = True
cam.set_info("NetWork.NetDHCP", dhcpst)
# 启用/禁用云支持
cloudEnabled = False
cam.set_info("NetWork.Nat", { "NatEnable" : cloudEnabled })
```
## 添加用户与修改密码
```
#User "test2" with pssword "123123"
cam.addUser("test2","123123")
#Bad password, change it
cam.changePasswd("321321",cam.sofia_hash("123123"),"test2")
#And delete user "test2"
if cam.delUser("test2"):
print("User deleted")
else:
print("Can not delete it")
#System users can not be deleted
if cam.delUser("admin"):
print("You do it! How?")
else:
print("It system reserved user")
```
## 深入调查更多设置
建议的方法将有助于理解摄像头 UI 与 API 设置之间的联系
欢迎提交 PR 更新文档
```
from deepdiff import DeepDiff
from pprint import pprint
latest = None
while True:
current = cam.get_info("Camera") # or "General", "Simplify.Encode", "NetWork"
if latest:
diff = DeepDiff(current, latest)
if diff == {}:
print("Nothing changed")
else:
pprint(diff['values_changed'], indent = 2)
latest = current
input("Change camera setting via UI and then press Enter,"
" or double Ctrl-C to exit\n")
```
## 获取 JPEG 快照
```
with open("snap.jpg", "wb") as f:
f.write(cam.snapshot())
```
## 获取视频/音频比特流
仅视频写入文件(使用简单 lambda):
```
with open("datastream.h265", "wb") as f:
cam.start_monitor(lambda frame, meta, user: f.write(frame))
```
带额外过滤的数据流写入(捕获前 100 帧):
```
class State:
def __init__(self):
self.counter = 0
def count(self):
return self.counter
def inc(self):
self.counter += 1
with open("datastream.h265", "wb") as f:
state = State()
def receiver(frame, meta, state):
if 'frame' in meta:
f.write(frame)
state.inc()
print(state.count())
if state.count() == 100:
cam.stop_monitor()
cam.start_monitor(receiver, state)
```
## 设置摄像头标题
```
# 简单更改图片标题的方法
cam.channel_title(["Backyard"])
# 使用主机计算机的Unicode字体来组成位图标题
from PIL import Image, ImageDraw, ImageFont
w_disp = 128
h_disp = 64
fontsize = 32
text = "Туалет"
imageRGB = Image.new('RGB', (w_disp, h_disp))
draw = ImageDraw.Draw(imageRGB)
font = ImageFont.truetype("/Library/Fonts/Arial Unicode.ttf", fontsize)
w, h = draw.textsize(text, font=font)
draw.text(((w_disp - w)/2, (h_disp - h)/2), text, font=font)
image1bit = imageRGB.convert("1")
data = image1bit.tobytes()
cam.channel_bitmap(w_disp, h_disp, data)
# 在图片上使用自己的标志
img = Image.open('vixand.png')
width, height = img.size
data = img.convert("1").tobytes()
cam.channel_bitmap(width, height, data)
```

```
# 显示当前温度、速度、GPS坐标等
# 使用相同的方法绘制文本位图并传输到相机
# 但请考虑将内部位图存储放置到RAM中:
mount -t tmpfs -o size=100k tmpfs /mnt/mtd/tmpfs
ln -sf /mnt/mtd/tmpfs/0.dot /mnt/mtd/Config/Dot/0.dot
```
## OSD 特殊文本显示
```
cam.set_info("fVideo.OSDInfo", {"Align": 2, "OSDInfo": [
{
"Info": [
"АБВГДЕЁЖЗИКЛМНОПРСТУФХЦЧШЩЭЮЯ",
"абвгдеёжзиклмеопрстуфхцчшщэюя",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"abcdefghijklmnopqrstuvwxyz",
"«»©°\"'()[]{}$%^&*_+=0123456789"
],
"OSDInfoWidget": {
"BackColor": "0x00000000",
"EncodeBlend": True,
"FrontColor": "0xD000FF00",
"PreviewBlend": True,
"RelativePos": [20, 50, 0, 0]
}
}
], "strEnc": "UTF-8"})
```

## 升级摄像头固件
```
# 可选:获取升级参数信息
print(cam.get_upgrade_info())
# 执行升级
cam.upgrade("General_HZXM_IPC_HI3516CV300_50H20L_AE_S38_V4.03.R12.Nat.OnvifS.HIK.20181126_ALL.bin")
```
## 监控脚本
该脚本将持续尝试连接到 `CAMERA_IP`,在 `FILE_PATH` 中创建名为 `CAMERA_NAME` 的目录,并按 `%Y/%m/%d` 结构的文件夹分别写入独立的视频和音频流文件,按 10 分钟切分。它还会记录操作日志。
```
./monitor.py
```
## OPFeederFunctions
这些函数用于在有设备时控制宠物喂食器
你可以通过以下方式查看:
```
>>> cam.get_system_capabilities()['OtherFunction']['SupportFeederFunction']
True
```
## 故障排除
```
cam.debug()
# 或启用非标准格式
cam.debug('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
```
## 感谢
_Telnet 访问凭据来自 gabonator_
https://gist.github.com/gabonator/74cdd6ab4f733ff047356198c781f27d
OPFeedManual
``` >>> cam.set_command("OPFeedManual", {"Servings": 1}) {'Name': 'OPFeedManual', 'OPFeedManual': {'Feeded': 1, 'NotFeeding': 0}, 'Ret': 100, 'SessionID': '0x38'} ``` Servings 表示份数OPFeedBook
``` >>> cam.get_command("OPFeedBook") {'FeedBook': [{'Enable': 1, 'RecDate': '2018-04-01', 'RecTime': '12:19:18', 'Servings': 1, 'Time': '03:00:00'}, {'Enable': 1, 'RecDate': '2018-04-01', 'RecTime': '12:19:18', 'Servings': 1, 'Time': '09:00:00'}, {'Enable': 1, 'RecDate': '2018-04-01', 'RecTime': '12:19:18', 'Servings': 1, 'Time': '06:00:00'}, {'Enable': 1, 'RecDate': '2018-04-01', 'RecTime': '12:19:18', 'Servings': 1, 'Time': '15:00:00'}, {'Enable': 1, 'RecDate': '2018-04-01', 'RecTime': '12:19:18', 'Servings': 1, 'Time': '12:00:00'}, {'Enable': 1, 'RecDate': '2018-04-01', 'RecTime': '12:19:18', 'Servings': 1, 'Time': '21:00:00'}, {'Enable': 1, 'RecDate': '2018-04-01', 'RecTime': '12:19:18', 'Servings': 1, 'Time': '18:00:00'}, {'Enable': 1, 'RecDate': '2018-04-01', 'RecTime': '12:19:18', 'Servings': 1, 'Time': '00:00:00'}, {'Enable': 1, 'RecDate': '2018-04-01', 'RecTime': '12:19:18', 'Servings': 5, 'Time': '01:00:00'}]} ``` ``` >>> cam.set_command("OPFeedBook", {"Action": "Delete", "FeedBook": [{'Enable': 1, 'RecDate': '2018-04-01', 'RecTime': '12:19:18', 'Servings': 5, 'Time': '01:00:00'}]}) {'Name': 'OPFeedBook', 'Ret': 100, 'SessionID': '0x00000018'} ``` ``` >>> cam.set_command("OPFeedBook", {"Action": "Add", "FeedBook": [{'Enable': 1, 'RecDate': '2018-04-01', 'RecTime': '12:19:18', 'Servings': 5, 'Time': '01:00:00'}]}) {'Name': 'OPFeedBook', 'Ret': 100, 'SessionID': '0x00000018'} ```OPFeedHistory
``` >>> cam.get_command("OPFeedHistory") {'FeedHistory': [{'Date': '2022-08-29', 'Servings': 1, 'Time': '18:49:45', 'Type': 2}, {'Date': '2022-08-26', 'Servings': 3, 'Time': '07:30:12', 'Type': 1}]} ``` 类型 1:自动 类型 2:手动 ``` >>> cam.set_command("OPFeedHistory", {"Action": "Delete", "FeedHistory": [{'Date': '2022-08-29', 'Servings': 1, 'Time': '19:40:01', 'Type': 2}]}) {'Name': 'OPFeedHistory', 'Ret': 100, 'SessionID': '0x00000027'} ```标签:ActiveX, DVR, DVRIP, IP摄像头, NETsurveillance, python, TCP协议, Tkinter, Xiongmai, XMeye SDK, 安防监控, 开源库, 控制台界面, 搜索引擎爬虫, 摄像头配置, 数字视频录像机, 网络摄像机, 计算机取证, 设备管理, 逆向工具, 配置工具