Skip to content

Instantly share code, notes, and snippets.

@jof
Last active September 23, 2019 20:23
Show Gist options
  • Save jof/5679141 to your computer and use it in GitHub Desktop.
Save jof/5679141 to your computer and use it in GitHub Desktop.
GoPro Timelapse Creation Script

This script takes a directory of source images off of a GoPro camera (or something similar that takes time-lapse images), and creates a directory of images suitably-laid-out for ffmpeg, clustered by file creation time.

Example usage:

jof@cucumis ~/Desktop/timelapse/ffmpeg % gopro_timelapse ../ride_in .
Building destination symlink directories for 1 clusters.
You can now call ffmpeg like: ffmpeg -i /Users/jof/Desktop/timelapse/ffmpeg/sequence_0/image%04d.jpg sequence_0.mp4

Then, run ffmpeg as directed, and this will generate a time lapse movie into "sequence_0.mp4"

#!/usr/bin/env ruby
require 'fileutils'
require 'find'
unless ARGV.length == 2
STDERR.puts "This utility needs a source directory and destination directory"
exit 1
end
source, destination = ARGV
FileUtils.mkdir_p destination
source_images = []
Find.find(source) do |path|
next unless File.file?(path)
next unless path =~ /JPG$/
source_images << [ path, File.stat(path) ]
end
source_images = source_images.sort {|a,b| a[1].ctime <=> b[1].ctime}
# This will walk over the source images, comparing their file creation times
# and group them by files of a similar creation time.
# After there is a gap longer than the average file delta, plus a fudge factor,
# create the next group.
fudge_factor = 10
clusters = []
total_deltas = 0
count = 1
average_delta = (source_images[0][1].ctime - source_images[1][1].ctime)
current_cluster = []
(0 .. source_images.length-1).each do |index|
previous_index = (index == 0 ? 0 : index - 1)
this = source_images[index]
previous = source_images[previous_index]
delta = this[1].ctime - previous[1].ctime
total_deltas += delta
count += 1
average_delta = total_deltas / count
if delta > (average_delta + fudge_factor) then # We've hit a boundary in out image sequence. Start a new cluster.
total_deltas = 0
count = 1
clusters << current_cluster
current_cluster = []
end
current_cluster << this
end
unless current_cluster.empty?
clusters << current_cluster
end
if clusters.length <= 0
STDERR.puts "Found no clusters of images?"
exit 3
end
puts "Building destination symlink directories for #{clusters.length} clusters."
(0 .. clusters.length-1).each do |cluster_number|
cluster_destination_dir = File.join(destination, "sequence_#{cluster_number}")
if File.exist?(cluster_destination_dir) and Dir.entries(cluster_destination_dir).length > 2 then
STDERR.puts "Destination directory #{cluster_destination_dir} already exists. Bailing out."
exit 3
end
FileUtils.mkdir_p cluster_destination_dir
cluster = clusters[cluster_number]
cluster.each_with_index do |entry, index|
path, stat = entry
destination_file = File.join(cluster_destination_dir, "image#{sprintf('%04d', index)}.jpg")
path = File.expand_path(path)
destination_file = File.expand_path(destination_file)
File.symlink(path, destination_file)
end
puts "You can now call ffmpeg like: ffmpeg -i #{File.expand_path(cluster_destination_dir)}/image%04d.jpg sequence_#{cluster_number}.mp4"
end
@miloh
Copy link

miloh commented Feb 1, 2014

somewhat related, for the fun of it:

ffmpeg version 0.8.9-6:0.8.9-1, Copyright (c) 2000-2013 the Libav developers
built on Nov 3 2013 00:54:50 with gcc 4.7.2
*** THIS PROGRAM IS DEPRECATED ***
This program is only provided for compatibility and will be removed in a future release. Please use avconv instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment