Recently I needed to create a script to make a single movie from static images and existing movie files. I hacked this together with ruby, ffmpeg and ImageMagick. Note, it will function under windows with little code change.

Putting it up in case someone finds it of use!

    require 'fileutils'
    # !! Script requires image magick to be installed (for convert) and ffmpeg !!
    # This was *hacked* up quickly, apologies for the general cr*ppy style, its
    # functional but not beautiful!
    # Note: I pipe ffmpeg output (STDOUT/STDERR) to /dev/null - If ffmpeg fails to
    # convert you might want to remove this "feature".
    # Also note you will have to change jpg_to_mpeg() to alter how long static
    # images play for.
    # Also, also note, its hardcoded to look for specific file extensions, you
    # might want to alter these as well.
    # For a given folder, find a unique filename of random string + post_fix
    def unique_filename_at(folder, post_fix)
      name = nil
        name = File.join(folder, ((1..8).map{|i| ('a'..'z').to_a[rand(26)]}.join) + post_fix)
      end while File.exists?(name)
    # Resize all image files in env[:src] writing them to env[:dst]
    def resize_images_as_jpg(env)
      resized_names = []
      images = Dir.glob(File.join(env[:src], '*.{jpg}'), File::FNM_CASEFOLD)
      images.each do |image|
        resized_names << unique_filename_at(env[:dst], ".jpg")     `convert -scale #{env[:width]}x#{env[:height]} #{image} #{resized_names[-1]}`     `identify #{resized_names[-1]}` =~ /(\d+)x(\d+)/     # Scale attempts to best fit (based on image aspect ratio), it doesn't guarantee the exact size, so     # add extents (pad) the image to one which has the desired dimensions     if (env[:width] - $1.to_i) > 0 or (env[:height] - $2.to_i) > 0
          `convert -gravity Center -extent #{env[:width]}x#{env[:height]} #{resized_names[-1]} #{resized_names[-1]}`
    # Resize all the movie files in env[:src] writing them to env[:dst]
    def resize_movies(env)
      resized_names = []
      movies = Dir.glob(File.join(env[:src], '*.{mpg,mp4,flv}'), File::FNM_CASEFOLD)
      movies.each do |movie|
        resized_names << unique_filename_at(env[:dst], ".mpg")     `ffmpeg -i #{movie} -s #{env[:width]}x#{env[:height]} -sameq -r 25 #{resized_names[-1]} > /dev/null 2>&1`
    # Convert the given file (must end .jpg) to an mpeg
    # !! Hard-coded to create movies of length 10 seconds
    # !! 25 frames per second and 250 frames in total
    def jpg_to_mpeg(file)
      movie_name = File.join(File.dirname(file), File.basename(file, ".jpg") + ".mpg")
      `ffmpeg -loop_input -qscale 1 -f image2 -vframes 250 -r 25 -i #{file} #{movie_name} > /dev/null 2>&1`
    # Usage: tomov src_folder dest_folder
    # src_folder contains images/movies
    # dest_folder will contain the final result
    if __FILE__ == $0
      # Change this to set the resultant video size
      env = { :width => 640, :height => 480 }
      # Get src/dest folders - I didn't care about fancy argument checks at the time...
      if ARGV.length != 2
          puts "tomov in_folder out_folder"
      env[:src] = ARGV[0]
      env[:dst] = ARGV[1]
      exit(-1) unless File.exists?(env[:src])
      exit(-1) unless File.exists?(env[:dst])
      # Get the movie files to be consistent
      movies = resize_movies(env)
      # Get the image files to be consistent and convert to movie files
      resized_images = resize_images_as_jpg(env)
      movies.concat(resized_images.collect {|resized_image| jpg_to_mpeg(resized_image) })
      out_tmp = File.join(env[:dst], "movie.mpg")
      out_final = File.join(env[:dst], "final.avi")
      # Concat individual movies into a single movie
      # (the sort tries to ensure video's and images are mingled (not guaranteed though))
      `cat #{movies.sort_by{rand}.join(" ")} > #{out_tmp}`
      # Ensure the final movie is consistent and the format I want...
      `ffmpeg -i #{out_tmp} -sameq -vcodec mpeg4 -an -r 25 #{out_final} > /dev/null 2>&1`
      # Clean-up intermediate files
      resized_images.each {|i| File.delete(i) }
      movies.each {|i| File.delete(i) }