Skip to content

Instantly share code, notes, and snippets.

@x0a
Last active November 28, 2019 23:45
Show Gist options
  • Save x0a/8784b504a4ae94ac45df31bb1ee410fb to your computer and use it in GitHub Desktop.
Save x0a/8784b504a4ae94ac45df31bb1ee410fb to your computer and use it in GitHub Desktop.
Zmodo Viewer, bash: View multiple cams in a grid with MPlayer
#!/bin/bash
server="securitysystem" # my router-defined hostname for my dvr, use ip instead
port=8000
res_x=960 # cam resolution, mine is 960H grabbed from running show_cam once w/o -really-silent and watching the output
res_y=480
cams=(1 3 4) #show cams 1, 3, and 4, skipping 2 since mine isn't connected
function get_current_workspace() {
workspace=`wmctrl -d | grep '*' | cut -d ' ' -f1`
}
function move_to_workspace() {
local window_id=`wmctrl -lp | grep $1 | awk '{print $1}'`
echo "Moving window $window_id (PID: $1) to workspace $workspace"
wmctrl -i -r $window_id -t $workspace
}
function set_screen_res() {
screenx=$(xrandr --current | grep '*' | uniq | awk '{print $1}' | cut -d 'x' -f1)
screeny=$(xrandr --current | grep '*' | uniq | awk '{print $1}' | cut -d 'x' -f2)
}
function set_grid() { # args: $1 = number of cams, $2 = round to nearest
local grid_size=$(( (($1 + $2 - 1) / $2) * $2 ))
cols=$(( $grid_size / 2 )) # if you wanted, you could set a custom number of columns per row
rows=$(( $grid_size / $cols )) # and a custom number of rows
}
function do_math(){ # does math in floating point and returns a regular int
last_math=`echo "scale=6; x=$1; scale=0; x / 1" | bc`;
}
function set_cam_dimensions() { # resize cams so they fit into their cell size, maintaining aspect ratio
local cell_x=$(( $screenx / $cols ))
local cell_y=$(( $screeny / $rows ))
do_math "(($cell_x / $cell_y) > ($res_x / $res_y))"
if [ $last_math -eq 1 ]; then
cam_y=$(( $cell_y ))
do_math "($cell_y / $res_y) * $res_x"
cam_x=$(( $last_math ))
else
cam_x=$(( $cell_x ))
do_math "($cell_x / $res_x) * $res_y"
cam_y=$(( $last_math ))
fi
}
function show_cam() { # args: $1 = cam number, $2 = x position, #3 = y position
local cam=$(($1 - 1))
# send header requesting 720p stream for $cam
# then trim first 12 bytes of received data, which is only an echo of what we sent
# then add magic bytes to identify as the first NAL unit the h264 stream
# finally, open borderless mplayer in stdin mode, with specific position and dimensions
echo "Requesting cam $cam at $server:$port $cam_x by $cam_y $2:$3"
echo -ne "\x55\x55\xaa\xaa\x00\x00\x00\x00\x00\x$cam\x00\x50" \
| nc $server $port \
| tail -c +12 \
| (echo -ne '\x00\x00\x00\x01' && cat) \
| mplayer -really-quiet -noborder -demuxer h264es -x $cam_x -y $cam_y -geometry "$2:$3" -fps 25 - &
# mplayer is run in background to get it's PID, used to move to initial workspace
# then we go back to synchrous mode by waiting on that PID
local job_id=$!
sleep 1
move_to_workspace $job_id
wait $job_id
}
function show_cam_persist() { # re-open cam if connection is lost
while true; do
show_cam $2 $3 $4
if kill -0 $1 > /dev/null 2>&1 ; then # check parent process still exists
echo "Cam connection closed, checking again in 10 seconds"
sleep 10 # Zmodo DVR disconnects around midnight fsr, blocks connections that happen too soon after that.
# i've experimented with 1s and 5s, both lead to infinite disconnects/reconnects. 10s works
else
echo "Parent process killed, ending session"
break
fi
done
}
function main() { # args: $1 = parent PID, $2: boolean = persist
local row=0
local col=0
local index=0
local posx=0;
local posy=0;
for i in ${cams[@]}; do
row=$(( $index / $cols ))
col=$(( $index % $cols ))
posx=$(( $col * $cam_x ))
posy=$(( ($row * $cam_y) + 48 )) # offset y position by height of gnome title bar
if $2; then
show_cam_persist $1 $i $posx $posy &
else
show_cam $i $posx $posy &
fi
index=$(( $index + 1 ))
done
}
get_current_workspace
set_screen_res
set_grid ${#cams[@]} 4 # 1-4 cams = 4 cells, 4-8 = 8 cells total, etc.
set_cam_dimensions
if [ "$1" == "--persist" ]; then
persist=true
else
persist=false
fi
main $$ $persist
wait
@x0a
Copy link
Author

x0a commented Nov 19, 2019

Imgur

3-cam example. If you plan on having these running 24/7, I recommend you keep them in their own workspace. Run with --persist argument to reopen cams if connection closes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment