Security cameras and video

We have a couple of security cameras mounted on our home. These are inexpensive Reolink POE cameras which are connected via ethernet to our network. Hardwired cameras such as these are vastly superior to wireless cameras and since I’ve set up the network to isolate these devices they are much safer than typical cloud connected cameras.

Continual recording

One basic function of security cameras are to provide a continual video history of events. There are many applications one can run to do this, such as:

For my case, I wanted something much simpler and instead use ffmpeg directly to record video. A simple bash script is called using cron:

#!/bin/sh
# record_lq.sh
# Record ip cam in segments

STARTTIME=$(date +"%Y%m%dT%H%M%S")

## IP Camera Names ##
FRONT_CAM="${STARTTIME}_FRONT"
BACK_CAM="${STARTTIME}_BACK"

CAM_USERNAME="<camera username>"
CAM_PASSWORD="<camera password>"

FRONT_IP="<camera IP>"

## Network and Local Storage Locations  ##
# Pay attending to when a trailing '/' is used and when it is not
## Network and Local Storage Locations  ##
HQDIR="<path>/<to>/<save>/<hq>" #Trailing '/' is necessary here
LQDIR="<path>/<to>/<save>/<lq>"

## Record Time per File ##
LQLENGTH="3600" # (Runtime expressed in seconds)

## Record Settings ##
#
# -v 0    // Log level = 0
# -i      // Input url
# -vcidec // Set the video codec. This is an alias for "-codec:v".
# -an     // Disable audio recording
# -t      // Stop writing the output after its duration reaches duration
#
# ffmpeg -v 0 -rtsp_transport tcp -i "rtsp://${CAM_USERNAME}:${CAM_PASSWORD}@${FRONT_IP}:554/h264Preview_01_sub" -vcodec copy -acodec copy -t ${LQLENGTH} ${LQDIR}${FRONT_CAM}.mp4 &
# ffmpeg -v 0 -rtsp_transport tcp -i "rtsp://${CAM_USERNAME}:${CAM_PASSWORD}@${BACK_IP}:554/h264Preview_01_sub" -vcodec copy -acodec copy -t ${LQLENGTH} ${LQDIR}${BACK_CAM}.mp4 &

ffmpeg -i "rtmp://${CAM_USERNAME}:${CAM_PASSWORD}@${FRONT_IP}/bcs/channel0_sub.bcs?token=dsfde&channel=0&stream=0&user=${CAM_USERNAME}&password=${CAM_PASSWORD}" -vcodec copy -acodec copy -t ${LQLENGTH} ${LQDIR}${FRONT_CAM}.mp4 &
ffmpeg -i "rtmp://${CAM_USERNAME}:${CAM_PASSWORD}@${BACK_IP}/bcs/channel0_sub.bcs?token=dfwa&channel=0&stream=0&user=${CAM_USERNAME}&password=${CAM_PASSWORD}" -vcodec copy -acodec copy -t ${LQLENGTH} ${LQDIR}${BACK_CAM}.mp4 &

The script above is called every LQLENGTH seconds and it simply uses fmpeg to capture the network stream and save to a file. Each camera will have a slightly different url but some searching online will find the appropriate value. An example cron task is below, which will call the script every 1 hour = 3600 seconds.

0 */1 * * *  bash /<path>/<to>/record_lq.sh

Eventually you’ll want to delete old videos and the following will do exactly that.

#!/bin/sh
# record_delete.sh
# Record ip cam in segments

LQDIR="<path>/<to>/<lq>" # Do not include tail '/' (bash 'find' command will not execute property)
HQDIR="<path>/<to>/<hq>" # Do not include tail '/'

## Delete LQ files : -mtime X is files strictly greater than X days old
find ${LQDIR} -maxdepth 1 -type f -mtime +30 -exec rm {} \;
## Delete HQ files
find ${HQDIR} -maxdepth 1 -type f -mtime +0 -exec rm {} \;

In my case I’m able to store 30 days of low quality video on an old external 320GB USB harddrive with plenty of leftover space. This approach has already proven useful/interesting and lets me capture all sorts of interesting events.

Image classification

It’s all good to simply record video, but everyone knows machine learning should be applied to every possible problem. In my case, I’m using homeassistant and frigate to perform image classification on the live video. Frigate uses a hardware TPU to run an image classifier on the live video, then report/record the findings. Combined with homeassistant I can then easily setup actions/notifications using this.

For example, an automation to send a notification using Signal anytime a person is detected when no one is home:

- id: frigate_signal 
  alias: Frigate Signal notification
  trigger:
    - platform: mqtt
      topic: "frigate/events"
  condition:
  - condition: and
    conditions:
    - condition: state
      entity_id: "binary_sensor.shankar_presence"
      state: 'off'
    - condition: state
      entity_id: "binary_sensor.christine_presence"
      state: 'off'
  action:
    - delay: "00:00:45"
    - service: notify.signal_group
      data_template:
        message: "{{ now().strftime('%Y-%m-%dT%H:%M:%S') }} - {{trigger.payload_json['after']['label']}} detected by {{trigger.payload_json['after']['camera']}} camera."
        data:
          attachments:
            - "/media/frigate/clips/{{trigger.payload_json['after']['camera']}}-{{trigger.payload_json['after']['id']}}.jpg"

Or simply to detect when our cat is outside

Cat 1 Cat 2
lily princess

Timelapse

Finally, the security cameras are used to capture an image at regular intervals, then these images are automatically combined using ffmpeg into videos. Currently I’m doing the following which seems like a good approach to capture long term changes in our garden

  1. Capture 1 frame every 10 minutes - combine into a video at end of the day
  2. Combine 1 month of daily videos into a monthly video and upload to youtube for easy viewing
  3. Capture 1 frame every day at noon and eventually create a very long duration timelapse

To capture a frame, ffmpeg again comes to the rescue with suitable schedule in cron


#!/bin/sh
# record_lq.sh
# Record ip cam in segments

STARTTIME=$(date +"%Y%m%dT%H%M")

## IP Camera Names ##
FRONT_CAM="${STARTTIME}_FRONT"
BACK_CAM="${STARTTIME}_BACK"

CAM_USERNAME="<cam username>"
CAM_PASSWORD="<cam password>"

FRONT_IP="192.168.200.15"
BACK_IP="192.168.200.16"

## Network and Local Storage Locations  ##
# Pay attending to when a trailing '/' is used and when it is not
## Network and Local Storage Locations  ##
IMAGE_DIR="/<path>/<to>/<images>"
if [[ ! -e ${IMAGE_DIR} ]]; then
    mkdir -p ${IMAGE_DIR}
fi

ffmpeg -i "rtmp://${CAM_USERNAME}:${CAM_PASSWORD}@${FRONT_IP}/bcs/channel0_main.bcs?token=ssdad&channel=0&stream=0&user=${CAM_USERNAME}&password=${CAM_PASSWORD}" -t 1 -r 1 ${IMAGE_DIR}${FRONT_CAM}.jpg &
ffmpeg -i "rtmp://${CAM_USERNAME}:${CAM_PASSWORD}@${BACK_IP}/bcs/channel0_main.bcs?token=sdasdasd&channel=0&stream=0&user=${CAM_USERNAME}&password=${CAM_PASSWORD}" -t 1 -r 1 ${IMAGE_DIR}${BACK_CAM}.jpg &

The ffmpeg command simply captures a single frame and saves it as an image. You can call this as often as desired/space allows using cron.

Once you have sufficient images, you can again use ffmpeg to combine them into a video


#!/bin/sh
# Create timelapse from the previous day

YESTERDAY=$(date -d "yesterday" +"%Y%m%d")

IMAGE_DIR="<path>/<to>/<images>"

# convert to video
# -r 30 - framerate of output video - compute as function of length
# get date using $(date -d "last month" +"%Y%m%d") or equivalent

# ffmpeg -r 30 -pattern_type glob -i 20211010T*.jpg -vcodec libx264 -crf 18 -pix_fmt yuv420p timelapse.mp4
# libx265 - seems to fail for signal attachments
ffmpeg -r 30 -pattern_type glob -i "${IMAGE_DIR}${YESTERDAY}*_FRONT.jpg" -vcodec libx264 -crf 28 -preset veryslow -pix_fmt yuv420p ${IMAGE_DIR}${YESTERDAY}_FRONT.mp4
ffmpeg -r 30 -pattern_type glob -i "${IMAGE_DIR}${YESTERDAY}*_BACK.jpg" -vcodec libx264 -crf 28 -preset veryslow -pix_fmt yuv420p ${IMAGE_DIR}${YESTERDAY}_BACK.mp4

# delete images used 
# rm "${IMAGE_DIR}${YESTERDAY}*.jpg"
find ${IMAGE_DIR} -maxdepth 1 -type f -name "${YESTERDAY}*.jpg" -exec rm {} +

Also, every month I combine the daily videos into a larger monthly video using the following:

#!/bin/sh
# Create timelapse from the previous month

LAST_MONTH=$(date -d "last month" +"%Y%m")
THIS_MONTH=$(date -d "this month" +"%Y%m")

IMAGE_DIR="<path>/<to>/<videos>"

FRONT_FILES="${IMAGE_DIR}front.txt"
BACK_FILES="${IMAGE_DIR}back.txt"

FRONT_VIDEO="${IMAGE_DIR}/monthly/${LAST_MONTH}_FRONT.mp4"
BACK_VIDEO="${IMAGE_DIR}/monthly/${LAST_MONTH}_BACK.mp4"

for filename in "${IMAGE_DIR}${LAST_MONTH}"*_FRONT.mp4; do
    [ -e "$filename" ] || continue
    echo "file '${filename}'" >> ${FRONT_FILES}
done

for filename in "${IMAGE_DIR}${LAST_MONTH}"*_BACK.mp4; do
    [ -e "$filename" ] || continue
    echo "file '${filename}'" >> ${BACK_FILES}
done

# concat mp4s
ffmpeg -hide_banner -r 30 -f concat -safe 0 -i ${FRONT_FILES} -vcodec libx264 -crf 28 -preset veryslow -pix_fmt yuv420p ${FRONT_VIDEO}
ffmpeg -hide_banner -r 30 -f concat -safe 0 -i ${BACK_FILES} -vcodec libx264 -crf 28 -preset veryslow -pix_fmt yuv420p ${BACK_VIDEO}

# delete mp4s
find ${IMAGE_DIR} -maxdepth 1 -type f -name "${LAST_MONTH}*.mp4" -exec rm {} +

# Delete file list documents
rm ${FRONT_FILES}
rm ${BACK_FILES}

# Upload to youtube
conda activate timelapse

youtube-upload \
    --title="${LAST_MONTH}_FRONT" \
    --description="Timelapse video" \
    --client-secrets="client_secrets.json" \
    --playlist="Timelapse Front" \
    --privacy="private" \
    ${FRONT_VIDEO}

youtube-upload \
    --title="${LAST_MONTH}_BACK" \
    --description="Timelapse video" \
    --client-secrets="client_secrets.json" \
    --playlist="Timelapse Back" \
    --privacy="private" \
    ${BACK_VIDEO} 

The final step is to upload to youtube. Long duration timelapse such as this is a long term project so hopefully given enough time this system will capture something interesting for us. You can find this timelapse code on Github.