exifr 0.9.6 → 0.10

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.
data/CHANGELOG CHANGED
@@ -1,3 +1,6 @@
1
+ EXIF Reader 0.10
2
+ * TIFF support
3
+
1
4
  EXIF Reader 0.9.6
2
5
  * bug fix; "[#8458] Conversion from string to Time fails", weird dates will now reflect nil
3
6
 
data/README CHANGED
@@ -1,12 +1,24 @@
1
1
  = EXIF Reader
2
- EXIF Reader is a module to read EXIF from JPEG images.
2
+ EXIF Reader is a module to read metadata from JPEG and TIFF images.
3
3
 
4
4
  == Examples
5
- EXIFR::JPEG.new('IMG_3422.JPG').width # -> 2272
6
- EXIFR::JPEG.new('IMG_3422.JPG').exif.model # -> "Canon PowerShot G3"
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
7
19
 
8
20
  == Author
9
21
  R.W. van 't Veer
10
22
 
11
23
  == Copyright
12
- Copyright (c) 2006 R.W. van 't Veer
24
+ Copyright (c) 2006, 2007 - R.W. van 't Veer
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # Copyright (c) 2006, 2007 - R.W. van 't Veer
2
+
1
3
  require 'rake/rdoctask'
2
4
  require 'rake/gempackagetask'
3
5
  require 'rake/testtask'
@@ -6,7 +8,7 @@ task :default => :test
6
8
 
7
9
  spec = Gem::Specification.new do |s|
8
10
  s.name = 'exifr'
9
- s.version = '0.9.6'
11
+ s.version = '0.10'
10
12
  s.author = "R.W. van 't Veer"
11
13
  s.email = 'remco@remvee.net'
12
14
  s.homepage = 'http://exifr.rubyforge.org/'
data/bin/exifr CHANGED
@@ -1,22 +1,36 @@
1
1
  require 'exifr'
2
2
  include EXIFR
3
3
 
4
- def pp(fname)
4
+ def pp_jpeg(fname)
5
5
  jpeg = JPEG.new(fname)
6
6
  ks = %w(width height comment bits)
7
- ks += jpeg.exif.keys.map{|a|a.to_s}.sort{|a,b|a<=>b} if jpeg.exif?
7
+ ks += jpeg.exif.to_hash.keys.map{|a|a.to_s}.sort{|a,b|a<=>b} if jpeg.exif?
8
8
 
9
9
  l = []
10
10
  ks[0..3].each do |k|
11
11
  v = jpeg.send(k)
12
- l << [k, v] if v
12
+ l << [k, v.inspect] if v
13
13
  end
14
14
  ks[4..-1].each do |k|
15
- v = jpeg.exif[k.to_sym]
16
- l << [k, v] if v
15
+ v = jpeg.exif.to_hash[k.to_sym]
16
+ l << [k, v.inspect] if v
17
17
  end
18
- l.delete_if{|k,v|v.nil?}
19
-
18
+ pp(fname, l)
19
+ end
20
+
21
+ def pp_tiff(fname)
22
+ tiff = TIFF.new(fname)
23
+ tiff.each_with_index do |img,index|
24
+ l = []
25
+ l << ['width', img.width] << ['height', img.height]
26
+ img.to_hash.keys.map{|a|a.to_s}.sort{|a,b|a<=>b}.each do |key|
27
+ l << [key, img.to_hash[key.to_sym].inspect]
28
+ end
29
+ pp(tiff.size == 1 ? fname : "#{fname}[#{index}]", l)
30
+ end
31
+ end
32
+
33
+ def pp(fname, l)
20
34
  puts "#{fname}:"
21
35
  f = " %#{l.sort{|a,b|a[0].size <=> b[0].size}.last[0].size}s = %s\n"
22
36
  l.each{|k,v|puts f % [k, [v].flatten.map{|t|t.to_s}.join(', ')]}
@@ -24,11 +38,12 @@ end
24
38
 
25
39
  if ARGV.size == 0
26
40
  STDERR.puts "Usage: #{$0} FILE .."
27
- elsif ARGV.size == 1
28
- pp ARGV[0]
29
41
  else
30
42
  ARGV.each do |fname|
31
- pp fname
43
+ case fname
44
+ when /\.(jpg|jpeg)$/i: pp_jpeg fname
45
+ when /\.(tif|tiff)$/i: pp_tiff fname
46
+ end
32
47
  puts
33
48
  end
34
49
  end
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2006 - R.W. van 't Veer
1
+ # Copyright (c) 2006, 2007 - R.W. van 't Veer
2
2
 
3
3
  require 'jpeg'
4
- require 'exif'
5
- require 'tiff_header'
4
+ require 'tiff'
@@ -1,6 +1,6 @@
1
- # Copyright (c) 2006 - R.W. van 't Veer
1
+ # Copyright (c) 2006, 2007 - R.W. van 't Veer
2
2
 
3
- require 'rational'
3
+ require 'stringio'
4
4
 
5
5
  module EXIFR
6
6
  # = JPEG decoder
@@ -13,15 +13,15 @@ module EXIFR
13
13
  attr_reader :height
14
14
  # image width
15
15
  attr_reader :width
16
- # number of bits per ???
17
- attr_reader :bits
16
+ # number of bits per ??
17
+ attr_reader :bits # :nodoc:
18
18
  # comment; a string if one comment found, an array if more,
19
19
  # otherwise <tt>nil</tt>
20
20
  attr_reader :comment
21
21
  # EXIF data if available
22
22
  attr_reader :exif
23
23
 
24
- # +file+ is a filename or an IO object
24
+ # +file+ is a filename or an IO object.
25
25
  def initialize(file)
26
26
  if file.kind_of? String
27
27
  File.open(file, 'rb') { |io| examine(io) }
@@ -30,18 +30,17 @@ module EXIFR
30
30
  end
31
31
  end
32
32
 
33
- # returns +true+ when EXIF data is available
33
+ # Returns +true+ when EXIF data is available.
34
34
  def exif?
35
35
  !exif.nil?
36
36
  end
37
37
 
38
- # patch through to exif
38
+ # Dispath to EXIF. When no EXIF data is available but the +method+ does exist
39
+ # for EXIF data +nil+ will be returned.
39
40
  def method_missing(method, *args)
40
- if args.empty?
41
- exif[method] if exif?
42
- else
43
- super
44
- end
41
+ super unless args.empty?
42
+ super unless TIFF::TAGS.include?(method)
43
+ @exif.send method if @exif
45
44
  end
46
45
 
47
46
  private
@@ -75,7 +74,7 @@ module EXIFR
75
74
  @comment = @comment.first if @comment && @comment.size == 1
76
75
 
77
76
  if app1 = app1s.find { |d| d[0..5] == "Exif\0\0" }
78
- @exif = EXIF.new(app1[6..-1]) # rescue nil
77
+ @exif = TIFF.new(StringIO.new(app1[6..-1]))
79
78
  end
80
79
  end
81
80
  end
@@ -0,0 +1,440 @@
1
+ # Copyright (c) 2007 - R.W. van 't Veer
2
+
3
+ require 'rational'
4
+
5
+ module EXIFR
6
+ # = TIFF decoder
7
+ #
8
+ # == Date properties
9
+ # The properties <tt>:date_time</tt>, <tt>:date_time_original</tt>,
10
+ # <tt>:date_time_digitized</tt> coerced into Time objects.
11
+ #
12
+ # == Orientation
13
+ # The property <tt>:orientation</tt> describes the subject rotated and/or
14
+ # mirrored in relation to the camera. It is translated to one of the following
15
+ # modules:
16
+ # * TopLeftOrientation
17
+ # * TopRightOrientation
18
+ # * BottomRightOrientation
19
+ # * BottomLeftOrientation
20
+ # * LeftTopOrientation
21
+ # * RightTopOrientation
22
+ # * RightBottomOrientation
23
+ # * LeftBottomOrientation
24
+ #
25
+ # These modules have two methods:
26
+ # * <tt>to_i</tt>; return the original integer
27
+ # * <tt>transform_rmagick(image)</tt>; transforms the given RMagick::Image
28
+ # to a viewable version
29
+ #
30
+ # == Examples
31
+ # EXIFR::TIFF.new('DSC_0218.TIF').width # => 3008
32
+ # EXIFR::TIFF.new('DSC_0218.TIF')[1].width # => 160
33
+ # EXIFR::TIFF.new('DSC_0218.TIF').model # => "NIKON D1X"
34
+ # EXIFR::TIFF.new('DSC_0218.TIF').date_time # => Tue May 23 19:15:32 +0200 2006
35
+ # EXIFR::TIFF.new('DSC_0218.TIF').exposure_time # => Rational(1, 100)
36
+ # EXIFR::TIFF.new('DSC_0218.TIF').orientation # => EXIFR::TIFF::TopLeftOrientation
37
+ class TIFF
38
+ include Enumerable
39
+
40
+ TAG_MAPPING = {} # :nodoc:
41
+ TAG_MAPPING.merge!({
42
+ :image => {
43
+ 0x00FE => :new_subfile_type,
44
+ 0x00FF => :subfile_type,
45
+ 0x0100 => :image_width,
46
+ 0x0101 => :image_length,
47
+ 0x0102 => :bits_per_sample,
48
+ 0x0103 => :compression,
49
+ 0x0106 => :photometric_interpretation,
50
+ 0x0107 => :threshholding,
51
+ 0x0108 => :cell_width,
52
+ 0x0109 => :cell_length,
53
+ 0x010a => :fill_order,
54
+ 0x010d => :document_name,
55
+ 0x010e => :image_description,
56
+ 0x010f => :make,
57
+ 0x0110 => :model,
58
+ 0x0111 => :strip_offsets,
59
+ 0x0112 => :orientation,
60
+ 0x0115 => :samples_per_pixel,
61
+ 0x0116 => :rows_per_strip,
62
+ 0x0117 => :strip_byte_counts,
63
+ 0x0118 => :min_sample_value,
64
+ 0x0119 => :max_sample_value,
65
+ 0x011a => :x_resolution,
66
+ 0x011b => :y_resolution,
67
+ 0x011c => :planar_configuration,
68
+ 0x011d => :page_name,
69
+ 0x011e => :x_position,
70
+ 0x011f => :y_position,
71
+ 0x0120 => :free_offsets,
72
+ 0x0121 => :free_byte_counts,
73
+ 0x0122 => :gray_response_unit,
74
+ 0x0123 => :gray_response_curve,
75
+ 0x0124 => :t4_options,
76
+ 0x0125 => :t6_options,
77
+ 0x0128 => :resolution_unit,
78
+ 0x012d => :transfer_function,
79
+ 0x0131 => :software,
80
+ 0x0132 => :date_time,
81
+ 0x013b => :artist,
82
+ 0x013c => :host_computer,
83
+ 0x013a => :predictor,
84
+ 0x013e => :white_point,
85
+ 0x013f => :primary_chromaticities,
86
+ 0x0140 => :color_map,
87
+ 0x0141 => :halftone_hints,
88
+ 0x0142 => :tile_width,
89
+ 0x0143 => :tile_length,
90
+ 0x0144 => :tile_offsets,
91
+ 0x0145 => :tile_byte_counts,
92
+ 0x0146 => :bad_fax_lines,
93
+ 0x0147 => :clean_fax_data,
94
+ 0x0148 => :consecutive_bad_fax_lines,
95
+ 0x014a => :sub_ifds,
96
+ 0x014c => :ink_set,
97
+ 0x014d => :ink_names,
98
+ 0x014e => :number_of_inks,
99
+ 0x0150 => :dot_range,
100
+ 0x0151 => :target_printer,
101
+ 0x0152 => :extra_samples,
102
+ 0x0156 => :transfer_range,
103
+ 0x0157 => :clip_path,
104
+ 0x0158 => :x_clip_path_units,
105
+ 0x0159 => :y_clip_path_units,
106
+ 0x015a => :indexed,
107
+ 0x015b => :jpeg_tables,
108
+ 0x015f => :opi_proxy,
109
+ 0x0190 => :global_parameters_ifd,
110
+ 0x0191 => :profile_type,
111
+ 0x0192 => :fax_profile,
112
+ 0x0193 => :coding_methods,
113
+ 0x0194 => :version_year,
114
+ 0x0195 => :mode_number,
115
+ 0x01B1 => :decode,
116
+ 0x01B2 => :default_image_color,
117
+ 0x0200 => :jpegproc,
118
+ 0x0201 => :jpeg_interchange_format,
119
+ 0x0202 => :jpeg_interchange_format_length,
120
+ 0x0203 => :jpeg_restart_interval,
121
+ 0x0205 => :jpeg_lossless_predictors,
122
+ 0x0206 => :jpeg_point_transforms,
123
+ 0x0207 => :jpeg_q_tables,
124
+ 0x0208 => :jpeg_dc_tables,
125
+ 0x0209 => :jpeg_ac_tables,
126
+ 0x0211 => :ycb_cr_coefficients,
127
+ 0x0212 => :ycb_cr_sub_sampling,
128
+ 0x0213 => :ycb_cr_positioning,
129
+ 0x0214 => :reference_black_white,
130
+ 0x022F => :strip_row_counts,
131
+ 0x02BC => :xmp,
132
+ 0x800D => :image_id,
133
+ 0x87AC => :image_layer,
134
+ 0x8298 => :copyright,
135
+ 0x83bb => :iptc,
136
+
137
+ 0x8769 => :exif,
138
+ 0x8825 => :gps,
139
+ },
140
+
141
+ :exif => {
142
+ 0x829a => :exposure_time,
143
+ 0x829d => :f_number,
144
+ 0x8822 => :exposure_program,
145
+ 0x8824 => :spectral_sensitivity,
146
+ 0x8827 => :iso_speed_ratings,
147
+ 0x8828 => :oecf,
148
+ 0x9000 => :exif_version,
149
+ 0x9003 => :date_time_original,
150
+ 0x9004 => :date_time_digitized,
151
+ 0x9101 => :components_configuration,
152
+ 0x9102 => :compressed_bits_per_pixel,
153
+ 0x9201 => :shutter_speed_value,
154
+ 0x9202 => :aperture_value,
155
+ 0x9203 => :brightness_value,
156
+ 0x9204 => :exposure_bias_value,
157
+ 0x9205 => :max_aperture_value,
158
+ 0x9206 => :subject_distance,
159
+ 0x9207 => :metering_mode,
160
+ 0x9208 => :light_source,
161
+ 0x9209 => :flash,
162
+ 0x920a => :focal_length,
163
+ 0x9214 => :subject_area,
164
+ 0x927c => :maker_note,
165
+ 0x9286 => :user_comment,
166
+ 0x9290 => :subsec_time,
167
+ 0x9291 => :subsec_time_orginal,
168
+ 0x9292 => :subsec_time_digitized,
169
+ 0xa000 => :flashpix_version,
170
+ 0xa001 => :color_space,
171
+ 0xa002 => :pixel_x_dimension,
172
+ 0xa003 => :pixel_y_dimension,
173
+ 0xa004 => :related_sound_file,
174
+ 0xa20b => :flash_energy,
175
+ 0xa20c => :spatial_frequency_response,
176
+ 0xa20e => :focal_plane_x_resolution,
177
+ 0xa20f => :focal_plane_y_resolution,
178
+ 0xa210 => :focal_plane_resolution_unit,
179
+ 0xa214 => :subject_location,
180
+ 0xa215 => :exposure_index,
181
+ 0xa217 => :sensing_method,
182
+ 0xa300 => :file_source,
183
+ 0xa301 => :scene_type,
184
+ 0xa302 => :cfa_pattern,
185
+ 0xa401 => :custom_rendered,
186
+ 0xa402 => :exposure_mode,
187
+ 0xa403 => :white_balance,
188
+ 0xa404 => :digital_zoom_ratio,
189
+ 0xa405 => :focal_length_in_35mm_film,
190
+ 0xa406 => :scene_capture_type,
191
+ 0xa407 => :gain_control,
192
+ 0xa408 => :contrast,
193
+ 0xa409 => :saturation,
194
+ 0xa40a => :sharpness,
195
+ 0xa40b => :device_setting_description,
196
+ 0xa40c => :subject_distance_range,
197
+ 0xa420 => :image_unique_id
198
+ },
199
+
200
+ :gps => {
201
+ 0x0000 => :gps_version_id,
202
+ 0x0001 => :gps_latitude_ref,
203
+ 0x0002 => :gps_latitude,
204
+ 0x0003 => :gps_longitude_ref,
205
+ 0x0004 => :gps_longitude,
206
+ 0x0005 => :gps_altitude_ref,
207
+ 0x0006 => :gps_altitude ,
208
+ 0x0007 => :gps_time_stamp,
209
+ 0x0008 => :gps_satellites,
210
+ 0x0009 => :gps_status,
211
+ 0x000a => :gps_measure_mode,
212
+ 0x000b => :gps_dop,
213
+ 0x000c => :gps_speed_ref,
214
+ 0x000d => :gps_speed,
215
+ 0x000e => :gps_track_ref,
216
+ 0x000f => :gps_track,
217
+ 0x0010 => :gps_img_direction_ref,
218
+ 0x0011 => :gps_img_direction,
219
+ 0x0012 => :gps_map_datum,
220
+ 0x0013 => :gps_dest_latitude_ref,
221
+ 0x0014 => :gps_dest_latitude,
222
+ 0x0015 => :gps_dest_longitude_ref,
223
+ 0x0016 => :gps_dest_longitude,
224
+ 0x0017 => :gps_dest_bearing_ref,
225
+ 0x0018 => :gps_dest_bearing,
226
+ 0x0019 => :gps_dest_distance_ref,
227
+ 0x001a => :gps_dest_distance,
228
+ 0x001b => :gps_processing_method,
229
+ 0x001c => :gps_area_information,
230
+ 0x001d => :gps_date_stamp,
231
+ 0x001e => :gps_differential,
232
+ },
233
+ })
234
+ IFD_TAGS = [:image, :exif, :gps] # :nodoc:
235
+
236
+ time_proc = proc do |value|
237
+ if value =~ /^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/
238
+ Time.mktime($1, $2, $3, $4, $5, $6) rescue nil
239
+ else
240
+ value
241
+ end
242
+ end
243
+
244
+ ORIENTATIONS = [] # :nodoc:
245
+ [
246
+ nil,
247
+ [:TopLeft, 'img'],
248
+ [:TopRight, 'img.flop'],
249
+ [:BottomRight, 'img.rotate(180)'],
250
+ [:BottomLeft, 'img.flip'],
251
+ [:LeftTop, 'img.rotate(90).flop'],
252
+ [:RightTop, 'img.rotate(90)'],
253
+ [:RightBottom, 'img.rotate(270).flop'],
254
+ [:LeftBottom, 'img.rotate(270)'],
255
+ ].each_with_index do |tuple,index|
256
+ next unless tuple
257
+ name, rmagic_code = *tuple
258
+
259
+ eval <<-EOS
260
+ module #{name}Orientation
261
+ def self.to_i; #{index}; end
262
+ def self.transform_rmagick(img); #{rmagic_code}; end
263
+ end
264
+ ORIENTATIONS[#{index}] = #{name}Orientation
265
+ EOS
266
+ end
267
+
268
+ ADAPTERS = Hash.new { proc { |v| v } } # :nodoc:
269
+ ADAPTERS.merge!({
270
+ :date_time_original => time_proc,
271
+ :date_time_digitized => time_proc,
272
+ :date_time => time_proc,
273
+ :orientation => proc { |v| ORIENTATIONS[v] }
274
+ })
275
+
276
+ # Names for all recognized TIFF fields.
277
+ TAGS = [TAG_MAPPING.keys, TAG_MAPPING.values.map{|a|a.values}].flatten.uniq - IFD_TAGS
278
+
279
+ # +file+ is a filename or an IO object.
280
+ def initialize(file)
281
+ @data = file.respond_to?(:read) ? file.read : File.open(file, 'rb') { |io| io.read }
282
+
283
+ class << @data
284
+ attr_accessor :short, :long
285
+ def readshort(pos); self[pos..(pos + 1)].unpack(@short)[0]; end
286
+ def readlong(pos); self[pos..(pos + 3)].unpack(@long)[0]; end
287
+ end
288
+
289
+ case @data[0..1]
290
+ when 'II'; @data.short, @data.long = 'v', 'V'
291
+ when 'MM'; @data.short, @data.long = 'n', 'N'
292
+ else; raise 'no II or MM marker found'
293
+ end
294
+
295
+ @ifds = [IFD.new(@data)]
296
+ while ifd = @ifds.last.next; @ifds << ifd; end
297
+ end
298
+
299
+ # Number of images.
300
+ def size
301
+ @ifds.size
302
+ end
303
+
304
+ # Yield for each image.
305
+ def each
306
+ @ifds.each { |ifd| yield ifd }
307
+ end
308
+
309
+ # Get +index+ image.
310
+ def [](index)
311
+ @ifds[index]
312
+ end
313
+
314
+ # Dispatch to first image.
315
+ def method_missing(method, *args)
316
+ super unless args.empty?
317
+
318
+ if @ifds.first.respond_to?(method)
319
+ @ifds.first.send(method)
320
+ elsif TAGS.include?(method)
321
+ @ifds.first.to_hash[method]
322
+ else
323
+ super
324
+ end
325
+ end
326
+
327
+ # Convenience method to access image width.
328
+ def width; @ifds.first.width; end
329
+
330
+ # Convenience method to access image height.
331
+ def height; @ifds.first.height; end
332
+
333
+ # Get a hash presentation of the (first) image.
334
+ def to_hash; @ifds.first.to_hash; end
335
+
336
+ def inspect # :nodoc:
337
+ @ifds.inspect
338
+ end
339
+
340
+ class IFD # :nodoc:
341
+ attr_reader :type, :fields
342
+
343
+ def initialize(data, offset = nil, type = :image)
344
+ @data, @type, @fields = data, type, {}
345
+
346
+ pos = offset || @data.readlong(4)
347
+ num = @data.readshort(pos)
348
+ pos += 2
349
+
350
+ num.times do
351
+ add_field(Field.new(@data, pos))
352
+ pos += 12
353
+ end
354
+
355
+ @offset_next = @data.readlong(pos)
356
+ end
357
+
358
+ def method_missing(method, *args)
359
+ super unless args.empty? && TAGS.include?(method)
360
+ to_hash[method]
361
+ end
362
+
363
+ def width; image_width; end
364
+ def height; image_length; end
365
+
366
+ def to_hash
367
+ @hash ||= begin
368
+ result = @fields.dup
369
+ result.delete_if { |key,value| value.nil? }
370
+ result.each do |key,value|
371
+ if IFD_TAGS.include? key
372
+ result.merge!(value.to_hash)
373
+ result.delete key
374
+ end
375
+ end
376
+ end
377
+ end
378
+
379
+ def inspect
380
+ to_hash.inspect
381
+ end
382
+
383
+ def next
384
+ IFD.new(@data, @offset_next) unless @offset_next == 0
385
+ end
386
+
387
+ private
388
+ def add_field(field)
389
+ return unless tag = TAG_MAPPING[@type][field.tag]
390
+ if IFD_TAGS.include? tag
391
+ @fields[tag] = IFD.new(@data, field.offset, tag)
392
+ else
393
+ value = field.value.map { |v| ADAPTERS[tag][v] } if field.value
394
+ @fields[tag] = value.kind_of?(Array) && value.size == 1 ? value.first : value
395
+ end
396
+ end
397
+ end
398
+
399
+ class Field # :nodoc:
400
+ attr_reader :tag, :offset, :value
401
+
402
+ def initialize(data, pos)
403
+ @tag, count, @offset = data.readshort(pos), data.readlong(pos + 4), data.readlong(pos + 8)
404
+
405
+ case data.readshort(pos + 2)
406
+ when 1, 6 # byte, signed byte
407
+ # TODO handle signed bytes
408
+ len, pack = count, proc { |d| d }
409
+ when 2 # ascii
410
+ len, pack = count, proc { |d| d.strip }
411
+ when 3, 8 # short, signed short
412
+ # TODO handle signed
413
+ len, pack = count * 2, proc { |d| d.unpack(data.short + '*') }
414
+ when 4, 9 # long, signed long
415
+ # TODO handle signed
416
+ len, pack = count * 4, proc { |d| d.unpack(data.long + '*') }
417
+ when 5, 10
418
+ len, pack = count * 8, proc do |d|
419
+ r = []
420
+ d.unpack(data.long + '*').each_with_index do |v,i|
421
+ i % 2 == 0 ? r << [v] : r.last << v
422
+ end
423
+ r.map do |f|
424
+ if f[1] == 0 # allow NaN and Infinity
425
+ f[0].to_f.quo(f[1])
426
+ else
427
+ Rational.reduce(*f)
428
+ end
429
+ end
430
+ end
431
+ end
432
+
433
+ if len && pack
434
+ start = len > 4 ? @offset : (pos + 8)
435
+ @value = pack[data[start..(start + len - 1)]]
436
+ end
437
+ end
438
+ end
439
+ end
440
+ end
Binary file
Binary file
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2006, 2007 - R.W. van 't Veer
2
4
 
3
5
  require 'test/unit'
4
6
  require 'stringio'
@@ -9,14 +11,19 @@ require 'exifr'
9
11
  include EXIFR
10
12
 
11
13
 
12
- def all_test_images
14
+ def all_test_jpegs
13
15
  Dir[f('*.jpg')]
14
16
  end
15
17
 
18
+
16
19
  def all_test_exifs
17
20
  Dir[f('*.exif')]
18
21
  end
19
22
 
23
+ def all_test_tiffs
24
+ Dir[f('*.tif')]
25
+ end
26
+
20
27
  def f(fname)
21
28
  "#{File.dirname(__FILE__)}/data/#{fname}"
22
29
  end
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2006, 2007 - R.W. van 't Veer
2
4
 
3
5
  require File.join(File.dirname(__FILE__), 'test_helper')
4
6
 
5
7
  class TestJPEG < Test::Unit::TestCase
6
8
  def test_initialize
7
- all_test_images.each do |fname|
9
+ all_test_jpegs.each do |fname|
8
10
  assert_nothing_raised do
9
11
  JPEG.new(fname)
10
12
  end
@@ -38,16 +40,24 @@ class TestJPEG < Test::Unit::TestCase
38
40
  def test_exif
39
41
  assert ! JPEG.new(f('image.jpg')).exif?
40
42
  assert JPEG.new(f('exif.jpg')).exif?
43
+ assert_not_nil JPEG.new(f('exif.jpg')).exif.date_time
44
+ assert_not_nil JPEG.new(f('exif.jpg')).exif.f_number
41
45
  end
42
46
 
43
- def test_multiple_app1
44
- assert JPEG.new(f('multiple-app1.jpg')).exif?
47
+ def test_exif_dispatch
48
+ j = JPEG.new(f('exif.jpg'))
49
+ assert_not_nil j.date_time
50
+ assert_kind_of Time, j.date_time
51
+ assert_not_nil j.f_number
52
+ assert_kind_of Rational, j.f_number
45
53
  end
46
54
 
47
- def test_patch_through
48
- jpeg = JPEG.new(f('exif.jpg'))
49
- jpeg.exif.each do |k,v|
50
- assert_equal v, jpeg.send(k)
51
- end
55
+ def test_no_method_error
56
+ assert_nothing_raised { JPEG.new(f('image.jpg')).f_number }
57
+ assert_raise(NoMethodError) { JPEG.new(f('image.jpg')).foo }
58
+ end
59
+
60
+ def test_multiple_app1
61
+ assert JPEG.new(f('multiple-app1.jpg')).exif?
52
62
  end
53
63
  end
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2006, 2007 - R.W. van 't Veer
4
+
5
+ require File.join(File.dirname(__FILE__), 'test_helper')
6
+
7
+ class TestTIFF < Test::Unit::TestCase
8
+ def setup
9
+ @t = TIFF.new(f('nikon_d1x.tif'))
10
+ end
11
+
12
+ def test_initialize
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
23
+ end
24
+ end
25
+
26
+ def test_multiple_images
27
+ assert_equal 2, @t.size
28
+ end
29
+
30
+ def test_size
31
+ assert_equal 269, @t.image_width
32
+ assert_equal 269, @t.image_length
33
+ assert_equal 269, @t.width
34
+ assert_equal 269, @t.height
35
+ assert_equal 120, @t[1].image_width
36
+ assert_equal 160, @t[1].image_length
37
+ assert_equal 120, @t[1].width
38
+ assert_equal 160, @t[1].height
39
+
40
+ @t = TIFF.new(f('plain.tif'))
41
+ assert_equal 23, @t.image_width
42
+ assert_equal 24, @t.image_length
43
+ assert_equal 23, @t.width
44
+ assert_equal 24, @t.height
45
+ end
46
+
47
+ def test_enumerable
48
+ assert_equal @t[1], @t.find { |i| i.f_number.nil? }
49
+ end
50
+
51
+ def test_misc_fields
52
+ assert_equal 'Canon PowerShot G3', TIFF.new(f('canon-g3.exif')).model
53
+ end
54
+
55
+ def test_dates
56
+ (all_test_tiffs - [f('weird_date.exif'), f('plain.tif')]).each do |fname|
57
+ assert_kind_of Time, TIFF.new(fname).date_time
58
+ end
59
+ assert_nil TIFF.new(f('weird_date.exif')).date_time
60
+ end
61
+
62
+ def test_orientation
63
+ all_test_exifs.each do |fname|
64
+ orientation = TIFF.new(fname).orientation
65
+ assert_kind_of Module, orientation
66
+ assert orientation.respond_to?(:to_i)
67
+ assert orientation.respond_to?(:transform_rmagick)
68
+ end
69
+ end
70
+
71
+ def test_gps
72
+ t = TIFF.new(f('gps.exif'))
73
+ assert_equal "\2\2\0\0", t.gps_version_id
74
+ assert_equal 'N', t.gps_latitude_ref
75
+ assert_equal 'W', t.gps_longitude_ref
76
+ assert_equal [5355537.quo(100000), 0.quo(1), 0.quo(1)], t.gps_latitude
77
+ assert_equal [678886.quo(100000), 0.quo(1), 0.quo(1)], t.gps_longitude
78
+ assert_equal 'WGS84', t.gps_map_datum
79
+
80
+ (all_test_exifs - [f('gps.exif')]).each do |fname|
81
+ assert_nil TIFF.new(fname).gps_version_id
82
+ end
83
+ end
84
+
85
+ def test_ifd_dispatch
86
+ assert_not_nil @t.f_number
87
+ assert_kind_of Rational, @t.f_number
88
+ assert_not_nil @t[0].f_number
89
+ assert_kind_of Rational, @t[0].f_number
90
+ end
91
+
92
+ def test_avoid_dispatch_to_nonexistent_ifds
93
+ assert_nothing_raised do
94
+ all_test_tiffs.each do |fname|
95
+ t = TIFF.new(fname)
96
+ TIFF::TAGS.each { |tag| t.send(tag) }
97
+ end
98
+ end
99
+ end
100
+
101
+ def test_to_hash
102
+ all_test_tiffs.each do |fname|
103
+ t = TIFF.new(fname)
104
+ TIFF::TAGS.each do |key|
105
+ assert_equal t.send(key), t.to_hash[key]
106
+ end
107
+ end
108
+ end
109
+ end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: exifr
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.9.6
7
- date: 2007-02-07 00:00:00 +01:00
6
+ version: "0.10"
7
+ date: 2007-02-27 00:00:00 +01:00
8
8
  summary: EXIF Reader is a module to read EXIF from JPEG images.
9
9
  require_paths:
10
10
  - lib
@@ -31,14 +31,13 @@ authors:
31
31
  files:
32
32
  - Rakefile
33
33
  - bin/exifr
34
- - lib/exif.rb
35
34
  - lib/exifr.rb
36
35
  - lib/jpeg.rb
37
- - lib/tiff_header.rb
36
+ - lib/tiff.rb
38
37
  - tests/data
39
- - tests/test_exif.rb
40
38
  - tests/test_helper.rb
41
39
  - tests/test_jpeg.rb
40
+ - tests/test_tiff.rb
42
41
  - tests/data/1x1.jpg
43
42
  - tests/data/canon-g3.exif
44
43
  - tests/data/Canon_PowerShot_A85.exif
@@ -48,7 +47,9 @@ files:
48
47
  - tests/data/gps.exif
49
48
  - tests/data/image.jpg
50
49
  - tests/data/multiple-app1.jpg
50
+ - tests/data/nikon_d1x.tif
51
51
  - tests/data/Panasonic-DMC-LC33.exif
52
+ - tests/data/plain.tif
52
53
  - tests/data/Trust-DC3500_MINI.exif
53
54
  - tests/data/weird_date.exif
54
55
  - README
@@ -1,255 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # Copyright (c) 2006 - R.W. van 't Veer
3
-
4
- module EXIFR
5
- # = EXIF decoder
6
- #
7
- # The EXIF class contains the EXIF properties.
8
- #
9
- #
10
- # == Date properties
11
- #
12
- # The properties <tt>:date_time</tt>, <tt>:date_time_original</tt>,
13
- # <tt>:date_time_digitized</tt> are stored in a EXIF tags as an ASCII
14
- # string. This class stores them as Time objects.
15
- #
16
- #
17
- # == Orientation
18
- #
19
- # The property <tt>:orientation</tt> describes the subject rotated and/or
20
- # mirrored in relation to the camera. The value is stored in an EXIF tags
21
- # as an integer. This class stores this value as a module;
22
- #
23
- # * TopLeftOrientation
24
- # * TopRightOrientation
25
- # * BottomRightOrientation
26
- # * BottomLeftOrientation
27
- # * LeftTopOrientation
28
- # * RightTopOrientation
29
- # * RightBottomOrientation
30
- # * LeftBottomOrientation
31
- #
32
- # These modules have two methods:
33
- # * <tt>to_i</tt>; return the original EXIF tag integer
34
- # * <tt>transform_rmagick(image)</tt>; transforms the given RMagick::Image
35
- # to a viewable version
36
- #
37
- class EXIF < Hash
38
- TAGS = {} # :nodoc:
39
- TAGS.merge!({
40
- :exif => {
41
- 0x0100 => :image_width,
42
- 0x0101 => :image_length,
43
- 0x0102 => :bits_per_sample,
44
- 0x0103 => :compression,
45
- 0x0106 => :photometric_interpretation,
46
- 0x010a => :fill_order,
47
- 0x010d => :document_name,
48
- 0x010e => :image_description,
49
- 0x010f => :make,
50
- 0x0110 => :model,
51
- 0x0111 => :strip_offsets,
52
- 0x0112 => :orientation,
53
- 0x0115 => :samples_per_pixel,
54
- 0x0116 => :rows_per_strip,
55
- 0x0117 => :strip_byte_counts,
56
- 0x011a => :xresolution,
57
- 0x011b => :yresolution,
58
- 0x011c => :planar_configuration,
59
- 0x0128 => :resolution_unit,
60
- 0x012d => :transfer_function,
61
- 0x0131 => :software,
62
- 0x0132 => :date_time,
63
- 0x013b => :artist,
64
- 0x013e => :white_point,
65
- 0x013f => :primary_chromaticities,
66
- 0x0156 => :transfer_range,
67
- 0x0200 => :jpegproc,
68
- 0x0201 => :jpeg_interchange_format,
69
- 0x0202 => :jpeg_interchange_format_length,
70
- 0x0211 => :ycb_cr_coefficients,
71
- 0x0212 => :ycb_cr_sub_sampling,
72
- 0x0213 => :ycb_cr_positioning,
73
- 0x0214 => :reference_black_white,
74
- 0x828d => :cfarepeat_pattern_dim,
75
- 0x828e => :cfapattern,
76
- 0x828f => :battery_level,
77
- 0x8298 => :copyright,
78
- 0x829a => :exposure_time,
79
- 0x829d => :fnumber,
80
- 0x83bb => :iptc_naa,
81
- 0x8769 => :exif,
82
- 0x8773 => :inter_color_profile,
83
- 0x8822 => :exposure_program,
84
- 0x8824 => :spectral_sensitivity,
85
- 0x8825 => :gps,
86
- 0x8827 => :isospeed_ratings,
87
- 0x8828 => :oecf,
88
- 0x9000 => :exif_version,
89
- 0x9003 => :date_time_original,
90
- 0x9004 => :date_time_digitized,
91
- 0x9101 => :components_configuration,
92
- 0x9102 => :compressed_bits_per_pixel,
93
- 0x9201 => :shutter_speed_value,
94
- 0x9202 => :aperture_value,
95
- 0x9203 => :brightness_value,
96
- 0x9204 => :exposure_bias_value,
97
- 0x9205 => :max_aperture_value,
98
- 0x9206 => :subject_distance,
99
- 0x9207 => :metering_mode,
100
- 0x9208 => :light_source,
101
- 0x9209 => :flash,
102
- 0x920a => :focal_length,
103
- 0x9214 => :subject_area,
104
- 0x927c => :maker_note,
105
- 0x9286 => :user_comment,
106
- 0x9290 => :subsec_time,
107
- 0x9291 => :subsec_time_orginal,
108
- 0x9292 => :subsec_time_digitized,
109
- 0xa000 => :flash_pix_version,
110
- 0xa001 => :color_space,
111
- 0xa002 => :pixel_xdimension,
112
- 0xa003 => :pixel_ydimension,
113
- 0xa004 => :related_sound_file,
114
- 0xa005 => :interoperability,
115
- 0xa20b => :flash_energy,
116
- 0xa20c => :spatial_frequency_response,
117
- 0xa20e => :focal_plane_xresolution,
118
- 0xa20f => :focal_plane_yresolution,
119
- 0xa210 => :focal_plane_resolution_unit,
120
- 0xa214 => :subject_location,
121
- 0xa215 => :exposure_index,
122
- 0xa217 => :sensing_method,
123
- 0xa300 => :file_source,
124
- 0xa301 => :scene_type,
125
- 0xa302 => :cfapattern,
126
- 0xa401 => :custom_rendered,
127
- 0xa402 => :exposure_mode,
128
- 0xa403 => :white_balance,
129
- 0xa404 => :digital_zoom_ratio,
130
- 0xa405 => :focal_len_in_35mm_film,
131
- 0xa406 => :scene_capture_type,
132
- 0xa407 => :gain_control,
133
- 0xa408 => :contrast,
134
- 0xa409 => :saturation,
135
- 0xa40a => :sharpness,
136
- 0xa40b => :device_setting_descr,
137
- 0xa40c => :subject_dist_range,
138
- 0xa420 => :image_unique_id
139
- },
140
-
141
- :gps => {
142
- 0x0000 => :gps_version_id,
143
- 0x0001 => :gps_latitude_ref,
144
- 0x0002 => :gps_latitude,
145
- 0x0003 => :gps_longitude_ref,
146
- 0x0004 => :gps_longitude,
147
- 0x0005 => :gps_altitude_ref,
148
- 0x0006 => :gps_altitude ,
149
- 0x0007 => :gps_time_stamp,
150
- 0x0008 => :gps_satellites,
151
- 0x0009 => :gps_status,
152
- 0x000a => :gps_measure_mode,
153
- 0x000b => :gpsdop,
154
- 0x000c => :gps_speed_ref,
155
- 0x000d => :gps_speed ,
156
- 0x000e => :gps_track_ref,
157
- 0x000f => :gps_track,
158
- 0x0010 => :gps_img_direction_ref,
159
- 0x0011 => :gps_img_direction,
160
- 0x0012 => :gps_map_datum,
161
- 0x0013 => :gps_dest_latitude_ref,
162
- 0x0014 => :gps_dest_latitude,
163
- 0x0015 => :gps_dest_longitude_ref,
164
- 0x0016 => :gps_dest_longitude,
165
- 0x0017 => :gps_dest_bearing_ref,
166
- 0x0018 => :gps_dest_bearing,
167
- 0x0019 => :gps_dest_distance_ref,
168
- 0x001a => :gps_dest_distance,
169
- 0x001b => :gps_processing_method,
170
- 0x001c => :gps_area_information,
171
- 0x001d => :gps_date_stamp,
172
- 0x001e => :gps_differential,
173
- },
174
-
175
- :interoperability => {
176
- 0x0001 => :interoperability_index
177
- }
178
- })
179
- EXIF_HEADERS = [:exif, :gps, :interoperability] # :nodoc:
180
-
181
- time_proc = proc do |value|
182
- if value =~ /^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/
183
- Time.mktime($1, $2, $3, $4, $5, $6) rescue nil
184
- else
185
- value
186
- end
187
- end
188
-
189
- ORIENTATIONS = [] # :nodoc:
190
- [
191
- nil,
192
- [:TopLeft, 'img'],
193
- [:TopRight, 'img.flop'],
194
- [:BottomRight, 'img.rotate(180)'],
195
- [:BottomLeft, 'img.flip'],
196
- [:LeftTop, 'img.rotate(90).flop'],
197
- [:RightTop, 'img.rotate(90)'],
198
- [:RightBottom, 'img.rotate(270).flop'],
199
- [:LeftBottom, 'img.rotate(270)'],
200
- ].each_with_index do |tuple,index|
201
- next unless tuple
202
- name, rmagic_code = *tuple
203
-
204
- eval <<-EOS
205
- module #{name}Orientation
206
- def self.to_i; #{index}; end
207
- def self.transform_rmagick(img); #{rmagic_code}; end
208
- end
209
- ORIENTATIONS[#{index}] = #{name}Orientation
210
- EOS
211
- end
212
-
213
- ADAPTERS = Hash.new { proc { |v| v } } # :nodoc:
214
- ADAPTERS.merge!({
215
- :date_time_original => time_proc,
216
- :date_time_digitized => time_proc,
217
- :date_time => time_proc,
218
- :orientation => proc { |v| ORIENTATIONS[v] }
219
- })
220
-
221
- # +data+ the content of the JPEG APP1 frame without the EXIF marker
222
- def initialize(data)
223
- traverse(data)
224
- pull_thumbnail(data)
225
- freeze
226
- end
227
-
228
- # thumbnail, if included
229
- attr_reader :thumbnail
230
-
231
- # convience; <tt>self[method]</tt>
232
- def method_missing(method, *args)
233
- self[method]
234
- end
235
-
236
- private
237
- def traverse(data, offset = nil, ifd = :exif)
238
- TiffHeader.new(data, offset).fields.each do |f|
239
- tag = TAGS[ifd][f.tag]
240
- value = f.value.map { |v| ADAPTERS[tag][v] } if f.value
241
- value = (value.kind_of?(Array) && value.size == 1) ? value.first : value
242
- if EXIF_HEADERS.include?(tag)
243
- traverse(data, f.offset, tag)
244
- elsif tag
245
- self[tag] = value
246
- end
247
- end
248
- end
249
-
250
- def pull_thumbnail(data)
251
- start, length = self[:jpeg_interchange_format], self[:jpeg_interchange_format_length]
252
- @thumbnail = data[start..(start + length)] if start && length
253
- end
254
- end
255
- end
@@ -1,83 +0,0 @@
1
- # Copyright (c) 2006 - R.W. van 't Veer
2
-
3
- module EXIFR
4
- class TiffHeader # :nodoc:
5
- attr_reader :data, :fields
6
-
7
- def initialize(data, offset = nil)
8
- @data = data
9
- @fields = []
10
-
11
- unless @data.respond_to? :readshort
12
- class << @data
13
- attr_accessor :short, :long
14
- def readshort(pos); self[pos..(pos + 1)].unpack(@short)[0]; end
15
- def readlong(pos); self[pos..(pos + 3)].unpack(@long)[0]; end
16
- end
17
-
18
- case @data[0..1]
19
- when 'II'; @data.short, @data.long = 'v', 'V'
20
- when 'MM'; @data.short, @data.long = 'n', 'N'
21
- else; raise 'no II or MM marker found'
22
- end
23
- end
24
-
25
- readIfds(offset || @data.readlong(4))
26
- end
27
-
28
- def readIfds(pos)
29
- while pos != 0 do
30
- num = @data.readshort(pos)
31
- pos += 2
32
-
33
- num.times do
34
- fields << TiffField.new(@data, pos)
35
- pos += 12
36
- end
37
-
38
- pos = @data.readlong(pos)
39
- end
40
- end
41
- end
42
-
43
- class TiffField # :nodoc:
44
- attr_reader :tag, :offset, :value
45
-
46
- def initialize(data, pos)
47
- @tag, count, @offset = data.readshort(pos), data.readlong(pos + 4), data.readlong(pos + 8)
48
-
49
- case data.readshort(pos + 2)
50
- when 1, 6 # byte, signed byte
51
- # TODO handle signed bytes
52
- len, pack = count, proc { |d| d }
53
- when 2 # ascii
54
- len, pack = count, proc { |d| d.strip }
55
- when 3, 8 # short, signed short
56
- # TODO handle signed
57
- len, pack = count * 2, proc { |d| d.unpack(data.short + '*') }
58
- when 4, 9 # long, signed long
59
- # TODO handle signed
60
- len, pack = count * 4, proc { |d| d.unpack(data.long + '*') }
61
- when 5, 10
62
- len, pack = count * 8, proc do |d|
63
- r = []
64
- d.unpack(data.long + '*').each_with_index do |v,i|
65
- i % 2 == 0 ? r << [v] : r.last << v
66
- end
67
- r.map do |f|
68
- if f[1] == 0 # allow NaN and Infinity
69
- f[0].to_f.quo(f[1])
70
- else
71
- Rational.reduce(*f)
72
- end
73
- end
74
- end
75
- end
76
-
77
- if len && pack
78
- start = len > 4 ? @offset : (pos + 8)
79
- @value = pack[data[start..(start + len - 1)]]
80
- end
81
- end
82
- end
83
- end
@@ -1,61 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require File.join(File.dirname(__FILE__), 'test_helper')
4
-
5
- class TestEXIF < Test::Unit::TestCase
6
- def test_initialize
7
- [[f('canon-g3.exif'), 'Canon PowerShot G3']].each do |fname,model|
8
- assert_equal EXIF.new(File.read(fname)).model, model
9
- end
10
-
11
- assert_raise RuntimeError, 'no II or MM marker found' do
12
- EXIF.new('X' * 100)
13
- end
14
- end
15
-
16
- def test_dates
17
- (all_test_exifs - [f('weird_date.exif')]).each do |fname|
18
- assert_kind_of Time, EXIF.new(File.read(fname)).date_time
19
- end
20
- assert_nil EXIF.new(File.read(f('weird_date.exif'))).date_time
21
- end
22
-
23
- def test_orientation
24
- all_test_exifs.each do |fname|
25
- orientation = EXIF.new(File.read(fname)).orientation
26
- assert_kind_of Module, orientation
27
- assert orientation.respond_to?(:to_i)
28
- assert orientation.respond_to?(:transform_rmagick)
29
- end
30
- end
31
-
32
- def test_thumbnail
33
- assert_not_nil JPEG.new(f('exif.jpg')).exif.thumbnail
34
-
35
- all_test_exifs.each do |fname|
36
- thumbnail = EXIF.new(File.read(fname)).thumbnail
37
- assert_nothing_raised do
38
- JPEG.new(StringIO.new(thumbnail))
39
- end
40
- end
41
- end
42
-
43
- def test_exif_offset
44
- assert JPEG.new(f('exif.jpg')).exif.include?(:exif_version)
45
- end
46
-
47
- def test_gps
48
- exif = EXIF.new(File.read(f('gps.exif')))
49
- assert exif.include?(:gps_version_id)
50
- assert_equal "\2\2\0\0", exif.gps_version_id
51
- assert_equal 'N', exif.gps_latitude_ref
52
- assert_equal 'W', exif.gps_longitude_ref
53
- assert_equal [5355537.quo(100000), 0.quo(1), 0.quo(1)], exif.gps_latitude
54
- assert_equal [678886.quo(100000), 0.quo(1), 0.quo(1)], exif.gps_longitude
55
- assert_equal 'WGS84', exif.gps_map_datum
56
-
57
- (all_test_exifs - [f('gps.exif')]).each do |fname|
58
- assert EXIF.new(File.read(fname)).keys.map{|k|k.to_s}.grep(/gps/).empty?
59
- end
60
- end
61
- end