mini_exiftool 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/Changelog CHANGED
@@ -1,3 +1,6 @@
1
+ Version 1.0.2
2
+ - Fixing warings
3
+ thanks to Peter-Hinrich Krogmann for the hint.
1
4
  Version 1.0.1
2
5
  - Fixing bug [#22726]
3
6
  Making MiniExiftool::Error public.
@@ -0,0 +1,445 @@
1
+ #
2
+ # MiniExiftool
3
+ #
4
+ # This library is wrapper for the Exiftool command-line
5
+ # application (http://www.sno.phy.queensu.ca/~phil/exiftool/)
6
+ # written by Phil Harvey.
7
+ # Read and write access is done in a clean OO manner.
8
+ #
9
+ # Author: Jan Friedrich
10
+ # Copyright (c) 2007, 2008 by Jan Friedrich
11
+ # Licensed under the GNU LESSER GENERAL PUBLIC LICENSE,
12
+ # Version 2.1, February 1999
13
+ #
14
+
15
+ require 'fileutils'
16
+ require 'tempfile'
17
+ require 'pstore'
18
+ require 'set'
19
+
20
+ # Simple OO access to the Exiftool command-line application.
21
+ class MiniExiftool
22
+
23
+ # Name of the Exiftool command-line application
24
+ @@cmd = 'exiftool'
25
+
26
+ # Hash of the standard options used when call MiniExiftool.new
27
+ @@opts = { :numerical => false, :composite => true, :convert_encoding => false, :timestamps => Time }
28
+
29
+ attr_reader :filename
30
+ attr_accessor :numerical, :composite, :convert_encoding, :errors, :timestamps
31
+
32
+ VERSION = '1.0.1'
33
+
34
+ # +opts+ support at the moment
35
+ # * <code>:numerical</code> for numerical values, default is +false+
36
+ # * <code>:composite</code> for including composite tags while loading,
37
+ # default is +true+
38
+ # * <code>:convert_encoding</code> convert encoding (See -L-option of
39
+ # the exiftool command-line application, default is +false+
40
+ # * <code>:timestamps</code> generating DateTime objects instead of
41
+ # Time objects if set to <code>DateTime</code>, default is +Time+
42
+ #
43
+ # <b>ATTENTION:</b> Time objects are created using <code>Time.local</code>
44
+ # therefore they use <em>your local timezone</em>, DateTime objects instead
45
+ # are created <em>without timezone</em>!
46
+ def initialize filename=nil, opts={}
47
+ opts = @@opts.merge opts
48
+ @numerical = opts[:numerical]
49
+ @composite = opts[:composite]
50
+ @convert_encoding = opts[:convert_encoding]
51
+ @timestamps = opts[:timestamps]
52
+ @values = TagHash.new
53
+ @tag_names = TagHash.new
54
+ @changed_values = TagHash.new
55
+ @errors = TagHash.new
56
+ load filename unless filename.nil?
57
+ end
58
+
59
+ def initialize_from_hash hash # :nodoc:
60
+ hash.each_pair do |tag,value|
61
+ set_value tag, value
62
+ end
63
+ set_attributes_by_heuristic
64
+ self
65
+ end
66
+
67
+ # Load the tags of filename.
68
+ def load filename
69
+ unless filename && File.exist?(filename)
70
+ raise MiniExiftool::Error.new("File '#{filename}' does not exist.")
71
+ end
72
+ if File.directory?(filename)
73
+ raise MiniExiftool::Error.new("'#{filename}' is a directory.")
74
+ end
75
+ @filename = filename
76
+ @values.clear
77
+ @tag_names.clear
78
+ @changed_values.clear
79
+ opt_params = ''
80
+ opt_params << (@numerical ? '-n ' : '')
81
+ opt_params << (@composite ? '' : '-e ')
82
+ opt_params << (@convert_encoding ? '-L ' : '')
83
+ cmd = %Q(#@@cmd -q -q -s -t #{opt_params} #{@@sep_op} "#{filename}")
84
+ if run(cmd)
85
+ parse_output
86
+ else
87
+ raise MiniExiftool::Error.new(@error_text)
88
+ end
89
+ self
90
+ end
91
+
92
+ # Reload the tags of an already readed file.
93
+ def reload
94
+ load @filename
95
+ end
96
+
97
+ # Returns the value of a tag.
98
+ def [] tag
99
+ @changed_values[tag] || @values[tag]
100
+ end
101
+
102
+ # Set the value of a tag.
103
+ def []=(tag, val)
104
+ @changed_values[tag] = val
105
+ end
106
+
107
+ # Returns true if any tag value is changed or if the value of a
108
+ # given tag is changed.
109
+ def changed? tag=false
110
+ if tag
111
+ @changed_values.include? tag
112
+ else
113
+ !@changed_values.empty?
114
+ end
115
+ end
116
+
117
+ # Revert all changes or the change of a given tag.
118
+ def revert tag=nil
119
+ if tag
120
+ val = @changed_values.delete(tag)
121
+ res = val != nil
122
+ else
123
+ res = @changed_values.size > 0
124
+ @changed_values.clear
125
+ end
126
+ res
127
+ end
128
+
129
+ # Returns an array of the tags (original tag names) of the readed file.
130
+ def tags
131
+ @values.keys.map { |key| @tag_names[key] }
132
+ end
133
+
134
+ # Returns an array of all changed tags.
135
+ def changed_tags
136
+ @changed_values.keys.map { |key| MiniExiftool.original_tag(key) }
137
+ end
138
+
139
+ # Save the changes to the file.
140
+ def save
141
+ return false if @changed_values.empty?
142
+ @errors.clear
143
+ temp_file = Tempfile.new('mini_exiftool')
144
+ temp_file.close
145
+ temp_filename = temp_file.path
146
+ FileUtils.cp filename, temp_filename
147
+ all_ok = true
148
+ @changed_values.each do |tag, val|
149
+ original_tag = MiniExiftool.original_tag(tag)
150
+ arr_val = val.kind_of?(Array) ? val : [val]
151
+ arr_val.map! {|e| convert e}
152
+ tag_params = ''
153
+ arr_val.each do |v|
154
+ tag_params << %Q(-#{original_tag}="#{v}" )
155
+ end
156
+ opt_params = ''
157
+ opt_params << (arr_val.detect {|x| x.kind_of?(Numeric)} ? '-n ' : '')
158
+ opt_params << (@convert_encoding ? '-L ' : '')
159
+ cmd = %Q(#@@cmd -q -P -overwrite_original #{opt_params} #{tag_params} "#{temp_filename}")
160
+ result = run(cmd)
161
+ unless result
162
+ all_ok = false
163
+ @errors[tag] = @error_text.gsub(/Nothing to do.\n\z/, '').chomp
164
+ end
165
+ end
166
+ if all_ok
167
+ FileUtils.cp temp_filename, filename
168
+ reload
169
+ end
170
+ temp_file.delete
171
+ all_ok
172
+ end
173
+
174
+ # Returns a hash of the original loaded values of the MiniExiftool
175
+ # instance.
176
+ def to_hash
177
+ result = {}
178
+ @values.each do |k,v|
179
+ result[@tag_names[k]] = v
180
+ end
181
+ result
182
+ end
183
+
184
+ # Returns a YAML representation of the original loaded values of the
185
+ # MiniExiftool instance.
186
+ def to_yaml
187
+ to_hash.to_yaml
188
+ end
189
+
190
+ # Create a MiniExiftool instance from a hash
191
+ def self.from_hash hash
192
+ instance = MiniExiftool.new
193
+ instance.initialize_from_hash hash
194
+ instance
195
+ end
196
+
197
+ # Create a MiniExiftool instance from YAML data created with
198
+ # MiniExiftool#to_yaml
199
+ def self.from_yaml yaml
200
+ MiniExiftool.from_hash YAML.load(yaml)
201
+ end
202
+
203
+ # Returns the command name of the called Exiftool application.
204
+ def self.command
205
+ @@cmd
206
+ end
207
+
208
+ # Setting the command name of the called Exiftool application.
209
+ def self.command= cmd
210
+ @@cmd = cmd
211
+ end
212
+
213
+ # Returns the options hash.
214
+ def self.opts
215
+ @@opts
216
+ end
217
+
218
+ # Returns a set of all known tags of Exiftool.
219
+ def self.all_tags
220
+ unless defined? @@all_tags
221
+ @@all_tags = pstore_get :all_tags
222
+ end
223
+ @@all_tags
224
+ end
225
+
226
+ # Returns a set of all possible writable tags of Exiftool.
227
+ def self.writable_tags
228
+ unless defined? @@writable_tags
229
+ @@writable_tags = pstore_get :writable_tags
230
+ end
231
+ @@writable_tags
232
+ end
233
+
234
+ # Returns the original Exiftool name of the given tag
235
+ def self.original_tag tag
236
+ unless defined? @@all_tags_map
237
+ @@all_tags_map = pstore_get :all_tags_map
238
+ end
239
+ @@all_tags_map[tag]
240
+ end
241
+
242
+ # Returns the version of the Exiftool command-line application.
243
+ def self.exiftool_version
244
+ output = `#{MiniExiftool.command} -ver 2>&1`
245
+ unless $?.exitstatus == 0
246
+ raise MiniExiftool::Error.new("Command '#{MiniExiftool.command}' not found")
247
+ end
248
+ output.chomp!
249
+ end
250
+
251
+ def self.unify tag
252
+ tag.gsub(/[-_]/,'').downcase
253
+ end
254
+
255
+ # Exception class
256
+ class MiniExiftool::Error < StandardError; end
257
+
258
+ ############################################################################
259
+ private
260
+ ############################################################################
261
+
262
+ @@error_file = Tempfile.new 'errors'
263
+ @@error_file.close
264
+
265
+ if Float(exiftool_version) < 7.41
266
+ @@separator = ', '
267
+ @@sep_op = ''
268
+ else
269
+ @@separator = '@@'
270
+ @@sep_op = '-sep @@'
271
+ end
272
+
273
+ def run cmd
274
+ if $DEBUG
275
+ $stderr.puts cmd
276
+ end
277
+ @output = `#{cmd} 2>#{@@error_file.path}`
278
+ @status = $?
279
+ unless @status.exitstatus == 0
280
+ @error_text = File.readlines(@@error_file.path).join
281
+ return false
282
+ else
283
+ @error_text = ''
284
+ return true
285
+ end
286
+ end
287
+
288
+ def convert val
289
+ case val
290
+ when Time
291
+ val = val.strftime('%Y:%m:%d %H:%M:%S')
292
+ end
293
+ val
294
+ end
295
+
296
+ def method_missing symbol, *args
297
+ tag_name = symbol.id2name
298
+ if tag_name.sub!(/=$/, '')
299
+ self[tag_name] = args.first
300
+ else
301
+ self[tag_name]
302
+ end
303
+ end
304
+
305
+ def parse_output
306
+ @output.each_line do |line|
307
+ tag, value = parse_line line
308
+ set_value tag, value
309
+ end
310
+ end
311
+
312
+ def parse_line line
313
+ if line =~ /^([^\t]+)\t(.*)$/
314
+ tag, value = $1, $2
315
+ case value
316
+ when /^\d{4}:\d\d:\d\d \d\d:\d\d:\d\d$/
317
+ arr = value.split(/[: ]/) #Fixed by kro44 - add parentheses
318
+ arr.map! {|elem| elem.to_i}
319
+ begin
320
+ if @timestamps == Time
321
+ value = Time.local(*arr) #Fixed by kro44 - add parentheses
322
+ elsif @timestamps == DateTime
323
+ value = DateTime.strptime(value,'%Y:%m:%d %H:%M:%S')
324
+ else
325
+ raise MiniExiftool::Error.new("Value #@timestamps not allowed for option timestamps.")
326
+ end
327
+ rescue ArgumentError
328
+ value = false
329
+ end
330
+ when /^\d+\.\d+$/
331
+ value = value.to_f
332
+ when /^0+[1-9]+$/
333
+ # nothing => String
334
+ when /^-?\d+$/
335
+ value = value.to_i
336
+ when /^[\d ]+$/
337
+ # nothing => String
338
+ when /#{@@separator}/
339
+ value = value.split @@separator
340
+ end
341
+ else
342
+ raise MiniExiftool::Error.new("Malformed line #{line.inspect} of exiftool output.")
343
+ end
344
+ unless value.respond_to?('to_a')
345
+ class << value
346
+ def to_a
347
+ [self]
348
+ end
349
+ end
350
+ end
351
+ return [tag, value]
352
+ end
353
+
354
+ def set_value tag, value
355
+ @tag_names[tag] = tag
356
+ @values[tag] = value
357
+ end
358
+
359
+ def set_attributes_by_heuristic
360
+ self.composite = tags.include?('ImageSize') ? true : false
361
+ self.numerical = self.file_size.kind_of?(Integer) ? true : false
362
+ # TODO: Is there a heuristic to determine @convert_encoding?
363
+ self.timestamps = self.FileModifyDate.kind_of?(DateTime) ? DateTime : Time
364
+ end
365
+
366
+ def temp_filename
367
+ unless @temp_filename
368
+ temp_file = Tempfile.new('mini-exiftool')
369
+ temp_file.close
370
+ FileUtils.cp(@filename, temp_file.path)
371
+ @temp_filename = temp_file.path
372
+ end
373
+ @temp_filename
374
+ end
375
+
376
+ def self.pstore_get attribute
377
+ load_or_create_pstore unless defined? @@pstore
378
+ result = nil
379
+ @@pstore.transaction(true) do |ps|
380
+ result = ps[attribute]
381
+ end
382
+ result
383
+ end
384
+
385
+ def self.load_or_create_pstore
386
+ # This will hopefully work on *NIX and Windows systems
387
+ home = ENV['HOME'] || ENV['HOMEDRIVE'] + ENV['HOMEPATH'] || ENV['USERPROFILE']
388
+ subdir = RUBY_PLATFORM =~ /win/i ? '_mini_exiftool' : '.mini_exiftool'
389
+ FileUtils.mkdir_p(File.join(home, subdir))
390
+ filename = File.join(home, subdir, 'exiftool_tags_' << exiftool_version.gsub('.', '_') << '.pstore')
391
+ @@pstore = PStore.new filename
392
+ if !File.exist?(filename) || File.size(filename) == 0
393
+ @@pstore.transaction do |ps|
394
+ ps[:all_tags] = all_tags = determine_tags('list')
395
+ ps[:writable_tags] = determine_tags('listw')
396
+ map = {}
397
+ all_tags.each { |k| map[unify(k)] = k }
398
+ ps[:all_tags_map] = map
399
+ end
400
+ end
401
+ end
402
+
403
+ def self.determine_tags arg
404
+ output = `#{@@cmd} -#{arg}`
405
+ lines = output.split(/\n/) #Fixed by kro44 - add parentheses
406
+ tags = Set.new
407
+ lines.each do |line|
408
+ next unless line =~ /^\s/
409
+ tags |= line.chomp.split
410
+ end
411
+ tags
412
+ end
413
+
414
+
415
+ # Hash with indifferent access:
416
+ # DateTimeOriginal == datetimeoriginal == date_time_original
417
+ class TagHash < Hash # :nodoc:
418
+ def[] k
419
+ super(unify(k))
420
+ end
421
+ def []= k, v
422
+ super(unify(k), v)
423
+ end
424
+ def delete k
425
+ super(unify(k))
426
+ end
427
+
428
+ def unify tag
429
+ MiniExiftool.unify tag
430
+ end
431
+ end
432
+
433
+ end
434
+
435
+ # Add to_a to Numerical if it's not yet defined
436
+ unless Numeric.instance_methods.include? 'to_a'
437
+ class Numeric
438
+ def to_a
439
+ [self]
440
+ end
441
+ end
442
+ end
443
+
444
+ # Test if we can run the Exiftool command
445
+ MiniExiftool.exiftool_version
@@ -29,7 +29,7 @@ class MiniExiftool
29
29
  attr_reader :filename
30
30
  attr_accessor :numerical, :composite, :convert_encoding, :errors, :timestamps
31
31
 
32
- VERSION = '1.0.1'
32
+ VERSION = '1.0.2'
33
33
 
34
34
  # +opts+ support at the moment
35
35
  # * <code>:numerical</code> for numerical values, default is +false+
@@ -314,11 +314,11 @@ class MiniExiftool
314
314
  tag, value = $1, $2
315
315
  case value
316
316
  when /^\d{4}:\d\d:\d\d \d\d:\d\d:\d\d$/
317
- arr = value.split /[: ]/
317
+ arr = value.split(/[: ]/)
318
318
  arr.map! {|elem| elem.to_i}
319
319
  begin
320
320
  if @timestamps == Time
321
- value = Time.local *arr
321
+ value = Time.local(*arr)
322
322
  elsif @timestamps == DateTime
323
323
  value = DateTime.strptime(value,'%Y:%m:%d %H:%M:%S')
324
324
  else
@@ -402,7 +402,7 @@ class MiniExiftool
402
402
 
403
403
  def self.determine_tags arg
404
404
  output = `#{@@cmd} -#{arg}`
405
- lines = output.split /\n/
405
+ lines = output.split(/\n/)
406
406
  tags = Set.new
407
407
  lines.each do |line|
408
408
  next unless line =~ /^\s/
data/x.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'mini_exiftool'
2
+
3
+ puts MiniExiftool.exiftool_version
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini_exiftool
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ hash: 19
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 2
10
+ version: 1.0.2
5
11
  platform: ruby
6
12
  authors:
7
13
  - Jan Friedrich
@@ -9,7 +15,7 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2008-11-10 00:00:00 +01:00
18
+ date: 2010-12-30 00:00:00 +01:00
13
19
  default_executable:
14
20
  dependencies: []
15
21
 
@@ -23,32 +29,36 @@ extra_rdoc_files:
23
29
  - README
24
30
  - Tutorial
25
31
  files:
32
+ - examples/external_photo.rb
33
+ - examples/print_portraits.rb
34
+ - examples/shift_time.rb
35
+ - lib/mini_exiftool.new.rb
26
36
  - lib/mini_exiftool.rb
27
- - test/test_write.rb
28
- - test/test_read.rb
29
- - test/test_special.rb
37
+ - setup.rb
38
+ - test/helpers_for_test.rb
30
39
  - test/test_class_methods.rb
31
- - test/test_save.rb
32
40
  - test/test_composite.rb
41
+ - test/test_dumping.rb
42
+ - test/test_read.rb
33
43
  - test/test_read_numerical.rb
34
- - test/helpers_for_test.rb
44
+ - test/test_save.rb
45
+ - test/test_special.rb
35
46
  - test/test_special_dates.rb
36
- - test/test_dumping.rb
37
- - examples/external_photo.rb
38
- - examples/print_portraits.rb
39
- - examples/shift_time.rb
40
- - setup.rb
47
+ - test/test_write.rb
48
+ - x.rb
41
49
  - Rakefile
42
50
  - COPYING
43
51
  - Changelog
44
- - test/data/test.jpg
45
52
  - test/data/Canon.jpg
46
53
  - test/data/INFORMATION
54
+ - test/data/test.jpg
47
55
  - test/data/test_special_dates.jpg
48
56
  - README
49
57
  - Tutorial
50
58
  has_rdoc: true
51
59
  homepage: http://miniexiftool.rubyforge.org/
60
+ licenses: []
61
+
52
62
  post_install_message:
53
63
  rdoc_options:
54
64
  - --title
@@ -58,31 +68,37 @@ rdoc_options:
58
68
  require_paths:
59
69
  - lib
60
70
  required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
61
72
  requirements:
62
73
  - - ">="
63
74
  - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
64
78
  version: "0"
65
- version:
66
79
  required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
67
81
  requirements:
68
82
  - - ">="
69
83
  - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
70
87
  version: "0"
71
- version:
72
88
  requirements: []
73
89
 
74
90
  rubyforge_project: miniexiftool
75
- rubygems_version: 1.2.0
91
+ rubygems_version: 1.3.7
76
92
  signing_key:
77
- specification_version: 2
93
+ specification_version: 3
78
94
  summary: A library for nice OO access to the Exiftool command-line application written by Phil Harvey.
79
95
  test_files:
80
- - test/test_write.rb
81
- - test/test_read.rb
82
- - test/test_special.rb
83
96
  - test/test_class_methods.rb
84
- - test/test_save.rb
85
97
  - test/test_composite.rb
98
+ - test/test_dumping.rb
99
+ - test/test_read.rb
86
100
  - test/test_read_numerical.rb
101
+ - test/test_save.rb
102
+ - test/test_special.rb
87
103
  - test/test_special_dates.rb
88
- - test/test_dumping.rb
104
+ - test/test_write.rb