Class: Exiftool

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/exiftool.rb,
lib/exiftool/result.rb,
lib/exiftool/version.rb,
lib/exiftool/field_parser.rb

Overview

Exiftool Class

Defined Under Namespace

Classes: ExiftoolNotInstalled, FieldParser, InvalidCommand, InvalidOption, NoDefaultResultWithMultiget, NoSuchFile, NotAFile, Result

Constant Summary collapse

CONTROL_CHAR_RE =
/[[:cntrl:]]/
VERSION =
Gem::Version.new('1.4.2')

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(filenames, exiftool_opts = '') ⇒ Exiftool

Returns a new instance of Exiftool.



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/exiftool.rb', line 86

def initialize(filenames, exiftool_opts = '')
  @file2result = {}
  io_input = nil
  if filenames.is_a?(IO) || filenames.is_a?(StringIO)
    io_input = filenames
    filenames = ['-']
  end

  filenames = [filenames] if filenames.is_a?(String) || filenames.is_a?(Pathname)
  return if filenames.empty?

  expanded_filenames = self.class.expand_paths(filenames)
  args = [
    self.class.command,
    *self.class.exiftool_args(exiftool_opts),
    '-j',
    '-coordFormat', '%.8f',
    *expanded_filenames
  ]

  json = ''
  begin
    Open3.popen3(*args) do |stdin, stdout, _stderr, wait_thr|
      if io_input
        IO.copy_stream(io_input, stdin)
        stdin.close
      end
      json = stdout.read.to_s.chomp
      wait_thr.value
    end
  rescue Errno::ENOENT
    json = ''
  end

  raise ExiftoolNotInstalled if json == ''

  JSON.parse(json).each do |raw|
    result = Result.new(raw)
    @file2result[result.source_file] = result
  end
end

Class Method Details

.commandObject



27
28
29
# File 'lib/exiftool.rb', line 27

def self.command
  @command ||= 'exiftool'
end

.command=(command) ⇒ Object

Raises:



31
32
33
34
35
# File 'lib/exiftool.rb', line 31

def self.command=(command)
  raise(InvalidCommand, command.inspect) if command.match?(CONTROL_CHAR_RE)

  @command = command
end

.exiftool_args(exiftool_opts) ⇒ Object

Raises:



76
77
78
79
80
# File 'lib/exiftool.rb', line 76

def self.exiftool_args(exiftool_opts)
  raise(InvalidOption, exiftool_opts.inspect) if exiftool_opts.match?(CONTROL_CHAR_RE)

  Shellwords.split(exiftool_opts)
end

.exiftool_installed?Boolean

Returns:

  • (Boolean)


37
38
39
# File 'lib/exiftool.rb', line 37

def self.exiftool_installed?
  exiftool_version.to_f.positive?
end

.exiftool_versionObject

This is a string, not a float, to handle versions like “9.40” properly.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/exiftool.rb', line 42

def self.exiftool_version
  return @exiftool_version if defined?(@exiftool_version) && @exiftool_version

  stdout_str = ''
  begin
    Open3.popen3(command, '-ver') do |_stdin, stdout, _stderr, wait_thr|
      stdout_str = stdout.read.to_s.chomp
      # Ensure the process is reaped
      wait_thr.value
    end
  rescue Errno::ENOENT
    stdout_str = ''
  end

  @exiftool_version = stdout_str
end

.expand_path(filename) ⇒ Object

Raises:



59
60
61
62
63
64
65
66
67
68
# File 'lib/exiftool.rb', line 59

def self.expand_path(filename)
  filename = filename.to_s
  raise(NoSuchFile, filename.inspect) if filename.match?(CONTROL_CHAR_RE)

  raise(NoSuchFile, filename) unless File.exist?(filename)

  raise(NotAFile, filename) unless File.file?(filename)

  File.expand_path(filename)
end

.expand_paths(filenames) ⇒ Object



70
71
72
73
74
# File 'lib/exiftool.rb', line 70

def self.expand_paths(filenames)
  filenames.map do |f|
    f == '-' ? '-' : expand_path(f.to_s)
  end
end

Instance Method Details

#errors?Boolean

Returns:

  • (Boolean)


144
145
146
# File 'lib/exiftool.rb', line 144

def errors?
  @file2result.values.any?(&:errors?)
end

#files_with_resultsObject



140
141
142
# File 'lib/exiftool.rb', line 140

def files_with_results
  results.map(&:source_file)
end

#result_for(filename) ⇒ Object



136
137
138
# File 'lib/exiftool.rb', line 136

def result_for(filename)
  @file2result[self.class.expand_path(filename)]
end

#results(include_results_with_errors: false) ⇒ Object



128
129
130
131
132
133
134
# File 'lib/exiftool.rb', line 128

def results(include_results_with_errors: false)
  if include_results_with_errors
    @file2result.values
  else
    @file2result.values.reject(&:errors?)
  end
end