exifr 0.9.1 → 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +10 -1
- data/bin/exifr +33 -0
- data/lib/exif.rb +90 -23
- data/lib/jpeg.rb +12 -8
- data/lib/tiff_header.rb +19 -17
- 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
|
data/bin/exifr
ADDED
@@ -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
|
data/lib/exif.rb
CHANGED
@@ -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
|
-
|
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 => :
|
35
|
-
0x0202 => :
|
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
|
-
|
107
|
-
|
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
|
-
#
|
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
|
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
|
205
|
+
if EXIF_HEADERS.include?(f.tag)
|
127
206
|
traverse(TiffHeader.new(@data, f.offset))
|
128
207
|
elsif tag
|
129
|
-
|
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
|
data/lib/jpeg.rb
CHANGED
@@ -15,17 +15,18 @@ module EXIFR
|
|
15
15
|
attr_reader :width
|
16
16
|
# number of bits per ???
|
17
17
|
attr_reader :bits
|
18
|
-
#
|
18
|
+
# comment; a string if one comment found, an array if more,
|
19
|
+
# otherwise <tt>nil</tt>
|
19
20
|
attr_reader :comment
|
20
|
-
#
|
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?
|
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
|
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
|
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
|
data/lib/tiff_header.rb
CHANGED
@@ -8,16 +8,18 @@ module EXIFR
|
|
8
8
|
@data = data
|
9
9
|
@fields = []
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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(/\
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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.
|
7
|
-
date: 2006-05-
|
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: []
|