Skip to content

Instantly share code, notes, and snippets.

@rob-murray
Last active August 29, 2015 14:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rob-murray/b5b34a30616390c9f374 to your computer and use it in GitHub Desktop.
Save rob-murray/b5b34a30616390c9f374 to your computer and use it in GitHub Desktop.
Check images in a directory for valid EXIF image creation date and update if necessary.
#!/usr/bin/env ruby
require 'optparse'
require 'mini_exiftool'
# Check images in a directory for valid EXIF image creation date and update if necessary.
# Can be used to fix older photos that do not have to the correct EXIF `date_time_original` field data.
#
# If not present then attempt fallback and update `date_time_original` field
# If fallback not available then otherwise prompt for input or use default date.
#
# Usage: ./update_images.rb -d ./directory/
#
DEFAULT_DATE = '2004-10-01 00:00:01 +00:00'
options = {}
OptionParser.new do |opts|
opts.banner = 'Update EXIF Date for all images in a directory'
opts.separator ''
opts.on('-d', '--directory [DIRECTORY]', 'Specify the path to the directory to process') do |directory|
options[:directory] = directory
end
opts.on('-f', '--force', 'Force to be asked to update image date') do |_force|
options[:force] = true
end
opts.on("-h", "--help", "Displays help") do
puts opts
exit
end
end.parse!
def main(options)
successes = 0
failures = []
image_directory = options[:directory].end_with?('/') ? options[:directory] : options[:directory] << '/'
images = image_list(image_directory)
force = true if options[:force]
print "Processing #{images.size} images from #{image_directory}.\n"
print "Default date is #{DEFAULT_DATE}.\n"
images.each do |image_file|
result = process_image(image_file, force)
if result
successes += 1
else
failures << image_file
end
end
print "Processed #{successes} / #{images.size} images\n"
print "Failures: #{failures}\n"
end
def image_list(image_directory)
image_matcher = "#{image_directory}*.jpg"
Dir.glob(image_matcher, File::FNM_CASEFOLD) # case insensitive glob
end
def process_image(photo_file, force = false)
puts "Working image: #{photo_file}\n"
photo = MiniExiftool.new(photo_file)
display_metadata(photo)
if !force && matched_date = find_ideal_date(photo)
display_date(matched_date)
print ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
# dont need to do anything
return true
elsif !force && matched_date = find_date(photo)
display_date(matched_date)
set_date(photo, matched_date)
print ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
return true
else
print "Unable to find a date for image: #{photo_file}\n"
if input_date = get_date_from_user_input
set_date(photo, input_date)
print ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
return true
end
print ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
return false
end
end
def display_metadata(p)
print "title: #{p.title}\n"
print "image_description: #{p.image_description}\n"
print "artist: #{p.artist}\n"
print "make: #{p.make}\n"
print "model: #{p.model}\n"
print "software: #{p.software}\n"
end
def display_date(date)
fmt = '%Y-%m-%d %H:%M:%S'
print "Matched date: #{date.strftime(fmt)}\n" if date
end
def find_ideal_date(p)
if p.date_time_original && valid_date?(p.date_time_original)
print "Matched via date_time_original\n"
return p.date_time_original
end
end
def find_date(p)
if p.create_date && valid_date?(p.create_date)
print "Matched via create_date\n"
return p.create_date
end
if p.modify_date && valid_date?(p.modify_date)
print "Matched via modify_date\n"
return p.modify_date
end
if p.metadata_date && valid_date?(p.metadata_date)
print "Matched via metadata_date\n"
return p.metadata_date
end
end
def set_date(p, date)
print "Setting #{date} to #{p.filename}\n"
p.date_time_original = date
p.save
end
def valid_date?(date)
result = DateTime.parse date.to_s rescue nil
!result.nil?
end
def get_date_from_user_input
puts "Enter a complete date or the year? Enter 'def' to take default from script. [d|y|def]\n"
input = gets.chomp
if input == 'y'
ask_for_year
elsif input == 'def'
use_default_date
else
ask_for_date
end
end
# 2007-09-23 18:25:39 +0100
def ask_for_date
puts "Enter a date for this photo or leave blank to skip. Format. '%Y-%m-%d %H:%M:%S %z' '2001-02-03 04:05:06 +07:00'\n"
datetime = gets.chomp
DateTime.strptime(datetime, '%Y-%m-%d %H:%M:%S %z') unless datetime.empty?
end
def ask_for_year
puts "Enter a year. Other date components will be filled with defaults\n"
year = gets.chomp
DateTime.strptime(year, '%Y') unless year.empty?
end
def use_default_date
DateTime.strptime(DEFAULT_DATE, '%Y-%m-%d %H:%M:%S %z')
end
if __FILE__ == $0
main(options)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment