How to make a time-lapse video with FFmpeg

Essentially there are two ways to make a time-lapse video, regardless of the tool. I chose FFmpeg because it was easy to use, and available on Ubuntu.

From Video

With the first, you take an existing video, recorded at 25 (or 30) frames per second, and strip out all the frames except for one every second. This gives you an effective speedup of 25x. For example, by extracting one frame per second from a 60 minute video, you can drop the length to a much more watchable, entertaining, and web-friendly 2.4 minutes (not to mention a smaller, easier to download file size).

Here’s a bash script (timelapsify.sh) that uses FFmpeg to convert a normal video into a time-lapse video. It takes an input video filename and an output video filename as command line arguments:

#!/bin/bash
# sample usage: ./timelapsify.sh input.avi output.mp4
mkdir ffmpeg_temp
ffmpeg -i $1 -r 1 -f image2 ffmpeg_temp/%05d.png
ffmpeg -i ffmpeg_temp/%05d.png -sameq $2
rm -rf ./ffmpeg_temp

From Stills

The other way is to set up your camera so that it takes a photo every 1 or 2 seconds. Many of the popular Canon Powershot cameras can be customized with a non-destructive, non-permanent firmware enhancement called CHDK which you can use to run a custom intervalometer (like this one). Or your camera may offer this functionality natively.

Keep in mind that 2 minutes of video represents 3000 distinct photos at 25 frames/second. Your battery or your memory card may run out before you get that far, so if possible, plug your camera in and set it to its lowest resolution setting.

One FFmpeg-specific caveat: in order to assemble photos into a video, you need to name them sequentially, starting from 1. So I wrote a simple little command line PHP script to rename the series of image filenames that my camera produced (IMG_3550.JPG, IMG_3551.JPG, IMG_3552.JPG) into something a little more FFmpeg-friendly (frame_1.jpg, frame_2.jpg, frame_3.jpg):

<?php
$files = scandir(".");
$i = 1;
foreach ($files as $file) {
  if (preg_match("/IMG_[0-9]{4}.JPG/", $file)) {
    copy($file, "frame_" . $i . ".jpg");
    $i++;
  }
}

And here’s the FFmpeg command to assemble all those renamed “frame” jpgs into an actual mp4, H.264-encoded video (very much like timelapsify.sh above):

ffmpeg -f image2 -r 25 -i frame_%d.jpg -vcodec libx264 -vpre hq -crf 22 video.mp4

7 Comments

That whole renaming files thing seems like a lot of work…

Input when I did it was one of the CHDK intervalometer scripts (basically) with camera set to a fairly low res.

I picked different kinds of output options, but single command-line version with mencoder in a directory with the right .jpg files, not renamed:
mencoder -nosound -ovc lavc -lavcopts vcodec=mpeg4 -o video.avi ‘mf://*.jpg’ -mf fps=20

Yeah, I used mencoder first, but found people saying ffmpeg had better encoding options. The copying over of the files to frames has one nice affect in that it creates a working copy I can muss with without affecting the originals. Been thinking about using Gimp to batch process over the files to creating some fun affects. I have another time-lapse on the way. Stay tuned.

Sully

How can I run the timelapsify.sh from a browser? I was able to mod the .php file to rename the jpg files in my upload directory. THANKS!!!

Sully, well, you could just wrap each of the ffmpeg commands in PHP’s exec() command

Sully

Thanks for a great writeup to get me started Justin… I have it working. I tried to post a snippet of what I used for other newbies like me, but the blog did not accept it.

Sully, glad you found this helpful. Feel free to email me your code sample and I’ll add it to your comment.

Ralph

Your script which creates a timelapse from a video extracts a frame every second. But I would like to use longer periods of time, for example every 20 seconds. That’s because I have a video of several hours that I want to reduce to a few minutes.
Do you know how to do this? I tried to change the “1” in the script to “0.05” but this doesn’t work.

Care to Comment?

Or if you'd prefer to get in touch privately, please send me an email.

Name

Email (optional)

Blog (optional)