xmp 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4275b1e67258b6a6b63a3b6c1a61bdaf9b5caf5fa6a6669008ecd6c43d58dd00
4
+ data.tar.gz: 7bff3b97c76db9f79ed337c84f18177680de92bb48edcd4400f25d8888bf5779
5
+ SHA512:
6
+ metadata.gz: 1056ea809c5e851a3c9b01778a4c6929d73a1f93df0ed592e75d0aca6bb98841156159b744aba7279b6ce4302d6af8d3486ddfae041558f29f4f2b27a3a85f41
7
+ data.tar.gz: e41d39d8e28bcd2a610257207d24be0c2fd58072e013c9b6c7a1ee4b0fd4fa8a0a29f3ce9bfb0ea4878e97c924716c4c20614545ed2b8f4822e965203c12e1f4
data/.gitignore CHANGED
@@ -1,4 +1,7 @@
1
+ *.swp
1
2
  *.gem
2
3
  .bundle
3
4
  Gemfile.lock
4
5
  pkg/*
6
+ /xmp.xml
7
+ .DS_Store
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ rvm:
2
+ - 2.7.8
3
+ - 3.1.6
4
+ - 3.2.5
5
+ - 3.3.5
6
+ - 3.4.0-preview1
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # xmp - Extensible Metadata Platform (XMP) parser
2
+
3
+ MP provides object oriented interface to [XMP data](http://en.wikipedia.org/wiki/Extensible_Metadata_Platform). XMP data can be found in PDF, JPEG, GIF, PNG, and many other formats.
4
+
5
+ ## Supported Formats
6
+
7
+ Format | File extension | Additional dependency
8
+ ----------|-----------------|-----------------------
9
+ JPEG | .jpeg, .jpg | [exifr/jpeg](https://github.com/remvee/exifr)
10
+ TIFF | .tiff, .tif | [exifr/tiff](https://github.com/remvee/exifr)
11
+ XMP Files | .xmp, .xml | none
12
+
13
+ ## Usage
14
+
15
+ ``` ruby
16
+ require 'xmp'
17
+ xmp = XMP.new('example.jpg')
18
+ xmp.dc.subject # => ["something interesting"]
19
+
20
+ xmp.namespaces.each do |namespace|
21
+ xmp[namespace].attributes.each do |attribute|
22
+ puts "#{namespace}.#{attribute}: #{xmp[namespace][attribute].inspect}"
23
+ end
24
+ end
25
+ ```
26
+
27
+ `XMP.new` accepts:
28
+ * Paths to files (it will choose based on file extension)
29
+ * File objects (it will choose based on file extension)
30
+ * Nokogiri documents or XML strings
31
+
32
+ Attributes can be accessed in the following ways:
33
+
34
+ ``` ruby
35
+ # assuming an XMP entry AttributeName within the NamespaceName namespace, these are all equivalent
36
+ xmp['NamespaceName']['AttributeName']
37
+ xmp.namespace_name.attribute_name
38
+ xmp.NamespaceName.AttributeName
39
+
40
+ # you can also mix them
41
+ xmp.namespace_name['AttributeName']
42
+
43
+ # or convert the data to an actual hash
44
+ xmp.to_h # => { 'NamespaceName' => { … }, … }
45
+ ```
46
+
47
+ ## Installation
48
+
49
+ ``` shell
50
+ $ gem install xmp
51
+ $ gem install exifr # optional, for jpeg/tiff support
52
+ ```
53
+
54
+ Or you can add it to your Gemfile:
55
+
56
+ ``` ruby
57
+ gem 'xmp', '~> 1.0'
58
+ gem 'exifr', '~> 1.3'
59
+ ```
60
+
61
+ ## Requirements
62
+ * Ruby 2 or Ruby 3
63
+ * Nokogiri (1.10 or newer, gem dependency - will be installed automatically)
64
+ * EXIFR (1.3 or newer) - optional
65
+
66
+ ## Development
67
+
68
+ Fork it at https://github.com/amberbit/xmp
69
+
70
+ ``` shell
71
+ $ bundle install # install development dependencies
72
+ $ rake spec # run specs
73
+ ```
74
+
75
+ ## License
76
+ Ruby's license.
77
+
78
+ Copyright (c) 2011 Wojciech Piekutowski, AmberBit (<http://amberbit.com>) and contributors.
data/Rakefile CHANGED
@@ -8,5 +8,4 @@ desc "Run all specs"
8
8
  RSpec::Core::RakeTask.new do |t|
9
9
  t.ruby_opts = "-w"
10
10
  t.verbose = true
11
- t.skip_bundler = true
12
11
  end
@@ -0,0 +1,38 @@
1
+ module XMP::Convenience
2
+ def include?(key)
3
+ key_map.include? key
4
+ end
5
+
6
+ def [](key)
7
+ return unless key = key_map[key]
8
+ @entries ||= {}
9
+ @entries[key] ||= get(key)
10
+ end
11
+
12
+ def respond_to_missing?(method_name, include_private = false)
13
+ include?(key) or super
14
+ end
15
+
16
+ def to_h
17
+ list.map do |key|
18
+ result = self[key]
19
+ result = result.to_h if result.is_a? XMP::Convenience
20
+ [key, result]
21
+ end.to_h
22
+ end
23
+
24
+ private
25
+
26
+ def method_missing(method_name, *arguments, &block)
27
+ return super unless include? method_name
28
+ raise ArgumentError, "wrong number of arguments (given #{arguments.size}, expected 0)" if arguments.any?
29
+ self[method_name]
30
+ end
31
+
32
+ def key_map
33
+ @key_map ||= list.inject({}) do |map, key|
34
+ underscore_key = key.gsub(/([a-z])([^a-z])|(\d)(\D)|(\D)(\d)/, '\1\3\5_\2\4\6').tr('-', '_').downcase
35
+ map.merge!(key => key, key.to_sym => key, underscore_key => key, underscore_key.to_sym => key)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,24 @@
1
+ class XMP::Document
2
+ include XMP::Convenience
3
+
4
+ attr_reader :namespaces
5
+ attr_reader :xml
6
+
7
+ def initialize(doc)
8
+ @xml = doc.root
9
+ @namespaces = doc.collect_namespaces.map do |ns, url|
10
+ @xml.add_namespace_definition ns, url
11
+ ns[/^(?:xmlns:)?xmlns:(.+)/, 1]
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def list
18
+ namespaces
19
+ end
20
+
21
+ def get(key)
22
+ XMP::Namespace.new(self, key)
23
+ end
24
+ end
data/lib/xmp/error.rb ADDED
@@ -0,0 +1,4 @@
1
+ module XMP
2
+ Error = Class.new(RuntimeError)
3
+ NoXMP = Class.new(Error)
4
+ end
@@ -0,0 +1,22 @@
1
+ begin
2
+ require 'exifr/jpeg'
3
+ require 'exifr/tiff'
4
+ rescue LoadError => exception
5
+ raise exception unless exception.path == 'exifr/jpeg'
6
+ end
7
+
8
+ module XMP::Handler
9
+ class Exifr
10
+ def call(object)
11
+ return unless defined?(EXIFR::JPEG)
12
+ case object
13
+ when EXIFR::JPEG then format, xmp_data = 'jpeg', object.app1s.find { |a| a =~ %r|\Ahttp://ns.adobe.com/xap/1.0/| }&.split("\000")[1]
14
+ when EXIFR::TIFF then format, xmp_data = 'tiff', object.xmp
15
+ else return
16
+ end
17
+
18
+ raise XMP::NoXMP, "XMP section missing from #{format}" unless xmp_data
19
+ xmp_data
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ require 'pathname'
2
+
3
+ module XMP::Handler
4
+ class File
5
+ SUPPORTED = [String, Pathname, ::File]
6
+ attr_reader :extensions, :callback
7
+
8
+ def initialize(*extensions, &callback)
9
+ @extensions = extensions
10
+ @callback = callback
11
+ end
12
+
13
+ def call(object)
14
+ return unless SUPPORTED.any? { |c| c === object }
15
+ return unless path = Pathname(object) and extensions.include? path.extname.downcase
16
+ return callback.call(object) if object.is_a? IO
17
+ return path.open(&callback) if path.exist? and path.readable?
18
+ raise XMP::Error, "cannot read file #{path}"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ require 'nokogiri'
2
+
3
+ module XMP::Handler
4
+ class XML
5
+ def call(object)
6
+ object = Nokogiri::XML(object) if object.is_a? String
7
+ XMP::Document.new(object) if object.is_a? Nokogiri::XML::Document and object.root
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,23 @@
1
+ module XMP::Handler
2
+ attr_accessor :handlers
3
+
4
+ def new(object)
5
+ last_error = nil
6
+ result = object
7
+
8
+ handlers.each do |handler|
9
+ begin
10
+ next unless new_result = handler.call(result)
11
+ return new_result if new_result.is_a? XMP::Document
12
+ result = new_result
13
+ rescue XMP::Error => error
14
+ last_error = error
15
+ end
16
+ end
17
+
18
+ raise last_error if last_error
19
+ raise XMP::Error, "cannot handle #{object.inspect}"
20
+ end
21
+
22
+ alias_method :parse, :new
23
+ end
data/lib/xmp/namespace.rb CHANGED
@@ -1,69 +1,52 @@
1
- class XMP
2
- class Namespace
3
- # available attributes
4
- attr_reader :attributes
1
+ class XMP::Namespace
2
+ include XMP::Convenience
3
+ attr_reader :document, :namespace, :standalone_attributes, :embedded_attributes
5
4
 
6
- def initialize(xmp, namespace) # :nodoc
7
- @xmp = xmp
8
- @namespace = namespace.to_s
9
-
10
- @attributes = []
11
- embedded_attributes =
12
- xml.xpath("//rdf:Description").map { |d|
13
- d.attributes.values.
14
- select { |attr| attr.namespace.prefix.to_s == @namespace }.
15
- map(&:name)
16
- }.flatten
17
- @attributes.concat embedded_attributes
18
- standalone_attributes = xml.xpath("//rdf:Description/#{@namespace}:*").
19
- map(&:name)
20
- @attributes.concat standalone_attributes
5
+ def initialize(document, namespace)
6
+ @document, @namespace = document, namespace
7
+ @standalone_attributes = xml.xpath("//rdf:Description/#{namespace}:*").map(&:name)
8
+ @embedded_attributes = xml.xpath('//rdf:Description').flat_map do |description|
9
+ description.attributes.values.select { |attr| attr.namespace.prefix.to_s == namespace }.map(&:name)
21
10
  end
11
+ end
12
+
13
+ def attributes
14
+ @attributes ||= (standalone_attributes + embedded_attributes).uniq
15
+ end
22
16
 
23
- def inspect
24
- "#<XMP::Namespace:#{@namespace}>"
25
- end
17
+ private
26
18
 
27
- def method_missing(method, *args)
28
- if has_attribute?(method)
29
- embedded_attribute(method) || standalone_attribute(method)
30
- else
31
- super
32
- end
33
- end
19
+ def xml
20
+ document.xml
21
+ end
34
22
 
35
- def respond_to?(method)
36
- has_attribute?(method) or super
37
- end
23
+ def list
24
+ attributes
25
+ end
38
26
 
39
- private
27
+ def get(name)
28
+ embedded_attributes.include?(name) ? get_embedded(name) : get_standalone(name)
29
+ end
40
30
 
41
- def embedded_attribute(name)
42
- element = xml.at("//rdf:Description[@#{@namespace}:#{name}]")
43
- return unless element
44
- element.attribute(name.to_s).text
45
- end
31
+ def get_embedded(name)
32
+ return unless element = xml.at("//rdf:Description[@#{namespace}:#{name}]")
33
+ element.attribute(name.to_s).text
34
+ end
46
35
 
47
- def has_attribute?(name)
48
- attributes.include?(name.to_s)
36
+ def get_standalone(name)
37
+ return unless attribute = xml.xpath("//#{namespace}:#{name}").first
38
+ if list = attribute.xpath("./rdf:Bag | ./rdf:Seq | ./rdf:Alt").first
39
+ return list.xpath("./rdf:li").map(&:text)
49
40
  end
50
41
 
51
- def standalone_attribute(name)
52
- attribute_xpath = "//#{@namespace}:#{name}"
53
- attribute = xml.xpath(attribute_xpath).first
54
- return unless attribute
42
+ hash = {}
43
+ attribute.element_children.each { |c| hash[c.name] = c.text }
44
+ attribute.attributes.each { |k, v| hash[k] = v.value } if hash.empty?
45
+ return hash unless hash.empty?
55
46
 
56
- array_value = attribute.xpath("./rdf:Bag | ./rdf:Seq | ./rdf:Alt").first
57
- if array_value
58
- items = array_value.xpath("./rdf:li")
59
- items.map { |i| i.text }
60
- else
61
- raise "Don't know how to handle: \n" + attribute.to_s
62
- end
63
- end
47
+ text = attribute.text.to_s.strip
48
+ return text if text.length > 0
64
49
 
65
- def xml
66
- @xmp.xml
67
- end
50
+ raise XMP::Error, "Don't know how to handle: \n" + attribute.to_s
68
51
  end
69
- end
52
+ end
data/lib/xmp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
- class XMP
2
- VERSION = "0.2.0"
1
+ module XMP
2
+ VERSION = '1.0.0'
3
3
  end
data/lib/xmp.rb CHANGED
@@ -1,70 +1,20 @@
1
- require 'xmp/silencer'
2
- XMP::Silencer.silently { require 'nokogiri' }
3
- require 'xmp/namespace'
4
-
5
- # = XMP XML parser
6
- #
7
- # == Example
8
- # xmp = XMP.new(File.read('xmp.xml'))
9
- # xmp.dc.title # => "Amazing Photo"
10
- # xmp.photoshop.Category # => "summer"
11
- # xmp.photoshop.SupplementalCategories # => ["morning", "sea"]
12
- class XMP
13
- # underlying XML content
14
- attr_reader :xml
15
- # available namespace names
16
- attr_reader :namespaces
17
-
18
- # accepts valid XMP XML
19
- def initialize(xml)
20
- doc = Nokogiri::XML(xml)
21
- @xml = doc.root
22
-
23
- available_namespaces = doc.collect_namespaces
24
- # let nokogiri know about all namespaces
25
- available_namespaces.each do |ns, url|
26
- @xml.add_namespace_definition ns, url
27
- end
28
-
29
- # collect namespace names
30
- @namespaces = available_namespaces.collect do |ns, _|
31
- ns =~ /^xmlns:(.+)/
32
- $1
33
- end
34
- end
35
-
36
- def inspect
37
- "#<XMP:@namespaces=#{@namespaces.inspect}>"
38
- end
39
-
40
- # returns Namespace object if namespace exists, otherwise tries to call a method
41
- def method_missing(namespace, *args)
42
- if has_namespace?(namespace)
43
- Namespace.new(self, namespace)
44
- else
45
- super
46
- end
47
- end
48
-
49
- def respond_to?(method)
50
- has_namespace?(method) or super
51
- end
52
-
53
- def self.parse(doc)
54
- case doc.class.to_s
55
- when 'EXIFR::JPEG'
56
- if xmp_chunk = doc.app1s.find { |a| a =~ %r|\Ahttp://ns.adobe.com/xap/1.0/| }
57
- xmp_data = xmp_chunk.split("\000")[1]
58
- XMP.new(xmp_data)
59
- end
60
- else
61
- raise "Document not supported:\n#{doc.inspect}"
62
- end
63
- end
64
-
65
- private
66
-
67
- def has_namespace?(namespace)
68
- namespaces.include?(namespace.to_s)
69
- end
70
- end
1
+ module XMP
2
+ require 'xmp/convenience'
3
+ require 'xmp/document'
4
+ require 'xmp/error'
5
+ require 'xmp/handler'
6
+ require 'xmp/handler/exifr'
7
+ require 'xmp/handler/file'
8
+ require 'xmp/handler/xml'
9
+ require 'xmp/namespace'
10
+ require 'xmp/version'
11
+
12
+ extend Handler
13
+ self.handlers = [
14
+ Handler::File.new('.jpg', '.jpeg') { |file| EXIFR::JPEG.new(file) },
15
+ Handler::File.new('.tif', '.tiff') { |file| EXIFR::TIFF.new(file) },
16
+ Handler::File.new('.xmp', '.xml') { |file| Nokogiri::XML(file) },
17
+ Handler::Exifr.new,
18
+ Handler::XML.new
19
+ ]
20
+ end
@@ -1,13 +1,23 @@
1
1
  require './spec/spec_helper.rb'
2
+ require 'exifr/jpeg'
2
3
 
3
4
  describe "XMP with EXIFR::JPEG" do
4
- before do
5
- XMP::Silencer.silently { @img = EXIFR::JPEG.new('spec/fixtures/multiple-app1.jpg') }
5
+ it "should parse image given as path" do
6
+ xmp = XMP.parse('spec/fixtures/multiple-app1.jpg')
7
+ xmp.should be_instance_of(XMP::Document)
8
+ xmp.namespaces.should =~ %w{dc iX pdf photoshop rdf tiff x xap xapRights}
9
+ end
10
+
11
+ it "should parse image given as path with upper case extension" do
12
+ xmp = XMP.parse('spec/fixtures/UPPERCASE.JPG')
13
+ xmp.should be_instance_of(XMP::Document)
14
+ xmp.namespaces.should =~ %w{dc iX pdf photoshop rdf tiff x xap xapRights}
6
15
  end
7
16
 
8
- it "should parse image" do
9
- xmp = XMP.parse(@img)
10
- xmp.should be_instance_of(XMP)
17
+ it "should parse image given as EXIFR::JPEG" do
18
+ img = EXIFR::JPEG.new('spec/fixtures/multiple-app1.jpg')
19
+ xmp = XMP.parse(img)
20
+ xmp.should be_instance_of(XMP::Document)
11
21
  xmp.namespaces.should =~ %w{dc iX pdf photoshop rdf tiff x xap xapRights}
12
22
  end
13
23
  end
@@ -0,0 +1,17 @@
1
+ require './spec/spec_helper.rb'
2
+ require 'exifr/tiff'
3
+
4
+ describe "XMP with EXIFR::TIFF" do
5
+ it "should parse image given as path" do
6
+ xmp = XMP.parse('spec/fixtures/multiple-app1.tiff')
7
+ xmp.should be_instance_of(XMP::Document)
8
+ xmp.namespaces.should =~ %w{dc iX pdf photoshop rdf tiff x xap xapRights}
9
+ end
10
+
11
+ it "should parse image given as EXIFR::TIFF" do
12
+ img = EXIFR::TIFF.new('spec/fixtures/multiple-app1.tiff')
13
+ xmp = XMP.parse(img)
14
+ xmp.should be_instance_of(XMP::Document)
15
+ xmp.namespaces.should =~ %w{dc iX pdf photoshop rdf tiff x xap xapRights}
16
+ end
17
+ end
Binary file
Binary file
@@ -192,6 +192,7 @@
192
192
  <rdf:li>255, 255</rdf:li>
193
193
  </rdf:Seq>
194
194
  </crs:ToneCurve>
195
+ <Iptc4xmpCore:CreatorContactInfo Iptc4xmpCore:CiAdrCtry="Germany" Iptc4xmpCore:CiAdrCity="Berlin" />
195
196
  </rdf:Description>
196
197
  </rdf:RDF>
197
198
  </x:xmpmeta>
@@ -0,0 +1,62 @@
1
+ <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
2
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
3
+ <rdf:Description xmlns:aux="http://ns.adobe.com/exif/1.0/aux/" xmlns:Iptc4xmpCore="http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:xmpRights="http://ns.adobe.com/xap/1.0/rights/" rdf:about="">
4
+ <aux:SerialNumber>1981003444</aux:SerialNumber>
5
+ <aux:FlashCompensation>0/1</aux:FlashCompensation>
6
+ <aux:ImageNumber>0</aux:ImageNumber>
7
+ <aux:LensID>237</aux:LensID>
8
+ <aux:Firmware>2.0.3</aux:Firmware>
9
+ <aux:LensInfo>24/1 105/1 0/0 0/0</aux:LensInfo>
10
+ <aux:Lens>EF24-105mm f/4L IS USM</aux:Lens>
11
+ <Iptc4xmpCore:Location>Ph&#x1EA1;m &#x110;&#xEC;nh H&#x1ED3;</Iptc4xmpCore:Location>
12
+ <Iptc4xmpCore:CreatorContactInfo rdf:parseType="Resource">
13
+ <Iptc4xmpCore:CiAdrCtry>Germany</Iptc4xmpCore:CiAdrCtry>
14
+ <Iptc4xmpCore:CiAdrCity>Berlin</Iptc4xmpCore:CiAdrCity>
15
+ </Iptc4xmpCore:CreatorContactInfo>
16
+ <xmp:ModifyDate>2014-07-06T17:54:07</xmp:ModifyDate>
17
+ <xmp:CreateDate>2012-12-23T10:38:45</xmp:CreateDate>
18
+ <xmp:CreatorTool>Adobe Photoshop Lightroom 4.4 (Macintosh)</xmp:CreatorTool>
19
+ <xmp:Rating>2</xmp:Rating>
20
+ <dc:subject>
21
+ <rdf:Bag>
22
+ <rdf:li>2012</rdf:li>
23
+ <rdf:li>Asia</rdf:li>
24
+ <rdf:li>Hanoi</rdf:li>
25
+ <rdf:li>Vietnam</rdf:li>
26
+ <rdf:li>food</rdf:li>
27
+ <rdf:li>frog</rdf:li>
28
+ <rdf:li>travel</rdf:li>
29
+ </rdf:Bag>
30
+ </dc:subject>
31
+ <dc:description>
32
+ <rdf:Alt>
33
+ <rdf:li xml:lang="x-default">This is the Caption</rdf:li>
34
+ </rdf:Alt>
35
+ </dc:description>
36
+ <dc:rights>
37
+ <rdf:Alt>
38
+ <rdf:li xml:lang="x-default">&#xA9; Jens Kr&#xE4;mer</rdf:li>
39
+ </rdf:Alt>
40
+ </dc:rights>
41
+ <dc:title>
42
+ <rdf:Alt>
43
+ <rdf:li xml:lang="x-default">The title</rdf:li>
44
+ </rdf:Alt>
45
+ </dc:title>
46
+ <dc:creator>
47
+ <rdf:Seq>
48
+ <rdf:li>Jens Kr&#xE4;mer</rdf:li>
49
+ </rdf:Seq>
50
+ </dc:creator>
51
+ <photoshop:City>Hanoi</photoshop:City>
52
+ <photoshop:State>Hanoi</photoshop:State>
53
+ <photoshop:Country>Vietnam</photoshop:Country>
54
+ <photoshop:DateCreated>2012-12-23T10:38:45</photoshop:DateCreated>
55
+ <xmpRights:UsageTerms>
56
+ <rdf:Alt>
57
+ <rdf:li xml:lang="x-default">All rights reserved</rdf:li>
58
+ </rdf:Alt>
59
+ </xmpRights:UsageTerms>
60
+ </rdf:Description>
61
+ </rdf:RDF>
62
+ </x:xmpmeta>
@@ -0,0 +1,66 @@
1
+ <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.5-c002 1.148022, 2012/07/15-18:06:45 ">
2
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
3
+ <rdf:Description xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:aux="http://ns.adobe.com/exif/1.0/aux/" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xmpRights="http://ns.adobe.com/xap/1.0/rights/" xmlns:Iptc4xmpCore="http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/" xmlns:crs="http://ns.adobe.com/camera-raw-settings/1.0/" rdf:about="" xmp:ModifyDate="2014-06-22T17:42:46+02:00" xmp:CreateDate="2012-12-23T23:39:14" xmp:MetadataDate="2014-06-22T17:42:46+02:00" xmp:Rating="2" xmp:CreatorTool="Adobe Photoshop Lightroom 4.4 (Macintosh)" aux:LensInfo="5200/1000 26000/1000 0/0 0/0" aux:Lens="5.2-26.0 mm" aux:ApproximateFocusDistance="298/100" aux:FlashCompensation="0/1" aux:Firmware="1.01" photoshop:DateCreated="2012-12-23T23:39:14" photoshop:City="Hanoi" photoshop:State="Hanoi" photoshop:Country="Vietnam" xmpMM:DocumentID="xmp.did:28b12b53-d653-4f0a-80ba-67c9f60d68bb" xmpMM:OriginalDocumentID="01AD727BA286F4EF2A360236E69DDE89" xmpMM:InstanceID="xmp.iid:28b12b53-d653-4f0a-80ba-67c9f60d68bb" dc:format="image/jpeg" xmpRights:Marked="True" Iptc4xmpCore:Location="Ph&#x1EA1;m &#x110;&#xEC;nh H&#x1ED3;" crs:Version="7.4" crs:ProcessVersion="6.7" crs:WhiteBalance="As Shot" crs:Temperature="3100" crs:Tint="+18" crs:Saturation="0" crs:Sharpness="30" crs:LuminanceSmoothing="50" crs:ColorNoiseReduction="25" crs:VignetteAmount="0" crs:ShadowTint="0" crs:RedHue="0" crs:RedSaturation="0" crs:GreenHue="0" crs:GreenSaturation="0" crs:BlueHue="0" crs:BlueSaturation="0" crs:Vibrance="+10" crs:HueAdjustmentRed="0" crs:HueAdjustmentOrange="0" crs:HueAdjustmentYellow="0" crs:HueAdjustmentGreen="0" crs:HueAdjustmentAqua="0" crs:HueAdjustmentBlue="0" crs:HueAdjustmentPurple="0" crs:HueAdjustmentMagenta="0" crs:SaturationAdjustmentRed="0" crs:SaturationAdjustmentOrange="0" crs:SaturationAdjustmentYellow="0" crs:SaturationAdjustmentGreen="0" crs:SaturationAdjustmentAqua="0" crs:SaturationAdjustmentBlue="0" crs:SaturationAdjustmentPurple="0" crs:SaturationAdjustmentMagenta="0" crs:LuminanceAdjustmentRed="0" crs:LuminanceAdjustmentOrange="0" crs:LuminanceAdjustmentYellow="0" crs:LuminanceAdjustmentGreen="0" crs:LuminanceAdjustmentAqua="0" crs:LuminanceAdjustmentBlue="0" crs:LuminanceAdjustmentPurple="0" crs:LuminanceAdjustmentMagenta="0" crs:SplitToningShadowHue="0" crs:SplitToningShadowSaturation="0" crs:SplitToningHighlightHue="0" crs:SplitToningHighlightSaturation="0" crs:SplitToningBalance="0" crs:ParametricShadows="0" crs:ParametricDarks="0" crs:ParametricLights="0" crs:ParametricHighlights="0" crs:ParametricShadowSplit="25" crs:ParametricMidtoneSplit="50" crs:ParametricHighlightSplit="75" crs:SharpenRadius="+1.0" crs:SharpenDetail="25" crs:SharpenEdgeMasking="0" crs:PostCropVignetteAmount="0" crs:GrainAmount="0" crs:LuminanceNoiseReductionDetail="50" crs:ColorNoiseReductionDetail="50" crs:LuminanceNoiseReductionContrast="0" crs:LensProfileEnable="1" crs:LensManualDistortionAmount="0" crs:PerspectiveVertical="0" crs:PerspectiveHorizontal="0" crs:PerspectiveRotate="0.0" crs:PerspectiveScale="100" crs:AutoLateralCA="1" crs:Exposure2012="0.00" crs:Contrast2012="0" crs:Highlights2012="-28" crs:Shadows2012="+28" crs:Whites2012="+4" crs:Blacks2012="+26" crs:Clarity2012="+15" crs:DefringePurpleAmount="0" crs:DefringePurpleHueLo="30" crs:DefringePurpleHueHi="70" crs:DefringeGreenAmount="0" crs:DefringeGreenHueLo="40" crs:DefringeGreenHueHi="60" crs:ConvertToGrayscale="False" crs:ToneCurveName2012="Medium Contrast" crs:CameraProfile="Adobe Standard" crs:LensProfileSetup="LensDefaults" crs:LensProfileName="Adobe (Canon PowerShot S100)" crs:LensProfileFilename="Canon PowerShot S100 - RAW.lcp" crs:LensProfileDigest="D95499C6F0BF984F0C805DC729DE5F48" crs:LensProfileDistortionScale="100" crs:LensProfileChromaticAberrationScale="100" crs:LensProfileVignettingScale="100" crs:HasSettings="True" crs:HasCrop="False" crs:AlreadyApplied="True">
4
+ <xmpMM:History>
5
+ <rdf:Seq>
6
+ <rdf:li stEvt:action="derived" stEvt:parameters="converted from image/x-canon-cr2 to image/jpeg, saved to new location"/>
7
+ <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:28b12b53-d653-4f0a-80ba-67c9f60d68bb" stEvt:when="2014-06-22T17:42:46+02:00" stEvt:softwareAgent="Adobe Photoshop Lightroom 4.4 (Macintosh)" stEvt:changed="/"/>
8
+ </rdf:Seq>
9
+ </xmpMM:History>
10
+ <xmpMM:DerivedFrom stRef:documentID="01AD727BA286F4EF2A360236E69DDE89" stRef:originalDocumentID="01AD727BA286F4EF2A360236E69DDE89"/>
11
+ <dc:creator>
12
+ <rdf:Seq>
13
+ <rdf:li>Jens Kr&#xE4;mer</rdf:li>
14
+ </rdf:Seq>
15
+ </dc:creator>
16
+ <dc:rights>
17
+ <rdf:Alt>
18
+ <rdf:li xml:lang="x-default">&#xA9; Jens Kr&#xE4;mer</rdf:li>
19
+ </rdf:Alt>
20
+ </dc:rights>
21
+ <dc:subject>
22
+ <rdf:Bag>
23
+ <rdf:li>2012</rdf:li>
24
+ <rdf:li>Asia</rdf:li>
25
+ <rdf:li>Hanoi</rdf:li>
26
+ <rdf:li>Vietnam</rdf:li>
27
+ <rdf:li>travel</rdf:li>
28
+ </rdf:Bag>
29
+ </dc:subject>
30
+ <xmpRights:UsageTerms>
31
+ <rdf:Alt>
32
+ <rdf:li xml:lang="x-default">All rights reserved</rdf:li>
33
+ </rdf:Alt>
34
+ </xmpRights:UsageTerms>
35
+ <Iptc4xmpCore:CreatorContactInfo Iptc4xmpCore:CiAdrCtry="Germany" Iptc4xmpCore:CiAdrCity="Berlin"/>
36
+ <crs:ToneCurvePV2012>
37
+ <rdf:Seq>
38
+ <rdf:li>0, 0</rdf:li>
39
+ <rdf:li>32, 22</rdf:li>
40
+ <rdf:li>64, 56</rdf:li>
41
+ <rdf:li>128, 128</rdf:li>
42
+ <rdf:li>192, 196</rdf:li>
43
+ <rdf:li>255, 255</rdf:li>
44
+ </rdf:Seq>
45
+ </crs:ToneCurvePV2012>
46
+ <crs:ToneCurvePV2012Red>
47
+ <rdf:Seq>
48
+ <rdf:li>0, 0</rdf:li>
49
+ <rdf:li>255, 255</rdf:li>
50
+ </rdf:Seq>
51
+ </crs:ToneCurvePV2012Red>
52
+ <crs:ToneCurvePV2012Green>
53
+ <rdf:Seq>
54
+ <rdf:li>0, 0</rdf:li>
55
+ <rdf:li>255, 255</rdf:li>
56
+ </rdf:Seq>
57
+ </crs:ToneCurvePV2012Green>
58
+ <crs:ToneCurvePV2012Blue>
59
+ <rdf:Seq>
60
+ <rdf:li>0, 0</rdf:li>
61
+ <rdf:li>255, 255</rdf:li>
62
+ </rdf:Seq>
63
+ </crs:ToneCurvePV2012Blue>
64
+ </rdf:Description>
65
+ </rdf:RDF>
66
+ </x:xmpmeta>
@@ -0,0 +1,63 @@
1
+ <x:xmpmeta xmlns:x="adobe:ns:meta/" xmlns:xmlns:x="adobe:ns:meta/" xmlns:xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:xmlns:crs="http://ns.adobe.com/camera-raw-settings/1.0/" xmlns:xmlns:stArea="http://ns.adobe.com/xmp/sType/Area#" xmlns:xmlns:stDim="http://ns.adobe.com/xap/1.0/sType/Dimensions#" xmlns:xmlns:mwg-rs="http://www.metadataworkinggroup.com/schemas/regions/" xmlns:xmlns:Iptc4xmpExt="http://iptc.org/std/Iptc4xmpExt/2008-02-29/" xmlns:xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:xmlns:exifEX="http://cipa.jp/exif/1.0/" xmlns:xmlns:aux="http://ns.adobe.com/exif/1.0/aux/" xmlns:xmlns:xmp="http://ns.adobe.com/xap/1.0/" x:xmptk="Adobe XMP Core 5.6-c140 79.160451, 2017/05/06-01:08:21 ">
2
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
3
+ <rdf:Description xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:aux="http://ns.adobe.com/exif/1.0/aux/" xmlns:exifEX="http://cipa.jp/exif/1.0/" xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:Iptc4xmpExt="http://iptc.org/std/Iptc4xmpExt/2008-02-29/" xmlns:mwg-rs="http://www.metadataworkinggroup.com/schemas/regions/" xmlns:stDim="http://ns.adobe.com/xap/1.0/sType/Dimensions#" xmlns:stArea="http://ns.adobe.com/xmp/sType/Area#" xmlns:crs="http://ns.adobe.com/camera-raw-settings/1.0/" rdf:about="" xmp:ModifyDate="2020-11-04T18:52:39+01:00" xmp:CreateDate="2019-05-24T15:45:35.65+01:00" xmp:MetadataDate="2020-11-04T18:52:39+01:00" xmp:Label="2019 - 006 - Wedding Sabine &amp; Nils" xmp:CreatorTool="Adobe Photoshop Lightroom Classic 10.0 (Windows)" aux:SerialNumber="053021002918" aux:LensInfo="50/1 50/1 0/0 0/0" aux:Lens="RF50mm F1.2 L USM" aux:LensID="61182" aux:LensSerialNumber="0000000000" aux:ImageNumber="0" aux:ApproximateFocusDistance="249/100" aux:FlashCompensation="0/1" aux:Firmware="1.0.0" exifEX:LensModel="RF50mm F1.2 L USM" photoshop:DateCreated="2019-05-24T15:45:35.65+01:00" xmpMM:DocumentID="xmp.did:f20f4ff3-ea80-f04e-8064-c9ccb27a1133" xmpMM:PreservedFileName="IMG_0259.CR3" xmpMM:OriginalDocumentID="DDD8F3AA8B7D1C0F1BD0D46063A8AA0D" xmpMM:InstanceID="xmp.iid:f20f4ff3-ea80-f04e-8064-c9ccb27a1133" dc:format="image/jpeg" crs:RawFileName="IMG_0259.CR3" crs:Version="13.0" crs:ProcessVersion="11.0" crs:WhiteBalance="As Shot" crs:Temperature="6250" crs:Tint="+9" crs:Exposure2012="0.00" crs:Contrast2012="-43" crs:Highlights2012="0" crs:Shadows2012="+26" crs:Whites2012="+13" crs:Blacks2012="0" crs:Texture="0" crs:Clarity2012="0" crs:Dehaze="-3" crs:Vibrance="+38" crs:Saturation="-20" crs:ParametricShadows="0" crs:ParametricDarks="0" crs:ParametricLights="0" crs:ParametricHighlights="0" crs:ParametricShadowSplit="25" crs:ParametricMidtoneSplit="50" crs:ParametricHighlightSplit="75" crs:Sharpness="25" crs:SharpenRadius="+1.0" crs:SharpenDetail="25" crs:SharpenEdgeMasking="0" crs:LuminanceSmoothing="0" crs:ColorNoiseReduction="25" crs:ColorNoiseReductionDetail="50" crs:ColorNoiseReductionSmoothness="50" crs:HueAdjustmentRed="+5" crs:HueAdjustmentOrange="+3" crs:HueAdjustmentYellow="+31" crs:HueAdjustmentGreen="+15" crs:HueAdjustmentAqua="-40" crs:HueAdjustmentBlue="+15" crs:HueAdjustmentPurple="+2" crs:HueAdjustmentMagenta="-10" crs:SaturationAdjustmentRed="-4" crs:SaturationAdjustmentOrange="+7" crs:SaturationAdjustmentYellow="-54" crs:SaturationAdjustmentGreen="-72" crs:SaturationAdjustmentAqua="-24" crs:SaturationAdjustmentBlue="-30" crs:SaturationAdjustmentPurple="-23" crs:SaturationAdjustmentMagenta="+1" crs:LuminanceAdjustmentRed="+16" crs:LuminanceAdjustmentOrange="+22" crs:LuminanceAdjustmentYellow="+27" crs:LuminanceAdjustmentGreen="+47" crs:LuminanceAdjustmentAqua="+15" crs:LuminanceAdjustmentBlue="-10" crs:LuminanceAdjustmentPurple="+2" crs:LuminanceAdjustmentMagenta="+31" crs:SplitToningShadowHue="31" crs:SplitToningShadowSaturation="4" crs:SplitToningHighlightHue="138" crs:SplitToningHighlightSaturation="3" crs:SplitToningBalance="-62" crs:ColorGradeMidtoneHue="0" crs:ColorGradeMidtoneSat="0" crs:ColorGradeShadowLum="0" crs:ColorGradeMidtoneLum="0" crs:ColorGradeHighlightLum="0" crs:ColorGradeBlending="100" crs:ColorGradeGlobalHue="0" crs:ColorGradeGlobalSat="0" crs:ColorGradeGlobalLum="0" crs:AutoLateralCA="0" crs:LensProfileEnable="0" crs:LensManualDistortionAmount="0" crs:VignetteAmount="0" crs:DefringePurpleAmount="0" crs:DefringePurpleHueLo="30" crs:DefringePurpleHueHi="70" crs:DefringeGreenAmount="0" crs:DefringeGreenHueLo="40" crs:DefringeGreenHueHi="60" crs:PerspectiveUpright="0" crs:PerspectiveVertical="0" crs:PerspectiveHorizontal="0" crs:PerspectiveRotate="0.0" crs:PerspectiveAspect="0" crs:PerspectiveScale="100" crs:PerspectiveX="0.00" crs:PerspectiveY="0.00" crs:GrainAmount="0" crs:PostCropVignetteAmount="0" crs:ShadowTint="0" crs:RedHue="0" crs:RedSaturation="0" crs:GreenHue="0" crs:GreenSaturation="0" crs:BlueHue="0" crs:BlueSaturation="0" crs:ConvertToGrayscale="False" crs:OverrideLookVignette="False" crs:ToneCurveName2012="Custom" crs:CameraProfile="Adobe Standard" crs:CameraProfileDigest="38E531B7F49849F74EF6FA15106BD31D" crs:HasSettings="True" crs:CropTop="0" crs:CropLeft="0" crs:CropBottom="1" crs:CropRight="1" crs:CropAngle="0" crs:CropConstrainToWarp="0" crs:HasCrop="False" crs:AlreadyApplied="True">
4
+ <xmpMM:History>
5
+ <rdf:Seq>
6
+ <rdf:li stEvt:action="derived" stEvt:parameters="converted from image/x-canon-cr3 to image/jpeg, saved to new location"/>
7
+ <rdf:li stEvt:action="saved" stEvt:instanceID="xmp.iid:f20f4ff3-ea80-f04e-8064-c9ccb27a1133" stEvt:when="2020-11-04T18:52:39+01:00" stEvt:softwareAgent="Adobe Photoshop Lightroom Classic 10.0 (Windows)" stEvt:changed="/"/>
8
+ </rdf:Seq>
9
+ </xmpMM:History>
10
+ <xmpMM:DerivedFrom stRef:documentID="DDD8F3AA8B7D1C0F1BD0D46063A8AA0D" stRef:originalDocumentID="DDD8F3AA8B7D1C0F1BD0D46063A8AA0D"/>
11
+ <dc:subject>
12
+ <rdf:Bag>
13
+ <rdf:li>A Person</rdf:li>
14
+ </rdf:Bag>
15
+ </dc:subject>
16
+ <Iptc4xmpExt:PersonInImage>
17
+ <rdf:Bag>
18
+ <rdf:li>A Person</rdf:li>
19
+ </rdf:Bag>
20
+ </Iptc4xmpExt:PersonInImage>
21
+ <mwg-rs:Regions rdf:parseType="Resource">
22
+ <mwg-rs:AppliedToDimensions stDim:w="4500" stDim:h="3000" stDim:unit="pixel"/>
23
+ <mwg-rs:RegionList>
24
+ <rdf:Bag>
25
+ <rdf:li>
26
+ <rdf:Description mwg-rs:Rotation="0.00000" mwg-rs:Name="A Person" mwg-rs:Type="Face">
27
+ <mwg-rs:Area stArea:h="0.12757" stArea:w="0.08496" stArea:x="0.49268" stArea:y="0.42009"/>
28
+ </rdf:Description>
29
+ </rdf:li>
30
+ </rdf:Bag>
31
+ </mwg-rs:RegionList>
32
+ </mwg-rs:Regions>
33
+ <crs:ToneCurvePV2012>
34
+ <rdf:Seq>
35
+ <rdf:li>0, 22</rdf:li>
36
+ <rdf:li>48, 32</rdf:li>
37
+ <rdf:li>78, 56</rdf:li>
38
+ <rdf:li>103, 100</rdf:li>
39
+ <rdf:li>151, 166</rdf:li>
40
+ <rdf:li>255, 219</rdf:li>
41
+ </rdf:Seq>
42
+ </crs:ToneCurvePV2012>
43
+ <crs:ToneCurvePV2012Red>
44
+ <rdf:Seq>
45
+ <rdf:li>0, 0</rdf:li>
46
+ <rdf:li>255, 255</rdf:li>
47
+ </rdf:Seq>
48
+ </crs:ToneCurvePV2012Red>
49
+ <crs:ToneCurvePV2012Green>
50
+ <rdf:Seq>
51
+ <rdf:li>0, 0</rdf:li>
52
+ <rdf:li>255, 255</rdf:li>
53
+ </rdf:Seq>
54
+ </crs:ToneCurvePV2012Green>
55
+ <crs:ToneCurvePV2012Blue>
56
+ <rdf:Seq>
57
+ <rdf:li>0, 0</rdf:li>
58
+ <rdf:li>255, 255</rdf:li>
59
+ </rdf:Seq>
60
+ </crs:ToneCurvePV2012Blue>
61
+ </rdf:Description>
62
+ </rdf:RDF>
63
+ </x:xmpmeta>
data/spec/spec_helper.rb CHANGED
@@ -1,12 +1,8 @@
1
- $LOAD_PATH.unshift 'lib'
2
- require 'xmp/silencer'
1
+ require 'bundler/setup'
2
+ Bundler.require(:development)
3
3
 
4
- XMP::Silencer.silently {
5
- require 'bundler/setup'
6
- Bundler.require(:development)
7
- }
4
+ RSpec.configure { |c| c.expect_with(:rspec) { |c| c.syntax = :should } }
8
5
 
9
6
  # load whole montgomery after bundler loads required gems
10
7
  require 'xmp'
11
-
12
8
  require 'pp'
data/spec/xmp_spec.rb CHANGED
@@ -3,16 +3,31 @@ require './spec/spec_helper.rb'
3
3
 
4
4
  describe XMP do
5
5
  describe "with xmp.xml" do
6
- before { @xmp = XMP.new(File.read('spec/fixtures/xmp.xml')) }
6
+ before { @xmp = XMP.new('spec/fixtures/xmp.xml') }
7
7
 
8
8
  it "should return all namespace names" do
9
9
  @xmp.namespaces.should =~ %w{rdf x tiff exif xap aux Iptc4xmpCore photoshop crs dc}
10
10
  end
11
11
 
12
+ it "should support converting to hash via to_h" do
13
+ hash = @xmp.to_h
14
+ hash.should be_a(Hash)
15
+ hash['dc'].should be_a(Hash)
16
+ hash.keys.should =~ %w{rdf x tiff exif xap aux Iptc4xmpCore photoshop crs dc}
17
+ hash['dc'].keys.should =~ %w{creator title rights subject description}
18
+ hash['dc']['title'].should eq(['Tytuł zdjęcia'])
19
+ end
20
+
12
21
  it "should return standalone attribute" do
13
22
  @xmp.dc.title.should eq(['Tytuł zdjęcia'])
14
23
  @xmp.dc.subject.should eq(['Słowa kluczowe i numery startowe.'])
15
24
  @xmp.photoshop.SupplementalCategories.should eq(['Nazwa imprezy'])
25
+ @xmp.photoshop.supplemental_categories.should eq(['Nazwa imprezy'])
26
+ @xmp['photoshop']['SupplementalCategories'].should eq(['Nazwa imprezy'])
27
+ end
28
+
29
+ it "should return standalone attribute hash" do
30
+ @xmp.Iptc4xmpCore.CreatorContactInfo.should eq({'CiAdrCtry' => 'Germany', 'CiAdrCity' => 'Berlin'})
16
31
  end
17
32
 
18
33
  it "should return embedded attribute" do
@@ -22,6 +37,7 @@ describe XMP do
22
37
 
23
38
  it "should raise NoMethodError on unknown attribute" do
24
39
  lambda { @xmp.photoshop.UnknownAttribute }.should raise_error(NoMethodError)
40
+ @xmp.photoshop['UnknownAttribute'].should eq(nil)
25
41
  end
26
42
 
27
43
  describe "namespace 'tiff'" do
@@ -58,4 +74,47 @@ describe XMP do
58
74
  @xmp.photoshop.Credit.should eq('Remco')
59
75
  end
60
76
  end
77
+
78
+ # metadata after lightroom -> preview (resize)
79
+ # this one has only standalone attributes
80
+ describe "with xmp3.xml" do
81
+ before { @xmp = XMP.new(File.read('spec/fixtures/xmp3.xml')) }
82
+
83
+ it "should return attributes" do
84
+ @xmp.Iptc4xmpCore.Location.should eq('Phạm Đình Hồ')
85
+ @xmp.photoshop.City.should eq('Hanoi')
86
+ @xmp.aux.Lens.should eq('EF24-105mm f/4L IS USM')
87
+ end
88
+
89
+ it "should return standalone attribute hash" do
90
+ @xmp.Iptc4xmpCore.CreatorContactInfo.should eq({'CiAdrCtry' => 'Germany', 'CiAdrCity' => 'Berlin'})
91
+ end
92
+
93
+ end
94
+
95
+ # metadata after lightroom
96
+ describe "with xmp4.xml" do
97
+ before { @xmp = XMP.new(File.read('spec/fixtures/xmp4.xml')) }
98
+
99
+ it "should return dc:format" do
100
+ @xmp.dc.format.should eq('image/jpeg')
101
+ end
102
+
103
+ it "should return standalone attribute hash" do
104
+ @xmp.Iptc4xmpCore.CreatorContactInfo.should eq({'CiAdrCtry' => 'Germany', 'CiAdrCity' => 'Berlin'})
105
+ end
106
+ end
107
+
108
+ # metadata after lightroom 10 with face recognition
109
+ describe "with xmp5.xml" do
110
+ before { @xmp = XMP.new(File.read('spec/fixtures/xmp5.xml')) }
111
+
112
+ it "should return dc:format" do
113
+ @xmp.dc.format.should eq('image/jpeg')
114
+ end
115
+
116
+ it "should be able to read all attribute" do
117
+ @xmp.to_h.keys.should =~ %w{Iptc4xmpExt aux crs dc exifEX mwg-rs photoshop rdf stArea stDim stEvt stRef x xmp xmpMM}
118
+ end
119
+ end
61
120
  end
data/xmp.gemspec CHANGED
@@ -9,20 +9,23 @@ Gem::Specification.new do |s|
9
9
  s.authors = ["Wojciech Piekutowski"]
10
10
  s.email = ["wojciech.piekutowski@amberbit.com"]
11
11
  s.homepage = "https://github.com/amberbit/xmp"
12
- s.summary = %q{Extensible Metadata Platform (XMP) parser}
13
- s.description = %q{Extensible Metadata Platform (XMP) parser}
14
-
15
- s.rubyforge_project = "xmp"
12
+ s.summary = %q{Extensible Metadata Platform (XMP) parser for JPEG, TIFF and raw XML files}
13
+ s.description = %q{Extensible Metadata Platform (XMP) parser extracts metadata from JPED and TIFF image files. It also supports parsing raw XML files containing XMP.}
14
+ s.licenses = ["MIT"]
16
15
 
17
16
  s.files = `git ls-files`.split("\n")
18
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
19
  s.require_paths = ["lib"]
21
20
 
22
- s.extra_rdoc_files = %w(README.rdoc)
21
+ s.extra_rdoc_files = %w(README.md)
22
+
23
+ s.required_ruby_version = '>= 2.7.0'
23
24
 
24
- s.add_dependency 'nokogiri', '~>1.5.0'
25
+ s.add_dependency 'nokogiri', '~> 1.0'
25
26
 
26
- s.add_development_dependency 'exifr', '>=1.0.4'
27
- s.add_development_dependency 'rspec', '~>2.0'
27
+ s.add_development_dependency 'exifr', '>= 1.0.4', '~> 1.0'
28
+ s.add_development_dependency 'rspec', '~> 3.0'
29
+ s.add_development_dependency 'logger', '~> 1.0'
30
+ s.add_development_dependency 'rake'
28
31
  end
metadata CHANGED
@@ -1,98 +1,162 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xmp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
5
- prerelease:
4
+ version: 1.0.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Wojciech Piekutowski
9
- autorequire:
8
+ autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2011-07-28 00:00:00.000000000 +02:00
13
- default_executable:
11
+ date: 2024-09-09 00:00:00.000000000 Z
14
12
  dependencies:
15
13
  - !ruby/object:Gem::Dependency
16
14
  name: nokogiri
17
- requirement: &14726140 !ruby/object:Gem::Requirement
18
- none: false
15
+ requirement: !ruby/object:Gem::Requirement
19
16
  requirements:
20
- - - ~>
17
+ - - "~>"
21
18
  - !ruby/object:Gem::Version
22
- version: 1.5.0
19
+ version: '1.0'
23
20
  type: :runtime
24
21
  prerelease: false
25
- version_requirements: *14726140
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
26
27
  - !ruby/object:Gem::Dependency
27
28
  name: exifr
28
- requirement: &14725640 !ruby/object:Gem::Requirement
29
- none: false
29
+ requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ! '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: 1.0.4
34
+ - - "~>"
35
+ - !ruby/object:Gem::Version
36
+ version: '1.0'
34
37
  type: :development
35
38
  prerelease: false
36
- version_requirements: *14725640
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.0.4
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
37
47
  - !ruby/object:Gem::Dependency
38
48
  name: rspec
39
- requirement: &14725180 !ruby/object:Gem::Requirement
40
- none: false
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: logger
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rake
77
+ requirement: !ruby/object:Gem::Requirement
41
78
  requirements:
42
- - - ~>
79
+ - - ">="
43
80
  - !ruby/object:Gem::Version
44
- version: '2.0'
81
+ version: '0'
45
82
  type: :development
46
83
  prerelease: false
47
- version_requirements: *14725180
48
- description: Extensible Metadata Platform (XMP) parser
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ description: Extensible Metadata Platform (XMP) parser extracts metadata from JPED
90
+ and TIFF image files. It also supports parsing raw XML files containing XMP.
49
91
  email:
50
92
  - wojciech.piekutowski@amberbit.com
51
93
  executables: []
52
94
  extensions: []
53
95
  extra_rdoc_files:
54
- - README.rdoc
96
+ - README.md
55
97
  files:
56
- - .gitignore
57
- - .rspec
58
- - .rvmrc
98
+ - ".gitignore"
99
+ - ".rspec"
100
+ - ".travis.yml"
59
101
  - Gemfile
60
- - README.rdoc
102
+ - README.md
61
103
  - Rakefile
62
104
  - lib/xmp.rb
105
+ - lib/xmp/convenience.rb
106
+ - lib/xmp/document.rb
107
+ - lib/xmp/error.rb
108
+ - lib/xmp/handler.rb
109
+ - lib/xmp/handler/exifr.rb
110
+ - lib/xmp/handler/file.rb
111
+ - lib/xmp/handler/xml.rb
63
112
  - lib/xmp/namespace.rb
64
- - lib/xmp/silencer.rb
65
113
  - lib/xmp/version.rb
66
114
  - spec/exifr_jpeg_spec.rb
115
+ - spec/exifr_tiff_spec.rb
116
+ - spec/fixtures/UPPERCASE.JPG
67
117
  - spec/fixtures/multiple-app1.jpg
118
+ - spec/fixtures/multiple-app1.tiff
68
119
  - spec/fixtures/xmp.xml
69
120
  - spec/fixtures/xmp2.xml
121
+ - spec/fixtures/xmp3.xml
122
+ - spec/fixtures/xmp4.xml
123
+ - spec/fixtures/xmp5.xml
70
124
  - spec/spec_helper.rb
71
125
  - spec/xmp_spec.rb
72
126
  - xmp.gemspec
73
- has_rdoc: true
74
127
  homepage: https://github.com/amberbit/xmp
75
- licenses: []
76
- post_install_message:
128
+ licenses:
129
+ - MIT
130
+ metadata: {}
131
+ post_install_message:
77
132
  rdoc_options: []
78
133
  require_paths:
79
134
  - lib
80
135
  required_ruby_version: !ruby/object:Gem::Requirement
81
- none: false
82
136
  requirements:
83
- - - ! '>='
137
+ - - ">="
84
138
  - !ruby/object:Gem::Version
85
- version: '0'
139
+ version: 2.7.0
86
140
  required_rubygems_version: !ruby/object:Gem::Requirement
87
- none: false
88
141
  requirements:
89
- - - ! '>='
142
+ - - ">="
90
143
  - !ruby/object:Gem::Version
91
144
  version: '0'
92
145
  requirements: []
93
- rubyforge_project: xmp
94
- rubygems_version: 1.6.2
95
- signing_key:
96
- specification_version: 3
97
- summary: Extensible Metadata Platform (XMP) parser
98
- test_files: []
146
+ rubygems_version: 3.5.16
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: Extensible Metadata Platform (XMP) parser for JPEG, TIFF and raw XML files
150
+ test_files:
151
+ - spec/exifr_jpeg_spec.rb
152
+ - spec/exifr_tiff_spec.rb
153
+ - spec/fixtures/UPPERCASE.JPG
154
+ - spec/fixtures/multiple-app1.jpg
155
+ - spec/fixtures/multiple-app1.tiff
156
+ - spec/fixtures/xmp.xml
157
+ - spec/fixtures/xmp2.xml
158
+ - spec/fixtures/xmp3.xml
159
+ - spec/fixtures/xmp4.xml
160
+ - spec/fixtures/xmp5.xml
161
+ - spec/spec_helper.rb
162
+ - spec/xmp_spec.rb
data/.rvmrc DELETED
@@ -1,34 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- # http://rvm.beginrescueend.com/workflow/rvmrc/
4
-
5
- ruby_string="ruby-1.9.2"
6
- gemset_name="xmp"
7
-
8
- if rvm list strings | grep -q "${ruby_string}" ; then
9
-
10
- # Load or create the specified environment
11
- if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
12
- && -s "${rvm_path:-$HOME/.rvm}/environments/${ruby_string}@${gemset_name}" ]] ; then
13
- \. "${rvm_path:-$HOME/.rvm}/environments/${ruby_string}@${gemset_name}"
14
- else
15
- rvm --create "${ruby_string}@${gemset_name}"
16
- fi
17
-
18
- # (
19
- # Ensure that Bundler is installed, install it if it is not.
20
- if ! command -v bundle &> /dev/null; then
21
- echo "Installing bundler and creating bundle"
22
- gem install bundler
23
- bundle
24
- fi
25
- # )&
26
-
27
- else
28
-
29
- # Notify the user to install the desired interpreter before proceeding.
30
- echo "${ruby_string} was not found, please run 'rvm install ${ruby_string}' and then cd back into the project directory."
31
-
32
- fi
33
-
34
-
data/README.rdoc DELETED
@@ -1,53 +0,0 @@
1
- = xmp - Extensible Metadata Platform (XMP) parser
2
-
3
- XMP provides object oriented interface to XMP data (http://en.wikipedia.org/wiki/Extensible_Metadata_Platform). XMP data can be found in PDF, JPEG, GIF, PNG, and many other formats.
4
-
5
- == Supported formats
6
-
7
- Currently only JPEG is supported through exifr gem.
8
-
9
- == JPEG example
10
-
11
- # gem install xmp exifr
12
- require 'xmp'
13
- require 'exifr'
14
- require 'open-uri'
15
-
16
- img = EXIFR::JPEG.new('spec/fixtures/multiple-app1.jpg')
17
- xmp = XMP.parse(img)
18
- xmp.dc.subject #=> ["something interesting"]
19
-
20
- # explore XMP data
21
- xmp.namespaces.each do |namespace_name|
22
- namespace = xmp.send(namespace_name)
23
- namespace.attributes.each do |attr|
24
- puts "#{namespace_name}.#{attr}: " + namespace.send(attr).inspect
25
- end
26
- end
27
-
28
- == Installation
29
-
30
- gem install xmp
31
- # for JPEG support
32
- gem install exifr -v ">=1.0.4"
33
-
34
- == Requirements
35
-
36
- * Ruby 1.8.7, 1.9.2
37
- * Nokogiri 1.4
38
- * EXIFR >= 1.0.4
39
-
40
- == Development
41
-
42
- Fork it at https://github.com/amberbit/xmp
43
-
44
- # install development dependencies
45
- bundle install
46
- # run specs
47
- rake spec
48
-
49
- == License
50
-
51
- Ruby's license.
52
-
53
- Copyright (c) 2011 Wojciech Piekutowski, AmberBit (http://amberbit.com)
data/lib/xmp/silencer.rb DELETED
@@ -1,11 +0,0 @@
1
- class XMP
2
- module Silencer
3
- def self.silently
4
- verbosity = $VERBOSE
5
- $VERBOSE = nil
6
- result = yield
7
- $VERBOSE = verbosity
8
- result
9
- end
10
- end
11
- end