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.
@@ -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,7 @@
1
+ module Cul
2
+ module Image
3
+ module Properties
4
+ VERSION = "0.1.0"
5
+ end
6
+ end
7
+ 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
@@ -0,0 +1,5 @@
1
+ require 'cul_image_props/image/properties'
2
+ module Cul
3
+ module Image
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ require 'cul_image_props/image/properties/version'
2
+ require 'cul_image_props/image'
3
+ module Cul
4
+ module Image
5
+ module Properties
6
+ def self.version
7
+ Cul::Image::Properties::VERSION
8
+ end
9
+ end
10
+ end
11
+ 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
+