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.
- 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: []
|