Creating automatic timelapses with webcams on Linux

I had a couple cheap USB webcams that I plugged into my mini computer (basically a NUC clone) that's running Ubuntu 14.04. For what it's worth, I had to plug one of them (the older one) into a USB 2.0 port because it would randomly disconnect after using it when it was plugged into the USB 3.0 port.

Anyways, they both show up in /dev/video*

stephen@ubuntu:~$ ls /dev/video*
/dev/video0  /dev/video1

My previous guide goes over detecting and troubleshooting webcams if you'd like more information on setup.

Timelapses on Linux - The basic idea

The idea is to use cron to take a picture from the webcam at some time interval - this can be every 30 seconds, 1 minute, 5 minutes, etc. I'll use FFmpeg to take the still picture from the webcam. You can also use other programs to take images, I detail them in my previous guide, but for this I use FFmpeg.

Once I have a bunch of still images, I'll use FFmpeg again to turn all of the still images into a video file. I can specify the input and output framerate to achieve the desired duration and speed.

FFmpeg and Cronjobs

To take a single picture using FFmpeg you can run this

$ ffmpeg -f video4linux2 -s 640x480 -i /dev/video0 -vframes 1 test.jpg  

For my purposes, I want to have a more useful filename (in this case the current date and time) and I'd also like to use FFmpeg's drawtext filter to overlay the current time onto the image. This let me watch the time fly by in the timelapse video at the end.

$ ffmpeg -f video4linux2 -s 640x480 -i /dev/video0 -vf drawtext="fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf:text='%{localtime\:%T}':x=20:y=20:fontcolor=white" -vframes 1 $(date +%Y-%m-%d-%H-%M-%S).jpg

You may need to check what fonts are installed on your system and switch the fontfile path.

Okay, almost there. The final step is to escape the % characters and put this entry in the cron tab so it runs every minute (or 5 minutes, or hour, whatever you decide). I also give an absolute file path for the resulting image. Open up crontab by typing

$ crontab -e
$ sudo crontab -e

Then I added these two lines (one just being a comment)

# To take a picture every minute
*/1 * * * * ffmpeg -f video4linux2 -s 640x480 -i /dev/video1 -vf drawtext="fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf:text='\%{localtime\:\%T}':x=20:y=20:fontcolor=white" -vframes 1 /home/stephen/webcam-images/$(date +\%Y-\%m-\%d-\%H-\%M-\%S).jpg

Note that if you'd like sub-minute intervals for cron jobs, see the special notes at the bottom.

Combining the still images into a video using FFmpeg

The best way to view all these still images is as a video, compressed with a nice encoding like H.264. In my example, I can take a 640x480 image every hour for say, an entire day, and turn it into a 2-3 minute long video and compress it to about 5 to 10 MB. That's a small enough file that I can upload/download without worrying too much about bandwidth constraints.

Here are the parameters I use to turn the .jpg images into a .mp4 video

$ ffmpeg -framerate 5 -pattern_type glob -i '*.jpg' -c:v libx264 -r 30 -pix_fmt yuv420p out.mp4

The things to note are the -framerate 5 which is the rate according to which input images are read (in this example, 5 pictures per second) and the -r 30 which is the output frame rate for the video stream (in this example, a smooth 30 frames per second). You'll also notice I specify a glob pattern type for the input images (in this example, all files that end with .jpg).

You can link the resulting out.mp4 file in a web page or download/transfer the file for your viewing pleasure.

Sub minute cronjobs - Or, how do I run a cronjob every 30 seconds?

Cron has 60 second granularity, or in other words, it's not really designed to run things more frequently than once a minute. So how do you take a picture every 30 seconds? Or every 15 seconds?

A common solution would be to run a script that has a sleep in it, and then you start the never ending script once.

As an example, here's a bash script that takes a picture every 15 seconds:

#!/bin/env bash
while [ true ]; do
 sleep 15
 ffmpeg -f video4linux2 -s 640x480 -i /dev/video0 -vframes 1 $(date +%Y-%m-%d-%H-%M).jpg

Another way of doing it is putting the sleep inside of the cron job, and having more than one cron job. As an example:

# To take a picture every minute
*/1 * * * * ffmpeg -f video4linux2 -s 640x480 -i /dev/video1 -vframes 1 /home/stephen/webcam-images/$(date +\%Y-\%m-\%d-\%H-\%M-\%S).jpg
# To take a picture every minute, but at the :30 second mark
*/1 * * * * (sleep 30; ffmpeg -f video4linux2 -s 640x480 -i /dev/video1 -vframes 1 /home/stephen/webcam-images/$(date +\%Y-\%m-\%d-\%H-\%M-\%S).jpg)

If you wanted to take a picture every 15 seconds, you'd need four cron jobs:

# Run these 4 commands every minute, for taking pictures every 15 seconds:
*/1 * * * * ffmpeg -f video4linux2 -s 640x480 -i /dev/video1 -vframes 1 /home/stephen/webcam-images/$(date +\%Y-\%m-\%d-\%H-\%M-\%S).jpg
*/1 * * * * (sleep 15; ffmpeg -f video4linux2 -s 640x480 -i /dev/video1 -vframes 1     /home/stephen/webcam-images/$(date +\%Y-\%m-\%d-\%H-\%M-\%S).jpg)
*/1 * * * * (sleep 30; ffmpeg -f video4linux2 -s 640x480 -i /dev/video1 -vframes 1 /home/stephen/webcam-images/$(date +\%Y-\%m-\%d-\%H-\%M-\%S).jpg)
*/1 * * * * (sleep 45; ffmpeg -f video4linux2 -s 640x480 -i /dev/video1 -vframes 1 /home/stephen/webcam-images/$(date +\%Y-\%m-\%d-\%H-\%M-\%S).jpg)

Viewing the video

I ended up just creating a static file webserver with NodeJS to serve the .jpg or .mp4 files. That way I could view them in a browser from anywhere.

Next steps

At some point in the future I'll explore Motion using these webcams, which looks to be a neat tool that'll detect motion and automatically record video.