OpenIPC/python-dvr

GitHub: OpenIPC/python-dvr

基于 Python 的 IP 摄像头配置库,兼容 NETSurveillance 与 Xiongmai 协议,帮助用户管理海康等品牌的 IPC 设备。

Stars: 59 | Forks: 12

# python-dvr 用于配置使用 NETsurveillance ActiveX 插件的一系列 IP 摄像头的 Python 库 XMeye SDK ![screenshot](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/078d2eecc4180102.jpg) ## 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) ``` ![screenshot](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/118a80866f180103.jpg) ``` # 显示当前温度、速度、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"}) ``` ![screenshot](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/ded4969bc6180104.png) ## 升级摄像头固件 ``` # 可选:获取升级参数信息 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 ```
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'} ```
## 故障排除 ``` cam.debug() # 或启用非标准格式 cam.debug('%(asctime)s - %(name)s - %(levelname)s - %(message)s') ``` ## 感谢 _Telnet 访问凭据来自 gabonator_ https://gist.github.com/gabonator/74cdd6ab4f733ff047356198c781f27d
标签:ActiveX, DVR, DVRIP, IP摄像头, NETsurveillance, python, TCP协议, Tkinter, Xiongmai, XMeye SDK, 安防监控, 开源库, 控制台界面, 搜索引擎爬虫, 摄像头配置, 数字视频录像机, 网络摄像机, 计算机取证, 设备管理, 逆向工具, 配置工具