exifr 0.9.1 → 0.9.2

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.
Files changed (6) hide show
  1. data/CHANGELOG +10 -1
  2. data/bin/exifr +33 -0
  3. data/lib/exif.rb +90 -23
  4. data/lib/jpeg.rb +12 -8
  5. data/lib/tiff_header.rb +19 -17
  6. metadata +6 -5
data/CHANGELOG CHANGED
@@ -1,5 +1,14 @@
1
+ EXIF Reader 0.9.2
2
+ * bug fix; "[#4595] EXIFR::JPEG doesn't support multiple comments", the
3
+ comment property of a JPEG object now contains an array instead of a string
4
+ when multiple COM frames are found
5
+ * EXIF orientation modules including RMagick code to rotate to viewable state
6
+ * access to thumbnail included in EXIF
7
+ * simple commandline utility, "exifr", to view image properties
8
+ * overall code improvements including documentation and tests
9
+
1
10
  EXIF Reader 0.9.1
2
- * bug fix "4321 Can't create object", division by zero when
11
+ * bug fix; "4321 Can't create object", division by zero when
3
12
  denominator of rational value is zero
4
13
 
5
14
  EXIF Reader 0.9
@@ -0,0 +1,33 @@
1
+ include EXIFR
2
+
3
+ def pp(fname)
4
+ jpeg = JPEG.new(fname)
5
+ ks = %w(width height comment bits)
6
+ ks += jpeg.exif.keys.map{|a|a.to_s}.sort{|a,b|a<=>b} if jpeg.exif?
7
+
8
+ l = []
9
+ ks[0..3].each do |k|
10
+ v = jpeg.send(k)
11
+ l << [k, v] if v
12
+ end
13
+ ks[4..-1].each do |k|
14
+ v = jpeg.exif[k.to_sym]
15
+ l << [k, v] if v
16
+ end
17
+ l.delete_if{|k,v|v.nil?}
18
+
19
+ puts "#{fname}:"
20
+ f = " %#{l.sort{|a,b|a[0].size <=> b[0].size}.last[0].size}s = %s\n"
21
+ l.each{|k,v|puts f % [k, v]}
22
+ end
23
+
24
+ if ARGV.size == 0
25
+ STDERR.puts "Usage: #{$0} FILE .."
26
+ elsif ARGV.size == 1
27
+ pp ARGV[0]
28
+ else
29
+ ARGV.each do |fname|
30
+ pp fname
31
+ puts
32
+ end
33
+ end
@@ -1,9 +1,42 @@
1
+ #!/usr/bin/env ruby
1
2
  # Copyright (c) 2006 - R.W. van 't Veer
2
3
 
3
- module EXIFR
4
+ module EXIFR
4
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
+ #
5
37
  class EXIF < Hash
6
- @@TAGS = {
38
+ TAGS = {} # :nodoc:
39
+ TAGS.merge!({
7
40
  0x0100 => :image_width,
8
41
  0x0101 => :image_length,
9
42
  0x0102 => :bits_per_sample,
@@ -31,8 +64,8 @@ module EXIFR
31
64
  0x013f => :primary_chromaticities,
32
65
  0x0156 => :transfer_range,
33
66
  0x0200 => :jpegproc,
34
- 0x0201 => :jpeginterchange_format,
35
- 0x0202 => :jpeginterchange_format_length,
67
+ 0x0201 => :jpeg_interchange_format,
68
+ 0x0202 => :jpeg_interchange_format_length,
36
69
  0x0211 => :ycb_cr_coefficients,
37
70
  0x0212 => :ycb_cr_sub_sampling,
38
71
  0x0213 => :ycb_cr_positioning,
@@ -102,9 +135,48 @@ module EXIFR
102
135
  0xa40b => :device_setting_descr,
103
136
  0xa40c => :subject_dist_range,
104
137
  0xa420 => :image_unique_id
105
- }
106
- @@EXIF_HEADERS = [0x8769, 0x8825, 0xa005]
107
- @@TIME_TAGS = [:date_time, :date_time_original, :date_time_digitized]
138
+ })
139
+ EXIF_HEADERS = [0x8769, 0x8825, 0xa005] # :nodoc:
140
+
141
+ time_proc = proc do |value|
142
+ if value =~ /^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/
143
+ Time.mktime($1, $2, $3, $4, $5, $6)
144
+ else
145
+ value
146
+ end
147
+ end
148
+
149
+ ORIENTATIONS = [] # :nodoc:
150
+ [
151
+ nil,
152
+ [:TopLeft, 'img'],
153
+ [:TopRight, 'img.flop'],
154
+ [:BottomRight, 'img.rotate(180)'],
155
+ [:BottomLeft, 'img.flip'],
156
+ [:LeftTop, 'img.rotate(90).flop'],
157
+ [:RightTop, 'img.rotate(90)'],
158
+ [:RightBottom, 'img.rotate(270).flop'],
159
+ [:LeftBottom, 'img.rotate(270)'],
160
+ ].each_with_index do |tuple,index|
161
+ next unless tuple
162
+ name, rmagic_code = *tuple
163
+
164
+ eval <<-EOS
165
+ module #{name}Orientation
166
+ def self.to_i; #{index}; end
167
+ def self.transform_rmagick(img); #{rmagic_code}; end
168
+ end
169
+ ORIENTATIONS[#{index}] = #{name}Orientation
170
+ EOS
171
+ end
172
+
173
+ ADAPTERS = Hash.new { proc { |v| v } } # :nodoc:
174
+ ADAPTERS.merge!({
175
+ :date_time_original => time_proc,
176
+ :date_time_digitized => time_proc,
177
+ :date_time => time_proc,
178
+ :orientation => proc { |v| ORIENTATIONS[v] }
179
+ })
108
180
 
109
181
  # +data+ the content of the JPEG APP1 frame without the EXIF marker
110
182
  def initialize(data)
@@ -113,7 +185,13 @@ module EXIFR
113
185
  freeze
114
186
  end
115
187
 
116
- # convience
188
+ # access to thumbnail, if included
189
+ def thumbnail
190
+ start, length = self[:jpeg_interchange_format], self[:jpeg_interchange_format_length]
191
+ @data[start..(start + length)] if start && length
192
+ end
193
+
194
+ # convience; <tt>self[method]</tt>
117
195
  def method_missing(method, *args)
118
196
  self[method]
119
197
  end
@@ -121,26 +199,15 @@ module EXIFR
121
199
  private
122
200
  def traverse(tiff)
123
201
  tiff.fields.each do |f|
124
- tag, value = @@TAGS[f.tag], f.value
202
+ tag = TAGS[f.tag]
203
+ value = f.value.map { |v| ADAPTERS[tag][v] } if f.value
125
204
  value = (value.kind_of?(Array) && value.size == 1) ? value.first : value
126
- if @@EXIF_HEADERS.include?(f.tag)
205
+ if EXIF_HEADERS.include?(f.tag)
127
206
  traverse(TiffHeader.new(@data, f.offset))
128
207
  elsif tag
129
- if @@TIME_TAGS.include? tag
130
- self[tag] = value2time(value)
131
- else
132
- self[tag] = value
133
- end
208
+ self[tag] = value
134
209
  end
135
210
  end
136
211
  end
137
-
138
- def value2time(value)
139
- if value.sub(/\000.*$/, '') =~ /^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/
140
- Time.mktime($1, $2, $3, $4, $5, $6)
141
- else
142
- value
143
- end
144
- end
145
212
  end
146
213
  end
@@ -15,17 +15,18 @@ module EXIFR
15
15
  attr_reader :width
16
16
  # number of bits per ???
17
17
  attr_reader :bits
18
- # image comment
18
+ # comment; a string if one comment found, an array if more,
19
+ # otherwise <tt>nil</tt>
19
20
  attr_reader :comment
20
- # hash of exif data if available
21
+ # EXIF data if available
21
22
  attr_reader :exif
22
23
 
23
24
  # +file+ is a filename or an IO object
24
25
  def initialize(file)
25
- if file.kind_of? IO
26
- examine(file)
27
- else
26
+ if file.kind_of? String
28
27
  File.open(file, 'rb') { |io| examine(io) }
28
+ else
29
+ examine(file.dup)
29
30
  end
30
31
  end
31
32
 
@@ -47,21 +48,24 @@ module EXIFR
47
48
  c = readchar while c == 0xFF
48
49
  c
49
50
  end
50
- end
51
+ end unless io.respond_to? :readsof
51
52
 
53
+ app1 = nil
52
54
  while marker = io.next
53
55
  case marker
54
56
  when 0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF # SOF markers
55
57
  length, @bits, @height, @width, components = io.readsof
56
58
  raise 'malformed JPEG' unless length == 8 + components * 3
57
59
  when 0xD9, 0xDA: break # EOI, SOS
58
- when 0xFE: @comment = io.readframe # COM
60
+ when 0xFE: (@comment ||= []) << io.readframe # COM
59
61
  when 0xE1: app1 = io.readframe # APP1, contains EXIF tag
60
62
  else io.readframe # ignore frame
61
63
  end
62
64
  end
63
65
 
64
- if app1 && EXIF
66
+ @comment = @comment.first if @comment && @comment.size == 1
67
+
68
+ if app1 && app1[0..5] == "Exif\0\0"
65
69
  @exif = EXIF.new(app1[6..-1]) # rescue nil
66
70
  end
67
71
  end
@@ -8,16 +8,18 @@ module EXIFR
8
8
  @data = data
9
9
  @fields = []
10
10
 
11
- class << @data
12
- attr_accessor :short, :long
13
- def readshort(pos); self[pos..(pos + 1)].unpack(@short)[0]; end
14
- def readlong(pos); self[pos..(pos + 3)].unpack(@long)[0]; end
15
- end
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
16
17
 
17
- case @data[0..1]
18
- when 'II'; @data.short, @data.long = 'v', 'V'
19
- when 'MM'; @data.short, @data.long = 'n', 'N'
20
- else; raise 'no II or MM marker found'
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
21
23
  end
22
24
 
23
25
  readIfds(offset || @data.readlong(4))
@@ -49,7 +51,7 @@ module EXIFR
49
51
  # TODO handle signed bytes
50
52
  len, pack = count, proc { |d| d }
51
53
  when 2 # ascii
52
- len, pack = count, proc { |d| d.sub(/\000.*$/, '').strip }
54
+ len, pack = count, proc { |d| d.sub(/\0.*$/, '').strip }
53
55
  when 3, 8 # short, signed short
54
56
  # TODO handle signed
55
57
  len, pack = count * 2, proc { |d| d.unpack(data.short + '*') }
@@ -62,13 +64,13 @@ module EXIFR
62
64
  d.unpack(data.long + '*').each_with_index do |v,i|
63
65
  i % 2 == 0 ? r << [v] : r.last << v
64
66
  end
65
- r.map do |f|
66
- if f[1] == 0 # allow NaN and Infinity
67
- f[0].to_f.quo(f[1])
68
- else
69
- Rational.reduce(*f)
70
- end
71
- 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
72
74
  end
73
75
  end
74
76
 
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: exifr
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.9.1
7
- date: 2006-05-03 00:00:00 +02:00
6
+ version: 0.9.2
7
+ date: 2006-05-30 00:00:00 +02:00
8
8
  summary: EXIF Reader is a module to read EXIF from JPEG images.
9
9
  require_paths:
10
10
  - lib
@@ -28,9 +28,10 @@ cert_chain:
28
28
  authors:
29
29
  - R.W. van 't Veer
30
30
  files:
31
+ - bin/exifr
32
+ - lib/exif.rb
31
33
  - lib/exifr.rb
32
34
  - lib/jpeg.rb
33
- - lib/exif.rb
34
35
  - lib/tiff_header.rb
35
36
  - README
36
37
  - CHANGELOG
@@ -41,8 +42,8 @@ rdoc_options: []
41
42
  extra_rdoc_files:
42
43
  - README
43
44
  - CHANGELOG
44
- executables: []
45
-
45
+ executables:
46
+ - exifr
46
47
  extensions: []
47
48
 
48
49
  requirements: []