exifr 1.1.3 → 1.3.8

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: 19c3f1b08e84425d9d0b8a6e6ee11bd2e25190a49ce34d42ea99800f5030fe20
4
+ data.tar.gz: 9a834f93b1a02e87bf401b09a033b25fb8b05625b0a60a8795e9ee65e1ea064c
5
+ SHA512:
6
+ metadata.gz: b4fc267041e9090a85eb5ca2a0bfc2fae3d36a72aa45ab716209e6e5e896a775957b65251587e16e7c4e3f0e3c2e491805ce226c68b9b189a8b5bdaf506ebc15
7
+ data.tar.gz: f39334aa7142a3b918edf30fa3bd6424ddc50c1906beb2c9d5bf384bc939fba35b410d2ee801410769929a29895e75b223d443af01d7acf7752fbf96adad0fa6
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,18 @@ 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
+ raise "expected [degrees, minutes, seconds]" unless arr.length == 3
306
335
  super
307
336
  end
308
337
 
@@ -312,7 +341,13 @@ module EXIFR
312
341
  end
313
342
 
314
343
  def self.rational(n, d)
315
- Rational.respond_to?(:reduce) ? Rational.reduce(n, d) : 1.quo(d)
344
+ if d == 0
345
+ n.to_f / d.to_f
346
+ elsif Rational.respond_to?(:reduce)
347
+ Rational.reduce(n, d)
348
+ else
349
+ n.quo(d)
350
+ end
316
351
  end
317
352
 
318
353
  def self.round(f, n)
@@ -325,19 +360,19 @@ module EXIFR
325
360
  :date_time_original => time_proc,
326
361
  :date_time_digitized => time_proc,
327
362
  :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) } }
363
+ :orientation => proc { |x| x.map{|y| ORIENTATIONS[y]} },
364
+ :gps_latitude => degrees_proc,
365
+ :gps_longitude => degrees_proc,
366
+ :gps_dest_latitude => degrees_proc,
367
+ :gps_dest_longitude => degrees_proc,
368
+ :shutter_speed_value => proc { |x| x.map { |y| y.abs < 100 ? rational(1, (2 ** y).to_i) : nil } },
369
+ :aperture_value => proc { |x| x.map { |y| round(1.4142 ** y, 1) } }
335
370
  })
336
371
 
337
372
  # 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}
373
+ TAGS = [TAG_MAPPING.keys, TAG_MAPPING.values.map{|v|v.values}].flatten.uniq - IFD_TAGS
339
374
 
340
- # +file+ is a filename or an IO object. Hint: use StringIO when working with slurped data like blobs.
375
+ # +file+ is a filename or an +IO+ object. Hint: use +StringIO+ when working with slurped data like blobs.
341
376
  def initialize(file)
342
377
  Data.open(file) do |data|
343
378
  @ifds = [IFD.new(data)]
@@ -346,10 +381,15 @@ module EXIFR
346
381
  @ifds << ifd
347
382
  end
348
383
 
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)]
384
+ @jpeg_thumbnails = @ifds.map do |v|
385
+ if v.jpeg_interchange_format && v.jpeg_interchange_format_length
386
+ start, length = v.jpeg_interchange_format, v.jpeg_interchange_format_length
387
+ if Integer === start && Integer === length
388
+ data[start..(start + length)]
389
+ else
390
+ EXIFR.logger.warn("Non numeric JpegInterchangeFormat data")
391
+ nil
392
+ end
353
393
  end
354
394
  end.compact
355
395
  end
@@ -376,21 +416,33 @@ module EXIFR
376
416
 
377
417
  if @ifds.first.respond_to?(method)
378
418
  @ifds.first.send(method)
379
- elsif TAGS.include?(method.to_s)
419
+ elsif TAGS.include?(method)
380
420
  @ifds.first.to_hash[method]
381
421
  else
382
422
  super
383
423
  end
384
424
  end
385
425
 
386
- def respond_to?(method) # :nodoc:
426
+ def respond_to?(method, include_all = false) # :nodoc:
387
427
  super ||
388
- (defined?(@ifds) && @ifds && @ifds.first && @ifds.first.respond_to?(method)) ||
389
- TAGS.include?(method.to_s)
428
+ (defined?(@ifds) && @ifds && @ifds.first && @ifds.first.respond_to?(method, include_all)) ||
429
+ TAGS.include?(method)
430
+ end
431
+
432
+ def methods(regular=true) # :nodoc:
433
+ if regular
434
+ (super + TAGS + IFD.instance_methods(false)).uniq
435
+ else
436
+ super
437
+ end
390
438
  end
391
439
 
392
- def methods # :nodoc:
393
- (super + TAGS + IFD.instance_methods(false)).uniq
440
+ def encode_with(coder)
441
+ coder["ifds"] = @ifds
442
+ end
443
+
444
+ def to_yaml_properties
445
+ ['@ifds']
394
446
  end
395
447
 
396
448
  class << self
@@ -414,9 +466,12 @@ module EXIFR
414
466
  # Get GPS location, altitude and image direction return nil when not available.
415
467
  def gps
416
468
  return nil unless gps_latitude && gps_longitude
469
+
470
+ altitude = gps_altitude.is_a?(Array) ? gps_altitude.first : gps_altitude
471
+
417
472
  GPS.new(gps_latitude.to_f * (gps_latitude_ref == 'S' ? -1 : 1),
418
473
  gps_longitude.to_f * (gps_longitude_ref == 'W' ? -1 : 1),
419
- gps_altitude && (gps_altitude.to_f * (gps_altitude_ref == "\1" ? -1 : 1)),
474
+ altitude && (altitude.to_f * (gps_altitude_ref == "\1" ? -1 : 1)),
420
475
  gps_img_direction && gps_img_direction.to_f)
421
476
  end
422
477
 
@@ -432,20 +487,23 @@ module EXIFR
432
487
 
433
488
  pos = offset || @data.readlong(4)
434
489
  num = @data.readshort(pos)
435
- pos += 2
436
490
 
437
- num.times do
438
- add_field(Field.new(@data, pos))
439
- pos += 12
440
- end
491
+ if pos && num
492
+ pos += 2
493
+
494
+ num.times do
495
+ add_field(Field.new(@data, pos))
496
+ pos += 12
497
+ end
441
498
 
442
- @offset_next = @data.readlong(pos)
443
- rescue
444
- @offset_next = 0
499
+ @offset_next = @data.readlong(pos)
500
+ end
501
+ rescue => ex
502
+ EXIFR.logger.warn("Badly formed IFD: #{ex}")
445
503
  end
446
504
 
447
505
  def method_missing(method, *args)
448
- super unless args.empty? && TAGS.include?(method.to_s)
506
+ super unless args.empty? && TAGS.include?(method)
449
507
  to_hash[method]
450
508
  end
451
509
 
@@ -469,13 +527,17 @@ module EXIFR
469
527
  end
470
528
 
471
529
  def next?
472
- @offset_next != 0 && @offset_next < @data.size
530
+ @offset_next && @offset_next > 0 && @offset_next < @data.size
473
531
  end
474
532
 
475
533
  def next
476
534
  IFD.new(@data, @offset_next) if next?
477
535
  end
478
536
 
537
+ def encode_with(coder)
538
+ coder["fields"] = @fields
539
+ end
540
+
479
541
  def to_yaml_properties
480
542
  ['@fields']
481
543
  end
@@ -507,7 +569,7 @@ module EXIFR
507
569
  when 6 # signed byte
508
570
  len, pack = count, proc { |d| sign_byte(d) }
509
571
  when 2 # ascii
510
- len, pack = count, proc { |d| d.unpack("A*") }
572
+ len, pack = count, proc { |d| d.unpack('Z*') }
511
573
  when 3 # short
512
574
  len, pack = count * 2, proc { |d| d.unpack(data.short + '*') }
513
575
  when 8 # signed short
@@ -540,6 +602,8 @@ module EXIFR
540
602
  end
541
603
  rationals
542
604
  end
605
+ else
606
+ return
543
607
  end
544
608
 
545
609
  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,33 @@ 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_lens_model
123
+ t = TIFF.new(f('sony-a7ii.exif'))
124
+ assert_equal('FE 16-35mm F4 ZA OSS', t.lens_model)
125
+ end
126
+
107
127
  def test_ifd_dispatch
108
128
  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')
129
+ assert @t.methods.include?(:f_number)
130
+ assert TIFF.instance_methods.include?(:f_number)
112
131
 
113
- assert_not_nil @t.f_number
132
+ assert @t.f_number
114
133
  assert_kind_of Rational, @t.f_number
115
- assert_not_nil @t[0].f_number
134
+ assert @t[0].f_number
116
135
  assert_kind_of Rational, @t[0].f_number
117
136
  end
118
137
 
119
138
  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
139
+ all_test_tiffs.each do |fname|
140
+ assert t = TIFF.new(fname)
141
+ assert TIFF::TAGS.map { |tag| t.send(tag) }
125
142
  end
126
143
  end
127
144
 
@@ -135,9 +152,7 @@ class TIFFTest < Test::Unit::TestCase
135
152
  end
136
153
 
137
154
  def test_old_style
138
- assert_nothing_raised do
139
- assert_not_nil @t[:f_number]
140
- end
155
+ assert @t[:f_number]
141
156
  end
142
157
 
143
158
  def test_yaml_dump_and_load
@@ -155,10 +170,8 @@ class TIFFTest < Test::Unit::TestCase
155
170
  all_test_tiffs.each do |fname|
156
171
  t = TIFF.new(fname)
157
172
  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
173
+ t.jpeg_thumbnails.each do |n|
174
+ assert JPEG.new(StringIO.new(n))
162
175
  end
163
176
  count += 1
164
177
  end
@@ -171,17 +184,31 @@ class TIFFTest < Test::Unit::TestCase
171
184
  assert true
172
185
  end
173
186
 
187
+ def test_should_read_truncated
188
+ TIFF.new(f('truncated.exif'))
189
+ assert true
190
+ end
191
+
174
192
  def test_user_comment
175
193
  assert_equal("Manassas Battlefield", TIFF.new(f('user-comment.exif')).user_comment)
176
194
  end
177
195
 
178
196
  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
197
+ assert_equal 'NIKON', TIFF.new(f('out-of-range.exif')).make
182
198
  end
183
-
199
+
184
200
  def test_negative_exposure_bias_value
185
201
  assert_equal(-1.quo(3), TIFF.new(f('negative-exposure-bias-value.exif')).exposure_bias_value)
186
202
  end
203
+
204
+ def test_nul_terminated_strings
205
+ assert_equal 'GoPro', TIFF.new(f('gopro_hd2.exif')).make
206
+ end
207
+
208
+ def test_methods_method
209
+ t = TIFF.new(f('gopro_hd2.exif'))
210
+ assert t.methods.include?(:make)
211
+ assert t.methods(true).include?(:make)
212
+ assert ! t.methods(false).include?(:make)
213
+ end
187
214
  end
metadata CHANGED
@@ -1,81 +1,110 @@
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.8
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-09-24 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
+ rubyforge_project:
106
+ rubygems_version: 2.7.6
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Read EXIF from JPEG and TIFF images
81
110
  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