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 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
+ [![Build Status](https://travis-ci.org/sj26/iptcr?branch=master)](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
+ }
@@ -0,0 +1,3 @@
1
+ module IPTCR
2
+ VERSION = "0.1.0"
3
+ end
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
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'iptcr'
3
+
4
+ require 'minitest/autorun'
data.tar.gz.sig ADDED
@@ -0,0 +1,2 @@
1
+ W���SA~(��<,�%u���D�E5��N����ݡu�
2
+ w��V�D�E��Rk���8㾢wP�R6��X���s�oww�ZWK��mu��DQ�.D�1"L=S�)�5r�Ә��(�=��#2A���{S�e���K�FGl&7�;�+�9M��7�ь�6�o��G�wV�b��ld\�+w8�Y�*�y�J-/��6�v�R��>�|��������1���9����{�<���*��z� �
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›#�