exifr 0.9.1 → 0.9.2

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