Skip to content

Instantly share code, notes, and snippets.

@mwvent
Created April 10, 2020 21:31
Show Gist options
  • Save mwvent/c9adf1f0f4466ed2a188965565e1fcc1 to your computer and use it in GitHub Desktop.
Save mwvent/c9adf1f0f4466ed2a188965565e1fcc1 to your computer and use it in GitHub Desktop.
Play Top Zoneminder Recordings as a loopback video device
future==0.18.2
mysql-connector==2.2.9
pkg-resources==0.0.0
#!/bin/bash
cd "$(dirname "$0")"
source ./bin/activate
modprobe v4l2loopback
#while : ; do
nice -n 19 python zmReviewLoopBack3.py
sleep 5
#done
import mysql.connector
import os
import ffmpeg
import subprocess
import threading
from datetime import timedelta
from datetime import datetime
from subprocess import Popen, PIPE
import time
# Settings
tempvidsfolder = "/mnt/HDD2/cctvreplayvids"
dbHost = "localhost" #MySql Zoneminder Database server
dbUser = "zmuser" #Mysql Zoneminder Database user ( TODO load from /etc/zm )
dbPass = "zmpass" #MySql Zoneminder Database pass ( TODO load from /etc/zm )
# Output Thread - ffmpeg subprocess accepting RAW video and outputting to v4l2 loopback
# TODO Detect v4l2 loopback device instead of hard coded video0
output_proc_cmd = [ "ffmpeg" ,
"-v", "error" ,
"-f", "rawvideo" ,
"-s", "640x480" ,
"-pix_fmt", "yuv420p" ,
"-r", "10", "-re",
"-i", "-" ,
"-vcodec", "rawvideo" ,
"-pix_fmt", "yuv420p" ,
"-vf", "scale=640x480",
"-threads", "0" ,
"-f", "v4l2" ,
"/dev/video0"
]
output_proc = subprocess.Popen(output_proc_cmd, stdin=PIPE)
# Processing thread - keep tempvidsfolder up to date with current videos
def processVideos() :
db = mysql.connector.connect(host=dbHost, user=dbUser, passwd=dbPass, database="zm", autocommit=True)
while True :
SQL = """SELECT
Frames.Delta,
Concat( Path, '/', MonitorId, '/', Date(StartTime), '/', Events.Id, '/', Events.Id, '-video.mp4') ,
StartTime
FROM Events
INNER JOIN
Storage ON StorageId=Storage.Id
INNER JOIN
Frames ON Frames.EventId = Events.Id
WHERE
Frames.Type='Alarm' AND
(
( Notes LIKE '%HasPerson: 1%' AND (MonitorId = 11 OR MonitorId = 12 OR MonitorId = 15) ) OR
( Notes LIKE '%PIR%' OR NOTES LIKE '%Doorbell%' )
)
AND StartTime > DATE_SUB(NOW(), INTERVAL 1 DAY)
ORDER BY Events.Id, Frames.Delta ASC;"""
dbcursor = db.cursor()
dbcursor.execute(SQL)
dbresult = dbcursor.fetchall()
videosegs_index_file = 0
videosegs_index_start = 1
videosegs_index_end = 2
videosegs = []
next_videosegs_entry = []
last_videofile = ""
last_delta = -100
for row in dbresult :
row_delta, row_videofile, row_starttime = row
next_videoseg_started = len(next_videosegs_entry) == 3
new_video = not row_videofile == last_videofile
# New Video File Started
if new_video :
# no previous data start fresh
if not next_videoseg_started :
next_videosegs_entry= [ row_starttime, row_videofile, row_delta ]
else :
# finish last video segment and start next
next_videosegs_entry.append( last_delta )
videosegs.append(next_videosegs_entry)
next_videosegs_entry= [ row_starttime, row_videofile, row_delta ]
# if time is more than 2 second away from the previous time
# we have a gap signifiant enougth to start a new entry
deltajumped = row_delta >= (last_delta + 2)
if not new_video and deltajumped and next_videoseg_started:
next_videosegs_entry.append( last_delta )
videosegs.append(next_videosegs_entry)
next_videosegs_entry= [ row_starttime, row_videofile, row_delta ]
# finish
last_delta, last_videofile, last_starttime = row
# clean old temp videos
# Get List Of Valid Tempfiles
validTempFiles=[]
for segment in videosegs :
starttime, videofile, ss, to = segment
tempFile = tempvidsfolder + "/" + os.path.basename(videofile) + "-" + str(ss) + ".raw"
validTempFiles.append(os.path.basename(tempFile))
for root, dirs, files in os.walk(tempvidsfolder) :
for file in files:
if not file in validTempFiles :
print("Removing " + file)
os.remove(tempvidsfolder + "/" + file)
# start writing new videos
for segment in videosegs :
starttime, videofile, ss, to = segment
if ss == to :
continue
if starttime.day == datetime.now().day :
drawtextFilterText = "TODAY "
else :
drawtextFilterText = "YESTERDAY "
starttime += timedelta(seconds=int(ss))
drawtextFilterText += starttime.strftime("%H\\:%M\\:%S")
drawtextFilterA = [
"fontfile=/usr/share/fonts/truetype/ubuntu/Ubuntu-B.ttf",
"text='" + drawtextFilterText +"' ",
"fontcolor=white",
"fontsize=16",
"box=1",
"boxcolor=black",
"x=(w-text_w)",
"y=(h-text_h-line_h)"
]
drawTextFilter = "drawtext=" + ": ".join(drawtextFilterA)
boxfilter=",[0:v]scale=ih*16/9:-1,boxblur=luma_radius=min(h\\,w)/ 20:luma_power=1:chroma_radius=min(cw\\,ch)/20:chroma_power=1[bg];[bg][0:v]overlay=(W-w)/2:(H-h)/2,crop=h=iw*9/16"
tempFile = tempvidsfolder + "/" + os.path.basename(videofile) + "-" + str(ss) + ".raw"
if not os.path.exists( tempFile ) :
print("Creating " + tempFile )
cmd = [ "ffmpeg", "-v", "error", "-probesize", "32", "-analyzeduration", "32",
"-i", videofile,
"-ss", str(ss),
"-to", str(to),
"-filter_complex", "scale=640:480,"+drawTextFilter,
"-f", "rawvideo",
"-r", "2",
"-pix_fmt", "yuv420p",
tempFile
]
print(cmd)
subprocess.call(cmd)
time.sleep(10)
# Play thread - read video data from temp files and send to output subprocess
def readVideos() :
while True :
for root, dirs, files in os.walk(tempvidsfolder) :
files.sort()
for file in files :
cmd = [ "cat", tempvidsfolder + "/" + file ]
segment_proc = subprocess.Popen(cmd, stdout=output_proc.stdin)
segment_proc.communicate()
break;
t = threading.Thread( target = processVideos )
t.start()
readVideos()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment