Dahua based Network Video Recorders

Do you have questions about writing plugins or scripts in Python? Meet the coders here.
Post Reply
User avatar
kgschlosser
Site Admin
Posts: 5146
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Dahua based Network Video Recorders

Post by kgschlosser » Sun Jul 14, 2019 5:56 pm

OK so here is the skinny on this.

EG 0.5 only

Dahua is a company that sells security cameras and networked video recorders. That are one of the major companies that sells them. They also are the manufacturer of quite a few other companies.. some examples are Lorex, Swann and Q-See.


What it does..
This script is going to pop up on the screen an OSD of the video feed of a camera that has motion detected on it. Once the motion detection alarm has gone away it will close the feed. You have the ability to full screen the feed by double clicking in it. You also have the ability to resize and reposition it. the size/position is not persistent at the moment.

This script works with Dahua based NVR's you will need to enter the IP of the NVR as well as the login information at the top.
place the code below into a python script action. place that action in your Autostart group to ensure it will only run a single time.

WARNING: ONLY RUN THIS SCRIPT A SINGLE TIME

there is also an attached zip file. You will need to extract the contents into /program files (x86)/EventGhost/lib27/site-packages


Enjoy!!

Code: Select all

# ip address of the dahua based NVR
IP_ADDRESS = '192.168.1.1'

# username and password if needed. if they are not needed then leave them empty
USER_NAME = 'admin'
PASSWORD = 'admin'


if USER_NAME and PASSWORD:
    URL = 'rtsp://{user_name}:{password}@{{ip_address}}:554/cam/realmonitor'
    URL = URL.format(user_name=USER_NAME, password=PASSWORD)
    MOTION_EVENT_URL = 'http://{user_name}:{password}@{{ip_address}}/cgi-bin/eventManager.cgi?action=getEventIndexes&code=VideoMotion'
    MOTION_EVENT_URL = MOTION_EVENT_URL.format(user_name=USER_NAME, password=PASSWORD)
    TITLE_URL = 'http://{user_name}:{password}@{{ip_address}}/cgi-bin/configManager.cgi?action=getConfig&name=ChannelTitle'
    TITLE_URL = TITLE_URL.format(user_name=USER_NAME, password=PASSWORD)


elif USER_NAME:
    URL = 'rtsp://{user_name}@{{ip_address}}:554/cam/realmonitor'
    URL = URL.format(user_name=USER_NAME)
    MOTION_EVENT_URL = 'http://{user_name}@{{ip_address}}/cgi-bin/eventManager.cgi?action=getEventIndexes&code=VideoMotion'
    MOTION_EVENT_URL = MOTION_EVENT_URL.format(user_name=USER_NAME)
    TITLE_URL = 'http://{user_name}@{{ip_address}}/cgi-bin/configManager.cgi?action=getConfig&name=ChannelTitle'
    TITLE_URL = TITLE_URL.format(user_name=USER_NAME)

else:
    URL = 'rtsp://{ip_address}:554/cam/realmonitor'
    MOTION_EVENT_URL = 'http://{ip_address}/cgi-bin/eventManager.cgi?action=getEventIndexes&code=VideoMotion'
    TITLE_URL = 'http://{ip_address}/cgi-bin/configManager.cgi?action=getConfig&name=ChannelTitle'

URL = URL.format(ip_address=IP_ADDRESS)
MOTION_EVENT_URL = MOTION_EVENT_URL.format(ip_address=IP_ADDRESS)
TITLE_URL = TITLE_URL.format(ip_address=ip_address)


import wx
import cv2
import threading

def scale_bitmap(bitmap, width, height):
    image = wx.ImageFromBitmap(bitmap)
    image = image.Scale(width, height, wx.IMAGE_QUALITY_HIGH)
    result = wx.BitmapFromImage(image)
    return result


class CameraFrame(wx.Frame):
    def __init__(self, parent, channel):
        wx.Frame.__init__(
            self,
            parent,
            style=wx.STAY_ON_TOP | wx.BORDER_NONE | wx.FRAME_SHAPED
        )

        # any parameters used. the parameters are everything after the ?
        # ?channel=5&subtype=0

        self.channel = channel

        url = URL + '?channel={channel}&subtype=0'.format(channel=channel)

        self.capture = cv2.VideoCapture(url)
        self.Bind(wx.EVT_ERASE_BACKGROUND, self.on_erase_background)
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_left_down)
        self.Bind(wx.EVT_MOTION, self.on_mouse_motion)
        self.Bind(wx.EVT_LEFT_UP, self.on_mouse_left_up)
        self.Bind(wx.EVT_LEFT_DCLICK, self.on_mouse_left_dclick)
        self.mouse_pos = None
        self.Bind(wx.EVT_CLOSE, self.on_close)
        self.original_rect = None
        self.resize_pos = None
        self.frames = []
        self.event = threading.Event()
        self.frame_event = threading.Event()
        self.thread = threading.Thread(target=self.redraw)
        self.frame_thread = threading.Thread(target=self.frame_loop)
        self.frame_thread.start()
        self.thread.start()

    def on_mouse_left_dclick(self, _):
        if self.original_rect is not None:
            self.SetRect(self.original_rect)
            self.original_rect = None
        else:
            x, y = self.GetPosition()

            display_count = wx.Display.GetCount()
            for i in range(display_count):
                display = wx.Display(i)
                rect = display.GetGeometry()

                if rect.Contains(x, y):
                    self.original_rect = self.GetRect()
                    self.SetRect(rect)

    def on_mouse_left_down(self, evt):
        if self.original_rect is not None:
            return

        if not self.HasCapture():
            w, h = self.GetClientSize()
            w -= 3
            h -= 3

            self.CaptureMouse()
            window_x, window_y = self.GetPosition()
            mouse_x, mouse_y = evt.GetPosition()
            rel_mouse_x = window_x + mouse_x
            rel_mouse_y = window_y + mouse_y

            self.mouse_pos = (rel_mouse_x, rel_mouse_y)

            left_rect = wx.Rect(0, 0, 6, h)
            top_left = wx.Rect(0, 0, 12, 12)
            top_right = wx.Rect(w - 12, 0, w, h + 12)
            right_rect = wx.Rect(w - 6, 0, 6, h)
            top_rect = wx.Rect(0, 0, w, 6)
            bottom_rect = wx.Rect(0, h - 6, w, h)
            bottom_left = wx.Rect(0, h - 12, 12, h)
            bottom_right = wx.Rect(w - 12, h - 12, w, h)

            if top_left.Contains(mouse_x, mouse_y):
                self.resize_pos = 4
            elif top_right.Contains(mouse_x, mouse_y):
                self.resize_pos = 5
            elif bottom_left.Contains(mouse_x, mouse_y):
                self.resize_pos = 7
            elif bottom_right.Contains(mouse_x, mouse_y):
                self.resize_pos = 6
            elif left_rect.Contains(mouse_x, mouse_y):
                self.resize_pos = 0
            elif right_rect.Contains(mouse_x, mouse_y):
                self.resize_pos = 2
            elif top_rect.Contains(mouse_x, mouse_y):
                self.resize_pos = 1
            elif bottom_rect.Contains(mouse_x, mouse_y):
                self.resize_pos = 3
            else:
                self.resize_pos = None

    def on_mouse_left_up(self, _):
        if self.HasCapture():
            self.SetCursor(wx.Cursor(wx.CURSOR_DEFAULT))
            self.ReleaseMouse()
            self.mouse_pos = None
            self.resize_pos = None

    def on_mouse_motion(self, evt):
        if self.HasCapture():
            window_x, window_y = self.GetPosition()
            mouse_x, mouse_y = evt.GetPosition()
            last_mouse_x, last_mouse_y = self.mouse_pos

            rel_mouse_x = window_x + mouse_x
            rel_mouse_y = window_y + mouse_y

            mouse_x = rel_mouse_x - last_mouse_x
            mouse_y = rel_mouse_y - last_mouse_y

            self.mouse_pos = (rel_mouse_x, rel_mouse_y)

            if self.resize_pos is None:
                window_x += mouse_x
                window_y += mouse_y
                self.SetPosition((window_x, window_y))
            else:
                width, height = self.GetSize()
                location = self.resize_pos

                if location == 0:
                    width += -mouse_x
                    window_x += mouse_x

                elif location == 1:
                    height += -mouse_y
                    window_y += mouse_y

                elif location == 2:
                    width += mouse_x

                elif location == 3:
                    height += mouse_y

                elif location == 4:
                    width += -mouse_x
                    window_x += mouse_x

                    height += -mouse_y
                    window_y += mouse_y

                elif location == 5:
                    width += mouse_x
                    height += -mouse_y
                    window_y += mouse_y

                elif location == 6:
                    width += mouse_x
                    height += mouse_y

                elif location == 7:
                    width += -mouse_x
                    window_x += mouse_x

                    height += mouse_y

                self.SetRect(wx.Rect(window_x, window_y, width, height))
        else:
            w, h = self.GetClientSize()
            w -= 3
            h -= 3

            mouse_x, mouse_y = evt.GetPosition()
            left_rect = wx.Rect(0, 0, 6, h)
            top_left = wx.Rect(0, 0, 12, 12)
            top_right = wx.Rect(w - 12, 0, w, h + 12)
            right_rect = wx.Rect(w - 6, 0, 6, h)
            top_rect = wx.Rect(0, 0, w, 6)
            bottom_rect = wx.Rect(0, h - 6, w, h)
            bottom_left = wx.Rect(0, h - 12, 12, h)
            bottom_right = wx.Rect(w - 12, h - 12, w, h)

            if top_left.Contains(mouse_x, mouse_y):
                resize_pos = 4
            elif top_right.Contains(mouse_x, mouse_y):
                resize_pos = 5
            elif bottom_left.Contains(mouse_x, mouse_y):
                resize_pos = 7
            elif bottom_right.Contains(mouse_x, mouse_y):
                resize_pos = 6
            elif left_rect.Contains(mouse_x, mouse_y):
                resize_pos = 0
            elif right_rect.Contains(mouse_x, mouse_y):
                resize_pos = 2
            elif top_rect.Contains(mouse_x, mouse_y):
                resize_pos = 1
            elif bottom_rect.Contains(mouse_x, mouse_y):
                resize_pos = 3
            else:
                resize_pos = None

            if resize_pos in (0, 2):
                self.SetCursor(wx.Cursor(wx.CURSOR_SIZEWE))
            elif resize_pos in (1, 3):
                self.SetCursor(wx.Cursor(wx.CURSOR_SIZENS))
            elif resize_pos in (4, 6):
                self.SetCursor(wx.Cursor(wx.CURSOR_SIZENWSE))
            elif resize_pos in (5, 7):
                self.SetCursor(wx.Cursor(wx.CURSOR_SIZENESW))
            else:
                self.SetCursor(wx.Cursor(wx.CURSOR_DEFAULT))

    def Close(self):
        self.event.set()
        self.frame_event.set()
        self.thread.join()
        self.frame_thread.join()
        self.capture.release()
        self.Show(False)
        self.Destroy()

    def on_close(self, _):
        self.Close()

    def on_erase_background(self, _):
        pass

    def OnPaint(self, _):
        pass

    def frame_loop(self):
        import time

        self.buffering = threading.Event()

        while not self.event.isSet():
            ret, frame = self.capture.read()
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            self.frames.append(frame)

            if len(self.frames) < 60 and self.buffering.isSet():
                self.buffering.clear()
            elif len(self.frames) == 100:
                self.buffering.set()

            while len(self.frames) > 100:
                time.sleep(0.02)

    def redraw(self):
        import time

        long_wait_time = 0.0
        while not self.event.isSet():
            start = time.time()

            if not self.IsShown():
                self.Show()

            width, height = self.GetClientSize()

            width -= 3
            height -= 3

            if not self.buffering.isSet():
                dc = wx.ClientDC(self)
                gcdc = wx.GCDC(dc)
                text = 'Buffering...'
                font = self.GetFont()
                gcdc.SetFont(font)
                gcdc.SetTextForeground(wx.RED)
                gcdc.SetTextBackground(wx.TransparentColour)
                text_width, text_height = gcdc.GetFullTextExtent(text, font)[:2]
                x = (width / 2) - (text_width / 2)
                y = height - (text_height * 2)
                gcdc.DrawText(text, x, y)

                gcdc.Destroy()
                del gcdc
                dc.Destroy()
                del dc

                self.buffering.wait()

            frame = self.frames.pop(0)
            frame = cv2.resize(frame, (width, height), interpolation=cv2.INTER_AREA)
            bmp = wx.BitmapFromBuffer(width, height, frame)

            dc = wx.MemoryDC()
            dc.SelectObject(bmp)
            gcdc = wx.GCDC(dc)

            mask_color = wx.Colour(255, 255, 3)
            gcdc.SetBrush(wx.TRANSPARENT_BRUSH)
            gcdc.SetPen(wx.Pen(mask_color, 6))

            for i in range(10):
                gcdc.DrawRoundedRectangle(-6 + i, -6 + i, width + i + 3, height + i + 3, 20)

            gcdc.SetPen(wx.Pen(wx.Colour(0, 255, 0, 255), 6))
            gcdc.DrawRoundedRectangle(3, 3, width - 6, height - 6, 20)

            gcdc.Destroy()
            del gcdc

            dc.SelectObject(wx.NullBitmap)
            dc.Destroy()
            del dc

            self.SetShape(wx.Region(bmp, mask_color, 10))
            dc = wx.ClientDC(self)
            gcdc = wx.GCDC(dc)
            gcdc.DrawBitmap(bmp, 0, 0)

            gcdc.Destroy()
            del gcdc

            stop = time.time()

            wait = (1.0 / 20.0) - (stop - start)

            if wait < 0.0:
                long_wait_time += wait

            if long_wait_time <= -(1.0 / 20.0):
                long_wait_time = 0.0
                self.frames.pop(0)

            self.event.wait(max(0.0, wait))


import requests
motion_event = threading.Event()

motion_frames = []

def motion_loop():
    while not motion_event.isSet():
        response = requests.get(MOTION_EVENT_URL)
        content = response.content

        if 'Error: No Events' in content:
            for frame in motion_frames[:]:
                frame.Close()
                motion_frames.remove(frame)
        else:
            content = content.split('\n')
            channels = []
            for line in content:
                channel = line.split('=')[-1].strip()
                channels += channel

            for frame in motion_frames[:]:
                if frame.channel not in channels:
                    frame.Close()
                    motion_frames.remove(frame)
                else:
                    channels.remove(frame.channel)

            response = requests.get(TITLE_URL)
            content = response.content
            
            titles = {}
            
            for line in content.split('\n'):
                channel = line.split('[', 1)[-1].split(']', 1)[0]
                title = line.split('=', 1)[-1]
                titles[channel] = title
                
            for channel in channels:
                frame = CameraFrame(None, channel)
                motion_frames.append(frame)
                eg.TriggerEvent(
                    prefix='Camera', 
                    suffix=titles[channel] + '.MotionDetected'
                )

        motion_event.wait(1.0)


motion_thread = threading.Thread(target=motion_loop)
motion_thread.start()


def on_close(*_):
    for frame in motion_frames:
        frame.Close()

    eg.Unbind('Main.OnClose', on_close)

    motion_event.set()
    motion_thread.join()


eg.Bind('Main.OnClose', on_close)
Attachments
cv2.zip
(24.33 MiB) Downloaded 46 times
If you like the work I have been doing then feel free to Image

User avatar
kgschlosser
Site Admin
Posts: 5146
Joined: Fri Jun 05, 2015 5:43 am
Location: Rocky Mountains, Colorado USA

Re: Dahua based Network Video Recorders

Post by kgschlosser » Sun Jul 14, 2019 6:06 pm

Oh i did want to mention.. There is a 5 second buffer for the video This is to smooth out the rendering process. If the video feed is full screen it takes a longer amount of time to draw the images. because the time to draw the images is slower then the frame rate i add the "extra" time it takes to draw the frames one that added time is equal to the length of time for the frame i will drop a frame and not draw it You will probably not even notice it. What was happening is it was causing a memory overflow problem after a while. because the frames would keep on piling up. I needed to come up with a mechanism to be able to make the image smooth. It also reduces any kind of feed problems caused by network latency problems.


oversimplified example of the handling of the frame dropping

Code: Select all

latency = 0

frame speed = 1 / 20

start = time
draw frame
stop = time

wait = frame_speed - stop - start

latency += wait
wait for next frame to be ready if wait is greater then 0

if -latency >= frame_speed:
    skip frame
    latency = 0
If you like the work I have been doing then feel free to Image

Post Reply