exifr 0.9.6 → 0.10

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