iptcr 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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/LICENSE +21 -0
- data/README.md +27 -0
- data/lib/iptcr/iptc.rb +221 -0
- data/lib/iptcr/records.rb +819 -0
- data/lib/iptcr/version.rb +3 -0
- data/lib/iptcr.rb +15 -0
- data/test/iptc_test.rb +91 -0
- data/test/test_helper.rb +4 -0
- data.tar.gz.sig +2 -0
- metadata +117 -0
- metadata.gz.sig +1 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 801173393ff24fc468de4a0c0842f03f28058b4b
|
4
|
+
data.tar.gz: fceac79394ba9e6823b39228652e440a69a78415
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 038c87adeebe6b3047abfb4304145b2fca3344ccef31de92ff9ab1f93d68b5ca42a32199280a02bf8689fde9d464de544126a2f6ff384f97515c91b67c704e02
|
7
|
+
data.tar.gz: 612b8924a4456eb48d252be3f097dd10b75d5acb7d5b6943133691ea927178878eeb41ce72719c734fc9455f36f119c9b0fcceb2c9eeab9195b4e45b969354e8
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Samuel Cochran
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# IPTCR
|
2
|
+
|
3
|
+
[](https://travis-ci.org/sj26/iptcr)
|
4
|
+
|
5
|
+
IPTC Reader in Ruby. Parse IPTC data extracted from an image into rich data types and respecting string encodings.
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
Use something like imagemagick to extract the IPTC, then read it with this class:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
require "iptcr"
|
13
|
+
raw_iptc = `convert images/ian.jpg iptc:-`
|
14
|
+
iptc = IPTCR.parse(iptc)
|
15
|
+
iptc["ObjectName"] # => "Ian"
|
16
|
+
iptc.to_hash # => {"ObjectName" => "Ian", "ColorSequence" => 32, ...}
|
17
|
+
```
|
18
|
+
|
19
|
+
## Thanks
|
20
|
+
|
21
|
+
Inspired by [ExifTool][exiftool].
|
22
|
+
|
23
|
+
[exiftool]: http://www.sno.phy.queensu.ca/~phil/exiftool/
|
24
|
+
|
25
|
+
## License
|
26
|
+
|
27
|
+
MIT license, see [LICENSE](LICENSE).
|
data/lib/iptcr/iptc.rb
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
require "iptcr"
|
2
|
+
require "iptcr/records"
|
3
|
+
|
4
|
+
module IPTCR
|
5
|
+
class Malformed < RuntimeError; end
|
6
|
+
|
7
|
+
# # IPTC
|
8
|
+
#
|
9
|
+
# ## Notes
|
10
|
+
#
|
11
|
+
# IPTC metadata is a series of records. Records contain datasets. Each
|
12
|
+
# dataset contains a data field, which is a particular value for that
|
13
|
+
# dataset. Repeated datasets represent mulitple fields for the same dataset,
|
14
|
+
# like for list data.
|
15
|
+
#
|
16
|
+
# ## References
|
17
|
+
#
|
18
|
+
# * https://www.iptc.org/std/photometadata/2008/specification/IPTC-PhotoMetadata-2008.pdf
|
19
|
+
# * https://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf
|
20
|
+
# * https://en.wikipedia.org/wiki/ISO/IEC_2022#ISO.2FIEC_2022_character_sets
|
21
|
+
class IPTC
|
22
|
+
attr_reader :fields
|
23
|
+
|
24
|
+
# IPTC IIM specifics the default encoding is ISO646/4873, which is roughly ASCII
|
25
|
+
DEFAULT_ENCODING = Encoding::ASCII
|
26
|
+
|
27
|
+
def initialize(io, length:)
|
28
|
+
@fields = []
|
29
|
+
read_fields(io, length: length)
|
30
|
+
end
|
31
|
+
|
32
|
+
def encoding
|
33
|
+
@encoding || DEFAULT_ENCODING
|
34
|
+
end
|
35
|
+
|
36
|
+
def [](name)
|
37
|
+
field_values[name]
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_hash
|
41
|
+
field_values
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def read_fields(io, length:)
|
47
|
+
unless length.nil?
|
48
|
+
start = io.tell
|
49
|
+
finish = start + length
|
50
|
+
end
|
51
|
+
|
52
|
+
until io.eof? || (!length.nil? && io.tell >= finish)
|
53
|
+
read_field(io)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
MARKER = 0x1c
|
58
|
+
|
59
|
+
def read_field(io)
|
60
|
+
marker = io.getbyte
|
61
|
+
unless marker == MARKER
|
62
|
+
raise Malformed, "Expected marker byte, got: #{marker}"
|
63
|
+
end
|
64
|
+
|
65
|
+
record_number = io.getbyte
|
66
|
+
|
67
|
+
dataset_number = io.getbyte
|
68
|
+
|
69
|
+
# Length can be a variable integer
|
70
|
+
length = io.read(2).unpack("S>").first
|
71
|
+
if length & 0x8000 == 0x8000
|
72
|
+
size = length & 0x7fff
|
73
|
+
length = 0
|
74
|
+
size.times do
|
75
|
+
length = (length << 8) + io.readbyte
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
start = io.tell
|
80
|
+
finish = start + length
|
81
|
+
|
82
|
+
field = Field.new(record_number: record_number, dataset_number: dataset_number, data: io.read(length), encoding: encoding)
|
83
|
+
@fields << field
|
84
|
+
|
85
|
+
# IPTC specifies that records must be ordered. This dataset is in the
|
86
|
+
# first possible record, so later records (like the main application
|
87
|
+
# record) should all receive this encoding.
|
88
|
+
#
|
89
|
+
# There is a list of encodings here:
|
90
|
+
# https://en.wikipedia.org/wiki/ISO/IEC_2022#ISO.2FIEC_2022_character_sets
|
91
|
+
#
|
92
|
+
# Ruby doesn't support a bunch of them, so we really only support UTF-8
|
93
|
+
# and fall back to ASCII.
|
94
|
+
if field.dataset? and field.dataset_name == "CodedCharacterSet"
|
95
|
+
case field.value
|
96
|
+
when "\x1b%G"
|
97
|
+
@encoding = Encoding::UTF_8
|
98
|
+
else
|
99
|
+
EXIFR.logger.warn { "IPTC: Unknown codec character set: #{field.value.inspect}" }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
unless io.tell == finish
|
104
|
+
io.seek(finish)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def field_values
|
109
|
+
@field_values ||= @fields.each_with_object({}) do |field, index|
|
110
|
+
if field.dataset?
|
111
|
+
if field.dataset[:list]
|
112
|
+
(index[field.dataset_name] ||= []) << field.value
|
113
|
+
else
|
114
|
+
index[field.dataset_name] = field.value
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class Field
|
121
|
+
def initialize(record_number:, dataset_number:, data:, encoding:)
|
122
|
+
@record_number = record_number
|
123
|
+
@dataset_number = dataset_number
|
124
|
+
@data = data
|
125
|
+
@encoding = encoding
|
126
|
+
end
|
127
|
+
|
128
|
+
attr_reader :record_number
|
129
|
+
|
130
|
+
def record?
|
131
|
+
RECORDS.has_key? record_number
|
132
|
+
end
|
133
|
+
|
134
|
+
def record
|
135
|
+
RECORDS[record_number]
|
136
|
+
end
|
137
|
+
|
138
|
+
def record_name
|
139
|
+
if record?
|
140
|
+
record[:name]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
attr_reader :dataset_number, :data
|
145
|
+
|
146
|
+
def dataset?
|
147
|
+
if record?
|
148
|
+
record[:datasets] && record[:datasets].has_key?(dataset_number)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def dataset
|
153
|
+
if dataset?
|
154
|
+
record[:datasets][dataset_number]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def dataset_name
|
159
|
+
if dataset?
|
160
|
+
dataset[:name]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def dataset_type
|
165
|
+
if dataset?
|
166
|
+
dataset[:type]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
attr_reader :data
|
171
|
+
|
172
|
+
attr_reader :encoding
|
173
|
+
|
174
|
+
def value
|
175
|
+
case dataset_type
|
176
|
+
when "string"
|
177
|
+
# Record number 1 is always the default encoding
|
178
|
+
if record_number == 1
|
179
|
+
@data.force_encoding(DEFAULT_ENCODING)
|
180
|
+
# Records 2-6 and 8 respect tagged encoding
|
181
|
+
elsif (2..6).include?(record_number) || record_number == 8
|
182
|
+
@data.force_encoding(encoding)
|
183
|
+
# Other behaviour is undefined
|
184
|
+
else
|
185
|
+
@data
|
186
|
+
end
|
187
|
+
when "digits"
|
188
|
+
@data
|
189
|
+
when "int8u"
|
190
|
+
@data.unpack("C").first
|
191
|
+
when "int16u"
|
192
|
+
@data.unpack("S").first
|
193
|
+
when "int32u"
|
194
|
+
@data.unpack("L").first
|
195
|
+
else
|
196
|
+
@data
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def inspect
|
201
|
+
"#<%s:0x%014x record=%s dataset=%s %p>" % [self.class, object_id, inspect_record, inspect_dataset, value]
|
202
|
+
end
|
203
|
+
|
204
|
+
private def inspect_record
|
205
|
+
if record?
|
206
|
+
record_name
|
207
|
+
else
|
208
|
+
"unknown:0x%x" % [record_number]
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
private def inspect_dataset
|
213
|
+
if dataset?
|
214
|
+
dataset_name
|
215
|
+
else
|
216
|
+
"unknown:0x%x" % [dataset_number]
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,819 @@
|
|
1
|
+
# Descriptions of records and their datasets.
|
2
|
+
# Copied from ExifTool
|
3
|
+
IPTCR::RECORDS = {
|
4
|
+
0x01 => {
|
5
|
+
name: 'IPTCEnvelope',
|
6
|
+
datasets: {
|
7
|
+
0x00 => {
|
8
|
+
name: 'EnvelopeRecordVersion',
|
9
|
+
type: 'int16u',
|
10
|
+
},
|
11
|
+
0x05 => {
|
12
|
+
name: 'Destination',
|
13
|
+
list: true,
|
14
|
+
type: 'string',
|
15
|
+
size: (0..1024),
|
16
|
+
},
|
17
|
+
0x14 => {
|
18
|
+
name: 'FileFormat',
|
19
|
+
type: 'int16u',
|
20
|
+
},
|
21
|
+
0x16 => {
|
22
|
+
name: 'FileVersion',
|
23
|
+
type: 'int16u',
|
24
|
+
},
|
25
|
+
0x1e => {
|
26
|
+
name: 'ServiceIdentifier',
|
27
|
+
type: 'string',
|
28
|
+
size: (0..10),
|
29
|
+
},
|
30
|
+
0x28 => {
|
31
|
+
name: 'EnvelopeNumber',
|
32
|
+
type: 'digits',
|
33
|
+
size: 8,
|
34
|
+
},
|
35
|
+
0x32 => {
|
36
|
+
name: 'ProductID',
|
37
|
+
list: true,
|
38
|
+
type: 'string',
|
39
|
+
size: (0..32),
|
40
|
+
},
|
41
|
+
0x3c => {
|
42
|
+
name: 'EnvelopePriority',
|
43
|
+
type: 'digits',
|
44
|
+
size: 1,
|
45
|
+
print_conv: {
|
46
|
+
0x00 => '0 (reserved)',
|
47
|
+
0x01 => '1 (most urgent)',
|
48
|
+
0x02 => 2,
|
49
|
+
0x03 => 3,
|
50
|
+
0x04 => 4,
|
51
|
+
0x05 => '5 (normal urgency)',
|
52
|
+
0x06 => 6,
|
53
|
+
0x07 => 7,
|
54
|
+
0x08 => '8 (least urgent)',
|
55
|
+
0x09 => '9 (user-defined priority)',
|
56
|
+
},
|
57
|
+
},
|
58
|
+
0x46 => {
|
59
|
+
name: 'DateSent',
|
60
|
+
type: 'digits',
|
61
|
+
size: 8,
|
62
|
+
shift: 'Time',
|
63
|
+
value_conv: 'Image::ExifTool::Exif::ExifDate($val)',
|
64
|
+
value_conv_inv: 'Image::ExifTool::IPTC::IptcDate($val)',
|
65
|
+
print_conv_inv: 'Image::ExifTool::IPTC::InverseDateOrTime($val)',
|
66
|
+
},
|
67
|
+
0x50 => {
|
68
|
+
name: 'TimeSent',
|
69
|
+
type: 'string',
|
70
|
+
size: 11,
|
71
|
+
shift: 'Time',
|
72
|
+
value_conv: 'Image::ExifTool::Exif::ExifTime($val)',
|
73
|
+
value_conv_inv: 'Image::ExifTool::IPTC::IptcTime($val)',
|
74
|
+
print_conv_inv: 'Image::ExifTool::IPTC::InverseDateOrTime($val)',
|
75
|
+
},
|
76
|
+
0x5a => {
|
77
|
+
name: 'CodedCharacterSet',
|
78
|
+
notes: %{
|
79
|
+
values are entered in the form "ESC X Y[, ...]". The escape sequence for
|
80
|
+
UTF-8 character coding is "ESC % G", but this is displayed as "UTF8" for
|
81
|
+
convenience. Either string may be used when writing. The value of this tag
|
82
|
+
affects the decoding of string values in the Application and NewsPhoto
|
83
|
+
records. This tag is marked as "unsafe" to prevent it from being copied by
|
84
|
+
default in a group operation because existing tags in the destination image
|
85
|
+
may use a different encoding. When creating a new IPTC record from scratch,
|
86
|
+
it is suggested that this be set to "UTF8" if special characters are a
|
87
|
+
possibility
|
88
|
+
},
|
89
|
+
protected: 1,
|
90
|
+
type: 'string',
|
91
|
+
size: (0..32),
|
92
|
+
# convert ISO 2022 escape sequences to a more readable format
|
93
|
+
print_conv: 'PrintCodedCharset',
|
94
|
+
print_conv_inv: 'PrintInvCodedCharset',
|
95
|
+
},
|
96
|
+
0x64 => {
|
97
|
+
name: 'UniqueObjectName',
|
98
|
+
type: 'string',
|
99
|
+
size: (14..80),
|
100
|
+
},
|
101
|
+
0x78 => {
|
102
|
+
name: 'ARMIdentifier',
|
103
|
+
type: 'int16u',
|
104
|
+
},
|
105
|
+
0x7a => {
|
106
|
+
name: 'ARMVersion',
|
107
|
+
type: 'int16u',
|
108
|
+
},
|
109
|
+
},
|
110
|
+
},
|
111
|
+
0x02 => {
|
112
|
+
name: 'IPTCApplication',
|
113
|
+
datasets: {
|
114
|
+
0x00 => {
|
115
|
+
name: 'ApplicationRecordVersion',
|
116
|
+
type: 'int16u',
|
117
|
+
mandatory: 1,
|
118
|
+
},
|
119
|
+
0x03 => {
|
120
|
+
name: 'ObjectTypeReference',
|
121
|
+
type: 'string',
|
122
|
+
size: (3..67),
|
123
|
+
},
|
124
|
+
0x04 => {
|
125
|
+
name: 'ObjectAttributeReference',
|
126
|
+
list: true,
|
127
|
+
type: 'string',
|
128
|
+
size: (4..68),
|
129
|
+
},
|
130
|
+
0x05 => {
|
131
|
+
name: 'ObjectName',
|
132
|
+
type: 'string',
|
133
|
+
size: (0..64),
|
134
|
+
},
|
135
|
+
0x07 => {
|
136
|
+
name: 'EditStatus',
|
137
|
+
type: 'string',
|
138
|
+
size: (0..64),
|
139
|
+
},
|
140
|
+
0x08 => {
|
141
|
+
name: 'EditorialUpdate',
|
142
|
+
type: 'digits',
|
143
|
+
size: 2,
|
144
|
+
print_conv: {
|
145
|
+
'01' => 'Additional language',
|
146
|
+
},
|
147
|
+
},
|
148
|
+
0x0a => {
|
149
|
+
name: 'Urgency',
|
150
|
+
type: 'digits',
|
151
|
+
size: 1,
|
152
|
+
print_conv: {
|
153
|
+
0x00 => '0 (reserved)',
|
154
|
+
0x01 => '1 (most urgent)',
|
155
|
+
0x02 => 2,
|
156
|
+
0x03 => 3,
|
157
|
+
0x04 => 4,
|
158
|
+
0x05 => '5 (normal urgency)',
|
159
|
+
0x06 => 6,
|
160
|
+
0x07 => 7,
|
161
|
+
0x08 => '8 (least urgent)',
|
162
|
+
0x09 => '9 (user-defined priority)',
|
163
|
+
},
|
164
|
+
},
|
165
|
+
0x0c => {
|
166
|
+
name: 'SubjectReference',
|
167
|
+
list: true,
|
168
|
+
type: 'string',
|
169
|
+
size: (13..236),
|
170
|
+
},
|
171
|
+
0x0f => {
|
172
|
+
name: 'Category',
|
173
|
+
type: 'string',
|
174
|
+
size: (0..3),
|
175
|
+
},
|
176
|
+
0x14 => {
|
177
|
+
name: 'SupplementalCategories',
|
178
|
+
list: true,
|
179
|
+
type: 'string',
|
180
|
+
size: (0..32),
|
181
|
+
},
|
182
|
+
0x16 => {
|
183
|
+
name: 'FixtureIdentifier',
|
184
|
+
type: 'string',
|
185
|
+
size: (0..32),
|
186
|
+
},
|
187
|
+
0x19 => {
|
188
|
+
name: 'Keywords',
|
189
|
+
list: true,
|
190
|
+
type: 'string',
|
191
|
+
size: (0..64),
|
192
|
+
},
|
193
|
+
0x1a => {
|
194
|
+
name: 'ContentLocationCode',
|
195
|
+
list: true,
|
196
|
+
type: 'string',
|
197
|
+
size: 3,
|
198
|
+
},
|
199
|
+
0x1b => {
|
200
|
+
name: 'ContentLocationName',
|
201
|
+
list: true,
|
202
|
+
type: 'string',
|
203
|
+
size: (0..64),
|
204
|
+
},
|
205
|
+
0x1e => {
|
206
|
+
name: 'ReleaseDate',
|
207
|
+
type: 'digits',
|
208
|
+
size: 8,
|
209
|
+
shift: 'Time',
|
210
|
+
value_conv: 'Image::ExifTool::Exif::ExifDate($val)',
|
211
|
+
value_conv_inv: 'Image::ExifTool::IPTC::IptcDate($val)',
|
212
|
+
print_conv_inv: 'Image::ExifTool::IPTC::InverseDateOrTime($val)',
|
213
|
+
},
|
214
|
+
0x23 => {
|
215
|
+
name: 'ReleaseTime',
|
216
|
+
type: 'string',
|
217
|
+
size: 11,
|
218
|
+
shift: 'Time',
|
219
|
+
value_conv: 'Image::ExifTool::Exif::ExifTime($val)',
|
220
|
+
value_conv_inv: 'Image::ExifTool::IPTC::IptcTime($val)',
|
221
|
+
print_conv_inv: 'Image::ExifTool::IPTC::InverseDateOrTime($val)',
|
222
|
+
},
|
223
|
+
0x25 => {
|
224
|
+
name: 'ExpirationDate',
|
225
|
+
type: 'digits',
|
226
|
+
size: 8,
|
227
|
+
shift: 'Time',
|
228
|
+
value_conv: 'Image::ExifTool::Exif::ExifDate($val)',
|
229
|
+
value_conv_inv: 'Image::ExifTool::IPTC::IptcDate($val)',
|
230
|
+
print_conv_inv: 'Image::ExifTool::IPTC::InverseDateOrTime($val)',
|
231
|
+
},
|
232
|
+
0x26 => {
|
233
|
+
name: 'ExpirationTime',
|
234
|
+
type: 'string',
|
235
|
+
size: 11,
|
236
|
+
shift: 'Time',
|
237
|
+
value_conv: 'Image::ExifTool::Exif::ExifTime($val)',
|
238
|
+
value_conv_inv: 'Image::ExifTool::IPTC::IptcTime($val)',
|
239
|
+
print_conv_inv: 'Image::ExifTool::IPTC::InverseDateOrTime($val)',
|
240
|
+
},
|
241
|
+
0x28 => {
|
242
|
+
name: 'SpecialInstructions',
|
243
|
+
type: 'string',
|
244
|
+
size: (0..256),
|
245
|
+
},
|
246
|
+
0x2a => {
|
247
|
+
name: 'ActionAdvised',
|
248
|
+
type: 'digits',
|
249
|
+
size: 2,
|
250
|
+
print_conv: {
|
251
|
+
'' => '',
|
252
|
+
'01' => 'Object Kill',
|
253
|
+
'02' => 'Object Replace',
|
254
|
+
'03' => 'Object Append',
|
255
|
+
'04' => 'Object Reference',
|
256
|
+
},
|
257
|
+
},
|
258
|
+
0x2d => {
|
259
|
+
name: 'ReferenceService',
|
260
|
+
list: true,
|
261
|
+
type: 'string',
|
262
|
+
size: (0..10),
|
263
|
+
},
|
264
|
+
0x2f => {
|
265
|
+
name: 'ReferenceDate',
|
266
|
+
list: true,
|
267
|
+
type: 'digits',
|
268
|
+
size: 8,
|
269
|
+
shift: 'Time',
|
270
|
+
value_conv: 'Image::ExifTool::Exif::ExifDate($val)',
|
271
|
+
value_conv_inv: 'Image::ExifTool::IPTC::IptcDate($val)',
|
272
|
+
print_conv_inv: 'Image::ExifTool::IPTC::InverseDateOrTime($val)',
|
273
|
+
},
|
274
|
+
0x32 => {
|
275
|
+
name: 'ReferenceNumber',
|
276
|
+
list: true,
|
277
|
+
type: 'digits',
|
278
|
+
size: 8,
|
279
|
+
},
|
280
|
+
0x37 => {
|
281
|
+
name: 'DateCreated',
|
282
|
+
type: 'digits',
|
283
|
+
size: 8,
|
284
|
+
shift: 'Time',
|
285
|
+
value_conv: 'Image::ExifTool::Exif::ExifDate($val)',
|
286
|
+
value_conv_inv: 'Image::ExifTool::IPTC::IptcDate($val)',
|
287
|
+
print_conv_inv: 'Image::ExifTool::IPTC::InverseDateOrTime($val)',
|
288
|
+
},
|
289
|
+
0x3c => {
|
290
|
+
name: 'TimeCreated',
|
291
|
+
type: 'string',
|
292
|
+
size: 11,
|
293
|
+
shift: 'Time',
|
294
|
+
value_conv: 'Image::ExifTool::Exif::ExifTime($val)',
|
295
|
+
value_conv_inv: 'Image::ExifTool::IPTC::IptcTime($val)',
|
296
|
+
print_conv_inv: 'Image::ExifTool::IPTC::InverseDateOrTime($val)',
|
297
|
+
},
|
298
|
+
0x3e => {
|
299
|
+
name: 'DigitalCreationDate',
|
300
|
+
type: 'digits',
|
301
|
+
size: 8,
|
302
|
+
shift: 'Time',
|
303
|
+
value_conv: 'Image::ExifTool::Exif::ExifDate($val)',
|
304
|
+
value_conv_inv: 'Image::ExifTool::IPTC::IptcDate($val)',
|
305
|
+
print_conv_inv: 'Image::ExifTool::IPTC::InverseDateOrTime($val)',
|
306
|
+
},
|
307
|
+
0x3f => {
|
308
|
+
name: 'DigitalCreationTime',
|
309
|
+
type: 'string',
|
310
|
+
size: 11,
|
311
|
+
shift: 'Time',
|
312
|
+
value_conv: 'Image::ExifTool::Exif::ExifTime($val)',
|
313
|
+
value_conv_inv: 'Image::ExifTool::IPTC::IptcTime($val)',
|
314
|
+
print_conv_inv: 'Image::ExifTool::IPTC::InverseDateOrTime($val)',
|
315
|
+
},
|
316
|
+
0x41 => {
|
317
|
+
name: 'OriginatingProgram',
|
318
|
+
type: 'string',
|
319
|
+
size: (0..32),
|
320
|
+
},
|
321
|
+
0x46 => {
|
322
|
+
name: 'ProgramVersion',
|
323
|
+
type: 'string',
|
324
|
+
size: (0..10),
|
325
|
+
},
|
326
|
+
0x4b => {
|
327
|
+
name: 'ObjectCycle',
|
328
|
+
type: 'string',
|
329
|
+
size: 1,
|
330
|
+
print_conv: {
|
331
|
+
'a' => 'Morning',
|
332
|
+
'p' => 'Evening',
|
333
|
+
'b' => 'Both Morning and Evening',
|
334
|
+
},
|
335
|
+
},
|
336
|
+
0x50 => {
|
337
|
+
name: 'By-line',
|
338
|
+
list: true,
|
339
|
+
type: 'string',
|
340
|
+
size: (0..32),
|
341
|
+
},
|
342
|
+
0x55 => {
|
343
|
+
name: 'By-lineTitle',
|
344
|
+
list: true,
|
345
|
+
type: 'string',
|
346
|
+
size: (0..32),
|
347
|
+
},
|
348
|
+
0x5a => {
|
349
|
+
name: 'City',
|
350
|
+
type: 'string',
|
351
|
+
size: (0..32),
|
352
|
+
},
|
353
|
+
0x5c => {
|
354
|
+
name: 'Sub-location',
|
355
|
+
type: 'string',
|
356
|
+
size: (0..32),
|
357
|
+
},
|
358
|
+
0x5f => {
|
359
|
+
name: 'Province-State',
|
360
|
+
type: 'string',
|
361
|
+
size: (0..32),
|
362
|
+
},
|
363
|
+
0x64 => {
|
364
|
+
name: 'Country-PrimaryLocationCode',
|
365
|
+
type: 'string',
|
366
|
+
size: 3,
|
367
|
+
},
|
368
|
+
0x65 => {
|
369
|
+
name: 'Country-PrimaryLocationName',
|
370
|
+
type: 'string',
|
371
|
+
size: (0..64),
|
372
|
+
},
|
373
|
+
0x67 => {
|
374
|
+
name: 'OriginalTransmissionReference',
|
375
|
+
type: 'string',
|
376
|
+
size: (0..32),
|
377
|
+
},
|
378
|
+
0x69 => {
|
379
|
+
name: 'Headline',
|
380
|
+
type: 'string',
|
381
|
+
size: (0..256),
|
382
|
+
},
|
383
|
+
0x6e => {
|
384
|
+
name: 'Credit',
|
385
|
+
type: 'string',
|
386
|
+
size: (0..32),
|
387
|
+
},
|
388
|
+
0x73 => {
|
389
|
+
name: 'Source',
|
390
|
+
type: 'string',
|
391
|
+
size: (0..32),
|
392
|
+
},
|
393
|
+
0x74 => {
|
394
|
+
name: 'CopyrightNotice',
|
395
|
+
type: 'string',
|
396
|
+
size: (0..128),
|
397
|
+
},
|
398
|
+
0x76 => {
|
399
|
+
name: 'Contact',
|
400
|
+
list: true,
|
401
|
+
type: 'string',
|
402
|
+
size: (0..128),
|
403
|
+
},
|
404
|
+
0x78 => {
|
405
|
+
name: 'Caption-Abstract',
|
406
|
+
type: 'string',
|
407
|
+
size: (0..2000),
|
408
|
+
},
|
409
|
+
0x79 => {
|
410
|
+
name: 'LocalCaption',
|
411
|
+
type: 'string',
|
412
|
+
size: (0..256), # (guess)
|
413
|
+
notes: %{
|
414
|
+
I haven't found a reference for the format of tags 121, 184-188 and
|
415
|
+
225-232, so I have just make them writable as strings with
|
416
|
+
reasonable length. Beware that if this is wrong, other utilities
|
417
|
+
won't be able to read these tags as written by ExifTool
|
418
|
+
},
|
419
|
+
},
|
420
|
+
0x7a => {
|
421
|
+
name: 'Writer-Editor',
|
422
|
+
list: true,
|
423
|
+
type: 'string',
|
424
|
+
size: (0..32),
|
425
|
+
},
|
426
|
+
0x7d => {
|
427
|
+
name: 'RasterizedCaption',
|
428
|
+
type: 'undef',
|
429
|
+
size: 7360,
|
430
|
+
binary: 1,
|
431
|
+
},
|
432
|
+
0x82 => {
|
433
|
+
name: 'ImageType',
|
434
|
+
type: 'string',
|
435
|
+
size: 2,
|
436
|
+
},
|
437
|
+
0x83 => {
|
438
|
+
name: 'ImageOrientation',
|
439
|
+
type: 'string',
|
440
|
+
size: 1,
|
441
|
+
print_conv: {
|
442
|
+
'P' => 'Portrait',
|
443
|
+
'L' => 'Landscape',
|
444
|
+
'S' => 'Square',
|
445
|
+
},
|
446
|
+
},
|
447
|
+
0x87 => {
|
448
|
+
name: 'LanguageIdentifier',
|
449
|
+
type: 'string',
|
450
|
+
size: (2..3),
|
451
|
+
},
|
452
|
+
0x96 => {
|
453
|
+
name: 'AudioType',
|
454
|
+
type: 'string',
|
455
|
+
size: 2,
|
456
|
+
print_conv: {
|
457
|
+
'1A' => 'Mono Actuality',
|
458
|
+
'2A' => 'Stereo Actuality',
|
459
|
+
'1C' => 'Mono Question and Answer Session',
|
460
|
+
'2C' => 'Stereo Question and Answer Session',
|
461
|
+
'1M' => 'Mono Music',
|
462
|
+
'2M' => 'Stereo Music',
|
463
|
+
'1Q' => 'Mono Response to a Question',
|
464
|
+
'2Q' => 'Stereo Response to a Question',
|
465
|
+
'1R' => 'Mono Raw Sound',
|
466
|
+
'2R' => 'Stereo Raw Sound',
|
467
|
+
'1S' => 'Mono Scener',
|
468
|
+
'2S' => 'Stereo Scener',
|
469
|
+
'0T' => 'Text Only',
|
470
|
+
'1V' => 'Mono Voicer',
|
471
|
+
'2V' => 'Stereo Voicer',
|
472
|
+
'1W' => 'Mono Wrap',
|
473
|
+
'2W' => 'Stereo Wrap',
|
474
|
+
},
|
475
|
+
},
|
476
|
+
0x97 => {
|
477
|
+
name: 'AudioSamplingRate',
|
478
|
+
type: 'digits',
|
479
|
+
size: 6,
|
480
|
+
},
|
481
|
+
0x98 => {
|
482
|
+
name: 'AudioSamplingResolution',
|
483
|
+
type: 'digits',
|
484
|
+
size: 2,
|
485
|
+
},
|
486
|
+
0x99 => {
|
487
|
+
name: 'AudioDuration',
|
488
|
+
type: 'digits',
|
489
|
+
size: 6,
|
490
|
+
},
|
491
|
+
0x9a => {
|
492
|
+
name: 'AudioOutcue',
|
493
|
+
type: 'string',
|
494
|
+
size: (0..64),
|
495
|
+
},
|
496
|
+
0xb8 => {
|
497
|
+
name: 'JobID',
|
498
|
+
type: 'string',
|
499
|
+
size: (0..64), # (guess)
|
500
|
+
},
|
501
|
+
0xb9 => {
|
502
|
+
name: 'MasterDocumentID',
|
503
|
+
type: 'string',
|
504
|
+
size: (0..256), # (guess)
|
505
|
+
},
|
506
|
+
0xba => {
|
507
|
+
name: 'ShortDocumentID',
|
508
|
+
type: 'string',
|
509
|
+
size: (0..64), # (guess)
|
510
|
+
},
|
511
|
+
0xbb => {
|
512
|
+
name: 'UniqueDocumentID',
|
513
|
+
type: 'string',
|
514
|
+
size: (0..128), # (guess)
|
515
|
+
},
|
516
|
+
0xbc => {
|
517
|
+
name: 'OwnerID',
|
518
|
+
type: 'string',
|
519
|
+
size: (0..128), # (guess)
|
520
|
+
},
|
521
|
+
0xc8 => {
|
522
|
+
name: 'ObjectPreviewFileFormat',
|
523
|
+
type: 'int16u',
|
524
|
+
print_conv: "fileFormat",
|
525
|
+
},
|
526
|
+
0xc9 => {
|
527
|
+
name: 'ObjectPreviewFileVersion',
|
528
|
+
type: 'int16u',
|
529
|
+
},
|
530
|
+
0xca => {
|
531
|
+
name: 'ObjectPreviewData',
|
532
|
+
type: 'undef',
|
533
|
+
size: (0..256000),
|
534
|
+
binary: 1,
|
535
|
+
},
|
536
|
+
0xdd => {
|
537
|
+
name: 'Prefs',
|
538
|
+
type: 'string',
|
539
|
+
size: (0..64),
|
540
|
+
notes: 'PhotoMechanic preferences',
|
541
|
+
print_conv: %{
|
542
|
+
$val =~ s[\s*(\d+):\s*(\d+):\s*(\d+):\s*(\S*)]
|
543
|
+
[Tagged:$1, ColorClass:$2, Rating:$3, FrameNum:$4];
|
544
|
+
return $val;
|
545
|
+
},
|
546
|
+
print_conv_inv: %{
|
547
|
+
$val =~ s[Tagged:\s*(\d+).*ColorClass:\s*(\d+).*Rating:\s*(\d+).*FrameNum:\s*(\S*)]
|
548
|
+
[$1:$2:$3:$4]is;
|
549
|
+
return $val;
|
550
|
+
},
|
551
|
+
},
|
552
|
+
0xe1 => {
|
553
|
+
name: 'ClassifyState',
|
554
|
+
type: 'string',
|
555
|
+
size: (0..64), # (guess)
|
556
|
+
},
|
557
|
+
0xe4 => {
|
558
|
+
name: 'SimilarityIndex',
|
559
|
+
type: 'string',
|
560
|
+
size: (0..32), # (guess)
|
561
|
+
},
|
562
|
+
0xe6 => {
|
563
|
+
name: 'DocumentNotes',
|
564
|
+
type: 'string',
|
565
|
+
size: (0..1024), # (guess)
|
566
|
+
},
|
567
|
+
0xe7 => {
|
568
|
+
name: 'DocumentHistory',
|
569
|
+
type: 'string',
|
570
|
+
size: (0..256), # (guess)
|
571
|
+
},
|
572
|
+
0xe8 => {
|
573
|
+
name: 'ExifCameraInfo',
|
574
|
+
type: 'string',
|
575
|
+
size: (0..4096), # (guess)
|
576
|
+
},
|
577
|
+
0xff => { #PH
|
578
|
+
name: 'CatalogSets',
|
579
|
+
list: 1,
|
580
|
+
type: 'string',
|
581
|
+
size: (0..256), # (guess)
|
582
|
+
notes: 'written by iView MediaPro',
|
583
|
+
},
|
584
|
+
},
|
585
|
+
},
|
586
|
+
0x03 => {
|
587
|
+
name: 'IPTCNewsPhoto',
|
588
|
+
datasets: {
|
589
|
+
0x00 => {
|
590
|
+
name: 'NewsPhotoVersion',
|
591
|
+
type: 'int16u',
|
592
|
+
mandatory: 1,
|
593
|
+
},
|
594
|
+
0x0a => {
|
595
|
+
name: 'IPTCPictureNumber',
|
596
|
+
type: 'string',
|
597
|
+
size: 16,
|
598
|
+
notes: '4 numbers: 1-Manufacturer ID, 2-Equipment ID, 3-Date, 4-Sequence',
|
599
|
+
print_conv: 'Image::ExifTool::IPTC::ConvertPictureNumber($val)',
|
600
|
+
print_conv_inv: 'Image::ExifTool::IPTC::InvConvertPictureNumber($val)',
|
601
|
+
},
|
602
|
+
0x14 => {
|
603
|
+
name: 'IPTCImageWidth',
|
604
|
+
type: 'int16u',
|
605
|
+
},
|
606
|
+
0x1e => {
|
607
|
+
name: 'IPTCImageHeight',
|
608
|
+
type: 'int16u',
|
609
|
+
},
|
610
|
+
0x28 => {
|
611
|
+
name: 'IPTCPixelWidth',
|
612
|
+
type: 'int16u',
|
613
|
+
},
|
614
|
+
0x32 => {
|
615
|
+
name: 'IPTCPixelHeight',
|
616
|
+
type: 'int16u',
|
617
|
+
},
|
618
|
+
0x37 => {
|
619
|
+
name: 'SupplementalType',
|
620
|
+
type: 'int8u',
|
621
|
+
print_conv: {
|
622
|
+
0x00 => 'Main Image',
|
623
|
+
0x01 => 'Reduced Resolution Image',
|
624
|
+
0x02 => 'Logo',
|
625
|
+
0x03 => 'Rasterized Caption',
|
626
|
+
},
|
627
|
+
},
|
628
|
+
0x3c => {
|
629
|
+
name: 'ColorRepresentation',
|
630
|
+
type: 'int16u',
|
631
|
+
print_hex: 1,
|
632
|
+
print_conv: {
|
633
|
+
0x000 => 'No Image, Single Frame',
|
634
|
+
0x100 => 'Monochrome, Single Frame',
|
635
|
+
0x300 => '3 Components, Single Frame',
|
636
|
+
0x301 => '3 Components, Frame Sequential in Multiple Objects',
|
637
|
+
0x302 => '3 Components, Frame Sequential in One Object',
|
638
|
+
0x303 => '3 Components, Line Sequential',
|
639
|
+
0x304 => '3 Components, Pixel Sequential',
|
640
|
+
0x305 => '3 Components, Special Interleaving',
|
641
|
+
0x400 => '4 Components, Single Frame',
|
642
|
+
0x401 => '4 Components, Frame Sequential in Multiple Objects',
|
643
|
+
0x402 => '4 Components, Frame Sequential in One Object',
|
644
|
+
0x403 => '4 Components, Line Sequential',
|
645
|
+
0x404 => '4 Components, Pixel Sequential',
|
646
|
+
0x405 => '4 Components, Special Interleaving',
|
647
|
+
},
|
648
|
+
},
|
649
|
+
0x40 => {
|
650
|
+
name: 'InterchangeColorSpace',
|
651
|
+
type: 'int8u',
|
652
|
+
print_conv: {
|
653
|
+
0x01 => 'X,Y,Z CIE',
|
654
|
+
0x02 => 'RGB SMPTE',
|
655
|
+
0x03 => 'Y,U,V (K) (D65)',
|
656
|
+
0x04 => 'RGB Device Dependent',
|
657
|
+
0x05 => 'CMY (K) Device Dependent',
|
658
|
+
0x06 => 'Lab (K) CIE',
|
659
|
+
0x07 => 'YCbCr',
|
660
|
+
0x08 => 'sRGB',
|
661
|
+
},
|
662
|
+
},
|
663
|
+
0x41 => {
|
664
|
+
name: 'ColorSequence',
|
665
|
+
type: 'int8u',
|
666
|
+
},
|
667
|
+
0x42 => {
|
668
|
+
name: 'ICC_Profile',
|
669
|
+
binary: 1,
|
670
|
+
},
|
671
|
+
0x46 => {
|
672
|
+
name: 'ColorCalibrationMatrix',
|
673
|
+
binary: 1,
|
674
|
+
},
|
675
|
+
0x50 => {
|
676
|
+
name: 'LookupTable',
|
677
|
+
binary: 1,
|
678
|
+
},
|
679
|
+
0x54 => {
|
680
|
+
name: 'NumIndexEntries',
|
681
|
+
type: 'int16u',
|
682
|
+
},
|
683
|
+
0x55 => {
|
684
|
+
name: 'ColorPalette',
|
685
|
+
binary: 1,
|
686
|
+
},
|
687
|
+
0x56 => {
|
688
|
+
name: 'IPTCBitsPerSample',
|
689
|
+
type: 'int8u',
|
690
|
+
},
|
691
|
+
0x5a => {
|
692
|
+
name: 'SampleStructure',
|
693
|
+
type: 'int8u',
|
694
|
+
print_conv: {
|
695
|
+
0x00 => 'OrthogonalConstangSampling',
|
696
|
+
0x01 => 'Orthogonal4-2-2Sampling',
|
697
|
+
0x02 => 'CompressionDependent',
|
698
|
+
},
|
699
|
+
},
|
700
|
+
0x64 => {
|
701
|
+
name: 'ScanningDirection',
|
702
|
+
type: 'int8u',
|
703
|
+
print_conv: {
|
704
|
+
0x00 => 'L-R, Top-Bottom',
|
705
|
+
0x01 => 'R-L, Top-Bottom',
|
706
|
+
0x02 => 'L-R, Bottom-Top',
|
707
|
+
0x03 => 'R-L, Bottom-Top',
|
708
|
+
0x04 => 'Top-Bottom, L-R',
|
709
|
+
0x05 => 'Bottom-Top, L-R',
|
710
|
+
0x06 => 'Top-Bottom, R-L',
|
711
|
+
0x07 => 'Bottom-Top, R-L',
|
712
|
+
},
|
713
|
+
},
|
714
|
+
0x66 => {
|
715
|
+
name: 'IPTCImageRotation',
|
716
|
+
type: 'int8u',
|
717
|
+
print_conv: {
|
718
|
+
0x00 => 0,
|
719
|
+
0x01 => 90,
|
720
|
+
0x02 => 180,
|
721
|
+
0x03 => 270,
|
722
|
+
},
|
723
|
+
},
|
724
|
+
0x6e => {
|
725
|
+
name: 'DataCompressionMethod',
|
726
|
+
type: 'int32u',
|
727
|
+
},
|
728
|
+
0x78 => {
|
729
|
+
name: 'QuantizationMethod',
|
730
|
+
type: 'int8u',
|
731
|
+
print_conv: {
|
732
|
+
0x00 => 'Linear Reflectance/Transmittance',
|
733
|
+
0x01 => 'Linear Density',
|
734
|
+
0x02 => 'IPTC Ref B',
|
735
|
+
0x03 => 'Linear Dot Percent',
|
736
|
+
0x04 => 'AP Domestic Analogue',
|
737
|
+
0x05 => 'Compression Method Specific',
|
738
|
+
0x06 => 'Color Space Specific',
|
739
|
+
0x07 => 'Gamma Compensated',
|
740
|
+
},
|
741
|
+
},
|
742
|
+
0x7d => {
|
743
|
+
name: 'EndPoints',
|
744
|
+
binary: 1,
|
745
|
+
},
|
746
|
+
0x82 => {
|
747
|
+
name: 'ExcursionTolerance',
|
748
|
+
type: 'int8u',
|
749
|
+
print_conv: {
|
750
|
+
0x00 => 'Not Allowed',
|
751
|
+
0x01 => 'Allowed',
|
752
|
+
},
|
753
|
+
},
|
754
|
+
0x87 => {
|
755
|
+
name: 'BitsPerComponent',
|
756
|
+
type: 'int8u',
|
757
|
+
},
|
758
|
+
0x8c => {
|
759
|
+
name: 'MaximumDensityRange',
|
760
|
+
type: 'int16u',
|
761
|
+
},
|
762
|
+
0x91 => {
|
763
|
+
name: 'GammaCompensatedValue',
|
764
|
+
type: 'int16u',
|
765
|
+
},
|
766
|
+
},
|
767
|
+
},
|
768
|
+
0x07 => {
|
769
|
+
# Record 7 -- Pre-object Data
|
770
|
+
name: 'IPTCPreObjectData',
|
771
|
+
datasets: {
|
772
|
+
0x0a => {
|
773
|
+
name: 'SizeMode',
|
774
|
+
type: 'int8u',
|
775
|
+
print_conv: {
|
776
|
+
0x00 => 'Size Not Known',
|
777
|
+
0x01 => 'Size Known',
|
778
|
+
},
|
779
|
+
},
|
780
|
+
0x14 => {
|
781
|
+
name: 'MaxSubfileSize',
|
782
|
+
type: 'int32u',
|
783
|
+
},
|
784
|
+
0x5a => {
|
785
|
+
name: 'ObjectSizeAnnounced',
|
786
|
+
type: 'int32u',
|
787
|
+
},
|
788
|
+
0x5f => {
|
789
|
+
name: 'MaximumObjectSize',
|
790
|
+
type: 'int32u',
|
791
|
+
},
|
792
|
+
},
|
793
|
+
},
|
794
|
+
0x08 => {
|
795
|
+
# Record 8 -- ObjectData
|
796
|
+
name: 'IPTCObjectData',
|
797
|
+
datasets: {
|
798
|
+
0x0a => {
|
799
|
+
name: 'SubFile',
|
800
|
+
list: true,
|
801
|
+
binary: 1,
|
802
|
+
},
|
803
|
+
},
|
804
|
+
},
|
805
|
+
0x09 => {
|
806
|
+
# Record 9 -- PostObjectData
|
807
|
+
name: 'IPTCPostObjectData',
|
808
|
+
datasets: {
|
809
|
+
0x0a => {
|
810
|
+
name: 'ConfirmedObjectSize',
|
811
|
+
type: 'int32u',
|
812
|
+
},
|
813
|
+
},
|
814
|
+
},
|
815
|
+
0xf0 => {
|
816
|
+
# Record 240 -- FotoStation proprietary data (ref PH)
|
817
|
+
name: 'IPTCFotoStation',
|
818
|
+
},
|
819
|
+
}
|
data/lib/iptcr.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "iptcr/version"
|
2
|
+
|
3
|
+
module IPTCR
|
4
|
+
def self.parse(value, length: nil)
|
5
|
+
if value.is_a? String
|
6
|
+
require "stringio"
|
7
|
+
length ||= value.bytesize
|
8
|
+
IPTC.new(StringIO.new(value), length: length)
|
9
|
+
else
|
10
|
+
IPTC.new(value, length: length)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
autoload :IPTC, "iptcr/iptc"
|
15
|
+
end
|
data/test/iptc_test.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class IPTCTest < Minitest::Test
|
4
|
+
def test_iptc_string
|
5
|
+
iptc = IPTCR.parse("\x1c\x00\x00\x00\x05Hello".b)
|
6
|
+
assert iptc.is_a? IPTCR::IPTC
|
7
|
+
assert_equal 1, iptc.fields.size
|
8
|
+
assert_equal "Hello".b, iptc.fields.first.value
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_iptc_string_respects_length
|
12
|
+
iptc = IPTCR.parse("\x1c\x00\x00\x00\x05Hello\x1c\x00\x00\x07\x00Goodbye".b, length: 10)
|
13
|
+
assert iptc.is_a? IPTCR::IPTC
|
14
|
+
assert_equal 1, iptc.fields.size
|
15
|
+
assert_equal "Hello".b, iptc.fields.first.value
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_iptc_io
|
19
|
+
iptc = IPTCR.parse(StringIO.new("\x1c\x00\x00\x00\x05Hello"))
|
20
|
+
assert iptc.is_a? IPTCR::IPTC
|
21
|
+
assert_equal 1, iptc.fields.size
|
22
|
+
assert_equal "Hello".b, iptc.fields.first.value
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_iptc_io_respects_length
|
26
|
+
iptc = IPTCR.parse(StringIO.new("\x1c\x00\x00\x00\x05Hello\x1c\x00\x00\x07\x00Goodbye".b), length: 10)
|
27
|
+
assert iptc.is_a? IPTCR::IPTC
|
28
|
+
assert_equal 1, iptc.fields.size
|
29
|
+
assert_equal "Hello".b, iptc.fields.first.value
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_bad_marker
|
33
|
+
assert_raises(IPTCR::Malformed) do
|
34
|
+
IPTCR.parse("\x1d\x00\x00\x00\x05Hello".b)
|
35
|
+
end
|
36
|
+
|
37
|
+
assert_raises(IPTCR::Malformed) do
|
38
|
+
IPTCR.parse("\x1c\x00\x00\x00\x05Hello\x1d".b)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_iptc_unknown_record
|
43
|
+
iptc = IPTCR.parse("\x1c\x00\x00\x00\x05Hello".b)
|
44
|
+
assert_equal(1, iptc.fields.size)
|
45
|
+
refute iptc.fields.first.record?
|
46
|
+
refute iptc.fields.first.dataset?
|
47
|
+
assert_equal "Hello".b, iptc.fields.first.value
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_iptc_unknown_dataset
|
51
|
+
iptc = IPTCR.parse("\x1c\x01\x01\x00\x05Hello".b)
|
52
|
+
assert_equal(1, iptc.fields.size)
|
53
|
+
assert iptc.fields.first.record?
|
54
|
+
refute iptc.fields.first.dataset?
|
55
|
+
assert_equal "Hello".b, iptc.fields.first.value
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_iptc_int8u
|
59
|
+
iptc = IPTCR.parse("\x1c\x03\x37\x00\x02\x01".b)
|
60
|
+
assert_equal({"SupplementalType" => 1}, iptc.to_hash)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_iptc_int16u
|
64
|
+
iptc = IPTCR.parse("\x1c\x01\x00\x00\x02\x01\x02".b)
|
65
|
+
assert_equal({"EnvelopeRecordVersion" => 513}, iptc.to_hash)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_iptc_int32u
|
69
|
+
iptc = IPTCR.parse("\x1c\x03\x6e\x00\x04\x01\x02\x04\x08".b)
|
70
|
+
assert_equal({"DataCompressionMethod" => 134480385}, iptc.to_hash)
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_iptc_string
|
74
|
+
iptc = IPTCR.parse("\x1c\x01\x1e\x00\x05Hello".b)
|
75
|
+
assert_equal({"ServiceIdentifier" => "Hello".b}, iptc.to_hash)
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_iptc_encoded_string
|
79
|
+
iptc = IPTCR.parse("\x1c\x01\x5a\x00\x03\x1b\x25\x47\x1c\x02\x05\x00\x03\xE2\x98\x83".b)
|
80
|
+
assert_equal(2, iptc.fields.size)
|
81
|
+
assert_equal("\e%G".b, iptc["CodedCharacterSet"])
|
82
|
+
assert_equal(Encoding::UTF_8, iptc["ObjectName"].encoding)
|
83
|
+
assert_equal("☃".force_encoding("UTF-8"), iptc["ObjectName"])
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_iptc_string_list
|
87
|
+
iptc = IPTCR.parse("\x1c\x01\x05\x00\x05Hello\x1c\x01\x05\x00\x05World".b)
|
88
|
+
assert_equal(2, iptc.fields.size)
|
89
|
+
assert_equal(["Hello", "World"], iptc["Destination"])
|
90
|
+
end
|
91
|
+
end
|
data/test/test_helper.rb
ADDED
data.tar.gz.sig
ADDED
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: iptcr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Samuel Cochran
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIDKDCCAhCgAwIBAgIBAzANBgkqhkiG9w0BAQUFADA6MQ0wCwYDVQQDDARzajI2
|
14
|
+
MRQwEgYKCZImiZPyLGQBGRYEc2oyNjETMBEGCgmSJomT8ixkARkWA2NvbTAeFw0x
|
15
|
+
NTAzMTcyMjUwMjZaFw0xNjAzMTYyMjUwMjZaMDoxDTALBgNVBAMMBHNqMjYxFDAS
|
16
|
+
BgoJkiaJk/IsZAEZFgRzajI2MRMwEQYKCZImiZPyLGQBGRYDY29tMIIBIjANBgkq
|
17
|
+
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr60Eo/ttCk8GMTMFiPr3GoYMIMFvLak
|
18
|
+
xSmTk9YGCB6UiEePB4THSSA5w6IPyeaCF/nWkDp3/BAam0eZMWG1IzYQB23TqIM0
|
19
|
+
1xzcNRvFsn0aQoQ00k+sj+G83j3T5OOV5OZIlu8xAChMkQmiPd1NXc6uFv+Iacz7
|
20
|
+
kj+CMsI9YUFdNoU09QY0b+u+Rb6wDYdpyvN60YC30h0h1MeYbvYZJx/iZK4XY5zu
|
21
|
+
4O/FL2ChjL2CPCpLZW55ShYyrzphWJwLOJe+FJ/ZBl6YXwrzQM9HKnt4titSNvyU
|
22
|
+
KzE3L63A3PZvExzLrN9u09kuWLLJfXB2sGOlw3n9t72rJiuBr3/OQQIDAQABozkw
|
23
|
+
NzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQU99dfRjEKFyczTeIz
|
24
|
+
m3ZsDWrNC80wDQYJKoZIhvcNAQEFBQADggEBAFxKLjiLkMLkUmdpsAzJad/t7Jo/
|
25
|
+
CGby/3n0WSXPBeZJfsnSdJ2qtG7iy/xqYDc1RjpKgX0RlMgeQRSE3ZDL/HZzBKDF
|
26
|
+
azaTgG9Zk1Quu59/79Z0Sltq07Z/IeccFl5j9M+1YS8VY2mOPi9g03OoOSRmhsMS
|
27
|
+
wpEF+zvJ0ESS5OPjtp6Sk4q1QYc0aVIthEznuVNMW6CPpTNhMAOFMaTC5AXCzJ3Q
|
28
|
+
52G9HuhbVSTgE/I10H9qZBOE3qdP8ka/Fk0PUrux/DuUanNZgSKJokrQvRA4H9Au
|
29
|
+
WpPA7HJYV6msWQiukoBEhfQ2l6Fl2HUwntvX3MCcFNHeJJ5ETERp9alo88E=
|
30
|
+
-----END CERTIFICATE-----
|
31
|
+
date: 2015-12-18 00:00:00.000000000 Z
|
32
|
+
dependencies:
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: bundler
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.10'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.10'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '10.0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '10.0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: minitest
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
description: Parse IPTC data extracted from an image into rich data types and respecting
|
76
|
+
string encodings
|
77
|
+
email: sj26@sj26.com
|
78
|
+
executables: []
|
79
|
+
extensions: []
|
80
|
+
extra_rdoc_files: []
|
81
|
+
files:
|
82
|
+
- LICENSE
|
83
|
+
- README.md
|
84
|
+
- lib/iptcr.rb
|
85
|
+
- lib/iptcr/iptc.rb
|
86
|
+
- lib/iptcr/records.rb
|
87
|
+
- lib/iptcr/version.rb
|
88
|
+
- test/iptc_test.rb
|
89
|
+
- test/test_helper.rb
|
90
|
+
homepage: https://github.com/sj26/iptcr
|
91
|
+
licenses:
|
92
|
+
- MIT
|
93
|
+
metadata: {}
|
94
|
+
post_install_message:
|
95
|
+
rdoc_options: []
|
96
|
+
require_paths:
|
97
|
+
- lib
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '2.1'
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
requirements: []
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 2.4.5.1
|
111
|
+
signing_key:
|
112
|
+
specification_version: 4
|
113
|
+
summary: Parse IPTC data
|
114
|
+
test_files:
|
115
|
+
- test/iptc_test.rb
|
116
|
+
- test/test_helper.rb
|
117
|
+
has_rdoc:
|
metadata.gz.sig
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
���KL������Z�[hя��`�ѵ���t�qx#�
|