iptc 0.0.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/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
+