iptc 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ GPL V2, (c) 2006 Pierre Baillet, (c) 2010 Jens Kraemer
2
+
3
+ Original code from http://raa.ruby-lang.org/project/jpeg-jfif-iptc/
4
+
5
+ Gemified, reorganized and modified in 2010 by Jens Kraemer.
@@ -0,0 +1,11 @@
1
+ Ruby IPTC metadata parser
2
+
3
+ This library can be used to read the metadata of a jpeg file.
4
+
5
+ It reads the IPTC informations embedded and allows to access this information
6
+ Read IPTC::JPEG::Image for more information.
7
+
8
+ The IPTC library parses the APP13 JPEG Tag and generates a hash of properties for a given file.
9
+
10
+ Original code from http://raa.ruby-lang.org/project/jpeg-jfif-iptc/
11
+ Gemified, reorganized and modified in 2010 by Jens Kraemer.
@@ -0,0 +1,3 @@
1
+ require 'iptc/jpeg/image'
2
+ require 'iptc/jpeg/markers'
3
+
@@ -0,0 +1,100 @@
1
+ require 'logger'
2
+
3
+ require 'iptc/multiple_hash'
4
+ require 'iptc/jpeg/marker_headers'
5
+
6
+ module IPTC
7
+ module JPEG
8
+ class Image
9
+ attr_reader :values
10
+ # creates a JPEG image from a file and does a "quick" load (Only the metadata
11
+ # are loaded, not the whole file).
12
+ def initialize filename, quick=true
13
+ @logger = Logger.new(STDOUT)
14
+ @logger.datetime_format = "%H:%M:%S"
15
+ @logger.level = $DEBUG?(Logger::DEBUG):(Logger::INFO)
16
+
17
+ @filename = filename
18
+ @position = 0
19
+ @content = File.open(@filename).binmode.read
20
+
21
+
22
+ if MARKERS[read(2)]!="SOI"
23
+ raise NotJPEGFileException.new("Not a JPEG file: #{@filename}")
24
+ end
25
+
26
+ @markers = Array.new()
27
+
28
+ begin
29
+
30
+ catch(:end_of_metadata) do
31
+ while true
32
+ @markers << read_marker
33
+ end
34
+ end
35
+
36
+ rescue Exception=>e
37
+ @logger.info "Exception in file #{@filename}:\n"+e.to_s
38
+ raise e
39
+ end
40
+ # Markers all read
41
+ # move back
42
+ seek(-2)
43
+
44
+ # in full mode, read the rest
45
+ if !quick
46
+ @data = read_rest
47
+ end
48
+
49
+ @values = MultipleHash.new
50
+
51
+ @markers.each do |marker|
52
+ # puts "processing marker: #{marker.inspect}"
53
+ marker.parse
54
+ # puts marker.valid?
55
+ @values.add(marker, marker.values)
56
+ end
57
+ end
58
+
59
+ def read(count)
60
+ @position += count
61
+ return @content[@position-count...@position]
62
+ end
63
+ def seek(count)
64
+ @position += count
65
+ end
66
+
67
+ def l message
68
+ @logger.debug message
69
+ end
70
+
71
+ # write the image to the disk
72
+ def write filename
73
+ f = File.open(filename,"wb+")
74
+ f.print "\xFF\xD8"
75
+
76
+ @markers.each do |marker|
77
+ f.print marker.to_binary
78
+ end
79
+ f.print @data.to_binary
80
+ f.close
81
+
82
+ end
83
+
84
+ def read_marker
85
+ type = read(2)
86
+ # finished reading all the metadata
87
+ throw :end_of_metadata if MARKERS[type]=='SOS'
88
+ size = read(2)
89
+ data = read(size.unpack('n')[0]-2)
90
+
91
+ return Marker.NewMarker(MARKERS[type], type+size+data, @logger)
92
+ end
93
+ def read_rest
94
+ rest = @content[@position..-1]
95
+ return Marker.new("BIN",rest)
96
+ end
97
+
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,97 @@
1
+ require 'logger'
2
+
3
+ module IPTC
4
+ module JPEG
5
+ # == Marker
6
+ # A Jpeg marker, generic class used by all others markers
7
+ class Marker
8
+ attr_reader :prefix
9
+ attr_reader :values
10
+
11
+ @@missing = []
12
+
13
+ # The NewMarker constructor method.
14
+ # Inspect the object space in order to find something usable later
15
+ def Marker.NewMarker(type,data, logger)
16
+ marker_class = "#{type}Marker"
17
+ if JPEG::Markers.constants.include?(marker_class.to_sym)
18
+ return JPEG::Markers.const_get(marker_class).new(type, data)
19
+ else
20
+ if !@@missing.include?(type)
21
+ logger.debug "Marker #{type+"Marker"} not found." if logger!=nil
22
+ @@missing << type
23
+ end
24
+ end
25
+ return Marker.new(type,data)
26
+ end
27
+
28
+ def l message
29
+ @logger.debug message
30
+ end
31
+
32
+ # binary serialization
33
+ # transform the marker to its final form
34
+ def to_binary data=nil
35
+ if data!=nil
36
+ return @type+[data.length+2].pack('n')+data
37
+ else
38
+ return @original_content
39
+ end
40
+ end
41
+
42
+ def initialize(type,data)
43
+ @logger = Logger.new(STDOUT)
44
+ @logger.datetime_format = "%H:%M:%S"
45
+ @logger.level = $DEBUG?(Logger::DEBUG):(Logger::INFO)
46
+
47
+ @original_content = data
48
+
49
+ @content = StringIO.new(data)
50
+ @type = @content.read(2)
51
+ @size = @content.read(2).unpack('n')[0]-2
52
+
53
+ if !valid?
54
+ raise InvalidBlockException.new("In block #{self.class}: invalid marker\n#{@content.read(20)}")
55
+ end
56
+
57
+ @prefix = self.class.to_s
58
+
59
+ @prefix = @prefix[@prefix.rindex("::")+2..-7]
60
+ if @prefix==""
61
+ @prefix= 'Marker'
62
+ end
63
+
64
+ @values = Hash.new
65
+ end
66
+ def read count
67
+ return @content.read(count)
68
+ end
69
+ def valid?
70
+ return true
71
+ end
72
+
73
+
74
+ # parse the data
75
+ def parse
76
+ return []
77
+ end
78
+
79
+ # returns the available properties for the given tag
80
+ def properties
81
+ return []
82
+ end
83
+ end
84
+
85
+ # This is not a JPEG file.
86
+ class NotJPEGFileException < Exception; end
87
+
88
+ # The end of the metadata has occured too early
89
+ class EndOfMetaDataException < Exception; end
90
+
91
+ # The block is invalide
92
+ class InvalidBlockException < Exception; end
93
+ end
94
+
95
+
96
+
97
+ end
@@ -0,0 +1,67 @@
1
+ module IPTC
2
+ module JPEG
3
+ MARKERS = {
4
+ "\xFF\xD8" => 'SOI',
5
+ "\xFF\xc0" => 'SOF0',
6
+ "\xFF\xc1" => 'SOF1',
7
+ "\xFF\xc2" => 'SOF2',
8
+ "\xFF\xc3" => 'SOF3',
9
+
10
+ "\xFF\xc5" => 'SOF5',
11
+ "\xFF\xc6" => 'SOF6',
12
+ "\xFF\xc7" => 'SOF7',
13
+
14
+ "\xFF\xc8" => 'JPG',
15
+ "\xFF\xc9" => 'SOF9',
16
+ "\xFF\xca" => 'SOF10',
17
+ "\xFF\xcb" => 'SOF11',
18
+
19
+ "\xFF\xcd" => 'SOF13',
20
+ "\xFF\xce" => 'SOF14',
21
+ "\xFF\xcf" => 'SOF15',
22
+
23
+ "\xFF\xc4" => 'DHT',
24
+
25
+ "\xFF\xcc" => 'DAC',
26
+
27
+ "\xFF\xd0" => 'RST0',
28
+ "\xFF\xd1" => 'RST1',
29
+ "\xFF\xd2" => 'RST2',
30
+ "\xFF\xd3" => 'RST3',
31
+ "\xFF\xd4" => 'RST4',
32
+ "\xFF\xd5" => 'RST5',
33
+ "\xFF\xd6" => 'RST6',
34
+ "\xFF\xd7" => 'RST7',
35
+
36
+ "\xFF\xd8" => 'SOI',
37
+ "\xFF\xd9" => 'EOI',
38
+ "\xFF\xda" => 'SOS',
39
+ "\xFF\xdb" => 'DQT',
40
+ "\xFF\xdc" => 'DNL',
41
+ "\xFF\xdd" => 'DRI',
42
+ "\xFF\xde" => 'DHP',
43
+ "\xFF\xdf" => 'EXP',
44
+
45
+ "\xFF\xe0" => 'APP0',
46
+ "\xFF\xe1" => 'APP1',
47
+ "\xFF\xe2" => 'APP2',
48
+ "\xFF\xe3" => 'APP3',
49
+ "\xFF\xe4" => 'APP4',
50
+ "\xFF\xe5" => 'APP5',
51
+ "\xFF\xe6" => 'APP6',
52
+ "\xFF\xe7" => 'APP7',
53
+ "\xFF\xe8" => 'APP8',
54
+ "\xFF\xe9" => 'APP9',
55
+ "\xFF\xea" => 'APP10',
56
+ "\xFF\xeb" => 'APP11',
57
+ "\xFF\xec" => 'APP12',
58
+ "\xFF\xed" => 'APP13',
59
+ "\xFF\xee" => 'APP14',
60
+ "\xFF\xef" => 'APP15',
61
+ "\xFF\xf0" => 'JPG0',
62
+ "\xFF\xfd" => 'JPG13',
63
+ "\xFF\xfe" => 'COM',
64
+ "\xFF\x01" => 'TEM'
65
+ }
66
+ end
67
+ end
@@ -0,0 +1,190 @@
1
+ require 'stringio'
2
+
3
+ require 'iptc/marker'
4
+ require 'iptc/marker_nomenclature'
5
+ require 'iptc/jpeg/marker'
6
+
7
+ module IPTC
8
+
9
+ module JPEG
10
+ # == Markers
11
+ # All the known JPEG markers
12
+ module Markers
13
+ # == SOIMarker
14
+ # The Start Of Image Marker
15
+ class SOIMarker < Marker
16
+ def valid?
17
+ return read(5)=="JFIF\0"
18
+ end
19
+ end
20
+ # == APP1Marker
21
+ # The APP1 Marker, Exif container
22
+ class APP1Marker < Marker
23
+ def valid?
24
+ xap = 'http://ns.adobe.com/xap/1.0/'
25
+ header = read(xap.size)
26
+ return header == xap || header.start_with?("Exif\0\0")
27
+ end
28
+ end
29
+ # == COMMarker
30
+ # The COM Marker, contains comments
31
+ class COMMarker < Marker
32
+ attr_reader :content
33
+ def parse
34
+ l "COM Marker Parsed"
35
+ @content = read(@size)
36
+ @dirty = false
37
+
38
+ @values['COM/COM']=@content
39
+ end
40
+ # def []=(key,value)
41
+ # if @content != value
42
+ # @content = value
43
+ # @dirty = true
44
+ # end
45
+ # end
46
+ def [](item)
47
+ return @content
48
+ end
49
+ end
50
+ # == The APP13Marker
51
+ # The APP13 marker, know as the IPTC Marker
52
+ # See also the IPTC::MarkerNomenclature.
53
+ class APP13Marker < Marker
54
+ def initialize(type, data)
55
+ @header = "Photoshop 3.0\0008BIM"
56
+
57
+ super(type, data)
58
+ @prefix = "iptc"
59
+
60
+ end
61
+ def valid?
62
+ return read(@header.length)==@header
63
+ end
64
+
65
+ def parse
66
+ l "APP13 marker parsed"
67
+ @markers = Array.new
68
+
69
+
70
+ @bim_type = read(2)
71
+ @bim_dummy = read(4)
72
+ size = read(2)
73
+
74
+ content = StringIO.new(read(size.unpack('n')[0]))
75
+
76
+ while !content.eof?
77
+
78
+ header = content.read(2)
79
+
80
+ # http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html
81
+ case header
82
+ when "\x1c\x01"
83
+ # skip the envelope
84
+ while !content.eof?
85
+ if content.read(1) == "\x1c"
86
+ content.pos = content.pos - 1
87
+ break
88
+ end
89
+ end
90
+ when "\x1c\x02"
91
+
92
+ type = content.read(1).unpack('c')[0]
93
+ size = content.read(2)
94
+ value = content.read(size.unpack('n')[0])
95
+
96
+ l "Found marker #{type}"
97
+ marker = IPTC::Marker.new(type, value)
98
+ @values[@prefix+"/"+IPTC::MarkerNomenclature.markers(type.to_i).name] ||= []
99
+ @values[@prefix+"/"+IPTC::MarkerNomenclature.markers(type.to_i).name] << value
100
+ @markers << marker
101
+
102
+ else
103
+ # raise InvalidBlockException.new("Invalid BIM segment #{header.inspect} in marker\n#{@original_content.inspect}")
104
+ end
105
+ end
106
+ return @values
107
+ end
108
+
109
+ def [](item)
110
+ return @values[item]
111
+ end
112
+ def to_binary
113
+ marker = ""
114
+ @markers.each do |value|
115
+ marker += value.to_binary
116
+ end
117
+
118
+ marker = @header+@bim_type+@bim_dummy+[marker.length].pack('n')+marker
119
+
120
+ # build the complete marker
121
+ marker = super(marker)
122
+
123
+ return marker
124
+ end
125
+ def properties
126
+ return IPTC::TAGS.values.sort
127
+ end
128
+ def set(property, value)
129
+ numerical_tag = IPTC::TAGS.index(property)
130
+ if numerical_tag!=nil
131
+ else
132
+ throw InvalidPropertyException.new("Invalid property #{property} for IPTC marker")
133
+ end
134
+ marker = IPTC::Marker.new(numerical_tag, value)
135
+ @markers << marker
136
+ end
137
+ end
138
+ class InvalidPropertyException < Exception
139
+ end
140
+ # == The APP0Marker
141
+ # Contains some useful JFIF informations about the current
142
+ # image.
143
+ class APP0Marker < Marker
144
+ def initialize type, data
145
+ super type, data
146
+ end
147
+ def valid?
148
+ if read(5)!="JFIF\0"
149
+ return false
150
+ end
151
+ return true
152
+ end
153
+ def parse
154
+
155
+ @values = {
156
+ 'APP1/revision'=>read(2).unpack('n')[0],
157
+ 'APP1/unit' => read(1),
158
+ 'APP1/xdensity' => read(2).unpack('n')[0],
159
+ 'APP1/ydensity' => read(2).unpack('n')[0],
160
+ 'APP1/xthumbnail' => read(1).unpack('c')[0],
161
+ 'APP1/ythumbnail' => read(1).unpack('c')[0]
162
+ }
163
+ end
164
+ def [](item)
165
+ return @values[item]
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ if $0 == __FILE__
173
+ if ARGV[0]==nil
174
+ puts "No file given. Aborting."
175
+ exit
176
+ end
177
+
178
+ require 'iptc/jpeg/image'
179
+
180
+
181
+ # read the image
182
+ im = IPTC::JPEG::Image.new(ARGV[0])
183
+
184
+ puts "Done reading #{ARGV[0]}"
185
+
186
+ im.values.each do |item|
187
+ puts "#{item.key}\t#{item.value}"
188
+ end
189
+
190
+ end
@@ -0,0 +1,18 @@
1
+ module IPTC
2
+ # == Marker
3
+ # A simple IPTC Marker
4
+ class Marker
5
+ attr_accessor :value
6
+ def initialize(type, value)
7
+ @type = type
8
+ @value = value
9
+ end
10
+ def to_s
11
+ self.value
12
+ end
13
+ def to_binary
14
+ "\x1c\x02"+[@type, @value.length].pack('cn')+@value
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,49 @@
1
+ require 'singleton'
2
+ require 'iptc/marker'
3
+
4
+ module IPTC
5
+ class MarkerNomenclature
6
+ include Singleton
7
+ def MarkerNomenclature.markers(id)
8
+ return MarkerNomenclature.instance.markers(id)
9
+ end
10
+
11
+ def markers(id)
12
+ if @markers.has_key?(id)
13
+ return @markers[id]
14
+ else
15
+ return @markers[-1]
16
+ end
17
+ end
18
+
19
+ def populate
20
+ @markers = {}
21
+ begin
22
+ fullpath = nil
23
+
24
+ [File.dirname(__FILE__), $:].flatten.each { |path|
25
+ break if File.exists?(fullpath = File.join(path, "iptc"))
26
+ }
27
+ content = File.open(fullpath).read
28
+ rescue Exception=>e
29
+ raise "Load failed for \"iptc\" file\nWith $:=#{$:.inspect}\n\n"+e
30
+ end
31
+ marker = Struct.new(:iid, :name, :description)
32
+
33
+ m = marker.new
34
+ m.name = "Unknow marker"
35
+ m.iid = -1
36
+ @markers[-1] = m
37
+
38
+ content.each_line do |line|
39
+ tags = line.split(/\t/)
40
+ m = marker.new
41
+ m[:name] = tags[0]
42
+ m[:description] = tags[1]
43
+ m[:iid] = tags[2].to_i
44
+ @markers[m.iid] = m
45
+ end
46
+ end
47
+ end
48
+ MarkerNomenclature.instance.populate
49
+ end
@@ -0,0 +1,54 @@
1
+ module IPTC
2
+
3
+
4
+ # a MultipleHash associate a String key, a Marker Object and a String value
5
+ # this object
6
+ class MultipleHash
7
+ def initialize()
8
+ @internal = Hash.new
9
+ end
10
+ def add marker, hash
11
+ hash.each do |key,value|
12
+ @internal[key] ||= []
13
+ @internal[key] << MultipleHashItem.new(marker,key,value)
14
+ end
15
+ end
16
+ def has_key?(key)
17
+ return @internal.has_key?(key)
18
+ end
19
+ # Returns one or more values for a given key
20
+ # if there is only one value, do not send an array back
21
+ # but send the value instead.
22
+ def [](key)
23
+ if has_key?(key)
24
+ if @internal[key].length == 1
25
+ return @internal[key][0]
26
+ else
27
+ return @internal[key]
28
+ end
29
+ end
30
+ end
31
+ def each
32
+ @internal.each do |key, values|
33
+ values.each do |value|
34
+ yield value
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ class MultipleHashItem
41
+ attr_reader :key
42
+ def initialize marker, key, value
43
+ @marker = marker
44
+ @key = key
45
+ end
46
+ def value=(value)
47
+ @marker[@key]=value
48
+ end
49
+ def value
50
+ return @marker[@key]
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,3 @@
1
+ module IPTC
2
+ VERSION = "0.0.2"
3
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: iptc
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 2
9
+ version: 0.0.2
10
+ platform: ruby
11
+ authors:
12
+ - Pierre Baillet
13
+ - Jens Kraemer
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-06 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Original code from http://raa.ruby-lang.org/project/jpeg-jfif-iptc/, gemified and updated by Jens Kraemer
23
+ email:
24
+ - jk@jkraemer.net
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files: []
30
+
31
+ files:
32
+ - lib/iptc/jpeg/image.rb
33
+ - lib/iptc/jpeg/marker.rb
34
+ - lib/iptc/jpeg/marker_headers.rb
35
+ - lib/iptc/jpeg/markers.rb
36
+ - lib/iptc/marker.rb
37
+ - lib/iptc/marker_nomenclature.rb
38
+ - lib/iptc/multiple_hash.rb
39
+ - lib/iptc/version.rb
40
+ - lib/iptc.rb
41
+ - LICENSE
42
+ - README.md
43
+ has_rdoc: true
44
+ homepage: http://github.com/jkraemer/ruby-iptc
45
+ licenses: []
46
+
47
+ post_install_message:
48
+ rdoc_options: []
49
+
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: -1405560905508476106
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ segments:
67
+ - 1
68
+ - 3
69
+ - 6
70
+ version: 1.3.6
71
+ requirements: []
72
+
73
+ rubyforge_project: ruby-iptc
74
+ rubygems_version: 1.3.7
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: Pure ruby IPTC metadata reader
78
+ test_files: []
79
+