PlantCam (how it works)

Technical information on how my plant camera operates using ffmpeg and raspistill
2 min read

Live frames were visible at /plantcam. Check back later for new plants!

The camera is a raspberry pi “noir” camera v2. Sensor data is captured using a BME280 sensor.

The final video of the basil plant. Check back later for new videos!


I’m using raspistill to capture the photos. It’s adding an annotation (with some metrics from a BME280 environment sensor) on top of the image. This runs every half-hour using a cron-job.

raspistill \
  -ae 70,0xff,0x808000 \
  -a 1024 \
  -a " PlantCam 'Basil' - $(date) \n $( " \
  -e jpg \
  -th none \
  -o "timelapse/plant-$(date +'%Y-%m-%d_%H_%M_%S').jpg"

Low Quality Capture

This variant with reduced quality is run every minute and is used for the live photo.

raspistill \
  -ae 20,0xff,0x808000 \
  -a 1024 \
  -a " PlantCam 'Basil' - $(date) \n $( " \
  -w 640 \
  -h 480  \
  -q 60 \
  -e jpg \
  -th none \
  -o latest.jpg

Compile Timelapse to Video

To compile the frames into an mp4 video, I’m combining some info from my previous post about using ffmpeg, with a trick to easily grab all the photos using a glob:

ffmpeg \
  -y \ # Always overwrite
  -f image2 \ # Force input type to image2
  -pattern_type glob \ # Use input pattern as a glob
  -i 'timelapse/*.jpg' \ # Glob all the files
  -vcodec libx264 \ # Use the h264 video codec
  -pix_fmt yuv420p \ # Apple Quicktime support
  -profile:v baseline \ # For compatibility
  -level 3 \
  -movflags faststart \ # <- move the moov atom

The resulting video is served on a hidden web server and then served to end users via CloudFront.

Cache Control

I’m setting Cache-Control: max-age=<duration> on my nginx server to ensure cloudfront doesn’t cache images & videos too long. The duration (seconds) is set to the interval of each script.

Camera Setup

The camera is secured to the lightbar using some velcro cable ties, and the bottom half of one of these camera mounts.

Camera and Sensor Mounting
Camera Setup

Change Log

  • 6/16/2020 - Initial Revision
  • 8/15/2021 - Update to show final results

Found a typo or technical problem? file an issue!