Our weather camera project originated during the Bullock Fire in 2002. We archived images from a television station’s camera on Mount Bigelow and manually generated our first timelapse. Soon afterwards, we installed a DLink DCS-1000 IP camera in a CCTV outdoor enclosure at the site. In 2006, we installed another DCS-1000 on Mount Lemmon, 4 miles away. These cameras have been upgraded over the years and are now 5 megapixel devices. The software has evolved as well. We now grab snapshots every 30 seconds and generate a timelapse for each camera nightly. The timelapses are uploaded to YouTube via API.
This document shows the commands necessary to implement our cameras on CentOS 8. The same packages should be available on other flavors of Linux, such as Rocky, Debian and Ubuntu. Replace the package manager from CentOS to that of your distribution. The various shell scripts may need to have command paths adjusted.
Since we have SSDs for some OS drives, camera images are stored in /export/cameras to permit a traditional hard drive or NAS to be mounted in this location. This avoids putting the high number of writes on the SSD drive.
Here are the various scripts:
camerascript:
#!/bin/bash
#Invoke with camerascript CAMNAME CAMIP OVERLAYNAME CAMTYPE
#Camtype 1 is older HTTP GW Security 5MP Model AC400L
#Camtype 2 is newer RTSP GW Security 5MP Model
CAMNAME=`echo $1`
CAMIP=`echo $2`
OVERLAYNAME=`echo $3`
CAMTYPE=`echo $4`
DIRECTORY=/export/cameras
DATE=`/usr/bin/date +%Y%m%d%H%M%S`
if [ -f /tmp/$CAMNAME.lock ] ; then
echo Lock Exists
/usr/bin/kill `/usr/bin/ps ax | /usr/bin/egrep -v grep | /usr/bin/grep cameras/$CAMNAME | /usr/bin/awk '{print $1}'`
exit 1
else
if [ ! -d $DIRECTORY/$CAMNAME ]; then
mkdir -p $DIRECTORY/$CAMNAME
fi
#/usr//bin/sleep 25
/bin/touch /tmp/$CAMNAME.lock
if [ "$CAMTYPE" = "" ]; then
echo "Error: CAMTYPE is non-existent or empty"
elif [ "$CAMTYPE" -eq 1 ]; then
/usr/bin/curl --connect-timeout 5 --max-time 30 -s -o $DIRECTORY/$CAMNAME/$DATE.jpg "http://$CAMIP/cgi-bin/snapshot.cgi?stream=1"
elif [ "$CAMTYPE" -eq 2 ]; then
/usr/bin/ffmpeg -rtsp_transport tcp -loglevel fatal -ss 2 -i rtsp://$CAMIP:554/h264?username=cam\&password=cam -y -f image2 -qscale 0 -frames 1 $DIRECTORY/$CAMNAME/$DATE.jpg
elif [ "$CAMTYPE" -eq 3 ]; then
/usr/bin/curl --connect-timeout 5 --max-time 30 -s -o $DIRECTORY/$CAMNAME/$DATE.jpg "http://$CAMIP/cgi-bin/snapshot.cgi?stream=0%20(main)&username=admin&password=123456"
fi
if [ -s $DIRECTORY/$CAMNAME/$DATE.jpg ]
then
/bin/composite $DIRECTORY/$OVERLAYNAME $DIRECTORY/$CAMNAME/$DATE.jpg $DIRECTORY/$CAMNAME-new.jpg
/usr/bin/mv $DIRECTORY/$CAMNAME-new.jpg $DIRECTORY/$CAMNAME.jpg
/usr/bin/ln -sf $DIRECTORY/$CAMNAME/$DATE.jpg $DIRECTORY/$CAMNAME-raw.jpg
/bin/convert $DIRECTORY/$CAMNAME/$DATE.jpg -resize 320x240 $DIRECTORY/$CAMNAME-thumb-new.jpg
/usr/bin/mv $DIRECTORY/$CAMNAME-thumb-new.jpg $DIRECTORY/$CAMNAME-thumb.jpg
else
/usr/bin/rm -f $DIRECTORY/$CAMNAME/$DATE.jpg
fi
/usr/bin/rm -f /tmp/$CAMNAME.lock
fi
daily-master:
#!/bin/bash
/home/camera/daily-timelapse publiccamera public "Camera at the Repeater Site"
/home/camera/daily-timelapse privatecamera unlisted "Camera at my House"
daily-timelapse:
#!/bin/bash
#Invoke with daily-timelapse CAMNAME PRIVACY CAMDESCRIPTION
#
#PRIVACY is unlisted public or private
CAMNAME=`echo $1`
PRIVACY=`echo $2`
CAMDESCRIPTION="`echo $3`"
DIRECTORY=/export/cameras
CAMDIR=`echo $DIRECTORY/$CAMNAME`
DATE=`/bin/date +%Y-%m-%d --date="yesterday"`
cd $CAMDIR
/usr/bin/rm -f $CAMDIR/timelapse.avi
/usr/bin/ffmpeg -f image2 -framerate 30 -pattern_type glob -i '*.jpg' -framerate 30 -vcodec mjpeg timelapse-new.avi
/usr/bin/rm $CAMDIR/2*.jpg
/usr/bin/mv $CAMDIR/timelapse-new.avi $CAMDIR/timelapse.avi
/usr/local/bin/youtube-upload --description=Timelapse --title="$CAMDESCRIPTION $DATE" --privacy $PRIVACY $CAMDIR/timelapse.avi
/usr/bin/rm -f $CAMDIR/timelapse.avi
noonencode:
#!/bin/bash
YEAR=`/usr/bin/date +%Y`
#declare -a CameraList=("lomalinda" "mlre" "house5" "mlfd2" "bigelow-w")
#declare -a CameraList=("cabin1" "cabin 7")
declare -a CameraList=("lemmon1" "lemmon2" "lemmon9")
for cam in ${CameraList[@]}; do
DIRECTORY=/export/cameras/$cam-noon
cd $DIRECTORY
/usr/bin/rm -f $DIRECTORY/timelapse.avi
/usr/bin/ffmpeg -f image2 -framerate 30 -pattern_type glob -i '*.jpg' -framerate 30 -vcodec mjpeg timelapse-new.avi
/usr/bin/rm $DIRECTORY/$YEAR*.jpg
/usr/bin/mv $DIRECTORY/timelapse-new.avi $DIRECTORY/timelapse.avi
/usr/local/bin/youtube-upload --description=Timelapse --title="$cam $YEAR" --privacy public $DIRECTORY/timelapse.avi
#/usr/bin/rm -f $DIRECTORY/timelapse.avi
done
noonscript: (Be sure to update your coordinates.)
#!/bin/bash
DIRECTORY=/export/cameras
DATE=`/usr/bin/date +%Y%m%d`
NOON=`/usr/local/bin/sunwait report 32.441972N 110.780247W | /usr/bin/grep directly | /usr/bin/awk '{print $4}' | /usr/bin/cut -b 1,2,4,5`
#declare -a CameraList=("lomalinda" "mlre" "house5" "mlfd2" "bigelow-w")
#declare -a CameraList=("cabin1" "cabin7")
declare -a CameraList=("lemmon1" "lemmon2" "lemmon9")
for cam in ${CameraList[@]}; do
if [ ! -d $DIRECTORY/$cam-noon ]; then
mkdir -p $DIRECTORY/$cam-noon
fi
FILE=`/usr/bin/ls $DIRECTORY/$cam/$DATE$NOON??.jpg | /usr/bin/tail -1`
#echo File is $FILE
/usr/bin/cp $FILE $DIRECTORY/$cam-noon/
done
Here’s a sample crontab for the camera user:
#Create timelapses at midnight
1 0 * * * /home/camera/daily-master >> /dev/null 2>&1
#Grab the solar noon image from selected cameras for annual timelapse
0 14 * * * /home/camera/noonscript >> /dev/null 2>&1
#Generate annual timelapse on New Years Eve Day
0 15 31 12 * /home/camera/noonencode >> /dev/null 2>&1
#Each camera gets 2 entries for 30-second image collection
* * * * * /home/camera/camerascript publiccamera 192.168.32.25 logooverlay.png 1 >> /dev/null 2>&1
* * * * * /usr/bin/sleep 30 ; /home/camera/camerascript publiccamera 192.168.32.25 logooverlay.png 1 >> /dev/null 2>&1
* * * * * /home/camera/camerascript privatecamera 192.168.42.25 logooverlay.png 1 >> /dev/null 2>&1
* * * * * /usr/bin/sleep 30 ; /home/camera/camerascript privatecamera 192.168.42.25 logooverlay.png 1 >> /dev/null 2>&1
Create an index.html file indexing your cameras in /export/cameras . The logooverlay.png files are transparent PNG files with a club logo, copyright notice and/or other information which is superimposed onto the publicly viewable images.
Be sure to create directories for each camera as /export/cameras/cameraname
Creating a Youtube channel and setting up the API key is beyond the scope of this document, but is well documented elsewhere.
To keep modern browsers happy, we use Let’s Encrypt and CertBot for SSL-encrypted connections to the webserver. Let’s Encrypt has extensive documentation on setup for the Apache webserver that we use.
Here are the various commands to install and configure the required packages, assuming a minimal OS installation. Some packages are to make future troubleshooting easier.
#yum -y update # vi /etc/selinux/config SELINUX=disabled # systemctl disable firewalld # yum -y install postfix # vi /etc/aliases root: [email protected] camera: root # newaliases # vi /etc/postfix/main.cf relayhost = mail.mydomain.com myhostname = camera.mydomain.com mydomain = mydomain.com # systemdctl enable postfix # yum -y install httpd httpd-tools # dnf install epel-release dnf-utils # dnf config-manager --set-enabled powertools # yum-config-manager --add-repo=https://negativo17.org/repos/epel-multimedia.repo # dnf install ffmpeg # yum -y install net-tools ntp ftp whois curl wget lsof # yum -y install python38 # easy_install-3.8 --upgrade google-api-python-client # adduser camera -m -d /home/camera -s /bin/bash # passwd camera # vi /etc/sshd/sshd_config PermitRootLogin no # dnf install ImageMagick ImageMagick-devel # yum -y install telnet # mkdir -p /export/cameras # chown camera:camera /export/cameras $ mkdir youtube-upload $ cd youtube-upload/ $ wget https://raw.githubusercontent.com/youtube/api-samples/master/python/upload_video.py $ chmod +x upload_video.py # pip-3.8 install --upgrade google-auth google-auth-oauthlib google-auth-httplib2 # pip3.8 install googlecl # pip3.8 install --upgrade google-api-python-client oauth2client progressbar2 # cd /usr/local/src/ # wget https://github.com/tokland/youtube-upload/archive/master.zip # mv master.zip youtube-upload.zip # yum -y install unzip # unzip youtube-upload.zip # mv youtube-upload-master youtube-upload # cd youtube-upload # python3.8 setup.py install # cd /usr/local/src # wget https://github.com/risacher/sunwait/archive/master.zip # mv master.zip sunwait.zip # unzip sunwait.zip # mv sunwait-master sunwait # cd sunwait # yum -y install gcc-c++ gcc make # make # mv sunwait /usr/local/bin/ Edit httpd.conf to change the root directory from /var/www/html to /export/cameras # systemctl enable httpd # systemctl start httpd