exiv2 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  = Exiv2
2
2
 
3
- A simple wrapper around the C++ Exiv2 libary for reading image metadata.
3
+ A simple wrapper around the C++ Exiv2 libary for reading and writing image metadata.
4
4
 
5
5
  See http://www.exiv2.org/
6
6
 
@@ -21,10 +21,18 @@ Requires that the exiv2 C++ library is installed.
21
21
 
22
22
  iptc_data_hash = image.iptc_data.to_hash
23
23
  xmp_data_hash = image.xmp_data.to_hash
24
+
25
+ image.exif_data.delete("Exif.Image.Software")
26
+ image.iptc_data.delete_all("Iptc.Application2.Keywords")
27
+
28
+ image.iptc_data["Iptc.Application2.Caption"] = "A New Caption"
29
+ image.iptc_data.add("Iptc.Application2.Keywords", "fishy")
30
+
31
+ image.write_metadata
24
32
 
25
33
  == Why?
26
34
 
27
- None of the existing Ruby libraries for reading image metadata did quite what
35
+ None of the existing Ruby libraries for reading and writing image metadata did quite what
28
36
  we wanted, so we figured it wouldn't be too hard to wrap enough of Exiv2 to
29
37
  meet our needs.
30
38
 
@@ -34,7 +42,7 @@ are welcome.
34
42
 
35
43
  == Compatibility
36
44
 
37
- Tested with Ruby 1.8.7 and Exiv2 0.21.
45
+ Tested with Ruby 1.8.7 and 1.9.3, and Exiv2 0.20 and 0.21
38
46
 
39
47
  == Developing
40
48
 
@@ -51,6 +59,10 @@ Tested with Ruby 1.8.7 and Exiv2 0.21.
51
59
  In early development. Very little of Exiv2's API is supported, and it hasn't
52
60
  been heavily tested.
53
61
 
62
+ == Contributors
63
+
64
+ Pete Yandell, Mark Turnley, Lucas Parry, Norbert Wojtwoicz, Jan Graichen, John Barton
65
+
54
66
  == Copyright
55
67
 
56
68
  Copyright (c) 2010 Envato & Pete Yandell. See LICENSE for details.
data/Rakefile CHANGED
@@ -5,8 +5,12 @@ task :default => :spec
5
5
  require 'rake/extensiontask'
6
6
  Rake::ExtensionTask.new('exiv2') do |ext|
7
7
  ext.lib_dir = "lib/exiv2"
8
+ if ENV['EXIV2_DIR']
9
+ ext.config_options << "--with-exiv2-dir=#{ENV['EXIV2_DIR']}"
10
+ end
8
11
  end
9
12
  Rake::Task[:spec].prerequisites << :compile
10
13
 
11
14
  require 'bundler'
12
15
  Bundler::GemHelper.install_tasks
16
+
@@ -9,7 +9,7 @@ static VALUE to_ruby_string(const std::string& string) {
9
9
  // Create a C++ std::string from a Ruby string.
10
10
  static std::string to_std_string(VALUE string) {
11
11
  string = StringValue(string); // Convert the Ruby object to a string if it isn't one.
12
- return std::string(RSTRING(string)->ptr, RSTRING(string)->len);
12
+ return std::string(RSTRING_PTR(string), RSTRING_LEN(string));
13
13
  }
14
14
 
15
15
  // Shared method for implementing each on XmpData, IptcData and ExifData.
@@ -38,6 +38,7 @@ extern "C" {
38
38
  static VALUE image_class;
39
39
  static void image_free(Exiv2::Image* image);
40
40
  static VALUE image_read_metadata(VALUE self);
41
+ static VALUE image_write_metadata(VALUE self);
41
42
  static VALUE image_iptc_data(VALUE self);
42
43
  static VALUE image_xmp_data(VALUE self);
43
44
  static VALUE image_exif_data(VALUE self);
@@ -47,12 +48,18 @@ extern "C" {
47
48
 
48
49
  static VALUE exif_data_class;
49
50
  static VALUE exif_data_each(VALUE self);
51
+ static VALUE exif_data_add(VALUE self, VALUE key, VALUE value);
52
+ static VALUE exif_data_delete(VALUE self, VALUE key);
50
53
 
51
54
  static VALUE iptc_data_class;
52
55
  static VALUE iptc_data_each(VALUE self);
56
+ static VALUE iptc_data_add(VALUE self, VALUE key, VALUE value);
57
+ static VALUE iptc_data_delete(VALUE self, VALUE key);
53
58
 
54
59
  static VALUE xmp_data_class;
55
60
  static VALUE xmp_data_each(VALUE self);
61
+ static VALUE xmp_data_add(VALUE self, VALUE key, VALUE value);
62
+ static VALUE xmp_data_delete(VALUE self, VALUE key);
56
63
 
57
64
  void Init_exiv2() {
58
65
  VALUE enumerable_module = rb_const_get(rb_cObject, rb_intern("Enumerable"));
@@ -63,6 +70,7 @@ extern "C" {
63
70
 
64
71
  image_class = rb_define_class_under(exiv2_module, "Image", rb_cObject);
65
72
  rb_define_method(image_class, "read_metadata", (Method)image_read_metadata, 0);
73
+ rb_define_method(image_class, "write_metadata", (Method)image_write_metadata, 0);
66
74
  rb_define_method(image_class, "iptc_data", (Method)image_iptc_data, 0);
67
75
  rb_define_method(image_class, "xmp_data", (Method)image_xmp_data, 0);
68
76
  rb_define_method(image_class, "exif_data", (Method)image_exif_data, 0);
@@ -73,14 +81,20 @@ extern "C" {
73
81
  exif_data_class = rb_define_class_under(exiv2_module, "ExifData", rb_cObject);
74
82
  rb_include_module(exif_data_class, enumerable_module);
75
83
  rb_define_method(exif_data_class, "each", (Method)exif_data_each, 0);
84
+ rb_define_method(exif_data_class, "add", (Method)exif_data_add, 2);
85
+ rb_define_method(exif_data_class, "delete", (Method)exif_data_delete, 1);
76
86
 
77
87
  iptc_data_class = rb_define_class_under(exiv2_module, "IptcData", rb_cObject);
78
88
  rb_include_module(iptc_data_class, enumerable_module);
79
89
  rb_define_method(iptc_data_class, "each", (Method)iptc_data_each, 0);
90
+ rb_define_method(iptc_data_class, "add", (Method)iptc_data_add, 2);
91
+ rb_define_method(iptc_data_class, "delete", (Method)iptc_data_delete, 1);
80
92
 
81
93
  xmp_data_class = rb_define_class_under(exiv2_module, "XmpData", rb_cObject);
82
94
  rb_include_module(xmp_data_class, enumerable_module);
83
95
  rb_define_method(xmp_data_class, "each", (Method)xmp_data_each, 0);
96
+ rb_define_method(xmp_data_class, "add", (Method)xmp_data_add, 2);
97
+ rb_define_method(xmp_data_class, "delete", (Method)xmp_data_delete, 1);
84
98
  }
85
99
 
86
100
 
@@ -103,6 +117,20 @@ extern "C" {
103
117
 
104
118
  return Qnil;
105
119
  }
120
+
121
+ static VALUE image_write_metadata(VALUE self) {
122
+ Exiv2::Image* image;
123
+ Data_Get_Struct(self, Exiv2::Image, image);
124
+
125
+ try {
126
+ image->writeMetadata();
127
+ }
128
+ catch (Exiv2::BasicError<char> error) {
129
+ rb_raise(basic_error_class, "%s", error.what());
130
+ }
131
+
132
+ return Qnil;
133
+ }
106
134
 
107
135
  static VALUE image_exif_data(VALUE self) {
108
136
  Exiv2::Image* image;
@@ -157,16 +185,103 @@ extern "C" {
157
185
  return metadata_each<Exiv2::ExifData>(self);
158
186
  }
159
187
 
188
+ static VALUE exif_data_add(VALUE self, VALUE key, VALUE value) {
189
+ Exiv2::ExifData* data;
190
+ Data_Get_Struct(self, Exiv2::ExifData, data);
191
+
192
+ Exiv2::ExifKey exifKey = Exiv2::ExifKey(to_std_string(key));
193
+
194
+ #if EXIV2_MAJOR_VERSION <= 0 && EXIV2_MINOR_VERSION <= 20
195
+ Exiv2::TypeId typeId = Exiv2::ExifTags::tagType(exifKey.tag(), exifKey.ifdId());
196
+ #else
197
+ Exiv2::TypeId typeId = exifKey.defaultTypeId();
198
+ #endif
199
+
200
+ Exiv2::Value::AutoPtr v = Exiv2::Value::create(typeId);
201
+ v->read(to_std_string(value));
202
+
203
+ data->add(exifKey, v.get());
204
+ return Qtrue;
205
+ }
206
+
207
+ static VALUE exif_data_delete(VALUE self, VALUE key) {
208
+ Exiv2::ExifData* data;
209
+ Data_Get_Struct(self, Exiv2::ExifData, data);
210
+
211
+ Exiv2::ExifKey exifKey = Exiv2::ExifKey(to_std_string(key));
212
+ Exiv2::ExifData::iterator pos = data->findKey(exifKey);
213
+ if(pos == data->end()) return Qfalse;
214
+ data->erase(pos);
215
+
216
+ return Qtrue;
217
+ }
218
+
160
219
 
161
220
  // Exiv2::IptcData methods
162
221
 
163
222
  static VALUE iptc_data_each(VALUE self) {
164
223
  return metadata_each<Exiv2::IptcData>(self);
165
224
  }
225
+
226
+ static VALUE iptc_data_add(VALUE self, VALUE key, VALUE value) {
227
+ Exiv2::IptcData* data;
228
+ Data_Get_Struct(self, Exiv2::IptcData, data);
229
+
230
+ Exiv2::IptcKey iptcKey = Exiv2::IptcKey(to_std_string(key));
231
+ Exiv2::TypeId typeId = Exiv2::IptcDataSets::dataSetType(iptcKey.tag(), iptcKey.record());
232
+
233
+ Exiv2::Value::AutoPtr v = Exiv2::Value::create(typeId);
234
+ v->read(to_std_string(value));
235
+
236
+ if(data->add(iptcKey, v.get())) {
237
+ return Qfalse;
238
+ }
239
+ return Qtrue;
240
+ }
241
+
242
+ static VALUE iptc_data_delete(VALUE self, VALUE key) {
243
+ Exiv2::IptcData* data;
244
+ Data_Get_Struct(self, Exiv2::IptcData, data);
245
+
246
+ Exiv2::IptcKey iptcKey = Exiv2::IptcKey(to_std_string(key));
247
+ Exiv2::IptcData::iterator pos = data->findKey(iptcKey);
248
+ if(pos == data->end()) return Qfalse;
249
+ data->erase(pos);
250
+
251
+ return Qtrue;
252
+ }
166
253
 
167
254
  // Exiv2::XmpData methods
168
255
 
169
256
  static VALUE xmp_data_each(VALUE self) {
170
257
  return metadata_each<Exiv2::XmpData>(self);
171
- }
258
+ }
259
+
260
+ static VALUE xmp_data_add(VALUE self, VALUE key, VALUE value) {
261
+ Exiv2::XmpData* data;
262
+ Data_Get_Struct(self, Exiv2::XmpData, data);
263
+
264
+ Exiv2::XmpKey xmpKey = Exiv2::XmpKey(to_std_string(key));
265
+ Exiv2::TypeId typeId = Exiv2::XmpProperties::propertyType(xmpKey);
266
+
267
+ Exiv2::Value::AutoPtr v = Exiv2::Value::create(typeId);
268
+ v->read(to_std_string(value));
269
+
270
+ if(data->add(xmpKey, v.get())) {
271
+ return Qfalse;
272
+ }
273
+ return Qtrue;
274
+ }
275
+
276
+ static VALUE xmp_data_delete(VALUE self, VALUE key) {
277
+ Exiv2::XmpData* data;
278
+ Data_Get_Struct(self, Exiv2::XmpData, data);
279
+
280
+ Exiv2::XmpKey xmpKey = Exiv2::XmpKey(to_std_string(key));
281
+ Exiv2::XmpData::iterator pos = data->findKey(xmpKey);
282
+ if(pos == data->end()) return Qfalse;
283
+ data->erase(pos);
284
+
285
+ return Qtrue;
286
+ }
172
287
  }
@@ -17,6 +17,32 @@ module SharedMethods
17
17
  end
18
18
 
19
19
  def inspect
20
- "#<#{self.class.name}: #{self.to_hash.inspect}>"
20
+ items = []
21
+ self.to_hash.sort.each do |k,v|
22
+ items << %Q["#{k}"=>#{v.inspect}]
23
+ end
24
+ "#<#{self.class.name}: {#{items.join(', ')}}>"
25
+ end
26
+
27
+ def [](key)
28
+ self.to_hash[key]
29
+ end
30
+
31
+ def []=(key, value)
32
+ delete_all(key)
33
+ if value.is_a?(Array)
34
+ value.each do |v|
35
+ self.add(key, v)
36
+ end
37
+ else
38
+ self.add(key, value)
39
+ end
40
+ end
41
+
42
+ def delete_all(key)
43
+ del = true
44
+ while(del) do
45
+ del = self.delete(key)
46
+ end
21
47
  end
22
48
  end
@@ -1,3 +1,3 @@
1
1
  module Exiv2
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -1,9 +1,10 @@
1
1
  require 'exiv2'
2
+ require 'fileutils'
2
3
 
3
4
  describe Exiv2 do
4
5
 
5
6
  it "should handle a Pathname being passed to open" do
6
- image = Exiv2::ImageFactory.open(Pathname.new("spec/files/test.jpg"))
7
+ image = Exiv2::ImageFactory.open(Pathname.new("spec/files/test.jpg").to_s)
7
8
  image.read_metadata
8
9
  image.iptc_data.to_hash.should_not be_empty
9
10
  end
@@ -14,6 +15,20 @@ describe Exiv2 do
14
15
  }.should raise_error(Exiv2::BasicError)
15
16
  end
16
17
 
18
+ it "should write metadata" do
19
+ FileUtils.cp("spec/files/test.jpg", "spec/files/test_tmp.jpg")
20
+ image = Exiv2::ImageFactory.open("spec/files/test_tmp.jpg")
21
+ image.read_metadata
22
+ image.iptc_data["Iptc.Application2.Caption"] = "A New Caption"
23
+ image.write_metadata
24
+ image = nil
25
+
26
+ image2 = Exiv2::ImageFactory.open("spec/files/test_tmp.jpg")
27
+ image2.read_metadata
28
+ image2.iptc_data["Iptc.Application2.Caption"].should == "A New Caption"
29
+ FileUtils.rm("spec/files/test_tmp.jpg")
30
+ end
31
+
17
32
  let(:image) do
18
33
  image = Exiv2::ImageFactory.open("spec/files/test.jpg")
19
34
  image.read_metadata
@@ -43,6 +58,36 @@ describe Exiv2 do
43
58
  "Iptc.Application2.Keywords" => ["fish", "custard"]
44
59
  }
45
60
  end
61
+
62
+ it "should write IPTC data" do
63
+ @iptc_data.add("Iptc.Application2.Keywords", "fishy")
64
+ @iptc_data.to_a.should == [
65
+ ["Iptc.Application2.Caption", "Rhubarb rhubarb rhubard"],
66
+ ["Iptc.Application2.Keywords", "fish"],
67
+ ["Iptc.Application2.Keywords", "custard"],
68
+ ["Iptc.Application2.Keywords", "fishy"]
69
+ ]
70
+ end
71
+
72
+ it "should set IPTC data" do
73
+ @iptc_data["Iptc.Application2.Caption"] = "A New Caption"
74
+ @iptc_data.to_hash["Iptc.Application2.Caption"].should == "A New Caption"
75
+ end
76
+
77
+ it "should set multiply IPTC data values" do
78
+ @iptc_data["Iptc.Application2.Keywords"] = ["abc", "cde"]
79
+ @iptc_data.to_hash["Iptc.Application2.Keywords"].should == ["abc", "cde"]
80
+ end
81
+
82
+ it "should delete one value of IPTC data" do
83
+ @iptc_data.delete("Iptc.Application2.Keywords")
84
+ @iptc_data.to_hash["Iptc.Application2.Keywords"].should == "custard"
85
+ end
86
+
87
+ it "should delete all values of IPTC data" do
88
+ @iptc_data.delete_all("Iptc.Application2.Keywords")
89
+ @iptc_data.to_hash["Iptc.Application2.Keywords"].should == nil
90
+ end
46
91
  end
47
92
 
48
93
  context "XMP data" do
@@ -68,6 +113,31 @@ describe Exiv2 do
68
113
  }
69
114
  end
70
115
 
116
+ it "should write XMP data" do
117
+ @xmp_data["Xmp.dc.title"] = "lang=\"x-default\" Changed!"
118
+ @xmp_data.to_hash["Xmp.dc.title"].should == "lang=\"x-default\" Changed!"
119
+ end
120
+
121
+ it "should set XMP data" do
122
+ @xmp_data["Xmp.dc.title"] = "A New Title"
123
+ @xmp_data.to_hash["Xmp.dc.title"].should == "lang=\"x-default\" A New Title"
124
+ end
125
+
126
+ it "should set multiply XMP data values" do
127
+ @xmp_data["Xmp.dc.title"] = ["abc", "cde"]
128
+ @xmp_data.to_hash["Xmp.dc.title"].should == ["lang=\"x-default\" abc", "lang=\"x-default\" cde"]
129
+ end
130
+
131
+ it "should delete one value of XMP data" do
132
+ @xmp_data["Xmp.dc.title"] = ["abc", "cde"]
133
+ @xmp_data.delete("Xmp.dc.title")
134
+ @xmp_data.to_hash["Xmp.dc.title"].should == "lang=\"x-default\" cde"
135
+ end
136
+
137
+ it "should delete all values of XMP data" do
138
+ @xmp_data.delete_all("Xmp.dc.title")
139
+ @xmp_data.to_hash["Xmp.dc.title"].should == nil
140
+ end
71
141
  end
72
142
 
73
143
  context "EXIF data" do
@@ -77,7 +147,7 @@ describe Exiv2 do
77
147
 
78
148
  it "should read Exif data" do
79
149
  @exif_data.should be_a(Exiv2::ExifData)
80
- @exif_data.inspect.should == "#<Exiv2::ExifData: {\"Exif.Photo.PixelXDimension\"=>\"32\", \"Exif.Photo.ExifVersion\"=>\"48 50 49 48\", \"Exif.Image.Software\"=>\"plasq skitch\", \"Exif.Photo.PixelYDimension\"=>\"32\", \"Exif.Image.ExifTag\"=>\"52\"}>"
150
+ @exif_data.inspect.should == '#<Exiv2::ExifData: {"Exif.Image.ExifTag"=>"52", "Exif.Image.Software"=>"plasq skitch", "Exif.Photo.ExifVersion"=>"48 50 49 48", "Exif.Photo.PixelXDimension"=>"32", "Exif.Photo.PixelYDimension"=>"32"}>'
81
151
  @exif_data.to_a.should == [
82
152
  ["Exif.Image.Software", "plasq skitch"],
83
153
  ["Exif.Image.ExifTag", "52"],
@@ -99,5 +169,36 @@ describe Exiv2 do
99
169
  }
100
170
  end
101
171
 
172
+ it "should write Exif data" do
173
+ @exif_data.add("Exif.Image.Software", "ruby-exiv2")
174
+ @exif_data.to_hash.should == {
175
+ "Exif.Photo.PixelXDimension" => "32",
176
+ "Exif.Photo.ExifVersion" => "48 50 49 48",
177
+ "Exif.Image.Software" => ["plasq skitch", "ruby-exiv2"],
178
+ "Exif.Photo.PixelYDimension" => "32",
179
+ "Exif.Image.ExifTag" => "52"
180
+ }
181
+ end
182
+
183
+ it "should set Exif data" do
184
+ @exif_data["Exif.Image.Software"] = "ruby-exiv2"
185
+ @exif_data.to_hash["Exif.Image.Software"].should == "ruby-exiv2"
186
+ end
187
+
188
+ it "should set multiply Exif data values" do
189
+ @exif_data["Exif.Image.Software"] = ["ruby-exiv2", "plasq skitch"]
190
+ @exif_data.to_hash["Exif.Image.Software"].should == ["ruby-exiv2", "plasq skitch"]
191
+ end
192
+
193
+ it "should delete one value of Exif data" do
194
+ @exif_data["Exif.Image.Software"] = ["ruby-exiv2", "plasq skitch"]
195
+ @exif_data.delete("Exif.Image.Software")
196
+ @exif_data.to_hash["Exif.Image.Software"].should == "plasq skitch"
197
+ end
198
+
199
+ it "should delete all values of Exif data" do
200
+ @exif_data.delete_all("Exif.Image.Software")
201
+ @exif_data.to_hash["Exif.Image.Software"].should == nil
202
+ end
102
203
  end
103
204
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exiv2
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 5
10
- version: 0.0.5
9
+ - 6
10
+ version: 0.0.6
11
11
  platform: ruby
12
12
  authors:
13
13
  - Pete Yandell
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-08-23 00:00:00 +10:00
18
+ date: 2011-11-11 00:00:00 +11:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency