mini_exiftool 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Changelog CHANGED
@@ -1,6 +1,10 @@
1
+ Version 1.1.0
2
+ - Escaping filenames in shell commands
3
+ Thanks to Michael Hoy for the hint and implementing a patch which was
4
+ the base for this fix.
1
5
  Version 1.0.2
2
6
  - Fixing warings
3
- thanks to Peter-Hinrich Krogmann for the hint.
7
+ Thanks to Peter-Hinrich Krogmann for the hint.
4
8
  Version 1.0.1
5
9
  - Fixing bug [#22726]
6
10
  Making MiniExiftool::Error public.
@@ -16,6 +16,7 @@ require 'fileutils'
16
16
  require 'tempfile'
17
17
  require 'pstore'
18
18
  require 'set'
19
+ require 'shellwords'
19
20
 
20
21
  # Simple OO access to the Exiftool command-line application.
21
22
  class MiniExiftool
@@ -29,7 +30,7 @@ class MiniExiftool
29
30
  attr_reader :filename
30
31
  attr_accessor :numerical, :composite, :convert_encoding, :errors, :timestamps
31
32
 
32
- VERSION = '1.0.2'
33
+ VERSION = '1.1.0'
33
34
 
34
35
  # +opts+ support at the moment
35
36
  # * <code>:numerical</code> for numerical values, default is +false+
@@ -80,7 +81,7 @@ class MiniExiftool
80
81
  opt_params << (@numerical ? '-n ' : '')
81
82
  opt_params << (@composite ? '' : '-e ')
82
83
  opt_params << (@convert_encoding ? '-L ' : '')
83
- cmd = %Q(#@@cmd -q -q -s -t #{opt_params} #{@@sep_op} "#{filename}")
84
+ cmd = %Q(#@@cmd -q -q -s -t #{opt_params} #{@@sep_op} #{Shellwords.escape(filename)})
84
85
  if run(cmd)
85
86
  parse_output
86
87
  else
@@ -156,7 +157,7 @@ class MiniExiftool
156
157
  opt_params = ''
157
158
  opt_params << (arr_val.detect {|x| x.kind_of?(Numeric)} ? '-n ' : '')
158
159
  opt_params << (@convert_encoding ? '-L ' : '')
159
- cmd = %Q(#@@cmd -q -P -overwrite_original #{opt_params} #{tag_params} "#{temp_filename}")
160
+ cmd = %Q(#@@cmd -q -P -overwrite_original #{opt_params} #{tag_params} #{temp_filename})
160
161
  result = run(cmd)
161
162
  unless result
162
163
  all_ok = false
@@ -27,14 +27,6 @@ class TestClassMethods < TestCase
27
27
  rescue MiniExiftool::Error => e
28
28
  assert_match /File 'not_existing_file' does not exist/, e.message
29
29
  end
30
- assert_raises MiniExiftool::Error do
31
- MiniExiftool.new __FILE__ # file type wich Exiftool can not handle
32
- end
33
- begin
34
- MiniExiftool.new __FILE__ # file type wich Exiftool can not handle
35
- rescue MiniExiftool::Error => e
36
- assert_match /Error: Unknown (?:image|file) type/, e.message
37
- end
38
30
  end
39
31
 
40
32
  def test_command
@@ -76,14 +76,4 @@ class TestDumping < TestCase
76
76
  assert_equal true, MiniExiftool.from_yaml(numerical.to_yaml).numerical
77
77
  end
78
78
 
79
- def test_heuristics_for_restoring_timestamps
80
- standard = @mini_exiftool.to_hash
81
- timestamps = MiniExiftool.new(@filename_test, :timestamps => DateTime).to_hash
82
- assert_equal Time, MiniExiftool.from_hash(standard).timestamps
83
- assert_equal DateTime, MiniExiftool.from_hash(timestamps).timestamps
84
- # Doesn't work yet.
85
- # assert_equal Time, MiniExiftool.from_yaml(standard.to_yaml).timestamps
86
- # assert_equal DateTime, MiniExiftool.from_yaml(timestamps.to_yaml).timestamps
87
- end
88
-
89
79
  end
@@ -0,0 +1,25 @@
1
+ require 'helpers_for_test'
2
+
3
+ class TestEscapeFilename < TestCase
4
+
5
+ def setup
6
+ @temp_file = Tempfile.new('test')
7
+ @temp_file.close
8
+ @temp_filename = @temp_file.path
9
+ @org_filename = File.dirname(__FILE__) + '/data/test 36"Bench.jpg'
10
+ FileUtils.cp(@org_filename, @temp_filename)
11
+ @mini_exiftool = MiniExiftool.new @temp_filename
12
+ end
13
+
14
+ def test_access
15
+ assert_equal '36" Bench', @mini_exiftool['description']
16
+ end
17
+
18
+ def test_save
19
+ desc = 'another bench'
20
+ @mini_exiftool.description = desc
21
+ assert @mini_exiftool.save
22
+ assert_equal desc, @mini_exiftool.description
23
+ end
24
+
25
+ end
@@ -25,7 +25,7 @@ class TestRead < TestCase
25
25
  assert_kind_of Float, @mini_exiftool['MaxApertureValue']
26
26
  assert_kind_of String, @mini_exiftool.flash
27
27
  assert_kind_of Fixnum, @mini_exiftool['ExposureCompensation']
28
- assert_kind_of String, @mini_exiftool['SubjectLocation']
28
+ assert_kind_of String, (@mini_exiftool['SubjectLocation'] || @mini_exiftool['SubjectArea'])
29
29
  assert_kind_of Array, @mini_exiftool['Keywords']
30
30
  assert_kind_of String, @mini_exiftool['SupplementalCategories']
31
31
  assert_kind_of Array, @mini_exiftool['SupplementalCategories'].to_a
@@ -22,7 +22,7 @@ class TestReadNumerical < TestCase
22
22
  assert_kind_of Fixnum, @mini_exiftool_num.flash
23
23
  assert_kind_of String, @mini_exiftool_num.exif_version
24
24
  assert_kind_of Fixnum, @mini_exiftool_num['ExposureCompensation']
25
- assert_kind_of String, @mini_exiftool_num['SubjectLocation']
25
+ assert_kind_of String, (@mini_exiftool_num['SubjectLocation'] || @mini_exiftool_num['SubjectArea'])
26
26
  assert_kind_of Array, @mini_exiftool_num['Keywords']
27
27
  assert_kind_of String, @mini_exiftool_num['SupplementalCategories']
28
28
  assert_kind_of Array, @mini_exiftool_num['SupplementalCategories'].to_a
@@ -32,7 +32,7 @@ class TestSave < TestCase
32
32
  result = @mini_exiftool.save
33
33
  assert_equal false, result
34
34
  assert_equal 1, @mini_exiftool.errors.size
35
- assert_equal("Can't convert IFD0:Orientation (not in PrintConv)",
35
+ assert_match(/Can't convert IFD0:Orientation \(not in PrintConv\)/,
36
36
  @mini_exiftool.errors['Orientation'])
37
37
  assert @mini_exiftool.changed?
38
38
  assert @mini_exiftool.changed_tags.include?('Orientation')
metadata CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
+ - 1
8
9
  - 0
9
- - 2
10
- version: 1.0.2
10
+ version: 1.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jan Friedrich
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-12-30 00:00:00 +01:00
18
+ date: 2011-02-10 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -32,25 +32,25 @@ files:
32
32
  - examples/external_photo.rb
33
33
  - examples/print_portraits.rb
34
34
  - examples/shift_time.rb
35
- - lib/mini_exiftool.new.rb
36
35
  - lib/mini_exiftool.rb
37
36
  - setup.rb
38
37
  - test/helpers_for_test.rb
39
38
  - test/test_class_methods.rb
40
39
  - test/test_composite.rb
41
40
  - test/test_dumping.rb
41
+ - test/test_escape_filename.rb
42
42
  - test/test_read.rb
43
43
  - test/test_read_numerical.rb
44
44
  - test/test_save.rb
45
45
  - test/test_special.rb
46
46
  - test/test_special_dates.rb
47
47
  - test/test_write.rb
48
- - x.rb
49
48
  - Rakefile
50
49
  - COPYING
51
50
  - Changelog
52
51
  - test/data/Canon.jpg
53
52
  - test/data/INFORMATION
53
+ - test/data/test 36"Bench.jpg
54
54
  - test/data/test.jpg
55
55
  - test/data/test_special_dates.jpg
56
56
  - README
@@ -96,6 +96,7 @@ test_files:
96
96
  - test/test_class_methods.rb
97
97
  - test/test_composite.rb
98
98
  - test/test_dumping.rb
99
+ - test/test_escape_filename.rb
99
100
  - test/test_read.rb
100
101
  - test/test_read_numerical.rb
101
102
  - test/test_save.rb
@@ -1,445 +0,0 @@
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
data/x.rb DELETED
@@ -1,3 +0,0 @@
1
- require 'mini_exiftool'
2
-
3
- puts MiniExiftool.exiftool_version