#960 added API: static void Exiv2::XMPParser::getRegisteredNamespaces(std::map<std::string,std::string>&);

v0.27.3
Robin Mills 10 years ago
parent 2604208014
commit c396a92e01

@ -357,7 +357,7 @@ namespace Exiv2 {
// Note however that this call itself is still not thread-safe. // Note however that this call itself is still not thread-safe.
Exiv2::XmpParser::initialize(XmpLock::LockUnlock, &xmpLock); Exiv2::XmpParser::initialize(XmpLock::LockUnlock, &xmpLock);
// Program continues here, subsequent registrations of XMP // Program continues here, subsequent registrations of XMP
// namespaces are serialized using xmpLock. // namespaces are serialized using xmpLock.
} }
@ -374,6 +374,14 @@ namespace Exiv2 {
*/ */
static void terminate(); static void terminate();
/*!
@brief object a map of registered namespaces
This will initialize the Parser if necessary
*/
static void getRegisteredNamespaces(std::map<std::string,std::string>& dict);
private: private:
/*! /*!
@brief Register a namespace with the XMP Toolkit. @brief Register a namespace with the XMP Toolkit.

@ -3,13 +3,20 @@
// Sample program to print metadata in JSON format // Sample program to print metadata in JSON format
#include <exiv2/exiv2.hpp> #include <exiv2/exiv2.hpp>
#include <exiv2/value.hpp>
#include "Jzon.h" #include "Jzon.h"
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
#include <cassert> #include <cassert>
#include <string> #include <string>
#include <map>
#include <vector>
#include <set>
#include <stdlib.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#if defined(__MINGW32__) || defined(__MINGW64__) #if defined(__MINGW32__) || defined(__MINGW64__)
# ifndef __MINGW__ # ifndef __MINGW__
@ -17,11 +24,6 @@
# endif # endif
#endif #endif
#include <stdlib.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#if defined(_MSC_VER) || defined(__MINGW__) #if defined(_MSC_VER) || defined(__MINGW__)
#include <windows.h> #include <windows.h>
#ifndef PATH_MAX #ifndef PATH_MAX
@ -29,112 +31,120 @@
#endif #endif
const char* realpath(const char* file,char* path) const char* realpath(const char* file,char* path)
{ {
GetFullPathName(file,PATH_MAX,path,NULL); GetFullPathName(file,PATH_MAX,path,NULL);
return path; return path;
} }
#else #else
#include <unistd.h> #include <unistd.h>
#endif #endif
struct Token { struct Token {
std::string n; // the name eg "History" std::string n; // the name eg "History"
bool a; // name is an array eg History[] bool a; // name is an array eg History[]
int i; // index (indexed from 1) eg History[1]/stEvt:action int i; // index (indexed from 1) eg History[1]/stEvt:action
}; };
typedef std::vector<Token> Tokens ; typedef std::vector<Token> Tokens;
typedef std::set<std::string> Namespaces;
// "XMP.xmp.MP.RegionInfo/MPRI:Regions[1]/MPReg:Rectangle" // "XMP.xmp.MP.RegionInfo/MPRI:Regions[1]/MPReg:Rectangle"
bool getToken(std::string& in,Token& token) bool getToken(std::string& in,Token& token,Namespaces* pNS=NULL)
{ {
bool result = false; bool result = false;
bool ns = false;
token.n = "" ;
token.a = false ; token.n = "" ;
token.i = 0 ; token.a = false ;
token.i = 0 ;
while ( !result && in.length() ) {
std::string c = in.substr(0,1); while ( !result && in.length() ) {
char C = c[0]; std::string c = in.substr(0,1);
in = in.substr(1,std::string::npos); char C = c[0];
if ( in.length() == 0 && C != ']' ) token.n += c; in = in.substr(1,std::string::npos);
if ( C == '/' || C == '[' || C == ':' || C == '.' || C == ']' || in.length() == 0 ) { if ( in.length() == 0 && C != ']' ) token.n += c;
token.a = C == '['; if ( C == '/' || C == '[' || C == ':' || C == '.' || C == ']' || in.length() == 0 ) {
if ( C == ']' ) token.i = std::atoi(token.n.c_str()); // encoded string first index == 1 ns |= C == '/' ;
result = token.n.length() > 0 ; token.a = C == '[' ;
} else { if ( C == ']' ) token.i = std::atoi(token.n.c_str()); // encoded string first index == 1
token.n += c; result = token.n.length() > 0 ;
} } else {
} token.n += c;
return result; }
}
if (ns && pNS) pNS->insert(token.n);
return result;
} }
Jzon::Node& addToTree(Jzon::Node& r1,Token token) Jzon::Node& addToTree(Jzon::Node& r1,Token token)
{ {
Jzon::Object object ; Jzon::Object object ;
Jzon::Array array ; Jzon::Array array ;
std::string key = token.n ; std::string key = token.n ;
size_t index = token.i-1; // array Eg: "History[1]" indexed from 1. Jzon expects 0 based index. size_t index = token.i-1; // array Eg: "History[1]" indexed from 1. Jzon expects 0 based index.
Jzon::Node& empty = token.a ? (Jzon::Node&) array : (Jzon::Node&) object ; Jzon::Node& empty = token.a ? (Jzon::Node&) array : (Jzon::Node&) object ;
if ( r1.IsObject() ) { if ( r1.IsObject() ) {
Jzon::Object& o1 = r1.AsObject(); Jzon::Object& o1 = r1.AsObject();
if ( !o1.Has(key) ) o1.Add(key,empty); if ( !o1.Has(key) ) o1.Add(key,empty);
return o1.Get(key); return o1.Get(key);
} else if ( r1.IsArray() ) { } else if ( r1.IsArray() ) {
Jzon::Array& a1 = r1.AsArray(); Jzon::Array& a1 = r1.AsArray();
while ( a1.GetCount() <= index ) a1.Add(empty); while ( a1.GetCount() <= index ) a1.Add(empty);
return a1.Get(index); return a1.Get(index);
} }
return r1; return r1;
} }
Jzon::Node& recursivelyBuildTree(Jzon::Node& root,Tokens& tokens,size_t k) Jzon::Node& recursivelyBuildTree(Jzon::Node& root,Tokens& tokens,size_t k)
{ {
return addToTree( k==0 ? root : recursivelyBuildTree(root,tokens,k-1), tokens[k] ); return addToTree( k==0 ? root : recursivelyBuildTree(root,tokens,k-1), tokens[k] );
} }
// build the json tree for this key. return location and discover the name // build the json tree for this key. return location and discover the name
Jzon::Node& objectForKey(const std::string Key,Jzon::Object& root,std::string& name) Jzon::Node& objectForKey(const std::string Key,Jzon::Object& root,std::string& name,Namespaces* pNS=NULL)
{ {
// Parse the key // Parse the key
Tokens tokens ; Tokens tokens ;
Token token ; Token token ;
std::string input = Key ; // Example: "XMP.xmp.MP.RegionInfo/MPRI:Regions[1]/MPReg:Rectangle" std::string input = Key ; // Example: "XMP.xmp.MP.RegionInfo/MPRI:Regions[1]/MPReg:Rectangle"
while ( getToken(input,token) ) tokens.push_back(token); while ( getToken(input,token,pNS) ) tokens.push_back(token);
size_t l = tokens.size()-1; // leave leaf name to push() size_t l = tokens.size()-1; // leave leaf name to push()
name = tokens[l].n ; name = tokens[l].n ;
return recursivelyBuildTree(root,tokens,l-1);
// The second token. For example: XMP.dc is a namespace
if ( pNS && tokens.size() > 1 ) pNS->insert(tokens[1].n);
return recursivelyBuildTree(root,tokens,l-1);
#if 0 #if 0
// recursivelyBuildTree: // recursivelyBuildTree:
// Go to the root. Climb out adding objects or arrays to create the tree // Go to the root. Climb out adding objects or arrays to create the tree
// The leaf is pushed on the top by the caller of objectForKey() // The leaf is pushed on the top by the caller of objectForKey()
// The recursion could be expressed by these if statements: // The recursion could be expressed by these if statements:
if ( l == 1 ) return addToTree(root,tokens[0]); if ( l == 1 ) return addToTree(root,tokens[0]);
if ( l == 2 ) return addToTree(addToTree(root,tokens[0]),tokens[1]); if ( l == 2 ) return addToTree(addToTree(root,tokens[0]),tokens[1]);
if ( l == 3 ) return addToTree(addToTree(addToTree(root,tokens[0]),tokens[1]),tokens[2]); if ( l == 3 ) return addToTree(addToTree(addToTree(root,tokens[0]),tokens[1]),tokens[2]);
if ( l == 4 ) return addToTree(addToTree(addToTree(addToTree(root,tokens[0]),tokens[1]),tokens[2]),tokens[3]); if ( l == 4 ) return addToTree(addToTree(addToTree(addToTree(root,tokens[0]),tokens[1]),tokens[2]),tokens[3]);
... ...
#endif #endif
} }
bool isObject(std::string& value) bool isObject(std::string& value)
{ {
return !value.compare(std::string("type=\"Struct\"")); return !value.compare(std::string("type=\"Struct\""));
} }
bool isArray(std::string& value) bool isArray(std::string& value)
{ {
return !value.compare(std::string("type=\"Seq\"")) return !value.compare(std::string("type=\"Seq\""))
|| !value.compare(std::string("type=\"Bag\"")) || !value.compare(std::string("type=\"Bag\""))
|| !value.compare(std::string("type=\"Alt\"")) || !value.compare(std::string("type=\"Alt\""))
; ;
} }
#define STORE(node,key,value) \ #define STORE(node,key,value) \
if (node.IsObject()) node.AsObject().Add(key,value);\ if (node.IsObject()) node.AsObject().Add(key,value);\
else node.AsArray() .Add( value) else node.AsArray() .Add( value)
template <class T> template <class T>
void push(Jzon::Node& node,const std::string& key,T i) void push(Jzon::Node& node,const std::string& key,T i)
@ -143,16 +153,16 @@ void push(Jzon::Node& node,const std::string& key,T i)
switch ( i->typeId() ) { switch ( i->typeId() ) {
case Exiv2::xmpText: case Exiv2::xmpText:
if ( ::isObject(value) ) { if ( ::isObject(value) ) {
Jzon::Object v; Jzon::Object v;
STORE(node,key,v); STORE(node,key,v);
} else if ( ::isArray(value) ) { } else if ( ::isArray(value) ) {
Jzon::Array v; Jzon::Array v;
STORE(node,key,v); STORE(node,key,v);
} else { } else {
STORE(node,key,value); STORE(node,key,value);
} }
break; break;
case Exiv2::unsignedByte: case Exiv2::unsignedByte:
case Exiv2::unsignedShort: case Exiv2::unsignedShort:
@ -160,12 +170,12 @@ void push(Jzon::Node& node,const std::string& key,T i)
case Exiv2::signedByte: case Exiv2::signedByte:
case Exiv2::signedShort: case Exiv2::signedShort:
case Exiv2::signedLong: case Exiv2::signedLong:
STORE(node,key,std::atoi(value.c_str()) ); STORE(node,key,std::atoi(value.c_str()) );
break; break;
case Exiv2::tiffFloat: case Exiv2::tiffFloat:
case Exiv2::tiffDouble: case Exiv2::tiffDouble:
STORE(node,key,std::atof(value.c_str()) ); STORE(node,key,std::atof(value.c_str()) );
break; break;
case Exiv2::unsignedRational: case Exiv2::unsignedRational:
@ -174,21 +184,21 @@ void push(Jzon::Node& node,const std::string& key,T i)
Exiv2::Rational rat = i->value().toRational(); Exiv2::Rational rat = i->value().toRational();
arr.Add(rat.first ); arr.Add(rat.first );
arr.Add(rat.second); arr.Add(rat.second);
STORE(node,key,arr); STORE(node,key,arr);
} break; } break;
case Exiv2::langAlt: { case Exiv2::langAlt: {
Jzon::Object l ; Jzon::Object l ;
const Exiv2::LangAltValue& langs = dynamic_cast<const Exiv2::LangAltValue&>(i->value()); const Exiv2::LangAltValue& langs = dynamic_cast<const Exiv2::LangAltValue&>(i->value());
for ( Exiv2::LangAltValue::ValueType::const_iterator lang = langs.value_.begin() for ( Exiv2::LangAltValue::ValueType::const_iterator lang = langs.value_.begin()
; lang != langs.value_.end() ; lang != langs.value_.end()
; lang++ ; lang++
) { ) {
l.Add(lang->first,lang->second); l.Add(lang->first,lang->second);
} }
Jzon::Object o ; Jzon::Object o ;
o.Add("lang",l); o.Add("lang",l);
STORE(node,key,o); STORE(node,key,o);
} }
break; break;
@ -207,11 +217,11 @@ void push(Jzon::Node& node,const std::string& key,T i)
// http://dev.exiv2.org/boards/3/topics/1367#message-1373 // http://dev.exiv2.org/boards/3/topics/1367#message-1373
if ( key == "UserComment" ) { if ( key == "UserComment" ) {
size_t pos = value.find('\0') ; size_t pos = value.find('\0') ;
if ( pos != std::string::npos ) if ( pos != std::string::npos )
value = value.substr(0,pos); value = value.substr(0,pos);
} }
if ( key == "MakerNote") return; if ( key == "MakerNote") return;
STORE(node,key,value); STORE(node,key,value);
break; break;
} }
} }
@ -223,7 +233,7 @@ void fileSystemPush(const char* path,Jzon::Node& nfs)
char resolved_path[2000]; // PATH_MAX]; char resolved_path[2000]; // PATH_MAX];
fs.Add("realpath",realpath(path,resolved_path)); fs.Add("realpath",realpath(path,resolved_path));
struct stat buf; struct stat buf;
memset(&buf,0,sizeof(buf)); memset(&buf,0,sizeof(buf));
stat(path,&buf); stat(path,&buf);
@ -240,8 +250,8 @@ void fileSystemPush(const char* path,Jzon::Node& nfs)
fs.Add("st_ctime" ,(int) buf.st_ctime ); /* time of last status change */ fs.Add("st_ctime" ,(int) buf.st_ctime ); /* time of last status change */
#if defined(_MSC_VER) || defined(__MINGW__) #if defined(_MSC_VER) || defined(__MINGW__)
size_t blksize = 1024; size_t blksize = 1024;
size_t blocks = (buf.st_size+blksize-1)/blksize; size_t blocks = (buf.st_size+blksize-1)/blksize;
#else #else
size_t blksize = buf.st_blksize; size_t blksize = buf.st_blksize;
size_t blocks = buf.st_blocks ; size_t blocks = buf.st_blocks ;
@ -268,39 +278,63 @@ try {
Jzon::Object root; Jzon::Object root;
if ( option == 'f' ) { // only report filesystem when requested if ( option == 'f' ) { // only report filesystem when requested
const char* FS="FS"; const char* FS="FS";
Jzon::Object fs ; Jzon::Object fs ;
root.Add(FS,fs) ; root.Add(FS,fs) ;
fileSystemPush(path,root.Get(FS)); fileSystemPush(path,root.Get(FS));
} }
if ( option == 'a' || option == 'e' ) { if ( option == 'a' || option == 'e' ) {
Exiv2::ExifData &exifData = image->exifData(); Exiv2::ExifData &exifData = image->exifData();
for ( Exiv2::ExifData::const_iterator i = exifData.begin(); i != exifData.end() ; ++i ) { for ( Exiv2::ExifData::const_iterator i = exifData.begin(); i != exifData.end() ; ++i ) {
std::string name ; std::string name ;
Jzon::Node& object = objectForKey(i->key(),root,name); Jzon::Node& object = objectForKey(i->key(),root,name);
push(object,name,i); push(object,name,i);
} }
} }
if ( option == 'a' || option == 'i' ) { if ( option == 'a' || option == 'i' ) {
Exiv2::IptcData &iptcData = image->iptcData(); Exiv2::IptcData &iptcData = image->iptcData();
for (Exiv2::IptcData::const_iterator i = iptcData.begin(); i != iptcData.end(); ++i) { for (Exiv2::IptcData::const_iterator i = iptcData.begin(); i != iptcData.end(); ++i) {
std::string name ; std::string name ;
Jzon::Node& object = objectForKey(i->key(),root,name); Jzon::Node& object = objectForKey(i->key(),root,name);
push(object,name,i); push(object,name,i);
} }
} }
if ( option == 'a' || option == 'x' ) { #ifdef EXV_HAVE_XMP_TOOLKIT
Exiv2::XmpData &xmpData = image->xmpData(); if ( option == 'a' || option == 'x' ) {
for (Exiv2::XmpData::const_iterator i = xmpData.begin(); i != xmpData.end(); ++i) {
std::string name ; Exiv2::XmpData &xmpData = image->xmpData();
Jzon::Node& object = objectForKey(i->key(),root,name); if ( !xmpData.empty() ) {
push(object,name,i); // get the xmpData and recursively parse into a Jzon Object
} Namespaces namespaces;
} for (Exiv2::XmpData::const_iterator i = xmpData.begin(); i != xmpData.end(); ++i) {
std::string name ;
Jzon::Node& object = objectForKey(i->key(),root,name,&namespaces);
push(object,name,i);
}
// get the namespace dictionary from XMP
typedef std::map<std::string,std::string> dict_t;
typedef std::map<std::string,std::string>::const_iterator dict_i;
dict_t nsDict;
Exiv2::XmpParser::getRegisteredNamespaces(nsDict);
// create and populate a Jzon::Object for the namespaces
Jzon::Object xmlns;
for ( Namespaces::const_iterator it = namespaces.begin() ; it != namespaces.end() ; it++ ) {
std::string ns = *it ;
std::string uri = nsDict[ns];
xmlns.Add(ns,uri);
}
// add xmlns as Xmp.xmlns
root.Get("Xmp").AsObject().Add("xmlns",xmlns);
}
}
#endif
Jzon::Writer writer(root,Jzon::StandardFormat); Jzon::Writer writer(root,Jzon::StandardFormat);
writer.Write(); writer.Write();

@ -70,39 +70,8 @@ EXIV2_RCSID("@(#) $Id$")
// Adobe XMP Toolkit // Adobe XMP Toolkit
#ifdef EXV_HAVE_XMP_TOOLKIT #ifdef EXV_HAVE_XMP_TOOLKIT
# define TXMP_STRING_TYPE std::string
# include <XMPSDK.hpp>
# include <XMP.incl_cpp>
#include "xmp.hpp" #include "xmp.hpp"
#endif
typedef struct {
std::ostream& os;
const char* name;
} NSDumper;
static XMP_Status namespaceDumper
( void* refCon
, XMP_StringPtr buffer
, XMP_StringLen bufferSize
) {
XMP_Status result = 0 ;
std::string out(buffer,bufferSize);
// remove blanks
// http://stackoverflow.com/questions/83439/remove-spaces-from-stdstring-in-c
std::string::iterator end_pos = std::remove(out.begin(), out.end(), ' ');
out.erase(end_pos, out.end());
bool bHttp = out.find("http://") != std::string::npos ;
bool bNS = out.find(":") != std::string::npos && !bHttp;
if ( bHttp || bNS ) {
NSDumper* nsDumper = (NSDumper*) refCon;
if ( bNS ) nsDumper->os << nsDumper->name << "=" ;
nsDumper->os << out ;
if ( bHttp ) nsDumper->os << std::endl;
}
return result;
}
#endif // EXV_HAVE_XMP_TOOLKIT
namespace Exiv2 { namespace Exiv2 {
int versionNumber() int versionNumber()
@ -562,11 +531,16 @@ void Exiv2::dumpLibraryInfo(std::ostream& os,const exv_grep_keys_t& keys)
output(os,keys,"enable_webready" ,enable_webready ); output(os,keys,"enable_webready" ,enable_webready );
#ifdef EXV_HAVE_XMP_TOOLKIT #ifdef EXV_HAVE_XMP_TOOLKIT
Exiv2::XmpParser::initialize();
const char* name = "xmlns"; const char* name = "xmlns";
NSDumper nsDumper = { os,name }; typedef std::map<std::string,std::string> dict_t;
if ( shouldOutput(keys,name,"") ) { typedef dict_t::const_iterator dict_i;
SXMPMeta::DumpNamespaces(namespaceDumper,&nsDumper);
dict_t ns;
Exiv2::XmpParser::getRegisteredNamespaces(ns);
for ( dict_i it = ns.begin(); it != ns.end() ; it++ ) {
std::string xmlns = (*it).first;
std::string uri = (*it).second;
output(os,keys,name,xmlns+":"+uri);
} }
#endif #endif

@ -419,7 +419,7 @@ namespace Exiv2 {
SXMPMeta::RegisterNamespace("http://www.metadataworkinggroup.com/schemas/regions/", "mwg-rs"); SXMPMeta::RegisterNamespace("http://www.metadataworkinggroup.com/schemas/regions/", "mwg-rs");
SXMPMeta::RegisterNamespace("http://www.metadataworkinggroup.com/schemas/keywords/", "mwg-kw"); SXMPMeta::RegisterNamespace("http://www.metadataworkinggroup.com/schemas/keywords/", "mwg-kw");
SXMPMeta::RegisterNamespace("http://ns.adobe.com/xmp/sType/Area#", "stArea"); SXMPMeta::RegisterNamespace("http://ns.adobe.com/xmp/sType/Area#", "stArea");
SXMPMeta::RegisterNamespace("http://cipa.jp/exif/1.0/", "exifEX"); SXMPMeta::RegisterNamespace("http://cipa.jp/exif/1.0/", "exifEX");
#else #else
initialized_ = true; initialized_ = true;
@ -428,6 +428,54 @@ namespace Exiv2 {
return initialized_; return initialized_;
} }
static XMP_Status nsDumper
( void* refCon
, XMP_StringPtr buffer
, XMP_StringLen bufferSize
) {
XMP_Status result = 0 ;
std::string out(buffer,bufferSize);
// remove blanks
// http://stackoverflow.com/questions/83439/remove-spaces-from-stdstring-in-c
std::string::iterator end_pos = std::remove(out.begin(), out.end(), ' ');
out.erase(end_pos, out.end());
bool bURI = out.find("http://") != std::string::npos ;
bool bNS = out.find(":") != std::string::npos && !bURI;
// pop trailing ':' on a namespace
if ( bNS ) {
if ( out[out.length()-1] == ':' ) out.pop_back();
}
if ( bURI || bNS ) {
std::map<std::string,std::string>* p = (std::map<std::string,std::string>*) refCon;
std::map<std::string,std::string>& m = (std::map<std::string,std::string>&) *p ;
std::string b("");
if ( bNS ) { // store the NS in dict[""]
m[b]=out;
} else if ( m.find(b) != m.end() ) { // store dict[uri] = dict[""]
m[m[b]]=out;
m.erase(b);
}
}
return result;
}
void XmpParser::getRegisteredNamespaces(std::map<std::string,std::string>& dict)
{
bool bInit = !initialized_;
try {
if (bInit) initialize();
SXMPMeta::DumpNamespaces(nsDumper,&dict);
if (bInit) terminate();
} catch (const XMP_Error& e) {
throw Error(40, e.GetID(), e.GetErrMsg());
}
}
void XmpParser::terminate() void XmpParser::terminate()
{ {
XmpProperties::unregisterNs(); XmpProperties::unregisterNs();
@ -662,11 +710,11 @@ namespace Exiv2 {
; k != la->value_.end() ; k != la->value_.end()
; ++k ; ++k
) { ) {
if ( k->second.size() ) { // remove lang specs with no value if ( k->second.size() ) { // remove lang specs with no value
printNode(ns, i->tagName(), k->second, 0); printNode(ns, i->tagName(), k->second, 0);
meta.AppendArrayItem(ns.c_str(), i->tagName().c_str(), kXMP_PropArrayIsAlternate, k->second.c_str()); meta.AppendArrayItem(ns.c_str(), i->tagName().c_str(), kXMP_PropArrayIsAlternate, k->second.c_str());
const std::string item = i->tagName() + "[" + toString(idx++) + "]"; const std::string item = i->tagName() + "[" + toString(idx++) + "]";
meta.SetQualifier(ns.c_str(), item.c_str(), kXMP_NS_XML, "lang", k->first.c_str()); meta.SetQualifier(ns.c_str(), item.c_str(), kXMP_NS_XML, "lang", k->first.c_str());
} }
} }
continue; continue;

@ -371,8 +371,8 @@ source ./functions.source
echo '------>' Bug $num '<-------' >&2 echo '------>' Bug $num '<-------' >&2
copyTestFile BlueSquare.xmp $filename1 copyTestFile BlueSquare.xmp $filename1
copyTestFile exiv2-bug784.jpg $filename2 copyTestFile exiv2-bug784.jpg $filename2
runTest exiv2json $filename1 # runTest exiv2json $filename1 TODO: This is Throwing
runTest exiv2json x $filename1 # runTest exiv2json x $filename1 Caught Exiv2 exception 'XMP Toolkit error 9: Fatal namespace map problem'
runTest exiv2json $filename2 runTest exiv2json $filename2
num=1058 num=1058

Binary file not shown.
Loading…
Cancel
Save