exifr 1.3.6 → 1.5.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 74d218b251891a0cf7e748402bc6e357c4ad3dfc3aa5aefa293372f60921a1be
4
- data.tar.gz: 3d4d4d8b073067bba935cce965007feed1ce04aff3b0043c31caedd64dd4b6f2
3
+ metadata.gz: 42fbc8bb5f1d6bf15e8aff2b8b662549cf021dd0703f117c4da9be306ffe22c8
4
+ data.tar.gz: 212e0905aacf857cefd312fa428963a306a83be9ca53e0f572d5300fc4c79b2c
5
5
  SHA512:
6
- metadata.gz: 3c785eff92ad9eb5c05599c6312a927764c602d26adb7e026e00218bdb6a52d98f79f113b7bc2c0784d207c83357ba787354f871edefac1c8ab18017f6b5160f
7
- data.tar.gz: 03fe558a81381a3e49c36d1e7bbabe4e8831c1332c191267a7c8a56820dbcc56c478cb0727dd1407ccad64afb0b1ce2f4341218fead72e73fbee8dfe2a49a53a
6
+ metadata.gz: fdc4e04a08191d42d37b2fa283316729dfd2295ecca28edec8aa7d32b915625ca8992c68f5a03fd84eb7e4726c595e9c2e7f44744822b2e995f9bdee9249f92a
7
+ data.tar.gz: '090a90bd64c694434a9fed13cb3994b1a04da04e323ee3b8600d416ad7d0df0c19040aa92806985cb7e6da1e46a22e833cc88fb07b55faf8a89cc3e1172f7908'
data/Gemfile CHANGED
@@ -1,2 +1,2 @@
1
- source :rubygems
1
+ source "https://rubygems.org"
2
2
  gemspec
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2006-2017 - R.W. van 't Veer
1
+ # Copyright (c) 2006-2020 - R.W. van 't Veer
2
2
 
3
3
  require 'rake/testtask'
4
4
 
data/bin/exifr CHANGED
@@ -15,7 +15,7 @@ def pp_jpeg(fname)
15
15
  l << [k, v.inspect] if v
16
16
  end
17
17
  ks[4..-1].each do |k|
18
- v = jpeg.exif.to_hash[k.to_sym]
18
+ v = jpeg.exif.send(k)
19
19
  l << [k, v.inspect] if v
20
20
  end
21
21
  pp(fname, l)
@@ -27,7 +27,7 @@ def pp_tiff(fname)
27
27
  l = []
28
28
  l << ['width', img.width] << ['height', img.height]
29
29
  img.to_hash.keys.map{|a|a.to_s}.sort{|a,b|a<=>b}.each do |key|
30
- l << [key, img.to_hash[key.to_sym].inspect]
30
+ l << [key, img.send(key).inspect]
31
31
  end
32
32
  pp(tiff.size == 1 ? fname : "#{fname}[#{index}]", l)
33
33
  end
data/lib/exifr/jpeg.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2006-2017 - R.W. van 't Veer
1
+ # Copyright (c) 2006-2020 - R.W. van 't Veer
2
2
 
3
3
  require 'exifr'
4
4
  require 'exifr/tiff'
@@ -29,11 +29,11 @@ module EXIFR
29
29
  attr_reader :app1s
30
30
 
31
31
  # +file+ is a filename or an IO object. Hint: use StringIO when working with slurped data like blobs.
32
- def initialize(file)
32
+ def initialize(file, load_thumbnails: true)
33
33
  if file.kind_of? String
34
- File.open(file, 'rb') { |io| examine(io) }
34
+ File.open(file, 'rb') { |io| examine(io, load_thumbnails: load_thumbnails) }
35
35
  else
36
- examine(file.dup)
36
+ examine(file.dup, load_thumbnails: load_thumbnails)
37
37
  end
38
38
  end
39
39
 
@@ -95,7 +95,7 @@ module EXIFR
95
95
  end
96
96
  end
97
97
 
98
- def examine(io)
98
+ def examine(io, load_thumbnails: true)
99
99
  io = Reader.new(io)
100
100
 
101
101
  unless io.getbyte == 0xFF && io.getbyte == 0xD8 # SOI
@@ -121,7 +121,7 @@ module EXIFR
121
121
 
122
122
  if app1 = @app1s.find { |d| d[0..5] == "Exif\0\0" }
123
123
  @exif_data = app1[6..-1]
124
- @exif = TIFF.new(StringIO.new(@exif_data))
124
+ @exif = TIFF.new(StringIO.new(@exif_data), load_thumbnails: load_thumbnails)
125
125
  end
126
126
  end
127
127
  end
data/lib/exifr/tiff.rb CHANGED
@@ -153,6 +153,9 @@ module EXIFR
153
153
  0x9000 => :exif_version,
154
154
  0x9003 => :date_time_original,
155
155
  0x9004 => :date_time_digitized,
156
+ 0x9010 => :offset_time,
157
+ 0x9011 => :offset_time_original,
158
+ 0x9012 => :offset_time_digitized,
156
159
  0x9101 => :components_configuration,
157
160
  0x9102 => :compressed_bits_per_pixel,
158
161
  0x9201 => :shutter_speed_value,
@@ -331,7 +334,9 @@ module EXIFR
331
334
 
332
335
  class Degrees < Array
333
336
  def initialize(arr)
334
- raise "expected [degrees, minutes, seconds]" unless arr.length == 3
337
+ unless arr.length == 3 && arr.all?{|v| Rational === v}
338
+ raise "expected [degrees, minutes, seconds]; got #{arr.inspect}"
339
+ end
335
340
  super
336
341
  end
337
342
 
@@ -373,7 +378,7 @@ module EXIFR
373
378
  TAGS = [TAG_MAPPING.keys, TAG_MAPPING.values.map{|v|v.values}].flatten.uniq - IFD_TAGS
374
379
 
375
380
  # +file+ is a filename or an +IO+ object. Hint: use +StringIO+ when working with slurped data like blobs.
376
- def initialize(file)
381
+ def initialize(file, load_thumbnails: true)
377
382
  Data.open(file) do |data|
378
383
  @ifds = [IFD.new(data)]
379
384
  while ifd = @ifds.last.next
@@ -381,12 +386,21 @@ module EXIFR
381
386
  @ifds << ifd
382
387
  end
383
388
 
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
- data[start..(start + length)]
388
- end
389
- end.compact
389
+ if load_thumbnails
390
+ @jpeg_thumbnails = @ifds.map do |v|
391
+ if v.jpeg_interchange_format && v.jpeg_interchange_format_length
392
+ start, length = v.jpeg_interchange_format, v.jpeg_interchange_format_length
393
+ if Integer === start && Integer === length
394
+ data[start..(start + length)]
395
+ else
396
+ EXIFR.logger.warn("Non numeric JpegInterchangeFormat data")
397
+ nil
398
+ end
399
+ end
400
+ end.compact
401
+ else
402
+ @jpeg_thumbnails = []
403
+ end
390
404
  end
391
405
  end
392
406
 
@@ -470,29 +484,65 @@ module EXIFR
470
484
  gps_img_direction && gps_img_direction.to_f)
471
485
  end
472
486
 
487
+ # File changed date time (with UTC offset when available).
488
+ def date_time
489
+ date_time_with_zone(:date_time, :offset_time)
490
+ end
491
+
492
+ # Date and time original image generated (with UTC offset when
493
+ # available).
494
+ def date_time_original
495
+ date_time_with_zone(:date_time_original, :offset_time_original)
496
+ end
497
+
498
+ # Date and time image digitized (with UTC offset when available).
499
+ def date_time_digitized
500
+ date_time_with_zone(:date_time_digitized, :offset_time_digitized)
501
+ end
502
+
473
503
  def inspect # :nodoc:
474
504
  @ifds.inspect
475
505
  end
476
506
 
507
+ private
508
+
509
+ def date_time_with_zone(date_time_field, offset_time_field)
510
+ if self[date_time_field] && self[date_time_field].is_a?(Time) && self[offset_time_field]
511
+ Time.new(
512
+ self[date_time_field].year,
513
+ self[date_time_field].month,
514
+ self[date_time_field].day,
515
+ self[date_time_field].hour,
516
+ self[date_time_field].min,
517
+ self[date_time_field].sec,
518
+ self[offset_time_field]
519
+ )
520
+ else
521
+ self[date_time_field]
522
+ end
523
+ end
524
+
477
525
  class IFD # :nodoc:
478
- attr_reader :type, :fields, :offset
526
+ attr_reader :type, :raw_fields, :fields, :offset
479
527
 
480
528
  def initialize(data, offset = nil, type = :image)
481
- @data, @offset, @type, @fields = data, offset, type, {}
529
+ @data, @offset, @type, @raw_fields, @fields = data, offset, type, {}, {}
482
530
 
483
531
  pos = offset || @data.readlong(4)
484
532
  num = @data.readshort(pos)
485
- pos += 2
486
533
 
487
- num.times do
488
- add_field(Field.new(@data, pos))
489
- pos += 12
490
- end
534
+ if pos && num
535
+ pos += 2
536
+
537
+ num.times do
538
+ add_field(Field.new(@data, pos))
539
+ pos += 12
540
+ end
491
541
 
492
- @offset_next = @data.readlong(pos)
542
+ @offset_next = @data.readlong(pos)
543
+ end
493
544
  rescue => ex
494
545
  EXIFR.logger.warn("Badly formed IFD: #{ex}")
495
- @offset_next = 0
496
546
  end
497
547
 
498
548
  def method_missing(method, *args)
@@ -520,7 +570,7 @@ module EXIFR
520
570
  end
521
571
 
522
572
  def next?
523
- @offset_next != 0 && @offset_next < @data.size
573
+ @offset_next && @offset_next > 0 && @offset_next < @data.size
524
574
  end
525
575
 
526
576
  def next
@@ -537,15 +587,16 @@ module EXIFR
537
587
 
538
588
  private
539
589
  def add_field(field)
540
- return unless tag = TAG_MAPPING[@type][field.tag]
541
- return if @fields[tag]
590
+ return if @raw_fields.include?(field.tag) # first encountered value wins
591
+ @raw_fields[field.tag] = field.value
542
592
 
543
- if IFD_TAGS.include? tag
544
- @fields[tag] = IFD.new(@data, field.offset, tag)
545
- else
546
- value = ADAPTERS[tag][field.value]
547
- @fields[tag] = value.kind_of?(Array) && value.size == 1 ? value.first : value
548
- end
593
+ return unless tag = TAG_MAPPING[@type][field.tag]
594
+ @fields[tag] = if IFD_TAGS.include?(tag)
595
+ IFD.new(@data, field.offset, tag)
596
+ else
597
+ value = ADAPTERS[tag][field.value]
598
+ value.kind_of?(Array) && value.size == 1 ? value.first : value
599
+ end
549
600
  end
550
601
  end
551
602
 
@@ -595,6 +646,12 @@ module EXIFR
595
646
  end
596
647
  rationals
597
648
  end
649
+ when 11 # float
650
+ len, pack = count * 4, proc { |d| d.unpack(data.float + '*') }
651
+ when 12 # double
652
+ len, pack = count * 8, proc { |d| d.unpack(data.double + '*') }
653
+ else
654
+ return
598
655
  end
599
656
 
600
657
  if len && pack && @type != 7
@@ -627,7 +684,7 @@ module EXIFR
627
684
  end
628
685
 
629
686
  class Data #:nodoc:
630
- attr_reader :short, :long, :file
687
+ attr_reader :short, :long, :float, :double, :file
631
688
 
632
689
  def initialize(file)
633
690
  @io = file.respond_to?(:read) ? file : (@file = File.open(file, 'rb'))
@@ -635,8 +692,8 @@ module EXIFR
635
692
  @pos = 0
636
693
 
637
694
  case self[0..1]
638
- when 'II'; @short, @long = 'v', 'V'
639
- when 'MM'; @short, @long = 'n', 'N'
695
+ when 'II'; @short, @long, @float, @double = 'v', 'V', 'e', 'E'
696
+ when 'MM'; @short, @long, @float, @double = 'n', 'N', 'g', 'G'
640
697
  else
641
698
  raise MalformedTIFF, "no byte order information found"
642
699
  end
data/lib/exifr.rb CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2006-2017 - R.W. van 't Veer
1
+ # Copyright (c) 2006-2020 - R.W. van 't Veer
2
2
 
3
3
  require 'logger'
4
4
 
Binary file
data/tests/data/exif.jpg CHANGED
Binary file
Binary file
data/tests/jpeg_test.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  #
3
- # Copyright (c) 2006-2017 - R.W. van 't Veer
3
+ # Copyright (c) 2006-2020 - R.W. van 't Veer
4
4
 
5
5
  require 'test_helper'
6
6
 
@@ -107,7 +107,7 @@ class JPEGTest < TestCase
107
107
  j = JPEG.new(f('exif.jpg'))
108
108
  assert j.methods.include?(:date_time)
109
109
  assert j.methods(true).include?(:date_time)
110
- refute j.methods(false).include?(:date_time)
110
+ assert ! j.methods(false).include?(:date_time)
111
111
  end
112
112
 
113
113
  def test_multiple_app1
@@ -127,6 +127,13 @@ class JPEGTest < TestCase
127
127
  assert count > 0, 'no thumbnails found'
128
128
  end
129
129
 
130
+ def test_skippable_thumbnail
131
+ all_test_jpegs.each do |fname|
132
+ jpeg = JPEG.new(fname, load_thumbnails: false)
133
+ assert jpeg.thumbnail.nil?
134
+ end
135
+ end
136
+
130
137
  def test_gps_with_altitude
131
138
  t = JPEG.new(f('gps-altitude.jpg'))
132
139
 
@@ -151,4 +158,17 @@ class JPEGTest < TestCase
151
158
  assert_equal('978', j.date_time_digitized.strftime('%L'))
152
159
  end
153
160
 
161
+ def test_offset_times
162
+ j = JPEG.new(f('exif.jpg'))
163
+ assert_equal('-01:00', j.offset_time)
164
+ assert_equal('+01:00', j.offset_time_original)
165
+ assert_equal('+02:00', j.offset_time_digitized)
166
+ end
167
+
168
+ def test_data_time_with_utc_offset
169
+ j = JPEG.new(f('exif.jpg'))
170
+ assert_equal(Time.new(2004, 9, 9, 15, 14, 26, '-01:00'), j.date_time)
171
+ assert_equal(Time.new(2004, 9, 9, 15, 14, 26, '+01:00'), j.date_time_original)
172
+ assert_equal(Time.new(2004, 9, 9, 15, 14, 26, '+02:00'), j.date_time_digitized)
173
+ end
154
174
  end
data/tests/test_helper.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  #
3
- # Copyright (c) 2006-2017 - R.W. van 't Veer
3
+ # Copyright (c) 2006-2020 - R.W. van 't Veer
4
4
 
5
5
  require 'stringio'
6
6
  require 'pp'
data/tests/tiff_test.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  #
3
- # Copyright (c) 2006-2017 - R.W. van 't Veer
3
+ # Copyright (c) 2006-2020 - R.W. van 't Veer
4
4
 
5
5
  require 'test_helper'
6
6
 
@@ -55,7 +55,7 @@ class TIFFTest < TestCase
55
55
  end
56
56
 
57
57
  def test_dates
58
- (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|
59
59
  assert_kind_of Time, TIFF.new(fname).date_time
60
60
  end
61
61
  assert_nil TIFF.new(f('weird_date.exif')).date_time
@@ -119,6 +119,10 @@ class TIFFTest < TestCase
119
119
  end
120
120
  end
121
121
 
122
+ def test_bad_gps
123
+ assert_nil TIFF.new(f('bad_gps.exif')).gps
124
+ end
125
+
122
126
  def test_lens_model
123
127
  t = TIFF.new(f('sony-a7ii.exif'))
124
128
  assert_equal('FE 16-35mm F4 ZA OSS', t.lens_model)
@@ -161,7 +165,12 @@ class TIFFTest < TestCase
161
165
  all_test_tiffs.each do |fname|
162
166
  t = TIFF.new(fname)
163
167
  y = YAML.dump(t)
164
- assert_literally_equal t.to_hash, YAML.load(y).to_hash
168
+ v = if YAML.respond_to?(:unsafe_load)
169
+ YAML.unsafe_load(y)
170
+ else
171
+ YAML.load(y)
172
+ end
173
+ assert_literally_equal t.to_hash, v.to_hash
165
174
  end
166
175
  end
167
176
 
@@ -179,11 +188,23 @@ class TIFFTest < TestCase
179
188
  assert count > 0, 'no thumbnails found'
180
189
  end
181
190
 
191
+ def test_skippable_jpeg_thumbnails
192
+ all_test_tiffs.each do |fname|
193
+ t = TIFF.new(fname, load_thumbnails: false)
194
+ assert t.jpeg_thumbnails.empty?
195
+ end
196
+ end
197
+
182
198
  def test_should_not_loop_endlessly
183
199
  TIFF.new(f('endless-loop.exif'))
184
200
  assert true
185
201
  end
186
202
 
203
+ def test_should_read_truncated
204
+ TIFF.new(f('truncated.exif'))
205
+ assert true
206
+ end
207
+
187
208
  def test_user_comment
188
209
  assert_equal("Manassas Battlefield", TIFF.new(f('user-comment.exif')).user_comment)
189
210
  end
@@ -204,6 +225,10 @@ class TIFFTest < TestCase
204
225
  t = TIFF.new(f('gopro_hd2.exif'))
205
226
  assert t.methods.include?(:make)
206
227
  assert t.methods(true).include?(:make)
207
- refute t.methods(false).include?(:make)
228
+ assert ! t.methods(false).include?(:make)
229
+ end
230
+
231
+ def test_unknow_field
232
+ assert_equal [1, 1, 1, 1], TIFF.new(f('plain.tif')).first.raw_fields[0x0153] # TIFF Tag SampleFormat
208
233
  end
209
234
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exifr
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.6
4
+ version: 1.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - R.W. van 't Veer
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2019-02-10 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: test-unit
@@ -30,14 +29,14 @@ dependencies:
30
29
  requirements:
31
30
  - - "~>"
32
31
  - !ruby/object:Gem::Version
33
- version: '10'
32
+ version: '12'
34
33
  type: :development
35
34
  prerelease: false
36
35
  version_requirements: !ruby/object:Gem::Requirement
37
36
  requirements:
38
37
  - - "~>"
39
38
  - !ruby/object:Gem::Version
40
- version: '10'
39
+ version: '12'
41
40
  description: EXIF Reader is a module to read EXIF from JPEG and TIFF images.
42
41
  email: exifr@remworks.net
43
42
  executables:
@@ -59,6 +58,7 @@ files:
59
58
  - tests/data/Trust-DC3500_MINI.exif
60
59
  - tests/data/apple-aperture-1.5.exif
61
60
  - tests/data/bad-shutter_speed_value.exif
61
+ - tests/data/bad_gps.exif
62
62
  - tests/data/canon-g3.exif
63
63
  - tests/data/endless-loop.exif
64
64
  - tests/data/exif.jpg
@@ -66,6 +66,7 @@ files:
66
66
  - tests/data/gps-altitude.jpg
67
67
  - tests/data/gps.exif
68
68
  - tests/data/image.jpg
69
+ - tests/data/ios-mspix-milliseconds.jpg
69
70
  - tests/data/multiple-app1.jpg
70
71
  - tests/data/negative-exposure-bias-value.exif
71
72
  - tests/data/nikon_d1x.tif
@@ -73,16 +74,20 @@ files:
73
74
  - tests/data/plain.tif
74
75
  - tests/data/samsung-sc-02b.jpg
75
76
  - tests/data/sony-a7ii.exif
77
+ - tests/data/truncated.exif
76
78
  - tests/data/user-comment.exif
77
79
  - tests/data/weird_date.exif
78
80
  - tests/jpeg_test.rb
79
81
  - tests/test_helper.rb
80
82
  - tests/tiff_test.rb
81
- homepage: http://github.com/remvee/exifr/
83
+ homepage: http://codeberg.org/rwv/exifr/
82
84
  licenses:
83
85
  - MIT
84
- metadata: {}
85
- post_install_message:
86
+ metadata:
87
+ bug_tracker_uri: https://codeberg.org/rwv/exifr/issues
88
+ changelog_uri: https://codeberg.org/rwv/exifr/raw/branch/master/CHANGELOG
89
+ documentation_uri: https://www.rubydoc.info/gems/exifr
90
+ homepage_uri: https://codeberg.org/rwv/exifr
86
91
  rdoc_options: []
87
92
  require_paths:
88
93
  - lib
@@ -90,16 +95,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
90
95
  requirements:
91
96
  - - ">="
92
97
  - !ruby/object:Gem::Version
93
- version: 1.8.7
98
+ version: '2.0'
94
99
  required_rubygems_version: !ruby/object:Gem::Requirement
95
100
  requirements:
96
101
  - - ">="
97
102
  - !ruby/object:Gem::Version
98
103
  version: '0'
99
104
  requirements: []
100
- rubyforge_project:
101
- rubygems_version: 2.7.6
102
- signing_key:
105
+ rubygems_version: 3.6.9
103
106
  specification_version: 4
104
107
  summary: Read EXIF from JPEG and TIFF images
105
108
  test_files: []