cul_image_props 0.1.2 → 0.2.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.
@@ -1,22 +1,23 @@
|
|
1
1
|
module Cul
|
2
2
|
module Image
|
3
3
|
module Magic
|
4
|
-
BMP = "\x42\x4D"
|
5
|
-
GIF = "\x47\x49\x46\x38"
|
6
|
-
JPEG = "\xFF\xD8"
|
7
|
-
|
4
|
+
BMP = [0x42, 0x4d] # "\x42\x4D"
|
5
|
+
GIF = [0x47,0x49,0x46,0x38] # "\x47\x49\x46\x38"
|
6
|
+
JPEG = [0xff,0xd8] # "\xFF\xD8"
|
7
|
+
JFM_BYTES = [[0xff,0xc0],[0xff,0xc1],[0xff,0xc2],[0xff,0xc5],[0xff,0xc6],[0xff,0xc9],[0xff,0xca],[0xff,0xcd],[0xff,0xce]]
|
8
|
+
JPEG_FRAME_MARKERS = JFM_BYTES.collect {|bytes| bytes.pack('C*')}
|
8
9
|
JPEG_SEGMENTS = {
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
10
|
+
[0xFF,0xE0]=>"APP0",
|
11
|
+
[0xFF,0xE1]=>"APP1",
|
12
|
+
[0xFF,0xC1]=>"SOF1", # image data: extended sequential dct
|
13
|
+
[0xFF,0xC2]=>"SOF2", # image data: progressive dct
|
14
|
+
[0xFF,0xC4]=>"DHT", # image data: huffman table(s)
|
15
|
+
[0xFF,0xDA]=>"SOS", # image data: start of scan
|
16
|
+
[0xFF,0xDB]=>"DQT" # quantization tables
|
16
17
|
}
|
17
|
-
PNG = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"
|
18
|
-
TIFF_MOTOROLA_BE = "\x4d\x4d\x00\x2a"
|
19
|
-
TIFF_INTEL_LE = "\x49\x49\x2a\x00"
|
18
|
+
PNG = [0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a] # "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"
|
19
|
+
TIFF_MOTOROLA_BE = [0x4d,0x4d,0x00,0x2a] # "\x4d\x4d\x00\x2a"
|
20
|
+
TIFF_INTEL_LE = [0x49,0x49,0x2a,0x00] # "\x49\x49\x2a\x00"
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
@@ -6,6 +6,14 @@ module Image
|
|
6
6
|
module Properties
|
7
7
|
module Exif
|
8
8
|
|
9
|
+
# return the value that should be compared to stringvalue[Fixnum] for a given number 'h'
|
10
|
+
def self.hex_comparison_value(h)
|
11
|
+
("X".respond_to? :ord) ? [h].pack('C') : h
|
12
|
+
end
|
13
|
+
XFF = hex_comparison_value(0xff)
|
14
|
+
X00 = hex_comparison_value(0x00)
|
15
|
+
X01 = hex_comparison_value(0x01)
|
16
|
+
X02 = hex_comparison_value(0x02)
|
9
17
|
|
10
18
|
# process an image file (expects an open file object)
|
11
19
|
# this is the function that has to deal with all the arbitrary nasty bits
|
@@ -23,70 +31,76 @@ def self.process_file(f, opts={})
|
|
23
31
|
# determine whether it's a JPEG or TIFF
|
24
32
|
data = f.read(12)
|
25
33
|
jfif = {}
|
26
|
-
if [Cul::Image::Magic::TIFF_MOTOROLA_BE, Cul::Image::Magic::TIFF_INTEL_LE].include? data[0, 4]
|
34
|
+
if [Cul::Image::Magic::TIFF_MOTOROLA_BE, Cul::Image::Magic::TIFF_INTEL_LE].include? data[0, 4].unpack('C*')
|
27
35
|
f.seek(0)
|
28
36
|
endian = data[0,1]
|
29
37
|
offset = 0
|
30
|
-
elsif data[0,2] == Cul::Image::Magic::JPEG
|
38
|
+
elsif data[0,2].unpack('C*') == Cul::Image::Magic::JPEG
|
31
39
|
# it's a JPEG file
|
32
40
|
base = 0
|
33
|
-
while data[2] ==
|
41
|
+
while data[2] == XFF and ['JFIF', 'JFXX', 'OLYM', 'Phot'].include? data[6, 4]
|
34
42
|
length = data[4,2].unpack('n')[0]
|
35
43
|
f.read(length-8)
|
36
44
|
# fake an EXIF beginning of file
|
37
|
-
data =
|
45
|
+
data = [0xff,0x00].pack('C*') + f.read(10)
|
38
46
|
fake_exif = 1
|
39
47
|
base = base + length
|
40
48
|
end
|
41
49
|
# Big ugly patch to deal with APP2 (or other) data coming before APP1
|
42
50
|
f.seek(0)
|
43
|
-
|
51
|
+
flen = base+8000
|
52
|
+
data = f.read(flen) # in theory, this could be insufficient --gd
|
44
53
|
|
45
54
|
base = 2
|
46
55
|
fptr = base
|
47
56
|
while true
|
48
|
-
if data[fptr,2]==
|
57
|
+
if data[fptr,2].unpack('C*') == [0xff,0xe1]
|
49
58
|
if data[fptr+4,4] == "Exif"
|
50
59
|
base = fptr-2
|
51
60
|
break
|
52
61
|
end
|
53
62
|
fptr += 2
|
54
63
|
fptr += data[fptr+2,2].unpack('n')[0]
|
55
|
-
elsif data[fptr,2]==
|
64
|
+
elsif data[fptr,2].unpack('C*') == [0xff,0xe2]
|
56
65
|
fptr += 2
|
57
66
|
fptr += data[fptr+2,2].unpack('n')[0]
|
58
|
-
elsif data[fptr,2]==
|
67
|
+
elsif data[fptr,2].unpack('C*') == [0xff,0xe0]
|
59
68
|
offset = fptr
|
60
69
|
fptr += 2
|
61
70
|
fptr += data[fptr,2].unpack('n')[0]
|
62
|
-
|
63
|
-
|
71
|
+
if data.length < offset + 16
|
72
|
+
lack = (offset + 16 - data.length)
|
73
|
+
data.concat f.read(lack)
|
74
|
+
end
|
75
|
+
exif = EXIF_TAGS[0x0128] # resolution unit
|
64
76
|
label = 'Image ' + exif.name
|
65
|
-
if (data[offset+11] ==
|
66
|
-
|
67
|
-
elsif (data[offset+11] ==
|
68
|
-
|
69
|
-
elsif (data[offset+11] ==
|
70
|
-
|
77
|
+
if (data[offset+11] == X00)
|
78
|
+
jfif[label] = IFD_Tag.new(exif.value[1],label,3,[0],offset+11,1)
|
79
|
+
elsif (data[offset+11] == X01)
|
80
|
+
jfif[label] = IFD_Tag.new(exif.value[2],label,3,[0],offset+11,1)
|
81
|
+
elsif (data[offset+11] == X02)
|
82
|
+
jfif[label] = IFD_Tag.new(exif.value[3],label,3,[0],offset+11,1)
|
71
83
|
else
|
72
|
-
|
84
|
+
puts "Unknown jfif tag: #{data[offset+11]}"
|
85
|
+
jfif[label] = IFD_Tag.new("Unknown",label,3,[0],offset+11,1)
|
73
86
|
end
|
74
87
|
xres= data[offset+12,2].unpack('n')[0]
|
75
88
|
yres= data[offset+14,2].unpack('n')[0]
|
76
|
-
exif = EXIF_TAGS[0x011a]
|
89
|
+
exif = EXIF_TAGS[0x011a] # x resolution
|
77
90
|
label = 'Image ' + EXIF_TAGS[0x011a].name
|
78
91
|
jfif[label] = IFD_Tag.new(xres.to_s,label,5,[xres],offset+12,2)
|
79
|
-
label = 'Image ' + EXIF_TAGS[0x011b].name
|
92
|
+
label = 'Image ' + EXIF_TAGS[0x011b].name # y resolution
|
80
93
|
jfif[label] = IFD_Tag.new(yres.to_s,label,5,[yres],offset+13,2)
|
81
94
|
else
|
82
95
|
if(data.length < fptr + 2)
|
83
96
|
break
|
84
97
|
end
|
85
98
|
# scan for the next APP header, /\xFF(^[\x00])/
|
86
|
-
|
99
|
+
ff = [0xff].pack('C')
|
100
|
+
_next = data.index(ff,fptr + 2)
|
87
101
|
_next = nil if !_next.nil? and _next + 2 >= data.length
|
88
|
-
while (!_next.nil? and data[_next + 1] ==
|
89
|
-
_next = data.index(
|
102
|
+
while (!_next.nil? and data[_next + 1] == X00)
|
103
|
+
_next = data.index(ff,_next + 2)
|
90
104
|
_next = nil if !_next.nil? and _next + 2 >= data.length
|
91
105
|
end
|
92
106
|
unless (_next.nil?)
|
@@ -97,7 +111,7 @@ def self.process_file(f, opts={})
|
|
97
111
|
end
|
98
112
|
end
|
99
113
|
f.seek(base+12)
|
100
|
-
if data[2+base] ==
|
114
|
+
if data[2+base] == XFF and data[6+base, 4] == 'Exif'
|
101
115
|
# detected EXIF header
|
102
116
|
offset = base+12 # f.tell()
|
103
117
|
endian = f.read(1)
|
@@ -35,6 +35,16 @@ xml
|
|
35
35
|
@src.rewind
|
36
36
|
@ng_xml = BASE_XML.clone
|
37
37
|
end
|
38
|
+
# this is a hack to deal with Ruby 1.9 shenanigans
|
39
|
+
def ord_value(val)
|
40
|
+
if val.is_a? Fixnum
|
41
|
+
return val
|
42
|
+
elsif val.is_a? String
|
43
|
+
return val.unpack('C')[0]
|
44
|
+
else
|
45
|
+
return val.to_i
|
46
|
+
end
|
47
|
+
end
|
38
48
|
def nodeset
|
39
49
|
@ng_xml.root.element_children
|
40
50
|
end
|
@@ -84,13 +94,21 @@ xml
|
|
84
94
|
def extent=(value)
|
85
95
|
add_dt_prop("dcmi", "extent", value)
|
86
96
|
end
|
97
|
+
|
98
|
+
def hex_inspect(str)
|
99
|
+
result = []
|
100
|
+
(0...str.length).each {|ix| result << str[ix].to_s(16)}
|
101
|
+
result.inspect
|
102
|
+
end
|
87
103
|
end
|
88
104
|
|
89
105
|
class Bmp < Base
|
90
106
|
def initialize(srcfile=nil)
|
91
107
|
super
|
92
108
|
header_bytes = @src.read(18)
|
93
|
-
|
109
|
+
unless header_bytes[0...2].unpack('C*') == Cul::Image::Magic::BMP
|
110
|
+
raise "Source file is not a bitmap: #{hex_inspect(header_bytes[0...2])}"
|
111
|
+
end
|
94
112
|
size = header_bytes[-4,4].unpack('V')[0]
|
95
113
|
header_bytes = header_bytes + @src.read(size)
|
96
114
|
dims = header_bytes[0x12...0x1a].unpack('VV')
|
@@ -108,7 +126,9 @@ class Gif < Base
|
|
108
126
|
def initialize(srcfile=nil)
|
109
127
|
super
|
110
128
|
header_bytes = @src.read(13)
|
111
|
-
|
129
|
+
unless header_bytes[0...4].unpack('C*') == Cul::Image::Magic::GIF
|
130
|
+
raise "Source file is not a gif: #{hex_inspect(header_bytes[0...4])}"
|
131
|
+
end
|
112
132
|
self.width= header_bytes[6,2].unpack('v')[0]
|
113
133
|
self.length= header_bytes[8,2].unpack('v')[0]
|
114
134
|
self.extent= srcfile.stat.size unless srcfile.nil?
|
@@ -119,14 +139,16 @@ class Jpeg < Base
|
|
119
139
|
def initialize(srcfile=nil)
|
120
140
|
super
|
121
141
|
header_bytes = @src.read(2)
|
122
|
-
|
142
|
+
unless header_bytes[0...2].unpack('C*') == Cul::Image::Magic::JPEG
|
143
|
+
raise "Source file is not a jpeg: #{hex_inspect(header_bytes[0...2])}"
|
144
|
+
end
|
123
145
|
xpix = 0
|
124
146
|
ypix = 0
|
125
147
|
while (!@src.eof?)
|
126
|
-
if
|
127
|
-
mrkr =
|
148
|
+
if 0xff == ord_value(@src.read(1))
|
149
|
+
mrkr = [0xff, ord_value(@src.read(1))]
|
128
150
|
blen = @src.read(2).unpack('n')[0]
|
129
|
-
if Cul::Image::Magic::
|
151
|
+
if Cul::Image::Magic::JFM_BYTES.include? mrkr # SOFn, Start of frame for scans
|
130
152
|
@src.read(1) #skip bits per sample
|
131
153
|
self.length= @src.read(2).unpack('n')[0]
|
132
154
|
self.width= @src.read(2).unpack('n')[0]
|
@@ -170,7 +192,9 @@ class Png < Base
|
|
170
192
|
def initialize(srcfile=nil)
|
171
193
|
super
|
172
194
|
header_bytes = @src.read(8)
|
173
|
-
|
195
|
+
unless header_bytes[0...8].unpack('C*') == Cul::Image::Magic::PNG
|
196
|
+
raise "Source file is not a png #{hex_inspect(header_bytes[0...8])}"
|
197
|
+
end
|
174
198
|
until @src.eof?
|
175
199
|
clen = @src.read(4).unpack('N')[0]
|
176
200
|
ctype = @src.read(4)
|
@@ -193,7 +217,7 @@ class Png < Base
|
|
193
217
|
val = @src.read(9)
|
194
218
|
xres = val[0,4].unpack('N')[0]
|
195
219
|
yres = val[4,4].unpack('N')[0]
|
196
|
-
unit = val[8]
|
220
|
+
unit = ord_value(val[8])
|
197
221
|
if unit == 1 # resolution unit is METER
|
198
222
|
xres = (xres / 100).ceil
|
199
223
|
yres = (yres / 100).ceil
|
@@ -277,13 +301,13 @@ class Tiff < Base
|
|
277
301
|
def initialize(srcfile=nil)
|
278
302
|
super
|
279
303
|
header_bytes = @src.read(14)
|
280
|
-
case header_bytes[0...4]
|
304
|
+
case header_bytes[0...4].unpack('C*')
|
281
305
|
when Cul::Image::Magic::TIFF_INTEL_LE
|
282
306
|
@endian = header_bytes[12]
|
283
307
|
when Cul::Image::Magic::TIFF_MOTOROLA_BE
|
284
308
|
@endian = header_bytes[12]
|
285
309
|
else
|
286
|
-
raise "Source file is not a tiff"
|
310
|
+
raise "Source file is not a tiff #{hex_inspect(header_bytes[0...4])}"
|
287
311
|
end
|
288
312
|
@src.rewind
|
289
313
|
tags = Cul::Image::Properties::Exif.process_file(srcfile)
|
@@ -16,7 +16,7 @@ module Properties
|
|
16
16
|
buf = ''
|
17
17
|
src.read(2,buf)
|
18
18
|
magic_bytes << buf
|
19
|
-
case magic_bytes
|
19
|
+
case magic_bytes.unpack('C*')
|
20
20
|
when Cul::Image::Magic::BMP
|
21
21
|
result = Cul::Image::Properties::Bmp.new(src)
|
22
22
|
when Cul::Image::Magic::JPEG
|
@@ -26,7 +26,7 @@ module Properties
|
|
26
26
|
if result.nil?
|
27
27
|
src.read(2,buf)
|
28
28
|
magic_bytes << buf
|
29
|
-
case magic_bytes
|
29
|
+
case magic_bytes.unpack('C*')
|
30
30
|
when Cul::Image::Magic::TIFF_MOTOROLA_BE
|
31
31
|
result = Cul::Image::Properties::Tiff.new(src)
|
32
32
|
when Cul::Image::Magic::TIFF_INTEL_LE
|
@@ -38,10 +38,10 @@ module Properties
|
|
38
38
|
if result.nil?
|
39
39
|
src.read(4,buf)
|
40
40
|
magic_bytes << buf
|
41
|
-
if magic_bytes == Cul::Image::Magic::PNG
|
41
|
+
if magic_bytes.unpack('C*') == Cul::Image::Magic::PNG
|
42
42
|
result = Cul::Image::Properties::Png.new(src)
|
43
43
|
else
|
44
|
-
puts magic_bytes.unpack('H2H2H2H2H2H2H2H2').inspect
|
44
|
+
puts "Unknown magic bytes: " + magic_bytes.unpack('H2H2H2H2H2H2H2H2').inspect
|
45
45
|
end
|
46
46
|
end
|
47
47
|
return result
|
metadata
CHANGED
@@ -1,77 +1,86 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: cul_image_props
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 1
|
9
|
-
- 2
|
10
|
-
version: 0.1.2
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Benjamin Armintor
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
12
|
+
date: 2012-06-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: nokogiri
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
22
23
|
prerelease: false
|
23
|
-
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
25
|
none: false
|
25
|
-
requirements:
|
26
|
-
- -
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
34
38
|
type: :development
|
35
|
-
version_requirements: *id001
|
36
|
-
- !ruby/object:Gem::Dependency
|
37
|
-
name: mocha
|
38
39
|
prerelease: false
|
39
|
-
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
41
|
none: false
|
41
|
-
requirements:
|
42
|
-
- -
|
43
|
-
- !ruby/object:Gem::Version
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
50
54
|
type: :development
|
51
|
-
version_requirements: *id002
|
52
|
-
- !ruby/object:Gem::Dependency
|
53
|
-
name: ruby-debug
|
54
55
|
prerelease: false
|
55
|
-
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
56
57
|
none: false
|
57
|
-
requirements:
|
58
|
-
- -
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: mocha
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.9.8
|
64
70
|
type: :development
|
65
|
-
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.9.8
|
66
78
|
description: Library for extracting basic image properties
|
67
79
|
email: armintor@gmail.com
|
68
80
|
executables: []
|
69
|
-
|
70
81
|
extensions: []
|
71
|
-
|
72
82
|
extra_rdoc_files: []
|
73
|
-
|
74
|
-
files:
|
83
|
+
files:
|
75
84
|
- lib/cul_image_props/image/magic.rb
|
76
85
|
- lib/cul_image_props/image/properties/exif/constants.rb
|
77
86
|
- lib/cul_image_props/image/properties/exif/types.rb
|
@@ -83,36 +92,26 @@ files:
|
|
83
92
|
- lib/cul_image_props.rb
|
84
93
|
homepage:
|
85
94
|
licenses: []
|
86
|
-
|
87
95
|
post_install_message:
|
88
96
|
rdoc_options: []
|
89
|
-
|
90
|
-
require_paths:
|
97
|
+
require_paths:
|
91
98
|
- lib
|
92
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
100
|
none: false
|
94
|
-
requirements:
|
95
|
-
- -
|
96
|
-
- !ruby/object:Gem::Version
|
97
|
-
|
98
|
-
|
99
|
-
- 0
|
100
|
-
version: "0"
|
101
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
106
|
none: false
|
103
|
-
requirements:
|
104
|
-
- -
|
105
|
-
- !ruby/object:Gem::Version
|
106
|
-
|
107
|
-
segments:
|
108
|
-
- 0
|
109
|
-
version: "0"
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
110
111
|
requirements: []
|
111
|
-
|
112
112
|
rubyforge_project:
|
113
|
-
rubygems_version: 1.8.
|
113
|
+
rubygems_version: 1.8.24
|
114
114
|
signing_key:
|
115
115
|
specification_version: 3
|
116
116
|
summary: Library for extracting basic image properties
|
117
117
|
test_files: []
|
118
|
-
|