Skip to content

Instantly share code, notes, and snippets.

@iconara
Created June 29, 2016 13:23
Show Gist options
  • Save iconara/3dcd18fc94eb66da7399401915e5f6d3 to your computer and use it in GitHub Desktop.
Save iconara/3dcd18fc94eb66da7399401915e5f6d3 to your computer and use it in GitHub Desktop.
Automatically tag Fujifilm raw files with the correct film simulation
#!/usr/bin/ruby
require 'shellwords'
require 'thread'
paths = Queue.new
ARGV.each { |path| paths << path }
# http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/FujiFilm.html
COLOR_CAMERA_PROFILES = {
'Classic Chrome' => 'Camera CLASSIC CHROME',
'Pro Neg. Hi' => 'Camera Pro Neg. Hi',
'Pro Neg. Std' => 'Camera Pro Neg. Std',
'F1b/Studio Portrait Smooth Skin Tone (Astia)' => 'Camera ASTIA/SOFT',
'F0/Standard (Provia)' => 'Camera PROVIA/STANDARD',
'F2/Fujichrome (Velvia)' => 'Camera Velvia/VIVID',
}.freeze
BW_CAMERA_PROFILES = {
'None (B&W)' => 'Camera MONOCHROME',
'B&W Green Filter' => 'Camera MONOCHROME+G FILTER',
'B&W Red Filter' => 'Camera MONOCHROME+R FILTER',
'B&W Yellow Filter' => 'Camera MONOCHROME+Ye FILTER',
}.freeze
def camel_case(str)
str.to_s.gsub(/^\w|(?:_\w)/) { |c| c[-1, 1].upcase }
end
def snake_case(str)
key.gsub!(/^\w| \w/, &:downcase)
key.gsub!(' ', '_')
end
def extract_metadata(path, *fields)
flags = fields.map { |f| '-' << camel_case(f) }
command = ['exiftool', '-t', *flags, path]
result = %x(#{Shellwords.join(command)})
if $?.exitstatus == 0
result.lines.each_with_object({}) do |line, acc|
key, value = line.chomp.split("\t")
key.gsub!(/^\w| \w/, &:downcase)
key.gsub!(' ', '_')
acc[key.to_sym] = value
end
else
raise "Could not extract metadata: exiftool exited with #{$?.exitstatus} (#{result.inspect})"
end
end
def update_metadata(path, fields)
flags = fields.map { |k, v| '-' << camel_case(k) << '=' << v }
command = ['exiftool', '-overwrite_original_in_place', *flags, path]
result = %x(#{Shellwords.join(command)})
if $?.exitstatus != 0
raise "Could not update metadata: exiftool exited with #{$?.exitstatus} (#{result.inspect})"
end
end
def find_camera_profile(meta_data)
COLOR_CAMERA_PROFILES[meta_data[:film_mode]] || BW_CAMERA_PROFILES[meta_data[:saturation]]
end
threads = Array.new(4) do
Thread.start do
paths << :die
while (raw_path = paths.pop) != :die
xmp_path = File.join(File.dirname(raw_path), File.basename(raw_path, '.*')) << '.xmp'
raw_meta_data = extract_metadata(raw_path, :film_mode, :saturation)
if File.exist?(xmp_path)
xmp_meta_data = extract_metadata(xmp_path, :camera_profile, :camera_profile_digest)
else
xmp_meta_data = {}
end
camera_profile = find_camera_profile(raw_meta_data)
if camera_profile && !xmp_meta_data.include?(:camera_profile)
$stderr.puts(sprintf('%s updated film simulation ("%s")', xmp_path, camera_profile))
update_metadata(xmp_path, camera_profile: camera_profile)
elsif !camera_profile
$stderr.puts(sprintf('%s could not detect film simulation (film mode was "%s", saturation was "%s")', raw_path, raw_meta_data[:film_mode], raw_meta_data[:saturation]))
elsif (camera_profile = xmp_meta_data[:camera_profile])
$stderr.puts(sprintf('%s already has a camera profile ("%s")', xmp_path, camera_profile))
end
end
end
end
threads.each(&:value)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment