You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

241 lines
8.4 KiB

from datetime import datetime
from asyncio_dvrip import DVRIPCam
import json
import struct
import base64
NVR_DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
PRIMARY_STREAM = 0
SECONDARY_STREAM = 1
UNKNOWN_STREAM = 2
H264 = "h264"
H265 = "h265"
H265X = "h265x"
class File:
def __init__(self, data, channel = 0, stream = 0) -> None:
self.begin = datetime.strptime(data.get("BeginTime", data.get("begin")), NVR_DATETIME_FORMAT)
self.end = datetime.strptime(data.get("EndTime", data.get("end")), NVR_DATETIME_FORMAT)
self.DiskNo = data.get("DiskNo")
self.SerialNo = data.get("SerialNo")
self.size = int(data.get("FileLength"), 0) * 1024 if "FileLength" in data else data.get("size", 0)
self.filename = data.get("FileName", data.get("filename"))
self.filename_cleared = self.filename.split("/")[-1].replace("[", "_").replace("]", "_").replace("@","_")
self.channel = channel
self.stream = stream
def __str__(self) -> str:
return f"{self.filename_cleared}/{self.size}/{self.channel}/{self.stream}"
def __repr__(self) -> str:
return self.__str__()
@property
def to_b64(self):
dict_obj = dict(self.__dict__)
dict_obj["begin"] = self.begin.strftime(NVR_DATETIME_FORMAT)
dict_obj["end"] = self.end.strftime(NVR_DATETIME_FORMAT)
return base64.b64encode(json.dumps(dict_obj).encode()).decode("utf8")
@property
def json(self):
return {"filename": self.filename_cleared, "size": self.size, "b64": self.to_b64.replace("==", "")}
@staticmethod
def from_b64(b64):
data = json.loads(base64.b64decode(b64).decode('utf-8'))
print(data)
return File(data, data.get("channel"), data.get("stream"))
async def generate_first_bytes(self, client:DVRIPCam, version = 0):
client.logger.debug("init request")
#init request
await client.send(
1424,
{
"Name": "OPPlayBack",
"OPPlayBack": {
"Action": "Claim",
"Parameter": {
"PlayMode": "ByName",
"FileName": self.filename,
"StreamType": self.stream,
"Value": 0,
"TransMode": "TCP",
},
"StartTime": self.begin.strftime(NVR_DATETIME_FORMAT),
"EndTime": self.end.strftime(NVR_DATETIME_FORMAT),
},
},
)
client.logger.debug("download request")
#download request
msg = 1420
data = {
"Name": "OPPlayBack",
"OPPlayBack": {
"Action": "DownloadStart",
"Parameter": {
"PlayMode": "ByName",
"FileName": self.filename,
"StreamType": self.stream,
"Value": 0,
"TransMode": "TCP",
},
"StartTime": self.begin.strftime(NVR_DATETIME_FORMAT),
"EndTime": self.end.strftime(NVR_DATETIME_FORMAT),
},
}
#if client.socket_writer is None:
# client.logger.debug("socket writer is null")
# await client.connect()
client.logger.debug("Blocking busy")
await client.busy.acquire()
if hasattr(data, "__iter__"):
if version == 1:
data["SessionID"] = f"{client.session:#0{12}x}"
data = bytes(
json.dumps(data, ensure_ascii=False, separators=(",", ":")), "utf-8"
)
tail = b"\x00"
if version == 0:
tail = b"\x0a" + tail
pkt = (
struct.pack(
"BB2xII2xHI",
255,
version,
client.session,
client.packet_count,
msg,
len(data) + len(tail),
)
+ data
+ tail
)
client.logger.debug("Send first package")
client.socket_send(pkt)
client.logger.debug("Grab first response")
data = await client.socket_recv(20)
client.logger.debug(data)
if data is None or len(data) < 20:
return None
(
head,
version,
client.session,
sequence_number,
msgid,
len_data,
) = struct.unpack("BB2xII2xHI", data)
return len_data
async def get_file_stream(self, client: DVRIPCam, first_chunk_size) -> bytes:
yield bytes(await client.receive_with_timeout(first_chunk_size))
while True:
header = await client.receive_with_timeout(20)
len_data = struct.unpack("I", header[16:])[0]
if len_data == 0:
break
yield bytes(await client.receive_with_timeout(len_data))
await client.busy.release()
await client.send(
1420,
{
"Name": "OPPlayBack",
"OPPlayBack": {
"Action": "DownloadStop",
"Parameter": {
"FileName": self.filename,
"PlayMode": "ByName",
"StreamType": self.stream,
"TransMode": "TCP",
"Channel": self.channel,
"Value": 0,
},
"StartTime": self.begin.strftime(NVR_DATETIME_FORMAT),
"EndTime": self.end.strftime(NVR_DATETIME_FORMAT),
},
},
)
yield bytes(b"")
async def list_local_files(client: DVRIPCam, startTime, endTime, filetype, channel = 0, streamType = 0):
# 1440 OPFileQuery
result = []
data = await client.send(
1440,
{
"Name": "OPFileQuery",
"OPFileQuery": {
"BeginTime": startTime,
"Channel": channel,
"DriverTypeMask": "0x0000FFFF",
"EndTime": endTime,
"Event": "*",
"StreamType": f"0x0000000{streamType}",
"Type": filetype,
},
},
)
if data == None:
client.logger.debug("Could not get files.")
raise ConnectionRefusedError("Could not get files")
# When no file can be found
if data["Ret"] != 100:
client.logger.debug(
f"No files found for channel {channel} for this time range. Start: {startTime}, End: {endTime}"
)
return []
# OPFileQuery only returns the first 64 items
# we therefore need to add the results to a list, modify the starttime with the begintime value of the last item we received and query again
# max number of results are 511
result = data["OPFileQuery"]
max_event = {"status": "init", "last_num_results": 0}
while max_event["status"] == "init" or max_event["status"] == "limit":
if max_event["status"] == "init":
max_event["status"] = "run"
while len(data["OPFileQuery"]) == 64 or max_event["status"] == "limit":
newStartTime = data["OPFileQuery"][-1]["BeginTime"]
data = await client.send(
1440,
{
"Name": "OPFileQuery",
"OPFileQuery": {
"BeginTime": newStartTime,
"Channel": channel,
"DriverTypeMask": "0x0000FFFF",
"EndTime": endTime,
"Event": "*",
"StreamType": "0x00000000",
"Type": filetype,
},
},
)
result += data["OPFileQuery"]
max_event["status"] = "run"
if len(result) % 511 == 0 or max_event["status"] == "limit":
client.logger.debug("Max number of events reached...")
if len(result) == max_event["last_num_results"]:
client.logger.debug(
"No new events since last run. All events queried"
)
return result
max_event["status"] = "limit"
max_event["last_num_results"] = len(result)
client.logger.debug(f"Found {len(result)} files.")
return result