exifr 1.0.6 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +4 -0
- data/README.rdoc +3 -0
- data/lib/exifr/jpeg.rb +7 -7
- data/lib/exifr/tiff.rb +35 -7
- data/tests/test_helper.rb +8 -2
- data/tests/tiff_test.rb +25 -23
- metadata +5 -5
data/CHANGELOG
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
EXIF Reader 1.1.1
|
2
|
+
* feature; "added gps convenience method to make accessing location data easier (degrees to float conversion)"
|
3
|
+
* bug fix; "[GH#22] Fix warnings about uninitialized @comment"; thanks to Ryan Greenberg"
|
4
|
+
|
1
5
|
EXIF Reader 1.0.6
|
2
6
|
* bug fix: "[GH#20] `readlong': undefined method `unpack' for nil:NilClass (NoMethodError)"
|
3
7
|
|
data/README.rdoc
CHANGED
@@ -17,6 +17,9 @@ EXIF Reader is a module to read metadata from JPEG and TIFF images.
|
|
17
17
|
EXIFR::TIFF.new('DSC_0218.TIF').exposure_time.to_s # => "1/100"
|
18
18
|
EXIFR::TIFF.new('DSC_0218.TIF').f_number.to_f # => 5.0
|
19
19
|
|
20
|
+
EXIFR::JPEG.new('enkhuizen.jpg').gps.latitude # => 52.7197888888889
|
21
|
+
EXIFR::JPEG.new('enkhuizen.jpg').gps.longitude # => 5.28397777777778
|
22
|
+
|
20
23
|
== XMP data access
|
21
24
|
If you need to access XMP data you can use the xmp gem. More info and
|
22
25
|
examples at https://github.com/amberbit/xmp
|
data/lib/exifr/jpeg.rb
CHANGED
@@ -42,7 +42,7 @@ module EXIFR
|
|
42
42
|
|
43
43
|
# Return thumbnail data when available.
|
44
44
|
def thumbnail
|
45
|
-
@exif && @exif.jpeg_thumbnails && @exif.jpeg_thumbnails.first
|
45
|
+
defined?(@exif) && @exif && @exif.jpeg_thumbnails && @exif.jpeg_thumbnails.first
|
46
46
|
end
|
47
47
|
|
48
48
|
# Get a hash presentation of the image.
|
@@ -56,22 +56,22 @@ module EXIFR
|
|
56
56
|
# +method+ does exist for EXIF data +nil+ will be returned.
|
57
57
|
def method_missing(method, *args)
|
58
58
|
super unless args.empty?
|
59
|
-
super unless
|
60
|
-
@exif.send method if @exif
|
59
|
+
super unless methods.include?(method.to_s)
|
60
|
+
@exif.send method if defined?(@exif) && @exif
|
61
61
|
end
|
62
62
|
|
63
63
|
def respond_to?(method) # :nodoc:
|
64
|
-
super ||
|
64
|
+
super || methods.include?(method.to_s)
|
65
65
|
end
|
66
66
|
|
67
67
|
def methods # :nodoc:
|
68
|
-
super + TIFF::TAGS
|
68
|
+
super + TIFF::TAGS << "gps"
|
69
69
|
end
|
70
70
|
|
71
71
|
class << self
|
72
72
|
alias instance_methods_without_jpeg_extras instance_methods
|
73
73
|
def instance_methods(include_super = true) # :nodoc:
|
74
|
-
instance_methods_without_jpeg_extras(include_super) + TIFF::TAGS
|
74
|
+
instance_methods_without_jpeg_extras(include_super) + TIFF::TAGS << "gps"
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
@@ -108,7 +108,7 @@ module EXIFR
|
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
111
|
-
@comment = @comment.first if @comment && @comment.size == 1
|
111
|
+
@comment = @comment.first if defined?(@comment) && @comment && @comment.size == 1
|
112
112
|
|
113
113
|
if app1 = @app1s.find { |d| d[0..5] == "Exif\0\0" }
|
114
114
|
@exif_data = app1[6..-1]
|
data/lib/exifr/tiff.rb
CHANGED
@@ -239,10 +239,12 @@ module EXIFR
|
|
239
239
|
IFD_TAGS = [:image, :exif, :gps] # :nodoc:
|
240
240
|
|
241
241
|
time_proc = proc do |value|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
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
|
245
|
+
else
|
246
|
+
value
|
247
|
+
end
|
246
248
|
end
|
247
249
|
end
|
248
250
|
|
@@ -298,12 +300,27 @@ module EXIFR
|
|
298
300
|
const_set("#{type}Orientation", ORIENTATIONS[index] = Orientation.new(index, type))
|
299
301
|
end
|
300
302
|
|
303
|
+
class Degrees < Array
|
304
|
+
def initialize(arr)
|
305
|
+
raise MalformedTIFF, "expected [degrees, minutes, seconds]" unless arr.length == 3
|
306
|
+
super
|
307
|
+
end
|
308
|
+
|
309
|
+
def to_f
|
310
|
+
reduce { |m,v| m * 60 + v}.to_f / 3600
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
301
314
|
ADAPTERS = Hash.new { proc { |v| v } } # :nodoc:
|
302
315
|
ADAPTERS.merge!({
|
303
316
|
:date_time_original => time_proc,
|
304
317
|
:date_time_digitized => time_proc,
|
305
318
|
:date_time => time_proc,
|
306
|
-
:orientation => proc { |v| ORIENTATIONS[v] }
|
319
|
+
:orientation => proc { |v| v.map{|v| ORIENTATIONS[v]} },
|
320
|
+
:gps_latitude => proc { |v| Degrees.new(v) },
|
321
|
+
:gps_longitude => proc { |v| Degrees.new(v) },
|
322
|
+
:gps_dest_latitude => proc { |v| Degrees.new(v) },
|
323
|
+
:gps_dest_longitude => proc { |v| Degrees.new(v) }
|
307
324
|
})
|
308
325
|
|
309
326
|
# Names for all recognized TIFF fields.
|
@@ -357,7 +374,7 @@ module EXIFR
|
|
357
374
|
|
358
375
|
def respond_to?(method) # :nodoc:
|
359
376
|
super ||
|
360
|
-
(@ifds && @ifds.first && @ifds.first.respond_to?(method)) ||
|
377
|
+
(defined?(@ifds) && @ifds && @ifds.first && @ifds.first.respond_to?(method)) ||
|
361
378
|
TAGS.include?(method.to_s)
|
362
379
|
end
|
363
380
|
|
@@ -381,6 +398,17 @@ module EXIFR
|
|
381
398
|
# Get a hash presentation of the (first) image.
|
382
399
|
def to_hash; @ifds.first.to_hash; end
|
383
400
|
|
401
|
+
GPS = Struct.new(:latitude, :longitude, :altitude, :image_direction)
|
402
|
+
|
403
|
+
# Get GPS location, altitude and image direction return nil when not available.
|
404
|
+
def gps
|
405
|
+
return nil unless gps_latitude && gps_longitude
|
406
|
+
GPS.new(gps_latitude.to_f * (gps_latitude_ref == 'S' ? -1 : 1),
|
407
|
+
gps_longitude.to_f * (gps_longitude_ref == 'W' ? -1 : 1),
|
408
|
+
gps_altitude && (gps_altitude.to_f * (gps_altitude_ref == "\1" ? -1 : 1)),
|
409
|
+
gps_img_direction && gps_img_direction.to_f)
|
410
|
+
end
|
411
|
+
|
384
412
|
def inspect # :nodoc:
|
385
413
|
@ifds.inspect
|
386
414
|
end
|
@@ -449,7 +477,7 @@ module EXIFR
|
|
449
477
|
if IFD_TAGS.include? tag
|
450
478
|
@fields[tag] = IFD.new(@data, field.offset, tag)
|
451
479
|
else
|
452
|
-
value =
|
480
|
+
value = ADAPTERS[tag][field.value]
|
453
481
|
@fields[tag] = value.kind_of?(Array) && value.size == 1 ? value.first : value
|
454
482
|
end
|
455
483
|
end
|
data/tests/test_helper.rb
CHANGED
@@ -29,11 +29,17 @@ def f(fname)
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def assert_literally_equal(expected, actual, *args)
|
32
|
-
assert_equal expected.
|
32
|
+
assert_equal expected.to_s_literally, actual.to_s_literally, *args
|
33
33
|
end
|
34
34
|
|
35
35
|
class Hash
|
36
|
-
def
|
36
|
+
def to_s_literally
|
37
37
|
keys.map{|k| k.to_s}.sort.map{|k| "#{k.inspect} => #{self[k].inspect}" }.join(', ')
|
38
38
|
end
|
39
39
|
end
|
40
|
+
|
41
|
+
class Object
|
42
|
+
def to_s_literally
|
43
|
+
to_s
|
44
|
+
end
|
45
|
+
end
|
data/tests/tiff_test.rb
CHANGED
@@ -30,32 +30,32 @@ class TIFFTest < Test::Unit::TestCase
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def test_multiple_images
|
33
|
-
assert_equal
|
33
|
+
assert_equal(2, @t.size)
|
34
34
|
end
|
35
35
|
|
36
36
|
def test_size
|
37
|
-
assert_equal
|
38
|
-
assert_equal
|
39
|
-
assert_equal
|
40
|
-
assert_equal
|
41
|
-
assert_equal
|
42
|
-
assert_equal
|
43
|
-
assert_equal
|
44
|
-
assert_equal
|
37
|
+
assert_equal(269, @t.image_width)
|
38
|
+
assert_equal(269, @t.image_length)
|
39
|
+
assert_equal(269, @t.width)
|
40
|
+
assert_equal(269, @t.height)
|
41
|
+
assert_equal(120, @t[1].image_width)
|
42
|
+
assert_equal(160, @t[1].image_length)
|
43
|
+
assert_equal(120, @t[1].width)
|
44
|
+
assert_equal(160, @t[1].height)
|
45
45
|
|
46
46
|
@t = TIFF.new(f('plain.tif'))
|
47
|
-
assert_equal
|
48
|
-
assert_equal
|
49
|
-
assert_equal
|
50
|
-
assert_equal
|
47
|
+
assert_equal(23, @t.image_width)
|
48
|
+
assert_equal(24, @t.image_length)
|
49
|
+
assert_equal(23, @t.width)
|
50
|
+
assert_equal(24, @t.height)
|
51
51
|
end
|
52
52
|
|
53
53
|
def test_enumerable
|
54
|
-
assert_equal
|
54
|
+
assert_equal(@t[1], @t.find { |i| i.f_number.nil? })
|
55
55
|
end
|
56
56
|
|
57
57
|
def test_misc_fields
|
58
|
-
assert_equal
|
58
|
+
assert_equal('Canon PowerShot G3', TIFF.new(f('canon-g3.exif')).model)
|
59
59
|
end
|
60
60
|
|
61
61
|
def test_dates
|
@@ -90,12 +90,14 @@ class TIFFTest < Test::Unit::TestCase
|
|
90
90
|
|
91
91
|
def test_gps
|
92
92
|
t = TIFF.new(f('gps.exif'))
|
93
|
-
assert_equal
|
94
|
-
assert_equal
|
95
|
-
assert_equal
|
96
|
-
assert_equal
|
97
|
-
assert_equal
|
98
|
-
assert_equal
|
93
|
+
assert_equal("\2\2\0\0", t.gps_version_id)
|
94
|
+
assert_equal('N', t.gps_latitude_ref)
|
95
|
+
assert_equal('W', t.gps_longitude_ref)
|
96
|
+
assert_equal([5355537.quo(100000), 0.quo(1), 0.quo(1)], t.gps_latitude)
|
97
|
+
assert_equal([678886.quo(100000), 0.quo(1), 0.quo(1)], t.gps_longitude)
|
98
|
+
assert_equal('WGS84', t.gps_map_datum)
|
99
|
+
assert_equal(54, t.gps.latitude.round)
|
100
|
+
assert_equal(-7, t.gps.longitude.round)
|
99
101
|
|
100
102
|
(all_test_exifs - %w(gps user-comment out-of-range negative-exposure-bias-value).map{|v| f("#{v}.exif")}).each do |fname|
|
101
103
|
assert_nil TIFF.new(fname).gps_version_id
|
@@ -170,7 +172,7 @@ class TIFFTest < Test::Unit::TestCase
|
|
170
172
|
end
|
171
173
|
|
172
174
|
def test_user_comment
|
173
|
-
assert_equal
|
175
|
+
assert_equal("Manassas Battlefield", TIFF.new(f('user-comment.exif')).user_comment)
|
174
176
|
end
|
175
177
|
|
176
178
|
def test_handle_out_of_range_offset
|
@@ -180,6 +182,6 @@ class TIFFTest < Test::Unit::TestCase
|
|
180
182
|
end
|
181
183
|
|
182
184
|
def test_negative_exposure_bias_value
|
183
|
-
assert_equal
|
185
|
+
assert_equal(-1.quo(3), TIFF.new(f('negative-exposure-bias-value.exif')).exposure_bias_value)
|
184
186
|
end
|
185
187
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: exifr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 17
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 1.
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 1.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- R.W. van 't Veer
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-09-12 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|