exifr 1.1.3 → 1.3.9

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 91531cf59588956e8b9675148197cdca2b8bcbe35edb42476bfe56f73959470b
4
+ data.tar.gz: ddd951e6773d11ce0a0db5214983774b8078e410451920cc441fdd55926c574e
5
+ SHA512:
6
+ metadata.gz: a8ebe84816409a77d19b67d1ac8a244ef63bd3a8046e08f7193e40f8bb01c2866320aa52a3664a43ed07dc6d30c2f17005d33d2408e69574987b2e5127425b0a
7
+ data.tar.gz: 296a144056ee9da865e663c9ef47dfc59811e24485c133a28a2b6978b62b37f7e8419a6c76e6e6aaacb52a03b599f04e18a9715e287f89c9fbff9fbac67a2c63
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
data/Rakefile CHANGED
@@ -1,39 +1,37 @@
1
- # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011 - R.W. van 't Veer
1
+ # Copyright (c) 2006-2020 - R.W. van 't Veer
2
2
 
3
- require 'rake/rdoctask'
4
3
  require 'rake/testtask'
5
4
 
6
5
  task :default => :test
7
6
 
8
- desc 'Generate site'
9
- task :site => :rdoc do
10
- system 'rsync -av --delete doc/ remvee@rubyforge.org:/var/www/gforge-projects/exifr'
11
- end
12
-
13
- Rake::RDocTask.new do |rd|
14
- rd.title = 'EXIF Reader for Ruby API Documentation'
15
- rd.main = "README.rdoc"
16
- rd.rdoc_dir = "doc/api"
17
- rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
18
- end
19
-
20
-
21
7
  Rake::TestTask.new do |t|
22
8
  t.libs << 'lib' << 'tests'
23
9
  t.test_files = FileList['tests/*_test.rb']
10
+ t.warning = true
24
11
  end
25
12
 
26
13
  begin
27
- require 'rcov/rcovtask'
14
+ begin
15
+ require 'rdoc/task'
16
+ rescue LoadError
17
+ require 'rake/rdoctask'
18
+ end
28
19
 
29
- Rcov::RcovTask.new do |t|
30
- t.libs << 'lib' << 'tests'
31
- t.test_files = FileList['tests/*_test.rb']
20
+ desc 'Generate site'
21
+ task :site => :rdoc do
22
+ system 'rsync -av --delete doc/ remvee@rubyforge.org:/var/www/gforge-projects/exifr'
23
+ end
24
+
25
+ Rake::RDocTask.new do |rd|
26
+ rd.title = 'EXIF Reader for Ruby API Documentation'
27
+ rd.main = "README.rdoc"
28
+ rd.rdoc_dir = "doc/api"
29
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
32
30
  end
33
31
 
34
- desc 'Remove all artifacts left by testing and packaging'
35
- task :clean => [:clobber_rdoc, :clobber_rcov]
36
- rescue LoadError
37
32
  desc 'Remove all artifacts left by testing and packaging'
38
33
  task :clean => [:clobber_rdoc]
34
+
35
+ rescue StandardError
36
+ nil
39
37
  end
data/bin/exifr CHANGED
@@ -1,4 +1,7 @@
1
- require 'exifr'
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'exifr/jpeg'
4
+ require 'exifr/tiff'
2
5
  include EXIFR
3
6
 
4
7
  def pp_jpeg(fname)
data/lib/exifr/jpeg.rb CHANGED
@@ -1,7 +1,9 @@
1
- # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011 - R.W. van 't Veer
1
+ # Copyright (c) 2006-2020 - R.W. van 't Veer
2
2
 
3
3
  require 'exifr'
4
+ require 'exifr/tiff'
4
5
  require 'stringio'
6
+ require 'delegate'
5
7
 
6
8
  module EXIFR
7
9
  # = JPEG decoder
@@ -56,40 +58,47 @@ module EXIFR
56
58
  # +method+ does exist for EXIF data +nil+ will be returned.
57
59
  def method_missing(method, *args)
58
60
  super unless args.empty?
59
- super unless methods.include?(method.to_s)
61
+ super unless methods.include?(method)
60
62
  @exif.send method if defined?(@exif) && @exif
61
63
  end
62
64
 
63
- def respond_to?(method) # :nodoc:
64
- super || methods.include?(method.to_s)
65
+ def respond_to?(method, include_all = false) # :nodoc:
66
+ super || methods.include?(method) || (include_all && private_methods.include?(method))
65
67
  end
66
68
 
67
- def methods # :nodoc:
68
- super + TIFF::TAGS << "gps"
69
+ def methods(regular=true) # :nodoc:
70
+ if regular
71
+ super + TIFF::TAGS << :gps
72
+ else
73
+ super
74
+ end
69
75
  end
70
76
 
71
77
  class << self
72
78
  alias instance_methods_without_jpeg_extras instance_methods
73
79
  def instance_methods(include_super = true) # :nodoc:
74
- instance_methods_without_jpeg_extras(include_super) + TIFF::TAGS << "gps"
80
+ instance_methods_without_jpeg_extras(include_super) + TIFF::TAGS << :gps
75
81
  end
76
82
  end
77
83
 
78
84
  private
85
+
86
+ class Reader < SimpleDelegator
87
+ def readbyte; readchar; end unless File.method_defined?(:readbyte)
88
+ def readint; (readbyte << 8) + readbyte; end
89
+ def readframe; read(readint - 2); end
90
+ def readsof; [readint, readbyte, readint, readint, readbyte]; end
91
+ def next
92
+ c = readbyte while c != 0xFF
93
+ c = readbyte while c == 0xFF
94
+ c
95
+ end
96
+ end
97
+
79
98
  def examine(io)
80
- class << io
81
- def readbyte; readchar; end unless method_defined?(:readbyte)
82
- def readint; (readbyte << 8) + readbyte; end
83
- def readframe; read(readint - 2); end
84
- def readsof; [readint, readbyte, readint, readint, readbyte]; end
85
- def next
86
- c = readbyte while c != 0xFF
87
- c = readbyte while c == 0xFF
88
- c
89
- end
90
- end unless io.respond_to? :readsof
99
+ io = Reader.new(io)
91
100
 
92
- unless io.readbyte == 0xFF && io.readbyte == 0xD8 # SOI
101
+ unless io.getbyte == 0xFF && io.getbyte == 0xD8 # SOI
93
102
  raise MalformedJPEG, "no start of image marker found"
94
103
  end
95
104
 
data/lib/exifr/tiff.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2007, 2008, 2009, 2010, 2011 - R.W. van 't Veer
1
+ # Copyright (c) 2007-2017 - R.W. van 't Veer
2
2
 
3
3
  require 'exifr'
4
4
  require 'rational'
@@ -169,7 +169,7 @@ module EXIFR
169
169
  0x927c => :maker_note,
170
170
  0x9286 => :user_comment,
171
171
  0x9290 => :subsec_time,
172
- 0x9291 => :subsec_time_orginal,
172
+ 0x9291 => :subsec_time_original,
173
173
  0x9292 => :subsec_time_digitized,
174
174
  0xa000 => :flashpix_version,
175
175
  0xa001 => :color_space,
@@ -199,7 +199,10 @@ module EXIFR
199
199
  0xa40a => :sharpness,
200
200
  0xa40b => :device_setting_description,
201
201
  0xa40c => :subject_distance_range,
202
- 0xa420 => :image_unique_id
202
+ 0xa420 => :image_unique_id,
203
+ 0xa433 => :lens_make,
204
+ 0xa434 => :lens_model,
205
+ 0xa435 => :lens_serial_number
203
206
  },
204
207
 
205
208
  :gps => {
@@ -234,16 +237,28 @@ module EXIFR
234
237
  0x001c => :gps_area_information,
235
238
  0x001d => :gps_date_stamp,
236
239
  0x001e => :gps_differential,
240
+ 0x001f => :gps_h_positioning_error
237
241
  },
238
242
  })
239
243
  IFD_TAGS = [:image, :exif, :gps] # :nodoc:
240
244
 
245
+ class << self
246
+ # Callable to create a +Time+ object. Defaults to <tt>proc{|*a|Time.local(*a)}</tt>.
247
+ attr_accessor :mktime_proc
248
+ end
249
+ self.mktime_proc = proc { |*args| Time.local(*args) }
250
+
241
251
  time_proc = proc do |value|
242
- value.map do |value|
243
- if value =~ /^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/
244
- Time.mktime($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i) rescue nil
252
+ value.map do |v|
253
+ if v =~ /^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)(?:\.(\d{3}))?$/
254
+ begin
255
+ mktime_proc.call($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, $7.to_i * 1000)
256
+ rescue => ex
257
+ EXIFR.logger.warn("Bad date/time value #{v.inspect}: #{ex}")
258
+ nil
259
+ end
245
260
  else
246
- value
261
+ v
247
262
  end
248
263
  end
249
264
  end
@@ -259,6 +274,11 @@ module EXIFR
259
274
  @value
260
275
  end
261
276
 
277
+ # Symbolic value.
278
+ def to_sym
279
+ @type
280
+ end
281
+
262
282
  # Debugging output.
263
283
  def inspect
264
284
  "\#<EXIFR::TIFF::Orientation:#{@type}(#{@value})>"
@@ -300,9 +320,20 @@ module EXIFR
300
320
  const_set("#{type}Orientation", ORIENTATIONS[index] = Orientation.new(index, type))
301
321
  end
302
322
 
323
+ degrees_proc = proc do |v|
324
+ begin
325
+ Degrees.new(v)
326
+ rescue => ex
327
+ EXIFR.logger.warn("malformed GPS degrees: #{ex}")
328
+ nil
329
+ end
330
+ end
331
+
303
332
  class Degrees < Array
304
333
  def initialize(arr)
305
- raise MalformedTIFF, "expected [degrees, minutes, seconds]" unless arr.length == 3
334
+ unless arr.length == 3 && arr.all?{|v| Rational === v}
335
+ raise "expected [degrees, minutes, seconds]; got #{arr.inspect}"
336
+ end
306
337
  super
307
338
  end
308
339
 
@@ -312,7 +343,13 @@ module EXIFR
312
343
  end
313
344
 
314
345
  def self.rational(n, d)
315
- Rational.respond_to?(:reduce) ? Rational.reduce(n, d) : 1.quo(d)
346
+ if d == 0
347
+ n.to_f / d.to_f
348
+ elsif Rational.respond_to?(:reduce)
349
+ Rational.reduce(n, d)
350
+ else
351
+ n.quo(d)
352
+ end
316
353
  end
317
354
 
318
355
  def self.round(f, n)
@@ -325,19 +362,19 @@ module EXIFR
325
362
  :date_time_original => time_proc,
326
363
  :date_time_digitized => time_proc,
327
364
  :date_time => time_proc,
328
- :orientation => proc { |v| v.map{|v| ORIENTATIONS[v]} },
329
- :gps_latitude => proc { |v| Degrees.new(v) },
330
- :gps_longitude => proc { |v| Degrees.new(v) },
331
- :gps_dest_latitude => proc { |v| Degrees.new(v) },
332
- :gps_dest_longitude => proc { |v| Degrees.new(v) },
333
- :shutter_speed_value => proc { |v| v.map { |v| v.abs < 100 ? rational(1, (2 ** v).to_i) : nil } },
334
- :aperture_value => proc { |v| v.map { |v| round(1.4142 ** v, 1) } }
365
+ :orientation => proc { |x| x.map{|y| ORIENTATIONS[y]} },
366
+ :gps_latitude => degrees_proc,
367
+ :gps_longitude => degrees_proc,
368
+ :gps_dest_latitude => degrees_proc,
369
+ :gps_dest_longitude => degrees_proc,
370
+ :shutter_speed_value => proc { |x| x.map { |y| y.abs < 100 ? rational(1, (2 ** y).to_i) : nil } },
371
+ :aperture_value => proc { |x| x.map { |y| round(1.4142 ** y, 1) } }
335
372
  })
336
373
 
337
374
  # Names for all recognized TIFF fields.
338
- TAGS = ([TAG_MAPPING.keys, TAG_MAPPING.values.map{|v|v.values}].flatten.uniq - IFD_TAGS).map{|v|v.to_s}
375
+ TAGS = [TAG_MAPPING.keys, TAG_MAPPING.values.map{|v|v.values}].flatten.uniq - IFD_TAGS
339
376
 
340
- # +file+ is a filename or an IO object. Hint: use StringIO when working with slurped data like blobs.
377
+ # +file+ is a filename or an +IO+ object. Hint: use +StringIO+ when working with slurped data like blobs.
341
378
  def initialize(file)
342
379
  Data.open(file) do |data|
343
380
  @ifds = [IFD.new(data)]
@@ -346,10 +383,15 @@ module EXIFR
346
383
  @ifds << ifd
347
384
  end
348
385
 
349
- @jpeg_thumbnails = @ifds.map do |ifd|
350
- if ifd.jpeg_interchange_format && ifd.jpeg_interchange_format_length
351
- start, length = ifd.jpeg_interchange_format, ifd.jpeg_interchange_format_length
352
- data[start..(start + length)]
386
+ @jpeg_thumbnails = @ifds.map do |v|
387
+ if v.jpeg_interchange_format && v.jpeg_interchange_format_length
388
+ start, length = v.jpeg_interchange_format, v.jpeg_interchange_format_length
389
+ if Integer === start && Integer === length
390
+ data[start..(start + length)]
391
+ else
392
+ EXIFR.logger.warn("Non numeric JpegInterchangeFormat data")
393
+ nil
394
+ end
353
395
  end
354
396
  end.compact
355
397
  end
@@ -376,21 +418,33 @@ module EXIFR
376
418
 
377
419
  if @ifds.first.respond_to?(method)
378
420
  @ifds.first.send(method)
379
- elsif TAGS.include?(method.to_s)
421
+ elsif TAGS.include?(method)
380
422
  @ifds.first.to_hash[method]
381
423
  else
382
424
  super
383
425
  end
384
426
  end
385
427
 
386
- def respond_to?(method) # :nodoc:
428
+ def respond_to?(method, include_all = false) # :nodoc:
387
429
  super ||
388
- (defined?(@ifds) && @ifds && @ifds.first && @ifds.first.respond_to?(method)) ||
389
- TAGS.include?(method.to_s)
430
+ (defined?(@ifds) && @ifds && @ifds.first && @ifds.first.respond_to?(method, include_all)) ||
431
+ TAGS.include?(method)
390
432
  end
391
433
 
392
- def methods # :nodoc:
393
- (super + TAGS + IFD.instance_methods(false)).uniq
434
+ def methods(regular=true) # :nodoc:
435
+ if regular
436
+ (super + TAGS + IFD.instance_methods(false)).uniq
437
+ else
438
+ super
439
+ end
440
+ end
441
+
442
+ def encode_with(coder)
443
+ coder["ifds"] = @ifds
444
+ end
445
+
446
+ def to_yaml_properties
447
+ ['@ifds']
394
448
  end
395
449
 
396
450
  class << self
@@ -414,9 +468,12 @@ module EXIFR
414
468
  # Get GPS location, altitude and image direction return nil when not available.
415
469
  def gps
416
470
  return nil unless gps_latitude && gps_longitude
471
+
472
+ altitude = gps_altitude.is_a?(Array) ? gps_altitude.first : gps_altitude
473
+
417
474
  GPS.new(gps_latitude.to_f * (gps_latitude_ref == 'S' ? -1 : 1),
418
475
  gps_longitude.to_f * (gps_longitude_ref == 'W' ? -1 : 1),
419
- gps_altitude && (gps_altitude.to_f * (gps_altitude_ref == "\1" ? -1 : 1)),
476
+ altitude && (altitude.to_f * (gps_altitude_ref == "\1" ? -1 : 1)),
420
477
  gps_img_direction && gps_img_direction.to_f)
421
478
  end
422
479
 
@@ -432,20 +489,23 @@ module EXIFR
432
489
 
433
490
  pos = offset || @data.readlong(4)
434
491
  num = @data.readshort(pos)
435
- pos += 2
436
492
 
437
- num.times do
438
- add_field(Field.new(@data, pos))
439
- pos += 12
440
- end
493
+ if pos && num
494
+ pos += 2
495
+
496
+ num.times do
497
+ add_field(Field.new(@data, pos))
498
+ pos += 12
499
+ end
441
500
 
442
- @offset_next = @data.readlong(pos)
443
- rescue
444
- @offset_next = 0
501
+ @offset_next = @data.readlong(pos)
502
+ end
503
+ rescue => ex
504
+ EXIFR.logger.warn("Badly formed IFD: #{ex}")
445
505
  end
446
506
 
447
507
  def method_missing(method, *args)
448
- super unless args.empty? && TAGS.include?(method.to_s)
508
+ super unless args.empty? && TAGS.include?(method)
449
509
  to_hash[method]
450
510
  end
451
511
 
@@ -469,13 +529,17 @@ module EXIFR
469
529
  end
470
530
 
471
531
  def next?
472
- @offset_next != 0 && @offset_next < @data.size
532
+ @offset_next && @offset_next > 0 && @offset_next < @data.size
473
533
  end
474
534
 
475
535
  def next
476
536
  IFD.new(@data, @offset_next) if next?
477
537
  end
478
538
 
539
+ def encode_with(coder)
540
+ coder["fields"] = @fields
541
+ end
542
+
479
543
  def to_yaml_properties
480
544
  ['@fields']
481
545
  end
@@ -507,7 +571,7 @@ module EXIFR
507
571
  when 6 # signed byte
508
572
  len, pack = count, proc { |d| sign_byte(d) }
509
573
  when 2 # ascii
510
- len, pack = count, proc { |d| d.unpack("A*") }
574
+ len, pack = count, proc { |d| d.unpack('Z*') }
511
575
  when 3 # short
512
576
  len, pack = count * 2, proc { |d| d.unpack(data.short + '*') }
513
577
  when 8 # signed short
@@ -540,6 +604,8 @@ module EXIFR
540
604
  end
541
605
  rationals
542
606
  end
607
+ else
608
+ return
543
609
  end
544
610
 
545
611
  if len && pack && @type != 7
data/lib/exifr.rb CHANGED
@@ -1,10 +1,12 @@
1
- # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011 - R.W. van 't Veer
1
+ # Copyright (c) 2006-2020 - R.W. van 't Veer
2
+
3
+ require 'logger'
2
4
 
3
5
  module EXIFR
4
6
  class MalformedImage < StandardError; end
5
7
  class MalformedJPEG < MalformedImage; end
6
8
  class MalformedTIFF < MalformedImage; end
7
- end
8
9
 
9
- require 'exifr/jpeg'
10
- require 'exifr/tiff'
10
+ class << self; attr_accessor :logger; end
11
+ self.logger = Logger.new(STDERR)
12
+ end
Binary file
Binary file
Binary file
Binary file
data/tests/jpeg_test.rb CHANGED
@@ -1,27 +1,28 @@
1
1
  #!/usr/bin/env ruby
2
2
  #
3
- # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011 - R.W. van 't Veer
3
+ # Copyright (c) 2006-2020 - R.W. van 't Veer
4
4
 
5
5
  require 'test_helper'
6
6
 
7
- class JPEGTest < Test::Unit::TestCase
7
+ class JPEGTest < TestCase
8
8
  def test_initialize
9
9
  all_test_jpegs.each do |fname|
10
- assert_nothing_raised do
11
- JPEG.new(fname)
12
- end
13
- assert_nothing_raised do
14
- open(fname) { |rd| JPEG.new(rd) }
15
- end
16
- assert_nothing_raised do
17
- JPEG.new(StringIO.new(File.read(fname)))
18
- end
10
+ assert JPEG.new(fname)
11
+ open(fname) { |rd| assert JPEG.new(rd) }
12
+ assert JPEG.new(StringIO.new(File.read(fname)))
19
13
  end
20
14
  end
21
15
 
22
16
  def test_raises_malformed_jpeg
23
- assert_raise MalformedJPEG do
17
+ begin
18
+ JPEG.new(StringIO.new(""))
19
+ rescue MalformedJPEG => ex
20
+ assert ex
21
+ end
22
+ begin
24
23
  JPEG.new(StringIO.new("djibberish"))
24
+ rescue MalformedJPEG => ex
25
+ assert ex
25
26
  end
26
27
  end
27
28
 
@@ -64,8 +65,8 @@ class JPEGTest < Test::Unit::TestCase
64
65
  def test_exif
65
66
  assert ! JPEG.new(f('image.jpg')).exif?
66
67
  assert JPEG.new(f('exif.jpg')).exif?
67
- assert_not_nil JPEG.new(f('exif.jpg')).exif.date_time
68
- assert_not_nil JPEG.new(f('exif.jpg')).exif.f_number
68
+ assert JPEG.new(f('exif.jpg')).exif.date_time
69
+ assert JPEG.new(f('exif.jpg')).exif.f_number
69
70
  end
70
71
 
71
72
  def test_to_hash
@@ -82,21 +83,31 @@ class JPEGTest < Test::Unit::TestCase
82
83
 
83
84
  def test_exif_dispatch
84
85
  j = JPEG.new(f('exif.jpg'))
85
-
86
- assert JPEG.instance_methods.include?('date_time')
87
- assert j.methods.include?('date_time')
86
+ assert JPEG.instance_methods.include?(:date_time)
87
+ assert j.methods.include?(:date_time)
88
88
  assert j.respond_to?(:date_time)
89
- assert j.respond_to?('date_time')
90
- assert_not_nil j.date_time
89
+ assert j.date_time
91
90
  assert_kind_of Time, j.date_time
92
91
 
93
- assert_not_nil j.f_number
92
+ assert j.f_number
94
93
  assert_kind_of Rational, j.f_number
95
94
  end
96
95
 
97
96
  def test_no_method_error
98
- assert_nothing_raised { JPEG.new(f('image.jpg')).f_number }
99
- assert_raise(NoMethodError) { JPEG.new(f('image.jpg')).foo }
97
+ JPEG.new(f('image.jpg')).f_number
98
+
99
+ begin
100
+ JPEG.new(f('image.jpg')).foo
101
+ rescue NoMethodError => ex
102
+ assert ex
103
+ end
104
+ end
105
+
106
+ def test_methods_method
107
+ j = JPEG.new(f('exif.jpg'))
108
+ assert j.methods.include?(:date_time)
109
+ assert j.methods(true).include?(:date_time)
110
+ assert ! j.methods(false).include?(:date_time)
100
111
  end
101
112
 
102
113
  def test_multiple_app1
@@ -108,9 +119,7 @@ class JPEGTest < Test::Unit::TestCase
108
119
  all_test_jpegs.each do |fname|
109
120
  jpeg = JPEG.new(fname)
110
121
  unless jpeg.thumbnail.nil?
111
- assert_nothing_raised 'thumbnail not a JPEG' do
112
- JPEG.new(StringIO.new(jpeg.thumbnail))
113
- end
122
+ assert JPEG.new(StringIO.new(jpeg.thumbnail))
114
123
  count += 1
115
124
  end
116
125
  end
@@ -118,4 +127,28 @@ class JPEGTest < Test::Unit::TestCase
118
127
  assert count > 0, 'no thumbnails found'
119
128
  end
120
129
 
130
+ def test_gps_with_altitude
131
+ t = JPEG.new(f('gps-altitude.jpg'))
132
+
133
+ assert_equal([Rational(230, 1), Rational(0, 1), Rational(0, 1)], t.gps_altitude)
134
+ assert_equal(230, t.gps.altitude)
135
+ end
136
+
137
+ def test_exif_datetime_milliseconds
138
+ if Time.now.strftime('%L') == '%L'
139
+ STDERR.puts("skipping milliseconds test; not supported on this platform")
140
+ return
141
+ end
142
+
143
+ j = JPEG.new(f('exif.jpg'))
144
+ assert_equal('000', j.date_time.strftime('%L'))
145
+ assert_equal('000', j.date_time_original.strftime('%L'))
146
+ assert_equal('000', j.date_time_digitized.strftime('%L'))
147
+
148
+ j = JPEG.new(f('ios-mspix-milliseconds.jpg'))
149
+ assert_equal('978', j.date_time.strftime('%L'))
150
+ assert_equal('978', j.date_time_original.strftime('%L'))
151
+ assert_equal('978', j.date_time_digitized.strftime('%L'))
152
+ end
153
+
121
154
  end
data/tests/test_helper.rb CHANGED
@@ -1,21 +1,39 @@
1
1
  #!/usr/bin/env ruby
2
2
  #
3
- # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011 - R.W. van 't Veer
3
+ # Copyright (c) 2006-2020 - R.W. van 't Veer
4
4
 
5
- require 'test/unit'
6
5
  require 'stringio'
7
6
  require 'pp'
8
7
 
8
+ TestCase = begin
9
+ tc = begin
10
+ gem 'minitest' rescue nil
11
+ require 'minitest/autorun'
12
+ case
13
+ when defined?(Minitest::Test) ; Minitest::Test
14
+ when defined?(Minitest::Unit::TestCase) ; Minitest::Unit::TestCase
15
+ end
16
+ rescue LoadError
17
+ # nop
18
+ end
19
+ unless tc
20
+ require "test/unit"
21
+ tc = Test::Unit::TestCase
22
+ end
23
+ tc
24
+ end
25
+
9
26
  $:.unshift("#{File.dirname(__FILE__)}/../lib")
10
- require 'exifr'
27
+ require 'exifr/jpeg'
28
+ require 'exifr/tiff'
11
29
  include EXIFR
12
30
 
31
+ EXIFR.logger = Logger.new(StringIO.new)
13
32
 
14
33
  def all_test_jpegs
15
34
  Dir[f('*.jpg')]
16
35
  end
17
36
 
18
-
19
37
  def all_test_exifs
20
38
  Dir[f('*.exif')]
21
39
  end
data/tests/tiff_test.rb CHANGED
@@ -1,31 +1,27 @@
1
1
  #!/usr/bin/env ruby
2
2
  #
3
- # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011 - R.W. van 't Veer
3
+ # Copyright (c) 2006-2020 - R.W. van 't Veer
4
4
 
5
5
  require 'test_helper'
6
6
 
7
- class TIFFTest < Test::Unit::TestCase
7
+ class TIFFTest < TestCase
8
8
  def setup
9
9
  @t = TIFF.new(f('nikon_d1x.tif'))
10
10
  end
11
11
 
12
12
  def test_initialize
13
13
  all_test_tiffs.each do |fname|
14
- assert_nothing_raised do
15
- TIFF.new(fname)
16
- end
17
- assert_nothing_raised do
18
- open(fname) { |rd| TIFF.new(rd) }
19
- end
20
- assert_nothing_raised do
21
- TIFF.new(StringIO.new(File.read(fname)))
22
- end
14
+ assert TIFF.new(fname)
15
+ open(fname) { |rd| assert TIFF.new(rd) }
16
+ assert TIFF.new(StringIO.new(File.read(fname)))
23
17
  end
24
18
  end
25
-
19
+
26
20
  def test_raises_malformed_tiff
27
- assert_raise MalformedTIFF do
21
+ begin
28
22
  TIFF.new(StringIO.new("djibberish"))
23
+ rescue MalformedTIFF => ex
24
+ assert ex
29
25
  end
30
26
  end
31
27
 
@@ -59,12 +55,20 @@ class TIFFTest < Test::Unit::TestCase
59
55
  end
60
56
 
61
57
  def test_dates
62
- (all_test_tiffs - [f('weird_date.exif'), f('plain.tif'), f('endless-loop.exif')]).each do |fname|
58
+ (all_test_tiffs - [f('weird_date.exif'), f('plain.tif'), f('endless-loop.exif'), f('truncated.exif')]).each do |fname|
63
59
  assert_kind_of Time, TIFF.new(fname).date_time
64
60
  end
65
61
  assert_nil TIFF.new(f('weird_date.exif')).date_time
66
62
  end
67
63
 
64
+ def test_time_with_zone
65
+ old_proc = TIFF.mktime_proc
66
+ TIFF.mktime_proc = proc { |*args| "TIME-WITH-ZONE" }
67
+ assert_equal "TIME-WITH-ZONE", TIFF.new(f('nikon_d1x.tif')).date_time
68
+ ensure
69
+ TIFF.mktime_proc = old_proc
70
+ end
71
+
68
72
  def test_orientation
69
73
  tested = 0 # count tests because not all exif samples have an orientation field
70
74
  all_test_exifs.each do |fname|
@@ -80,6 +84,16 @@ class TIFFTest < Test::Unit::TestCase
80
84
  TIFF::RightBottomOrientation,
81
85
  TIFF::LeftBottomOrientation
82
86
  ].any? { |c| orientation == c }, 'not an orientation'
87
+ assert [
88
+ :TopLeft,
89
+ :TopRight,
90
+ :BottomRight,
91
+ :BottomLeft,
92
+ :LeftTop,
93
+ :RightTop,
94
+ :RightBottom,
95
+ :LeftBottom
96
+ ].any? { |c| orientation.to_sym == c }, 'not an orientation symbol'
83
97
  assert orientation.respond_to?(:to_i)
84
98
  assert orientation.respond_to?(:transform_rmagick)
85
99
  tested += 1
@@ -98,30 +112,37 @@ class TIFFTest < Test::Unit::TestCase
98
112
  assert_equal('WGS84', t.gps_map_datum)
99
113
  assert_equal(54, t.gps.latitude.round)
100
114
  assert_equal(-7, t.gps.longitude.round)
115
+ assert_nil(t.gps.altitude)
101
116
 
102
117
  (all_test_exifs - %w(gps user-comment out-of-range negative-exposure-bias-value).map{|v| f("#{v}.exif")}).each do |fname|
103
118
  assert_nil TIFF.new(fname).gps_version_id
104
119
  end
105
120
  end
106
121
 
122
+ def test_bad_gps
123
+ assert_nil TIFF.new(f('bad_gps.exif')).gps
124
+ end
125
+
126
+ def test_lens_model
127
+ t = TIFF.new(f('sony-a7ii.exif'))
128
+ assert_equal('FE 16-35mm F4 ZA OSS', t.lens_model)
129
+ end
130
+
107
131
  def test_ifd_dispatch
108
132
  assert @t.respond_to?(:f_number)
109
- assert @t.respond_to?('f_number')
110
- assert @t.methods.include?('f_number')
111
- assert TIFF.instance_methods.include?('f_number')
133
+ assert @t.methods.include?(:f_number)
134
+ assert TIFF.instance_methods.include?(:f_number)
112
135
 
113
- assert_not_nil @t.f_number
136
+ assert @t.f_number
114
137
  assert_kind_of Rational, @t.f_number
115
- assert_not_nil @t[0].f_number
138
+ assert @t[0].f_number
116
139
  assert_kind_of Rational, @t[0].f_number
117
140
  end
118
141
 
119
142
  def test_avoid_dispatch_to_nonexistent_ifds
120
- assert_nothing_raised do
121
- all_test_tiffs.each do |fname|
122
- t = TIFF.new(fname)
123
- TIFF::TAGS.each { |tag| t.send(tag) }
124
- end
143
+ all_test_tiffs.each do |fname|
144
+ assert t = TIFF.new(fname)
145
+ assert TIFF::TAGS.map { |tag| t.send(tag) }
125
146
  end
126
147
  end
127
148
 
@@ -135,9 +156,7 @@ class TIFFTest < Test::Unit::TestCase
135
156
  end
136
157
 
137
158
  def test_old_style
138
- assert_nothing_raised do
139
- assert_not_nil @t[:f_number]
140
- end
159
+ assert @t[:f_number]
141
160
  end
142
161
 
143
162
  def test_yaml_dump_and_load
@@ -155,10 +174,8 @@ class TIFFTest < Test::Unit::TestCase
155
174
  all_test_tiffs.each do |fname|
156
175
  t = TIFF.new(fname)
157
176
  unless t.jpeg_thumbnails.empty?
158
- assert_nothing_raised do
159
- t.jpeg_thumbnails.each do |n|
160
- JPEG.new(StringIO.new(n))
161
- end
177
+ t.jpeg_thumbnails.each do |n|
178
+ assert JPEG.new(StringIO.new(n))
162
179
  end
163
180
  count += 1
164
181
  end
@@ -171,17 +188,31 @@ class TIFFTest < Test::Unit::TestCase
171
188
  assert true
172
189
  end
173
190
 
191
+ def test_should_read_truncated
192
+ TIFF.new(f('truncated.exif'))
193
+ assert true
194
+ end
195
+
174
196
  def test_user_comment
175
197
  assert_equal("Manassas Battlefield", TIFF.new(f('user-comment.exif')).user_comment)
176
198
  end
177
199
 
178
200
  def test_handle_out_of_range_offset
179
- assert_nothing_raised do
180
- assert 'NIKON', TIFF.new(f('out-of-range.exif')).make
181
- end
201
+ assert_equal 'NIKON', TIFF.new(f('out-of-range.exif')).make
182
202
  end
183
-
203
+
184
204
  def test_negative_exposure_bias_value
185
205
  assert_equal(-1.quo(3), TIFF.new(f('negative-exposure-bias-value.exif')).exposure_bias_value)
186
206
  end
207
+
208
+ def test_nul_terminated_strings
209
+ assert_equal 'GoPro', TIFF.new(f('gopro_hd2.exif')).make
210
+ end
211
+
212
+ def test_methods_method
213
+ t = TIFF.new(f('gopro_hd2.exif'))
214
+ assert t.methods.include?(:make)
215
+ assert t.methods(true).include?(:make)
216
+ assert ! t.methods(false).include?(:make)
217
+ end
187
218
  end
metadata CHANGED
@@ -1,81 +1,109 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exifr
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
5
- prerelease:
4
+ version: 1.3.9
6
5
  platform: ruby
7
6
  authors:
8
7
  - R.W. van 't Veer
9
- autorequire:
8
+ autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-05-31 00:00:00.000000000 Z
13
- dependencies: []
14
- description:
15
- email: remco@remvee.net
11
+ date: 2020-10-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: test-unit
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 3.1.5
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 3.1.5
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '12'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '12'
41
+ description: EXIF Reader is a module to read EXIF from JPEG and TIFF images.
42
+ email: exifr@remworks.net
16
43
  executables:
17
44
  - exifr
18
45
  extensions: []
19
- extra_rdoc_files:
20
- - README.rdoc
21
- - CHANGELOG
46
+ extra_rdoc_files: []
22
47
  files:
48
+ - Gemfile
23
49
  - Rakefile
24
50
  - bin/exifr
25
51
  - lib/exifr.rb
26
52
  - lib/exifr/jpeg.rb
27
53
  - lib/exifr/tiff.rb
28
54
  - tests/data/1x1.jpg
29
- - tests/data/apple-aperture-1.5.exif
30
- - tests/data/canon-g3.exif
31
55
  - tests/data/Canon_PowerShot_A85.exif
32
56
  - tests/data/Casio-EX-S20.exif
57
+ - tests/data/FUJIFILM-FinePix_S3000.exif
58
+ - tests/data/Panasonic-DMC-LC33.exif
59
+ - tests/data/Trust-DC3500_MINI.exif
60
+ - tests/data/apple-aperture-1.5.exif
61
+ - tests/data/bad-shutter_speed_value.exif
62
+ - tests/data/canon-g3.exif
33
63
  - tests/data/endless-loop.exif
34
64
  - tests/data/exif.jpg
35
- - tests/data/FUJIFILM-FinePix_S3000.exif
65
+ - tests/data/gopro_hd2.exif
66
+ - tests/data/gps-altitude.jpg
36
67
  - tests/data/gps.exif
37
68
  - tests/data/image.jpg
38
69
  - tests/data/multiple-app1.jpg
39
70
  - tests/data/negative-exposure-bias-value.exif
40
71
  - tests/data/nikon_d1x.tif
41
72
  - tests/data/out-of-range.exif
42
- - tests/data/Panasonic-DMC-LC33.exif
43
73
  - tests/data/plain.tif
44
- - tests/data/Trust-DC3500_MINI.exif
74
+ - tests/data/samsung-sc-02b.jpg
75
+ - tests/data/sony-a7ii.exif
45
76
  - tests/data/user-comment.exif
46
77
  - tests/data/weird_date.exif
47
- - tests/data/bad-shutter_speed_value.exif
48
78
  - tests/jpeg_test.rb
49
79
  - tests/test_helper.rb
50
80
  - tests/tiff_test.rb
51
- - README.rdoc
52
- - CHANGELOG
53
81
  homepage: http://github.com/remvee/exifr/
54
- licenses: []
55
- post_install_message:
56
- rdoc_options:
57
- - --title
58
- - EXIF Reader for Ruby API Documentation
59
- - --main
60
- - README.rdoc
82
+ licenses:
83
+ - MIT
84
+ metadata:
85
+ bug_tracker_uri: https://github.com/remvee/exifr/issues
86
+ changelog_uri: https://github.com/remvee/exifr/blob/master/CHANGELOG
87
+ documentation_uri: https://remvee.github.io/exifr/api/
88
+ homepage_uri: https://remvee.github.io/exifr/
89
+ source_code_uri: https://github.com/remvee/exifr
90
+ post_install_message:
91
+ rdoc_options: []
61
92
  require_paths:
62
93
  - lib
63
94
  required_ruby_version: !ruby/object:Gem::Requirement
64
- none: false
65
95
  requirements:
66
- - - ! '>='
96
+ - - ">="
67
97
  - !ruby/object:Gem::Version
68
- version: '0'
98
+ version: 1.8.7
69
99
  required_rubygems_version: !ruby/object:Gem::Requirement
70
- none: false
71
100
  requirements:
72
- - - ! '>='
101
+ - - ">="
73
102
  - !ruby/object:Gem::Version
74
103
  version: '0'
75
104
  requirements: []
76
- rubyforge_project:
77
- rubygems_version: 1.8.24
78
- signing_key:
79
- specification_version: 3
80
- summary: EXIF Reader is a module to read EXIF from JPEG images.
105
+ rubygems_version: 3.1.4
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: Read EXIF from JPEG and TIFF images
81
109
  test_files: []
data/CHANGELOG DELETED
@@ -1,108 +0,0 @@
1
- EXIF Reader 1.1.3
2
- * bug fix; "[GH#27] some error when :shutter_speed_value is too big"; thanks to zamia
3
-
4
- EXIF Reader 1.1.2
5
- * bug fix; "[GH#25] Incorrect values being extracted for a number of fields"; thanks to Matthew McEachen
6
-
7
- EXIF Reader 1.1.1
8
- * feature; "added gps convenience method to make accessing location data easier (degrees to float conversion)"
9
- * bug fix; "[GH#22] Fix warnings about uninitialized @comment"; thanks to Ryan Greenberg"
10
-
11
- EXIF Reader 1.0.6
12
- * bug fix: "[GH#20] `readlong': undefined method `unpack' for nil:NilClass (NoMethodError)"
13
-
14
- EXIF Reader 1.0.5
15
- * bug fix: "[GH#19] files opened by TIFF initialize were not being closed"; thanks to Joe Van Overberghe
16
-
17
- EXIF Reader 1.0.4
18
- * bug fix: "[GH#17] avoid library search path pollution"
19
- * enhancement: "[GH#18] add EXIFR::JPEG#app1s method"
20
-
21
- EXIF Reader 1.0.3
22
- * enhancement; "raise specific exception to allow better error handling"; thanks to James Miller
23
-
24
- EXIF Reader 1.0.2
25
- * bug fix; "[GH#9/12] no block given"; thanks to Ian Leitch
26
-
27
- EXIF Reader 1.0.1
28
- * bug fix; "[GH#7] Unable to properly display exposure_bias_value"; thanks to John Krueger
29
-
30
- EXIF Reader 1.0.0
31
- * bug fix; "ArgumentError: invalid byte sequence in UTF-8" when reading messy field using Ruby 1.9+
32
- * enhancement; "[GH#4] more informative inspect for EXIFR::TIFF::Orientation instance"
33
- * bug fix; "[GH#6] ERROR RuntimeError: can't add a new key into hash during iteration"; thanks to KISHIMOTO, Makoto
34
-
35
- EXIF Reader 0.10.9
36
- * bug fix; "[GH#1] user_comment returns nil for jpeg with UserComment"; thanks to Mark Lundquist
37
- * bug fix; "[GH#2] Nil pointer in tiff.rb"
38
- * enhancement; "don't read entire files into memory"; thanks to Victor Bogado
39
-
40
- EXIF Reader 0.10.8
41
- * feature request; "[#23694] The object interface of JPEG is different from the TIFF one."
42
-
43
- EXIF Reader 0.10.7
44
- * bug fix; "[#22403] Wrong file size reported"
45
-
46
- EXIF Reader 0.10.6.1
47
- * moved to GitHub
48
-
49
- EXIF Reader 0.10.6
50
- * bug fix (thanks to Forian Munz for reporting it); endless loop when reading a malformed EXIF/TIFF
51
-
52
- EXIF Reader 0.10.5
53
- * bug fix; "[#15421] duplicate orientation field behavior", first field (of any type) is leading now
54
- * Ruby 1.9 compatible
55
-
56
- EXIF Reader 0.10.4
57
- * Thumbnail extraction; [#15317] Please add thumbnail extraction
58
-
59
- EXIF Reader 0.10.3
60
- * YAML friendly; can now safely (de)serialize
61
-
62
- EXIF Reader 0.10.2
63
- * bug fix (thanks to Alexander Staubo for providing me with sample data);
64
- don't fail on out-of-range IFD offsets for Apple Aperture generated JPGs
65
-
66
- EXIF Reader 0.10.1
67
- * old style exif access
68
-
69
- EXIF Reader 0.10
70
- * TIFF support
71
-
72
- EXIF Reader 0.9.6
73
- * bug fix; "[#8458] Conversion from string to Time fails", weird dates will now reflect nil
74
-
75
- EXIF Reader 0.9.5.1
76
- * make tinderbox happy by hiding rcov task
77
-
78
- EXIF Reader 0.9.5
79
- * patch calls to jpeg through to exif, i.e. jpeg., i.e. jpeg.model == jpeg.exif.model
80
- * fix exifr commandline utility, needs require 'exifr' now
81
- * improve test helper
82
- * reduce size of test images
83
- * include tests for tinderbox
84
-
85
- EXIF Reader 0.9.4
86
- * bug fix (thanks to Benjamin Storrier for providing me with sample date);
87
- multiple app1 frames will potentially overwrite EXIF tag
88
-
89
- EXIF Reader 0.9.3
90
- * bug fix; "[#4876] Unable to extract gpsinfo"
91
- * one-off bug in TiffHeader found and fixed
92
- * make "InteroperabilityIndex" available
93
-
94
- EXIF Reader 0.9.2
95
- * bug fix; "[#4595] EXIFR::JPEG doesn't support multiple comments", the
96
- comment property of a JPEG object now contains an array instead of a string
97
- when multiple COM frames are found
98
- * EXIF orientation modules including RMagick code to rotate to viewable state
99
- * access to thumbnail included in EXIF
100
- * simple commandline utility, "exifr", to view image properties
101
- * overall code improvements including documentation and tests
102
-
103
- EXIF Reader 0.9.1
104
- * bug fix; "4321 Can't create object", division by zero when
105
- denominator of rational value is zero
106
-
107
- EXIF Reader 0.9
108
- * 1st release
data/README.rdoc DELETED
@@ -1,31 +0,0 @@
1
- = EXIF Reader
2
- EXIF Reader is a module to read metadata from JPEG and TIFF images.
3
-
4
- == Examples
5
- EXIFR::JPEG.new('IMG_6841.JPG').width # => 2272
6
- EXIFR::JPEG.new('IMG_6841.JPG').height # => 1704
7
- EXIFR::JPEG.new('IMG_6841.JPG').exif? # => true
8
- EXIFR::JPEG.new('IMG_6841.JPG').model # => "Canon PowerShot G3"
9
- EXIFR::JPEG.new('IMG_6841.JPG').date_time # => Fri Feb 09 16:48:54 +0100 2007
10
- EXIFR::JPEG.new('IMG_6841.JPG').exposure_time.to_s # => "1/15"
11
- EXIFR::JPEG.new('IMG_6841.JPG').f_number.to_f # => 2.0
12
-
13
- EXIFR::TIFF.new('DSC_0218.TIF').width # => 3008
14
- EXIFR::TIFF.new('DSC_0218.TIF')[1].width # => 160
15
- EXIFR::TIFF.new('DSC_0218.TIF').model # => "NIKON D1X"
16
- EXIFR::TIFF.new('DSC_0218.TIF').date_time # => Tue May 23 19:15:32 +0200 2006
17
- EXIFR::TIFF.new('DSC_0218.TIF').exposure_time.to_s # => "1/100"
18
- EXIFR::TIFF.new('DSC_0218.TIF').f_number.to_f # => 5.0
19
-
20
- EXIFR::JPEG.new('enkhuizen.jpg').gps.latitude # => 52.7197888888889
21
- EXIFR::JPEG.new('enkhuizen.jpg').gps.longitude # => 5.28397777777778
22
-
23
- == XMP data access
24
- If you need to access XMP data you can use the xmp gem. More info and
25
- examples at https://github.com/amberbit/xmp
26
-
27
- == Author
28
- R.W. van 't Veer
29
-
30
- == Copyright
31
- Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011 - R.W. van 't Veer