exifr 1.1.3 → 1.3.8

Sign up to get free protection for your applications and to get access to all the features.
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