Browse Source

dos2unix *

pull/2/head
Dmitry Ermakov 2 years ago
parent
commit
33d33de5dd
  1. 110
      AlarmServer.py
  2. 198
      ArduinoOSD.cpp
  3. 2286
      DeviceManager.py
  4. 24
      Dockerfile
  5. 42
      LICENSE
  6. 188
      NVR.py
  7. 22
      NVRVideoDownloader.json
  8. 178
      NVRVideoDownloader.py
  9. 1406
      README.md
  10. 1572
      asyncio_dvrip.py
  11. 92
      connect.py
  12. 254
      download-local-files.py
  13. 2248
      dvrip.py
  14. 34
      examples/socketio/Dockerfile
  15. 30
      examples/socketio/README.md
  16. 214
      examples/socketio/app.py
  17. 34
      examples/socketio/client.py
  18. 28
      examples/socketio/requirements.txt
  19. 240
      monitor.py
  20. 94
      setup.py
  21. 434
      solarcam.py
  22. 488
      telnet_opener.py

110
AlarmServer.py

@ -1,55 +1,55 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os, sys, struct, json import os, sys, struct, json
from time import sleep from time import sleep
from socket import * from socket import *
from datetime import * from datetime import *
if len(sys.argv) > 1: if len(sys.argv) > 1:
port = sys.argv[1] port = sys.argv[1]
else: else:
print("Usage: %s [Port]" % os.path.basename(sys.argv[0])) print("Usage: %s [Port]" % os.path.basename(sys.argv[0]))
port = input("Port(default 15002): ") port = input("Port(default 15002): ")
if port == "": if port == "":
port = "15002" port = "15002"
server = socket(AF_INET, SOCK_STREAM) server = socket(AF_INET, SOCK_STREAM)
server.bind(("0.0.0.0", int(port))) server.bind(("0.0.0.0", int(port)))
# server.settimeout(0.5) # server.settimeout(0.5)
server.listen(1) server.listen(1)
log = "info.txt" log = "info.txt"
def tolog(s): def tolog(s):
logfile = open(datetime.now().strftime("%Y_%m_%d_") + log, "a+") logfile = open(datetime.now().strftime("%Y_%m_%d_") + log, "a+")
logfile.write(s) logfile.write(s)
logfile.close() logfile.close()
def GetIP(s): def GetIP(s):
return inet_ntoa(struct.pack("<I", int(s, 16))) return inet_ntoa(struct.pack("<I", int(s, 16)))
while True: while True:
try: try:
conn, addr = server.accept() conn, addr = server.accept()
head, version, session, sequence_number, msgid, len_data = struct.unpack( head, version, session, sequence_number, msgid, len_data = struct.unpack(
"BB2xII2xHI", conn.recv(20) "BB2xII2xHI", conn.recv(20)
) )
sleep(0.1) # Just for recive whole packet sleep(0.1) # Just for recive whole packet
data = conn.recv(len_data) data = conn.recv(len_data)
conn.close() conn.close()
reply = json.loads(data, encoding="utf8") reply = json.loads(data, encoding="utf8")
print(datetime.now().strftime("[%Y-%m-%d %H:%M:%S]>>>")) print(datetime.now().strftime("[%Y-%m-%d %H:%M:%S]>>>"))
print(head, version, session, sequence_number, msgid, len_data) print(head, version, session, sequence_number, msgid, len_data)
print(json.dumps(reply, indent=4, sort_keys=True)) print(json.dumps(reply, indent=4, sort_keys=True))
print("<<<") print("<<<")
tolog(repr(data) + "\r\n") tolog(repr(data) + "\r\n")
except (KeyboardInterrupt, SystemExit): except (KeyboardInterrupt, SystemExit):
break break
# except: # except:
# e = 1 # e = 1
# print "no" # print "no"
server.close() server.close()
sys.exit(1) sys.exit(1)

198
ArduinoOSD.cpp

@ -1,99 +1,99 @@
//заготовки //заготовки
//'{"EncryptType": "MD5", "LoginType": "DVRIP-Web", "PassWord": "00000000", "UserName": "admin"}' //'{"EncryptType": "MD5", "LoginType": "DVRIP-Web", "PassWord": "00000000", "UserName": "admin"}'
char login_packet_bytes[] = { char login_packet_bytes[] = {
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x03,
0x5f, 0x00, 0x00, 0x00, 0x7b, 0x22, 0x45, 0x6e, 0x5f, 0x00, 0x00, 0x00, 0x7b, 0x22, 0x45, 0x6e,
0x63, 0x72, 0x79, 0x70, 0x74, 0x54, 0x79, 0x70, 0x63, 0x72, 0x79, 0x70, 0x74, 0x54, 0x79, 0x70,
0x65, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x44, 0x35, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x44, 0x35,
0x22, 0x2c, 0x20, 0x22, 0x4c, 0x6f, 0x67, 0x69, 0x22, 0x2c, 0x20, 0x22, 0x4c, 0x6f, 0x67, 0x69,
0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20,
0x22, 0x44, 0x56, 0x52, 0x49, 0x50, 0x2d, 0x57, 0x22, 0x44, 0x56, 0x52, 0x49, 0x50, 0x2d, 0x57,
0x65, 0x62, 0x22, 0x2c, 0x20, 0x22, 0x50, 0x61, 0x65, 0x62, 0x22, 0x2c, 0x20, 0x22, 0x50, 0x61,
0x73, 0x73, 0x57, 0x6f, 0x72, 0x64, 0x22, 0x3a, 0x73, 0x73, 0x57, 0x6f, 0x72, 0x64, 0x22, 0x3a,
0x20, 0x22, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x20, 0x22, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x22, 0x2c, 0x20, 0x22, 0x55, 0x73, 0x30, 0x30, 0x22, 0x2c, 0x20, 0x22, 0x55, 0x73,
0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3a,
0x20, 0x22, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x22, 0x20, 0x22, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x22,
0x7d, 0x0a, 0x00 0x7d, 0x0a, 0x00
}; };
//'{"Name": "fVideo.OSDInfo", "SessionID": "0x00000002", "fVideo.OSDInfo": {"OSDInfo": [{"Info": ["0", "0", "0"], "OSDInfoWidget": {"BackColor": "0x00000000", "EncodeBlend": true, "FrontColor": "0xF0FFFFFF", "PreviewBlend": true, "RelativePos": [6144, 6144, 8192, 8192]}}], "strEnc": "UTF-8"}}' //'{"Name": "fVideo.OSDInfo", "SessionID": "0x00000002", "fVideo.OSDInfo": {"OSDInfo": [{"Info": ["0", "0", "0"], "OSDInfoWidget": {"BackColor": "0x00000000", "EncodeBlend": true, "FrontColor": "0xF0FFFFFF", "PreviewBlend": true, "RelativePos": [6144, 6144, 8192, 8192]}}], "strEnc": "UTF-8"}}'
char set_packet_bytes[] = { char set_packet_bytes[] = {
0xff, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x04,
0x24, 0x01, 0x00, 0x00, 0x7b, 0x22, 0x4e, 0x61, 0x24, 0x01, 0x00, 0x00, 0x7b, 0x22, 0x4e, 0x61,
0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x66, 0x56, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x66, 0x56,
0x69, 0x64, 0x65, 0x6f, 0x2e, 0x4f, 0x53, 0x44, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x4f, 0x53, 0x44,
0x49, 0x6e, 0x66, 0x6f, 0x22, 0x2c, 0x20, 0x22, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x2c, 0x20, 0x22,
0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49,
0x44, 0x22, 0x3a, 0x20, 0x22, 0x30, 0x78, 0x30, 0x44, 0x22, 0x3a, 0x20, 0x22, 0x30, 0x78, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x22, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x22,
0x2c, 0x20, 0x22, 0x66, 0x56, 0x69, 0x64, 0x65, 0x2c, 0x20, 0x22, 0x66, 0x56, 0x69, 0x64, 0x65,
0x6f, 0x2e, 0x4f, 0x53, 0x44, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4f, 0x53, 0x44, 0x49, 0x6e, 0x66,
0x6f, 0x22, 0x3a, 0x20, 0x7b, 0x22, 0x4f, 0x53, 0x6f, 0x22, 0x3a, 0x20, 0x7b, 0x22, 0x4f, 0x53,
0x44, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x3a, 0x20, 0x44, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x3a, 0x20,
0x5b, 0x7b, 0x22, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x5b, 0x7b, 0x22, 0x49, 0x6e, 0x66, 0x6f, 0x22,
0x3a, 0x20, 0x5b, 0x22, 0x30, 0x22, 0x2c, 0x20, 0x3a, 0x20, 0x5b, 0x22, 0x30, 0x22, 0x2c, 0x20,
0x22, 0x30, 0x22, 0x2c, 0x20, 0x22, 0x30, 0x22, 0x22, 0x30, 0x22, 0x2c, 0x20, 0x22, 0x30, 0x22,
0x5d, 0x2c, 0x20, 0x22, 0x4f, 0x53, 0x44, 0x49, 0x5d, 0x2c, 0x20, 0x22, 0x4f, 0x53, 0x44, 0x49,
0x6e, 0x66, 0x6f, 0x57, 0x69, 0x64, 0x67, 0x65, 0x6e, 0x66, 0x6f, 0x57, 0x69, 0x64, 0x67, 0x65,
0x74, 0x22, 0x3a, 0x20, 0x7b, 0x22, 0x42, 0x61, 0x74, 0x22, 0x3a, 0x20, 0x7b, 0x22, 0x42, 0x61,
0x63, 0x6b, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x22, 0x63, 0x6b, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x22,
0x3a, 0x20, 0x22, 0x30, 0x78, 0x30, 0x30, 0x30, 0x3a, 0x20, 0x22, 0x30, 0x78, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x22, 0x2c, 0x20, 0x30, 0x30, 0x30, 0x30, 0x30, 0x22, 0x2c, 0x20,
0x22, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x42, 0x22, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x42,
0x6c, 0x65, 0x6e, 0x64, 0x22, 0x3a, 0x20, 0x74, 0x6c, 0x65, 0x6e, 0x64, 0x22, 0x3a, 0x20, 0x74,
0x72, 0x75, 0x65, 0x2c, 0x20, 0x22, 0x46, 0x72, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x22, 0x46, 0x72,
0x6f, 0x6e, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x6f, 0x6e, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72,
0x22, 0x3a, 0x20, 0x22, 0x30, 0x78, 0x46, 0x30, 0x22, 0x3a, 0x20, 0x22, 0x30, 0x78, 0x46, 0x30,
0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x22, 0x2c, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x22, 0x2c,
0x20, 0x22, 0x50, 0x72, 0x65, 0x76, 0x69, 0x65, 0x20, 0x22, 0x50, 0x72, 0x65, 0x76, 0x69, 0x65,
0x77, 0x42, 0x6c, 0x65, 0x6e, 0x64, 0x22, 0x3a, 0x77, 0x42, 0x6c, 0x65, 0x6e, 0x64, 0x22, 0x3a,
0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x22, 0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x22,
0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65,
0x50, 0x6f, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x36, 0x50, 0x6f, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x36,
0x31, 0x34, 0x34, 0x2c, 0x20, 0x36, 0x31, 0x34, 0x31, 0x34, 0x34, 0x2c, 0x20, 0x36, 0x31, 0x34,
0x34, 0x2c, 0x20, 0x38, 0x31, 0x39, 0x32, 0x2c, 0x34, 0x2c, 0x20, 0x38, 0x31, 0x39, 0x32, 0x2c,
0x20, 0x38, 0x31, 0x39, 0x32, 0x5d, 0x7d, 0x7d, 0x20, 0x38, 0x31, 0x39, 0x32, 0x5d, 0x7d, 0x7d,
0x5d, 0x2c, 0x20, 0x22, 0x73, 0x74, 0x72, 0x45, 0x5d, 0x2c, 0x20, 0x22, 0x73, 0x74, 0x72, 0x45,
0x6e, 0x63, 0x22, 0x3a, 0x20, 0x22, 0x55, 0x54, 0x6e, 0x63, 0x22, 0x3a, 0x20, 0x22, 0x55, 0x54,
0x46, 0x2d, 0x38, 0x22, 0x7d, 0x7d, 0x0a, 0x00 0x46, 0x2d, 0x38, 0x22, 0x7d, 0x7d, 0x0a, 0x00
}; };
char str1[] = "Test: 1"; char str1[] = "Test: 1";
char str2[] = "Test: 2"; char str2[] = "Test: 2";
char str3[] = "Test: 3"; char str3[] = "Test: 3";
memcpy( &login_packet_bytes[83], "00000000", 8 );//set password hash(83..88) memcpy( &login_packet_bytes[83], "00000000", 8 );//set password hash(83..88)
client.write(login_packet_bytes); client.write(login_packet_bytes);
char income[20] = client.read(20) char income[20] = client.read(20)
int len = 289+sizeof(str1)+sizeof(str2)+sizeof(str3); int len = 289+sizeof(str1)+sizeof(str2)+sizeof(str3);
char buff[len]; char buff[len];
int offset = 0; int offset = 0;
memcpy( &buff[4], $income[4], 4 );//4...7 - session id memcpy( &buff[4], $income[4], 4 );//4...7 - session id
memcpy( &buff[16], &len, 2);//set len 16..17 - bytes memcpy( &buff[16], &len, 2);//set len 16..17 - bytes
//TO DO: set session hex str //TO DO: set session hex str
//70...63 - hex string session //70...63 - hex string session
memcpy( &buff[offset], &set_packet_bytes[0], 116); memcpy( &buff[offset], &set_packet_bytes[0], 116);
//116 str1 //116 str1
//121 str2 //121 str2
//126 str3 //126 str3
offset += 116; offset += 116;
memcpy( &buff[offset], &str1[0], sizeof(str1));//set str1 memcpy( &buff[offset], &str1[0], sizeof(str1));//set str1
offset +=sizeof(str1); offset +=sizeof(str1);
memcpy( &buff[offset], &set_packet_bytes[117], 4); memcpy( &buff[offset], &set_packet_bytes[117], 4);
offset += 4; offset += 4;
memcpy( &buff[offset], &str2[0], sizeof(str2));//set str2 memcpy( &buff[offset], &str2[0], sizeof(str2));//set str2
offset += sizeof(str2); offset += sizeof(str2);
memcpy( &buff[offset], &set_packet_bytes[117], 4); memcpy( &buff[offset], &set_packet_bytes[117], 4);
offset += 4; offset += 4;
memcpy( &buff[offset], &str3[0], sizeof(str3));//set str3 memcpy( &buff[offset], &str3[0], sizeof(str3));//set str3
offset += sizeof(str3); offset += sizeof(str3);
memcpy( &buff[offset], &set_packet_bytes[127], 185); memcpy( &buff[offset], &set_packet_bytes[127], 185);
offset += 38; offset += 38;
memcpy( &buff[offset], "00000000", 8 );//BG color memcpy( &buff[offset], "00000000", 8 );//BG color
offset += 41; offset += 41;
memcpy( &buff[offset], "F0FFFFFF", 8 );//FG color memcpy( &buff[offset], "F0FFFFFF", 8 );//FG color
//Serial.write(buff);//debug //Serial.write(buff);//debug
client.write(buff); client.write(buff);
client.close(); client.close();

2286
DeviceManager.py

File diff suppressed because it is too large

24
Dockerfile

@ -1,12 +1,12 @@
FROM python:slim FROM python:slim
RUN apt-get update && \ RUN apt-get update && \
apt-get upgrade -y && \ apt-get upgrade -y && \
apt-get install -y \ apt-get install -y \
ffmpeg ffmpeg
WORKDIR /app WORKDIR /app
COPY . . COPY . .
CMD [ "python3", "./download-local-files.py"] CMD [ "python3", "./download-local-files.py"]

42
LICENSE

@ -1,21 +1,21 @@
MIT License MIT License
Copyright (c) 2017 Eliot Kent Woodrich Copyright (c) 2017 Eliot Kent Woodrich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.

188
NVR.py

@ -1,94 +1,94 @@
from time import sleep, monotonic from time import sleep, monotonic
from dvrip import DVRIPCam, SomethingIsWrongWithCamera from dvrip import DVRIPCam, SomethingIsWrongWithCamera
from pathlib import Path from pathlib import Path
import logging import logging
class NVR: class NVR:
nvr = None nvr = None
logger = None logger = None
def __init__(self, host_ip, user, password, logger): def __init__(self, host_ip, user, password, logger):
self.logger = logger self.logger = logger
self.nvr = DVRIPCam( self.nvr = DVRIPCam(
host_ip, host_ip,
user=user, user=user,
password=password, password=password,
) )
if logger.level <= logging.DEBUG: if logger.level <= logging.DEBUG:
self.nvr.debug() self.nvr.debug()
def login(self): def login(self):
try: try:
self.logger.info(f"Connecting to NVR...") self.logger.info(f"Connecting to NVR...")
self.nvr.login() self.nvr.login()
self.logger.info("Successfuly connected to NVR.") self.logger.info("Successfuly connected to NVR.")
return return
except SomethingIsWrongWithCamera: except SomethingIsWrongWithCamera:
self.logger.error("Can't connect to NVR") self.logger.error("Can't connect to NVR")
self.nvr.close() self.nvr.close()
def logout(self): def logout(self):
self.nvr.close() self.nvr.close()
def get_channel_statuses(self): def get_channel_statuses(self):
channel_statuses = self.nvr.get_channel_statuses() channel_statuses = self.nvr.get_channel_statuses()
if 'Ret' in channel_statuses: if 'Ret' in channel_statuses:
return None return None
channel_titles = self.nvr.get_channel_titles() channel_titles = self.nvr.get_channel_titles()
if 'Ret' in channel_titles: if 'Ret' in channel_titles:
return None return None
for i in range(min(len(channel_statuses), len(channel_titles))): for i in range(min(len(channel_statuses), len(channel_titles))):
channel_statuses[i]['Title'] = channel_titles[i] channel_statuses[i]['Title'] = channel_titles[i]
channel_statuses[i]['Channel'] = i channel_statuses[i]['Channel'] = i
return [c for c in channel_statuses if c['Status'] != ''] return [c for c in channel_statuses if c['Status'] != '']
def get_local_files(self, channel, start, end, filetype): def get_local_files(self, channel, start, end, filetype):
return self.nvr.list_local_files(start, end, filetype, channel) return self.nvr.list_local_files(start, end, filetype, channel)
def generateTargetFileName(self, filename): def generateTargetFileName(self, filename):
# My NVR's filename example: /idea0/2023-11-19/002/05.38.58-05.39.34[M][@69f17][0].h264 # My NVR's filename example: /idea0/2023-11-19/002/05.38.58-05.39.34[M][@69f17][0].h264
# You should check file names in your NVR and review the transformation # You should check file names in your NVR and review the transformation
filenameSplit = filename.replace("][", "/").replace("[", "/").replace("]", "/").split("/") filenameSplit = filename.replace("][", "/").replace("[", "/").replace("]", "/").split("/")
return f"{filenameSplit[3]}_{filenameSplit[2]}_{filenameSplit[4]}{filenameSplit[-1]}" return f"{filenameSplit[3]}_{filenameSplit[2]}_{filenameSplit[4]}{filenameSplit[-1]}"
def save_files(self, download_dir, files): def save_files(self, download_dir, files):
self.logger.info(f"Files downloading: start") self.logger.info(f"Files downloading: start")
size_to_download = sum(int(f['FileLength'], 0) for f in files) size_to_download = sum(int(f['FileLength'], 0) for f in files)
for file in files: for file in files:
target_file_name = self.generateTargetFileName(file["FileName"]) target_file_name = self.generateTargetFileName(file["FileName"])
target_file_path = f"{download_dir}/{target_file_name}" target_file_path = f"{download_dir}/{target_file_name}"
size = int(file['FileLength'], 0) size = int(file['FileLength'], 0)
size_to_download -= size size_to_download -= size
if Path(f"{target_file_path}").is_file(): if Path(f"{target_file_path}").is_file():
self.logger.info(f" {target_file_name} file already exists, skipping download") self.logger.info(f" {target_file_name} file already exists, skipping download")
continue continue
self.logger.info(f" {target_file_name} [{size/1024:.1f} MBytes] downloading...") self.logger.info(f" {target_file_name} [{size/1024:.1f} MBytes] downloading...")
time_dl = monotonic() time_dl = monotonic()
self.nvr.download_file( self.nvr.download_file(
file["BeginTime"], file["EndTime"], file["FileName"], target_file_path file["BeginTime"], file["EndTime"], file["FileName"], target_file_path
) )
time_dl = monotonic() - time_dl time_dl = monotonic() - time_dl
speed = size / time_dl speed = size / time_dl
self.logger.info(f" Done [{speed:.1f} KByte/s] {size_to_download/1024:.1f} MBytes more to download") self.logger.info(f" Done [{speed:.1f} KByte/s] {size_to_download/1024:.1f} MBytes more to download")
self.logger.info(f"Files downloading: done") self.logger.info(f"Files downloading: done")
def list_files(self, files): def list_files(self, files):
self.logger.info(f"Files listing: start") self.logger.info(f"Files listing: start")
for file in files: for file in files:
target_file_name = self.generateTargetFileName(file["FileName"]) target_file_name = self.generateTargetFileName(file["FileName"])
size = int(file['FileLength'], 0) size = int(file['FileLength'], 0)
self.logger.info(f" {target_file_name} [{size/1024:.1f} MBytes]") self.logger.info(f" {target_file_name} [{size/1024:.1f} MBytes]")
self.logger.info(f"Files listing: end") self.logger.info(f"Files listing: end")

22
NVRVideoDownloader.json

@ -1,11 +1,11 @@
{ {
"host_ip": "10.0.0.8", "host_ip": "10.0.0.8",
"user": "admin", "user": "admin",
"password": "mypassword", "password": "mypassword",
"channel": 0, "channel": 0,
"download_dir": "./download", "download_dir": "./download",
"start": "2023-11-19 6:22:34", "start": "2023-11-19 6:22:34",
"end": "2023-11-19 6:23:09", "end": "2023-11-19 6:23:09",
"just_list_files": false, "just_list_files": false,
"log_level": "INFO" "log_level": "INFO"
} }

178
NVRVideoDownloader.py

@ -1,89 +1,89 @@
from pathlib import Path from pathlib import Path
import os import os
import json import json
import logging import logging
from collections import namedtuple from collections import namedtuple
from NVR import NVR from NVR import NVR
def init_logger(log_level): def init_logger(log_level):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.setLevel(log_level) logger.setLevel(log_level)
ch = logging.StreamHandler() ch = logging.StreamHandler()
formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
ch.setFormatter(formatter) ch.setFormatter(formatter)
logger.addHandler(ch) logger.addHandler(ch)
return logger return logger
def load_config(): def load_config():
def config_decoder(config_dict): def config_decoder(config_dict):
return namedtuple("X", config_dict.keys())(*config_dict.values()) return namedtuple("X", config_dict.keys())(*config_dict.values())
config_path = os.environ.get("NVRVIDEODOWNLOADER_CFG") config_path = os.environ.get("NVRVIDEODOWNLOADER_CFG")
if config_path is None or not Path(config_path).exists(): if config_path is None or not Path(config_path).exists():
config_path = "NVRVideoDownloader.json" config_path = "NVRVideoDownloader.json"
if Path(config_path).exists(): if Path(config_path).exists():
with open(config_path, "r") as file: with open(config_path, "r") as file:
return json.loads(file.read(), object_hook=config_decoder) return json.loads(file.read(), object_hook=config_decoder)
return { return {
"host_ip": os.environ.get("IP_ADDRESS"), "host_ip": os.environ.get("IP_ADDRESS"),
"user": os.environ.get("USER"), "user": os.environ.get("USER"),
"password": os.environ.get("PASSWORD"), "password": os.environ.get("PASSWORD"),
"channel": os.environ.get("CHANNEL"), "channel": os.environ.get("CHANNEL"),
"download_dir": os.environ.get("DOWNLOAD_DIR"), "download_dir": os.environ.get("DOWNLOAD_DIR"),
"start": os.environ.get("START"), "start": os.environ.get("START"),
"end": os.environ.get("END"), "end": os.environ.get("END"),
"just_list_files": os.environ.get("DUMP_LOCAL_FILES").lower() in ["true", "1", "y", "yes"], "just_list_files": os.environ.get("DUMP_LOCAL_FILES").lower() in ["true", "1", "y", "yes"],
"log_level": "INFO" "log_level": "INFO"
} }
def main(): def main():
config = load_config() config = load_config()
logger = init_logger(config.log_level) logger = init_logger(config.log_level)
channel = config.channel; channel = config.channel;
start = config.start start = config.start
end = config.end end = config.end
just_list_files = config.just_list_files; just_list_files = config.just_list_files;
nvr = NVR(config.host_ip, config.user, config.password, logger) nvr = NVR(config.host_ip, config.user, config.password, logger)
try: try:
nvr.login() nvr.login()
channel_statuses = nvr.get_channel_statuses() channel_statuses = nvr.get_channel_statuses()
if channel_statuses: if channel_statuses:
channel_statuses_short = [{f"{c['Channel']}:{c['Title']}({c['ChnName']})"} channel_statuses_short = [{f"{c['Channel']}:{c['Title']}({c['ChnName']})"}
for c in channel_statuses if c['Status'] != 'NoConfig'] for c in channel_statuses if c['Status'] != 'NoConfig']
logger.info(f"Configured channels in NVR: {channel_statuses_short}") logger.info(f"Configured channels in NVR: {channel_statuses_short}")
videos = nvr.get_local_files(channel, start, end, "h264") videos = nvr.get_local_files(channel, start, end, "h264")
if videos: if videos:
size = sum(int(f['FileLength'], 0) for f in videos) size = sum(int(f['FileLength'], 0) for f in videos)
logger.info(f"Video files found: {len(videos)}. Total size: {size/1024:.1f}M") logger.info(f"Video files found: {len(videos)}. Total size: {size/1024:.1f}M")
Path(config.download_dir).parent.mkdir( Path(config.download_dir).parent.mkdir(
parents=True, exist_ok=True parents=True, exist_ok=True
) )
if just_list_files: if just_list_files:
nvr.list_files(videos) nvr.list_files(videos)
else: else:
nvr.save_files(config.download_dir, videos) nvr.save_files(config.download_dir, videos)
else: else:
logger.info(f"No video files found") logger.info(f"No video files found")
nvr.logout() nvr.logout()
except ConnectionRefusedError: except ConnectionRefusedError:
logger.error(f"Connection can't be established or got disconnected") logger.error(f"Connection can't be established or got disconnected")
except TypeError as e: except TypeError as e:
print(e) print(e)
logger.error(f"Error while downloading a file") logger.error(f"Error while downloading a file")
except KeyError: except KeyError:
logger.error(f"Error while getting the file list") logger.error(f"Error while getting the file list")
if __name__ == "__main__": if __name__ == "__main__":
main() main()

1406
README.md

File diff suppressed because it is too large

1572
asyncio_dvrip.py

File diff suppressed because it is too large

92
connect.py

@ -1,46 +1,46 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: UTF-8 -*- # -*- coding: UTF-8 -*-
import sys import sys
from dvrip import DVRIPCam from dvrip import DVRIPCam
from time import sleep from time import sleep
import json import json
host_ip = "192.168.0.100" host_ip = "192.168.0.100"
if len(sys.argv) > 1: if len(sys.argv) > 1:
host_ip = str(sys.argv[1]) host_ip = str(sys.argv[1])
cam = DVRIPCam(host_ip, user="admin", password="46216") cam = DVRIPCam(host_ip, user="admin", password="46216")
if cam.login(): if cam.login():
print("Success! Connected to " + host_ip) print("Success! Connected to " + host_ip)
else: else:
print("Failure. Could not connect.") print("Failure. Could not connect.")
info = cam.get_info("fVideo.OSDInfo") info = cam.get_info("fVideo.OSDInfo")
print(json.dumps(info, ensure_ascii=False)) print(json.dumps(info, ensure_ascii=False))
info["OSDInfo"][0]["Info"] = [u"Тест00", "Test01", "Test02"] info["OSDInfo"][0]["Info"] = [u"Тест00", "Test01", "Test02"]
# info["OSDInfo"][0]["Info"][1] = "" # info["OSDInfo"][0]["Info"][1] = ""
# info["OSDInfo"][0]["Info"][2] = "" # info["OSDInfo"][0]["Info"][2] = ""
# info["OSDInfo"][0]["Info"][3] = "Test3" # info["OSDInfo"][0]["Info"][3] = "Test3"
info["OSDInfo"][0]["OSDInfoWidget"]["EncodeBlend"] = True info["OSDInfo"][0]["OSDInfoWidget"]["EncodeBlend"] = True
info["OSDInfo"][0]["OSDInfoWidget"]["PreviewBlend"] = True info["OSDInfo"][0]["OSDInfoWidget"]["PreviewBlend"] = True
# info["OSDInfo"][0]["OSDInfoWidget"]["RelativePos"] = [6144,6144,8192,8192] # info["OSDInfo"][0]["OSDInfoWidget"]["RelativePos"] = [6144,6144,8192,8192]
cam.set_info("fVideo.OSDInfo", info) cam.set_info("fVideo.OSDInfo", info)
# enc_info = cam.get_info("Simplify.Encode") # enc_info = cam.get_info("Simplify.Encode")
# Alarm example # Alarm example
def alarm(content, ids): def alarm(content, ids):
print(content) print(content)
cam.setAlarm(alarm) cam.setAlarm(alarm)
cam.alarmStart() cam.alarmStart()
# cam.get_encode_info() # cam.get_encode_info()
# sleep(1) # sleep(1)
# cam.get_camera_info() # cam.get_camera_info()
# sleep(1) # sleep(1)
# enc_info[0]['ExtraFormat']['Video']['FPS'] = 20 # enc_info[0]['ExtraFormat']['Video']['FPS'] = 20
# cam.set_info("Simplify.Encode", enc_info) # cam.set_info("Simplify.Encode", enc_info)
# sleep(2) # sleep(2)
# print(cam.get_info("Simplify.Encode")) # print(cam.get_info("Simplify.Encode"))
# cam.close() # cam.close()

254
download-local-files.py

@ -1,127 +1,127 @@
from pathlib import Path from pathlib import Path
from time import sleep from time import sleep
import os import os
import json import json
import logging import logging
from collections import namedtuple from collections import namedtuple
from solarcam import SolarCam from solarcam import SolarCam
def init_logger(): def init_logger():
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler() ch = logging.StreamHandler()
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter) ch.setFormatter(formatter)
logger.addHandler(ch) logger.addHandler(ch)
return logger return logger
def load_config(): def load_config():
def config_decoder(config_dict): def config_decoder(config_dict):
return namedtuple("X", config_dict.keys())(*config_dict.values()) return namedtuple("X", config_dict.keys())(*config_dict.values())
config_path = os.environ.get("CONFIG_PATH") config_path = os.environ.get("CONFIG_PATH")
if Path(config_path).exists(): if Path(config_path).exists():
with open(config_path, "r") as file: with open(config_path, "r") as file:
return json.loads(file.read(), object_hook=config_decoder) return json.loads(file.read(), object_hook=config_decoder)
return { return {
"host_ip": os.environ.get("IP_ADDRESS"), "host_ip": os.environ.get("IP_ADDRESS"),
"user": os.environ.get("USER"), "user": os.environ.get("USER"),
"password": os.environ.get("PASSWORD"), "password": os.environ.get("PASSWORD"),
"target_filetype_video": os.environ.get("target_filetype_video"), "target_filetype_video": os.environ.get("target_filetype_video"),
"download_dir_video": os.environ.get("DOWNLOAD_DIR_VIDEO"), "download_dir_video": os.environ.get("DOWNLOAD_DIR_VIDEO"),
"download_dir_picture": os.environ.get("DOWNLOAD_DIR_PICTURE"), "download_dir_picture": os.environ.get("DOWNLOAD_DIR_PICTURE"),
"start": os.environ.get("START"), "start": os.environ.get("START"),
"end": os.environ.get("END"), "end": os.environ.get("END"),
"blacklist_path": os.environ.get("BLACKLIST_PATH"), "blacklist_path": os.environ.get("BLACKLIST_PATH"),
"cooldown": int(os.environ.get("COOLDOWN")), "cooldown": int(os.environ.get("COOLDOWN")),
"dump_local_files": ( "dump_local_files": (
os.environ.get("DUMP_LOCAL_FILES").lower() in ["true", "1", "y", "yes"] os.environ.get("DUMP_LOCAL_FILES").lower() in ["true", "1", "y", "yes"]
), ),
} }
def main(): def main():
logger = init_logger() logger = init_logger()
config = load_config() config = load_config()
start = config.start start = config.start
end = config.end end = config.end
cooldown = config.cooldown cooldown = config.cooldown
blacklist = None blacklist = None
if Path(config.blacklist_path).exists(): if Path(config.blacklist_path).exists():
with open(config.blacklist_path, "r") as file: with open(config.blacklist_path, "r") as file:
blacklist = [line.rstrip() for line in file] blacklist = [line.rstrip() for line in file]
while True: while True:
solarCam = SolarCam(config.host_ip, config.user, config.password, logger) solarCam = SolarCam(config.host_ip, config.user, config.password, logger)
try: try:
solarCam.login() solarCam.login()
battery = solarCam.get_battery() battery = solarCam.get_battery()
logger.debug(f"Current battery status: {battery}") logger.debug(f"Current battery status: {battery}")
storage = solarCam.get_storage()[0] storage = solarCam.get_storage()[0]
logger.debug(f"Current storage status: {storage}") logger.debug(f"Current storage status: {storage}")
logger.debug(f"Syncing time...") logger.debug(f"Syncing time...")
solarCam.set_time() # setting it to system clock solarCam.set_time() # setting it to system clock
logger.debug(f"Camera time is now {solarCam.get_time()}") logger.debug(f"Camera time is now {solarCam.get_time()}")
sleep(5) # sleep some seconds so camera can get ready sleep(5) # sleep some seconds so camera can get ready
pics = solarCam.get_local_files(start, end, "jpg") pics = solarCam.get_local_files(start, end, "jpg")
if pics: if pics:
Path(config.download_dir_picture).parent.mkdir( Path(config.download_dir_picture).parent.mkdir(
parents=True, exist_ok=True parents=True, exist_ok=True
) )
solarCam.save_files( solarCam.save_files(
config.download_dir_picture, pics, blacklist=blacklist config.download_dir_picture, pics, blacklist=blacklist
) )
videos = solarCam.get_local_files(start, end, "h264") videos = solarCam.get_local_files(start, end, "h264")
if videos: if videos:
Path(config.download_dir_video).parent.mkdir( Path(config.download_dir_video).parent.mkdir(
parents=True, exist_ok=True parents=True, exist_ok=True
) )
solarCam.save_files( solarCam.save_files(
config.download_dir_video, config.download_dir_video,
videos, videos,
blacklist=blacklist, blacklist=blacklist,
target_filetype=config.target_filetype_video, target_filetype=config.target_filetype_video,
) )
if config.dump_local_files: if config.dump_local_files:
logger.debug(f"Dumping local files...") logger.debug(f"Dumping local files...")
solarCam.dump_local_files( solarCam.dump_local_files(
videos, videos,
config.blacklist_path, config.blacklist_path,
config.download_dir_video, config.download_dir_video,
target_filetype=config.target_filetype_video, target_filetype=config.target_filetype_video,
) )
solarCam.dump_local_files( solarCam.dump_local_files(
pics, config.blacklist_path, config.download_dir_picture pics, config.blacklist_path, config.download_dir_picture
) )
solarCam.logout() solarCam.logout()
except ConnectionRefusedError: except ConnectionRefusedError:
logger.debug(f"Connection could not be established or got disconnected") logger.debug(f"Connection could not be established or got disconnected")
except TypeError as e: except TypeError as e:
print(e) print(e)
logger.debug(f"Error while downloading a file") logger.debug(f"Error while downloading a file")
except KeyError: except KeyError:
logger.debug(f"Error while getting the file list") logger.debug(f"Error while getting the file list")
logger.debug(f"Sleeping for {cooldown} seconds...") logger.debug(f"Sleeping for {cooldown} seconds...")
sleep(cooldown) sleep(cooldown)
if __name__ == "__main__": if __name__ == "__main__":
main() main()
# todo add flask api for moving cam # todo add flask api for moving cam
# todo show current stream # todo show current stream
# todo show battery on webinterface and write it to mqtt topic # todo show battery on webinterface and write it to mqtt topic
# todo change camera name # todo change camera name

2248
dvrip.py

File diff suppressed because it is too large

34
examples/socketio/Dockerfile

@ -1,17 +1,17 @@
FROM python:3.10-slim-buster FROM python:3.10-slim-buster
RUN apt-get update && \ RUN apt-get update && \
apt-get upgrade -y && \ apt-get upgrade -y && \
apt-get install -y \ apt-get install -y \
git \ git \
curl curl
WORKDIR /app WORKDIR /app
COPY . . COPY . .
RUN pip3 install -r requirements.txt RUN pip3 install -r requirements.txt
EXPOSE 8888 EXPOSE 8888
CMD [ "python3", "./app.py"] CMD [ "python3", "./app.py"]

30
examples/socketio/README.md

@ -1,15 +1,15 @@
### SocketIO example ### SocketIO example
Build image Build image
```bash ```bash
docker build -t video-stream . docker build -t video-stream .
``` ```
Run container Run container
```bash ```bash
docker run -d \ docker run -d \
--restart always \ --restart always \
--network host \ --network host \
--name video-stream \ --name video-stream \
video-stream video-stream
``` ```

214
examples/socketio/app.py

@ -1,107 +1,107 @@
import socketio import socketio
from asyncio_dvrip import DVRIPCam from asyncio_dvrip import DVRIPCam
from aiohttp import web from aiohttp import web
import asyncio import asyncio
import signal import signal
import traceback import traceback
import base64 import base64
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
queue = asyncio.Queue() queue = asyncio.Queue()
# socket clients # socket clients
clients = [] clients = []
sio = socketio.AsyncServer() sio = socketio.AsyncServer()
app = web.Application() app = web.Application()
sio.attach(app) sio.attach(app)
@sio.event @sio.event
def connect(sid, environ): def connect(sid, environ):
print("connect ", sid) print("connect ", sid)
clients.append(sid) clients.append(sid)
@sio.event @sio.event
def my_message(sid, data): def my_message(sid, data):
print('message ', data) print('message ', data)
@sio.event @sio.event
def disconnect(sid): def disconnect(sid):
print('disconnect ', sid) print('disconnect ', sid)
clients.remove(sid) clients.remove(sid)
def stop(loop): def stop(loop):
loop.remove_signal_handler(signal.SIGTERM) loop.remove_signal_handler(signal.SIGTERM)
tasks = asyncio.gather(*asyncio.Task.all_tasks(loop=loop), loop=loop, return_exceptions=True) tasks = asyncio.gather(*asyncio.Task.all_tasks(loop=loop), loop=loop, return_exceptions=True)
tasks.add_done_callback(lambda t: loop.stop()) tasks.add_done_callback(lambda t: loop.stop())
tasks.cancel() tasks.cancel()
async def stream(loop, queue): async def stream(loop, queue):
cam = DVRIPCam("192.168.0.100", port=34567, user="admin", password="") cam = DVRIPCam("192.168.0.100", port=34567, user="admin", password="")
# login # login
if not await cam.login(loop): if not await cam.login(loop):
raise Exception("Can't open cam") raise Exception("Can't open cam")
try: try:
await cam.start_monitor(lambda frame, meta, user: queue.put_nowait(frame), stream="Main") await cam.start_monitor(lambda frame, meta, user: queue.put_nowait(frame), stream="Main")
except Exception as err: except Exception as err:
msg = ''.join(traceback.format_tb(err.__traceback__) + [str(err)]) msg = ''.join(traceback.format_tb(err.__traceback__) + [str(err)])
print(msg) print(msg)
finally: finally:
cam.stop_monitor() cam.stop_monitor()
cam.close() cam.close()
async def process(queue, lock): async def process(queue, lock):
while True: while True:
frame = await queue.get() frame = await queue.get()
if frame: if frame:
await lock.acquire() await lock.acquire()
try: try:
for sid in clients: for sid in clients:
await sio.emit('message', {'data': base64.b64encode(frame).decode("utf-8")}, room=sid) await sio.emit('message', {'data': base64.b64encode(frame).decode("utf-8")}, room=sid)
finally: finally:
lock.release() lock.release()
async def worker(loop, queue, lock): async def worker(loop, queue, lock):
task = None task = None
# infinyty loop # infinyty loop
while True: while True:
await lock.acquire() await lock.acquire()
try: try:
# got clients and task not started # got clients and task not started
if len(clients) > 0 and task is None: if len(clients) > 0 and task is None:
# create stream task # create stream task
task = loop.create_task(stream(loop, queue)) task = loop.create_task(stream(loop, queue))
# no more clients, neet stop task # no more clients, neet stop task
if len(clients) == 0 and task is not None: if len(clients) == 0 and task is not None:
# I don't like this way, maybe someone can do it better # I don't like this way, maybe someone can do it better
task.cancel() task.cancel()
task = None task = None
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
except Exception as err: except Exception as err:
msg = ''.join(traceback.format_tb(err.__traceback__) + [str(err)]) msg = ''.join(traceback.format_tb(err.__traceback__) + [str(err)])
print(msg) print(msg)
finally: finally:
lock.release() lock.release()
if __name__ == '__main__': if __name__ == '__main__':
try: try:
lock = asyncio.Lock() lock = asyncio.Lock()
# run wb application # run wb application
runner = web.AppRunner(app) runner = web.AppRunner(app)
loop.run_until_complete(runner.setup()) loop.run_until_complete(runner.setup())
site = web.TCPSite(runner, host='0.0.0.0', port=8888) site = web.TCPSite(runner, host='0.0.0.0', port=8888)
loop.run_until_complete(site.start()) loop.run_until_complete(site.start())
# run worker # run worker
loop.create_task(worker(loop, queue, lock)) loop.create_task(worker(loop, queue, lock))
loop.create_task(process(queue, lock)) loop.create_task(process(queue, lock))
# wait stop # wait stop
loop.run_forever() loop.run_forever()
except: except:
stop(loop) stop(loop)

34
examples/socketio/client.py

@ -1,18 +1,18 @@
import socketio import socketio
# standard Python # standard Python
sio = socketio.Client() sio = socketio.Client()
@sio.event @sio.event
def connect(): def connect():
print("I'm connected!") print("I'm connected!")
@sio.event @sio.event
def connect_error(): def connect_error():
print("The connection failed!") print("The connection failed!")
@sio.on('message') @sio.on('message')
def on_message(data): def on_message(data):
print('frame', data) print('frame', data)
sio.connect('http://localhost:8888') sio.connect('http://localhost:8888')

28
examples/socketio/requirements.txt

@ -1,14 +1,14 @@
aiohttp==3.8.5 aiohttp==3.8.5
aiosignal==1.3.1 aiosignal==1.3.1
async-timeout==4.0.2 async-timeout==4.0.2
asyncio==3.4.3 asyncio==3.4.3
attrs==22.1.0 attrs==22.1.0
bidict==0.22.0 bidict==0.22.0
charset-normalizer==2.1.1 charset-normalizer==2.1.1
frozenlist==1.3.3 frozenlist==1.3.3
idna==3.4 idna==3.4
multidict==6.0.2 multidict==6.0.2
python-dvr @ git+https://github.com/NeiroNx/python-dvr@06ff6dc0082767e7c9f23401f828533459f783a4 python-dvr @ git+https://github.com/NeiroNx/python-dvr@06ff6dc0082767e7c9f23401f828533459f783a4
python-engineio==4.3.4 python-engineio==4.3.4
python-socketio==5.7.2 python-socketio==5.7.2
yarl==1.8.1 yarl==1.8.1

240
monitor.py

@ -1,121 +1,121 @@
#! /usr/bin/python3 #! /usr/bin/python3
from dvrip import DVRIPCam, SomethingIsWrongWithCamera from dvrip import DVRIPCam, SomethingIsWrongWithCamera
from signal import signal, SIGINT, SIGTERM from signal import signal, SIGINT, SIGTERM
from sys import argv, stdout, exit from sys import argv, stdout, exit
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from time import sleep, time from time import sleep, time
import logging import logging
baseDir = argv[3] baseDir = argv[3]
retryIn = 5 retryIn = 5
rebootWait = 10 rebootWait = 10
camIp = argv[1] camIp = argv[1]
camName = argv[2] camName = argv[2]
cam = None cam = None
isShuttingDown = False isShuttingDown = False
chunkSize = 600 # new file every 10 minutes chunkSize = 600 # new file every 10 minutes
logFile = baseDir + '/' + camName + '/log.log' logFile = baseDir + '/' + camName + '/log.log'
def log(str): def log(str):
logging.info(str) logging.info(str)
def mkpath(): def mkpath():
path = baseDir + '/' + camName + "/" + datetime.today().strftime('%Y/%m/%d/%H.%M.%S') path = baseDir + '/' + camName + "/" + datetime.today().strftime('%Y/%m/%d/%H.%M.%S')
Path(path).parent.mkdir(parents=True, exist_ok=True) Path(path).parent.mkdir(parents=True, exist_ok=True)
return path return path
def shutDown(): def shutDown():
global isShuttingDown global isShuttingDown
isShuttingDown = True isShuttingDown = True
log('Shutting down...') log('Shutting down...')
try: try:
cam.stop_monitor() cam.stop_monitor()
close() close()
except (RuntimeError, TypeError, NameError, Exception): except (RuntimeError, TypeError, NameError, Exception):
pass pass
log('done') log('done')
exit(0) exit(0)
def handler(signum, b): def handler(signum, b):
log('Signal ' + str(signum) + ' received') log('Signal ' + str(signum) + ' received')
shutDown() shutDown()
signal(SIGINT, handler) signal(SIGINT, handler)
signal(SIGTERM, handler) signal(SIGTERM, handler)
def close(): def close():
cam.close() cam.close()
def theActualJob(): def theActualJob():
prevtime = 0 prevtime = 0
video = None video = None
audio = None audio = None
def receiver(frame, meta, user): def receiver(frame, meta, user):
nonlocal prevtime, video, audio nonlocal prevtime, video, audio
if frame is None: if frame is None:
log('Empty frame') log('Empty frame')
else: else:
tn = time() tn = time()
if tn - prevtime >= chunkSize: if tn - prevtime >= chunkSize:
if video != None: if video != None:
video.close() video.close()
audio.close() audio.close()
prevtime = tn prevtime = tn
path = mkpath() path = mkpath()
log('Starting files: ' + path) log('Starting files: ' + path)
video = open(path + '.video', "wb") video = open(path + '.video', "wb")
audio = open(path + '.audio', "wb") audio = open(path + '.audio', "wb")
if 'type' in meta and meta["type"] == "g711a": audio.write(frame) if 'type' in meta and meta["type"] == "g711a": audio.write(frame)
elif 'frame' in meta: video.write(frame) elif 'frame' in meta: video.write(frame)
log('Starting to grab streams...') log('Starting to grab streams...')
cam.start_monitor(receiver) cam.start_monitor(receiver)
def syncTime(): def syncTime():
log('Synching time...') log('Synching time...')
cam.set_time() cam.set_time()
log('done') log('done')
def jobWrapper(): def jobWrapper():
global cam global cam
log('Logging in to camera ' + camIp + '...') log('Logging in to camera ' + camIp + '...')
cam = DVRIPCam(camIp) cam = DVRIPCam(camIp)
if cam.login(): if cam.login():
log('done') log('done')
else: else:
raise SomethingIsWrongWithCamera('Cannot login') raise SomethingIsWrongWithCamera('Cannot login')
syncTime() syncTime()
theActualJob() theActualJob()
def theJob(): def theJob():
while True: while True:
try: try:
jobWrapper() jobWrapper()
except (TypeError, ValueError) as err: except (TypeError, ValueError) as err:
if isShuttingDown: if isShuttingDown:
exit(0) exit(0)
else: else:
try: try:
log('Error. Attempting to reboot camera...') log('Error. Attempting to reboot camera...')
cam.reboot() cam.reboot()
log('Waiting for ' + str(rebootWait) + 's for reboot...') log('Waiting for ' + str(rebootWait) + 's for reboot...')
sleep(rebootWait) sleep(rebootWait)
except (UnicodeDecodeError, ValueError, TypeError): except (UnicodeDecodeError, ValueError, TypeError):
raise SomethingIsWrongWithCamera('Failed to reboot') raise SomethingIsWrongWithCamera('Failed to reboot')
def main(): def main():
Path(logFile).parent.mkdir(parents=True, exist_ok=True) Path(logFile).parent.mkdir(parents=True, exist_ok=True)
logging.basicConfig(filename=logFile, level=logging.INFO, format='[%(asctime)s] %(message)s') logging.basicConfig(filename=logFile, level=logging.INFO, format='[%(asctime)s] %(message)s')
while True: while True:
try: try:
theJob() theJob()
except SomethingIsWrongWithCamera as err: except SomethingIsWrongWithCamera as err:
close() close()
log(str(err) + '. Waiting for ' + str(retryIn) + ' seconds before trying again...') log(str(err) + '. Waiting for ' + str(retryIn) + ' seconds before trying again...')
sleep(retryIn) sleep(retryIn)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

94
setup.py

@ -1,47 +1,47 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
import pathlib import pathlib
here = pathlib.Path(__file__).parent.resolve() here = pathlib.Path(__file__).parent.resolve()
# Get the long description from the README file # Get the long description from the README file
long_description = (here / 'README.md').read_text(encoding='utf-8') long_description = (here / 'README.md').read_text(encoding='utf-8')
setup( setup(
name='python-dvr', name='python-dvr',
version='0.0.0', version='0.0.0',
description='Python library for configuring a wide range of IP cameras which use the NETsurveillance ActiveX plugin XMeye SDK', description='Python library for configuring a wide range of IP cameras which use the NETsurveillance ActiveX plugin XMeye SDK',
long_description=long_description, long_description=long_description,
long_description_content_type='text/markdown', long_description_content_type='text/markdown',
url='https://github.com/NeiroNx/python-dvr/', url='https://github.com/NeiroNx/python-dvr/',
author='NeiroN', author='NeiroN',
classifiers=[ classifiers=[
'Development Status :: 3 - Alpha', 'Development Status :: 3 - Alpha',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'Topic :: Multimedia :: Video :: Capture', 'Topic :: Multimedia :: Video :: Capture',
'License :: OSI Approved :: MIT License', 'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3 :: Only',
], ],
py_modules=["dvrip", "DeviceManager", "asyncio_dvrip"], py_modules=["dvrip", "DeviceManager", "asyncio_dvrip"],
python_requires='>=3.6', python_requires='>=3.6',
project_urls={ project_urls={
'Bug Reports': 'https://github.com/NeiroNx/python-dvr/issues', 'Bug Reports': 'https://github.com/NeiroNx/python-dvr/issues',
'Source': 'https://github.com/NeiroNx/python-dvr', 'Source': 'https://github.com/NeiroNx/python-dvr',
}, },
) )

434
solarcam.py

@ -1,217 +1,217 @@
from time import sleep from time import sleep
from dvrip import DVRIPCam, SomethingIsWrongWithCamera from dvrip import DVRIPCam, SomethingIsWrongWithCamera
from pathlib import Path from pathlib import Path
import subprocess import subprocess
import json import json
from datetime import datetime from datetime import datetime
class SolarCam: class SolarCam:
cam = None cam = None
logger = None logger = None
def __init__(self, host_ip, user, password, logger): def __init__(self, host_ip, user, password, logger):
self.logger = logger self.logger = logger
self.cam = DVRIPCam( self.cam = DVRIPCam(
host_ip, host_ip,
user=user, user=user,
password=password, password=password,
) )
def login(self, num_retries=10): def login(self, num_retries=10):
for i in range(num_retries): for i in range(num_retries):
try: try:
self.logger.debug("Try login...") self.logger.debug("Try login...")
self.cam.login() self.cam.login()
self.logger.debug( self.logger.debug(
f"Success! Connected to Camera. Waiting few seconds to let Camera fully boot..." f"Success! Connected to Camera. Waiting few seconds to let Camera fully boot..."
) )
# waiting until camera is ready # waiting until camera is ready
sleep(10) sleep(10)
return return
except SomethingIsWrongWithCamera: except SomethingIsWrongWithCamera:
self.logger.debug("Could not connect...Camera could be offline") self.logger.debug("Could not connect...Camera could be offline")
self.cam.close() self.cam.close()
if i == 9: if i == 9:
raise ConnectionRefusedError( raise ConnectionRefusedError(
f"Could not connect {num_retries} times...aborting" f"Could not connect {num_retries} times...aborting"
) )
sleep(2) sleep(2)
def logout(self): def logout(self):
self.cam.close() self.cam.close()
def get_time(self): def get_time(self):
return self.cam.get_time() return self.cam.get_time()
def set_time(self, time=None): def set_time(self, time=None):
if time is None: if time is None:
time = datetime.now() time = datetime.now()
return self.cam.set_time(time=time) return self.cam.set_time(time=time)
def get_local_files(self, start, end, filetype): def get_local_files(self, start, end, filetype):
return self.cam.list_local_files(start, end, filetype) return self.cam.list_local_files(start, end, filetype)
def dump_local_files( def dump_local_files(
self, files, blacklist_path, download_dir, target_filetype=None self, files, blacklist_path, download_dir, target_filetype=None
): ):
with open(f"{blacklist_path}.dmp", "a") as outfile: with open(f"{blacklist_path}.dmp", "a") as outfile:
for file in files: for file in files:
target_file_path = self.generateTargetFilePath( target_file_path = self.generateTargetFilePath(
file["FileName"], download_dir file["FileName"], download_dir
) )
outfile.write(f"{target_file_path}\n") outfile.write(f"{target_file_path}\n")
if target_filetype: if target_filetype:
target_file_path_convert = self.generateTargetFilePath( target_file_path_convert = self.generateTargetFilePath(
file["FileName"], download_dir, extention=f"{target_filetype}" file["FileName"], download_dir, extention=f"{target_filetype}"
) )
outfile.write(f"{target_file_path_convert}\n") outfile.write(f"{target_file_path_convert}\n")
def generateTargetFilePath(self, filename, downloadDir, extention=""): def generateTargetFilePath(self, filename, downloadDir, extention=""):
fileExtention = Path(filename).suffix fileExtention = Path(filename).suffix
filenameSplit = filename.split("/") filenameSplit = filename.split("/")
filenameDisk = f"{filenameSplit[3]}_{filenameSplit[5][:8]}".replace(".", "-") filenameDisk = f"{filenameSplit[3]}_{filenameSplit[5][:8]}".replace(".", "-")
targetPathClean = f"{downloadDir}/{filenameDisk}" targetPathClean = f"{downloadDir}/{filenameDisk}"
if extention != "": if extention != "":
return f"{targetPathClean}{extention}" return f"{targetPathClean}{extention}"
return f"{targetPathClean}{fileExtention}" return f"{targetPathClean}{fileExtention}"
def convertFile(self, sourceFile, targetFile): def convertFile(self, sourceFile, targetFile):
if ( if (
subprocess.run( subprocess.run(
f"ffmpeg -framerate 15 -i {sourceFile} -b:v 1M -c:v libvpx-vp9 -c:a libopus {targetFile}", f"ffmpeg -framerate 15 -i {sourceFile} -b:v 1M -c:v libvpx-vp9 -c:a libopus {targetFile}",
stdout=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
shell=True, shell=True,
).returncode ).returncode
!= 0 != 0
): ):
self.logger.debug(f"Error converting video. Check {sourceFile}") self.logger.debug(f"Error converting video. Check {sourceFile}")
self.logger.debug(f"File successfully converted: {targetFile}") self.logger.debug(f"File successfully converted: {targetFile}")
Path(sourceFile).unlink() Path(sourceFile).unlink()
self.logger.debug(f"Orginal file successfully deleted: {sourceFile}") self.logger.debug(f"Orginal file successfully deleted: {sourceFile}")
def save_files(self, download_dir, files, blacklist=None, target_filetype=None): def save_files(self, download_dir, files, blacklist=None, target_filetype=None):
self.logger.debug(f"Start downloading files") self.logger.debug(f"Start downloading files")
for file in files: for file in files:
target_file_path = self.generateTargetFilePath( target_file_path = self.generateTargetFilePath(
file["FileName"], download_dir file["FileName"], download_dir
) )
target_file_path_convert = None target_file_path_convert = None
if target_filetype: if target_filetype:
target_file_path_convert = self.generateTargetFilePath( target_file_path_convert = self.generateTargetFilePath(
file["FileName"], download_dir, extention=f"{target_filetype}" file["FileName"], download_dir, extention=f"{target_filetype}"
) )
if Path(f"{target_file_path}").is_file(): if Path(f"{target_file_path}").is_file():
self.logger.debug(f"File already exists: {target_file_path}") self.logger.debug(f"File already exists: {target_file_path}")
continue continue
if ( if (
target_file_path_convert target_file_path_convert
and Path(f"{target_file_path_convert}").is_file() and Path(f"{target_file_path_convert}").is_file()
): ):
self.logger.debug( self.logger.debug(
f"Converted file already exists: {target_file_path_convert}" f"Converted file already exists: {target_file_path_convert}"
) )
continue continue
if blacklist: if blacklist:
if target_file_path in blacklist: if target_file_path in blacklist:
self.logger.debug(f"File is on the blacklist: {target_file_path}") self.logger.debug(f"File is on the blacklist: {target_file_path}")
continue continue
if target_file_path_convert and target_file_path_convert in blacklist: if target_file_path_convert and target_file_path_convert in blacklist:
self.logger.debug( self.logger.debug(
f"File is on the blacklist: {target_file_path_convert}" f"File is on the blacklist: {target_file_path_convert}"
) )
continue continue
self.logger.debug(f"Downloading {target_file_path}...") self.logger.debug(f"Downloading {target_file_path}...")
self.cam.download_file( self.cam.download_file(
file["BeginTime"], file["EndTime"], file["FileName"], target_file_path file["BeginTime"], file["EndTime"], file["FileName"], target_file_path
) )
self.logger.debug(f"Finished downloading {target_file_path}...") self.logger.debug(f"Finished downloading {target_file_path}...")
if target_file_path_convert: if target_file_path_convert:
self.logger.debug(f"Converting {target_file_path_convert}...") self.logger.debug(f"Converting {target_file_path_convert}...")
self.convertFile(target_file_path, target_file_path_convert) self.convertFile(target_file_path, target_file_path_convert)
self.logger.debug(f"Finished converting {target_file_path_convert}.") self.logger.debug(f"Finished converting {target_file_path_convert}.")
self.logger.debug(f"Finish downloading files") self.logger.debug(f"Finish downloading files")
def move_cam(self, direction, step=5): def move_cam(self, direction, step=5):
match direction: match direction:
case "up": case "up":
self.cam.ptz_step("DirectionUp", step=step) self.cam.ptz_step("DirectionUp", step=step)
case "down": case "down":
self.cam.ptz_step("DirectionDown", step=step) self.cam.ptz_step("DirectionDown", step=step)
case "left": case "left":
self.cam.ptz_step("DirectionLeft", step=step) self.cam.ptz_step("DirectionLeft", step=step)
case "right": case "right":
self.cam.ptz_step("DirectionRight", step=step) self.cam.ptz_step("DirectionRight", step=step)
case _: case _:
self.logger.debug(f"No direction found") self.logger.debug(f"No direction found")
def mute_cam(self): def mute_cam(self):
print( print(
self.cam.send( self.cam.send(
1040, 1040,
{ {
"fVideo.Volume": [ "fVideo.Volume": [
{"AudioMode": "Single", "LeftVolume": 0, "RightVolume": 0} {"AudioMode": "Single", "LeftVolume": 0, "RightVolume": 0}
], ],
"Name": "fVideo.Volume", "Name": "fVideo.Volume",
}, },
) )
) )
def set_volume(self, volume): def set_volume(self, volume):
print( print(
self.cam.send( self.cam.send(
1040, 1040,
{ {
"fVideo.Volume": [ "fVideo.Volume": [
{ {
"AudioMode": "Single", "AudioMode": "Single",
"LeftVolume": volume, "LeftVolume": volume,
"RightVolume": volume, "RightVolume": volume,
} }
], ],
"Name": "fVideo.Volume", "Name": "fVideo.Volume",
}, },
) )
) )
def get_battery(self): def get_battery(self):
data = self.cam.send_custom( data = self.cam.send_custom(
1610, 1610,
{"Name": "OPTUpData", "OPTUpData": {"UpLoadDataType": 5}}, {"Name": "OPTUpData", "OPTUpData": {"UpLoadDataType": 5}},
size=260, size=260,
)[87:-2].decode("utf-8") )[87:-2].decode("utf-8")
json_data = json.loads(data) json_data = json.loads(data)
return { return {
"BatteryPercent": json_data["Dev.ElectCapacity"]["percent"], "BatteryPercent": json_data["Dev.ElectCapacity"]["percent"],
"Charging": json_data["Dev.ElectCapacity"]["electable"], "Charging": json_data["Dev.ElectCapacity"]["electable"],
} }
def get_storage(self): def get_storage(self):
# get available storage in gb # get available storage in gb
storage_result = [] storage_result = []
data = self.cam.send(1020, {"Name": "StorageInfo"}) data = self.cam.send(1020, {"Name": "StorageInfo"})
for storage_index, storage in enumerate(data["StorageInfo"]): for storage_index, storage in enumerate(data["StorageInfo"]):
for partition_index, partition in enumerate(storage["Partition"]): for partition_index, partition in enumerate(storage["Partition"]):
s = { s = {
"Storage": storage_index, "Storage": storage_index,
"Partition": partition_index, "Partition": partition_index,
"RemainingSpace": int(partition["RemainSpace"], 0) / 1024, "RemainingSpace": int(partition["RemainSpace"], 0) / 1024,
"TotalSpace": int(partition["TotalSpace"], 0) / 1024, "TotalSpace": int(partition["TotalSpace"], 0) / 1024,
} }
storage_result.append(s) storage_result.append(s)
return storage_result return storage_result

488
telnet_opener.py

@ -1,244 +1,244 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from dvrip import DVRIPCam from dvrip import DVRIPCam
from telnetlib import Telnet from telnetlib import Telnet
import argparse import argparse
import datetime import datetime
import json import json
import os import os
import socket import socket
import time import time
import requests import requests
import zipfile import zipfile
TELNET_PORT = 4321 TELNET_PORT = 4321
ARCHIVE_URL = "https://github.com/widgetii/xmupdates/raw/main/archive" ARCHIVE_URL = "https://github.com/widgetii/xmupdates/raw/main/archive"
""" """
Tested on XM boards: Tested on XM boards:
IPG-53H20PL-S 53H20L_S39 00002532 IPG-53H20PL-S 53H20L_S39 00002532
IPG-80H20PS-S 50H20L 00022520 IPG-80H20PS-S 50H20L 00022520
IVG-85HF20PYA-S HI3516EV200_50H20AI_S38 000559A7 IVG-85HF20PYA-S HI3516EV200_50H20AI_S38 000559A7
IVG-85HG50PYA-S HI3516EV300_85H50AI 000529B2 IVG-85HG50PYA-S HI3516EV300_85H50AI 000529B2
Issues with: "armbenv: can't load library 'libdvr.so'" Issues with: "armbenv: can't load library 'libdvr.so'"
IPG-50HV20PES-S 50H20L_18EV200_S38 00018520 IPG-50HV20PES-S 50H20L_18EV200_S38 00018520
""" """
# downgrade archive (mainly Yandex.Disk) # downgrade archive (mainly Yandex.Disk)
# https://www.cctvsp.ru/articles/obnovlenie-proshivok-dlya-ip-kamer-ot-xiong-mai # https://www.cctvsp.ru/articles/obnovlenie-proshivok-dlya-ip-kamer-ot-xiong-mai
XMV4 = { XMV4 = {
"envtool": "XmEnv", "envtool": "XmEnv",
"flashes": [ "flashes": [
"0x00EF4017", "0x00EF4017",
"0x00EF4018", "0x00EF4018",
"0x00C22017", "0x00C22017",
"0x00C22018", "0x00C22018",
"0x00C22019", "0x00C22019",
"0x00C84017", "0x00C84017",
"0x00C84018", "0x00C84018",
"0x001C7017", "0x001C7017",
"0x001C7018", "0x001C7018",
"0x00207017", "0x00207017",
"0x00207018", "0x00207018",
"0x000B4017", "0x000B4017",
"0x000B4018", "0x000B4018",
], ],
} }
def down(template, filename): def down(template, filename):
t = template.copy() t = template.copy()
t['downgrade'] = filename t['downgrade'] = filename
return t return t
# Borrowed from InstallDesc # Borrowed from InstallDesc
conf = { conf = {
"000559A7": down(XMV4, "General_IPC_HI3516EV200_50H20AI_S38.Nat.dss.OnvifS.HIK_V5.00.R02.20200507_all.bin"), "000559A7": down(XMV4, "General_IPC_HI3516EV200_50H20AI_S38.Nat.dss.OnvifS.HIK_V5.00.R02.20200507_all.bin"),
"000529B2": down(XMV4, "General_IPC_HI3516EV300_85H50AI_Nat_dss_OnvifS_HIK_V5_00_R02_20200507.bin"), "000529B2": down(XMV4, "General_IPC_HI3516EV300_85H50AI_Nat_dss_OnvifS_HIK_V5_00_R02_20200507.bin"),
"000529E9": down(XMV4, "hacked_from_HI3516EV300_85H50AI.bin"), "000529E9": down(XMV4, "hacked_from_HI3516EV300_85H50AI.bin"),
} }
def add_flashes(desc, swver): def add_flashes(desc, swver):
board = conf.get(swver) board = conf.get(swver)
if board is None: if board is None:
return return
fls = [] fls = []
for i in board["flashes"]: for i in board["flashes"]:
fls.append({"FlashID": i}) fls.append({"FlashID": i})
desc["SupportFlashType"] = fls desc["SupportFlashType"] = fls
def get_envtool(swver): def get_envtool(swver):
board = conf.get(swver) board = conf.get(swver)
if board is None: if board is None:
return "armbenv" return "armbenv"
return board["envtool"] return board["envtool"]
def make_zip(filename, data): def make_zip(filename, data):
zipf = zipfile.ZipFile(filename, "w", zipfile.ZIP_DEFLATED) zipf = zipfile.ZipFile(filename, "w", zipfile.ZIP_DEFLATED)
zipf.writestr("InstallDesc", data) zipf.writestr("InstallDesc", data)
zipf.close() zipf.close()
def check_port(host_ip, port): def check_port(host_ip, port):
a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
result_of_check = a_socket.connect_ex((host_ip, port)) result_of_check = a_socket.connect_ex((host_ip, port))
return result_of_check == 0 return result_of_check == 0
def extract_gen(swver): def extract_gen(swver):
return swver.split(".")[3] return swver.split(".")[3]
def cmd_armebenv(swver): def cmd_armebenv(swver):
envtool = get_envtool(swver) envtool = get_envtool(swver)
return { return {
"Command": "Shell", "Command": "Shell",
"Script": f"{envtool} -s xmuart 0; {envtool} -s telnetctrl 1", "Script": f"{envtool} -s xmuart 0; {envtool} -s telnetctrl 1",
} }
def cmd_telnetd(port): def cmd_telnetd(port):
return { return {
"Command": "Shell", "Command": "Shell",
"Script": f"busybox telnetd -F -p {port} -l /bin/sh", "Script": f"busybox telnetd -F -p {port} -l /bin/sh",
} }
def cmd_backup(): def cmd_backup():
return [ return [
{ {
"Command": "Shell", "Command": "Shell",
"Script": "mount -o nolock 95.217.179.189:/srv/ro /utils/", "Script": "mount -o nolock 95.217.179.189:/srv/ro /utils/",
}, },
{"Command": "Shell", "Script": "/utils/ipctool -w"}, {"Command": "Shell", "Script": "/utils/ipctool -w"},
] ]
def downgrade_old_version(cam, buildtime, swver): def downgrade_old_version(cam, buildtime, swver):
milestone = datetime.date(2020, 5, 7) milestone = datetime.date(2020, 5, 7)
dto = datetime.datetime.strptime(buildtime, "%Y-%m-%d %H:%M:%S") dto = datetime.datetime.strptime(buildtime, "%Y-%m-%d %H:%M:%S")
if dto.date() > milestone: if dto.date() > milestone:
print( print(
f"Current firmware date {dto.date()}, but it needs to be no more than" f"Current firmware date {dto.date()}, but it needs to be no more than"
f" {milestone}\nConsider downgrade and only then continue.\n\n" f" {milestone}\nConsider downgrade and only then continue.\n\n"
) )
a = input("Are you sure to overwrite current firmware without backup (y/n)? ") a = input("Are you sure to overwrite current firmware without backup (y/n)? ")
if a == "y": if a == "y":
board = conf.get(swver) board = conf.get(swver)
if board is None: if board is None:
print(f"{swver} firmware is not supported yet") print(f"{swver} firmware is not supported yet")
return False return False
print("DOWNGRADING\n") print("DOWNGRADING\n")
url = f"{ARCHIVE_URL}/{swver}/{board['downgrade']}" url = f"{ARCHIVE_URL}/{swver}/{board['downgrade']}"
print(f"Downloading {url}") print(f"Downloading {url}")
r = requests.get(url, allow_redirects=True) r = requests.get(url, allow_redirects=True)
if r.status_code != requests.codes.ok: if r.status_code != requests.codes.ok:
print("Something went wrong") print("Something went wrong")
return False return False
open('upgrade.bin', 'wb').write(r.content) open('upgrade.bin', 'wb').write(r.content)
print(f"Upgrading...") print(f"Upgrading...")
cam.upgrade('upgrade.bin') cam.upgrade('upgrade.bin')
print("Completed. Wait a minute and then rerun") print("Completed. Wait a minute and then rerun")
return False return False
return False return False
return True return True
def open_telnet(host_ip, port, **kwargs): def open_telnet(host_ip, port, **kwargs):
make_telnet = kwargs.get("telnet", False) make_telnet = kwargs.get("telnet", False)
make_backup = kwargs.get("backup", False) make_backup = kwargs.get("backup", False)
user = kwargs.get("username", "admin") user = kwargs.get("username", "admin")
password = kwargs.get("password", "") password = kwargs.get("password", "")
cam = DVRIPCam(host_ip, user=user, password=password) cam = DVRIPCam(host_ip, user=user, password=password)
if not cam.login(): if not cam.login():
print(f"Cannot connect {host_ip}") print(f"Cannot connect {host_ip}")
return return
upinfo = cam.get_upgrade_info() upinfo = cam.get_upgrade_info()
hw = upinfo["Hardware"] hw = upinfo["Hardware"]
sysinfo = cam.get_system_info() sysinfo = cam.get_system_info()
swver = extract_gen(sysinfo["SoftWareVersion"]) swver = extract_gen(sysinfo["SoftWareVersion"])
print(f"Modifying camera {hw}, firmware {swver}") print(f"Modifying camera {hw}, firmware {swver}")
if not downgrade_old_version(cam, sysinfo["BuildTime"], swver): if not downgrade_old_version(cam, sysinfo["BuildTime"], swver):
cam.close() cam.close()
return return
print(f"Firmware generation {swver}") print(f"Firmware generation {swver}")
desc = { desc = {
"Hardware": hw, "Hardware": hw,
"DevID": f"{swver}1001000000000000", "DevID": f"{swver}1001000000000000",
"CompatibleVersion": 2, "CompatibleVersion": 2,
"Vendor": "General", "Vendor": "General",
"CRC": "1ce6242100007636", "CRC": "1ce6242100007636",
} }
upcmd = [] upcmd = []
if make_telnet: if make_telnet:
upcmd.append(cmd_telnetd(port)) upcmd.append(cmd_telnetd(port))
elif make_backup: elif make_backup:
upcmd = cmd_backup() upcmd = cmd_backup()
else: else:
upcmd.append(cmd_armebenv(swver)) upcmd.append(cmd_armebenv(swver))
desc["UpgradeCommand"] = upcmd desc["UpgradeCommand"] = upcmd
add_flashes(desc, swver) add_flashes(desc, swver)
zipfname = "upgrade.bin" zipfname = "upgrade.bin"
make_zip(zipfname, json.dumps(desc, indent=2)) make_zip(zipfname, json.dumps(desc, indent=2))
cam.upgrade(zipfname) cam.upgrade(zipfname)
cam.close() cam.close()
os.remove(zipfname) os.remove(zipfname)
if make_backup: if make_backup:
print("Check backup") print("Check backup")
return return
if not make_telnet: if not make_telnet:
port = 23 port = 23
print("Waiting for camera is rebooting...") print("Waiting for camera is rebooting...")
for i in range(10): for i in range(10):
time.sleep(4) time.sleep(4)
if check_port(host_ip, port): if check_port(host_ip, port):
tport = f" {port}" if port != 23 else "" tport = f" {port}" if port != 23 else ""
print(f"Now use 'telnet {host_ip}{tport}' to login") print(f"Now use 'telnet {host_ip}{tport}' to login")
return return
print("Something went wrong") print("Something went wrong")
return return
def main(): def main():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("hostname", help="Camera IP address or hostname") parser.add_argument("hostname", help="Camera IP address or hostname")
parser.add_argument( parser.add_argument(
"-u", "--username", default="admin", help="Username for camera login" "-u", "--username", default="admin", help="Username for camera login"
) )
parser.add_argument( parser.add_argument(
"-p", "--password", default="", help="Password for camera login" "-p", "--password", default="", help="Password for camera login"
) )
parser.add_argument( parser.add_argument(
"-b", "--backup", action="store_true", help="Make backup to the cloud" "-b", "--backup", action="store_true", help="Make backup to the cloud"
) )
parser.add_argument( parser.add_argument(
"-t", "-t",
"--telnet", "--telnet",
action="store_true", action="store_true",
help="Open telnet port without rebooting camera", help="Open telnet port without rebooting camera",
) )
args = parser.parse_args() args = parser.parse_args()
open_telnet(args.hostname, TELNET_PORT, **vars(args)) open_telnet(args.hostname, TELNET_PORT, **vars(args))
if __name__ == "__main__": if __name__ == "__main__":
main() main()

Loading…
Cancel
Save