Last active
April 13, 2017 00:26
-
-
Save spraints/cd978d7cfcaa160a0d7dc309fb9bc182 to your computer and use it in GitHub Desktop.
Stitch a folder of images into a movie
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
root=$(cd $(dirname "$0"); pwd) | |
cd $root | |
exec ruby process.rb "$@" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "shellwords" | |
require "tmpdir" | |
FRAMES_PER_SECOND = 6 | |
def main | |
log_all_output! | |
puts "#{Time.now} -- Start" | |
dirin = getdir("in") | |
dirdone = getdir("done") | |
dirout = getdir("out") | |
mm = MovieMaker.new dirin, dirdone, dirout | |
Dir.entries(dirin).each do |entry| | |
next if entry == "." || entry == ".." | |
mm.make_movie(entry) | |
end | |
ensure | |
puts "#{Time.now} -- Done" | |
end | |
class MovieMaker | |
def initialize(from, done, to) | |
@from = from | |
@done = done | |
@to = to | |
end | |
attr_reader :from, :to, :done | |
def make_movie(name) | |
image_dir = File.join(from, name) | |
return skipnondir(name) unless File.directory?(image_dir) | |
done_dir = File.join(done, name) | |
return doneexists(done_dir) if File.exist?(done_dir) | |
File.rename(image_dir, done_dir) | |
image_dir = done_dir | |
images = Dir["#{image_dir}/*"].select { |p| File.file?(p) }.sort | |
return noimages(image_dir) if images.empty? | |
movie = File.join(to, "#{Time.now.strftime("%Y-%m-%d-%H-%M-%S")}-#{name}.mp4") | |
Dir.mktmpdir do |numbered_dir| | |
prep_images images, numbered_dir | |
convert_movie numbered_dir, movie | |
end | |
end | |
def skipnondir(name) | |
puts "skipping #{name}: not a directory" | |
end | |
def doneexists(dir) | |
puts "#{dir} exists, not reprocessing" | |
end | |
def noimages(dir) | |
puts "skipping #{dir}: empty" | |
end | |
FORMAT = "%09d.jpg".freeze | |
HR = ("-"*40).freeze | |
def prep_images(image_paths, numbered_dir) | |
image_paths.each_with_index do |file, i| | |
File.link file, File.join(numbered_dir, FORMAT % i) | |
end | |
end | |
def convert_movie(numbered_dir, outpath) | |
# quicktime can't seem to handle the gigantic images from the dslr. | |
# so shrink them to 1024 pixels wide, maintain the current aspect ratio, | |
# but also ensure that the height is divisible by two. ?!?!?!?!! | |
cmd = [ "/usr/local/bin/ffmpeg", "-hide_banner", | |
"-f", "image2", | |
"-r", FRAMES_PER_SECOND.to_s, | |
"-i", FORMAT, | |
"-vcodec", "libx264", | |
"-vf", "scale=1024:-2,format=yuv420p", | |
"-r", "30", | |
outpath ] | |
puts HR, "MAKE MOVIE: #{cmd.shelljoin} (in #{numbered_dir})" | |
system "ls", "-l", numbered_dir or return | |
system(*cmd, :chdir => numbered_dir) or return | |
puts HR | |
system "ffprobe", "-hide_banner", outpath | |
end | |
end | |
ROOT = File.expand_path(File.dirname(__FILE__)).freeze | |
def getdir(name) | |
path = File.join(ROOT, name) | |
Dir.mkdir(path) unless Dir.exist?(path) | |
path | |
end | |
def log_all_output!(path = File.join(getdir("log"), Time.now.strftime("%Y-%m-%d-%H-%M-%S-#$$.log"))) | |
r, w = IO.pipe | |
tee_pid = spawn "tee", "-a", path, :in => r | |
r.close | |
$stdout.reopen(w) | |
$stderr.reopen(w) | |
end | |
main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment