cul_image_props 0.1.0
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/lib/cul_image_props/image/magic.rb +22 -0
- data/lib/cul_image_props/image/properties/exif/constants.rb +1075 -0
- data/lib/cul_image_props/image/properties/exif/types.rb +542 -0
- data/lib/cul_image_props/image/properties/exif.rb +189 -0
- data/lib/cul_image_props/image/properties/types.rb +318 -0
- data/lib/cul_image_props/image/properties/version.rb +7 -0
- data/lib/cul_image_props/image/properties.rb +51 -0
- data/lib/cul_image_props/image.rb +5 -0
- data/lib/cul_image_props.rb +11 -0
- metadata +118 -0
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'cul_image_props/image/magic'
|
2
|
+
require 'cul_image_props/image/properties/exif/types'
|
3
|
+
require 'cul_image_props/image/properties/exif/constants'
|
4
|
+
module Cul
|
5
|
+
module Image
|
6
|
+
module Properties
|
7
|
+
module Exif
|
8
|
+
|
9
|
+
|
10
|
+
# process an image file (expects an open file object)
|
11
|
+
# this is the function that has to deal with all the arbitrary nasty bits
|
12
|
+
# of the EXIF standard
|
13
|
+
def self.process_file(f, opts={})
|
14
|
+
def_opts = { :stop_tag => 'UNDEF', :details => true, :strict => false }
|
15
|
+
opts = def_opts.merge(opts)
|
16
|
+
stop_tag = opts[:stop_tag]
|
17
|
+
details = opts[:details]
|
18
|
+
strict = opts[:strict]
|
19
|
+
|
20
|
+
# by default do not fake an EXIF beginning
|
21
|
+
fake_exif = 0
|
22
|
+
|
23
|
+
# determine whether it's a JPEG or TIFF
|
24
|
+
data = f.read(12)
|
25
|
+
jfif = {}
|
26
|
+
if [Cul::Image::Magic::TIFF_MOTOROLA_BE, Cul::Image::Magic::TIFF_INTEL_LE].include? data[0, 4]
|
27
|
+
f.seek(0)
|
28
|
+
endian = data[0,1]
|
29
|
+
offset = 0
|
30
|
+
elsif data[0,2] == Cul::Image::Magic::JPEG
|
31
|
+
# it's a JPEG file
|
32
|
+
base = 0
|
33
|
+
while data[2] == "\xFF" and ['JFIF', 'JFXX', 'OLYM', 'Phot'].include? data[6, 4]
|
34
|
+
length = data[4,2].unpack('n')[0]
|
35
|
+
f.read(length-8)
|
36
|
+
# fake an EXIF beginning of file
|
37
|
+
data = "\xFF\x00"+f.read(10)
|
38
|
+
fake_exif = 1
|
39
|
+
base = base + length
|
40
|
+
end
|
41
|
+
# Big ugly patch to deal with APP2 (or other) data coming before APP1
|
42
|
+
f.seek(0)
|
43
|
+
data = f.read(base+8000) # in theory, this could be insufficient --gd
|
44
|
+
|
45
|
+
base = 2
|
46
|
+
fptr = base
|
47
|
+
while true
|
48
|
+
if data[fptr,2]=="\xFF\xE1"
|
49
|
+
if data[fptr+4,4] == "Exif"
|
50
|
+
base = fptr-2
|
51
|
+
break
|
52
|
+
end
|
53
|
+
fptr += 2
|
54
|
+
fptr += data[fptr+2,2].unpack('n')[0]
|
55
|
+
elsif data[fptr,2]=="\xFF\xE2"
|
56
|
+
fptr += 2
|
57
|
+
fptr += data[fptr+2,2].unpack('n')[0]
|
58
|
+
elsif data[fptr,2]=="\xFF\xE0"
|
59
|
+
offset = fptr
|
60
|
+
fptr += 2
|
61
|
+
fptr += data[fptr,2].unpack('n')[0]
|
62
|
+
|
63
|
+
exif = EXIF_TAGS[0x0128]
|
64
|
+
label = 'Image ' + exif.name
|
65
|
+
if (data[offset+11] == 0x00)
|
66
|
+
jfif[label] = IFD_Tag.new(exif.value[1],label,3,[0],offset+11,1)
|
67
|
+
elsif (data[offset+11] == 0x01)
|
68
|
+
jfif[label] = IFD_Tag.new(exif.value[2],label,3,[0],offset+11,1)
|
69
|
+
elsif (data[offset+11] == 0x02)
|
70
|
+
jfif[label] = IFD_Tag.new(exif.value[3],label,3,[0],offset+11,1)
|
71
|
+
else
|
72
|
+
jfif[label] = IFD_Tag.new("Unknown",label,3,[0],offset+11,1)
|
73
|
+
end
|
74
|
+
xres= data[offset+12,2].unpack('n')[0]
|
75
|
+
yres= data[offset+14,2].unpack('n')[0]
|
76
|
+
exif = EXIF_TAGS[0x011a]
|
77
|
+
label = 'Image ' + EXIF_TAGS[0x011a].name
|
78
|
+
jfif[label] = IFD_Tag.new(xres.to_s,label,5,[xres],offset+12,2)
|
79
|
+
label = 'Image ' + EXIF_TAGS[0x011b].name
|
80
|
+
jfif[label] = IFD_Tag.new(yres.to_s,label,5,[yres],offset+13,2)
|
81
|
+
else
|
82
|
+
if(data.length < fptr + 2)
|
83
|
+
break
|
84
|
+
end
|
85
|
+
# scan for the next APP header, /\xFF(^[\x00])/
|
86
|
+
_next = data.index("\xff",fptr + 2)
|
87
|
+
_next = nil if !_next.nil? and _next + 2 >= data.length
|
88
|
+
while (!_next.nil? and data[_next + 1] == 0x00)
|
89
|
+
_next = data.index("\xff",_next + 2)
|
90
|
+
_next = nil if !_next.nil? and _next + 2 >= data.length
|
91
|
+
end
|
92
|
+
unless (_next.nil?)
|
93
|
+
fptr = _next
|
94
|
+
else
|
95
|
+
break
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
f.seek(base+12)
|
100
|
+
if data[2+base] == 0xFF and data[6+base, 4] == 'Exif'
|
101
|
+
# detected EXIF header
|
102
|
+
offset = base+12 # f.tell()
|
103
|
+
endian = f.read(1)
|
104
|
+
else
|
105
|
+
# no EXIF information
|
106
|
+
unless fake_exif
|
107
|
+
return {}
|
108
|
+
else
|
109
|
+
return jfif
|
110
|
+
end
|
111
|
+
end
|
112
|
+
else
|
113
|
+
# file format not recognized
|
114
|
+
return {}
|
115
|
+
end
|
116
|
+
# deal with the EXIF info we found
|
117
|
+
|
118
|
+
hdr = EXIF_header.new(f, endian, offset, fake_exif, strict)
|
119
|
+
jfif.each { |tag|
|
120
|
+
unless hdr.tags.include? tag
|
121
|
+
hdr.tags[tag] = jfif[tag]
|
122
|
+
end
|
123
|
+
}
|
124
|
+
ifd_list = hdr.list_IFDs()
|
125
|
+
ctr = 0
|
126
|
+
ifd_list.each { |i|
|
127
|
+
if ctr == 0
|
128
|
+
ifd_name = 'Image'
|
129
|
+
elsif ctr == 1
|
130
|
+
ifd_name = 'Thumbnail'
|
131
|
+
thumb_ifd = i
|
132
|
+
else
|
133
|
+
ifd_name = 'IFD %d' % ctr
|
134
|
+
end
|
135
|
+
hdr.dump_IFD(i, ifd_name, {:dict=>EXIF_TAGS, :relative=>false, :stop_tag=>stop_tag})
|
136
|
+
# EXIF IFD
|
137
|
+
exif_off = hdr.tags[ifd_name +' ExifOffset']
|
138
|
+
if exif_off
|
139
|
+
hdr.dump_IFD(exif_off.values[0], 'EXIF', {:dict=>EXIF_TAGS, :relative=>false, :stop_tag =>stop_tag})
|
140
|
+
# Interoperability IFD contained in EXIF IFD
|
141
|
+
intr_off = hdr.tags['EXIF SubIFD InteroperabilityOffset']
|
142
|
+
if intr_off
|
143
|
+
hdr.dump_IFD(intr_off.values[0], 'EXIF Interoperability',
|
144
|
+
:dict=>INTR_TAGS, :relative=>false, :stop_tag =>stop_tag)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
# GPS IFD
|
148
|
+
gps_off = hdr.tags[ifd_name+' GPSInfo']
|
149
|
+
if gps_off
|
150
|
+
hdr.dump_IFD(gps_off.values[0], 'GPS', {:dict=>GPS_TAGS, :relative=>false, :stop_tag =>stop_tag})
|
151
|
+
end
|
152
|
+
ctr += 1
|
153
|
+
}
|
154
|
+
# extract uncompressed TIFF thumbnail
|
155
|
+
thumb = hdr.tags['Thumbnail Compression']
|
156
|
+
if thumb and thumb.printable == 'Uncompressed TIFF'
|
157
|
+
hdr.extract_TIFF_thumbnail(thumb_ifd)
|
158
|
+
end
|
159
|
+
# JPEG thumbnail (thankfully the JPEG data is stored as a unit)
|
160
|
+
thumb_off = hdr.tags['Thumbnail JPEGInterchangeFormat']
|
161
|
+
if thumb_off
|
162
|
+
f.seek(offset+thumb_off.values[0])
|
163
|
+
size = hdr.tags['Thumbnail JPEGInterchangeFormatLength'].values[0]
|
164
|
+
hdr.tags['JPEGThumbnail'] = f.read(size)
|
165
|
+
end
|
166
|
+
|
167
|
+
# deal with MakerNote contained in EXIF IFD
|
168
|
+
# (Some apps use MakerNote tags but do not use a format for which we
|
169
|
+
# have a description, do not process these).
|
170
|
+
if hdr.tags.include? 'EXIF MakerNote' and hdr.tags.include? 'Image Make' and detailed
|
171
|
+
hdr.decode_maker_note()
|
172
|
+
end
|
173
|
+
|
174
|
+
# Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote
|
175
|
+
# since it's not allowed in a uncompressed TIFF IFD
|
176
|
+
unless hdr.tags.include? 'JPEGThumbnail'
|
177
|
+
thumb_off=hdr.tags['MakerNote JPEGThumbnail']
|
178
|
+
if thumb_off
|
179
|
+
f.seek(offset+thumb_off.values[0])
|
180
|
+
hdr.tags['JPEGThumbnail']=file.read(thumb_off.field_length)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
return hdr.tags
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,318 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'cul_image_props/image/properties/exif'
|
3
|
+
module Cul
|
4
|
+
module Image
|
5
|
+
module Properties
|
6
|
+
|
7
|
+
class Namespace
|
8
|
+
def initialize(href, prefix)
|
9
|
+
@href = href
|
10
|
+
@prefix = prefix
|
11
|
+
end
|
12
|
+
def href
|
13
|
+
@href
|
14
|
+
end
|
15
|
+
def prefix
|
16
|
+
@prefix
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
ASSESS = Namespace.new("http://purl.oclc.org/NET/CUL/RESOURCE/STILLIMAGE/ASSESSMENT/","si-assess")
|
21
|
+
BASIC = Namespace.new("http://purl.oclc.org/NET/CUL/RESOURCE/STILLIMAGE/BASIC/","si-basic")
|
22
|
+
DCMI = Namespace.new("http://purl.org/dc/terms/","dcmi")
|
23
|
+
|
24
|
+
class Base
|
25
|
+
attr_accessor :nodeset
|
26
|
+
BASE_XML = Nokogiri::XML.parse(<<-xml
|
27
|
+
<rdf:Description xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
28
|
+
xmlns:si-assess="http://purl.oclc.org/NET/CUL/RESOURCE/STILLIMAGE/ASSESSMENT/"
|
29
|
+
xmlns:si-basic="http://purl.oclc.org/NET/CUL/RESOURCE/STILLIMAGE/BASIC/"
|
30
|
+
xmlns:dcmi="http://purl.org/dc/terms/"></rdf:Description>
|
31
|
+
xml
|
32
|
+
)
|
33
|
+
def initialize(srcfile=nil)
|
34
|
+
@src = srcfile
|
35
|
+
@src.rewind
|
36
|
+
@ng_xml = BASE_XML.clone
|
37
|
+
end
|
38
|
+
def nodeset
|
39
|
+
@ng_xml.root.element_children
|
40
|
+
end
|
41
|
+
def [](key)
|
42
|
+
result = nil
|
43
|
+
nodeset.each { |node|
|
44
|
+
if (node.namespace.href + node.name) == key
|
45
|
+
if node.attribute('resource').nil?
|
46
|
+
result = node.text
|
47
|
+
else
|
48
|
+
result = node.attribute('resource').value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
}
|
52
|
+
result
|
53
|
+
end
|
54
|
+
def add_dt_prop(prefix, name, value)
|
55
|
+
prop = @ng_xml.create_element(name)
|
56
|
+
@ng_xml.root.namespace_definitions.each { |ns| prop.namespace = ns if ns.prefix == prefix }
|
57
|
+
prop.add_child(@ng_xml.create_text_node(value.to_s))
|
58
|
+
@ng_xml.root.add_child( prop )
|
59
|
+
end
|
60
|
+
|
61
|
+
def sampling_unit=(name)
|
62
|
+
prop = @ng_xml.create_element("samplingFrequencyUnit")
|
63
|
+
@ng_xml.root.namespace_definitions.each { |ns| prop.namespace = ns if ns.prefix == "si-assess" }
|
64
|
+
@ng_xml.root.add_child( prop )
|
65
|
+
prop.set_attribute("rdf:resource", prop.namespace.href + name)
|
66
|
+
end
|
67
|
+
|
68
|
+
def x_sampling_freq=(value)
|
69
|
+
add_dt_prop("si-assess", "xSamplingFrequency", value)
|
70
|
+
end
|
71
|
+
|
72
|
+
def y_sampling_freq=(value)
|
73
|
+
add_dt_prop("si-assess", "ySamplingFrequency", value)
|
74
|
+
end
|
75
|
+
|
76
|
+
def width=(value)
|
77
|
+
add_dt_prop("si-basic", "imageWidth", value)
|
78
|
+
end
|
79
|
+
|
80
|
+
def length=(value)
|
81
|
+
add_dt_prop("si-basic", "imageLength", value)
|
82
|
+
end
|
83
|
+
|
84
|
+
def extent=(value)
|
85
|
+
add_dt_prop("dcmi", "extent", value)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Bmp < Base
|
90
|
+
def initialize(srcfile=nil)
|
91
|
+
super
|
92
|
+
header_bytes = @src.read(18)
|
93
|
+
raise "Source file is not a bitmap" unless header_bytes[0...2] == Cul::Image::Magic::BMP
|
94
|
+
size = header_bytes[-4,4].unpack('V')[0]
|
95
|
+
header_bytes = header_bytes + @src.read(size)
|
96
|
+
dims = header_bytes[0x12...0x1a].unpack('VV')
|
97
|
+
sampling = header_bytes[0x26...0x2e].unpack('VV')
|
98
|
+
self.sampling_unit='CentimeterSampling'
|
99
|
+
self.width= dims[0]
|
100
|
+
self.length= dims[1]
|
101
|
+
self.extent= srcfile.stat.size unless srcfile.nil?
|
102
|
+
self.x_sampling_freq= (sampling[0]) # / 100).ceil
|
103
|
+
self.y_sampling_freq= (sampling[1]) # / 100).ceil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class Gif < Base
|
108
|
+
def initialize(srcfile=nil)
|
109
|
+
super
|
110
|
+
header_bytes = @src.read(13)
|
111
|
+
raise "Source file is not a gif" unless header_bytes[0...4] == Cul::Image::Magic::GIF
|
112
|
+
self.width= header_bytes[6,2].unpack('v')[0]
|
113
|
+
self.length= header_bytes[8,2].unpack('v')[0]
|
114
|
+
self.extent= srcfile.stat.size unless srcfile.nil?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class Jpeg < Base
|
119
|
+
def initialize(srcfile=nil)
|
120
|
+
super
|
121
|
+
header_bytes = @src.read(2)
|
122
|
+
raise "Source file is not a jpeg" unless header_bytes[0...2] == Cul::Image::Magic::JPEG
|
123
|
+
xpix = 0
|
124
|
+
ypix = 0
|
125
|
+
while (!@src.eof?)
|
126
|
+
if "\xFF" == @src.read(1)
|
127
|
+
mrkr = "\xFF" + @src.read(1)
|
128
|
+
blen = @src.read(2).unpack('n')[0]
|
129
|
+
if Cul::Image::Magic::JPEG_FRAME_MARKERS.include? mrkr # SOFn, Start of frame for scans
|
130
|
+
@src.read(1) #skip bits per sample
|
131
|
+
self.length= @src.read(2).unpack('n')[0]
|
132
|
+
self.width= @src.read(2).unpack('n')[0]
|
133
|
+
@src.seek(0, IO::SEEK_END)
|
134
|
+
else
|
135
|
+
@src.seek(blen - 2, IO::SEEK_CUR)
|
136
|
+
end
|
137
|
+
else
|
138
|
+
@src.seek(0, IO::SEEK_END)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
@src.rewind
|
143
|
+
tags = Cul::Image::Properties::Exif.process_file(@src)
|
144
|
+
if tags.include? 'Image ImageWidth'
|
145
|
+
self.width= tags['Image ImageWidth'].values[0]
|
146
|
+
end
|
147
|
+
if tags.include? 'Image ImageLength'
|
148
|
+
self.length= tags['Image ImageLength'].values[0]
|
149
|
+
end
|
150
|
+
if tags.include? 'Image XResolution'
|
151
|
+
self.x_sampling_freq= tags['Image XResolution'].values[0]
|
152
|
+
end
|
153
|
+
if tags.include? 'Image YResolution'
|
154
|
+
self.y_sampling_freq= tags['Image YResolution'].values[0]
|
155
|
+
end
|
156
|
+
if tags.include? 'Image ResolutionUnit'
|
157
|
+
if (tags['Image ResolutionUnit'].values[0] == 3)
|
158
|
+
self.sampling_unit='CentimeterSampling'
|
159
|
+
elsif (tags['Image ResolutionUnit'].values[0] == 2)
|
160
|
+
self.sampling_unit='InchSampling'
|
161
|
+
else
|
162
|
+
self.sampling_unit='NoAbsoluteSampling'
|
163
|
+
end
|
164
|
+
end
|
165
|
+
self.extent= srcfile.stat.size unless srcfile.nil?
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class Png < Base
|
170
|
+
def initialize(srcfile=nil)
|
171
|
+
super
|
172
|
+
header_bytes = @src.read(8)
|
173
|
+
raise "Source file is not a png" unless header_bytes[0...8] == Cul::Image::Magic::PNG
|
174
|
+
until @src.eof?
|
175
|
+
clen = @src.read(4).unpack('N')[0]
|
176
|
+
ctype = @src.read(4)
|
177
|
+
case ctype
|
178
|
+
when 'pHYs'
|
179
|
+
pHYs(clen)
|
180
|
+
when 'IHDR'
|
181
|
+
IHDR(clen)
|
182
|
+
when 'tEXt'
|
183
|
+
tEXt(clen)
|
184
|
+
when 'IEND'
|
185
|
+
IEND(clen)
|
186
|
+
else
|
187
|
+
@src.seek(clen+4, IO::SEEK_CUR)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
self.extent= srcfile.stat.size unless srcfile.nil?
|
191
|
+
end
|
192
|
+
def pHYs(len)
|
193
|
+
val = @src.read(9)
|
194
|
+
xres = val[0,4].unpack('N')[0]
|
195
|
+
yres = val[4,4].unpack('N')[0]
|
196
|
+
unit = val[8]
|
197
|
+
if unit == 1 # resolution unit is METER
|
198
|
+
xres = (xres / 100).ceil
|
199
|
+
yres = (yres / 100).ceil
|
200
|
+
self.sampling_unit='CentimeterSampling'
|
201
|
+
else
|
202
|
+
self.sampling_unit='NoAbsoluteSampling'
|
203
|
+
end
|
204
|
+
self.x_sampling_freq= xres
|
205
|
+
self.y_sampling_freq= yres
|
206
|
+
@src.seek(len - 5, IO::SEEK_CUR) # remaining block + end tag
|
207
|
+
end
|
208
|
+
def IHDR(len)
|
209
|
+
val = @src.read(8)
|
210
|
+
self.width= val[0,4].unpack('N')[0]
|
211
|
+
self.length= val[4,4].unpack('N')[0]
|
212
|
+
@src.seek(len - 4, IO::SEEK_CUR) # remaining block + end tag
|
213
|
+
end
|
214
|
+
def tEXt(len)
|
215
|
+
@src.seek(len + 4, IO::SEEK_CUR) # remaining block + end tag
|
216
|
+
end
|
217
|
+
def IEND(len)
|
218
|
+
@src.seek(0, IO::SEEK_END)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
module Exif
|
223
|
+
def read_header(srcfile, endian, offset)
|
224
|
+
return ExifHeader(srcfile, endian, offset)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
module LittleEndian
|
229
|
+
def byte(str, signed=false)
|
230
|
+
if signed
|
231
|
+
return str[0,1].unpack('c')[0]
|
232
|
+
else
|
233
|
+
return str[0,1].unpack('C')[0]
|
234
|
+
end
|
235
|
+
end
|
236
|
+
def short(str, signed=false)
|
237
|
+
result = str[0,2].unpack('v')[0]
|
238
|
+
if signed
|
239
|
+
return ~ result unless result < 32768
|
240
|
+
end
|
241
|
+
return result
|
242
|
+
end
|
243
|
+
def int(str, signed=false)
|
244
|
+
result = str[0,4].unpack('V')[0]
|
245
|
+
if signed
|
246
|
+
return ~ result unless result < 2147483648
|
247
|
+
end
|
248
|
+
return result
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
module BigEndian
|
253
|
+
def byte(str, signed=false)
|
254
|
+
if signed
|
255
|
+
return str[0,1].unpack('c')[0]
|
256
|
+
else
|
257
|
+
return str[0,1].unpack('C')[0]
|
258
|
+
end
|
259
|
+
end
|
260
|
+
def short(str, signed=false)
|
261
|
+
result = str[0,2].unpack('n')[0]
|
262
|
+
if signed
|
263
|
+
return ~ result unless result < 32768
|
264
|
+
end
|
265
|
+
return result
|
266
|
+
end
|
267
|
+
def int(str, signed=false)
|
268
|
+
result = str[0,4].unpack('N')[0]
|
269
|
+
if signed
|
270
|
+
return ~ result unless result < 2147483648
|
271
|
+
end
|
272
|
+
return result
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
class Tiff < Base
|
277
|
+
def initialize(srcfile=nil)
|
278
|
+
super
|
279
|
+
header_bytes = @src.read(14)
|
280
|
+
case header_bytes[0...4]
|
281
|
+
when Cul::Image::Magic::TIFF_INTEL_LE
|
282
|
+
@endian = header_bytes[12]
|
283
|
+
when Cul::Image::Magic::TIFF_MOTOROLA_BE
|
284
|
+
@endian = header_bytes[12]
|
285
|
+
else
|
286
|
+
raise "Source file is not a tiff"
|
287
|
+
end
|
288
|
+
@src.rewind
|
289
|
+
tags = Cul::Image::Properties::Exif.process_file(srcfile)
|
290
|
+
if tags.include? 'Image ImageWidth'
|
291
|
+
self.width= tags['Image ImageWidth'].values[0]
|
292
|
+
end
|
293
|
+
if tags.include? 'Image ImageLength'
|
294
|
+
self.length= tags['Image ImageLength'].values[0]
|
295
|
+
end
|
296
|
+
if tags.include? 'Image XResolution'
|
297
|
+
self.x_sampling_freq= tags['Image XResolution'].values[0].inspect
|
298
|
+
end
|
299
|
+
if tags.include? 'Image YResolution'
|
300
|
+
self.y_sampling_freq= tags['Image YResolution'].values[0].inspect
|
301
|
+
end
|
302
|
+
if tags.include? 'Image ResolutionUnit'
|
303
|
+
if (tags['Image ResolutionUnit'].values[0] == 3)
|
304
|
+
self.sampling_unit='CentimeterSampling'
|
305
|
+
elsif (tags['Image ResolutionUnit'].values[0] == 2)
|
306
|
+
self.sampling_unit='InchSampling'
|
307
|
+
else
|
308
|
+
self.sampling_unit='NoAbsoluteSampling'
|
309
|
+
end
|
310
|
+
end
|
311
|
+
# do stuff with tags
|
312
|
+
self.extent= srcfile.stat.size unless srcfile.nil?
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'cul_image_props/image/properties/version'
|
2
|
+
require 'cul_image_props/image/properties/types'
|
3
|
+
require 'cul_image_props/image/magic'
|
4
|
+
require 'open-uri'
|
5
|
+
|
6
|
+
module Cul
|
7
|
+
module Image
|
8
|
+
module Properties
|
9
|
+
def self.identify(src)
|
10
|
+
if src.is_a? String
|
11
|
+
src = open(src)
|
12
|
+
end
|
13
|
+
filesize = src.stat.size
|
14
|
+
result = nil
|
15
|
+
magic_bytes = ''
|
16
|
+
buf = ''
|
17
|
+
src.read(2,buf)
|
18
|
+
magic_bytes << buf
|
19
|
+
case magic_bytes
|
20
|
+
when Cul::Image::Magic::BMP
|
21
|
+
result = Cul::Image::Properties::Bmp.new(src)
|
22
|
+
when Cul::Image::Magic::JPEG
|
23
|
+
result = Cul::Image::Properties::Jpeg.new(src)
|
24
|
+
end
|
25
|
+
|
26
|
+
if result.nil?
|
27
|
+
src.read(2,buf)
|
28
|
+
magic_bytes << buf
|
29
|
+
case magic_bytes
|
30
|
+
when Cul::Image::Magic::TIFF_MOTOROLA_BE
|
31
|
+
result = Cul::Image::Properties::Tiff.new(src)
|
32
|
+
when Cul::Image::Magic::TIFF_INTEL_LE
|
33
|
+
result = Cul::Image::Properties::Tiff.new(src)
|
34
|
+
when Cul::Image::Magic::GIF
|
35
|
+
result = Cul::Image::Properties::Gif.new(src)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
if result.nil?
|
39
|
+
src.read(4,buf)
|
40
|
+
magic_bytes << buf
|
41
|
+
if magic_bytes == Cul::Image::Magic::PNG
|
42
|
+
result = Cul::Image::Properties::Png.new(src)
|
43
|
+
else
|
44
|
+
puts magic_bytes.unpack('H2H2H2H2H2H2H2H2').inspect
|
45
|
+
end
|
46
|
+
end
|
47
|
+
return result
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cul_image_props
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Benjamin Armintor
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-02-14 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - <
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 15
|
29
|
+
segments:
|
30
|
+
- 2
|
31
|
+
- 0
|
32
|
+
- 0
|
33
|
+
version: 2.0.0
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: mocha
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 43
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
- 9
|
48
|
+
- 8
|
49
|
+
version: 0.9.8
|
50
|
+
type: :development
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
name: ruby-debug
|
54
|
+
prerelease: false
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
type: :development
|
65
|
+
version_requirements: *id003
|
66
|
+
description: Library for extracting basic image properties
|
67
|
+
email: armintor@gmail.com
|
68
|
+
executables: []
|
69
|
+
|
70
|
+
extensions: []
|
71
|
+
|
72
|
+
extra_rdoc_files: []
|
73
|
+
|
74
|
+
files:
|
75
|
+
- lib/cul_image_props/image/magic.rb
|
76
|
+
- lib/cul_image_props/image/properties/exif/constants.rb
|
77
|
+
- lib/cul_image_props/image/properties/exif/types.rb
|
78
|
+
- lib/cul_image_props/image/properties/exif.rb
|
79
|
+
- lib/cul_image_props/image/properties/types.rb
|
80
|
+
- lib/cul_image_props/image/properties/version.rb
|
81
|
+
- lib/cul_image_props/image/properties.rb
|
82
|
+
- lib/cul_image_props/image.rb
|
83
|
+
- lib/cul_image_props.rb
|
84
|
+
homepage:
|
85
|
+
licenses: []
|
86
|
+
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
hash: 3
|
98
|
+
segments:
|
99
|
+
- 0
|
100
|
+
version: "0"
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
hash: 3
|
107
|
+
segments:
|
108
|
+
- 0
|
109
|
+
version: "0"
|
110
|
+
requirements: []
|
111
|
+
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 1.8.13
|
114
|
+
signing_key:
|
115
|
+
specification_version: 3
|
116
|
+
summary: Library for extracting basic image properties
|
117
|
+
test_files: []
|
118
|
+
|