mirror of https://github.com/OpenIPC/python-dvr
22 changed files with 5108 additions and 5108 deletions
@ -1,55 +1,55 @@ |
|||
#!/usr/bin/env python |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
import os, sys, struct, json |
|||
from time import sleep |
|||
from socket import * |
|||
from datetime import * |
|||
|
|||
if len(sys.argv) > 1: |
|||
port = sys.argv[1] |
|||
else: |
|||
print("Usage: %s [Port]" % os.path.basename(sys.argv[0])) |
|||
port = input("Port(default 15002): ") |
|||
if port == "": |
|||
port = "15002" |
|||
server = socket(AF_INET, SOCK_STREAM) |
|||
server.bind(("0.0.0.0", int(port))) |
|||
# server.settimeout(0.5) |
|||
server.listen(1) |
|||
|
|||
log = "info.txt" |
|||
|
|||
|
|||
def tolog(s): |
|||
logfile = open(datetime.now().strftime("%Y_%m_%d_") + log, "a+") |
|||
logfile.write(s) |
|||
logfile.close() |
|||
|
|||
|
|||
def GetIP(s): |
|||
return inet_ntoa(struct.pack("<I", int(s, 16))) |
|||
|
|||
|
|||
while True: |
|||
try: |
|||
conn, addr = server.accept() |
|||
head, version, session, sequence_number, msgid, len_data = struct.unpack( |
|||
"BB2xII2xHI", conn.recv(20) |
|||
) |
|||
sleep(0.1) # Just for recive whole packet |
|||
data = conn.recv(len_data) |
|||
conn.close() |
|||
reply = json.loads(data, encoding="utf8") |
|||
print(datetime.now().strftime("[%Y-%m-%d %H:%M:%S]>>>")) |
|||
print(head, version, session, sequence_number, msgid, len_data) |
|||
print(json.dumps(reply, indent=4, sort_keys=True)) |
|||
print("<<<") |
|||
tolog(repr(data) + "\r\n") |
|||
except (KeyboardInterrupt, SystemExit): |
|||
break |
|||
# except: |
|||
# e = 1 |
|||
# print "no" |
|||
server.close() |
|||
sys.exit(1) |
|||
#!/usr/bin/env python |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
import os, sys, struct, json |
|||
from time import sleep |
|||
from socket import * |
|||
from datetime import * |
|||
|
|||
if len(sys.argv) > 1: |
|||
port = sys.argv[1] |
|||
else: |
|||
print("Usage: %s [Port]" % os.path.basename(sys.argv[0])) |
|||
port = input("Port(default 15002): ") |
|||
if port == "": |
|||
port = "15002" |
|||
server = socket(AF_INET, SOCK_STREAM) |
|||
server.bind(("0.0.0.0", int(port))) |
|||
# server.settimeout(0.5) |
|||
server.listen(1) |
|||
|
|||
log = "info.txt" |
|||
|
|||
|
|||
def tolog(s): |
|||
logfile = open(datetime.now().strftime("%Y_%m_%d_") + log, "a+") |
|||
logfile.write(s) |
|||
logfile.close() |
|||
|
|||
|
|||
def GetIP(s): |
|||
return inet_ntoa(struct.pack("<I", int(s, 16))) |
|||
|
|||
|
|||
while True: |
|||
try: |
|||
conn, addr = server.accept() |
|||
head, version, session, sequence_number, msgid, len_data = struct.unpack( |
|||
"BB2xII2xHI", conn.recv(20) |
|||
) |
|||
sleep(0.1) # Just for recive whole packet |
|||
data = conn.recv(len_data) |
|||
conn.close() |
|||
reply = json.loads(data, encoding="utf8") |
|||
print(datetime.now().strftime("[%Y-%m-%d %H:%M:%S]>>>")) |
|||
print(head, version, session, sequence_number, msgid, len_data) |
|||
print(json.dumps(reply, indent=4, sort_keys=True)) |
|||
print("<<<") |
|||
tolog(repr(data) + "\r\n") |
|||
except (KeyboardInterrupt, SystemExit): |
|||
break |
|||
# except: |
|||
# e = 1 |
|||
# print "no" |
|||
server.close() |
|||
sys.exit(1) |
|||
|
@ -1,99 +1,99 @@ |
|||
//заготовки
|
|||
//'{"EncryptType": "MD5", "LoginType": "DVRIP-Web", "PassWord": "00000000", "UserName": "admin"}'
|
|||
char login_packet_bytes[] = { |
|||
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x03, |
|||
0x5f, 0x00, 0x00, 0x00, 0x7b, 0x22, 0x45, 0x6e, |
|||
0x63, 0x72, 0x79, 0x70, 0x74, 0x54, 0x79, 0x70, |
|||
0x65, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x44, 0x35, |
|||
0x22, 0x2c, 0x20, 0x22, 0x4c, 0x6f, 0x67, 0x69, |
|||
0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, |
|||
0x22, 0x44, 0x56, 0x52, 0x49, 0x50, 0x2d, 0x57, |
|||
0x65, 0x62, 0x22, 0x2c, 0x20, 0x22, 0x50, 0x61, |
|||
0x73, 0x73, 0x57, 0x6f, 0x72, 0x64, 0x22, 0x3a, |
|||
0x20, 0x22, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, |
|||
0x30, 0x30, 0x22, 0x2c, 0x20, 0x22, 0x55, 0x73, |
|||
0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3a, |
|||
0x20, 0x22, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x22, |
|||
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"}}'
|
|||
char set_packet_bytes[] = { |
|||
0xff, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, |
|||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x04, |
|||
0x24, 0x01, 0x00, 0x00, 0x7b, 0x22, 0x4e, 0x61, |
|||
0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x66, 0x56, |
|||
0x69, 0x64, 0x65, 0x6f, 0x2e, 0x4f, 0x53, 0x44, |
|||
0x49, 0x6e, 0x66, 0x6f, 0x22, 0x2c, 0x20, 0x22, |
|||
0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, |
|||
0x44, 0x22, 0x3a, 0x20, 0x22, 0x30, 0x78, 0x30, |
|||
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x22, |
|||
0x2c, 0x20, 0x22, 0x66, 0x56, 0x69, 0x64, 0x65, |
|||
0x6f, 0x2e, 0x4f, 0x53, 0x44, 0x49, 0x6e, 0x66, |
|||
0x6f, 0x22, 0x3a, 0x20, 0x7b, 0x22, 0x4f, 0x53, |
|||
0x44, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x3a, 0x20, |
|||
0x5b, 0x7b, 0x22, 0x49, 0x6e, 0x66, 0x6f, 0x22, |
|||
0x3a, 0x20, 0x5b, 0x22, 0x30, 0x22, 0x2c, 0x20, |
|||
0x22, 0x30, 0x22, 0x2c, 0x20, 0x22, 0x30, 0x22, |
|||
0x5d, 0x2c, 0x20, 0x22, 0x4f, 0x53, 0x44, 0x49, |
|||
0x6e, 0x66, 0x6f, 0x57, 0x69, 0x64, 0x67, 0x65, |
|||
0x74, 0x22, 0x3a, 0x20, 0x7b, 0x22, 0x42, 0x61, |
|||
0x63, 0x6b, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x22, |
|||
0x3a, 0x20, 0x22, 0x30, 0x78, 0x30, 0x30, 0x30, |
|||
0x30, 0x30, 0x30, 0x30, 0x30, 0x22, 0x2c, 0x20, |
|||
0x22, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x42, |
|||
0x6c, 0x65, 0x6e, 0x64, 0x22, 0x3a, 0x20, 0x74, |
|||
0x72, 0x75, 0x65, 0x2c, 0x20, 0x22, 0x46, 0x72, |
|||
0x6f, 0x6e, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, |
|||
0x22, 0x3a, 0x20, 0x22, 0x30, 0x78, 0x46, 0x30, |
|||
0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x22, 0x2c, |
|||
0x20, 0x22, 0x50, 0x72, 0x65, 0x76, 0x69, 0x65, |
|||
0x77, 0x42, 0x6c, 0x65, 0x6e, 0x64, 0x22, 0x3a, |
|||
0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x22, |
|||
0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, |
|||
0x50, 0x6f, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x36, |
|||
0x31, 0x34, 0x34, 0x2c, 0x20, 0x36, 0x31, 0x34, |
|||
0x34, 0x2c, 0x20, 0x38, 0x31, 0x39, 0x32, 0x2c, |
|||
0x20, 0x38, 0x31, 0x39, 0x32, 0x5d, 0x7d, 0x7d, |
|||
0x5d, 0x2c, 0x20, 0x22, 0x73, 0x74, 0x72, 0x45, |
|||
0x6e, 0x63, 0x22, 0x3a, 0x20, 0x22, 0x55, 0x54, |
|||
0x46, 0x2d, 0x38, 0x22, 0x7d, 0x7d, 0x0a, 0x00 |
|||
}; |
|||
|
|||
char str1[] = "Test: 1"; |
|||
char str2[] = "Test: 2"; |
|||
char str3[] = "Test: 3"; |
|||
|
|||
memcpy( &login_packet_bytes[83], "00000000", 8 );//set password hash(83..88)
|
|||
client.write(login_packet_bytes); |
|||
char income[20] = client.read(20) |
|||
int len = 289+sizeof(str1)+sizeof(str2)+sizeof(str3); |
|||
char buff[len]; |
|||
int offset = 0; |
|||
memcpy( &buff[4], $income[4], 4 );//4...7 - session id
|
|||
memcpy( &buff[16], &len, 2);//set len 16..17 - bytes
|
|||
//TO DO: set session hex str
|
|||
//70...63 - hex string session
|
|||
memcpy( &buff[offset], &set_packet_bytes[0], 116); |
|||
//116 str1
|
|||
//121 str2
|
|||
//126 str3
|
|||
offset += 116; |
|||
memcpy( &buff[offset], &str1[0], sizeof(str1));//set str1
|
|||
offset +=sizeof(str1); |
|||
memcpy( &buff[offset], &set_packet_bytes[117], 4); |
|||
offset += 4; |
|||
memcpy( &buff[offset], &str2[0], sizeof(str2));//set str2
|
|||
offset += sizeof(str2); |
|||
memcpy( &buff[offset], &set_packet_bytes[117], 4); |
|||
offset += 4; |
|||
memcpy( &buff[offset], &str3[0], sizeof(str3));//set str3
|
|||
offset += sizeof(str3); |
|||
memcpy( &buff[offset], &set_packet_bytes[127], 185); |
|||
offset += 38; |
|||
memcpy( &buff[offset], "00000000", 8 );//BG color
|
|||
offset += 41; |
|||
memcpy( &buff[offset], "F0FFFFFF", 8 );//FG color
|
|||
//Serial.write(buff);//debug
|
|||
client.write(buff); |
|||
client.close(); |
|||
//заготовки
|
|||
//'{"EncryptType": "MD5", "LoginType": "DVRIP-Web", "PassWord": "00000000", "UserName": "admin"}'
|
|||
char login_packet_bytes[] = { |
|||
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x03, |
|||
0x5f, 0x00, 0x00, 0x00, 0x7b, 0x22, 0x45, 0x6e, |
|||
0x63, 0x72, 0x79, 0x70, 0x74, 0x54, 0x79, 0x70, |
|||
0x65, 0x22, 0x3a, 0x20, 0x22, 0x4d, 0x44, 0x35, |
|||
0x22, 0x2c, 0x20, 0x22, 0x4c, 0x6f, 0x67, 0x69, |
|||
0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, |
|||
0x22, 0x44, 0x56, 0x52, 0x49, 0x50, 0x2d, 0x57, |
|||
0x65, 0x62, 0x22, 0x2c, 0x20, 0x22, 0x50, 0x61, |
|||
0x73, 0x73, 0x57, 0x6f, 0x72, 0x64, 0x22, 0x3a, |
|||
0x20, 0x22, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, |
|||
0x30, 0x30, 0x22, 0x2c, 0x20, 0x22, 0x55, 0x73, |
|||
0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3a, |
|||
0x20, 0x22, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x22, |
|||
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"}}'
|
|||
char set_packet_bytes[] = { |
|||
0xff, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, |
|||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x04, |
|||
0x24, 0x01, 0x00, 0x00, 0x7b, 0x22, 0x4e, 0x61, |
|||
0x6d, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x66, 0x56, |
|||
0x69, 0x64, 0x65, 0x6f, 0x2e, 0x4f, 0x53, 0x44, |
|||
0x49, 0x6e, 0x66, 0x6f, 0x22, 0x2c, 0x20, 0x22, |
|||
0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, |
|||
0x44, 0x22, 0x3a, 0x20, 0x22, 0x30, 0x78, 0x30, |
|||
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x22, |
|||
0x2c, 0x20, 0x22, 0x66, 0x56, 0x69, 0x64, 0x65, |
|||
0x6f, 0x2e, 0x4f, 0x53, 0x44, 0x49, 0x6e, 0x66, |
|||
0x6f, 0x22, 0x3a, 0x20, 0x7b, 0x22, 0x4f, 0x53, |
|||
0x44, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x3a, 0x20, |
|||
0x5b, 0x7b, 0x22, 0x49, 0x6e, 0x66, 0x6f, 0x22, |
|||
0x3a, 0x20, 0x5b, 0x22, 0x30, 0x22, 0x2c, 0x20, |
|||
0x22, 0x30, 0x22, 0x2c, 0x20, 0x22, 0x30, 0x22, |
|||
0x5d, 0x2c, 0x20, 0x22, 0x4f, 0x53, 0x44, 0x49, |
|||
0x6e, 0x66, 0x6f, 0x57, 0x69, 0x64, 0x67, 0x65, |
|||
0x74, 0x22, 0x3a, 0x20, 0x7b, 0x22, 0x42, 0x61, |
|||
0x63, 0x6b, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x22, |
|||
0x3a, 0x20, 0x22, 0x30, 0x78, 0x30, 0x30, 0x30, |
|||
0x30, 0x30, 0x30, 0x30, 0x30, 0x22, 0x2c, 0x20, |
|||
0x22, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x42, |
|||
0x6c, 0x65, 0x6e, 0x64, 0x22, 0x3a, 0x20, 0x74, |
|||
0x72, 0x75, 0x65, 0x2c, 0x20, 0x22, 0x46, 0x72, |
|||
0x6f, 0x6e, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, |
|||
0x22, 0x3a, 0x20, 0x22, 0x30, 0x78, 0x46, 0x30, |
|||
0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x22, 0x2c, |
|||
0x20, 0x22, 0x50, 0x72, 0x65, 0x76, 0x69, 0x65, |
|||
0x77, 0x42, 0x6c, 0x65, 0x6e, 0x64, 0x22, 0x3a, |
|||
0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x22, |
|||
0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, |
|||
0x50, 0x6f, 0x73, 0x22, 0x3a, 0x20, 0x5b, 0x36, |
|||
0x31, 0x34, 0x34, 0x2c, 0x20, 0x36, 0x31, 0x34, |
|||
0x34, 0x2c, 0x20, 0x38, 0x31, 0x39, 0x32, 0x2c, |
|||
0x20, 0x38, 0x31, 0x39, 0x32, 0x5d, 0x7d, 0x7d, |
|||
0x5d, 0x2c, 0x20, 0x22, 0x73, 0x74, 0x72, 0x45, |
|||
0x6e, 0x63, 0x22, 0x3a, 0x20, 0x22, 0x55, 0x54, |
|||
0x46, 0x2d, 0x38, 0x22, 0x7d, 0x7d, 0x0a, 0x00 |
|||
}; |
|||
|
|||
char str1[] = "Test: 1"; |
|||
char str2[] = "Test: 2"; |
|||
char str3[] = "Test: 3"; |
|||
|
|||
memcpy( &login_packet_bytes[83], "00000000", 8 );//set password hash(83..88)
|
|||
client.write(login_packet_bytes); |
|||
char income[20] = client.read(20) |
|||
int len = 289+sizeof(str1)+sizeof(str2)+sizeof(str3); |
|||
char buff[len]; |
|||
int offset = 0; |
|||
memcpy( &buff[4], $income[4], 4 );//4...7 - session id
|
|||
memcpy( &buff[16], &len, 2);//set len 16..17 - bytes
|
|||
//TO DO: set session hex str
|
|||
//70...63 - hex string session
|
|||
memcpy( &buff[offset], &set_packet_bytes[0], 116); |
|||
//116 str1
|
|||
//121 str2
|
|||
//126 str3
|
|||
offset += 116; |
|||
memcpy( &buff[offset], &str1[0], sizeof(str1));//set str1
|
|||
offset +=sizeof(str1); |
|||
memcpy( &buff[offset], &set_packet_bytes[117], 4); |
|||
offset += 4; |
|||
memcpy( &buff[offset], &str2[0], sizeof(str2));//set str2
|
|||
offset += sizeof(str2); |
|||
memcpy( &buff[offset], &set_packet_bytes[117], 4); |
|||
offset += 4; |
|||
memcpy( &buff[offset], &str3[0], sizeof(str3));//set str3
|
|||
offset += sizeof(str3); |
|||
memcpy( &buff[offset], &set_packet_bytes[127], 185); |
|||
offset += 38; |
|||
memcpy( &buff[offset], "00000000", 8 );//BG color
|
|||
offset += 41; |
|||
memcpy( &buff[offset], "F0FFFFFF", 8 );//FG color
|
|||
//Serial.write(buff);//debug
|
|||
client.write(buff); |
|||
client.close(); |
|||
|
File diff suppressed because it is too large
@ -1,12 +1,12 @@ |
|||
FROM python:slim |
|||
|
|||
RUN apt-get update && \ |
|||
apt-get upgrade -y && \ |
|||
apt-get install -y \ |
|||
ffmpeg |
|||
|
|||
WORKDIR /app |
|||
|
|||
COPY . . |
|||
|
|||
CMD [ "python3", "./download-local-files.py"] |
|||
FROM python:slim |
|||
|
|||
RUN apt-get update && \ |
|||
apt-get upgrade -y && \ |
|||
apt-get install -y \ |
|||
ffmpeg |
|||
|
|||
WORKDIR /app |
|||
|
|||
COPY . . |
|||
|
|||
CMD [ "python3", "./download-local-files.py"] |
|||
|
@ -1,21 +1,21 @@ |
|||
MIT License |
|||
|
|||
Copyright (c) 2017 Eliot Kent Woodrich |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
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 |
|||
SOFTWARE. |
|||
MIT License |
|||
|
|||
Copyright (c) 2017 Eliot Kent Woodrich |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
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 |
|||
SOFTWARE. |
|||
|
@ -1,94 +1,94 @@ |
|||
from time import sleep, monotonic |
|||
from dvrip import DVRIPCam, SomethingIsWrongWithCamera |
|||
from pathlib import Path |
|||
import logging |
|||
|
|||
|
|||
class NVR: |
|||
nvr = None |
|||
logger = None |
|||
|
|||
def __init__(self, host_ip, user, password, logger): |
|||
self.logger = logger |
|||
self.nvr = DVRIPCam( |
|||
host_ip, |
|||
user=user, |
|||
password=password, |
|||
) |
|||
if logger.level <= logging.DEBUG: |
|||
self.nvr.debug() |
|||
|
|||
def login(self): |
|||
try: |
|||
self.logger.info(f"Connecting to NVR...") |
|||
self.nvr.login() |
|||
self.logger.info("Successfuly connected to NVR.") |
|||
return |
|||
except SomethingIsWrongWithCamera: |
|||
self.logger.error("Can't connect to NVR") |
|||
self.nvr.close() |
|||
|
|||
def logout(self): |
|||
self.nvr.close() |
|||
|
|||
def get_channel_statuses(self): |
|||
channel_statuses = self.nvr.get_channel_statuses() |
|||
if 'Ret' in channel_statuses: |
|||
return None |
|||
|
|||
channel_titles = self.nvr.get_channel_titles() |
|||
if 'Ret' in channel_titles: |
|||
return None |
|||
|
|||
for i in range(min(len(channel_statuses), len(channel_titles))): |
|||
channel_statuses[i]['Title'] = channel_titles[i] |
|||
channel_statuses[i]['Channel'] = i |
|||
|
|||
return [c for c in channel_statuses if c['Status'] != ''] |
|||
|
|||
def get_local_files(self, channel, start, end, filetype): |
|||
return self.nvr.list_local_files(start, end, filetype, channel) |
|||
|
|||
def generateTargetFileName(self, filename): |
|||
# 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 |
|||
filenameSplit = filename.replace("][", "/").replace("[", "/").replace("]", "/").split("/") |
|||
return f"{filenameSplit[3]}_{filenameSplit[2]}_{filenameSplit[4]}{filenameSplit[-1]}" |
|||
|
|||
def save_files(self, download_dir, files): |
|||
self.logger.info(f"Files downloading: start") |
|||
|
|||
size_to_download = sum(int(f['FileLength'], 0) for f in files) |
|||
|
|||
for file in files: |
|||
target_file_name = self.generateTargetFileName(file["FileName"]) |
|||
target_file_path = f"{download_dir}/{target_file_name}" |
|||
|
|||
size = int(file['FileLength'], 0) |
|||
size_to_download -= size |
|||
|
|||
if Path(f"{target_file_path}").is_file(): |
|||
self.logger.info(f" {target_file_name} file already exists, skipping download") |
|||
continue |
|||
|
|||
self.logger.info(f" {target_file_name} [{size/1024:.1f} MBytes] downloading...") |
|||
time_dl = monotonic() |
|||
self.nvr.download_file( |
|||
file["BeginTime"], file["EndTime"], file["FileName"], target_file_path |
|||
) |
|||
time_dl = monotonic() - 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"Files downloading: done") |
|||
|
|||
def list_files(self, files): |
|||
self.logger.info(f"Files listing: start") |
|||
|
|||
for file in files: |
|||
target_file_name = self.generateTargetFileName(file["FileName"]) |
|||
|
|||
size = int(file['FileLength'], 0) |
|||
self.logger.info(f" {target_file_name} [{size/1024:.1f} MBytes]") |
|||
|
|||
self.logger.info(f"Files listing: end") |
|||
from time import sleep, monotonic |
|||
from dvrip import DVRIPCam, SomethingIsWrongWithCamera |
|||
from pathlib import Path |
|||
import logging |
|||
|
|||
|
|||
class NVR: |
|||
nvr = None |
|||
logger = None |
|||
|
|||
def __init__(self, host_ip, user, password, logger): |
|||
self.logger = logger |
|||
self.nvr = DVRIPCam( |
|||
host_ip, |
|||
user=user, |
|||
password=password, |
|||
) |
|||
if logger.level <= logging.DEBUG: |
|||
self.nvr.debug() |
|||
|
|||
def login(self): |
|||
try: |
|||
self.logger.info(f"Connecting to NVR...") |
|||
self.nvr.login() |
|||
self.logger.info("Successfuly connected to NVR.") |
|||
return |
|||
except SomethingIsWrongWithCamera: |
|||
self.logger.error("Can't connect to NVR") |
|||
self.nvr.close() |
|||
|
|||
def logout(self): |
|||
self.nvr.close() |
|||
|
|||
def get_channel_statuses(self): |
|||
channel_statuses = self.nvr.get_channel_statuses() |
|||
if 'Ret' in channel_statuses: |
|||
return None |
|||
|
|||
channel_titles = self.nvr.get_channel_titles() |
|||
if 'Ret' in channel_titles: |
|||
return None |
|||
|
|||
for i in range(min(len(channel_statuses), len(channel_titles))): |
|||
channel_statuses[i]['Title'] = channel_titles[i] |
|||
channel_statuses[i]['Channel'] = i |
|||
|
|||
return [c for c in channel_statuses if c['Status'] != ''] |
|||
|
|||
def get_local_files(self, channel, start, end, filetype): |
|||
return self.nvr.list_local_files(start, end, filetype, channel) |
|||
|
|||
def generateTargetFileName(self, filename): |
|||
# 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 |
|||
filenameSplit = filename.replace("][", "/").replace("[", "/").replace("]", "/").split("/") |
|||
return f"{filenameSplit[3]}_{filenameSplit[2]}_{filenameSplit[4]}{filenameSplit[-1]}" |
|||
|
|||
def save_files(self, download_dir, files): |
|||
self.logger.info(f"Files downloading: start") |
|||
|
|||
size_to_download = sum(int(f['FileLength'], 0) for f in files) |
|||
|
|||
for file in files: |
|||
target_file_name = self.generateTargetFileName(file["FileName"]) |
|||
target_file_path = f"{download_dir}/{target_file_name}" |
|||
|
|||
size = int(file['FileLength'], 0) |
|||
size_to_download -= size |
|||
|
|||
if Path(f"{target_file_path}").is_file(): |
|||
self.logger.info(f" {target_file_name} file already exists, skipping download") |
|||
continue |
|||
|
|||
self.logger.info(f" {target_file_name} [{size/1024:.1f} MBytes] downloading...") |
|||
time_dl = monotonic() |
|||
self.nvr.download_file( |
|||
file["BeginTime"], file["EndTime"], file["FileName"], target_file_path |
|||
) |
|||
time_dl = monotonic() - 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"Files downloading: done") |
|||
|
|||
def list_files(self, files): |
|||
self.logger.info(f"Files listing: start") |
|||
|
|||
for file in files: |
|||
target_file_name = self.generateTargetFileName(file["FileName"]) |
|||
|
|||
size = int(file['FileLength'], 0) |
|||
self.logger.info(f" {target_file_name} [{size/1024:.1f} MBytes]") |
|||
|
|||
self.logger.info(f"Files listing: end") |
|||
|
@ -1,11 +1,11 @@ |
|||
{ |
|||
"host_ip": "10.0.0.8", |
|||
"user": "admin", |
|||
"password": "mypassword", |
|||
"channel": 0, |
|||
"download_dir": "./download", |
|||
"start": "2023-11-19 6:22:34", |
|||
"end": "2023-11-19 6:23:09", |
|||
"just_list_files": false, |
|||
"log_level": "INFO" |
|||
} |
|||
{ |
|||
"host_ip": "10.0.0.8", |
|||
"user": "admin", |
|||
"password": "mypassword", |
|||
"channel": 0, |
|||
"download_dir": "./download", |
|||
"start": "2023-11-19 6:22:34", |
|||
"end": "2023-11-19 6:23:09", |
|||
"just_list_files": false, |
|||
"log_level": "INFO" |
|||
} |
|||
|
@ -1,89 +1,89 @@ |
|||
from pathlib import Path |
|||
import os |
|||
import json |
|||
import logging |
|||
from collections import namedtuple |
|||
from NVR import NVR |
|||
|
|||
|
|||
def init_logger(log_level): |
|||
logger = logging.getLogger(__name__) |
|||
logger.setLevel(log_level) |
|||
ch = logging.StreamHandler() |
|||
formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") |
|||
ch.setFormatter(formatter) |
|||
logger.addHandler(ch) |
|||
return logger |
|||
|
|||
|
|||
def load_config(): |
|||
def config_decoder(config_dict): |
|||
return namedtuple("X", config_dict.keys())(*config_dict.values()) |
|||
|
|||
config_path = os.environ.get("NVRVIDEODOWNLOADER_CFG") |
|||
|
|||
if config_path is None or not Path(config_path).exists(): |
|||
config_path = "NVRVideoDownloader.json" |
|||
|
|||
if Path(config_path).exists(): |
|||
with open(config_path, "r") as file: |
|||
return json.loads(file.read(), object_hook=config_decoder) |
|||
|
|||
return { |
|||
"host_ip": os.environ.get("IP_ADDRESS"), |
|||
"user": os.environ.get("USER"), |
|||
"password": os.environ.get("PASSWORD"), |
|||
"channel": os.environ.get("CHANNEL"), |
|||
"download_dir": os.environ.get("DOWNLOAD_DIR"), |
|||
"start": os.environ.get("START"), |
|||
"end": os.environ.get("END"), |
|||
"just_list_files": os.environ.get("DUMP_LOCAL_FILES").lower() in ["true", "1", "y", "yes"], |
|||
"log_level": "INFO" |
|||
} |
|||
|
|||
|
|||
def main(): |
|||
config = load_config() |
|||
logger = init_logger(config.log_level) |
|||
channel = config.channel; |
|||
start = config.start |
|||
end = config.end |
|||
just_list_files = config.just_list_files; |
|||
|
|||
nvr = NVR(config.host_ip, config.user, config.password, logger) |
|||
|
|||
try: |
|||
nvr.login() |
|||
|
|||
channel_statuses = nvr.get_channel_statuses() |
|||
if channel_statuses: |
|||
channel_statuses_short = [{f"{c['Channel']}:{c['Title']}({c['ChnName']})"} |
|||
for c in channel_statuses if c['Status'] != 'NoConfig'] |
|||
logger.info(f"Configured channels in NVR: {channel_statuses_short}") |
|||
|
|||
videos = nvr.get_local_files(channel, start, end, "h264") |
|||
if 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") |
|||
Path(config.download_dir).parent.mkdir( |
|||
parents=True, exist_ok=True |
|||
) |
|||
if just_list_files: |
|||
nvr.list_files(videos) |
|||
else: |
|||
nvr.save_files(config.download_dir, videos) |
|||
else: |
|||
logger.info(f"No video files found") |
|||
|
|||
nvr.logout() |
|||
except ConnectionRefusedError: |
|||
logger.error(f"Connection can't be established or got disconnected") |
|||
except TypeError as e: |
|||
print(e) |
|||
logger.error(f"Error while downloading a file") |
|||
except KeyError: |
|||
logger.error(f"Error while getting the file list") |
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
main() |
|||
from pathlib import Path |
|||
import os |
|||
import json |
|||
import logging |
|||
from collections import namedtuple |
|||
from NVR import NVR |
|||
|
|||
|
|||
def init_logger(log_level): |
|||
logger = logging.getLogger(__name__) |
|||
logger.setLevel(log_level) |
|||
ch = logging.StreamHandler() |
|||
formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") |
|||
ch.setFormatter(formatter) |
|||
logger.addHandler(ch) |
|||
return logger |
|||
|
|||
|
|||
def load_config(): |
|||
def config_decoder(config_dict): |
|||
return namedtuple("X", config_dict.keys())(*config_dict.values()) |
|||
|
|||
config_path = os.environ.get("NVRVIDEODOWNLOADER_CFG") |
|||
|
|||
if config_path is None or not Path(config_path).exists(): |
|||
config_path = "NVRVideoDownloader.json" |
|||
|
|||
if Path(config_path).exists(): |
|||
with open(config_path, "r") as file: |
|||
return json.loads(file.read(), object_hook=config_decoder) |
|||
|
|||
return { |
|||
"host_ip": os.environ.get("IP_ADDRESS"), |
|||
"user": os.environ.get("USER"), |
|||
"password": os.environ.get("PASSWORD"), |
|||
"channel": os.environ.get("CHANNEL"), |
|||
"download_dir": os.environ.get("DOWNLOAD_DIR"), |
|||
"start": os.environ.get("START"), |
|||
"end": os.environ.get("END"), |
|||
"just_list_files": os.environ.get("DUMP_LOCAL_FILES").lower() in ["true", "1", "y", "yes"], |
|||
"log_level": "INFO" |
|||
} |
|||
|
|||
|
|||
def main(): |
|||
config = load_config() |
|||
logger = init_logger(config.log_level) |
|||
channel = config.channel; |
|||
start = config.start |
|||
end = config.end |
|||
just_list_files = config.just_list_files; |
|||
|
|||
nvr = NVR(config.host_ip, config.user, config.password, logger) |
|||
|
|||
try: |
|||
nvr.login() |
|||
|
|||
channel_statuses = nvr.get_channel_statuses() |
|||
if channel_statuses: |
|||
channel_statuses_short = [{f"{c['Channel']}:{c['Title']}({c['ChnName']})"} |
|||
for c in channel_statuses if c['Status'] != 'NoConfig'] |
|||
logger.info(f"Configured channels in NVR: {channel_statuses_short}") |
|||
|
|||
videos = nvr.get_local_files(channel, start, end, "h264") |
|||
if 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") |
|||
Path(config.download_dir).parent.mkdir( |
|||
parents=True, exist_ok=True |
|||
) |
|||
if just_list_files: |
|||
nvr.list_files(videos) |
|||
else: |
|||
nvr.save_files(config.download_dir, videos) |
|||
else: |
|||
logger.info(f"No video files found") |
|||
|
|||
nvr.logout() |
|||
except ConnectionRefusedError: |
|||
logger.error(f"Connection can't be established or got disconnected") |
|||
except TypeError as e: |
|||
print(e) |
|||
logger.error(f"Error while downloading a file") |
|||
except KeyError: |
|||
logger.error(f"Error while getting the file list") |
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
main() |
|||
|
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -1,46 +1,46 @@ |
|||
#!/usr/bin/env python |
|||
# -*- coding: UTF-8 -*- |
|||
import sys |
|||
from dvrip import DVRIPCam |
|||
from time import sleep |
|||
import json |
|||
|
|||
host_ip = "192.168.0.100" |
|||
if len(sys.argv) > 1: |
|||
host_ip = str(sys.argv[1]) |
|||
|
|||
cam = DVRIPCam(host_ip, user="admin", password="46216") |
|||
|
|||
if cam.login(): |
|||
print("Success! Connected to " + host_ip) |
|||
else: |
|||
print("Failure. Could not connect.") |
|||
|
|||
info = cam.get_info("fVideo.OSDInfo") |
|||
print(json.dumps(info, ensure_ascii=False)) |
|||
info["OSDInfo"][0]["Info"] = [u"Тест00", "Test01", "Test02"] |
|||
# info["OSDInfo"][0]["Info"][1] = "" |
|||
# info["OSDInfo"][0]["Info"][2] = "" |
|||
# info["OSDInfo"][0]["Info"][3] = "Test3" |
|||
info["OSDInfo"][0]["OSDInfoWidget"]["EncodeBlend"] = True |
|||
info["OSDInfo"][0]["OSDInfoWidget"]["PreviewBlend"] = True |
|||
# info["OSDInfo"][0]["OSDInfoWidget"]["RelativePos"] = [6144,6144,8192,8192] |
|||
cam.set_info("fVideo.OSDInfo", info) |
|||
# enc_info = cam.get_info("Simplify.Encode") |
|||
# Alarm example |
|||
def alarm(content, ids): |
|||
print(content) |
|||
|
|||
|
|||
cam.setAlarm(alarm) |
|||
cam.alarmStart() |
|||
# cam.get_encode_info() |
|||
# sleep(1) |
|||
# cam.get_camera_info() |
|||
# sleep(1) |
|||
|
|||
# enc_info[0]['ExtraFormat']['Video']['FPS'] = 20 |
|||
# cam.set_info("Simplify.Encode", enc_info) |
|||
# sleep(2) |
|||
# print(cam.get_info("Simplify.Encode")) |
|||
# cam.close() |
|||
#!/usr/bin/env python |
|||
# -*- coding: UTF-8 -*- |
|||
import sys |
|||
from dvrip import DVRIPCam |
|||
from time import sleep |
|||
import json |
|||
|
|||
host_ip = "192.168.0.100" |
|||
if len(sys.argv) > 1: |
|||
host_ip = str(sys.argv[1]) |
|||
|
|||
cam = DVRIPCam(host_ip, user="admin", password="46216") |
|||
|
|||
if cam.login(): |
|||
print("Success! Connected to " + host_ip) |
|||
else: |
|||
print("Failure. Could not connect.") |
|||
|
|||
info = cam.get_info("fVideo.OSDInfo") |
|||
print(json.dumps(info, ensure_ascii=False)) |
|||
info["OSDInfo"][0]["Info"] = [u"Тест00", "Test01", "Test02"] |
|||
# info["OSDInfo"][0]["Info"][1] = "" |
|||
# info["OSDInfo"][0]["Info"][2] = "" |
|||
# info["OSDInfo"][0]["Info"][3] = "Test3" |
|||
info["OSDInfo"][0]["OSDInfoWidget"]["EncodeBlend"] = True |
|||
info["OSDInfo"][0]["OSDInfoWidget"]["PreviewBlend"] = True |
|||
# info["OSDInfo"][0]["OSDInfoWidget"]["RelativePos"] = [6144,6144,8192,8192] |
|||
cam.set_info("fVideo.OSDInfo", info) |
|||
# enc_info = cam.get_info("Simplify.Encode") |
|||
# Alarm example |
|||
def alarm(content, ids): |
|||
print(content) |
|||
|
|||
|
|||
cam.setAlarm(alarm) |
|||
cam.alarmStart() |
|||
# cam.get_encode_info() |
|||
# sleep(1) |
|||
# cam.get_camera_info() |
|||
# sleep(1) |
|||
|
|||
# enc_info[0]['ExtraFormat']['Video']['FPS'] = 20 |
|||
# cam.set_info("Simplify.Encode", enc_info) |
|||
# sleep(2) |
|||
# print(cam.get_info("Simplify.Encode")) |
|||
# cam.close() |
|||
|
@ -1,127 +1,127 @@ |
|||
from pathlib import Path |
|||
from time import sleep |
|||
import os |
|||
import json |
|||
import logging |
|||
from collections import namedtuple |
|||
from solarcam import SolarCam |
|||
|
|||
|
|||
def init_logger(): |
|||
logger = logging.getLogger(__name__) |
|||
logger.setLevel(logging.DEBUG) |
|||
ch = logging.StreamHandler() |
|||
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") |
|||
ch.setFormatter(formatter) |
|||
logger.addHandler(ch) |
|||
return logger |
|||
|
|||
|
|||
def load_config(): |
|||
def config_decoder(config_dict): |
|||
return namedtuple("X", config_dict.keys())(*config_dict.values()) |
|||
|
|||
config_path = os.environ.get("CONFIG_PATH") |
|||
if Path(config_path).exists(): |
|||
with open(config_path, "r") as file: |
|||
return json.loads(file.read(), object_hook=config_decoder) |
|||
|
|||
return { |
|||
"host_ip": os.environ.get("IP_ADDRESS"), |
|||
"user": os.environ.get("USER"), |
|||
"password": os.environ.get("PASSWORD"), |
|||
"target_filetype_video": os.environ.get("target_filetype_video"), |
|||
"download_dir_video": os.environ.get("DOWNLOAD_DIR_VIDEO"), |
|||
"download_dir_picture": os.environ.get("DOWNLOAD_DIR_PICTURE"), |
|||
"start": os.environ.get("START"), |
|||
"end": os.environ.get("END"), |
|||
"blacklist_path": os.environ.get("BLACKLIST_PATH"), |
|||
"cooldown": int(os.environ.get("COOLDOWN")), |
|||
"dump_local_files": ( |
|||
os.environ.get("DUMP_LOCAL_FILES").lower() in ["true", "1", "y", "yes"] |
|||
), |
|||
} |
|||
|
|||
|
|||
def main(): |
|||
logger = init_logger() |
|||
config = load_config() |
|||
start = config.start |
|||
end = config.end |
|||
cooldown = config.cooldown |
|||
|
|||
blacklist = None |
|||
if Path(config.blacklist_path).exists(): |
|||
with open(config.blacklist_path, "r") as file: |
|||
blacklist = [line.rstrip() for line in file] |
|||
|
|||
while True: |
|||
solarCam = SolarCam(config.host_ip, config.user, config.password, logger) |
|||
|
|||
try: |
|||
solarCam.login() |
|||
|
|||
battery = solarCam.get_battery() |
|||
logger.debug(f"Current battery status: {battery}") |
|||
storage = solarCam.get_storage()[0] |
|||
logger.debug(f"Current storage status: {storage}") |
|||
|
|||
logger.debug(f"Syncing time...") |
|||
solarCam.set_time() # setting it to system clock |
|||
logger.debug(f"Camera time is now {solarCam.get_time()}") |
|||
|
|||
sleep(5) # sleep some seconds so camera can get ready |
|||
|
|||
pics = solarCam.get_local_files(start, end, "jpg") |
|||
|
|||
if pics: |
|||
Path(config.download_dir_picture).parent.mkdir( |
|||
parents=True, exist_ok=True |
|||
) |
|||
solarCam.save_files( |
|||
config.download_dir_picture, pics, blacklist=blacklist |
|||
) |
|||
|
|||
videos = solarCam.get_local_files(start, end, "h264") |
|||
if videos: |
|||
Path(config.download_dir_video).parent.mkdir( |
|||
parents=True, exist_ok=True |
|||
) |
|||
solarCam.save_files( |
|||
config.download_dir_video, |
|||
videos, |
|||
blacklist=blacklist, |
|||
target_filetype=config.target_filetype_video, |
|||
) |
|||
|
|||
if config.dump_local_files: |
|||
logger.debug(f"Dumping local files...") |
|||
solarCam.dump_local_files( |
|||
videos, |
|||
config.blacklist_path, |
|||
config.download_dir_video, |
|||
target_filetype=config.target_filetype_video, |
|||
) |
|||
solarCam.dump_local_files( |
|||
pics, config.blacklist_path, config.download_dir_picture |
|||
) |
|||
|
|||
solarCam.logout() |
|||
except ConnectionRefusedError: |
|||
logger.debug(f"Connection could not be established or got disconnected") |
|||
except TypeError as e: |
|||
print(e) |
|||
logger.debug(f"Error while downloading a file") |
|||
except KeyError: |
|||
logger.debug(f"Error while getting the file list") |
|||
logger.debug(f"Sleeping for {cooldown} seconds...") |
|||
sleep(cooldown) |
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
main() |
|||
|
|||
# todo add flask api for moving cam |
|||
# todo show current stream |
|||
# todo show battery on webinterface and write it to mqtt topic |
|||
# todo change camera name |
|||
from pathlib import Path |
|||
from time import sleep |
|||
import os |
|||
import json |
|||
import logging |
|||
from collections import namedtuple |
|||
from solarcam import SolarCam |
|||
|
|||
|
|||
def init_logger(): |
|||
logger = logging.getLogger(__name__) |
|||
logger.setLevel(logging.DEBUG) |
|||
ch = logging.StreamHandler() |
|||
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") |
|||
ch.setFormatter(formatter) |
|||
logger.addHandler(ch) |
|||
return logger |
|||
|
|||
|
|||
def load_config(): |
|||
def config_decoder(config_dict): |
|||
return namedtuple("X", config_dict.keys())(*config_dict.values()) |
|||
|
|||
config_path = os.environ.get("CONFIG_PATH") |
|||
if Path(config_path).exists(): |
|||
with open(config_path, "r") as file: |
|||
return json.loads(file.read(), object_hook=config_decoder) |
|||
|
|||
return { |
|||
"host_ip": os.environ.get("IP_ADDRESS"), |
|||
"user": os.environ.get("USER"), |
|||
"password": os.environ.get("PASSWORD"), |
|||
"target_filetype_video": os.environ.get("target_filetype_video"), |
|||
"download_dir_video": os.environ.get("DOWNLOAD_DIR_VIDEO"), |
|||
"download_dir_picture": os.environ.get("DOWNLOAD_DIR_PICTURE"), |
|||
"start": os.environ.get("START"), |
|||
"end": os.environ.get("END"), |
|||
"blacklist_path": os.environ.get("BLACKLIST_PATH"), |
|||
"cooldown": int(os.environ.get("COOLDOWN")), |
|||
"dump_local_files": ( |
|||
os.environ.get("DUMP_LOCAL_FILES").lower() in ["true", "1", "y", "yes"] |
|||
), |
|||
} |
|||
|
|||
|
|||
def main(): |
|||
logger = init_logger() |
|||
config = load_config() |
|||
start = config.start |
|||
end = config.end |
|||
cooldown = config.cooldown |
|||
|
|||
blacklist = None |
|||
if Path(config.blacklist_path).exists(): |
|||
with open(config.blacklist_path, "r") as file: |
|||
blacklist = [line.rstrip() for line in file] |
|||
|
|||
while True: |
|||
solarCam = SolarCam(config.host_ip, config.user, config.password, logger) |
|||
|
|||
try: |
|||
solarCam.login() |
|||
|
|||
battery = solarCam.get_battery() |
|||
logger.debug(f"Current battery status: {battery}") |
|||
storage = solarCam.get_storage()[0] |
|||
logger.debug(f"Current storage status: {storage}") |
|||
|
|||
logger.debug(f"Syncing time...") |
|||
solarCam.set_time() # setting it to system clock |
|||
logger.debug(f"Camera time is now {solarCam.get_time()}") |
|||
|
|||
sleep(5) # sleep some seconds so camera can get ready |
|||
|
|||
pics = solarCam.get_local_files(start, end, "jpg") |
|||
|
|||
if pics: |
|||
Path(config.download_dir_picture).parent.mkdir( |
|||
parents=True, exist_ok=True |
|||
) |
|||
solarCam.save_files( |
|||
config.download_dir_picture, pics, blacklist=blacklist |
|||
) |
|||
|
|||
videos = solarCam.get_local_files(start, end, "h264") |
|||
if videos: |
|||
Path(config.download_dir_video).parent.mkdir( |
|||
parents=True, exist_ok=True |
|||
) |
|||
solarCam.save_files( |
|||
config.download_dir_video, |
|||
videos, |
|||
blacklist=blacklist, |
|||
target_filetype=config.target_filetype_video, |
|||
) |
|||
|
|||
if config.dump_local_files: |
|||
logger.debug(f"Dumping local files...") |
|||
solarCam.dump_local_files( |
|||
videos, |
|||
config.blacklist_path, |
|||
config.download_dir_video, |
|||
target_filetype=config.target_filetype_video, |
|||
) |
|||
solarCam.dump_local_files( |
|||
pics, config.blacklist_path, config.download_dir_picture |
|||
) |
|||
|
|||
solarCam.logout() |
|||
except ConnectionRefusedError: |
|||
logger.debug(f"Connection could not be established or got disconnected") |
|||
except TypeError as e: |
|||
print(e) |
|||
logger.debug(f"Error while downloading a file") |
|||
except KeyError: |
|||
logger.debug(f"Error while getting the file list") |
|||
logger.debug(f"Sleeping for {cooldown} seconds...") |
|||
sleep(cooldown) |
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
main() |
|||
|
|||
# todo add flask api for moving cam |
|||
# todo show current stream |
|||
# todo show battery on webinterface and write it to mqtt topic |
|||
# todo change camera name |
|||
|
File diff suppressed because it is too large
@ -1,17 +1,17 @@ |
|||
FROM python:3.10-slim-buster |
|||
|
|||
RUN apt-get update && \ |
|||
apt-get upgrade -y && \ |
|||
apt-get install -y \ |
|||
git \ |
|||
curl |
|||
|
|||
WORKDIR /app |
|||
|
|||
COPY . . |
|||
|
|||
RUN pip3 install -r requirements.txt |
|||
|
|||
EXPOSE 8888 |
|||
|
|||
CMD [ "python3", "./app.py"] |
|||
FROM python:3.10-slim-buster |
|||
|
|||
RUN apt-get update && \ |
|||
apt-get upgrade -y && \ |
|||
apt-get install -y \ |
|||
git \ |
|||
curl |
|||
|
|||
WORKDIR /app |
|||
|
|||
COPY . . |
|||
|
|||
RUN pip3 install -r requirements.txt |
|||
|
|||
EXPOSE 8888 |
|||
|
|||
CMD [ "python3", "./app.py"] |
|||
|
@ -1,15 +1,15 @@ |
|||
### SocketIO example |
|||
|
|||
Build image |
|||
```bash |
|||
docker build -t video-stream . |
|||
``` |
|||
|
|||
Run container |
|||
```bash |
|||
docker run -d \ |
|||
--restart always \ |
|||
--network host \ |
|||
--name video-stream \ |
|||
video-stream |
|||
``` |
|||
### SocketIO example |
|||
|
|||
Build image |
|||
```bash |
|||
docker build -t video-stream . |
|||
``` |
|||
|
|||
Run container |
|||
```bash |
|||
docker run -d \ |
|||
--restart always \ |
|||
--network host \ |
|||
--name video-stream \ |
|||
video-stream |
|||
``` |
|||
|
@ -1,107 +1,107 @@ |
|||
import socketio |
|||
from asyncio_dvrip import DVRIPCam |
|||
from aiohttp import web |
|||
import asyncio |
|||
import signal |
|||
import traceback |
|||
import base64 |
|||
|
|||
loop = asyncio.get_event_loop() |
|||
queue = asyncio.Queue() |
|||
|
|||
# socket clients |
|||
clients = [] |
|||
sio = socketio.AsyncServer() |
|||
app = web.Application() |
|||
sio.attach(app) |
|||
|
|||
@sio.event |
|||
def connect(sid, environ): |
|||
print("connect ", sid) |
|||
clients.append(sid) |
|||
|
|||
@sio.event |
|||
def my_message(sid, data): |
|||
print('message ', data) |
|||
|
|||
@sio.event |
|||
def disconnect(sid): |
|||
print('disconnect ', sid) |
|||
clients.remove(sid) |
|||
|
|||
def stop(loop): |
|||
loop.remove_signal_handler(signal.SIGTERM) |
|||
tasks = asyncio.gather(*asyncio.Task.all_tasks(loop=loop), loop=loop, return_exceptions=True) |
|||
tasks.add_done_callback(lambda t: loop.stop()) |
|||
tasks.cancel() |
|||
|
|||
async def stream(loop, queue): |
|||
cam = DVRIPCam("192.168.0.100", port=34567, user="admin", password="") |
|||
# login |
|||
if not await cam.login(loop): |
|||
raise Exception("Can't open cam") |
|||
|
|||
try: |
|||
await cam.start_monitor(lambda frame, meta, user: queue.put_nowait(frame), stream="Main") |
|||
except Exception as err: |
|||
msg = ''.join(traceback.format_tb(err.__traceback__) + [str(err)]) |
|||
print(msg) |
|||
finally: |
|||
cam.stop_monitor() |
|||
cam.close() |
|||
|
|||
async def process(queue, lock): |
|||
while True: |
|||
frame = await queue.get() |
|||
|
|||
if frame: |
|||
await lock.acquire() |
|||
try: |
|||
for sid in clients: |
|||
await sio.emit('message', {'data': base64.b64encode(frame).decode("utf-8")}, room=sid) |
|||
finally: |
|||
lock.release() |
|||
|
|||
async def worker(loop, queue, lock): |
|||
task = None |
|||
|
|||
# infinyty loop |
|||
while True: |
|||
await lock.acquire() |
|||
|
|||
try: |
|||
# got clients and task not started |
|||
if len(clients) > 0 and task is None: |
|||
# create stream task |
|||
task = loop.create_task(stream(loop, queue)) |
|||
|
|||
# no more clients, neet stop task |
|||
if len(clients) == 0 and task is not None: |
|||
# I don't like this way, maybe someone can do it better |
|||
task.cancel() |
|||
task = None |
|||
await asyncio.sleep(0.1) |
|||
except Exception as err: |
|||
msg = ''.join(traceback.format_tb(err.__traceback__) + [str(err)]) |
|||
print(msg) |
|||
finally: |
|||
lock.release() |
|||
|
|||
if __name__ == '__main__': |
|||
try: |
|||
lock = asyncio.Lock() |
|||
|
|||
# run wb application |
|||
runner = web.AppRunner(app) |
|||
loop.run_until_complete(runner.setup()) |
|||
site = web.TCPSite(runner, host='0.0.0.0', port=8888) |
|||
loop.run_until_complete(site.start()) |
|||
|
|||
# run worker |
|||
loop.create_task(worker(loop, queue, lock)) |
|||
loop.create_task(process(queue, lock)) |
|||
|
|||
# wait stop |
|||
loop.run_forever() |
|||
except: |
|||
stop(loop) |
|||
import socketio |
|||
from asyncio_dvrip import DVRIPCam |
|||
from aiohttp import web |
|||
import asyncio |
|||
import signal |
|||
import traceback |
|||
import base64 |
|||
|
|||
loop = asyncio.get_event_loop() |
|||
queue = asyncio.Queue() |
|||
|
|||
# socket clients |
|||
clients = [] |
|||
sio = socketio.AsyncServer() |
|||
app = web.Application() |
|||
sio.attach(app) |
|||
|
|||
@sio.event |
|||
def connect(sid, environ): |
|||
print("connect ", sid) |
|||
clients.append(sid) |
|||
|
|||
@sio.event |
|||
def my_message(sid, data): |
|||
print('message ', data) |
|||
|
|||
@sio.event |
|||
def disconnect(sid): |
|||
print('disconnect ', sid) |
|||
clients.remove(sid) |
|||
|
|||
def stop(loop): |
|||
loop.remove_signal_handler(signal.SIGTERM) |
|||
tasks = asyncio.gather(*asyncio.Task.all_tasks(loop=loop), loop=loop, return_exceptions=True) |
|||
tasks.add_done_callback(lambda t: loop.stop()) |
|||
tasks.cancel() |
|||
|
|||
async def stream(loop, queue): |
|||
cam = DVRIPCam("192.168.0.100", port=34567, user="admin", password="") |
|||
# login |
|||
if not await cam.login(loop): |
|||
raise Exception("Can't open cam") |
|||
|
|||
try: |
|||
await cam.start_monitor(lambda frame, meta, user: queue.put_nowait(frame), stream="Main") |
|||
except Exception as err: |
|||
msg = ''.join(traceback.format_tb(err.__traceback__) + [str(err)]) |
|||
print(msg) |
|||
finally: |
|||
cam.stop_monitor() |
|||
cam.close() |
|||
|
|||
async def process(queue, lock): |
|||
while True: |
|||
frame = await queue.get() |
|||
|
|||
if frame: |
|||
await lock.acquire() |
|||
try: |
|||
for sid in clients: |
|||
await sio.emit('message', {'data': base64.b64encode(frame).decode("utf-8")}, room=sid) |
|||
finally: |
|||
lock.release() |
|||
|
|||
async def worker(loop, queue, lock): |
|||
task = None |
|||
|
|||
# infinyty loop |
|||
while True: |
|||
await lock.acquire() |
|||
|
|||
try: |
|||
# got clients and task not started |
|||
if len(clients) > 0 and task is None: |
|||
# create stream task |
|||
task = loop.create_task(stream(loop, queue)) |
|||
|
|||
# no more clients, neet stop task |
|||
if len(clients) == 0 and task is not None: |
|||
# I don't like this way, maybe someone can do it better |
|||
task.cancel() |
|||
task = None |
|||
await asyncio.sleep(0.1) |
|||
except Exception as err: |
|||
msg = ''.join(traceback.format_tb(err.__traceback__) + [str(err)]) |
|||
print(msg) |
|||
finally: |
|||
lock.release() |
|||
|
|||
if __name__ == '__main__': |
|||
try: |
|||
lock = asyncio.Lock() |
|||
|
|||
# run wb application |
|||
runner = web.AppRunner(app) |
|||
loop.run_until_complete(runner.setup()) |
|||
site = web.TCPSite(runner, host='0.0.0.0', port=8888) |
|||
loop.run_until_complete(site.start()) |
|||
|
|||
# run worker |
|||
loop.create_task(worker(loop, queue, lock)) |
|||
loop.create_task(process(queue, lock)) |
|||
|
|||
# wait stop |
|||
loop.run_forever() |
|||
except: |
|||
stop(loop) |
|||
|
@ -1,18 +1,18 @@ |
|||
import socketio |
|||
|
|||
# standard Python |
|||
sio = socketio.Client() |
|||
|
|||
@sio.event |
|||
def connect(): |
|||
print("I'm connected!") |
|||
|
|||
@sio.event |
|||
def connect_error(): |
|||
print("The connection failed!") |
|||
|
|||
@sio.on('message') |
|||
def on_message(data): |
|||
print('frame', data) |
|||
|
|||
import socketio |
|||
|
|||
# standard Python |
|||
sio = socketio.Client() |
|||
|
|||
@sio.event |
|||
def connect(): |
|||
print("I'm connected!") |
|||
|
|||
@sio.event |
|||
def connect_error(): |
|||
print("The connection failed!") |
|||
|
|||
@sio.on('message') |
|||
def on_message(data): |
|||
print('frame', data) |
|||
|
|||
sio.connect('http://localhost:8888') |
@ -1,14 +1,14 @@ |
|||
aiohttp==3.8.5 |
|||
aiosignal==1.3.1 |
|||
async-timeout==4.0.2 |
|||
asyncio==3.4.3 |
|||
attrs==22.1.0 |
|||
bidict==0.22.0 |
|||
charset-normalizer==2.1.1 |
|||
frozenlist==1.3.3 |
|||
idna==3.4 |
|||
multidict==6.0.2 |
|||
python-dvr @ git+https://github.com/NeiroNx/python-dvr@06ff6dc0082767e7c9f23401f828533459f783a4 |
|||
python-engineio==4.3.4 |
|||
python-socketio==5.7.2 |
|||
yarl==1.8.1 |
|||
aiohttp==3.8.5 |
|||
aiosignal==1.3.1 |
|||
async-timeout==4.0.2 |
|||
asyncio==3.4.3 |
|||
attrs==22.1.0 |
|||
bidict==0.22.0 |
|||
charset-normalizer==2.1.1 |
|||
frozenlist==1.3.3 |
|||
idna==3.4 |
|||
multidict==6.0.2 |
|||
python-dvr @ git+https://github.com/NeiroNx/python-dvr@06ff6dc0082767e7c9f23401f828533459f783a4 |
|||
python-engineio==4.3.4 |
|||
python-socketio==5.7.2 |
|||
yarl==1.8.1 |
|||
|
@ -1,121 +1,121 @@ |
|||
#! /usr/bin/python3 |
|||
from dvrip import DVRIPCam, SomethingIsWrongWithCamera |
|||
from signal import signal, SIGINT, SIGTERM |
|||
from sys import argv, stdout, exit |
|||
from datetime import datetime |
|||
from pathlib import Path |
|||
from time import sleep, time |
|||
import logging |
|||
|
|||
baseDir = argv[3] |
|||
retryIn = 5 |
|||
rebootWait = 10 |
|||
camIp = argv[1] |
|||
camName = argv[2] |
|||
cam = None |
|||
isShuttingDown = False |
|||
chunkSize = 600 # new file every 10 minutes |
|||
logFile = baseDir + '/' + camName + '/log.log' |
|||
|
|||
def log(str): |
|||
logging.info(str) |
|||
|
|||
def mkpath(): |
|||
path = baseDir + '/' + camName + "/" + datetime.today().strftime('%Y/%m/%d/%H.%M.%S') |
|||
Path(path).parent.mkdir(parents=True, exist_ok=True) |
|||
return path |
|||
|
|||
def shutDown(): |
|||
global isShuttingDown |
|||
isShuttingDown = True |
|||
log('Shutting down...') |
|||
try: |
|||
cam.stop_monitor() |
|||
close() |
|||
except (RuntimeError, TypeError, NameError, Exception): |
|||
pass |
|||
log('done') |
|||
exit(0) |
|||
|
|||
def handler(signum, b): |
|||
log('Signal ' + str(signum) + ' received') |
|||
shutDown() |
|||
|
|||
signal(SIGINT, handler) |
|||
signal(SIGTERM, handler) |
|||
|
|||
def close(): |
|||
cam.close() |
|||
|
|||
def theActualJob(): |
|||
|
|||
prevtime = 0 |
|||
video = None |
|||
audio = None |
|||
|
|||
def receiver(frame, meta, user): |
|||
nonlocal prevtime, video, audio |
|||
if frame is None: |
|||
log('Empty frame') |
|||
else: |
|||
tn = time() |
|||
if tn - prevtime >= chunkSize: |
|||
if video != None: |
|||
video.close() |
|||
audio.close() |
|||
prevtime = tn |
|||
path = mkpath() |
|||
log('Starting files: ' + path) |
|||
video = open(path + '.video', "wb") |
|||
audio = open(path + '.audio', "wb") |
|||
if 'type' in meta and meta["type"] == "g711a": audio.write(frame) |
|||
elif 'frame' in meta: video.write(frame) |
|||
|
|||
log('Starting to grab streams...') |
|||
cam.start_monitor(receiver) |
|||
|
|||
def syncTime(): |
|||
log('Synching time...') |
|||
cam.set_time() |
|||
log('done') |
|||
|
|||
def jobWrapper(): |
|||
global cam |
|||
log('Logging in to camera ' + camIp + '...') |
|||
cam = DVRIPCam(camIp) |
|||
if cam.login(): |
|||
log('done') |
|||
else: |
|||
raise SomethingIsWrongWithCamera('Cannot login') |
|||
syncTime() |
|||
theActualJob() |
|||
|
|||
def theJob(): |
|||
while True: |
|||
try: |
|||
jobWrapper() |
|||
except (TypeError, ValueError) as err: |
|||
if isShuttingDown: |
|||
exit(0) |
|||
else: |
|||
try: |
|||
log('Error. Attempting to reboot camera...') |
|||
cam.reboot() |
|||
log('Waiting for ' + str(rebootWait) + 's for reboot...') |
|||
sleep(rebootWait) |
|||
except (UnicodeDecodeError, ValueError, TypeError): |
|||
raise SomethingIsWrongWithCamera('Failed to reboot') |
|||
|
|||
def main(): |
|||
Path(logFile).parent.mkdir(parents=True, exist_ok=True) |
|||
logging.basicConfig(filename=logFile, level=logging.INFO, format='[%(asctime)s] %(message)s') |
|||
while True: |
|||
try: |
|||
theJob() |
|||
except SomethingIsWrongWithCamera as err: |
|||
close() |
|||
log(str(err) + '. Waiting for ' + str(retryIn) + ' seconds before trying again...') |
|||
sleep(retryIn) |
|||
|
|||
if __name__ == "__main__": |
|||
#! /usr/bin/python3 |
|||
from dvrip import DVRIPCam, SomethingIsWrongWithCamera |
|||
from signal import signal, SIGINT, SIGTERM |
|||
from sys import argv, stdout, exit |
|||
from datetime import datetime |
|||
from pathlib import Path |
|||
from time import sleep, time |
|||
import logging |
|||
|
|||
baseDir = argv[3] |
|||
retryIn = 5 |
|||
rebootWait = 10 |
|||
camIp = argv[1] |
|||
camName = argv[2] |
|||
cam = None |
|||
isShuttingDown = False |
|||
chunkSize = 600 # new file every 10 minutes |
|||
logFile = baseDir + '/' + camName + '/log.log' |
|||
|
|||
def log(str): |
|||
logging.info(str) |
|||
|
|||
def mkpath(): |
|||
path = baseDir + '/' + camName + "/" + datetime.today().strftime('%Y/%m/%d/%H.%M.%S') |
|||
Path(path).parent.mkdir(parents=True, exist_ok=True) |
|||
return path |
|||
|
|||
def shutDown(): |
|||
global isShuttingDown |
|||
isShuttingDown = True |
|||
log('Shutting down...') |
|||
try: |
|||
cam.stop_monitor() |
|||
close() |
|||
except (RuntimeError, TypeError, NameError, Exception): |
|||
pass |
|||
log('done') |
|||
exit(0) |
|||
|
|||
def handler(signum, b): |
|||
log('Signal ' + str(signum) + ' received') |
|||
shutDown() |
|||
|
|||
signal(SIGINT, handler) |
|||
signal(SIGTERM, handler) |
|||
|
|||
def close(): |
|||
cam.close() |
|||
|
|||
def theActualJob(): |
|||
|
|||
prevtime = 0 |
|||
video = None |
|||
audio = None |
|||
|
|||
def receiver(frame, meta, user): |
|||
nonlocal prevtime, video, audio |
|||
if frame is None: |
|||
log('Empty frame') |
|||
else: |
|||
tn = time() |
|||
if tn - prevtime >= chunkSize: |
|||
if video != None: |
|||
video.close() |
|||
audio.close() |
|||
prevtime = tn |
|||
path = mkpath() |
|||
log('Starting files: ' + path) |
|||
video = open(path + '.video', "wb") |
|||
audio = open(path + '.audio', "wb") |
|||
if 'type' in meta and meta["type"] == "g711a": audio.write(frame) |
|||
elif 'frame' in meta: video.write(frame) |
|||
|
|||
log('Starting to grab streams...') |
|||
cam.start_monitor(receiver) |
|||
|
|||
def syncTime(): |
|||
log('Synching time...') |
|||
cam.set_time() |
|||
log('done') |
|||
|
|||
def jobWrapper(): |
|||
global cam |
|||
log('Logging in to camera ' + camIp + '...') |
|||
cam = DVRIPCam(camIp) |
|||
if cam.login(): |
|||
log('done') |
|||
else: |
|||
raise SomethingIsWrongWithCamera('Cannot login') |
|||
syncTime() |
|||
theActualJob() |
|||
|
|||
def theJob(): |
|||
while True: |
|||
try: |
|||
jobWrapper() |
|||
except (TypeError, ValueError) as err: |
|||
if isShuttingDown: |
|||
exit(0) |
|||
else: |
|||
try: |
|||
log('Error. Attempting to reboot camera...') |
|||
cam.reboot() |
|||
log('Waiting for ' + str(rebootWait) + 's for reboot...') |
|||
sleep(rebootWait) |
|||
except (UnicodeDecodeError, ValueError, TypeError): |
|||
raise SomethingIsWrongWithCamera('Failed to reboot') |
|||
|
|||
def main(): |
|||
Path(logFile).parent.mkdir(parents=True, exist_ok=True) |
|||
logging.basicConfig(filename=logFile, level=logging.INFO, format='[%(asctime)s] %(message)s') |
|||
while True: |
|||
try: |
|||
theJob() |
|||
except SomethingIsWrongWithCamera as err: |
|||
close() |
|||
log(str(err) + '. Waiting for ' + str(retryIn) + ' seconds before trying again...') |
|||
sleep(retryIn) |
|||
|
|||
if __name__ == "__main__": |
|||
main() |
@ -1,47 +1,47 @@ |
|||
from setuptools import setup, find_packages |
|||
import pathlib |
|||
|
|||
here = pathlib.Path(__file__).parent.resolve() |
|||
|
|||
# Get the long description from the README file |
|||
long_description = (here / 'README.md').read_text(encoding='utf-8') |
|||
|
|||
setup( |
|||
name='python-dvr', |
|||
|
|||
version='0.0.0', |
|||
|
|||
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_content_type='text/markdown', |
|||
|
|||
url='https://github.com/NeiroNx/python-dvr/', |
|||
|
|||
author='NeiroN', |
|||
|
|||
classifiers=[ |
|||
'Development Status :: 3 - Alpha', |
|||
|
|||
'Intended Audience :: Developers', |
|||
'Topic :: Multimedia :: Video :: Capture', |
|||
|
|||
'License :: OSI Approved :: MIT License', |
|||
|
|||
'Programming Language :: Python :: 3', |
|||
'Programming Language :: Python :: 3.6', |
|||
'Programming Language :: Python :: 3.7', |
|||
'Programming Language :: Python :: 3.8', |
|||
'Programming Language :: Python :: 3.9', |
|||
'Programming Language :: Python :: 3 :: Only', |
|||
], |
|||
|
|||
py_modules=["dvrip", "DeviceManager", "asyncio_dvrip"], |
|||
|
|||
python_requires='>=3.6', |
|||
|
|||
project_urls={ |
|||
'Bug Reports': 'https://github.com/NeiroNx/python-dvr/issues', |
|||
'Source': 'https://github.com/NeiroNx/python-dvr', |
|||
}, |
|||
) |
|||
from setuptools import setup, find_packages |
|||
import pathlib |
|||
|
|||
here = pathlib.Path(__file__).parent.resolve() |
|||
|
|||
# Get the long description from the README file |
|||
long_description = (here / 'README.md').read_text(encoding='utf-8') |
|||
|
|||
setup( |
|||
name='python-dvr', |
|||
|
|||
version='0.0.0', |
|||
|
|||
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_content_type='text/markdown', |
|||
|
|||
url='https://github.com/NeiroNx/python-dvr/', |
|||
|
|||
author='NeiroN', |
|||
|
|||
classifiers=[ |
|||
'Development Status :: 3 - Alpha', |
|||
|
|||
'Intended Audience :: Developers', |
|||
'Topic :: Multimedia :: Video :: Capture', |
|||
|
|||
'License :: OSI Approved :: MIT License', |
|||
|
|||
'Programming Language :: Python :: 3', |
|||
'Programming Language :: Python :: 3.6', |
|||
'Programming Language :: Python :: 3.7', |
|||
'Programming Language :: Python :: 3.8', |
|||
'Programming Language :: Python :: 3.9', |
|||
'Programming Language :: Python :: 3 :: Only', |
|||
], |
|||
|
|||
py_modules=["dvrip", "DeviceManager", "asyncio_dvrip"], |
|||
|
|||
python_requires='>=3.6', |
|||
|
|||
project_urls={ |
|||
'Bug Reports': 'https://github.com/NeiroNx/python-dvr/issues', |
|||
'Source': 'https://github.com/NeiroNx/python-dvr', |
|||
}, |
|||
) |
|||
|
@ -1,217 +1,217 @@ |
|||
from time import sleep |
|||
from dvrip import DVRIPCam, SomethingIsWrongWithCamera |
|||
from pathlib import Path |
|||
import subprocess |
|||
import json |
|||
from datetime import datetime |
|||
|
|||
|
|||
class SolarCam: |
|||
cam = None |
|||
logger = None |
|||
|
|||
def __init__(self, host_ip, user, password, logger): |
|||
self.logger = logger |
|||
self.cam = DVRIPCam( |
|||
host_ip, |
|||
user=user, |
|||
password=password, |
|||
) |
|||
|
|||
def login(self, num_retries=10): |
|||
for i in range(num_retries): |
|||
try: |
|||
self.logger.debug("Try login...") |
|||
self.cam.login() |
|||
self.logger.debug( |
|||
f"Success! Connected to Camera. Waiting few seconds to let Camera fully boot..." |
|||
) |
|||
# waiting until camera is ready |
|||
sleep(10) |
|||
return |
|||
except SomethingIsWrongWithCamera: |
|||
self.logger.debug("Could not connect...Camera could be offline") |
|||
self.cam.close() |
|||
|
|||
if i == 9: |
|||
raise ConnectionRefusedError( |
|||
f"Could not connect {num_retries} times...aborting" |
|||
) |
|||
sleep(2) |
|||
|
|||
def logout(self): |
|||
self.cam.close() |
|||
|
|||
def get_time(self): |
|||
return self.cam.get_time() |
|||
|
|||
def set_time(self, time=None): |
|||
if time is None: |
|||
time = datetime.now() |
|||
return self.cam.set_time(time=time) |
|||
|
|||
def get_local_files(self, start, end, filetype): |
|||
return self.cam.list_local_files(start, end, filetype) |
|||
|
|||
def dump_local_files( |
|||
self, files, blacklist_path, download_dir, target_filetype=None |
|||
): |
|||
with open(f"{blacklist_path}.dmp", "a") as outfile: |
|||
for file in files: |
|||
target_file_path = self.generateTargetFilePath( |
|||
file["FileName"], download_dir |
|||
) |
|||
outfile.write(f"{target_file_path}\n") |
|||
|
|||
if target_filetype: |
|||
target_file_path_convert = self.generateTargetFilePath( |
|||
file["FileName"], download_dir, extention=f"{target_filetype}" |
|||
) |
|||
outfile.write(f"{target_file_path_convert}\n") |
|||
|
|||
def generateTargetFilePath(self, filename, downloadDir, extention=""): |
|||
fileExtention = Path(filename).suffix |
|||
filenameSplit = filename.split("/") |
|||
filenameDisk = f"{filenameSplit[3]}_{filenameSplit[5][:8]}".replace(".", "-") |
|||
targetPathClean = f"{downloadDir}/{filenameDisk}" |
|||
|
|||
if extention != "": |
|||
return f"{targetPathClean}{extention}" |
|||
|
|||
return f"{targetPathClean}{fileExtention}" |
|||
|
|||
def convertFile(self, sourceFile, targetFile): |
|||
if ( |
|||
subprocess.run( |
|||
f"ffmpeg -framerate 15 -i {sourceFile} -b:v 1M -c:v libvpx-vp9 -c:a libopus {targetFile}", |
|||
stdout=subprocess.DEVNULL, |
|||
stderr=subprocess.DEVNULL, |
|||
shell=True, |
|||
).returncode |
|||
!= 0 |
|||
): |
|||
self.logger.debug(f"Error converting video. Check {sourceFile}") |
|||
|
|||
self.logger.debug(f"File successfully converted: {targetFile}") |
|||
Path(sourceFile).unlink() |
|||
self.logger.debug(f"Orginal file successfully deleted: {sourceFile}") |
|||
|
|||
def save_files(self, download_dir, files, blacklist=None, target_filetype=None): |
|||
self.logger.debug(f"Start downloading files") |
|||
|
|||
for file in files: |
|||
target_file_path = self.generateTargetFilePath( |
|||
file["FileName"], download_dir |
|||
) |
|||
|
|||
target_file_path_convert = None |
|||
if target_filetype: |
|||
target_file_path_convert = self.generateTargetFilePath( |
|||
file["FileName"], download_dir, extention=f"{target_filetype}" |
|||
) |
|||
|
|||
if Path(f"{target_file_path}").is_file(): |
|||
self.logger.debug(f"File already exists: {target_file_path}") |
|||
continue |
|||
|
|||
if ( |
|||
target_file_path_convert |
|||
and Path(f"{target_file_path_convert}").is_file() |
|||
): |
|||
self.logger.debug( |
|||
f"Converted file already exists: {target_file_path_convert}" |
|||
) |
|||
continue |
|||
|
|||
if blacklist: |
|||
if target_file_path in blacklist: |
|||
self.logger.debug(f"File is on the blacklist: {target_file_path}") |
|||
continue |
|||
if target_file_path_convert and target_file_path_convert in blacklist: |
|||
self.logger.debug( |
|||
f"File is on the blacklist: {target_file_path_convert}" |
|||
) |
|||
continue |
|||
|
|||
self.logger.debug(f"Downloading {target_file_path}...") |
|||
self.cam.download_file( |
|||
file["BeginTime"], file["EndTime"], file["FileName"], target_file_path |
|||
) |
|||
self.logger.debug(f"Finished downloading {target_file_path}...") |
|||
|
|||
if target_file_path_convert: |
|||
self.logger.debug(f"Converting {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"Finish downloading files") |
|||
|
|||
def move_cam(self, direction, step=5): |
|||
match direction: |
|||
case "up": |
|||
self.cam.ptz_step("DirectionUp", step=step) |
|||
case "down": |
|||
self.cam.ptz_step("DirectionDown", step=step) |
|||
case "left": |
|||
self.cam.ptz_step("DirectionLeft", step=step) |
|||
case "right": |
|||
self.cam.ptz_step("DirectionRight", step=step) |
|||
case _: |
|||
self.logger.debug(f"No direction found") |
|||
|
|||
def mute_cam(self): |
|||
print( |
|||
self.cam.send( |
|||
1040, |
|||
{ |
|||
"fVideo.Volume": [ |
|||
{"AudioMode": "Single", "LeftVolume": 0, "RightVolume": 0} |
|||
], |
|||
"Name": "fVideo.Volume", |
|||
}, |
|||
) |
|||
) |
|||
|
|||
def set_volume(self, volume): |
|||
print( |
|||
self.cam.send( |
|||
1040, |
|||
{ |
|||
"fVideo.Volume": [ |
|||
{ |
|||
"AudioMode": "Single", |
|||
"LeftVolume": volume, |
|||
"RightVolume": volume, |
|||
} |
|||
], |
|||
"Name": "fVideo.Volume", |
|||
}, |
|||
) |
|||
) |
|||
|
|||
def get_battery(self): |
|||
data = self.cam.send_custom( |
|||
1610, |
|||
{"Name": "OPTUpData", "OPTUpData": {"UpLoadDataType": 5}}, |
|||
size=260, |
|||
)[87:-2].decode("utf-8") |
|||
json_data = json.loads(data) |
|||
return { |
|||
"BatteryPercent": json_data["Dev.ElectCapacity"]["percent"], |
|||
"Charging": json_data["Dev.ElectCapacity"]["electable"], |
|||
} |
|||
|
|||
def get_storage(self): |
|||
# get available storage in gb |
|||
storage_result = [] |
|||
data = self.cam.send(1020, {"Name": "StorageInfo"}) |
|||
for storage_index, storage in enumerate(data["StorageInfo"]): |
|||
for partition_index, partition in enumerate(storage["Partition"]): |
|||
s = { |
|||
"Storage": storage_index, |
|||
"Partition": partition_index, |
|||
"RemainingSpace": int(partition["RemainSpace"], 0) / 1024, |
|||
"TotalSpace": int(partition["TotalSpace"], 0) / 1024, |
|||
} |
|||
storage_result.append(s) |
|||
return storage_result |
|||
from time import sleep |
|||
from dvrip import DVRIPCam, SomethingIsWrongWithCamera |
|||
from pathlib import Path |
|||
import subprocess |
|||
import json |
|||
from datetime import datetime |
|||
|
|||
|
|||
class SolarCam: |
|||
cam = None |
|||
logger = None |
|||
|
|||
def __init__(self, host_ip, user, password, logger): |
|||
self.logger = logger |
|||
self.cam = DVRIPCam( |
|||
host_ip, |
|||
user=user, |
|||
password=password, |
|||
) |
|||
|
|||
def login(self, num_retries=10): |
|||
for i in range(num_retries): |
|||
try: |
|||
self.logger.debug("Try login...") |
|||
self.cam.login() |
|||
self.logger.debug( |
|||
f"Success! Connected to Camera. Waiting few seconds to let Camera fully boot..." |
|||
) |
|||
# waiting until camera is ready |
|||
sleep(10) |
|||
return |
|||
except SomethingIsWrongWithCamera: |
|||
self.logger.debug("Could not connect...Camera could be offline") |
|||
self.cam.close() |
|||
|
|||
if i == 9: |
|||
raise ConnectionRefusedError( |
|||
f"Could not connect {num_retries} times...aborting" |
|||
) |
|||
sleep(2) |
|||
|
|||
def logout(self): |
|||
self.cam.close() |
|||
|
|||
def get_time(self): |
|||
return self.cam.get_time() |
|||
|
|||
def set_time(self, time=None): |
|||
if time is None: |
|||
time = datetime.now() |
|||
return self.cam.set_time(time=time) |
|||
|
|||
def get_local_files(self, start, end, filetype): |
|||
return self.cam.list_local_files(start, end, filetype) |
|||
|
|||
def dump_local_files( |
|||
self, files, blacklist_path, download_dir, target_filetype=None |
|||
): |
|||
with open(f"{blacklist_path}.dmp", "a") as outfile: |
|||
for file in files: |
|||
target_file_path = self.generateTargetFilePath( |
|||
file["FileName"], download_dir |
|||
) |
|||
outfile.write(f"{target_file_path}\n") |
|||
|
|||
if target_filetype: |
|||
target_file_path_convert = self.generateTargetFilePath( |
|||
file["FileName"], download_dir, extention=f"{target_filetype}" |
|||
) |
|||
outfile.write(f"{target_file_path_convert}\n") |
|||
|
|||
def generateTargetFilePath(self, filename, downloadDir, extention=""): |
|||
fileExtention = Path(filename).suffix |
|||
filenameSplit = filename.split("/") |
|||
filenameDisk = f"{filenameSplit[3]}_{filenameSplit[5][:8]}".replace(".", "-") |
|||
targetPathClean = f"{downloadDir}/{filenameDisk}" |
|||
|
|||
if extention != "": |
|||
return f"{targetPathClean}{extention}" |
|||
|
|||
return f"{targetPathClean}{fileExtention}" |
|||
|
|||
def convertFile(self, sourceFile, targetFile): |
|||
if ( |
|||
subprocess.run( |
|||
f"ffmpeg -framerate 15 -i {sourceFile} -b:v 1M -c:v libvpx-vp9 -c:a libopus {targetFile}", |
|||
stdout=subprocess.DEVNULL, |
|||
stderr=subprocess.DEVNULL, |
|||
shell=True, |
|||
).returncode |
|||
!= 0 |
|||
): |
|||
self.logger.debug(f"Error converting video. Check {sourceFile}") |
|||
|
|||
self.logger.debug(f"File successfully converted: {targetFile}") |
|||
Path(sourceFile).unlink() |
|||
self.logger.debug(f"Orginal file successfully deleted: {sourceFile}") |
|||
|
|||
def save_files(self, download_dir, files, blacklist=None, target_filetype=None): |
|||
self.logger.debug(f"Start downloading files") |
|||
|
|||
for file in files: |
|||
target_file_path = self.generateTargetFilePath( |
|||
file["FileName"], download_dir |
|||
) |
|||
|
|||
target_file_path_convert = None |
|||
if target_filetype: |
|||
target_file_path_convert = self.generateTargetFilePath( |
|||
file["FileName"], download_dir, extention=f"{target_filetype}" |
|||
) |
|||
|
|||
if Path(f"{target_file_path}").is_file(): |
|||
self.logger.debug(f"File already exists: {target_file_path}") |
|||
continue |
|||
|
|||
if ( |
|||
target_file_path_convert |
|||
and Path(f"{target_file_path_convert}").is_file() |
|||
): |
|||
self.logger.debug( |
|||
f"Converted file already exists: {target_file_path_convert}" |
|||
) |
|||
continue |
|||
|
|||
if blacklist: |
|||
if target_file_path in blacklist: |
|||
self.logger.debug(f"File is on the blacklist: {target_file_path}") |
|||
continue |
|||
if target_file_path_convert and target_file_path_convert in blacklist: |
|||
self.logger.debug( |
|||
f"File is on the blacklist: {target_file_path_convert}" |
|||
) |
|||
continue |
|||
|
|||
self.logger.debug(f"Downloading {target_file_path}...") |
|||
self.cam.download_file( |
|||
file["BeginTime"], file["EndTime"], file["FileName"], target_file_path |
|||
) |
|||
self.logger.debug(f"Finished downloading {target_file_path}...") |
|||
|
|||
if target_file_path_convert: |
|||
self.logger.debug(f"Converting {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"Finish downloading files") |
|||
|
|||
def move_cam(self, direction, step=5): |
|||
match direction: |
|||
case "up": |
|||
self.cam.ptz_step("DirectionUp", step=step) |
|||
case "down": |
|||
self.cam.ptz_step("DirectionDown", step=step) |
|||
case "left": |
|||
self.cam.ptz_step("DirectionLeft", step=step) |
|||
case "right": |
|||
self.cam.ptz_step("DirectionRight", step=step) |
|||
case _: |
|||
self.logger.debug(f"No direction found") |
|||
|
|||
def mute_cam(self): |
|||
print( |
|||
self.cam.send( |
|||
1040, |
|||
{ |
|||
"fVideo.Volume": [ |
|||
{"AudioMode": "Single", "LeftVolume": 0, "RightVolume": 0} |
|||
], |
|||
"Name": "fVideo.Volume", |
|||
}, |
|||
) |
|||
) |
|||
|
|||
def set_volume(self, volume): |
|||
print( |
|||
self.cam.send( |
|||
1040, |
|||
{ |
|||
"fVideo.Volume": [ |
|||
{ |
|||
"AudioMode": "Single", |
|||
"LeftVolume": volume, |
|||
"RightVolume": volume, |
|||
} |
|||
], |
|||
"Name": "fVideo.Volume", |
|||
}, |
|||
) |
|||
) |
|||
|
|||
def get_battery(self): |
|||
data = self.cam.send_custom( |
|||
1610, |
|||
{"Name": "OPTUpData", "OPTUpData": {"UpLoadDataType": 5}}, |
|||
size=260, |
|||
)[87:-2].decode("utf-8") |
|||
json_data = json.loads(data) |
|||
return { |
|||
"BatteryPercent": json_data["Dev.ElectCapacity"]["percent"], |
|||
"Charging": json_data["Dev.ElectCapacity"]["electable"], |
|||
} |
|||
|
|||
def get_storage(self): |
|||
# get available storage in gb |
|||
storage_result = [] |
|||
data = self.cam.send(1020, {"Name": "StorageInfo"}) |
|||
for storage_index, storage in enumerate(data["StorageInfo"]): |
|||
for partition_index, partition in enumerate(storage["Partition"]): |
|||
s = { |
|||
"Storage": storage_index, |
|||
"Partition": partition_index, |
|||
"RemainingSpace": int(partition["RemainSpace"], 0) / 1024, |
|||
"TotalSpace": int(partition["TotalSpace"], 0) / 1024, |
|||
} |
|||
storage_result.append(s) |
|||
return storage_result |
|||
|
@ -1,244 +1,244 @@ |
|||
#!/usr/bin/env python3 |
|||
|
|||
from dvrip import DVRIPCam |
|||
from telnetlib import Telnet |
|||
import argparse |
|||
import datetime |
|||
import json |
|||
import os |
|||
import socket |
|||
import time |
|||
import requests |
|||
import zipfile |
|||
|
|||
TELNET_PORT = 4321 |
|||
ARCHIVE_URL = "https://github.com/widgetii/xmupdates/raw/main/archive" |
|||
|
|||
""" |
|||
Tested on XM boards: |
|||
IPG-53H20PL-S 53H20L_S39 00002532 |
|||
IPG-80H20PS-S 50H20L 00022520 |
|||
IVG-85HF20PYA-S HI3516EV200_50H20AI_S38 000559A7 |
|||
IVG-85HG50PYA-S HI3516EV300_85H50AI 000529B2 |
|||
|
|||
Issues with: "armbenv: can't load library 'libdvr.so'" |
|||
IPG-50HV20PES-S 50H20L_18EV200_S38 00018520 |
|||
""" |
|||
|
|||
# downgrade archive (mainly Yandex.Disk) |
|||
# https://www.cctvsp.ru/articles/obnovlenie-proshivok-dlya-ip-kamer-ot-xiong-mai |
|||
|
|||
XMV4 = { |
|||
"envtool": "XmEnv", |
|||
"flashes": [ |
|||
"0x00EF4017", |
|||
"0x00EF4018", |
|||
"0x00C22017", |
|||
"0x00C22018", |
|||
"0x00C22019", |
|||
"0x00C84017", |
|||
"0x00C84018", |
|||
"0x001C7017", |
|||
"0x001C7018", |
|||
"0x00207017", |
|||
"0x00207018", |
|||
"0x000B4017", |
|||
"0x000B4018", |
|||
], |
|||
} |
|||
|
|||
|
|||
def down(template, filename): |
|||
t = template.copy() |
|||
t['downgrade'] = filename |
|||
return t |
|||
|
|||
|
|||
# Borrowed from InstallDesc |
|||
conf = { |
|||
"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"), |
|||
"000529E9": down(XMV4, "hacked_from_HI3516EV300_85H50AI.bin"), |
|||
} |
|||
|
|||
|
|||
def add_flashes(desc, swver): |
|||
board = conf.get(swver) |
|||
if board is None: |
|||
return |
|||
|
|||
fls = [] |
|||
for i in board["flashes"]: |
|||
fls.append({"FlashID": i}) |
|||
desc["SupportFlashType"] = fls |
|||
|
|||
|
|||
def get_envtool(swver): |
|||
board = conf.get(swver) |
|||
if board is None: |
|||
return "armbenv" |
|||
|
|||
return board["envtool"] |
|||
|
|||
|
|||
def make_zip(filename, data): |
|||
zipf = zipfile.ZipFile(filename, "w", zipfile.ZIP_DEFLATED) |
|||
zipf.writestr("InstallDesc", data) |
|||
zipf.close() |
|||
|
|||
|
|||
def check_port(host_ip, port): |
|||
a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|||
result_of_check = a_socket.connect_ex((host_ip, port)) |
|||
return result_of_check == 0 |
|||
|
|||
|
|||
def extract_gen(swver): |
|||
return swver.split(".")[3] |
|||
|
|||
|
|||
def cmd_armebenv(swver): |
|||
envtool = get_envtool(swver) |
|||
return { |
|||
"Command": "Shell", |
|||
"Script": f"{envtool} -s xmuart 0; {envtool} -s telnetctrl 1", |
|||
} |
|||
|
|||
|
|||
def cmd_telnetd(port): |
|||
return { |
|||
"Command": "Shell", |
|||
"Script": f"busybox telnetd -F -p {port} -l /bin/sh", |
|||
} |
|||
|
|||
|
|||
def cmd_backup(): |
|||
return [ |
|||
{ |
|||
"Command": "Shell", |
|||
"Script": "mount -o nolock 95.217.179.189:/srv/ro /utils/", |
|||
}, |
|||
{"Command": "Shell", "Script": "/utils/ipctool -w"}, |
|||
] |
|||
|
|||
|
|||
def downgrade_old_version(cam, buildtime, swver): |
|||
milestone = datetime.date(2020, 5, 7) |
|||
dto = datetime.datetime.strptime(buildtime, "%Y-%m-%d %H:%M:%S") |
|||
if dto.date() > milestone: |
|||
print( |
|||
f"Current firmware date {dto.date()}, but it needs to be no more than" |
|||
f" {milestone}\nConsider downgrade and only then continue.\n\n" |
|||
) |
|||
a = input("Are you sure to overwrite current firmware without backup (y/n)? ") |
|||
if a == "y": |
|||
board = conf.get(swver) |
|||
if board is None: |
|||
print(f"{swver} firmware is not supported yet") |
|||
return False |
|||
|
|||
print("DOWNGRADING\n") |
|||
url = f"{ARCHIVE_URL}/{swver}/{board['downgrade']}" |
|||
print(f"Downloading {url}") |
|||
r = requests.get(url, allow_redirects=True) |
|||
if r.status_code != requests.codes.ok: |
|||
print("Something went wrong") |
|||
return False |
|||
|
|||
open('upgrade.bin', 'wb').write(r.content) |
|||
print(f"Upgrading...") |
|||
cam.upgrade('upgrade.bin') |
|||
print("Completed. Wait a minute and then rerun") |
|||
return False |
|||
|
|||
return False |
|||
return True |
|||
|
|||
|
|||
def open_telnet(host_ip, port, **kwargs): |
|||
make_telnet = kwargs.get("telnet", False) |
|||
make_backup = kwargs.get("backup", False) |
|||
user = kwargs.get("username", "admin") |
|||
password = kwargs.get("password", "") |
|||
|
|||
cam = DVRIPCam(host_ip, user=user, password=password) |
|||
if not cam.login(): |
|||
print(f"Cannot connect {host_ip}") |
|||
return |
|||
upinfo = cam.get_upgrade_info() |
|||
hw = upinfo["Hardware"] |
|||
sysinfo = cam.get_system_info() |
|||
swver = extract_gen(sysinfo["SoftWareVersion"]) |
|||
print(f"Modifying camera {hw}, firmware {swver}") |
|||
if not downgrade_old_version(cam, sysinfo["BuildTime"], swver): |
|||
cam.close() |
|||
return |
|||
|
|||
print(f"Firmware generation {swver}") |
|||
|
|||
desc = { |
|||
"Hardware": hw, |
|||
"DevID": f"{swver}1001000000000000", |
|||
"CompatibleVersion": 2, |
|||
"Vendor": "General", |
|||
"CRC": "1ce6242100007636", |
|||
} |
|||
upcmd = [] |
|||
if make_telnet: |
|||
upcmd.append(cmd_telnetd(port)) |
|||
elif make_backup: |
|||
upcmd = cmd_backup() |
|||
else: |
|||
upcmd.append(cmd_armebenv(swver)) |
|||
desc["UpgradeCommand"] = upcmd |
|||
add_flashes(desc, swver) |
|||
|
|||
zipfname = "upgrade.bin" |
|||
make_zip(zipfname, json.dumps(desc, indent=2)) |
|||
cam.upgrade(zipfname) |
|||
cam.close() |
|||
os.remove(zipfname) |
|||
|
|||
if make_backup: |
|||
print("Check backup") |
|||
return |
|||
|
|||
if not make_telnet: |
|||
port = 23 |
|||
print("Waiting for camera is rebooting...") |
|||
|
|||
for i in range(10): |
|||
time.sleep(4) |
|||
if check_port(host_ip, port): |
|||
tport = f" {port}" if port != 23 else "" |
|||
print(f"Now use 'telnet {host_ip}{tport}' to login") |
|||
return |
|||
|
|||
print("Something went wrong") |
|||
return |
|||
|
|||
|
|||
def main(): |
|||
parser = argparse.ArgumentParser() |
|||
parser.add_argument("hostname", help="Camera IP address or hostname") |
|||
parser.add_argument( |
|||
"-u", "--username", default="admin", help="Username for camera login" |
|||
) |
|||
parser.add_argument( |
|||
"-p", "--password", default="", help="Password for camera login" |
|||
) |
|||
parser.add_argument( |
|||
"-b", "--backup", action="store_true", help="Make backup to the cloud" |
|||
) |
|||
parser.add_argument( |
|||
"-t", |
|||
"--telnet", |
|||
action="store_true", |
|||
help="Open telnet port without rebooting camera", |
|||
) |
|||
args = parser.parse_args() |
|||
open_telnet(args.hostname, TELNET_PORT, **vars(args)) |
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
main() |
|||
#!/usr/bin/env python3 |
|||
|
|||
from dvrip import DVRIPCam |
|||
from telnetlib import Telnet |
|||
import argparse |
|||
import datetime |
|||
import json |
|||
import os |
|||
import socket |
|||
import time |
|||
import requests |
|||
import zipfile |
|||
|
|||
TELNET_PORT = 4321 |
|||
ARCHIVE_URL = "https://github.com/widgetii/xmupdates/raw/main/archive" |
|||
|
|||
""" |
|||
Tested on XM boards: |
|||
IPG-53H20PL-S 53H20L_S39 00002532 |
|||
IPG-80H20PS-S 50H20L 00022520 |
|||
IVG-85HF20PYA-S HI3516EV200_50H20AI_S38 000559A7 |
|||
IVG-85HG50PYA-S HI3516EV300_85H50AI 000529B2 |
|||
|
|||
Issues with: "armbenv: can't load library 'libdvr.so'" |
|||
IPG-50HV20PES-S 50H20L_18EV200_S38 00018520 |
|||
""" |
|||
|
|||
# downgrade archive (mainly Yandex.Disk) |
|||
# https://www.cctvsp.ru/articles/obnovlenie-proshivok-dlya-ip-kamer-ot-xiong-mai |
|||
|
|||
XMV4 = { |
|||
"envtool": "XmEnv", |
|||
"flashes": [ |
|||
"0x00EF4017", |
|||
"0x00EF4018", |
|||
"0x00C22017", |
|||
"0x00C22018", |
|||
"0x00C22019", |
|||
"0x00C84017", |
|||
"0x00C84018", |
|||
"0x001C7017", |
|||
"0x001C7018", |
|||
"0x00207017", |
|||
"0x00207018", |
|||
"0x000B4017", |
|||
"0x000B4018", |
|||
], |
|||
} |
|||
|
|||
|
|||
def down(template, filename): |
|||
t = template.copy() |
|||
t['downgrade'] = filename |
|||
return t |
|||
|
|||
|
|||
# Borrowed from InstallDesc |
|||
conf = { |
|||
"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"), |
|||
"000529E9": down(XMV4, "hacked_from_HI3516EV300_85H50AI.bin"), |
|||
} |
|||
|
|||
|
|||
def add_flashes(desc, swver): |
|||
board = conf.get(swver) |
|||
if board is None: |
|||
return |
|||
|
|||
fls = [] |
|||
for i in board["flashes"]: |
|||
fls.append({"FlashID": i}) |
|||
desc["SupportFlashType"] = fls |
|||
|
|||
|
|||
def get_envtool(swver): |
|||
board = conf.get(swver) |
|||
if board is None: |
|||
return "armbenv" |
|||
|
|||
return board["envtool"] |
|||
|
|||
|
|||
def make_zip(filename, data): |
|||
zipf = zipfile.ZipFile(filename, "w", zipfile.ZIP_DEFLATED) |
|||
zipf.writestr("InstallDesc", data) |
|||
zipf.close() |
|||
|
|||
|
|||
def check_port(host_ip, port): |
|||
a_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|||
result_of_check = a_socket.connect_ex((host_ip, port)) |
|||
return result_of_check == 0 |
|||
|
|||
|
|||
def extract_gen(swver): |
|||
return swver.split(".")[3] |
|||
|
|||
|
|||
def cmd_armebenv(swver): |
|||
envtool = get_envtool(swver) |
|||
return { |
|||
"Command": "Shell", |
|||
"Script": f"{envtool} -s xmuart 0; {envtool} -s telnetctrl 1", |
|||
} |
|||
|
|||
|
|||
def cmd_telnetd(port): |
|||
return { |
|||
"Command": "Shell", |
|||
"Script": f"busybox telnetd -F -p {port} -l /bin/sh", |
|||
} |
|||
|
|||
|
|||
def cmd_backup(): |
|||
return [ |
|||
{ |
|||
"Command": "Shell", |
|||
"Script": "mount -o nolock 95.217.179.189:/srv/ro /utils/", |
|||
}, |
|||
{"Command": "Shell", "Script": "/utils/ipctool -w"}, |
|||
] |
|||
|
|||
|
|||
def downgrade_old_version(cam, buildtime, swver): |
|||
milestone = datetime.date(2020, 5, 7) |
|||
dto = datetime.datetime.strptime(buildtime, "%Y-%m-%d %H:%M:%S") |
|||
if dto.date() > milestone: |
|||
print( |
|||
f"Current firmware date {dto.date()}, but it needs to be no more than" |
|||
f" {milestone}\nConsider downgrade and only then continue.\n\n" |
|||
) |
|||
a = input("Are you sure to overwrite current firmware without backup (y/n)? ") |
|||
if a == "y": |
|||
board = conf.get(swver) |
|||
if board is None: |
|||
print(f"{swver} firmware is not supported yet") |
|||
return False |
|||
|
|||
print("DOWNGRADING\n") |
|||
url = f"{ARCHIVE_URL}/{swver}/{board['downgrade']}" |
|||
print(f"Downloading {url}") |
|||
r = requests.get(url, allow_redirects=True) |
|||
if r.status_code != requests.codes.ok: |
|||
print("Something went wrong") |
|||
return False |
|||
|
|||
open('upgrade.bin', 'wb').write(r.content) |
|||
print(f"Upgrading...") |
|||
cam.upgrade('upgrade.bin') |
|||
print("Completed. Wait a minute and then rerun") |
|||
return False |
|||
|
|||
return False |
|||
return True |
|||
|
|||
|
|||
def open_telnet(host_ip, port, **kwargs): |
|||
make_telnet = kwargs.get("telnet", False) |
|||
make_backup = kwargs.get("backup", False) |
|||
user = kwargs.get("username", "admin") |
|||
password = kwargs.get("password", "") |
|||
|
|||
cam = DVRIPCam(host_ip, user=user, password=password) |
|||
if not cam.login(): |
|||
print(f"Cannot connect {host_ip}") |
|||
return |
|||
upinfo = cam.get_upgrade_info() |
|||
hw = upinfo["Hardware"] |
|||
sysinfo = cam.get_system_info() |
|||
swver = extract_gen(sysinfo["SoftWareVersion"]) |
|||
print(f"Modifying camera {hw}, firmware {swver}") |
|||
if not downgrade_old_version(cam, sysinfo["BuildTime"], swver): |
|||
cam.close() |
|||
return |
|||
|
|||
print(f"Firmware generation {swver}") |
|||
|
|||
desc = { |
|||
"Hardware": hw, |
|||
"DevID": f"{swver}1001000000000000", |
|||
"CompatibleVersion": 2, |
|||
"Vendor": "General", |
|||
"CRC": "1ce6242100007636", |
|||
} |
|||
upcmd = [] |
|||
if make_telnet: |
|||
upcmd.append(cmd_telnetd(port)) |
|||
elif make_backup: |
|||
upcmd = cmd_backup() |
|||
else: |
|||
upcmd.append(cmd_armebenv(swver)) |
|||
desc["UpgradeCommand"] = upcmd |
|||
add_flashes(desc, swver) |
|||
|
|||
zipfname = "upgrade.bin" |
|||
make_zip(zipfname, json.dumps(desc, indent=2)) |
|||
cam.upgrade(zipfname) |
|||
cam.close() |
|||
os.remove(zipfname) |
|||
|
|||
if make_backup: |
|||
print("Check backup") |
|||
return |
|||
|
|||
if not make_telnet: |
|||
port = 23 |
|||
print("Waiting for camera is rebooting...") |
|||
|
|||
for i in range(10): |
|||
time.sleep(4) |
|||
if check_port(host_ip, port): |
|||
tport = f" {port}" if port != 23 else "" |
|||
print(f"Now use 'telnet {host_ip}{tport}' to login") |
|||
return |
|||
|
|||
print("Something went wrong") |
|||
return |
|||
|
|||
|
|||
def main(): |
|||
parser = argparse.ArgumentParser() |
|||
parser.add_argument("hostname", help="Camera IP address or hostname") |
|||
parser.add_argument( |
|||
"-u", "--username", default="admin", help="Username for camera login" |
|||
) |
|||
parser.add_argument( |
|||
"-p", "--password", default="", help="Password for camera login" |
|||
) |
|||
parser.add_argument( |
|||
"-b", "--backup", action="store_true", help="Make backup to the cloud" |
|||
) |
|||
parser.add_argument( |
|||
"-t", |
|||
"--telnet", |
|||
action="store_true", |
|||
help="Open telnet port without rebooting camera", |
|||
) |
|||
args = parser.parse_args() |
|||
open_telnet(args.hostname, TELNET_PORT, **vars(args)) |
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
main() |
|||
|
Loading…
Reference in new issue