Class: MiniExiftool

Inherits:
Object
  • Object
show all
Defined in:
lib/mini_exiftool.rb

Overview

Simple OO access to the ExifTool command-line application.

Defined Under Namespace

Modules: BackportYAML Classes: Error, TagHash

Constant Summary collapse

VERSION =
'2.14.0'
@@cmd =

Name of the ExifTool command-line application

'exiftool'
@@opts =

Hash of the standard options used when call MiniExiftool.new

{ :numerical => false, :composite => true, :fast => false, :fast2 => false,
:ignore_minor_errors => false, :replace_invalid_chars => false,
:timestamps => Time }
@@fs_enc =

Encoding of the filesystem (filenames in command line)

Encoding.find('filesystem')
@@encoding_types =
%w(exif iptc xmp png id3 pdf photoshop quicktime aiff mie vorbis)
@@running_on_windows =

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(filename_or_io = nil, opts = {}) ⇒ MiniExiftool

filename_or_io The kind of the parameter is determined via duck typing: if the argument responds to to_str it is interpreted as filename, if it responds to read it is interpreted es IO instance.

ATTENTION: If using an IO instance writing of meta data is not supported!

opts support at the moment

  • :numerical for numerical values, default is false

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

  • :ignore_minor_errors ignore minor errors (See -m-option of the exiftool command-line application, default is false)

  • :coord_format set format for GPS coordinates (See -c-option of the exiftool command-line application, default is nil that means exiftool standard)

  • :fast useful when reading JPEGs over a slow network connection (See -fast-option of the exiftool command-line application, default is false)

  • :fast2 useful when reading JPEGs over a slow network connection (See -fast2-option of the exiftool command-line application, default is false)

  • :replace_invalid_chars replace string for invalid UTF-8 characters or false if no replacing should be done, 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!

  • :exif_encoding, :iptc_encoding, :xmp_encoding, :png_encoding, :id3_encoding, :pdf_encoding, :photoshop_encoding, :quicktime_encoding, :aiff_encoding, :mie_encoding, :vorbis_encoding to set this specific encoding (see -charset option of the exiftool command-line application, default is nil: no encoding specified)



107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/mini_exiftool.rb', line 107

def initialize filename_or_io=nil, opts={}
  @opts = @@opts.merge opts
  if @opts[:convert_encoding]
    warn 'Option :convert_encoding is not longer supported!'
    warn 'Please use the String#encod* methods.'
  end
  @filename = nil
  @io = nil
  @values = TagHash.new
  @changed_values = TagHash.new
  @errors = TagHash.new
  load filename_or_io unless filename_or_io.nil?
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(symbol, *args) ⇒ Object (private)



428
429
430
431
432
433
434
435
# File 'lib/mini_exiftool.rb', line 428

def method_missing symbol, *args
  tag_name = symbol.id2name
  if tag_name =~ /^(.+)=$/
    self[$1] = args.first
  else
    self[tag_name]
  end
end

Instance Attribute Details

#errorsObject (readonly)

Returns the value of attribute errors.



56
57
58
# File 'lib/mini_exiftool.rb', line 56

def errors
  @errors
end

#filenameObject (readonly)

Returns the value of attribute filename.



56
57
58
# File 'lib/mini_exiftool.rb', line 56

def filename
  @filename
end

#ioObject (readonly)

Returns the value of attribute io.



56
57
58
# File 'lib/mini_exiftool.rb', line 56

def io
  @io
end

Class Method Details

.all_tagsObject

Returns a set of all known tags of ExifTool.



329
330
331
332
333
334
# File 'lib/mini_exiftool.rb', line 329

def self.all_tags
  unless defined? @@all_tags
    @@all_tags = pstore_get :all_tags
  end
  @@all_tags
end

.commandObject

Returns the command name of the called ExifTool application.



314
315
316
# File 'lib/mini_exiftool.rb', line 314

def self.command
  @@cmd
end

.command=(cmd) ⇒ Object

Setting the command name of the called ExifTool application.



319
320
321
# File 'lib/mini_exiftool.rb', line 319

def self.command= cmd
  @@cmd = cmd
end

.encoding_opt(enc_type) ⇒ Object



63
64
65
# File 'lib/mini_exiftool.rb', line 63

def self.encoding_opt enc_type
  (enc_type.to_s + '_encoding').to_sym
end

.exiftool_versionObject

Returns the version of the ExifTool command-line application.



353
354
355
356
357
358
359
# File 'lib/mini_exiftool.rb', line 353

def self.exiftool_version
  Open3.popen3 "#{MiniExiftool.command} -ver" do |_inp, out, _err, _thr|
    out.read.chomp!
  end
rescue SystemCallError
  raise MiniExiftool::Error.new("Command '#{MiniExiftool.command}' not found")
end

.from_hash(hash, opts = {}) ⇒ Object

Create a MiniExiftool instance from a hash. Default value conversions will be applied if neccesary.



293
294
295
296
297
# File 'lib/mini_exiftool.rb', line 293

def self.from_hash hash, opts={}
  instance = MiniExiftool.new nil, opts
  instance.initialize_from_hash hash
  instance
end

.from_json(json, opts = {}) ⇒ Object

Create a MiniExiftool instance from JSON data. Default value conversions will be applied if neccesary.



301
302
303
304
305
# File 'lib/mini_exiftool.rb', line 301

def self.from_json json, opts={}
  instance = MiniExiftool.new nil, opts
  instance.initialize_from_json json
  instance
end

.from_yaml(yaml, opts = {}) ⇒ Object

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



309
310
311
# File 'lib/mini_exiftool.rb', line 309

def self.from_yaml yaml, opts={}
  MiniExiftool.from_hash YAML.unsafe_load(yaml), opts
end

.optsObject

Returns the options hash.



324
325
326
# File 'lib/mini_exiftool.rb', line 324

def self.opts
  @@opts
end

.opts_accessor(*attrs) ⇒ Object



45
46
47
48
49
50
51
52
53
54
# File 'lib/mini_exiftool.rb', line 45

def self.opts_accessor *attrs
  attrs.each do |a|
    define_method a do
      @opts[a]
    end
    define_method "#{a}=" do |val|
      @opts[a] = val
    end
  end
end

.original_tag(tag) ⇒ Object

Returns the original ExifTool name of the given tag



345
346
347
348
349
350
# File 'lib/mini_exiftool.rb', line 345

def self.original_tag tag
  unless defined? @@all_tags_map
    @@all_tags_map = pstore_get :all_tags_map
  end
  @@all_tags_map[tag]
end

.pstore_dirObject



367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/mini_exiftool.rb', line 367

def self.pstore_dir
  unless defined? @@pstore_dir
    @@pstore_dir =
      if env = ENV['MINI_EXIFTOOL_PSTORE_DIR']
        env
      elsif defined?(Gem.cache_home) && File.writable?(Gem.cache_home)
        File.join(Gem.cache_home, 'mini_exiftool')
      else
        # This fallback will hopefully work on *NIX and Windows systems
        cache_dir = ENV['USERPROFILE'] || Dir.tmpdir
        File.join(cache_dir, 'mini_exiftool')
      end
  end
  @@pstore_dir
end

.pstore_dir=(dir) ⇒ Object



383
384
385
# File 'lib/mini_exiftool.rb', line 383

def self.pstore_dir= dir
  @@pstore_dir = dir
end

.unify(tag) ⇒ Object



361
362
363
# File 'lib/mini_exiftool.rb', line 361

def self.unify tag
  tag.to_s.gsub(/[-_]/,'').downcase
end

.writable_tagsObject

Returns a set of all possible writable tags of ExifTool.



337
338
339
340
341
342
# File 'lib/mini_exiftool.rb', line 337

def self.writable_tags
  unless defined? @@writable_tags
    @@writable_tags = pstore_get :writable_tags
  end
  @@writable_tags
end

Instance Method Details

#[](tag) ⇒ Object

Returns the value of a tag.



173
174
175
# File 'lib/mini_exiftool.rb', line 173

def [] tag
  @changed_values[tag] || @values[tag]
end

#[]=(tag, val) ⇒ Object

Set the value of a tag.



178
179
180
# File 'lib/mini_exiftool.rb', line 178

def []= tag, val
  @changed_values[tag] = val
end

#changed?(tag = false) ⇒ Boolean

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

Returns:

  • (Boolean)


184
185
186
187
188
189
190
# File 'lib/mini_exiftool.rb', line 184

def changed? tag=false
  if tag
    @changed_values.include? tag
  else
    !@changed_values.empty?
  end
end

#changed_tagsObject

Returns an array of all changed tags.



210
211
212
# File 'lib/mini_exiftool.rb', line 210

def changed_tags
  @changed_values.keys.map { |key| MiniExiftool.original_tag(key) }
end

#copy_tags_from(source_filename, tags) ⇒ Object



261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/mini_exiftool.rb', line 261

def copy_tags_from(source_filename, tags)
  @errors.clear
  unless File.exist?(source_filename)
    raise MiniExiftool::Error.new("Source file #{source_filename} does not exist!")
  end
  params = '-q -P -overwrite_original '
  tags_params = Array(tags).map {|t| '-' << t.to_s}.join(' ')
  cmd = [@@cmd, params, '-tagsFromFile', escape(source_filename).encode(@@fs_enc), tags_params.encode('UTF-8'), escape(filename).encode(@@fs_enc)].join(' ')
  cmd.force_encoding('UTF-8')
  result = run(cmd)
  reload
  result
end

#initialize_from_hash(hash) ⇒ Object

:nodoc:



121
122
123
124
125
# File 'lib/mini_exiftool.rb', line 121

def initialize_from_hash hash # :nodoc:
  set_values hash
  set_opts_by_heuristic
  self
end

#initialize_from_json(json) ⇒ Object

:nodoc:



127
128
129
130
131
132
# File 'lib/mini_exiftool.rb', line 127

def initialize_from_json json # :nodoc:
  @output = json
  @errors.clear
  parse_output
  self
end

#load(filename_or_io) ⇒ Object

Load the tags of filename or io.



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/mini_exiftool.rb', line 135

def load filename_or_io
  if filename_or_io.respond_to?(:to_str) || filename_or_io.kind_of?(Pathname) # String-like
    unless filename_or_io && File.exist?(filename_or_io)
      raise MiniExiftool::Error.new("File '#{filename_or_io}' does not exist.")
    end
    if File.directory?(filename_or_io)
      raise MiniExiftool::Error.new("'#{filename_or_io}' is a directory.")
    end
    @filename = filename_or_io.to_s
  elsif filename_or_io.respond_to? :read # IO-like
    @io = filename_or_io
    @filename = '-'
  else
    raise MiniExiftool::Error.new("Could not open filename_or_io.")
  end
  @values.clear
  @changed_values.clear
  params = '-j '
  params << (@opts[:numerical] ? '-n ' : '')
  params << (@opts[:composite] ? '' : '-e ')
  params << (@opts[:coord_format] ? "-c #{escape(@opts[:coord_format])}" : '')
  params << (@opts[:fast] ? '-fast ' : '')
  params << (@opts[:fast2] ? '-fast2 ' : '')
  params << generate_encoding_params
  if run(cmd_gen(params, @filename))
    parse_output
  else
    raise MiniExiftool::Error.new(@error_text)
  end
  self
end

#reloadObject

Reload the tags of an already read file.



168
169
170
# File 'lib/mini_exiftool.rb', line 168

def reload
  load @filename
end

#revert(tag = nil) ⇒ Object

Revert all changes or the change of a given tag.



193
194
195
196
197
198
199
200
201
202
# File 'lib/mini_exiftool.rb', line 193

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

#saveObject

Save the changes to the file.



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/mini_exiftool.rb', line 215

def save
  if @io
    raise MiniExiftool::Error.new('No writing support when using an IO.')
  end
  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.encode(@@fs_enc), 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_before_save(e)}
    params = '-q -P -overwrite_original '
    params << (arr_val.detect {|x| x.kind_of?(Numeric)} ? '-n ' : '')
    params << (@opts[:ignore_minor_errors] ? '-m ' : '')
    params << generate_encoding_params
    arr_val.each do |v|
      params << %Q(-#{original_tag}=#{escape(v)} )
    end
    result = run(cmd_gen(params, temp_filename))
    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.encode(@@fs_enc)
    reload
  end
  temp_file.delete
  all_ok
end

#save!Object



251
252
253
254
255
256
257
258
259
# File 'lib/mini_exiftool.rb', line 251

def save!
  unless save
    err = []
    @errors.each do |key, value|
      err << "(#{key}) #{value}"
    end
    raise MiniExiftool::Error.new("MiniExiftool couldn't save. The following errors occurred: #{err.empty? ? "None" : err.join(", ")}")
  end
end

#tagsObject

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



205
206
207
# File 'lib/mini_exiftool.rb', line 205

def tags
  @values.keys.map { |key| MiniExiftool.original_tag(key) }
end

#to_hashObject

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



277
278
279
280
281
282
283
# File 'lib/mini_exiftool.rb', line 277

def to_hash
  result = {}
  @values.each do |k,v|
    result[MiniExiftool.original_tag(k)] = v
  end
  result
end

#to_yamlObject

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



287
288
289
# File 'lib/mini_exiftool.rb', line 287

def to_yaml
  to_hash.to_yaml
end