class MiniExiftool

Simple OO access to the Exiftool command-line application.

Constants

VERSION

Attributes

composite[RW]
convert_encoding[RW]
errors[RW]
filename[R]
ignore_minor_errors[RW]
numerical[RW]
timestamps[RW]

Public Class Methods

all_tags() click to toggle source

Returns a set of all known tags of Exiftool.

# File lib/mini_exiftool/mini_exiftool.rb, line 232
def self.all_tags
  unless defined? @@all_tags
    @@all_tags = pstore_get :all_tags
  end
  @@all_tags
end
command() click to toggle source

Returns the command name of the called Exiftool application.

# File lib/mini_exiftool/mini_exiftool.rb, line 217
def self.command
  @@cmd
end
command=(cmd) click to toggle source

Setting the command name of the called Exiftool application.

# File lib/mini_exiftool/mini_exiftool.rb, line 222
def self.command= cmd
  @@cmd = cmd
end
exiftool_version() click to toggle source

Returns the version of the Exiftool command-line application.

# File lib/mini_exiftool/mini_exiftool.rb, line 256
def self.exiftool_version
  output = `#{MiniExiftool.command} -ver 2>&1`
  unless $?.exitstatus == 0
    raise MiniExiftool::Error.new("Command '#{MiniExiftool.command}' not found")
  end
  output.chomp!
end
from_hash(hash) click to toggle source

Create a MiniExiftool instance from a hash

# File lib/mini_exiftool/mini_exiftool.rb, line 204
def self.from_hash hash
  instance = MiniExiftool.new
  instance.initialize_from_hash hash
  instance
end
from_yaml(yaml) click to toggle source

Create a MiniExiftool instance from YAML data created with MiniExiftool#to_yaml

# File lib/mini_exiftool/mini_exiftool.rb, line 212
def self.from_yaml yaml
  MiniExiftool.from_hash YAML.load(yaml)
end
new(filename=nil, opts={}) click to toggle source

opts support at the moment

  • :numerical for numerical values, default is false

  • :composite for including composite tags while loading, default is true

  • :convert_encoding convert encoding (See -L-option of the exiftool command-line application, default is false)

  • :ignore_minor_errors ignore minor errors (See -m-option

of the exiftool command-line application, default is false)

  • :timestamps generating DateTime objects instead of Time objects if set to DateTime, default is Time

    ATTENTION: Time objects are created using Time.local therefore they use your local timezone, DateTime objects instead are created without timezone!

# File lib/mini_exiftool/mini_exiftool.rb, line 54
def initialize filename=nil, opts={}
  opts = @@opts.merge opts
  @numerical = opts[:numerical]
  @composite = opts[:composite]
  @convert_encoding = opts[:convert_encoding]
  @ignore_minor_errors = opts[:ignore_minor_errors]
  @timestamps = opts[:timestamps]
  @values = TagHash.new
  @tag_names = TagHash.new
  @changed_values = TagHash.new
  @errors = TagHash.new
  load filename unless filename.nil?
end
opts() click to toggle source

Returns the options hash.

# File lib/mini_exiftool/mini_exiftool.rb, line 227
def self.opts
  @@opts
end
original_tag(tag) click to toggle source

Returns the original Exiftool name of the given tag

# File lib/mini_exiftool/mini_exiftool.rb, line 248
def self.original_tag tag
  unless defined? @@all_tags_map
    @@all_tags_map = pstore_get :all_tags_map
  end
  @@all_tags_map[tag]
end
unify(tag) click to toggle source
# File lib/mini_exiftool/mini_exiftool.rb, line 264
def self.unify tag
  tag.to_s.gsub(/[-_]/,'').downcase
end
writable_tags() click to toggle source

Returns a set of all possible writable tags of Exiftool.

# File lib/mini_exiftool/mini_exiftool.rb, line 240
def self.writable_tags
  unless defined? @@writable_tags
    @@writable_tags = pstore_get :writable_tags
  end
  @@writable_tags
end

Private Class Methods

determine_tags(arg) click to toggle source
# File lib/mini_exiftool/mini_exiftool.rb, line 420
def self.determine_tags arg
  output = `#{@@cmd} -#{arg}`
  lines = output.split(/\n/)
  tags = Set.new
  lines.each do |line|
    next unless line =~ /^\s/
    tags |= line.chomp.split
  end
  tags
end
load_or_create_pstore() click to toggle source
# File lib/mini_exiftool/mini_exiftool.rb, line 402
def self.load_or_create_pstore
  # This will hopefully work on *NIX and Windows systems
  home = ENV['HOME'] || ENV['HOMEDRIVE'] + ENV['HOMEPATH'] || ENV['USERPROFILE']
  subdir = RUBY_PLATFORM =~ /\bmswin/i ? '_mini_exiftool' : '.mini_exiftool'
  FileUtils.mkdir_p(File.join(home, subdir))
  filename = File.join(home, subdir, 'exiftool_tags_' << exiftool_version.gsub('.', '_') << '.pstore')
  @@pstore = PStore.new filename
  if !File.exist?(filename) || File.size(filename) == 0
    @@pstore.transaction do |ps|
      ps[:all_tags] = all_tags = determine_tags('list')
      ps[:writable_tags] = determine_tags('listw')
      map = {}
      all_tags.each { |k| map[unify(k)] = k }
      ps[:all_tags_map] = map
    end
  end
end
pstore_get(attribute) click to toggle source
# File lib/mini_exiftool/mini_exiftool.rb, line 393
def self.pstore_get attribute
  load_or_create_pstore unless defined? @@pstore
  result = nil
  @@pstore.transaction(true) do |ps|
    result = ps[attribute]
  end
  result
end

Public Instance Methods

[](tag) click to toggle source

Returns the value of a tag.

# File lib/mini_exiftool/mini_exiftool.rb, line 107
def [] tag
  @changed_values[tag] || @values[tag]
end
[]=(tag, val) click to toggle source

Set the value of a tag.

# File lib/mini_exiftool/mini_exiftool.rb, line 112
def []=(tag, val)
  @changed_values[tag] = val
end
changed?(tag=false) click to toggle source

Returns true if any tag value is changed or if the value of a given tag is changed.

# File lib/mini_exiftool/mini_exiftool.rb, line 118
def changed? tag=false
  if tag
    @changed_values.include? tag
  else
    !@changed_values.empty?
  end
end
changed_tags() click to toggle source

Returns an array of all changed tags.

# File lib/mini_exiftool/mini_exiftool.rb, line 144
def changed_tags
  @changed_values.keys.map { |key| MiniExiftool.original_tag(key) }
end
load(filename) click to toggle source

Load the tags of filename.

# File lib/mini_exiftool/mini_exiftool.rb, line 77
def load filename
  unless filename && File.exist?(filename)
    raise MiniExiftool::Error.new("File '#{filename}' does not exist.")
  end
  if File.directory?(filename)
    raise MiniExiftool::Error.new("'#{filename}' is a directory.")
  end
  @filename = filename
  @values.clear
  @tag_names.clear
  @changed_values.clear
  opt_params = ''
  opt_params << (@numerical ? '-n ' : '')
  opt_params << (@composite ? '' : '-e ')
  opt_params << (@convert_encoding ? '-L ' : '')
  cmd = %Q(#@@cmd -q -q -s -t #{opt_params} #{@@sep_op} #{Shellwords.escape(filename)})
  if run(cmd)
    parse_output
  else
    raise MiniExiftool::Error.new(@error_text)
  end
  self
end
reload() click to toggle source

Reload the tags of an already read file.

# File lib/mini_exiftool/mini_exiftool.rb, line 102
def reload
  load @filename
end
revert(tag=nil) click to toggle source

Revert all changes or the change of a given tag.

# File lib/mini_exiftool/mini_exiftool.rb, line 127
def revert tag=nil
  if tag
    val = @changed_values.delete(tag)
    res = val != nil
  else
    res = @changed_values.size > 0
    @changed_values.clear
  end
  res
end
save() click to toggle source

Save the changes to the file.

# File lib/mini_exiftool/mini_exiftool.rb, line 149
def save
  return false if @changed_values.empty?
  @errors.clear
  temp_file = Tempfile.new('mini_exiftool')
  temp_file.close
  temp_filename = temp_file.path
  FileUtils.cp filename, temp_filename
  all_ok = true
  @changed_values.each do |tag, val|
    original_tag = MiniExiftool.original_tag(tag)
    arr_val = val.kind_of?(Array) ? val : [val]
    arr_val.map! {|e| convert e}
    tag_params = ''
    arr_val.each do |v|
      tag_params << %Q(-#{original_tag}=#{Shellwords.escape(v.to_s)} )
    end
    opt_params = ''
    opt_params << (arr_val.detect {|x| x.kind_of?(Numeric)} ? '-n ' : '')
    opt_params << (@convert_encoding ? '-L ' : '')
    opt_params << (@ignore_minor_errors ? '-m' : '')
    cmd = %Q(#@@cmd -q -P -overwrite_original #{opt_params} #{tag_params} #{temp_filename})
    if convert_encoding && cmd.respond_to?(:encode)
      cmd.encode('ISO-8859-1')
    end
    result = run(cmd)
    unless result
      all_ok = false
      @errors[tag] = @error_text.gsub(/Nothing to do.\n\z/, '').chomp
    end
  end
  if all_ok
    FileUtils.cp temp_filename, filename
    reload
  end
  temp_file.delete
  all_ok
end
tags() click to toggle source

Returns an array of the tags (original tag names) of the read file.

# File lib/mini_exiftool/mini_exiftool.rb, line 139
def tags
  @values.keys.map { |key| @tag_names[key] }
end
to_hash() click to toggle source

Returns a hash of the original loaded values of the MiniExiftool instance.

# File lib/mini_exiftool/mini_exiftool.rb, line 189
def to_hash
  result = {}
  @values.each do |k,v|
    result[@tag_names[k]] = v
  end
  result
end
to_yaml() click to toggle source

Returns a YAML representation of the original loaded values of the MiniExiftool instance.

# File lib/mini_exiftool/mini_exiftool.rb, line 199
def to_yaml
  to_hash.to_yaml
end

Private Instance Methods

convert(val) click to toggle source
# File lib/mini_exiftool/mini_exiftool.rb, line 311
def convert val
  case val
  when Time
    val = val.strftime('%Y:%m:%d %H:%M:%S')
  end
  val
end
method_missing(symbol, *args) click to toggle source
# File lib/mini_exiftool/mini_exiftool.rb, line 319
def method_missing symbol, *args
  tag_name = symbol.id2name
  if tag_name.sub!(/=$/, '')
    self[tag_name] = args.first
  else
    self[tag_name]
  end
end
parse_line(line) click to toggle source
# File lib/mini_exiftool/mini_exiftool.rb, line 335
def parse_line line
  if line =~ /^([^\t]+)\t(.*)$/
    tag, value = $1, $2
    case value
    when /^\d{4}:\d\d:\d\d \d\d:\d\d:\d\d/
      s = value.sub(/^(\d+):(\d+):/, '\1-\2-')
      begin
        if @timestamps == Time
          value = Time.parse(s)
          elsif @timestamps == DateTime
          value = DateTime.parse(s)
        else
          raise MiniExiftool::Error.new("Value #@timestamps not allowed for option timestamps.")
        end
      rescue ArgumentError
        value = false
      end
    when /^\d+\.\d+$/
      value = value.to_f
    when /^0+[1-9]+$/
      # nothing => String
    when /^-?\d+$/
      value = value.to_i
    when %r(^(\d+)/(\d+)$)
      value = Rational($1.to_i, $2.to_i)
    when /^[\d ]+$/
      # nothing => String
    when /#{@@separator}/
      value = value.split @@separator
    end
  else
    raise MiniExiftool::Error.new("Malformed line #{line.inspect} of exiftool output.")
  end
  return [tag, value]
end
parse_output() click to toggle source
# File lib/mini_exiftool/mini_exiftool.rb, line 328
def parse_output
  @output.each_line do |line|
    tag, value = parse_line line
    set_value tag, value
  end
end
run(cmd) click to toggle source
# File lib/mini_exiftool/mini_exiftool.rb, line 293
def run cmd
  if $DEBUG
    $stderr.puts cmd
  end
  @output = `#{cmd} 2>#{@@error_file.path}`
  if convert_encoding && @output.respond_to?(:force_encoding)
    @output.force_encoding('ISO-8859-1')
  end
  @status = $?
  unless @status.exitstatus == 0
    @error_text = File.readlines(@@error_file.path).join
    return false
  else
    @error_text = ''
    return true
  end
end
set_attributes_by_heuristic() click to toggle source
# File lib/mini_exiftool/mini_exiftool.rb, line 376
def set_attributes_by_heuristic
  self.composite = tags.include?('ImageSize') ? true : false
  self.numerical = self.file_size.kind_of?(Integer) ? true : false
  # TODO: Is there a heuristic to determine @convert_encoding?
  self.timestamps = self.FileModifyDate.kind_of?(DateTime) ? DateTime : Time
end
set_value(tag, value) click to toggle source
# File lib/mini_exiftool/mini_exiftool.rb, line 371
def set_value tag, value
  @tag_names[tag] = tag
  @values[tag] = value
end
temp_filename() click to toggle source
# File lib/mini_exiftool/mini_exiftool.rb, line 383
def temp_filename
  unless @temp_filename
    temp_file = Tempfile.new('mini-exiftool')
    temp_file.close
    FileUtils.cp(@filename, temp_file.path)
    @temp_filename = temp_file.path
  end
  @temp_filename
end