12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043 |
- #------------------------------------------------------------------------------
- # File: XMP.pm
- #
- # Description: Read XMP meta information
- #
- # Revisions: 11/25/2003 - P. Harvey Created
- # 10/28/2004 - P. Harvey Major overhaul to conform with XMP spec
- # 02/27/2005 - P. Harvey Also read UTF-16 and UTF-32 XMP
- # 08/30/2005 - P. Harvey Split tag tables into separate namespaces
- # 10/24/2005 - P. Harvey Added ability to parse .XMP files
- # 08/25/2006 - P. Harvey Added ability to handle blank nodes
- # 08/22/2007 - P. Harvey Added ability to handle alternate language tags
- # 09/26/2008 - P. Harvey Added Iptc4xmpExt tags (version 1.0 rev 2)
- #
- # References: 1) http://www.adobe.com/products/xmp/pdfs/xmpspec.pdf
- # 2) http://www.w3.org/TR/rdf-syntax-grammar/ (20040210)
- # 3) http://www.portfoliofaq.com/pfaq/v7mappings.htm
- # 4) http://www.iptc.org/IPTC4XMP/
- # 5) http://creativecommons.org/technology/xmp
- # --> changed to http://wiki.creativecommons.org/Companion_File_metadata_specification (2007/12/21)
- # 6) http://www.optimasc.com/products/fileid/xmp-extensions.pdf
- # 7) Lou Salkind private communication
- # 8) http://partners.adobe.com/public/developer/en/xmp/sdk/XMPspecification.pdf
- # 9) http://www.w3.org/TR/SVG11/
- # 10) http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf (Oct 2008)
- # 11) http://www.extensis.com/en/support/kb_article.jsp?articleNumber=6102211
- # 12) http://www.cipa.jp/std/documents/e/DC-010-2012_E.pdf
- #
- # Notes: - Property qualifiers are handled as if they were separate
- # properties (with no associated namespace).
- #
- # - Currently, there is no special treatment of the following
- # properties which could potentially affect the extracted
- # information: xml:base, rdf:parseType (note that parseType
- # Literal isn't allowed by the XMP spec).
- #
- # - The family 2 group names will be set to 'Unknown' for any XMP
- # tags not found in the XMP or Exif tag tables.
- #------------------------------------------------------------------------------
- package Image::ExifTool::XMP;
- use strict;
- use vars qw($VERSION $AUTOLOAD @ISA @EXPORT_OK %stdXlatNS %nsURI %dateTimeInfo
- %xmpTableDefaults %specialStruct %sDimensions %sArea %sColorant);
- use Image::ExifTool qw(:Utils);
- use Image::ExifTool::Exif;
- require Exporter;
- $VERSION = '2.90';
- @ISA = qw(Exporter);
- @EXPORT_OK = qw(EscapeXML UnescapeXML);
- sub ProcessXMP($$;$);
- sub WriteXMP($$;$);
- sub CheckXMP($$$);
- sub ParseXMPElement($$$;$$$$);
- sub DecodeBase64($);
- sub EncodeBase64($;$);
- sub SaveBlankInfo($$$;$);
- sub ProcessBlankInfo($$$;$);
- sub ValidateXMP($;$);
- sub UnescapeChar($$);
- sub AddFlattenedTags($;$$);
- sub FormatXMPDate($);
- sub ConvertRational($);
- sub ConvertRationalList($);
- # lookup for translating to ExifTool namespaces
- %stdXlatNS = (
- # shorten ugly namespace prefixes
- 'Iptc4xmpCore' => 'iptcCore',
- 'Iptc4xmpExt' => 'iptcExt',
- 'photomechanic'=> 'photomech',
- 'MicrosoftPhoto' => 'microsoft',
- 'prismusagerights' => 'pur',
- 'GettyImagesGIFT' => 'getty',
- );
- # translate ExifTool XMP family 1 group names to standard XMP namespace prefixes
- my %xmpNS = (
- # shorten ugly namespace prefixes
- 'iptcCore' => 'Iptc4xmpCore',
- 'iptcExt' => 'Iptc4xmpExt',
- 'photomechanic'=> 'photomech',
- 'microsoft' => 'MicrosoftPhoto',
- 'gettyImages' => 'GettyImagesGIFT',
- # (prism changed their spec to now use 'pur')
- # 'pur' => 'prismusagerights',
- );
- # Lookup to translate standard XMP namespace prefixes into URI's. This list
- # need not be complete, but it must contain an entry for each namespace prefix
- # (NAMESPACE) for writable tags in the XMP tables or in structures that doesn't
- # define a URI. Also, the namespace must be defined here for non-standard
- # namespace prefixes to be recognized.
- %nsURI = (
- aux => 'http://ns.adobe.com/exif/1.0/aux/',
- album => 'http://ns.adobe.com/album/1.0/',
- cc => 'http://creativecommons.org/ns#', # changed 2007/12/21 - PH
- crs => 'http://ns.adobe.com/camera-raw-settings/1.0/',
- crss => 'http://ns.adobe.com/camera-raw-saved-settings/1.0/',
- dc => 'http://purl.org/dc/elements/1.1/',
- exif => 'http://ns.adobe.com/exif/1.0/',
- exifEX => 'http://cipa.jp/exif/1.0/',
- iX => 'http://ns.adobe.com/iX/1.0/',
- pdf => 'http://ns.adobe.com/pdf/1.3/',
- pdfx => 'http://ns.adobe.com/pdfx/1.3/',
- photoshop => 'http://ns.adobe.com/photoshop/1.0/',
- rdf => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
- rdfs => 'http://www.w3.org/2000/01/rdf-schema#',
- stDim => 'http://ns.adobe.com/xap/1.0/sType/Dimensions#',
- stEvt => 'http://ns.adobe.com/xap/1.0/sType/ResourceEvent#',
- stFnt => 'http://ns.adobe.com/xap/1.0/sType/Font#',
- stJob => 'http://ns.adobe.com/xap/1.0/sType/Job#',
- stRef => 'http://ns.adobe.com/xap/1.0/sType/ResourceRef#',
- stVer => 'http://ns.adobe.com/xap/1.0/sType/Version#',
- stMfs => 'http://ns.adobe.com/xap/1.0/sType/ManifestItem#',
- tiff => 'http://ns.adobe.com/tiff/1.0/',
- 'x' => 'adobe:ns:meta/',
- xmpG => 'http://ns.adobe.com/xap/1.0/g/',
- xmpGImg => 'http://ns.adobe.com/xap/1.0/g/img/',
- xmp => 'http://ns.adobe.com/xap/1.0/',
- xmpBJ => 'http://ns.adobe.com/xap/1.0/bj/',
- xmpDM => 'http://ns.adobe.com/xmp/1.0/DynamicMedia/',
- xmpMM => 'http://ns.adobe.com/xap/1.0/mm/',
- xmpRights => 'http://ns.adobe.com/xap/1.0/rights/',
- xmpNote => 'http://ns.adobe.com/xmp/note/',
- xmpTPg => 'http://ns.adobe.com/xap/1.0/t/pg/',
- xmpidq => 'http://ns.adobe.com/xmp/Identifier/qual/1.0/',
- xmpPLUS => 'http://ns.adobe.com/xap/1.0/PLUS/',
- dex => 'http://ns.optimasc.com/dex/1.0/',
- mediapro => 'http://ns.iview-multimedia.com/mediapro/1.0/',
- expressionmedia => 'http://ns.microsoft.com/expressionmedia/1.0/',
- Iptc4xmpCore => 'http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/',
- Iptc4xmpExt => 'http://iptc.org/std/Iptc4xmpExt/2008-02-29/',
- MicrosoftPhoto => 'http://ns.microsoft.com/photo/1.0',
- MP1 => 'http://ns.microsoft.com/photo/1.1', #PH (MP1 is fabricated)
- MP => 'http://ns.microsoft.com/photo/1.2/',
- MPRI => 'http://ns.microsoft.com/photo/1.2/t/RegionInfo#',
- MPReg => 'http://ns.microsoft.com/photo/1.2/t/Region#',
- lr => 'http://ns.adobe.com/lightroom/1.0/',
- DICOM => 'http://ns.adobe.com/DICOM/',
- svg => 'http://www.w3.org/2000/svg',
- et => 'http://ns.exiftool.ca/1.0/',
- #
- # namespaces defined in XMP2.pl:
- #
- plus => 'http://ns.useplus.org/ldf/xmp/1.0/',
- # (prism recommendations from http://www.prismstandard.org/specifications/3.0/Image_Guide_3.0.htm)
- prism => 'http://prismstandard.org/namespaces/basic/2.0/',
- prl => 'http://prismstandard.org/namespaces/prl/2.1/',
- pur => 'http://prismstandard.org/namespaces/prismusagerights/2.1/',
- pmi => 'http://prismstandard.org/namespaces/pmi/2.2/',
- prm => 'http://prismstandard.org/namespaces/prm/3.0/',
- acdsee => 'http://ns.acdsee.com/iptc/1.0/',
- digiKam => 'http://www.digikam.org/ns/1.0/',
- swf => 'http://ns.adobe.com/swf/1.0',
- cell => 'http://developer.sonyericsson.com/cell/1.0/',
- aas => 'http://ns.apple.com/adjustment-settings/1.0/',
- 'mwg-rs' => 'http://www.metadataworkinggroup.com/schemas/regions/',
- 'mwg-kw' => 'http://www.metadataworkinggroup.com/schemas/keywords/',
- 'mwg-coll' => 'http://www.metadataworkinggroup.com/schemas/collections/',
- stArea => 'http://ns.adobe.com/xmp/sType/Area#',
- extensis => 'http://ns.extensis.com/extensis/1.0/',
- ics => 'http://ns.idimager.com/ics/1.0/',
- fpv => 'http://ns.fastpictureviewer.com/fpv/1.0/',
- creatorAtom=>'http://ns.adobe.com/creatorAtom/1.0/',
- 'apple-fi' => 'http://ns.apple.com/faceinfo/1.0/',
- GPano => 'http://ns.google.com/photos/1.0/panorama/',
- dwc => 'http://rs.tdwg.org/dwc/index.htm',
- GettyImagesGIFT => 'http://xmp.gettyimages.com/gift/1.0/',
- );
- # build reverse namespace lookup
- my %uri2ns;
- {
- my $ns;
- foreach $ns (keys %nsURI) {
- $uri2ns{$nsURI{$ns}} = $ns;
- }
- }
- # conversions for GPS coordinates
- sub ToDegrees
- {
- require Image::ExifTool::GPS;
- Image::ExifTool::GPS::ToDegrees($_[0], 1);
- }
- my %latConv = (
- ValueConv => \&ToDegrees,
- RawConv => 'require Image::ExifTool::GPS; $val', # to load Composite tags and routines
- ValueConvInv => q{
- require Image::ExifTool::GPS;
- Image::ExifTool::GPS::ToDMS($self, $val, 2, "N");
- },
- PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
- PrintConvInv => \&ToDegrees,
- );
- my %longConv = (
- ValueConv => \&ToDegrees,
- RawConv => 'require Image::ExifTool::GPS; $val',
- ValueConvInv => q{
- require Image::ExifTool::GPS;
- Image::ExifTool::GPS::ToDMS($self, $val, 2, "E");
- },
- PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
- PrintConvInv => \&ToDegrees,
- );
- %dateTimeInfo = (
- # NOTE: Do NOT put "Groups" here because Groups hash must not be common!
- Writable => 'date',
- Shift => 'Time',
- PrintConv => '$self->ConvertDateTime($val)',
- PrintConvInv => '$self->InverseDateTime($val,undef,1)',
- );
- # this conversion allows alternate language support for designated boolean tags
- my %boolConv = (
- PrintConv => {
- OTHER => sub { # (inverse conversion is the same)
- my $val = shift;
- return 'False' if lc $val eq 'false';
- return 'True' if lc $val eq 'true';
- return $val;
- },
- True => 'True',
- False => 'False',
- },
- );
- # XMP namespaces which we don't want to contribute to generated EXIF tag names
- # (Note: namespaces with non-standard prefixes aren't currently ignored)
- my %ignoreNamespace = ( 'x'=>1, rdf=>1, xmlns=>1, xml=>1, svg=>1, et=>1, office=>1 );
- # XMP properties to ignore (set dynamically via dirInfo IgnoreProp)
- my %ignoreProp;
- # these are the attributes that we handle for properties that contain
- # sub-properties. Attributes for simple properties are easy, and we
- # just copy them over. These are harder since we don't store attributes
- # for properties without simple values. (maybe this will change...)
- # (special attributes are indicated by a list reference of tag information)
- my %recognizedAttrs = (
- 'rdf:about' => [ 'Image::ExifTool::XMP::rdf', 'about', 'About' ],
- 'x:xmptk' => [ 'Image::ExifTool::XMP::x', 'xmptk', 'XMPToolkit' ],
- 'x:xaptk' => [ 'Image::ExifTool::XMP::x', 'xmptk', 'XMPToolkit' ],
- 'rdf:parseType' => 1,
- 'rdf:nodeID' => 1,
- 'et:toolkit' => 1,
- 'rdf:xmlns' => 1, # this is presumably the default namespace, which we currently ignore
- 'lastUpdate' => [ 'Image::ExifTool::XMP::XML', 'lastUpdate', 'LastUpdate' ], # found in XML from Sony ILCE-7S MP4
- );
- # special tags in structures below
- # NOTE: this lookup is duplicated in TagLookup.pm!!
- %specialStruct = (
- STRUCT_NAME => 1, # [optional] name of structure
- NAMESPACE => 1, # [mandatory] namespace prefix used for fields of this structure
- NOTES => 1, # [optional] notes for documentation about this structure
- TYPE => 1, # [optional] rdf:type resource for struct (if used, the StructType flag
- # will be set automatically for all derived flattened tags when writing)
- );
- # XMP structures (each structure is similar to a tag table so we can
- # recurse through them in SetPropertyPath() as if they were tag tables)
- # The main differences between structure field information and tagInfo hashes are:
- # 1) Field information hashes do not contain Name, Groups or Table entries, and
- # 2) The TagID entry is optional, and is used only if the key in the structure hash
- # is different from the TagID (currently only true for alternate language fields)
- # 3) Field information hashes support a additional "Namespace" property.
- my %sResourceRef = (
- STRUCT_NAME => 'ResourceRef',
- NAMESPACE => 'stRef',
- documentID => { },
- instanceID => { },
- manager => { },
- managerVariant => { },
- manageTo => { },
- manageUI => { },
- renditionClass => { },
- renditionParams => { },
- versionID => { },
- # added Oct 2008
- alternatePaths => { List => 'Seq' },
- filePath => { },
- fromPart => { },
- lastModifyDate => { %dateTimeInfo, Groups => { 2 => 'Time' } },
- maskMarkers => { PrintConv => { All => 'All', None => 'None' } },
- partMapping => { },
- toPart => { },
- # added May 2010
- originalDocumentID => { }, # (undocumented property written by Adobe InDesign)
- );
- my %sResourceEvent = (
- STRUCT_NAME => 'ResourceEvent',
- NAMESPACE => 'stEvt',
- action => { },
- instanceID => { },
- parameters => { },
- softwareAgent => { },
- when => { %dateTimeInfo, Groups => { 2 => 'Time' } },
- # added Oct 2008
- changed => { },
- );
- my %sJobRef = (
- STRUCT_NAME => 'JobRef',
- NAMESPACE => 'stJob',
- id => { },
- name => { },
- url => { },
- );
- my %sVersion = (
- STRUCT_NAME => 'Version',
- NAMESPACE => 'stVer',
- comments => { },
- event => { Struct => \%sResourceEvent },
- modifier => { },
- modifyDate => { %dateTimeInfo, Groups => { 2 => 'Time' } },
- version => { },
- );
- my %sThumbnail = (
- STRUCT_NAME => 'Thumbnail',
- NAMESPACE => 'xmpGImg',
- height => { Writable => 'integer' },
- width => { Writable => 'integer' },
- 'format' => { },
- image => {
- Avoid => 1,
- Groups => { 2 => 'Preview' },
- ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)',
- ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)',
- },
- );
- my %sPageInfo = (
- STRUCT_NAME => 'PageInfo',
- NAMESPACE => 'xmpGImg',
- PageNumber => { Writable => 'integer', Namespace => 'xmpTPg' }, # override default namespace
- height => { Writable => 'integer' },
- width => { Writable => 'integer' },
- 'format' => { },
- image => {
- Groups => { 2 => 'Preview' },
- ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)',
- ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)',
- },
- );
- #my %sIdentifierScheme = (
- # NAMESPACE => 'xmpidq',
- # Scheme => { }, # qualifier for xmp:Identifier only
- #);
- %sDimensions = (
- STRUCT_NAME => 'Dimensions',
- NAMESPACE => 'stDim',
- w => { Writable => 'real' },
- h => { Writable => 'real' },
- unit => { },
- );
- %sArea = (
- STRUCT_NAME => 'Area',
- NAMESPACE => 'stArea',
- 'x' => { Writable => 'real' },
- 'y' => { Writable => 'real' },
- w => { Writable => 'real' },
- h => { Writable => 'real' },
- d => { Writable => 'real' },
- unit => { },
- );
- %sColorant = (
- STRUCT_NAME => 'Colorant',
- NAMESPACE => 'xmpG',
- swatchName => { },
- mode => { PrintConv => { CMYK=>'CMYK', RGB=>'RGB', LAB=>'Lab' } },
- # note: do not implement closed choice for "type" because Adobe can't
- # get the case right: spec. says "PROCESS" but Indesign writes "Process"
- type => { },
- cyan => { Writable => 'real' },
- magenta => { Writable => 'real' },
- yellow => { Writable => 'real' },
- black => { Writable => 'real' },
- red => { Writable => 'integer' },
- green => { Writable => 'integer' },
- blue => { Writable => 'integer' },
- L => { Writable => 'real' },
- A => { Writable => 'integer' },
- B => { Writable => 'integer' },
- # 'tint' observed in INDD sample - PH
- tint => { Writable => 'integer', Notes => 'not part of 2010 XMP specification' },
- );
- my %sFont = (
- STRUCT_NAME => 'Font',
- NAMESPACE => 'stFnt',
- fontName => { },
- fontFamily => { },
- fontFace => { },
- fontType => { },
- versionString => { },
- composite => { Writable => 'boolean' },
- fontFileName=> { },
- childFontFiles => { List => 'Seq' },
- );
- my %sOECF = (
- STRUCT_NAME => 'OECF',
- NAMESPACE => 'exif',
- Columns => { Writable => 'integer' },
- Rows => { Writable => 'integer' },
- Names => { List => 'Seq' },
- Values => { List => 'Seq', Writable => 'rational' },
- );
- # new LR2 crs structures (PH)
- my %sCorrectionMask = (
- STRUCT_NAME => 'CorrectionMask',
- NAMESPACE => 'crs',
- # disable List behaviour of flattened Gradient/PaintBasedCorrections
- # because these are nested in lists and the flattened tags can't
- # do justice to this complex structure
- What => { List => 0 },
- MaskValue => { Writable => 'real', List => 0, FlatName => 'Value' },
- Radius => { Writable => 'real', List => 0 },
- Flow => { Writable => 'real', List => 0 },
- CenterWeight => { Writable => 'real', List => 0 },
- Dabs => { List => 'Seq' },
- ZeroX => { Writable => 'real', List => 0 },
- ZeroY => { Writable => 'real', List => 0 },
- FullX => { Writable => 'real', List => 0 },
- FullY => { Writable => 'real', List => 0 },
- # new elements used in CircularGradientBasedCorrections CorrectionMasks
- # and RetouchAreas Masks
- Top => { Writable => 'real', List => 0 },
- Left => { Writable => 'real', List => 0 },
- Bottom => { Writable => 'real', List => 0 },
- Right => { Writable => 'real', List => 0 },
- Angle => { Writable => 'real', List => 0 },
- Midpoint => { Writable => 'real', List => 0 },
- Roundness => { Writable => 'real', List => 0 },
- Feather => { Writable => 'real', List => 0 },
- Flipped => { Writable => 'boolean', List => 0 },
- Version => { Writable => 'integer', List => 0 },
- SizeX => { Writable => 'real', List => 0 },
- SizeY => { Writable => 'real', List => 0 },
- X => { Writable => 'real', List => 0 },
- Y => { Writable => 'real', List => 0 },
- Alpha => { Writable => 'real', List => 0 },
- CenterValue => { Writable => 'real', List => 0 },
- PerimeterValue=>{ Writable => 'real', List => 0 },
- );
- my %sCorrection = (
- STRUCT_NAME => 'Correction',
- NAMESPACE => 'crs',
- What => { List => 0 },
- CorrectionAmount => { FlatName => 'Amount', Writable => 'real', List => 0 },
- CorrectionActive => { FlatName => 'Active', Writable => 'boolean', List => 0 },
- LocalExposure => { FlatName => 'Exposure', Writable => 'real', List => 0 },
- LocalSaturation => { FlatName => 'Saturation', Writable => 'real', List => 0 },
- LocalContrast => { FlatName => 'Contrast', Writable => 'real', List => 0 },
- LocalClarity => { FlatName => 'Clarity', Writable => 'real', List => 0 },
- LocalSharpness => { FlatName => 'Sharpness', Writable => 'real', List => 0 },
- LocalBrightness => { FlatName => 'Brightness', Writable => 'real', List => 0 },
- LocalToningHue => { FlatName => 'Hue', Writable => 'real', List => 0 },
- LocalToningSaturation => { FlatName => 'Saturation', Writable => 'real', List => 0 },
- LocalExposure2012 => { FlatName => 'Exposure2012', Writable => 'real', List => 0 },
- LocalContrast2012 => { FlatName => 'Contrast2012', Writable => 'real', List => 0 },
- LocalHighlights2012 => { FlatName => 'Highlights2012', Writable => 'real', List => 0 },
- LocalShadows2012 => { FlatName => 'Shadows2012', Writable => 'real', List => 0 },
- LocalClarity2012 => { FlatName => 'Clarity2012', Writable => 'real', List => 0 },
- LocalLuminanceNoise => { FlatName => 'LuminanceNoise', Writable => 'real', List => 0 },
- LocalMoire => { FlatName => 'Moire', Writable => 'real', List => 0 },
- LocalDefringe => { FlatName => 'Defringe', Writable => 'real', List => 0 },
- LocalTemperature => { FlatName => 'Temperature',Writable => 'real', List => 0 },
- LocalTint => { FlatName => 'Tint', Writable => 'real', List => 0 },
- CorrectionMasks => {
- FlatName => 'Mask',
- Struct => \%sCorrectionMask,
- List => 'Seq',
- },
- );
- my %sRetouchArea = (
- STRUCT_NAME => 'RetouchArea',
- NAMESPACE => 'crs',
- SpotType => { List => 0 },
- SourceState => { List => 0 },
- Method => { List => 0 },
- SourceX => { Writable => 'real', List => 0 },
- OffsetY => { Writable => 'real', List => 0 },
- Opacity => { Writable => 'real', List => 0 },
- Feather => { Writable => 'real', List => 0 },
- Seed => { Writable => 'integer', List => 0 },
- Masks => {
- FlatName => 'Mask',
- Struct => \%sCorrectionMask,
- List => 'Seq',
- },
- );
- # IPTC Extension 1.0 structures
- my %sLocationDetails = (
- STRUCT_NAME => 'LocationDetails',
- NAMESPACE => 'Iptc4xmpExt',
- City => { },
- CountryCode => { },
- CountryName => { },
- ProvinceState=> { },
- Sublocation => { },
- WorldRegion => { },
- LocationId => { List => 'Bag' },
- );
- my %sCVTermDetails = (
- STRUCT_NAME => 'CVTermDetails',
- NAMESPACE => 'Iptc4xmpExt',
- CvTermId => { },
- CvTermName => { Writable => 'lang-alt' },
- CvId => { },
- CvTermRefinedAbout => { },
- );
- # main XMP tag table (tag ID's are used for the family 1 group names)
- %Image::ExifTool::XMP::Main = (
- GROUPS => { 2 => 'Unknown' },
- PROCESS_PROC => \&ProcessXMP,
- WRITE_PROC => \&WriteXMP,
- dc => {
- Name => 'dc', # (otherwise generated name would be 'Dc')
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::dc' },
- },
- xmp => {
- Name => 'xmp',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmp' },
- },
- xmpDM => {
- Name => 'xmpDM',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpDM' },
- },
- xmpRights => {
- Name => 'xmpRights',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpRights' },
- },
- xmpNote => {
- Name => 'xmpNote',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpNote' },
- },
- xmpMM => {
- Name => 'xmpMM',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpMM' },
- },
- xmpBJ => {
- Name => 'xmpBJ',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpBJ' },
- },
- xmpTPg => {
- Name => 'xmpTPg',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpTPg' },
- },
- pdf => {
- Name => 'pdf',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::pdf' },
- },
- pdfx => {
- Name => 'pdfx',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::pdfx' },
- },
- photoshop => {
- Name => 'photoshop',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::photoshop' },
- },
- crs => {
- Name => 'crs',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::crs' },
- },
- # crss - it would be tedious to add the ability to write this
- aux => {
- Name => 'aux',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::aux' },
- },
- tiff => {
- Name => 'tiff',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::tiff' },
- },
- exif => {
- Name => 'exif',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::exif' },
- },
- exifEX => {
- Name => 'exifEX',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::exifEX' },
- },
- iptcCore => {
- Name => 'iptcCore',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::iptcCore' },
- },
- iptcExt => {
- Name => 'iptcExt',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::iptcExt' },
- },
- PixelLive => {
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::PixelLive' },
- },
- xmpPLUS => {
- Name => 'xmpPLUS',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpPLUS' },
- },
- plus => {
- Name => 'plus',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::plus' },
- },
- cc => {
- Name => 'cc',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::cc' },
- },
- dex => {
- Name => 'dex',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::dex' },
- },
- photomech => {
- Name => 'photomech',
- SubDirectory => { TagTable => 'Image::ExifTool::PhotoMechanic::XMP' },
- },
- mediapro => {
- Name => 'mediapro',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::MediaPro' },
- },
- expressionmedia => {
- Name => 'expressionmedia',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::ExpressionMedia' },
- },
- microsoft => {
- Name => 'microsoft',
- SubDirectory => { TagTable => 'Image::ExifTool::Microsoft::XMP' },
- },
- MP => {
- Name => 'MP',
- SubDirectory => { TagTable => 'Image::ExifTool::Microsoft::MP' },
- },
- MP1 => {
- Name => 'MP1',
- SubDirectory => { TagTable => 'Image::ExifTool::Microsoft::MP1' },
- },
- lr => {
- Name => 'lr',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::Lightroom' },
- },
- DICOM => {
- Name => 'DICOM',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::DICOM' },
- },
- album => {
- Name => 'album',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::Album' },
- },
- prism => {
- Name => 'prism',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::prism' },
- },
- prl => {
- Name => 'prl',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::prl' },
- },
- pur => {
- Name => 'pur',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::pur' },
- },
- pmi => {
- Name => 'pmi',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::pmi' },
- },
- prm => {
- Name => 'prm',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::prm' },
- },
- rdf => {
- Name => 'rdf',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::rdf' },
- },
- 'x' => {
- Name => 'x',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::x' },
- },
- acdsee => {
- Name => 'acdsee',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::acdsee' },
- },
- digiKam => {
- Name => 'digiKam',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::digiKam' },
- },
- swf => {
- Name => 'swf',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::swf' },
- },
- cell => {
- Name => 'cell',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::cell' },
- },
- aas => {
- Name => 'aas',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::aas' },
- },
- 'mwg-rs' => {
- Name => 'mwg-rs',
- SubDirectory => { TagTable => 'Image::ExifTool::MWG::Regions' },
- },
- 'mwg-kw' => {
- Name => 'mwg-kw',
- SubDirectory => { TagTable => 'Image::ExifTool::MWG::Keywords' },
- },
- 'mwg-coll' => {
- Name => 'mwg-coll',
- SubDirectory => { TagTable => 'Image::ExifTool::MWG::Collections' },
- },
- extensis => {
- Name => 'extensis',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::extensis' },
- },
- ics => {
- Name => 'ics',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::ics' },
- },
- fpv => {
- Name => 'fpv',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::fpv' },
- },
- creatorAtom => {
- Name => 'creatorAtom',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::creatorAtom' },
- },
- 'apple-fi' => {
- Name => 'apple-fi',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::apple_fi' },
- },
- GPano => {
- Name => 'GPano',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::GPano' },
- },
- dwc => {
- Name => 'dwc',
- SubDirectory => { TagTable => 'Image::ExifTool::DarwinCore::Main' },
- },
- getty => {
- Name => 'getty',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::GettyImages' },
- },
- );
- # hack to allow XML containing Dublin Core metadata to be handled like XMP (eg. EPUB - see ZIP.pm)
- %Image::ExifTool::XMP::XML = (
- GROUPS => { 0 => 'XML', 1 => 'XML', 2 => 'Unknown' },
- PROCESS_PROC => \&ProcessXMP,
- dc => {
- Name => 'dc',
- SubDirectory => { TagTable => 'Image::ExifTool::XMP::dc' },
- },
- lastUpdate => {
- Groups => { 2 => 'Time' },
- ValueConv => 'Image::ExifTool::XMP::ConvertXMPDate($val)',
- PrintConv => '$self->ConvertDateTime($val)',
- },
- );
- #
- # Tag tables for all XMP namespaces:
- #
- # Writable - only need to define this for writable tags if not plain text
- # (boolean, integer, rational, real, date or lang-alt)
- # List - XMP list type (Bag, Seq or Alt, or set to 1 for elements in Struct lists --
- # this is necessary to obtain proper list behaviour when reading/writing)
- #
- # (Note that family 1 group names are generated from the property namespace, not
- # the group1 names below which exist so the groups will appear in the list.)
- #
- %xmpTableDefaults = (
- WRITE_PROC => \&WriteXMP,
- CHECK_PROC => \&CheckXMP,
- WRITABLE => 'string',
- LANG_INFO => \&GetLangInfo,
- );
- # rdf attributes extracted
- %Image::ExifTool::XMP::rdf = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-rdf', 2 => 'Document' },
- NAMESPACE => 'rdf',
- NOTES => q{
- Most RDF attributes are handled internally, but the "about" attribute is
- treated specially to allow it to be set to a specific value if required.
- },
- about => { Protected => 1 },
- );
- # x attributes extracted
- %Image::ExifTool::XMP::x = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-x', 2 => 'Document' },
- NAMESPACE => 'x',
- NOTES => qq{
- The "x" namespace is used for the "xmpmeta" wrapper, and may contain an
- "xmptk" attribute that is extracted as the XMPToolkit tag. When writing,
- the XMPToolkit tag is generated automatically by ExifTool unless
- specifically set to another value.
- },
- xmptk => { Name => 'XMPToolkit', Protected => 1 },
- );
- # Dublin Core namespace properties (dc)
- %Image::ExifTool::XMP::dc = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-dc', 2 => 'Other' },
- NAMESPACE => 'dc',
- TABLE_DESC => 'XMP Dublin Core',
- NOTES => 'Dublin Core namespace tags.',
- contributor => { Groups => { 2 => 'Author' }, List => 'Bag' },
- coverage => { },
- creator => { Groups => { 2 => 'Author' }, List => 'Seq' },
- date => { Groups => { 2 => 'Time' }, List => 'Seq', %dateTimeInfo },
- description => { Groups => { 2 => 'Image' }, Writable => 'lang-alt' },
- 'format' => { Groups => { 2 => 'Image' } },
- identifier => { Groups => { 2 => 'Image' } },
- language => { List => 'Bag' },
- publisher => { Groups => { 2 => 'Author' }, List => 'Bag' },
- relation => { List => 'Bag' },
- rights => { Groups => { 2 => 'Author' }, Writable => 'lang-alt' },
- source => { Groups => { 2 => 'Author' }, Avoid => 1 },
- subject => { Groups => { 2 => 'Image' }, List => 'Bag' },
- title => { Groups => { 2 => 'Image' }, Writable => 'lang-alt' },
- type => { Groups => { 2 => 'Image' }, List => 'Bag' },
- );
- # XMP namespace properties (xmp, xap)
- %Image::ExifTool::XMP::xmp = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-xmp', 2 => 'Image' },
- NAMESPACE => 'xmp',
- NOTES => q{
- XMP namespace tags. If the older "xap", "xapBJ", "xapMM" or "xapRights"
- namespace prefixes are found, they are translated to the newer "xmp",
- "xmpBJ", "xmpMM" and "xmpRights" prefixes for use in family 1 group names.
- },
- Advisory => { List => 'Bag', Notes => 'deprecated' },
- BaseURL => { },
- # (date/time tags not as reliable as EXIF)
- CreateDate => { Groups => { 2 => 'Time' }, %dateTimeInfo, Priority => 0 },
- CreatorTool => { },
- Identifier => { Avoid => 1, List => 'Bag' },
- Label => { },
- MetadataDate=> { Groups => { 2 => 'Time' }, %dateTimeInfo },
- ModifyDate => { Groups => { 2 => 'Time' }, %dateTimeInfo, Priority => 0 },
- Nickname => { },
- Rating => { Writable => 'real', Notes => 'a value from 0 to 5, or -1 for "rejected"' },
- Thumbnails => {
- FlatName => 'Thumbnail',
- Struct => \%sThumbnail,
- List => 'Alt',
- },
- # the following written by Adobe InDesign, not part of XMP spec:
- PageInfo => {
- FlatName => 'PageImage',
- Struct => \%sPageInfo,
- List => 'Seq',
- },
- PageInfoImage => { Name => 'PageImage', Flat => 1 },
- Title => { Avoid => 1, Notes => 'non-standard', Writable => 'lang-alt' }, #11
- Author => { Avoid => 1, Notes => 'non-standard', Groups => { 2 => 'Author' } }, #11
- Keywords => { Avoid => 1, Notes => 'non-standard' }, #11
- Description => { Avoid => 1, Notes => 'non-standard', Writable => 'lang-alt' }, #11
- Format => { Avoid => 1, Notes => 'non-standard' }, #11
- );
- # XMP Rights Management namespace properties (xmpRights, xapRights)
- %Image::ExifTool::XMP::xmpRights = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-xmpRights', 2 => 'Author' },
- NAMESPACE => 'xmpRights',
- NOTES => 'XMP Rights Management namespace tags.',
- Certificate => { },
- Marked => { Writable => 'boolean' },
- Owner => { List => 'Bag' },
- UsageTerms => { Writable => 'lang-alt' },
- WebStatement => { },
- );
- # XMP Note namespace properties (xmpNote)
- %Image::ExifTool::XMP::xmpNote = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-xmpNote' },
- NAMESPACE => 'xmpNote',
- NOTES => 'XMP Note namespace tags.',
- HasExtendedXMP => {
- Notes => q{
- this tag is protected so it is not writable directly. Instead, it is set
- automatically to the GUID of the extended XMP when writing extended XMP to a
- JPEG image
- },
- Protected => 2,
- },
- );
- # XMP xmpMM ManifestItem struct (ref PH, written by Adobe PDF library 8.0)
- my %sManifestItem = (
- STRUCT_NAME => 'ManifestItem',
- NAMESPACE => 'stMfs',
- linkForm => { },
- placedXResolution => { Namespace => 'xmpMM', Writable => 'real' },
- placedYResolution => { Namespace => 'xmpMM', Writable => 'real' },
- placedResolutionUnit=> { Namespace => 'xmpMM' },
- reference => { Struct => \%sResourceRef },
- );
- # the xmpMM Pantry
- my %sPantryItem = (
- STRUCT_NAME => 'PantryItem',
- NAMESPACE => undef, # stores any top-level XMP tags
- NOTES => q{
- This structure must have an InstanceID field, but may also contain any other
- XMP properties.
- },
- InstanceID => { Namespace => 'xmpMM' },
- );
- # XMP Media Management namespace properties (xmpMM, xapMM)
- %Image::ExifTool::XMP::xmpMM = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-xmpMM', 2 => 'Other' },
- NAMESPACE => 'xmpMM',
- TABLE_DESC => 'XMP Media Management',
- NOTES => 'XMP Media Management namespace tags.',
- DerivedFrom => { Struct => \%sResourceRef },
- DocumentID => { },
- History => { Struct => \%sResourceEvent, List => 'Seq' },
- # we treat these like list items since History is a list
- Ingredients => { Struct => \%sResourceRef, List => 'Bag' },
- InstanceID => { }, #PH (CS3)
- ManagedFrom => { Struct => \%sResourceRef },
- Manager => { Groups => { 2 => 'Author' } },
- ManageTo => { Groups => { 2 => 'Author' } },
- ManageUI => { },
- ManagerVariant => { },
- Manifest => { Struct => \%sManifestItem, List => 'Bag' },
- OriginalDocumentID=> { },
- Pantry => { Struct => \%sPantryItem, List => 'Bag' },
- PreservedFileName => { }, # undocumented
- RenditionClass => { },
- RenditionParams => { },
- VersionID => { },
- Versions => { Struct => \%sVersion, List => 'Seq' },
- LastURL => { }, # (deprecated)
- RenditionOf => { Struct => \%sResourceRef }, # (deprecated)
- SaveID => { Writable => 'integer' }, # (deprecated)
- subject => { List => 'Seq', Avoid => 1, Notes => 'undocumented' },
- );
- # XMP Basic Job Ticket namespace properties (xmpBJ, xapBJ)
- %Image::ExifTool::XMP::xmpBJ = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-xmpBJ', 2 => 'Other' },
- NAMESPACE => 'xmpBJ',
- TABLE_DESC => 'XMP Basic Job Ticket',
- NOTES => 'XMP Basic Job Ticket namespace tags.',
- # Note: JobRef is a List of structures. To accomplish this, we set the XMP
- # List=>'Bag', but since SubDirectory is defined, this tag isn't writable
- # directly. Then we need to set List=>1 for the members so the Writer logic
- # will allow us to add list items.
- JobRef => { Struct => \%sJobRef, List => 'Bag' },
- );
- # XMP Paged-Text namespace properties (xmpTPg)
- %Image::ExifTool::XMP::xmpTPg = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-xmpTPg', 2 => 'Image' },
- NAMESPACE => 'xmpTPg',
- TABLE_DESC => 'XMP Paged-Text',
- NOTES => 'XMP Paged-Text namespace tags.',
- MaxPageSize => { Struct => \%sDimensions },
- NPages => { Writable => 'integer' },
- Fonts => {
- FlatName => '',
- Struct => \%sFont,
- List => 'Bag',
- },
- FontsVersionString => { Name => 'FontVersion', Flat => 1 },
- FontsComposite => { Name => 'FontComposite', Flat => 1 },
- Colorants => {
- FlatName => 'Colorant',
- Struct => \%sColorant,
- List => 'Seq',
- },
- PlateNames => { List => 'Seq' },
- );
- # PDF namespace properties (pdf)
- %Image::ExifTool::XMP::pdf = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-pdf', 2 => 'Image' },
- NAMESPACE => 'pdf',
- TABLE_DESC => 'XMP PDF',
- NOTES => q{
- Adobe PDF namespace tags. The official XMP specification defines only
- Keywords, PDFVersion, Producer and Trapped. The other tags are included
- because they have been observed in PDF files, but some are avoided when
- writing due to name conflicts with other XMP namespaces.
- },
- Author => { Groups => { 2 => 'Author' } }, #PH
- ModDate => { Groups => { 2 => 'Time' }, %dateTimeInfo }, #PH
- CreationDate=> { Groups => { 2 => 'Time' }, %dateTimeInfo }, #PH
- Creator => { Groups => { 2 => 'Author' }, Avoid => 1 },
- Copyright => { Groups => { 2 => 'Author' }, Avoid => 1 }, #PH
- Marked => { Avoid => 1, Writable => 'boolean' }, #PH
- Subject => { Avoid => 1 },
- Title => { Avoid => 1 },
- Trapped => { #PH
- # remove leading '/' from '/True' or '/False'
- ValueConv => '$val=~s{^/}{}; $val',
- ValueConvInv => '"/$val"',
- PrintConv => { True => 'True', False => 'False', Unknown => 'Unknown' },
- },
- Keywords => { Priority => 0 },
- PDFVersion => { },
- Producer => { Groups => { 2 => 'Author' } },
- );
- # PDF extension namespace properties (pdfx)
- %Image::ExifTool::XMP::pdfx = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-pdfx', 2 => 'Document' },
- NAMESPACE => 'pdfx',
- NOTES => q{
- PDF extension tags. This namespace is used to store application-defined PDF
- information, so there are no pre-defined tags. User-defined tags must be
- created to enable writing of XMP-pdfx information.
- },
- );
- # Photoshop namespace properties (photoshop)
- %Image::ExifTool::XMP::photoshop = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-photoshop', 2 => 'Image' },
- NAMESPACE => 'photoshop',
- TABLE_DESC => 'XMP Photoshop',
- NOTES => 'Adobe Photoshop namespace tags.',
- AuthorsPosition => { Groups => { 2 => 'Author' } },
- CaptionWriter => { Groups => { 2 => 'Author' } },
- Category => { },
- City => { Groups => { 2 => 'Location' } },
- ColorMode => {
- Writable => 'integer', # (as of July 2010 spec, courtesy of yours truly)
- PrintConvColumns => 2,
- PrintConv => {
- 0 => 'Bitmap',
- 1 => 'Grayscale',
- 2 => 'Indexed',
- 3 => 'RGB',
- 4 => 'CMYK',
- 7 => 'Multichannel',
- 8 => 'Duotone',
- 9 => 'Lab',
- },
- },
- Country => { Groups => { 2 => 'Location' } },
- Credit => { Groups => { 2 => 'Author' } },
- DateCreated => { Groups => { 2 => 'Time' }, %dateTimeInfo },
- DocumentAncestors => {
- List => 'Bag',
- # Contrary to their own XMP specification, Adobe writes this as a simple Bag
- # of strings instead of structures, so comment out the structure definition...
- # FlatName => 'Document',
- # Struct => {
- # STRUCT_NAME => 'Ancestor',
- # NAMESPACE => 'photoshop',
- # AncestorID => { },
- # },
- },
- Headline => { },
- History => { }, #PH (CS3)
- ICCProfile => { Name => 'ICCProfileName' }, #PH
- Instructions => { },
- LegacyIPTCDigest=> { }, #PH
- SidecarForExtension => { }, #PH (CS3)
- Source => { Groups => { 2 => 'Author' } },
- State => { Groups => { 2 => 'Location' } },
- # the XMP spec doesn't show SupplementalCategories as a 'Bag', but
- # that's the way Photoshop writes it [fixed in the June 2005 XMP spec].
- # Also, it is incorrectly listed as "SupplementalCategory" in the
- # IPTC Standard Photo Metadata docs (2008rev2 and July 2009rev1) - PH
- SupplementalCategories => { List => 'Bag' },
- TextLayers => {
- FlatName => 'Text',
- List => 'Seq',
- Struct => {
- STRUCT_NAME => 'Layer',
- NAMESPACE => 'photoshop',
- LayerName => { },
- LayerText => { },
- },
- },
- TransmissionReference => { },
- Urgency => {
- Writable => 'integer',
- Notes => 'should be in the range 1-8 to conform with the XMP spec',
- PrintConv => { # (same values as IPTC:Urgency)
- 0 => '0 (reserved)', # (not standard XMP)
- 1 => '1 (most urgent)',
- 2 => 2,
- 3 => 3,
- 4 => 4,
- 5 => '5 (normal urgency)',
- 6 => 6,
- 7 => 7,
- 8 => '8 (least urgent)',
- 9 => '9 (user-defined priority)', # (not standard XMP)
- },
- },
- EmbeddedXMPDigest => { }, #PH (LR5)
- );
- # Photoshop Camera Raw namespace properties (crs) - (ref 8,PH)
- %Image::ExifTool::XMP::crs = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-crs', 2 => 'Image' },
- NAMESPACE => 'crs',
- TABLE_DESC => 'Photoshop Camera Raw namespace',
- NOTES => q{
- Photoshop Camera Raw namespace tags. It is a shame that Adobe pollutes the
- metadata space with these incredibly bulky image editing parameters.
- },
- AlreadyApplied => { Writable => 'boolean' }, #PH (written by LightRoom beta 4.1)
- AutoBrightness => { Writable => 'boolean' },
- AutoContrast => { Writable => 'boolean' },
- AutoExposure => { Writable => 'boolean' },
- AutoShadows => { Writable => 'boolean' },
- BlueHue => { Writable => 'integer' },
- BlueSaturation => { Writable => 'integer' },
- Brightness => { Writable => 'integer' },
- CameraProfile => { },
- ChromaticAberrationB=> { Writable => 'integer' },
- ChromaticAberrationR=> { Writable => 'integer' },
- ColorNoiseReduction => { Writable => 'integer' },
- Contrast => { Writable => 'integer', Avoid => 1 },
- Converter => { }, #PH guess (found in EXIF)
- CropTop => { Writable => 'real' },
- CropLeft => { Writable => 'real' },
- CropBottom => { Writable => 'real' },
- CropRight => { Writable => 'real' },
- CropAngle => { Writable => 'real' },
- CropWidth => { Writable => 'real' },
- CropHeight => { Writable => 'real' },
- CropUnits => {
- Writable => 'integer',
- PrintConv => {
- 0 => 'pixels',
- 1 => 'inches',
- 2 => 'cm',
- },
- },
- Exposure => { Writable => 'real' },
- GreenHue => { Writable => 'integer' },
- GreenSaturation => { Writable => 'integer' },
- HasCrop => { Writable => 'boolean' },
- HasSettings => { Writable => 'boolean' },
- LuminanceSmoothing => { Writable => 'integer' },
- MoireFilter => { PrintConv => { Off=>'Off', On=>'On' } },
- RawFileName => { },
- RedHue => { Writable => 'integer' },
- RedSaturation => { Writable => 'integer' },
- Saturation => { Writable => 'integer', Avoid => 1 },
- Shadows => { Writable => 'integer' },
- ShadowTint => { Writable => 'integer' },
- Sharpness => { Writable => 'integer', Avoid => 1 },
- Smoothness => { Writable => 'integer' },
- Temperature => { Writable => 'integer', Avoid => 1, Name => 'ColorTemperature' },
- Tint => { Writable => 'integer' },
- ToneCurve => { List => 'Seq' },
- ToneCurveName => {
- PrintConv => {
- Linear => 'Linear',
- 'Medium Contrast' => 'Medium Contrast',
- 'Strong Contrast' => 'Strong Contrast',
- Custom => 'Custom',
- },
- },
- Version => { },
- VignetteAmount => { Writable => 'integer' },
- VignetteMidpoint=> { Writable => 'integer' },
- WhiteBalance => {
- Avoid => 1,
- PrintConv => {
- 'As Shot' => 'As Shot',
- Auto => 'Auto',
- Daylight => 'Daylight',
- Cloudy => 'Cloudy',
- Shade => 'Shade',
- Tungsten => 'Tungsten',
- Fluorescent => 'Fluorescent',
- Flash => 'Flash',
- Custom => 'Custom',
- },
- },
- # new tags observed in Adobe Lightroom output - PH
- CameraProfileDigest => { },
- Clarity => { Writable => 'integer' },
- ConvertToGrayscale => { Writable => 'boolean' },
- Defringe => { Writable => 'integer' },
- FillLight => { Writable => 'integer' },
- HighlightRecovery => { Writable => 'integer' },
- HueAdjustmentAqua => { Writable => 'integer' },
- HueAdjustmentBlue => { Writable => 'integer' },
- HueAdjustmentGreen => { Writable => 'integer' },
- HueAdjustmentMagenta => { Writable => 'integer' },
- HueAdjustmentOrange => { Writable => 'integer' },
- HueAdjustmentPurple => { Writable => 'integer' },
- HueAdjustmentRed => { Writable => 'integer' },
- HueAdjustmentYellow => { Writable => 'integer' },
- IncrementalTemperature => { Writable => 'integer' },
- IncrementalTint => { Writable => 'integer' },
- LuminanceAdjustmentAqua => { Writable => 'integer' },
- LuminanceAdjustmentBlue => { Writable => 'integer' },
- LuminanceAdjustmentGreen => { Writable => 'integer' },
- LuminanceAdjustmentMagenta => { Writable => 'integer' },
- LuminanceAdjustmentOrange => { Writable => 'integer' },
- LuminanceAdjustmentPurple => { Writable => 'integer' },
- LuminanceAdjustmentRed => { Writable => 'integer' },
- LuminanceAdjustmentYellow => { Writable => 'integer' },
- ParametricDarks => { Writable => 'integer' },
- ParametricHighlights => { Writable => 'integer' },
- ParametricHighlightSplit => { Writable => 'integer' },
- ParametricLights => { Writable => 'integer' },
- ParametricMidtoneSplit => { Writable => 'integer' },
- ParametricShadows => { Writable => 'integer' },
- ParametricShadowSplit => { Writable => 'integer' },
- SaturationAdjustmentAqua => { Writable => 'integer' },
- SaturationAdjustmentBlue => { Writable => 'integer' },
- SaturationAdjustmentGreen => { Writable => 'integer' },
- SaturationAdjustmentMagenta => { Writable => 'integer' },
- SaturationAdjustmentOrange => { Writable => 'integer' },
- SaturationAdjustmentPurple => { Writable => 'integer' },
- SaturationAdjustmentRed => { Writable => 'integer' },
- SaturationAdjustmentYellow => { Writable => 'integer' },
- SharpenDetail => { Writable => 'integer' },
- SharpenEdgeMasking => { Writable => 'integer' },
- SharpenRadius => { Writable => 'real' },
- SplitToningBalance => { Writable => 'integer' },
- SplitToningHighlightHue => { Writable => 'integer' },
- SplitToningHighlightSaturation => { Writable => 'integer' },
- SplitToningShadowHue => { Writable => 'integer' },
- SplitToningShadowSaturation => { Writable => 'integer' },
- Vibrance => { Writable => 'integer' },
- # new tags written by LR 1.4 (not sure in what version they first appeared)
- GrayMixerRed => { Writable => 'integer' },
- GrayMixerOrange => { Writable => 'integer' },
- GrayMixerYellow => { Writable => 'integer' },
- GrayMixerGreen => { Writable => 'integer' },
- GrayMixerAqua => { Writable => 'integer' },
- GrayMixerBlue => { Writable => 'integer' },
- GrayMixerPurple => { Writable => 'integer' },
- GrayMixerMagenta => { Writable => 'integer' },
- RetouchInfo => { List => 'Seq' },
- RedEyeInfo => { List => 'Seq' },
- # new tags written by LR 2.0 (ref PH)
- CropUnit => { # was the XMP documentation wrong with "CropUnits"??
- Writable => 'integer',
- PrintConv => {
- 0 => 'pixels',
- 1 => 'inches',
- 2 => 'cm',
- # have seen a value of 3 here! - PH
- },
- },
- PostCropVignetteAmount => { Writable => 'integer' },
- PostCropVignetteMidpoint => { Writable => 'integer' },
- PostCropVignetteFeather => { Writable => 'integer' },
- PostCropVignetteRoundness => { Writable => 'integer' },
- PostCropVignetteStyle => { Writable => 'integer' },
- # disable List behaviour of flattened Gradient/PaintBasedCorrections
- # because these are nested in lists and the flattened tags can't
- # do justice to this complex structure
- GradientBasedCorrections => {
- FlatName => 'GradientBasedCorr',
- Struct => \%sCorrection,
- List => 'Seq',
- },
- GradientBasedCorrectionsCorrectionMasks => {
- Name => 'GradientBasedCorrMasks',
- FlatName => 'GradientBasedCorrMask',
- Flat => 1
- },
- GradientBasedCorrectionsCorrectionMasksDabs => {
- Name => 'GradientBasedCorrMaskDabs',
- Flat => 1, List => 0,
- },
- PaintBasedCorrections => {
- FlatName => 'PaintCorrection',
- Struct => \%sCorrection,
- List => 'Seq',
- },
- PaintBasedCorrectionsCorrectionMasks => {
- Name => 'PaintBasedCorrectionMasks',
- FlatName => 'PaintCorrectionMask',
- Flat => 1,
- },
- PaintBasedCorrectionsCorrectionMasksDabs => {
- Name => 'PaintCorrectionMaskDabs',
- Flat => 1, List => 0,
- },
- # new tags written by LR 3 (thanks Wolfgang Guelcker)
- ProcessVersion => { },
- LensProfileEnable => { Writable => 'integer' },
- LensProfileSetup => { },
- LensProfileName => { },
- LensProfileFilename => { },
- LensProfileDigest => { },
- LensProfileDistortionScale => { Writable => 'integer' },
- LensProfileChromaticAberrationScale => { Writable => 'integer' },
- LensProfileVignettingScale => { Writable => 'integer' },
- LensManualDistortionAmount => { Writable => 'integer' },
- PerspectiveVertical => { Writable => 'integer' },
- PerspectiveHorizontal => { Writable => 'integer' },
- PerspectiveRotate => { Writable => 'real' },
- PerspectiveScale => { Writable => 'integer' },
- CropConstrainToWarp => { Writable => 'integer' },
- LuminanceNoiseReductionDetail => { Writable => 'integer' },
- LuminanceNoiseReductionContrast => { Writable => 'integer' },
- ColorNoiseReductionDetail => { Writable => 'integer' },
- GrainAmount => { Writable => 'integer' },
- GrainSize => { Writable => 'integer' },
- GrainFrequency => { Writable => 'integer' },
- # new tags written by LR4
- AutoLateralCA => { Writable => 'integer' },
- Exposure2012 => { Writable => 'real' },
- Contrast2012 => { Writable => 'integer' },
- Highlights2012 => { Writable => 'integer' },
- Shadows2012 => { Writable => 'integer' },
- Whites2012 => { Writable => 'integer' },
- Blacks2012 => { Writable => 'integer' },
- Clarity2012 => { Writable => 'integer' },
- PostCropVignetteHighlightContrast => { Writable => 'integer' },
- ToneCurveName2012 => { },
- ToneCurveRed => { List => 'Seq' },
- ToneCurveGreen => { List => 'Seq' },
- ToneCurveBlue => { List => 'Seq' },
- ToneCurvePV2012 => { List => 'Seq' },
- ToneCurvePV2012Red => { List => 'Seq' },
- ToneCurvePV2012Green => { List => 'Seq' },
- ToneCurvePV2012Blue => { List => 'Seq' },
- DefringePurpleAmount => { Writable => 'integer' },
- DefringePurpleHueLo => { Writable => 'integer' },
- DefringePurpleHueHi => { Writable => 'integer' },
- DefringeGreenAmount => { Writable => 'integer' },
- DefringeGreenHueLo => { Writable => 'integer' },
- DefringeGreenHueHi => { Writable => 'integer' },
- # new tags written by LR5
- AutoWhiteVersion => { Writable => 'integer' },
- CircularGradientBasedCorrections => {
- FlatName => 'CircGradBasedCorr',
- Struct => \%sCorrection,
- List => 'Seq',
- },
- CircularGradientBasedCorrectionsCorrectionMasks => {
- Name => 'CircGradBasedCorrMasks',
- FlatName => 'CircGradBasedCorrMask',
- Flat => 1
- },
- CircularGradientBasedCorrectionsCorrectionMasksDabs => {
- Name => 'CircGradBasedCorrMaskDabs',
- Flat => 1, List => 0,
- },
- ColorNoiseReductionSmoothness => { Writable => 'integer' },
- PerspectiveAspect => { Writable => 'integer' },
- PerspectiveUpright => { Writable => 'integer' },
- RetouchAreas => {
- FlatName => 'RetouchArea',
- Struct => \%sRetouchArea,
- List => 'Seq',
- },
- RetouchAreasMasks => {
- Name => 'RetouchAreaMasks',
- FlatName => 'RetouchAreaMask',
- Flat => 1
- },
- RetouchAreasMasksDabs => {
- Name => 'RetouchAreaMaskDabs',
- Flat => 1, List => 0,
- },
- UprightVersion => { Writable => 'integer' },
- UprightCenterMode => { Writable => 'integer' },
- UprightCenterNormX => { Writable => 'real' },
- UprightCenterNormY => { Writable => 'real' },
- UprightFocalMode => { Writable => 'integer' },
- UprightFocalLength35mm => { Writable => 'real' },
- UprightPreview => { Writable => 'boolean' },
- UprightTransformCount => { Writable => 'integer' },
- UprightDependentDigest => { },
- UprightTransform_0 => { },
- UprightTransform_1 => { },
- UprightTransform_2 => { },
- UprightTransform_3 => { },
- UprightTransform_4 => { },
- # more stuff seen in lens profile file (unknown source)
- What => { }, # (with value "LensProfileDefaultSettings")
- LensProfileMatchKeyExifMake => { },
- LensProfileMatchKeyExifModel => { },
- LensProfileMatchKeyCameraModelName => { },
- LensProfileMatchKeyLensInfo => { },
- LensProfileMatchKeyLensID => { },
- LensProfileMatchKeyLensName => { },
- LensProfileMatchKeyIsRaw => { Writable => 'boolean' },
- LensProfileMatchKeySensorFormatFactor=>{ Writable => 'real' },
- );
- # Tiff namespace properties (tiff)
- %Image::ExifTool::XMP::tiff = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-tiff', 2 => 'Image' },
- NAMESPACE => 'tiff',
- PRIORITY => 0, # not as reliable as actual TIFF tags
- TABLE_DESC => 'XMP TIFF',
- NOTES => q{
- EXIF namespace for TIFF tags. See
- L<http://www.cipa.jp/std/documents/e/DC-010-2012_E.pdf> for the
- specification.
- },
- ImageWidth => { Writable => 'integer' },
- ImageLength => { Writable => 'integer', Name => 'ImageHeight' },
- BitsPerSample => { Writable => 'integer', List => 'Seq', AutoSplit => 1 },
- Compression => {
- Writable => 'integer',
- SeparateTable => 'EXIF Compression',
- PrintConv => \%Image::ExifTool::Exif::compression,
- },
- PhotometricInterpretation => {
- Writable => 'integer',
- PrintConv => \%Image::ExifTool::Exif::photometricInterpretation,
- },
- Orientation => {
- Writable => 'integer',
- PrintConv => \%Image::ExifTool::Exif::orientation,
- },
- SamplesPerPixel => { Writable => 'integer' },
- PlanarConfiguration => {
- Writable => 'integer',
- PrintConv => {
- 1 => 'Chunky',
- 2 => 'Planar',
- },
- },
- YCbCrSubSampling => {
- Writable => 'integer',
- List => 'Seq',
- # join the raw values before conversion to allow PrintConv to operate on
- # the combined string as it does for the corresponding EXIF tag
- RawJoin => 1,
- Notes => q{
- while technically this is a list-type tag, for compatibility with its EXIF
- counterpart it is written and read as a simple string
- },
- PrintConv => \%Image::ExifTool::JPEG::yCbCrSubSampling,
- },
- YCbCrPositioning => {
- Writable => 'integer',
- PrintConv => {
- 1 => 'Centered',
- 2 => 'Co-sited',
- },
- },
- XResolution => { Writable => 'rational' },
- YResolution => { Writable => 'rational' },
- ResolutionUnit => {
- Writable => 'integer',
- Notes => 'the value 1 is not standard EXIF',
- PrintConv => {
- 1 => 'None',
- 2 => 'inches',
- 3 => 'cm',
- },
- },
- TransferFunction => { Writable => 'integer', List => 'Seq', AutoSplit => 1 },
- WhitePoint => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
- PrimaryChromaticities => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
- YCbCrCoefficients => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
- ReferenceBlackWhite => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
- DateTime => { # (EXIF tag named ModifyDate, but this exists in XMP-xmp)
- Description => 'Date/Time Modified',
- Groups => { 2 => 'Time' },
- %dateTimeInfo,
- },
- ImageDescription => { Writable => 'lang-alt' },
- Make => {
- Groups => { 2 => 'Camera' },
- RawConv => '$$self{Make} ? $val : $$self{Make} = $val',
- },
- Model => {
- Groups => { 2 => 'Camera' },
- Description => 'Camera Model Name',
- RawConv => '$$self{Model} ? $val : $$self{Model} = $val',
- },
- Software => { },
- Artist => { Groups => { 2 => 'Author' } },
- Copyright => { Groups => { 2 => 'Author' }, Writable => 'lang-alt' },
- NativeDigest => { }, #PH
- );
- # Exif namespace properties (exif)
- %Image::ExifTool::XMP::exif = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-exif', 2 => 'Image' },
- NAMESPACE => 'exif',
- PRIORITY => 0, # not as reliable as actual EXIF tags
- NOTES => q{
- EXIF namespace for EXIF tags. See
- L<http://www.cipa.jp/std/documents/e/DC-010-2012_E.pdf> for the
- specification.
- },
- ExifVersion => { },
- FlashpixVersion => { },
- ColorSpace => {
- Writable => 'integer',
- # (some applications incorrectly write -1 as a long integer)
- ValueConv => '$val == 0xffffffff ? 0xffff : $val',
- ValueConvInv => '$val',
- PrintConv => {
- 1 => 'sRGB',
- 2 => 'Adobe RGB',
- 0xffff => 'Uncalibrated',
- },
- },
- ComponentsConfiguration => {
- Writable => 'integer',
- List => 'Seq',
- AutoSplit => 1,
- PrintConvColumns => 2,
- PrintConv => {
- 0 => '-',
- 1 => 'Y',
- 2 => 'Cb',
- 3 => 'Cr',
- 4 => 'R',
- 5 => 'G',
- 6 => 'B',
- },
- },
- CompressedBitsPerPixel => { Writable => 'rational' },
- PixelXDimension => { Name => 'ExifImageWidth', Writable => 'integer' },
- PixelYDimension => { Name => 'ExifImageHeight', Writable => 'integer' },
- MakerNote => { },
- UserComment => { Writable => 'lang-alt' },
- RelatedSoundFile => { },
- DateTimeOriginal => {
- Description => 'Date/Time Original',
- Groups => { 2 => 'Time' },
- %dateTimeInfo,
- },
- DateTimeDigitized => { # (EXIF tag named CreateDate, but this exists in XMP-xmp)
- Description => 'Date/Time Digitized',
- Groups => { 2 => 'Time' },
- %dateTimeInfo,
- },
- ExposureTime => {
- Writable => 'rational',
- PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
- PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)',
- },
- FNumber => {
- Writable => 'rational',
- PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)',
- PrintConvInv => '$val',
- },
- ExposureProgram => {
- Groups => { 2 => 'Camera' },
- Writable => 'integer',
- PrintConv => {
- 0 => 'Not Defined',
- 1 => 'Manual',
- 2 => 'Program AE',
- 3 => 'Aperture-priority AE',
- 4 => 'Shutter speed priority AE',
- 5 => 'Creative (Slow speed)',
- 6 => 'Action (High speed)',
- 7 => 'Portrait',
- 8 => 'Landscape',
- },
- },
- SpectralSensitivity => { Groups => { 2 => 'Camera' } },
- ISOSpeedRatings => {
- Name => 'ISO',
- Writable => 'integer',
- List => 'Seq',
- AutoSplit => 1,
- },
- OECF => {
- Name => 'Opto-ElectricConvFactor',
- FlatName => 'OECF',
- Groups => { 2 => 'Camera' },
- Struct => \%sOECF,
- },
- ShutterSpeedValue => {
- Writable => 'rational',
- ValueConv => 'abs($val)<100 ? 1/(2**$val) : 0',
- PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
- ValueConvInv => '$val>0 ? -log($val)/log(2) : 0',
- PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)',
- },
- ApertureValue => {
- Writable => 'rational',
- ValueConv => 'sqrt(2) ** $val',
- PrintConv => 'sprintf("%.1f",$val)',
- ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0',
- PrintConvInv => '$val',
- },
- BrightnessValue => { Writable => 'rational' },
- ExposureBiasValue => {
- Name => 'ExposureCompensation',
- Writable => 'rational',
- PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)',
- PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)',
- },
- MaxApertureValue => {
- Groups => { 2 => 'Camera' },
- Writable => 'rational',
- ValueConv => 'sqrt(2) ** $val',
- PrintConv => 'sprintf("%.1f",$val)',
- ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0',
- PrintConvInv => '$val',
- },
- SubjectDistance => {
- Groups => { 2 => 'Camera' },
- Writable => 'rational',
- PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"',
- PrintConvInv => '$val=~s/\s*m$//;$val',
- },
- MeteringMode => {
- Groups => { 2 => 'Camera' },
- Writable => 'integer',
- PrintConv => {
- 1 => 'Average',
- 2 => 'Center-weighted average',
- 3 => 'Spot',
- 4 => 'Multi-spot',
- 5 => 'Multi-segment',
- 6 => 'Partial',
- 255 => 'Other',
- },
- },
- LightSource => {
- Groups => { 2 => 'Camera' },
- SeparateTable => 'EXIF LightSource',
- PrintConv => \%Image::ExifTool::Exif::lightSource,
- },
- Flash => {
- Groups => { 2 => 'Camera' },
- Struct => {
- STRUCT_NAME => 'Flash',
- NAMESPACE => 'exif',
- Fired => { Writable => 'boolean', %boolConv },
- Return => {
- Writable => 'integer',
- PrintConv => {
- 0 => 'No return detection',
- 2 => 'Return not detected',
- 3 => 'Return detected',
- },
- },
- Mode => {
- Writable => 'integer',
- PrintConv => {
- 0 => 'Unknown',
- 1 => 'On',
- 2 => 'Off',
- 3 => 'Auto',
- },
- },
- Function => { Writable => 'boolean', %boolConv },
- RedEyeMode => { Writable => 'boolean', %boolConv },
- },
- },
- FocalLength=> {
- Groups => { 2 => 'Camera' },
- Writable => 'rational',
- PrintConv => 'sprintf("%.1f mm",$val)',
- PrintConvInv => '$val=~s/\s*mm$//;$val',
- },
- SubjectArea => { Writable => 'integer', List => 'Seq', AutoSplit => 1 },
- FlashEnergy => { Groups => { 2 => 'Camera' }, Writable => 'rational' },
- SpatialFrequencyResponse => {
- Groups => { 2 => 'Camera' },
- Struct => \%sOECF,
- },
- FocalPlaneXResolution => { Groups => { 2 => 'Camera' }, Writable => 'rational' },
- FocalPlaneYResolution => { Groups => { 2 => 'Camera' }, Writable => 'rational' },
- FocalPlaneResolutionUnit => {
- Groups => { 2 => 'Camera' },
- Writable => 'integer',
- Notes => 'values 1, 4 and 5 are not standard EXIF',
- PrintConv => {
- 1 => 'None', # (not standard EXIF)
- 2 => 'inches',
- 3 => 'cm',
- 4 => 'mm', # (not standard EXIF)
- 5 => 'um', # (not standard EXIF)
- },
- },
- SubjectLocation => { Writable => 'integer', List => 'Seq', AutoSplit => 1 },
- ExposureIndex => { Writable => 'rational' },
- SensingMethod => {
- Groups => { 2 => 'Camera' },
- Writable => 'integer',
- Notes => 'values 1 and 6 are not standard EXIF',
- PrintConv => {
- 1 => 'Monochrome area', # (not standard EXIF)
- 2 => 'One-chip color area',
- 3 => 'Two-chip color area',
- 4 => 'Three-chip color area',
- 5 => 'Color sequential area',
- 6 => 'Monochrome linear', # (not standard EXIF)
- 7 => 'Trilinear',
- 8 => 'Color sequential linear',
- },
- },
- FileSource => {
- Writable => 'integer',
- PrintConv => {
- 1 => 'Film Scanner',
- 2 => 'Reflection Print Scanner',
- 3 => 'Digital Camera',
- }
- },
- SceneType => { Writable => 'integer', PrintConv => { 1 => 'Directly photographed' } },
- CFAPattern => {
- Struct => {
- STRUCT_NAME => 'CFAPattern',
- NAMESPACE => 'exif',
- Columns => { Writable => 'integer' },
- Rows => { Writable => 'integer' },
- Values => { Writable => 'integer', List => 'Seq' },
- },
- },
- CustomRendered => {
- Writable => 'integer',
- PrintConv => {
- 0 => 'Normal',
- 1 => 'Custom',
- },
- },
- ExposureMode => {
- Groups => { 2 => 'Camera' },
- Writable => 'integer',
- PrintConv => {
- 0 => 'Auto',
- 1 => 'Manual',
- 2 => 'Auto bracket',
- },
- },
- WhiteBalance => {
- Groups => { 2 => 'Camera' },
- Writable => 'integer',
- PrintConv => {
- 0 => 'Auto',
- 1 => 'Manual',
- },
- },
- DigitalZoomRatio => { Writable => 'rational' },
- FocalLengthIn35mmFilm => {
- Name => 'FocalLengthIn35mmFormat',
- Writable => 'integer',
- Groups => { 2 => 'Camera' },
- PrintConv => '"$val mm"',
- PrintConvInv => '$val=~s/\s*mm$//;$val',
- },
- SceneCaptureType => {
- Groups => { 2 => 'Camera' },
- Writable => 'integer',
- PrintConv => {
- 0 => 'Standard',
- 1 => 'Landscape',
- 2 => 'Portrait',
- 3 => 'Night',
- },
- },
- GainControl => {
- Groups => { 2 => 'Camera' },
- Writable => 'integer',
- PrintConv => {
- 0 => 'None',
- 1 => 'Low gain up',
- 2 => 'High gain up',
- 3 => 'Low gain down',
- 4 => 'High gain down',
- },
- },
- Contrast => {
- Groups => { 2 => 'Camera' },
- Writable => 'integer',
- PrintConv => {
- 0 => 'Normal',
- 1 => 'Low',
- 2 => 'High',
- },
- PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)',
- },
- Saturation => {
- Groups => { 2 => 'Camera' },
- Writable => 'integer',
- PrintConv => {
- 0 => 'Normal',
- 1 => 'Low',
- 2 => 'High',
- },
- PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)',
- },
- Sharpness => {
- Groups => { 2 => 'Camera' },
- Writable => 'integer',
- PrintConv => {
- 0 => 'Normal',
- 1 => 'Soft',
- 2 => 'Hard',
- },
- PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)',
- },
- DeviceSettingDescription => {
- Groups => { 2 => 'Camera' },
- Struct => {
- STRUCT_NAME => 'DeviceSettings',
- NAMESPACE => 'exif',
- Columns => { Writable => 'integer' },
- Rows => { Writable => 'integer' },
- Settings => { List => 'Seq' },
- },
- },
- SubjectDistanceRange => {
- Groups => { 2 => 'Camera' },
- Writable => 'integer',
- PrintConv => {
- 0 => 'Unknown',
- 1 => 'Macro',
- 2 => 'Close',
- 3 => 'Distant',
- },
- },
- ImageUniqueID => { },
- GPSVersionID => { Groups => { 2 => 'Location' } },
- GPSLatitude => { Groups => { 2 => 'Location' }, %latConv },
- GPSLongitude => { Groups => { 2 => 'Location' }, %longConv },
- GPSAltitudeRef => {
- Groups => { 2 => 'Location' },
- Writable => 'integer',
- PrintConv => {
- 0 => 'Above Sea Level',
- 1 => 'Below Sea Level',
- },
- },
- GPSAltitude => {
- Groups => { 2 => 'Location' },
- Writable => 'rational',
- RawConv => 'require Image::ExifTool::GPS; $val', # to load Composite tags and routines
- # extricate unsigned decimal number from string
- ValueConvInv => '$val=~/((?=\d|\.\d)\d*(?:\.\d*)?)/ ? $1 : undef',
- PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"',
- PrintConvInv => '$val=~s/\s*m$//;$val',
- },
- GPSTimeStamp => {
- Name => 'GPSDateTime',
- Description => 'GPS Date/Time',
- Groups => { 2 => 'Time' },
- Notes => q{
- a date/time tag called GPSTimeStamp by the XMP specification. This tag is
- renamed here to prevent direct copy from EXIF:GPSTimeStamp which is a
- time-only tag. Instead, the value of this tag should be taken from
- Composite:GPSDateTime when copying from EXIF
- },
- %dateTimeInfo,
- },
- GPSSatellites => { Groups => { 2 => 'Location' } },
- GPSStatus => {
- Groups => { 2 => 'Location' },
- PrintConv => {
- A => 'Measurement Active',
- V => 'Measurement Void',
- },
- },
- GPSMeasureMode => {
- Groups => { 2 => 'Location' },
- Writable => 'integer',
- PrintConv => {
- 2 => '2-Dimensional',
- 3 => '3-Dimensional',
- },
- },
- GPSDOP => { Groups => { 2 => 'Location' }, Writable => 'rational' },
- GPSSpeedRef => {
- Groups => { 2 => 'Location' },
- PrintConv => {
- K => 'km/h',
- M => 'mph',
- N => 'knots',
- },
- },
- GPSSpeed => { Groups => { 2 => 'Location' }, Writable => 'rational' },
- GPSTrackRef => {
- Groups => { 2 => 'Location' },
- PrintConv => {
- M => 'Magnetic North',
- T => 'True North',
- },
- },
- GPSTrack => { Groups => { 2 => 'Location' }, Writable => 'rational' },
- GPSImgDirectionRef => {
- Groups => { 2 => 'Location' },
- PrintConv => {
- M => 'Magnetic North',
- T => 'True North',
- },
- },
- GPSImgDirection => { Groups => { 2 => 'Location' }, Writable => 'rational' },
- GPSMapDatum => { Groups => { 2 => 'Location' } },
- GPSDestLatitude => { Groups => { 2 => 'Location' }, %latConv },
- GPSDestLongitude=> { Groups => { 2 => 'Location' }, %longConv },
- GPSDestBearingRef => {
- Groups => { 2 => 'Location' },
- PrintConv => {
- M => 'Magnetic North',
- T => 'True North',
- },
- },
- GPSDestBearing => { Groups => { 2 => 'Location' }, Writable => 'rational' },
- GPSDestDistanceRef => {
- Groups => { 2 => 'Location' },
- PrintConv => {
- K => 'Kilometers',
- M => 'Miles',
- N => 'Nautical Miles',
- },
- },
- GPSDestDistance => {
- Groups => { 2 => 'Location' },
- Writable => 'rational',
- },
- GPSProcessingMethod => { Groups => { 2 => 'Location' } },
- GPSAreaInformation => { Groups => { 2 => 'Location' } },
- GPSDifferential => {
- Groups => { 2 => 'Location' },
- Writable => 'integer',
- PrintConv => {
- 0 => 'No Correction',
- 1 => 'Differential Corrected',
- },
- },
- GPSHPositioningError => { #12
- Description => 'GPS Horizontal Positioning Error',
- Groups => { 2 => 'Location' },
- Writable => 'rational',
- PrintConv => '"$val m"',
- PrintConvInv => '$val=~s/\s*m$//; $val',
- },
- NativeDigest => { }, #PH
- );
- # Exif extended properties (exifEX, ref 12)
- %Image::ExifTool::XMP::exifEX = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-exifEX', 2 => 'Image' },
- NAMESPACE => 'exifEX',
- PRIORITY => 0, # not as reliable as actual EXIF tags
- NOTES => q{
- EXIF tags added by the EXIF 2.3 for XMP specification (see
- L<http://www.cipa.jp/std/documents/e/DC-010-2012_E.pdf>).
- },
- Gamma => { Writable => 'rational' },
- PhotographicSensitivity => { Writable => 'integer' },
- SensitivityType => {
- Writable => 'integer',
- PrintConv => {
- 0 => 'Unknown',
- 1 => 'Standard Output Sensitivity',
- 2 => 'Recommended Exposure Index',
- 3 => 'ISO Speed',
- 4 => 'Standard Output Sensitivity and Recommended Exposure Index',
- 5 => 'Standard Output Sensitivity and ISO Speed',
- 6 => 'Recommended Exposure Index and ISO Speed',
- 7 => 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed',
- },
- },
- StandardOutputSensitivity => { Writable => 'integer' },
- RecommendedExposureIndex => { Writable => 'integer' },
- ISOSpeed => { Writable => 'integer' },
- ISOSpeedLatitudeyyy => {
- Description => 'ISO Speed Latitude yyy',
- Writable => 'integer',
- },
- ISOSpeedLatitudezzz => {
- Description => 'ISO Speed Latitude zzz',
- Writable => 'integer',
- },
- CameraOwnerName => { Name => 'OwnerName' },
- BodySerialNumber => { Name => 'SerialNumber' },
- LensSpecification => {
- Name => 'LensInfo',
- Writable => 'rational',
- List => 'Seq',
- RawJoin => 1, # join list into a string before ValueConv
- ValueConv => \&ConvertRationalList,
- ValueConvInv => sub {
- my $val = shift;
- my @vals = split ' ', $val;
- return $val unless @vals == 4;
- foreach (@vals) {
- $_ eq 'inf' and $_ = '1/0', next;
- $_ eq 'undef' and $_ = '0/0', next;
- Image::ExifTool::IsFloat($_) or return $val;
- my @a = Image::ExifTool::Rationalize($_);
- $_ = join '/', @a;
- }
- return \@vals; # return list reference (List-type tag)
- },
- PrintConv => \&Image::ExifTool::Exif::PrintLensInfo,
- PrintConvInv => \&Image::ExifTool::Exif::ConvertLensInfo,
- Notes => q{
- called LensSpecification by the spec. Unfortunately the EXIF 2.3 for XMP
- specification defined this new tag instead of using the existing
- XMP-aux:LensInfo
- },
- },
- LensMake => { },
- LensModel => { },
- LensSerialNumber => { },
- InteroperabilityIndex => {
- Name => 'InteropIndex',
- Description => 'Interoperability Index',
- PrintConv => {
- R98 => 'R98 - DCF basic file (sRGB)',
- R03 => 'R03 - DCF option file (Adobe RGB)',
- THM => 'THM - DCF thumbnail file',
- },
- },
- );
- # Auxiliary namespace properties (aux) - not fully documented (ref PH)
- %Image::ExifTool::XMP::aux = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-aux', 2 => 'Camera' },
- NAMESPACE => 'aux',
- NOTES => q{
- Adobe-defined auxiliary EXIF tags. This namespace existed in the XMP
- specification until it was dropped in 2012, presumably due to the
- introduction of the EXIF 2.3 for XMP specification and the exifEX namespace
- at this time. For this reason, tags below with equivalents in the
- L<exifEX namespace|/XMP exifEX Tags> are avoided when writing.
- },
- Firmware => { }, #7
- FlashCompensation => { Writable => 'rational' }, #7
- ImageNumber => { }, #7
- LensInfo => { #7
- Notes => '4 rational values giving focal and aperture ranges',
- Avoid => 1,
- # convert to floating point values (or 'inf' or 'undef')
- ValueConv => \&ConvertRationalList,
- ValueConvInv => sub {
- my $val = shift;
- my @vals = split ' ', $val;
- return $val unless @vals == 4;
- foreach (@vals) {
- $_ eq 'inf' and $_ = '1/0', next;
- $_ eq 'undef' and $_ = '0/0', next;
- Image::ExifTool::IsFloat($_) or return $val;
- my @a = Image::ExifTool::Rationalize($_);
- $_ = join '/', @a;
- }
- return join ' ', @vals; # return string (string tag)
- },
- # convert to the form "12-20mm f/3.8-4.5" or "50mm f/1.4"
- PrintConv => \&Image::ExifTool::Exif::PrintLensInfo,
- PrintConvInv => \&Image::ExifTool::Exif::ConvertLensInfo,
- },
- Lens => { },
- OwnerName => { Avoid => 1 }, #7
- SerialNumber => { Avoid => 1 },
- LensSerialNumber=> { Avoid => 1 },
- LensID => {
- Priority => 0,
- # prevent this from getting set from a LensID that has been converted
- ValueConvInv => q{
- warn "Expected one or more integer values" if $val =~ /[^\d ]/;
- return $val;
- },
- },
- ApproximateFocusDistance => { Writable => 'rational' }, #PH (LR3)
- # the following new in LR6 (ref forum6497)
- IsMergedPanorama => { Writable => 'boolean' },
- IsMergedHDR => { Writable => 'boolean' },
- DistortionCorrectionAlreadyApplied => { Writable => 'boolean' },
- VignetteCorrectionAlreadyApplied => { Writable => 'boolean' },
- LateralChromaticAberrationCorrectionAlreadyApplied => { Writable => 'boolean' },
- );
- # IPTC Core namespace properties (Iptc4xmpCore) (ref 4)
- %Image::ExifTool::XMP::iptcCore = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-iptcCore', 2 => 'Author' },
- NAMESPACE => 'Iptc4xmpCore',
- TABLE_DESC => 'XMP IPTC Core',
- NOTES => q{
- IPTC Core namespace tags. The actual IPTC Core namespace prefix is
- "Iptc4xmpCore", which is the prefix recorded in the file, but ExifTool
- shortens this for the "XMP-iptcCore" family 1 group name. (see
- L<http://www.iptc.org/IPTC4XMP/>)
- },
- CountryCode => { Groups => { 2 => 'Location' } },
- CreatorContactInfo => {
- Struct => {
- STRUCT_NAME => 'ContactInfo',
- NAMESPACE => 'Iptc4xmpCore',
- CiAdrCity => { },
- CiAdrCtry => { },
- CiAdrExtadr => { },
- CiAdrPcode => { },
- CiAdrRegion => { },
- CiEmailWork => { },
- CiTelWork => { },
- CiUrlWork => { },
- },
- },
- CreatorContactInfoCiAdrCity => { Flat => 1, Name => 'CreatorCity' },
- CreatorContactInfoCiAdrCtry => { Flat => 1, Name => 'CreatorCountry' },
- CreatorContactInfoCiAdrExtadr => { Flat => 1, Name => 'CreatorAddress' },
- CreatorContactInfoCiAdrPcode => { Flat => 1, Name => 'CreatorPostalCode' },
- CreatorContactInfoCiAdrRegion => { Flat => 1, Name => 'CreatorRegion' },
- CreatorContactInfoCiEmailWork => { Flat => 1, Name => 'CreatorWorkEmail' },
- CreatorContactInfoCiTelWork => { Flat => 1, Name => 'CreatorWorkTelephone' },
- CreatorContactInfoCiUrlWork => { Flat => 1, Name => 'CreatorWorkURL' },
- IntellectualGenre => { Groups => { 2 => 'Other' } },
- Location => { Groups => { 2 => 'Location' } },
- Scene => { Groups => { 2 => 'Other' }, List => 'Bag' },
- SubjectCode => { Groups => { 2 => 'Other' }, List => 'Bag' },
- );
- # IPTC Extension namespace properties (Iptc4xmpExt) (ref 4)
- %Image::ExifTool::XMP::iptcExt = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-iptcExt', 2 => 'Author' },
- NAMESPACE => 'Iptc4xmpExt',
- TABLE_DESC => 'XMP IPTC Extension',
- NOTES => q{
- IPTC Extension namespace tags. The actual namespace prefix is
- "Iptc4xmpExt", but ExifTool shortens this for the "XMP-iptcExt" family 1
- group name. (see L<http://www.iptc.org/IPTC4XMP/>)
- },
- AboutCvTerm => {
- Struct => \%sCVTermDetails,
- List => 'Bag',
- },
- AboutCvTermCvId => { Flat => 1, Name => 'AboutCvTermCvId' },
- AboutCvTermCvTermId => { Flat => 1, Name => 'AboutCvTermId' },
- AboutCvTermCvTermName => { Flat => 1, Name => 'AboutCvTermName' },
- AboutCvTermCvTermRefinedAbout => { Flat => 1, Name => 'AboutCvTermRefinedAbout' },
- AddlModelInfo => { Name => 'AdditionalModelInformation' },
- ArtworkOrObject => {
- Struct => {
- STRUCT_NAME => 'ArtworkOrObjectDetails',
- NAMESPACE => 'Iptc4xmpExt',
- AOCopyrightNotice => { },
- AOCreator => { List => 'Seq' },
- AODateCreated=> { Groups => { 2 => 'Time' }, %dateTimeInfo },
- AOSource => { },
- AOSourceInvNo=> { },
- AOTitle => { Writable => 'lang-alt' },
- AOCurrentCopyrightOwnerName => { },
- AOCurrentCopyrightOwnerId => { },
- AOCurrentLicensorName => { },
- AOCurrentLicensorId => { },
- AOCreatorId => { List => 'Seq' },
- AOCircaDateCreated => { },
- AOStylePeriod => { List => 'Bag' },
- AOSourceInvURL => { },
- AOContentDescription => { Writable => 'lang-alt' },
- AOContributionDescription => { Writable => 'lang-alt' },
- AOPhysicalDescription => { Writable => 'lang-alt' },
- },
- List => 'Bag',
- },
- ArtworkOrObjectAOCopyrightNotice => { Flat => 1, Name => 'ArtworkCopyrightNotice' },
- ArtworkOrObjectAOCreator => { Flat => 1, Name => 'ArtworkCreator' },
- ArtworkOrObjectAODateCreated => { Flat => 1, Name => 'ArtworkDateCreated' },
- ArtworkOrObjectAOSource => { Flat => 1, Name => 'ArtworkSource' },
- ArtworkOrObjectAOSourceInvNo => { Flat => 1, Name => 'ArtworkSourceInventoryNo' },
- ArtworkOrObjectAOTitle => { Flat => 1, Name => 'ArtworkTitle' },
- ArtworkOrObjectAOCurrentCopyrightOwnerName => { Flat => 1, Name => 'ArtworkCopyrightOwnerName' },
- ArtworkOrObjectAOCurrentCopyrightOwnerId => { Flat => 1, Name => 'ArtworkCopyrightOwnerID' },
- ArtworkOrObjectAOCurrentLicensorName => { Flat => 1, Name => 'ArtworkLicensorName' },
- ArtworkOrObjectAOCurrentLicensorId => { Flat => 1, Name => 'ArtworkLicensorID' },
- ArtworkOrObjectAOCreatorId => { Flat => 1, Name => 'ArtworkCreatorID' },
- ArtworkOrObjectAOCircaDateCreated => { Flat => 1, Name => 'ArtworkCircaDateCreated' },
- ArtworkOrObjectAOStylePeriod => { Flat => 1, Name => 'ArtworkStylePeriod' },
- ArtworkOrObjectAOSourceInvURL => { Flat => 1, Name => 'ArtworkSourceInvURL' },
- ArtworkOrObjectAOContentDescription => { Flat => 1, Name => 'ArtworkSContentDescription' },
- ArtworkOrObjectAOContributionDescription => { Flat => 1, Name => 'ArtworkContributionDescription' },
- ArtworkOrObjectAOPhysicalDescription => { Flat => 1, Name => 'ArtworkPhysicalDescription' },
- CVterm => {
- Name => 'ControlledVocabularyTerm',
- List => 'Bag',
- Notes => 'deprecated by version 1.2',
- },
- DigImageGUID => { Name => 'DigitalImageGUID' },
- DigitalSourcefileType => {
- Name => 'DigitalSourceFileType',
- Notes => 'now deprecated -- replaced by DigitalSourceType',
- },
- DigitalSourceType => { Name => 'DigitalSourceType' },
- EmbdEncRightsExpr => {
- Struct => {
- STRUCT_NAME => 'EEREDetails',
- NAMESPACE => 'Iptc4xmpExt',
- EncRightsExpr => { },
- RightsExprEncType => { },
- RightsExprLangId => { },
- },
- List => 'Bag',
- },
- EmbdEncRightsExprEncRightsExpr => { Flat => 1, Name => 'EmbeddedEncodedRightsExpr' },
- EmbdEncRightsExprRightsExprEncType => { Flat => 1, Name => 'EmbeddedEncodedRightsExprType' },
- EmbdEncRightsExprRightsExprLangId => { Flat => 1, Name => 'EmbeddedEncodedRightsExprLangID' },
- Event => { Writable => 'lang-alt' },
- IptcLastEdited => {
- Name => 'IPTCLastEdited',
- Groups => { 2 => 'Time' },
- %dateTimeInfo,
- },
- LinkedEncRightsExpr => {
- Struct => {
- STRUCT_NAME => 'LEREDetails',
- NAMESPACE => 'Iptc4xmpExt',
- LinkedRightsExpr => { },
- RightsExprEncType => { },
- RightsExprLangId => { },
- },
- List => 'Bag',
- },
- LinkedEncRightsExprLinkedRightsExpr => { Flat => 1, Name => 'LinkedEncodedRightsExpr' },
- LinkedEncRightsExprRightsExprEncType => { Flat => 1, Name => 'LinkedEncodedRightsExprType' },
- LinkedEncRightsExprRightsExprLangId => { Flat => 1, Name => 'LinkedEncodedRightsExprLangID' },
- LocationCreated => {
- Struct => \%sLocationDetails,
- Groups => { 2 => 'Location' },
- List => 'Bag',
- },
- LocationShown => {
- Struct => \%sLocationDetails,
- Groups => { 2 => 'Location' },
- List => 'Bag',
- },
- MaxAvailHeight => { Writable => 'integer' },
- MaxAvailWidth => { Writable => 'integer' },
- ModelAge => { List => 'Bag', Writable => 'integer' },
- OrganisationInImageCode => { List => 'Bag' },
- OrganisationInImageName => { List => 'Bag' },
- PersonInImage => { List => 'Bag' },
- PersonInImageWDetails => {
- Struct => {
- STRUCT_NAME => 'PersonDetails',
- NAMESPACE => 'Iptc4xmpExt',
- PersonId => { List => 'Bag' },
- PersonName => { Writable => 'lang-alt' },
- PersonCharacteristic => {
- Struct => \%sCVTermDetails,
- List => 'Bag',
- },
- PersonDescription => { Writable => 'lang-alt' },
- },
- List => 'Bag',
- },
- PersonInImageWDetailsPersonId => { Flat => 1, Name => 'PersonInImageId' },
- PersonInImageWDetailsPersonName => { Flat => 1, Name => 'PersonInImageName' },
- PersonInImageWDetailsPersonCharacteristic => { Flat => 1, Name => 'PersonInImageCharacteristic' },
- PersonInImageWDetailsPersonCharacteristicCvId => { Flat => 1, Name => 'PersonInImageCvTermCvId' },
- PersonInImageWDetailsPersonCharacteristicCvTermId => { Flat => 1, Name => 'PersonInImageCvTermId' },
- PersonInImageWDetailsPersonCharacteristicCvTermName => { Flat => 1, Name => 'PersonInImageCvTermName' },
- PersonInImageWDetailsPersonCharacteristicCvTermRefinedAbout => { Flat => 1, Name => 'PersonInImageCvTermRefinedAbout' },
- PersonInImageWDetailsPersonDescription => { Flat => 1, Name => 'PersonInImageDescription' },
- ProductInImage => {
- Struct => {
- STRUCT_NAME => 'ProductDetails',
- NAMESPACE => 'Iptc4xmpExt',
- ProductName => { Writable => 'lang-alt' },
- ProductGTIN => { },
- ProductDescription => { Writable => 'lang-alt' },
- },
- List => 'Bag',
- },
- ProductInImageProductName => { Flat => 1, Name => 'ProductInImageName' },
- ProductInImageProductGTIN => { Flat => 1, Name => 'ProductInImageGTIN' },
- ProductInImageProductDescription => { Flat => 1, Name => 'ProductInImageDescription' },
- RegistryId => {
- Name => 'RegistryID',
- Struct => {
- STRUCT_NAME => 'RegistryEntryDetails',
- NAMESPACE => 'Iptc4xmpExt',
- RegItemId => { },
- RegOrgId => { },
- },
- List => 'Bag',
- },
- RegistryIdRegItemId => { Flat => 1, Name => 'RegistryItemID' },
- RegistryIdRegOrgId => { Flat => 1, Name => 'RegistryOrganisationID' },
- );
- # Adobe Lightroom namespace properties (lr) (ref PH)
- %Image::ExifTool::XMP::Lightroom = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-lr', 2 => 'Image' },
- NAMESPACE => 'lr',
- TABLE_DESC => 'XMP Adobe Lightroom',
- NOTES => 'Adobe Lightroom "lr" namespace tags.',
- privateRTKInfo => { },
- hierarchicalSubject => { List => 'Bag' },
- );
- # Adobe Album namespace properties (album) (ref PH)
- %Image::ExifTool::XMP::Album = (
- %xmpTableDefaults,
- GROUPS => { 1 => 'XMP-album', 2 => 'Image' },
- NAMESPACE => 'album',
- TABLE_DESC => 'XMP Adobe Album',
- NOTES => 'Adobe Album namespace tags.',
- Notes => { },
- );
- # table to add tags in other namespaces
- %Image::ExifTool::XMP::other = (
- GROUPS => { 2 => 'Unknown' },
- LANG_INFO => \&GetLangInfo,
- );
- # Composite XMP tags
- %Image::ExifTool::XMP::Composite = (
- # get latitude/logitude reference from XMP lat/long tags
- # (used to set EXIF GPS position from XMP tags)
- GPSLatitudeRef => {
- Require => 'XMP:GPSLatitude',
- ValueConv => q{
- IsFloat($val[0]) and return $val[0] < 0 ? "S" : "N";
- $val[0] =~ /^.*([NS])/;
- return $1;
- },
- PrintConv => {
- N => 'North',
- S => 'South',
- },
- },
- GPSLongitudeRef => {
- Require => 'XMP:GPSLongitude',
- ValueConv => q{
- IsFloat($val[0]) and return $val[0] < 0 ? "W" : "E";
- $val[0] =~ /^.*([EW])/;
- return $1;
- },
- PrintConv => {
- E => 'East',
- W => 'West',
- },
- },
- LensID => {
- Notes => 'attempt to convert numerical XMP-aux:LensID stored by Adobe applications',
- Require => {
- 0 => 'XMP-aux:LensID',
- 1 => 'Make',
- },
- Desire => {
- 2 => 'LensInfo',
- 3 => 'FocalLength',
- 4 => 'LensModel',
- 5 => 'MaxApertureValue',
- },
- Inhibit => {
- 6 => 'Composite:LensID', # don't override existing Composite:LensID
- },
- ValueConv => '$val',
- PrintConv => 'Image::ExifTool::XMP::PrintLensID($self, @val)',
- },
- Flash => {
- Notes => 'facilitates copying camera flash information between XMP and EXIF',
- Desire => {
- 0 => 'XMP:FlashFired',
- 1 => 'XMP:FlashReturn',
- 2 => 'XMP:FlashMode',
- 3 => 'XMP:FlashFunction',
- 4 => 'XMP:FlashRedEyeMode',
- 5 => 'XMP:Flash', # handle structured flash information too
- },
- Writable => 1,
- PrintHex => 1,
- SeparateTable => 'EXIF Flash',
- ValueConv => q{
- if (ref $val[5] eq 'HASH') {
- # copy structure fields into value array
- my $i = 0;
- $val[$i++] = $val[5]{$_} foreach qw(Fired Return Mode Function RedEyeMode);
- }
- return (($val[0] and lc($val[0]) eq 'true') ? 0x01 : 0) |
- (($val[1] || 0) << 1) |
- (($val[2] || 0) << 3) |
- (($val[3] and lc($val[3]) eq 'true') ? 0x20 : 0) |
- (($val[4] and lc($val[4]) eq 'true') ? 0x40 : 0);
- },
- PrintConv => \%Image::ExifTool::Exif::flash,
- WriteAlso => {
- 'XMP:FlashFired' => '$val & 0x01 ? "True" : "False"',
- 'XMP:FlashReturn' => '($val & 0x06) >> 1',
- 'XMP:FlashMode' => '($val & 0x18) >> 3',
- 'XMP:FlashFunction' => '$val & 0x20 ? "True" : "False"',
- 'XMP:FlashRedEyeMode' => '$val & 0x40 ? "True" : "False"',
- },
- },
- );
- # add our composite tags
- Image::ExifTool::AddCompositeTags('Image::ExifTool::XMP');
- #------------------------------------------------------------------------------
- # AutoLoad our writer routines when necessary
- #
- sub AUTOLOAD
- {
- return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_);
- }
- #------------------------------------------------------------------------------
- # Escape necessary XML characters in UTF-8 string
- # Inputs: 0) string to be escaped
- # Returns: escaped string
- my %charName = ('"'=>'quot', '&'=>'amp', "'"=>'#39', '<'=>'lt', '>'=>'gt');
- sub EscapeXML($)
- {
- my $str = shift;
- $str =~ s/([&><'"])/&$charName{$1};/sg; # escape necessary XML characters
- return $str;
- }
- #------------------------------------------------------------------------------
- # Unescape XML character references (entities and numerical)
- # Inputs: 0) string to be unescaped
- # 1) optional hash reference to convert entity names to numbers
- # Returns: unescaped string
- my %charNum = ('quot'=>34, 'amp'=>38, 'apos'=>39, 'lt'=>60, 'gt'=>62);
- sub UnescapeXML($;$)
- {
- my ($str, $conv) = @_;
- $conv = \%charNum unless $conv;
- $str =~ s/&(#?\w+);/UnescapeChar($1,$conv)/sge;
- return $str;
- }
- #------------------------------------------------------------------------------
- # Escape string for XML, ensuring valid XML and UTF-8
- # Inputs: 0) string
- # Returns: escaped string
- sub FullEscapeXML($)
- {
- my $str = shift;
- $str =~ s/([&><'"])/&$charName{$1};/sg; # escape necessary XML characters
- $str =~ s/\\/\/sg; # escape backslashes too
- # then use C-escape sequences for invalid characters
- if ($str =~ /[\0-\x1f]/ or IsUTF8(\$str) < 0) {
- $str =~ s/([\0-\x1f\x80-\xff])/sprintf("\\x%.2x",ord $1)/sge;
- }
- return $str;
- }
- #------------------------------------------------------------------------------
- # Unescape XML/C escaped string
- # Inputs: 0) string
- # Returns: unescaped string
- sub FullUnescapeXML($)
- {
- my $str = shift;
- # unescape C escape sequences first
- $str =~ s/\\x([\da-f]{2})/chr(hex($1))/sge;
- my $conv = \%charNum;
- $str =~ s/&(#?\w+);/UnescapeChar($1,$conv)/sge;
- return $str;
- }
- #------------------------------------------------------------------------------
- # Convert XML character reference to UTF-8
- # Inputs: 0) XML character reference stripped of the '&' and ';' (eg. 'quot', '#34', '#x22')
- # 1) hash reference for looking up character numbers by name
- # Returns: UTF-8 equivalent (or original character on conversion error)
- sub UnescapeChar($$)
- {
- my ($ch, $conv) = @_;
- my $val = $$conv{$ch};
- unless (defined $val) {
- if ($ch =~ /^#x([0-9a-fA-F]+)$/) {
- $val = hex($1);
- } elsif ($ch =~ /^#(\d+)$/) {
- $val = $1;
- } else {
- return "&$ch;"; # should issue a warning here? [no]
- }
- }
- return chr($val) if $val < 0x80; # simple ASCII
- return pack('C0U', $val) if $] >= 5.006001;
- return Image::ExifTool::PackUTF8($val);
- }
- #------------------------------------------------------------------------------
- # Does a string contain valid UTF-8 characters?
- # Inputs: 0) string reference
- # Returns: 0=regular ASCII, -1=invalid UTF-8, 1=valid UTF-8 with maximum 16-bit
- # wide characters, 2=valid UTF-8 requiring 32-bit wide characters
- # Notes: Changes current string position
- # (see http://www.fileformat.info/info/unicode/utf8.htm for help understanding this)
- sub IsUTF8($)
- {
- my $strPt = shift;
- pos($$strPt) = 0; # start at beginning of string
- return 0 unless $$strPt =~ /([\x80-\xff])/g;
- my $rtnVal = 1;
- for (;;) {
- my $ch = ord($1);
- # minimum lead byte for 2-byte sequence is 0xc2 (overlong sequences
- # not allowed), 0xf8-0xfd are restricted by RFC 3629 (no 5 or 6 byte
- # sequences), and 0xfe and 0xff are not valid in UTF-8 strings
- return -1 if $ch < 0xc2 or $ch >= 0xf8;
- # determine number of bytes remaining in sequence
- my $n;
- if ($ch < 0xe0) {
- $n = 1;
- } elsif ($ch < 0xf0) {
- $n = 2;
- } else {
- $n = 3;
- # character code is greater than 0xffff if more than 2 extra bytes
- # were required in the UTF-8 character
- $rtnVal = 2;
- }
- return -1 unless $$strPt =~ /\G[\x80-\xbf]{$n}/g;
- last unless $$strPt =~ /([\x80-\xff])/g;
- }
- return $rtnVal;
- }
- #------------------------------------------------------------------------------
- # Fix malformed UTF8 (by replacing bad bytes with '?')
- # Inputs: 0) string reference
- # Returns: true if string was fixed, and updates string
- sub FixUTF8($)
- {
- my $strPt = shift;
- my $fixed;
- pos($$strPt) = 0; # start at beginning of string
- for (;;) {
- last unless $$strPt =~ /([\x80-\xff])/g;
- my $ch = ord($1);
- my $pos = pos($$strPt);
- # (see comments in IsUTF8() above)
- if ($ch >= 0xc2 and $ch < 0xf8) {
- my $n = $ch < 0xe0 ? 1 : ($ch < 0xf0 ? 2 : 3);
- next if $$strPt =~ /\G[\x80-\xbf]{$n}/g;
- }
- # replace bad character with '?'
- substr($$strPt, $pos-1, 1) = '?';
- pos($$strPt) = $fixed = $pos;
- }
- return $fixed;
- }
- #------------------------------------------------------------------------------
- # Utility routine to decode a base64 string
- # Inputs: 0) base64 string
- # Returns: reference to decoded data
- sub DecodeBase64($)
- {
- local($^W) = 0; # unpack('u',...) gives bogus warning in 5.00[123]
- my $str = shift;
- # truncate at first unrecognized character (base 64 data
- # may only contain A-Z, a-z, 0-9, +, /, =, or white space)
- $str =~ s/[^A-Za-z0-9+\/= \t\n\r\f].*//s;
- # translate to uucoded and remove padding and white space
- $str =~ tr/A-Za-z0-9+\/= \t\n\r\f/ -_/d;
- # convert the data to binary in chunks
- my $chunkSize = 60;
- my $uuLen = pack('c', 32 + $chunkSize * 3 / 4); # calculate length byte
- my $dat = '';
- my ($i, $substr);
- # loop through the whole chunks
- my $len = length($str) - $chunkSize;
- for ($i=0; $i<=$len; $i+=$chunkSize) {
- $substr = substr($str, $i, $chunkSize); # get a chunk of the data
- $dat .= unpack('u', $uuLen . $substr); # decode it
- }
- $len += $chunkSize;
- # handle last partial chunk if necessary
- if ($i < $len) {
- $uuLen = pack('c', 32 + ($len-$i) * 3 / 4); # recalculate length
- $substr = substr($str, $i, $len-$i); # get the last partial chunk
- $dat .= unpack('u', $uuLen . $substr); # decode it
- }
- return \$dat;
- }
- #------------------------------------------------------------------------------
- # Generate a tag ID for this XMP tag
- # Inputs: 0) tag property name list ref, 1) array ref for receiving structure property list
- # 2) array for receiving namespace list
- # Returns: tagID and outtermost interesting namespace (or '' if no namespace)
- sub GetXMPTagID($;$$)
- {
- my ($props, $structProps, $nsList) = @_;
- my ($tag, $prop, $namespace);
- foreach $prop (@$props) {
- # split name into namespace and property name
- # (Note: namespace can be '' for property qualifiers)
- my ($ns, $nm) = ($prop =~ /(.*?):(.*)/) ? ($1, $2) : ('', $prop);
- if ($ignoreNamespace{$ns} or $ignoreProp{$prop}) {
- # special case: don't ignore rdf numbered items
- unless ($prop =~ /^rdf:(_\d+)$/) {
- # save list index if necessary for structures
- if ($structProps and @$structProps and $prop =~ /^rdf:li (\d+)$/) {
- push @{$$structProps[-1]}, $1;
- }
- next;
- }
- $tag .= $1 if defined $tag;
- } else {
- $nm =~ s/ .*//; # remove nodeID if it exists
- # all uppercase is ugly, so convert it
- if ($nm !~ /[a-z]/) {
- my $xlat = $stdXlatNS{$ns} || $ns;
- my $info = $Image::ExifTool::XMP::Main{$xlat};
- my $table;
- if (ref $info eq 'HASH' and $$info{SubDirectory}) {
- $table = GetTagTable($$info{SubDirectory}{TagTable});
- }
- unless ($table and $$table{$nm}) {
- $nm = lc($nm);
- $nm =~ s/_([a-z])/\u$1/g;
- }
- }
- if (defined $tag) {
- $tag .= ucfirst($nm); # add to tag name
- } else {
- $tag = $nm;
- }
- # save structure information if necessary
- if ($structProps) {
- push @$structProps, [ $nm ];
- push @$nsList, $ns if $nsList;
- }
- }
- # save namespace of first property to contribute to tag name
- $namespace = $ns unless $namespace;
- }
- if (wantarray) {
- return ($tag, $namespace || '');
- } else {
- return $tag;
- }
- }
- #------------------------------------------------------------------------------
- # Register namespace for specified user-defined table
- # Inputs: 0) tag/structure table ref
- # Returns: namespace prefix
- sub RegisterNamespace($)
- {
- my $table = shift;
- return $$table{NAMESPACE} unless ref $$table{NAMESPACE};
- my $nsRef = $$table{NAMESPACE};
- # recognize as either a list or hash
- my $ns;
- if (ref $nsRef eq 'ARRAY') {
- $ns = $$nsRef[0];
- $nsURI{$ns} = $$nsRef[1];
- $uri2ns{$$nsRef[1]} = $ns;
- } else { # must be a hash
- my @ns = sort keys %$nsRef; # allow multiple namespace definitions
- while (@ns) {
- $ns = pop @ns;
- if ($nsURI{$ns} and $nsURI{$ns} ne $$nsRef{$ns}) {
- warn "User-defined namespace prefix '$ns' conflicts with existing namespace\n";
- }
- $nsURI{$ns} = $$nsRef{$ns};
- $uri2ns{$$nsRef{$ns}} = $ns;
- }
- }
- return $$table{NAMESPACE} = $ns;
- }
- #------------------------------------------------------------------------------
- # Generate flattened tags and add to table
- # Inputs: 0) tag table ref, 1) tag ID for Struct tag (if not defined, whole table is done),
- # 2) flag to not expand sub-structures
- # Returns: number of tags added (not counting those just initialized)
- # Notes: Must have verified that $$tagTablePtr{$tagID}{Struct} exists before calling this routine
- # - makes sure that the tagInfo Struct is a HASH reference
- sub AddFlattenedTags($;$$)
- {
- local $_;
- my ($tagTablePtr, $tagID, $noSubStruct) = @_;
- my $count = 0;
- my @tagIDs;
- if (defined $tagID) {
- push @tagIDs, $tagID;
- } else {
- foreach $tagID (TagTableKeys($tagTablePtr)) {
- my $tagInfo = $$tagTablePtr{$tagID};
- next unless ref $tagInfo eq 'HASH' and $$tagInfo{Struct};
- push @tagIDs, $tagID;
- }
- }
- # loop through specified tags
- foreach $tagID (@tagIDs) {
- my $tagInfo = $$tagTablePtr{$tagID};
- $$tagInfo{Flattened} and next; # only generate flattened tags once
- $$tagInfo{Flattened} = 1;
- my $strTable = $$tagInfo{Struct};
- unless (ref $strTable) { # (allow a structure name for backward compatibility only)
- my $strName = $strTable;
- $strTable = $Image::ExifTool::UserDefined::xmpStruct{$strTable} or next;
- $$strTable{STRUCT_NAME} or $$strTable{STRUCT_NAME} = "XMP $strName";
- $$tagInfo{Struct} = $strTable; # replace old-style name with HASH ref
- delete $$tagInfo{SubDirectory}; # deprecated use of SubDirectory in Struct tags
- }
- # do not add flattened tags to variable-namespace structures
- next if exists $$strTable{NAMESPACE} and not defined $$strTable{NAMESPACE};
- # get prefix for flattened tag names
- my $flat = (defined $$tagInfo{FlatName} ? $$tagInfo{FlatName} : $$tagInfo{Name});
- # get family 2 group name for this structure tag
- my ($tagG2, $field);
- $tagG2 = $$tagInfo{Groups}{2} if $$tagInfo{Groups};
- $tagG2 or $tagG2 = $$tagTablePtr{GROUPS}{2};
- foreach $field (keys %$strTable) {
- next if $specialStruct{$field};
- my $fieldInfo = $$strTable{$field};
- next if $$fieldInfo{LangCode}; # don't flatten lang-alt tags
- next if $$fieldInfo{Struct} and $noSubStruct; # don't expand sub-structures if specified
- # build a tag ID for the corresponding flattened tag
- my $fieldName = ucfirst($field);
- my $flatField = $$fieldInfo{FlatName} || $fieldName;
- my $flatID = $tagID . $fieldName;
- my $flatInfo = $$tagTablePtr{$flatID};
- if ($flatInfo) {
- ref $flatInfo eq 'HASH' or warn("$flatInfo is not a HASH!\n"), next; # (to be safe)
- # pre-defined flattened tags should have Flat flag set
- if (not defined $$flatInfo{Flat} and $Image::ExifTool::debug) {
- warn "Missing Flat flag for $$flatInfo{Name}\n";
- }
- $$flatInfo{Flat} = 0;
- # copy all missing entries from field information
- foreach (keys %$fieldInfo) {
- # must not copy PropertyPath (but can't delete it afterwards
- # because the flat tag may already have this set)
- next if $_ eq 'PropertyPath' or defined $$flatInfo{$_};
- # copy the property (making a copy of the Groups hash)
- $$flatInfo{$_} = $_ eq 'Groups' ? { %{$$fieldInfo{$_}} } : $$fieldInfo{$_};
- }
- # (NOTE: Can NOT delete Groups because we need them if GotGroups was done)
- # re-generate List flag unless it is set to 0
- delete $$flatInfo{List} if $$flatInfo{List};
- } else {
- # generate new flattened tag information based on structure field
- my $flatName = $flat . $flatField;
- $flatInfo = { %$fieldInfo, Name => $flatName, Flat => 0 };
- # make a copy of the Groups hash if necessary
- $$flatInfo{Groups} = { %{$$fieldInfo{Groups}} } if $$fieldInfo{Groups};
- # add new flattened tag to table
- AddTagToTable($tagTablePtr, $flatID, $flatInfo);
- ++$count;
- }
- # propagate List flag (unless set to 0 in pre-defined flattened tag)
- unless (defined $$flatInfo{List}) {
- $$flatInfo{List} = $$fieldInfo{List} || 1 if $$fieldInfo{List} or $$tagInfo{List};
- }
- # set group 2 name from the first existing family 2 group in the:
- # 1) structure field Groups, 2) structure table GROUPS, 3) structure tag Groups
- if ($$fieldInfo{Groups} and $$fieldInfo{Groups}{2}) {
- $$flatInfo{Groups}{2} = $$fieldInfo{Groups}{2};
- } elsif ($$strTable{GROUPS} and $$strTable{GROUPS}{2}) {
- $$flatInfo{Groups}{2} = $$strTable{GROUPS}{2};
- } else {
- $$flatInfo{Groups}{2} = $tagG2;
- }
- # save reference to top-level structure
- $$flatInfo{RootTagInfo} = $$tagInfo{RootTagInfo} || $tagInfo;
- # recursively generate flattened tags for sub-structures
- next unless $$flatInfo{Struct};
- length($flatID) > 250 and warn("Possible deep recursion for tag $flatID\n"), last;
- # reset flattened tag just in case we flattened hierarchy in the wrong order
- # because we must start from the outtermost structure to get the List flags right
- # (this should only happen when building tag tables)
- delete $$flatInfo{Flattened};
- $count += AddFlattenedTags($tagTablePtr, $flatID, $$flatInfo{NoSubStruct});
- }
- }
- return $count;
- }
- #------------------------------------------------------------------------------
- # Get localized version of tagInfo hash
- # Inputs: 0) tagInfo hash ref, 1) language code (eg. "x-default")
- # Returns: new tagInfo hash ref, or undef if invalid
- sub GetLangInfo($$)
- {
- my ($tagInfo, $langCode) = @_;
- # only allow alternate language tags in lang-alt lists
- return undef unless $$tagInfo{Writable} and $$tagInfo{Writable} eq 'lang-alt';
- $langCode =~ tr/_/-/; # RFC 3066 specifies '-' as a separator
- my $langInfo = Image::ExifTool::GetLangInfo($tagInfo, $langCode);
- return $langInfo;
- }
- #------------------------------------------------------------------------------
- # Get standard case for language code
- # Inputs: 0) Language code
- # Returns: Language code in standard case
- sub StandardLangCase($)
- {
- my $lang = shift;
- # make 2nd subtag uppercase only if it is 2 letters
- return lc($1) . uc($2) . lc($3) if $lang =~ /^([a-z]{2,3}|[xi])(-[a-z]{2})\b(.*)/i;
- return lc($lang);
- }
- #------------------------------------------------------------------------------
- # Scan for XMP in a file
- # Inputs: 0) ExifTool object ref, 1) RAF reference
- # Returns: 1 if xmp was found, 0 otherwise
- # Notes: Currently only recognizes UTF8-encoded XMP
- sub ScanForXMP($$)
- {
- my ($et, $raf) = @_;
- my ($buff, $xmp);
- my $lastBuff = '';
- $et->VPrint(0,"Scanning for XMP\n");
- for (;;) {
- defined $buff or $raf->Read($buff, 65536) or return 0;
- unless (defined $xmp) {
- $lastBuff .= $buff;
- unless ($lastBuff =~ /(<\?xpacket begin=)/g) {
- # must keep last 15 bytes to match 16-byte "xpacket begin" string
- $lastBuff = length($buff) <= 15 ? $buff : substr($buff, -15);
- undef $buff;
- next;
- }
- $xmp = $1;
- $buff = substr($lastBuff, pos($lastBuff));
- }
- my $pos = length($xmp) - 18; # (18 = length("<?xpacket end...") - 1)
- $xmp .= $buff; # add new data to our XMP
- pos($xmp) = $pos if $pos > 0; # set start for "xpacket end" scan
- if ($xmp =~ /<\?xpacket end=['"][wr]['"]\?>/g) {
- $buff = substr($xmp, pos($xmp)); # save data after end of XMP
- $xmp = substr($xmp, 0, pos($xmp)); # isolate XMP
- # check XMP for validity (not valid if it contains null bytes)
- $pos = rindex($xmp, "\0") + 1 or last;
- $lastBuff = substr($xmp, $pos); # re-parse beginning after last null byte
- undef $xmp;
- } else {
- undef $buff;
- }
- }
- unless ($$et{VALUE}{FileType}) {
- $$et{FILE_TYPE} = $$et{FILE_EXT};
- $et->SetFileType('<unknown file containing XMP>', undef, '');
- }
- my %dirInfo = (
- DataPt => \$xmp,
- DirLen => length $xmp,
- DataLen => length $xmp,
- );
- ProcessXMP($et, \%dirInfo);
- return 1;
- }
- #------------------------------------------------------------------------------
- # Print conversion for XMP-aux:LensID
- # Inputs: 0) ExifTool ref, 1) LensID, 2) Make, 3) LensInfo, 4) FocalLength,
- # 5) LensModel, 6) MaxApertureValue
- # (yes, this is ugly -- blame Adobe)
- sub PrintLensID(@)
- {
- local $_;
- my ($et, $id, $make, $info, $focalLength, $lensModel, $maxAv) = @_;
- my ($mk, $printConv);
- my %alt = ( Pentax => 'Ricoh' ); # Pentax changed its name to Ricoh
- # missing: Olympus (no XMP:LensID written by Adobe)
- foreach $mk (qw(Canon Nikon Pentax Sony Sigma Samsung Leica)) {
- next unless $make =~ /$mk/i or ($alt{$mk} and $make =~ /$alt{$mk}/i);
- # get name of module containing the lens lookup (default "Make.pm")
- my $mod = { Sigma => 'SigmaRaw', Leica => 'Panasonic' }->{$mk} || $mk;
- require "Image/ExifTool/$mod.pm";
- # get the name of the lens name lookup (default "makeLensTypes")
- # (canonLensTypes, pentaxLensTypes, nikonLensIDs, etc)
- my $convName = "Image::ExifTool::${mod}::" .
- ({ Nikon => 'nikonLensIDs' }->{$mk} || lc($mk) . 'LensTypes');
- no strict 'refs';
- %$convName or last;
- my $printConv = \%$convName;
- use strict 'refs';
- # sf = short focal
- # lf = long focal
- # sa = max aperture at short focal
- # la = max aperture at long focal
- my ($sf, $lf, $sa, $la);
- if ($info) {
- my @a = split ' ', $info;
- $_ eq 'undef' and $_ = undef foreach @a;
- ($sf, $lf, $sa, $la) = @a;
- # for Sony and ambiguous LensID, $info data may be incorrect:
- # use only if it agrees with $focalLength and $maxAv (ref JR)
- if ($mk eq 'Sony' and
- (($focalLength and (($sf and $focalLength < $sf - 0.5) or
- ($lf and $focalLength > $lf + 0.5))) or
- ($maxAv and (($sa and $maxAv < $sa - 0.15) or
- ($la and $maxAv > $la + 0.15)))))
- {
- undef $sf;
- undef $lf;
- undef $sa;
- undef $la;
- } elsif ($maxAv) {
- # (using the short-focal-length max aperture in place of MaxAperture
- # is a bad approximation, so don't do this if MaxApertureValue exists)
- undef $sa;
- }
- }
- if ($mk eq 'Pentax' and $id =~ /^\d+$/) {
- # for Pentax, CS4 stores an int16u, but we use 2 x int8u
- $id = join(' ', unpack('C*', pack('n', $id)));
- }
- my $str = $$printConv{$id} || "Unknown ($id)";
- # Nikon is a special case because Adobe doesn't store the full LensID
- if ($mk eq 'Nikon') {
- my $hex = sprintf("%.2X", $id);
- my (%newConv, %used);
- my $i = 0;
- foreach (grep /^$hex /, keys %$printConv) {
- my $lens = $$printConv{$_};
- next if $used{$lens}; # avoid duplicates
- $used{$lens} = 1;
- $newConv{$i ? "$id.$i" : $id} = $lens;
- ++$i;
- }
- $printConv = \%newConv;
- }
- return Image::ExifTool::Exif::PrintLensID($et, $str, $printConv,
- undef, $id, $focalLength, $sa, $maxAv, $sf, $lf, $lensModel);
- }
- return "Unknown ($id)";
- }
- #------------------------------------------------------------------------------
- # Convert XMP date/time to EXIF format
- # Inputs: 0) XMP date/time string, 1) set if we aren't sure this is a date
- # Returns: EXIF date/time
- sub ConvertXMPDate($;$)
- {
- my ($val, $unsure) = @_;
- if ($val =~ /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}:\d{2})(:\d{2})?\s*(\S*)$/) {
- my $s = $5 || ''; # seconds may be missing
- $val = "$1:$2:$3 $4$s$6"; # convert back to EXIF time format
- } elsif (not $unsure and $val =~ /^(\d{4})(-\d{2}){0,2}/) {
- $val =~ tr/-/:/;
- }
- return $val;
- }
- #------------------------------------------------------------------------------
- # Convert rational string value
- # Inputs: 0) string (converted to number, 'inf' or 'undef' on return if rational)
- # Returns: true if value was converted
- sub ConvertRational($)
- {
- my $val = $_[0];
- $val =~ m{^(-?\d+)/(-?\d+)$} or return undef;
- if ($2 != 0) {
- $_[0] = $1 / $2; # calculate quotient
- } elsif ($1) {
- $_[0] = 'inf';
- } else {
- $_[0] = 'undef';
- }
- return 1;
- }
- #------------------------------------------------------------------------------
- # Convert a string of floating point values to rationals
- # Inputs: 0) string of floating point numbers separated by spaces
- # Returns: string of rational numbers separated by spaces
- sub ConvertRationalList($)
- {
- my $val = shift;
- my @vals = split ' ', $val;
- return $val unless @vals == 4;
- foreach (@vals) {
- ConvertRational($_) or return $val;
- }
- return join ' ', @vals;
- }
- #------------------------------------------------------------------------------
- # We found an XMP property name/value
- # Inputs: 0) ExifTool object ref, 1) Pointer to tag table
- # 2) reference to array of XMP property names (last is current property)
- # 3) property value, 4) attribute hash ref (for 'xml:lang' or 'rdf:datatype')
- # Returns: 1 if valid tag was found
- sub FoundXMP($$$$;$)
- {
- local $_;
- my ($et, $tagTablePtr, $props, $val, $attrs) = @_;
- my ($lang, @structProps, $rawVal, $rational);
- my ($tag, $ns) = GetXMPTagID($props, $$et{OPTIONS}{Struct} ? \@structProps : undef);
- return 0 unless $tag; # ignore things that aren't valid tags
- # translate namespace if necessary
- $ns = $stdXlatNS{$ns} if $stdXlatNS{$ns};
- my $info = $$tagTablePtr{$ns};
- my ($table, $added, $xns, $tagID);
- if ($info) {
- $table = $$info{SubDirectory}{TagTable} or warn "Missing TagTable for $tag!\n";
- } elsif ($$props[0] eq 'svg:svg') {
- if (not $ns) {
- # disambiguate MetadataID by adding back the 'metadata' we ignored
- $tag = 'metadataId' if $tag eq 'id' and $$props[1] eq 'svg:metadata';
- # use SVG namespace in SVG files if nothing better to use
- $table = 'Image::ExifTool::XMP::SVG';
- } elsif (not grep /^rdf:/, @$props) {
- # only other SVG information if not inside RDF (call it XMP if in RDF)
- $table = 'Image::ExifTool::XMP::otherSVG';
- }
- }
- if (not $ns and $$tagTablePtr{GROUPS}{0} ne 'XMP') {
- # this is a simple XML table (no namespaces)
- $tagID = $tag;
- } else {
- # look up this tag in the appropriate table
- $table or $table = 'Image::ExifTool::XMP::other';
- $tagTablePtr = GetTagTable($table);
- if ($$tagTablePtr{NAMESPACE}) {
- $tagID = $tag;
- } else {
- # add XMP namespace prefix to avoid collisions in variable-namespace tables
- $xns = $xmpNS{$ns} || $ns;
- $tagID = "$xns:$tag";
- # add namespace to top-level structure property
- $structProps[0][0] = "$xns:" . $structProps[0][0] if @structProps;
- }
- }
- my $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID);
- $lang = $$attrs{'xml:lang'} if $attrs;
- # must add a new tag table entry if this tag isn't pre-defined
- # (or initialize from structure field if this is a pre-defined flattened tag)
- NoLoop:
- while (not $tagInfo or $$tagInfo{Flat}) {
- my (@tagList, @nsList);
- GetXMPTagID($props, \@tagList, \@nsList);
- my ($ta, $t, $ti, $addedFlat, $i, $j);
- # build tag ID strings for each level in the property path
- foreach $ta (@tagList) {
- # insert tag ID in index 1 of tagList list
- $t = $$ta[1] = $t ? $t . ucfirst($$ta[0]) : $$ta[0];
- # generate flattened tags for top-level structure if necessary
- next if defined $addedFlat;
- $ti = $$tagTablePtr{$t} or next;
- next unless ref $ti eq 'HASH' and $$ti{Struct};
- $addedFlat = AddFlattenedTags($tagTablePtr, $t);
- if ($tagInfo) {
- # all done if we just wanted to initialize the flattened tag
- if ($$tagInfo{Flat}) {
- warn "Orphan tagInfo with Flat flag set: $$tagInfo{Name}\n";
- delete $$tagInfo{Flat};
- }
- last NoLoop;
- }
- # all done if we generated the tag we are looking for
- $tagInfo = $$tagTablePtr{$tagID} and last NoLoop if $addedFlat;
- }
- my $name = ucfirst($tag);
- # search for the innermost containing structure
- # (in case tag is an unknown field in a known structure)
- # (only necessary if we found a structure above)
- if (defined $addedFlat) {
- my $t2 = '';
- for ($i=$#tagList-1; $i>=0; --$i) {
- $t = $tagList[$i][1];
- $t2 = $tagList[$i+1][0] . ucfirst($t2); # build relative tag id
- $ti = $$tagTablePtr{$t} or next;
- next unless ref $ti eq 'HASH';
- my $strTable = $$ti{Struct} or next;
- my $flat = (defined $$ti{FlatName} ? $$ti{FlatName} : $$ti{Name});
- $name = $flat . ucfirst($t2);
- # don't continue if structure is known but field is not
- last if $$strTable{NAMESPACE} or not exists $$strTable{NAMESPACE};
- # this is a variable-namespace structure, so we must:
- # 1) get tagInfo from corresponding top-level XMP tag if it exists
- # 2) add new entry in this tag table, but with namespace prefix on tag ID
- my $n = $nsList[$i+1]; # namespace of structure field
- # translate to standard ExifTool namespace
- $n = $stdXlatNS{$n} if $stdXlatNS{$n};
- my $xn = $xmpNS{$n} || $n; # standard XMP namespace
- # no need to continue with variable-namespace logic if
- # we are in our own namespace (right?)
- last if $xn eq ($$tagTablePtr{NAMESPACE} || '');
- $tagID = "$xn:$tag"; # add namespace to avoid collisions
- # change structure properties to add the standard XMP namespace
- # prefix for this field (needed for variable-namespace fields)
- if (@structProps) {
- $structProps[$i+1][0] = "$xn:" . $structProps[$i+1][0];
- }
- # copy tagInfo entries from the existing top-level XMP tag
- my $tg = $Image::ExifTool::XMP::Main{$n};
- last unless ref $tg eq 'HASH' and $$tg{SubDirectory};
- my $tbl = GetTagTable($$tg{SubDirectory}{TagTable}) or last;
- my $sti = $et->GetTagInfo($tbl, $t2);
- if (not $sti or $$sti{Flat}) {
- # again, we must initialize flattened tags if necessary
- # (but don't bother to recursively apply full logic to
- # allow nested variable-namespace strucures until someone
- # actually wants to do such a silly thing)
- my $t3 = '';
- for ($j=$i+1; $j<@tagList; ++$j) {
- $t3 = $tagList[$j][0] . ucfirst($t3);
- my $ti3 = $$tbl{$t3} or next;
- next unless ref $ti3 eq 'HASH' and $$ti3{Struct};
- last unless AddFlattenedTags($tbl, $t3);
- $sti = $$tbl{$t2};
- last;
- }
- last unless $sti;
- }
- $tagInfo = {
- %$sti,
- Name => $flat . $$sti{Name},
- WasAdded => 1,
- };
- # be careful not to copy elements we shouldn't...
- delete $$tagInfo{Description}; # Description will be different
- # can't copy group hash because group 1 will be different and
- # we need to check this when writing tag to a specific group
- delete $$tagInfo{Groups};
- $$tagInfo{Groups}{2} = $$sti{Groups}{2} if $$sti{Groups};
- last;
- }
- }
- $tagInfo or $tagInfo = { Name => $name, WasAdded => 1 };
- # add tag Namespace entry for tags in variable-namespace tables
- $$tagInfo{Namespace} = $xns if $xns;
- if ($$et{curURI}{$ns} and $$et{curURI}{$ns} =~ m{^http://ns.exiftool.ca/(.*?)/(.*?)/}) {
- my %grps = ( 0 => $1, 1 => $2 );
- # apply a little magic to recover original group names
- # from this exiftool-written RDF/XML file
- if ($grps{1} =~ /^\d/) {
- # URI's with only family 0 are internal tags from the source file,
- # so change the group name to avoid confusion with tags from this file
- $grps{1} = "XML-$grps{0}";
- $grps{0} = 'XML';
- }
- $$tagInfo{Groups} = \%grps;
- # flag to avoid setting group 1 later
- $$tagInfo{StaticGroup1} = 1;
- }
- # construct tag information for this unknown tag
- # -> make this a List or lang-alt tag if necessary
- if (@$props > 2 and $$props[-1] =~ /^rdf:li \d+$/ and
- $$props[-2] =~ /^rdf:(Bag|Seq|Alt)$/)
- {
- if ($lang and $1 eq 'Alt') {
- $$tagInfo{Writable} = 'lang-alt';
- } else {
- $$tagInfo{List} = $1;
- }
- # tried this, but maybe not a good idea for complex structures:
- #} elsif (grep / /, @$props) {
- # $$tagInfo{List} = 1;
- }
- AddTagToTable($tagTablePtr, $tagID, $tagInfo);
- $added = 1;
- last;
- }
- # decode value if necessary (et:encoding was used before exiftool 7.71)
- if ($attrs) {
- my $enc = $$attrs{'rdf:datatype'} || $$attrs{'et:encoding'};
- if ($enc and $enc =~ /base64/) {
- $val = DecodeBase64($val); # (now a value ref)
- $val = $$val unless length $$val > 100 or $$val =~ /[\0-\x08\x0b\0x0c\x0e-\x1f]/;
- }
- }
- if (defined $lang and lc($lang) ne 'x-default') {
- $lang = StandardLangCase($lang);
- my $langInfo = GetLangInfo($tagInfo, $lang);
- $tagInfo = $langInfo if $langInfo;
- }
- # un-escape XML character entities (handling CDATA)
- pos($val) = 0;
- if ($val =~ /<!\[CDATA\[(.*?)\]\]>/sg) {
- my $p = pos $val;
- # unescape everything up to the start of the CDATA section
- # (the length of "<[[CDATA[]]>" is 12 characters)
- my $v = UnescapeXML(substr($val, 0, $p - length($1) - 12)) . $1;
- while ($val =~ /<!\[CDATA\[(.*?)\]\]>/sg) {
- my $p1 = pos $val;
- $v .= UnescapeXML(substr($val, $p, $p1 - length($1) - 12)) . $1;
- $p = $p1;
- }
- $val = $v . UnescapeXML(substr($val, $p));
- } else {
- $val = UnescapeXML($val);
- }
- # decode from UTF8
- $val = $et->Decode($val, 'UTF8');
- # convert rational and date values to a more sensible format
- my $fmt = $$tagInfo{Writable};
- my $new = $$tagInfo{WasAdded} && $$et{OPTIONS}{XMPAutoConv};
- if ($fmt or $new) {
- $rawVal = $val; # save raw value for verbose output
- if (($new or $fmt eq 'rational') and ConvertRational($val)) {
- $rational = $rawVal;
- } else {
- $val = ConvertXMPDate($val, $new) if $new or $fmt eq 'date';
- }
- # protect against large binary data in unknown tags
- $$tagInfo{Binary} = 1 if $new and length($val) > 65536;
- }
- # store the value for this tag
- my $key = $et->FoundTag($tagInfo, $val) or return 0;
- # save original components of rational numbers (used when copying)
- $$et{RATIONAL}{$key} = $rational if defined $rational;
- # save structure/list information if necessary
- if (@structProps and (@structProps > 1 or defined $structProps[0][1]) and
- not $$et{NO_STRUCT})
- {
- $$et{TAG_EXTRA}{$key}{Struct} = \@structProps;
- $$et{IsStruct} = 1;
- }
- if ($ns and not $$tagInfo{StaticGroup1}) {
- # set group1 dynamically according to the namespace
- $et->SetGroup($key, "$$tagTablePtr{GROUPS}{0}-$ns");
- }
- if ($$et{OPTIONS}{Verbose}) {
- if ($added) {
- my $g1 = $et->GetGroup($key, 1);
- $et->VPrint(0, $$et{INDENT}, "[adding $g1:$tag]\n");
- }
- my $tagID = join('/',@$props);
- $et->VerboseInfo($tagID, $tagInfo, Value => $rawVal || $val);
- }
- return 1;
- }
- #------------------------------------------------------------------------------
- # Recursively parse nested XMP data element
- # Inputs: 0) ExifTool ref, 1) tag table ref, 2) XMP data ref
- # 3) offset to start of XMP element, 4) offset to end of XMP element
- # 5) reference to array of enclosing XMP property names (undef if none)
- # 6) reference to blank node information hash
- # Returns: Number of contained XMP elements
- sub ParseXMPElement($$$;$$$$)
- {
- my ($et, $tagTablePtr, $dataPt, $start, $end, $propListPt, $blankInfo) = @_;
- my ($count, $nItems) = (0, 0);
- my $isWriting = $$et{XMP_CAPTURE};
- my $isSVG = $$et{XMP_IS_SVG};
- my $saveNS; # save xlatNS lookup if changed for the scope of this element
- # get our parse procs
- my ($attrProc, $foundProc);
- if ($$et{XMPParseOpts}) {
- $attrProc = $$et{XMPParseOpts}{AttrProc};
- $foundProc = $$et{XMPParseOpts}{FoundProc} || \&FoundXMP;
- } else {
- $foundProc = \&FoundXMP;
- }
- $start or $start = 0;
- $end or $end = length $$dataPt;
- $propListPt or $propListPt = [ ];
- my $processBlankInfo;
- # create empty blank node information hash if necessary
- $blankInfo or $blankInfo = $processBlankInfo = { Prop => { } };
- # keep track of current nodeID at this nesting level
- my $oldNodeID = $$blankInfo{NodeID};
- pos($$dataPt) = $start;
- # lookup for translating namespace prefixes
- my $xlatNS = $$et{xlatNS};
- Element: for (;;) {
- # all done if there isn't enough data for another element
- # (the smallest possible element is 4 bytes, eg. "<a/>")
- last if pos($$dataPt) > $end - 4;
- # reset nodeID before processing each element
- my $nodeID = $$blankInfo{NodeID} = $oldNodeID;
- # get next element
- last if $$dataPt !~ m{<([?/]?)([-\w:.\x80-\xff]+)([^>]*)>}sg or pos($$dataPt) > $end;
- # (the only reason we match '<[?/]' is to keep from scanning past the
- # "<?xpacket end..." terminator or other closing token, so
- next if $1;
- my ($prop, $attrs) = ($2, $3);
- my $valStart = pos($$dataPt);
- my $valEnd;
- # only look for closing token if this is not an empty element
- # (empty elements end with '/', eg. <a:b/>)
- if ($attrs !~ s/\/$//) {
- my $nesting = 1;
- my $tok;
- for (;;) {
- # this match fails with perl 5.6.2 (perl bug!), but it works without
- # the '(.*?)', so we must do it differently...
- # $$dataPt =~ m/(.*?)<\/$prop>/sg or last Element;
- # my $val2 = $1;
- # find next matching closing token, or the next opening token
- # of a nested same-named element
- if ($$dataPt !~ m{<(/?)$prop([-\w:.\x80-\xff]*)(.*?(/?))>}sg or
- pos($$dataPt) > $end)
- {
- $et->Warn("XMP format error (no closing tag for $prop)");
- last Element;
- }
- next if $2; # ignore opening properties with different names
- if ($1) {
- next if --$nesting;
- $valEnd = pos($$dataPt) - length($prop) - length($3) - 3;
- last; # this element is complete
- }
- # this is a nested opening token (or empty element)
- ++$nesting unless $4;
- }
- } else {
- $valEnd = $valStart;
- }
- $start = pos($$dataPt); # start from here the next time around
- # extract property attributes
- my ($parseResource, %attrs, @attrs);
- while ($attrs =~ m/(\S+?)\s*=\s*(['"])(.*?)\2/sg) {
- my ($attr, $val) = ($1, $3);
- # handle namespace prefixes (defined by xmlns:PREFIX, or used with PREFIX:tag)
- if ($attr =~ /(.*?):/) {
- if ($1 eq 'xmlns') {
- my $ns = substr($attr, 6);
- my $stdNS = $uri2ns{$val};
- unless ($stdNS) {
- my $try = $val;
- # patch for Nikon NX2 URI bug for Microsoft PhotoInfo namespace
- $try =~ s{/$}{} or $try .= '/';
- $stdNS = $uri2ns{$try};
- if ($stdNS) {
- $val = $try;
- $et->WarnOnce("Fixed incorrect URI for xmlns:$ns", 1);
- } else {
- # look for same namespace with different version number
- $try = quotemeta $val; # (note: escapes slashes too)
- $try =~ s{\\/\d+\\\.\d+(\\/|$)}{\\/\\d+\\\.\\d+$1};
- my ($good) = grep /^$try$/, keys %uri2ns;
- if ($good) {
- $stdNS = $uri2ns{$good};
- $et->VPrint(0, $$et{INDENT}, "[different $stdNS version: $val]\n");
- }
- }
- }
- # tame wild namespace prefixes (patches Microsoft stupidity)
- my $newNS;
- if ($stdNS) {
- # use standard namespace prefix if pre-defined
- if ($stdNS ne $ns) {
- $newNS = $stdNS;
- } elsif ($$xlatNS{$ns}) {
- # this prefix is re-defined to the standard prefix in this scope
- $newNS = '';
- }
- } elsif ($$et{curNS}{$val}) {
- # use a consistent prefix over the entire XMP for a given namespace URI
- $newNS = $$et{curNS}{$val} if $$et{curNS}{$val} ne $ns;
- } else {
- my $curURI = $$et{curURI};
- my $curNS = $$et{curNS};
- my $usedNS = $ns;
- # use unique prefixes for all namespaces across the entire XMP
- if ($$curURI{$ns} or $nsURI{$ns}) {
- # generate a temporary namespace prefix to resolve any conflict
- my $i = 0;
- ++$i while $$curURI{"tmp$i"};
- $newNS = $usedNS = "tmp$i";
- }
- # keep track of the namespace prefixes and URI's used in this XMP
- $$curNS{$val} = $usedNS;
- $$curURI{$usedNS} = $val;
- }
- if (defined $newNS) {
- # save translation used in containing scope if necessary
- # create new namespace translation for the scope of this element
- $saveNS or $saveNS = $xlatNS, $xlatNS = $$et{xlatNS} = { %$xlatNS };
- if (length $newNS) {
- # use the new namespace prefix
- $$xlatNS{$ns} = $newNS;
- $attr = 'xmlns:' . $newNS;
- } else {
- delete $$xlatNS{$ns};
- }
- }
- } elsif ($$xlatNS{$1}) {
- $attr = $$xlatNS{$1} . substr($attr, length($1));
- }
- }
- push @attrs, $attr; # preserve order
- $attrs{$attr} = $val;
- }
- # tame wild namespace prefixes (patch for Microsoft stupidity)
- if ($prop =~ /(.*?):/ and $$xlatNS{$1}) {
- $prop = $$xlatNS{$1} . substr($prop, length($1));
- }
- if ($prop eq 'rdf:li') {
- # impose a reasonable maximum on the number of items in a list
- if ($nItems == 1000) {
- my ($tg,$ns) = GetXMPTagID($propListPt);
- if ($isWriting) {
- $et->Warn("Excessive number of items for $ns:$tg. Processing may be slow", 1);
- } elsif (not $$et{OPTIONS}{IgnoreMinorErrors}) {
- $et->Warn("Extracted only 1000 $ns:$tg items. Ignore minor errors to extract all", 2);
- last;
- }
- }
- # add index to list items so we can keep them in order
- # (this also enables us to keep structure elements grouped properly
- # for lists of structures, like JobRef)
- # Note: the list index is prefixed by the number of digits so sorting
- # alphabetically gives the correct order while still allowing a flexible
- # number of digits -- this scheme allows up to 9 digits in the index,
- # with index numbers ranging from 0 to 999999999. The sequence is:
- # 10,11,12-19,210,211-299,3100,3101-3999,41000...9999999999.
- $prop .= ' ' . length($nItems) . $nItems;
- # reset LIST_TAGS at the start of the outtermost list
- # (avoids accumulating incorrectly-written elements in a correctly-written list)
- if (not $nItems and not grep /^rdf:li /, @$propListPt) {
- $$et{LIST_TAGS} = { };
- }
- ++$nItems;
- } elsif ($prop eq 'rdf:Description') {
- # remove unnecessary rdf:Description elements since parseType='Resource'
- # is more efficient (also necessary to make property path consistent)
- $parseResource = 1 if grep /^rdf:Description$/, @$propListPt;
- } elsif ($prop eq 'xmp:xmpmeta') {
- # patch MicrosoftPhoto unconformity
- $prop = 'x:xmpmeta';
- }
- # hook for special parsing of attributes
- my $val;
- if ($attrProc) {
- $val = substr($$dataPt, $valStart, $valEnd - $valStart);
- if (&$attrProc(\@attrs, \%attrs, \$prop, \$val)) {
- # the value was changed, so reset $valStart/$valEnd to use $val instead
- $valStart = $valEnd;
- }
- }
- # add nodeID to property path (with leading ' #') if it exists
- if (defined $attrs{'rdf:nodeID'}) {
- $nodeID = $$blankInfo{NodeID} = $attrs{'rdf:nodeID'};
- delete $attrs{'rdf:nodeID'};
- $prop .= ' #' . $nodeID;
- undef $parseResource; # can't ignore if this is a node
- }
- # push this property name onto our hierarchy list
- push @$propListPt, $prop unless $parseResource;
- if ($isSVG) {
- # ignore everything but top level SVG tags and metadata unless Unknown set
- unless ($$et{OPTIONS}{Unknown} > 1 or $$et{OPTIONS}{Verbose}) {
- if (@$propListPt > 1 and $$propListPt[1] !~ /\b(metadata|desc|title)$/) {
- pop @$propListPt;
- next;
- }
- }
- if ($prop eq 'svg' or $prop eq 'metadata') {
- # add svg namespace prefix if missing to ignore these entries in the tag name
- $$propListPt[-1] = "svg:$prop";
- }
- }
- # handle properties inside element attributes (RDF shorthand format):
- # (attributes take the form a:b='c' or a:b="c")
- my ($shortName, $shorthand, $ignored);
- foreach $shortName (@attrs) {
- next unless defined $attrs{$shortName};
- my $propName = $shortName;
- my ($ns, $name);
- if ($propName =~ /(.*?):(.*)/) {
- $ns = $1; # specified namespace
- $name = $2;
- } elsif ($prop =~ /(\S*?):/) {
- $ns = $1; # assume same namespace as parent
- $name = $propName;
- $propName = "$ns:$name"; # generate full property name
- } else {
- # a property qualifier is the only property name that may not
- # have a namespace, and a qualifier shouldn't have attributes,
- # but what the heck, let's allow this anyway
- $ns = '';
- $name = $propName;
- }
- if ($isWriting) {
- # keep track of our namespaces when writing
- if ($ns eq 'xmlns') {
- my $stdNS = $uri2ns{$attrs{$shortName}};
- unless ($stdNS and ($stdNS eq 'x' or $stdNS eq 'iX')) {
- my $nsUsed = $$et{XMP_NS};
- $$nsUsed{$name} = $attrs{$shortName} unless defined $$nsUsed{$name};
- }
- delete $attrs{$shortName}; # (handled by namespace logic)
- next;
- } elsif ($recognizedAttrs{$propName}) {
- # save UUID to use same ID when writing
- if ($propName eq 'rdf:about') {
- if (not $$et{XMP_ABOUT}) {
- $$et{XMP_ABOUT} = $attrs{$shortName};
- } elsif ($$et{XMP_ABOUT} ne $attrs{$shortName}) {
- $et->Error("Different 'rdf:about' attributes not handled", 1);
- }
- }
- next;
- }
- }
- my $shortVal = $attrs{$shortName};
- if ($ignoreNamespace{$ns} or $ignoreProp{$prop}) {
- $ignored = $propName;
- # handle special attributes (extract as tags only once if not empty)
- if (ref $recognizedAttrs{$propName} and $shortVal) {
- my ($tbl, $id, $name) = @{$recognizedAttrs{$propName}};
- my $tval = UnescapeXML($shortVal);
- unless (defined $$et{VALUE}{$name} and $$et{VALUE}{$name} eq $tval) {
- $et->HandleTag(GetTagTable($tbl), $id, $tval);
- }
- }
- next;
- }
- delete $attrs{$shortName}; # don't re-use this attribute
- push @$propListPt, $propName;
- # save this shorthand XMP property
- if (defined $nodeID) {
- SaveBlankInfo($blankInfo, $propListPt, $shortVal);
- } elsif ($isWriting) {
- CaptureXMP($et, $propListPt, $shortVal);
- } else {
- &$foundProc($et, $tagTablePtr, $propListPt, $shortVal);
- }
- pop @$propListPt;
- $shorthand = 1;
- }
- if ($isWriting) {
- if (ParseXMPElement($et, $tagTablePtr, $dataPt, $valStart, $valEnd,
- $propListPt, $blankInfo))
- {
- # (no value since we found more properties within this one)
- # set an error on any ignored attributes here, because they will be lost
- $$et{XMP_ERROR} = "Can't handle XMP attribute '$ignored'" if $ignored;
- } elsif (not $shorthand or $valEnd != $valStart) {
- $val = substr($$dataPt, $valStart, $valEnd - $valStart);
- # remove comments and whitespace from rdf:Description only
- if ($prop eq 'rdf:Description') {
- $val =~ s/<!--.*?-->//g; $val =~ s/^\s+//; $val =~ s/\s+$//;
- }
- if (defined $nodeID) {
- SaveBlankInfo($blankInfo, $propListPt, $val, \%attrs);
- } else {
- CaptureXMP($et, $propListPt, $val, \%attrs);
- }
- }
- } else {
- # look for additional elements contained within this one
- if ($valStart == $valEnd or
- !ParseXMPElement($et, $tagTablePtr, $dataPt, $valStart, $valEnd,
- $propListPt, $blankInfo))
- {
- my $wasEmpty;
- unless (defined $val) {
- $val = substr($$dataPt, $valStart, $valEnd - $valStart);
- # remove comments and whitespace from rdf:Description only
- if ($prop eq 'rdf:Description' and $val) {
- $val =~ s/<!--.*?-->//g; $val =~ s/^\s+//; $val =~ s/\s+$//;
- }
- # if element value is empty, take value from 'resource' attribute
- # (preferentially) or 'about' attribute (if no 'resource')
- if ($val eq '' and ($attrs =~ /\bresource=(['"])(.*?)\1/ or
- $attrs =~ /\babout=(['"])(.*?)\1/))
- {
- $val = $2;
- $wasEmpty = 1;
- }
- }
- # there are no contained elements, so this must be a simple property value
- # (unless we already extracted shorthand values from this element)
- if (length $val or not $shorthand) {
- my $lastProp = $$propListPt[-1];
- if (defined $nodeID) {
- SaveBlankInfo($blankInfo, $propListPt, $val);
- } elsif ($lastProp eq 'rdf:type' and $wasEmpty) {
- # do not extract empty structure types (for now)
- } elsif ($lastProp =~ /^et:(desc|prt|val)$/ and ($count or $1 eq 'desc')) {
- # ignore et:desc, and et:val if preceded by et:prt
- --$count;
- } else {
- &$foundProc($et, $tagTablePtr, $propListPt, $val, \%attrs);
- }
- }
- }
- }
- pop @$propListPt unless $parseResource;
- ++$count;
- last if $start >= $end;
- pos($$dataPt) = $start;
- $$dataPt =~ /\G\s+/gc; # skip white space after closing token
- }
- #
- # process resources referenced by blank nodeID's
- #
- if ($processBlankInfo and %{$$blankInfo{Prop}}) {
- ProcessBlankInfo($et, $tagTablePtr, $blankInfo, $isWriting);
- %$blankInfo = (); # free some memory
- }
- # restore namespace lookup from the containing scope
- $$et{xlatNS} = $saveNS if $saveNS;
- return $count; # return the number of elements found at this level
- }
- #------------------------------------------------------------------------------
- # Process XMP data
- # Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) Pointer to tag table
- # Returns: 1 on success
- # Notes: The following flavours of XMP files are currently recognized:
- # - standard XMP with xpacket, x:xmpmeta and rdf:RDF elements
- # - XMP that is missing the xpacket and/or x:xmpmeta elements
- # - mutant Microsoft XMP with xmp:xmpmeta element
- # - XML files beginning with "<xml"
- # - SVG files that begin with "<svg" or "<!DOCTYPE svg"
- # - XMP and XML files beginning with a UTF-8 byte order mark
- # - UTF-8, UTF-16 and UTF-32 encoded XMP
- # - erroneously double-UTF8 encoded XMP
- # - otherwise valid files with leading XML comment
- sub ProcessXMP($$;$)
- {
- my ($et, $dirInfo, $tagTablePtr) = @_;
- my $dataPt = $$dirInfo{DataPt};
- my ($dirStart, $dirLen, $dataLen, $double);
- my ($buff, $fmt, $hasXMP, $isXML, $isRDF, $isSVG);
- my $rtnVal = 0;
- my $bom = 0;
- # namespaces and prefixes currently in effect while parsing the file,
- # and lookup to translate brain-dead-Microsoft-Photo-software prefixes
- $$et{curURI} = { };
- $$et{curNS} = { };
- $$et{xlatNS} = { };
- # ignore non-standard XMP while in strict MWG compatibility mode
- if ($Image::ExifTool::MWG::strict and not $$et{XMP_CAPTURE} and
- $$et{FILE_TYPE} =~ /^(JPEG|TIFF|PSD)$/)
- {
- my $path = $et->MetadataPath();
- unless ($path =~ /^(JPEG-APP1-XMP|TIFF-IFD0-XMP|PSD-XMP)$/) {
- $et->Warn("Ignored non-standard XMP at $path");
- return 1;
- }
- }
- if ($dataPt) {
- $dirStart = $$dirInfo{DirStart} || 0;
- $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart);
- $dataLen = $$dirInfo{DataLen} || length($$dataPt);
- # check leading BOM (may indicate double-encoded UTF)
- pos($$dataPt) = $dirStart;
- $double = $1 if $$dataPt =~ /\G((\0\0)?\xfe\xff|\xff\xfe(\0\0)?|\xef\xbb\xbf)\0*<\0*\?\0*x\0*p\0*a\0*c\0*k\0*e\0*t/g;
- } else {
- my ($type, $buf2, $buf3);
- # read information from XMP file
- my $raf = $$dirInfo{RAF} or return 0;
- $raf->Read($buff, 256) or return 0;
- ($buf2 = $buff) =~ tr/\0//d; # cheap conversion to UTF-8
- # remove leading comments if they exist (eg. ImageIngester)
- while ($buf2 =~ /^\s*<!--/) {
- # remove the comment if it is complete
- if ($buf2 =~ s/^\s*<!--.*?-->\s+//s) {
- # continue with parsing if we have more than 128 bytes remaining
- next if length $buf2 > 128;
- } else {
- # don't read more than 10k when looking for the end of comment
- return 0 if length($buf2) > 10000;
- }
- $raf->Read($buf3, 256) or last; # read more data if available
- $buff .= $buf3;
- $buf3 =~ tr/\0//d;
- $buf2 .= $buf3;
- }
- # check to see if this is XMP format
- # (CS2 writes .XMP files without the "xpacket begin")
- if ($buf2 =~ /^\s*(<\?xpacket begin=|<x(mp)?:x[ma]pmeta)/) {
- $hasXMP = 1;
- } else {
- # also recognize XML files and .XMP files with BOM and without x:xmpmeta
- if ($buf2 =~ /^(\xfe\xff)(<\?xml|<rdf:RDF|<x(mp)?:x[ma]pmeta)/g) {
- $fmt = 'n'; # UTF-16 or 32 MM with BOM
- } elsif ($buf2 =~ /^(\xff\xfe)(<\?xml|<rdf:RDF|<x(mp)?:x[ma]pmeta)/g) {
- $fmt = 'v'; # UTF-16 or 32 II with BOM
- } elsif ($buf2 =~ /^(\xef\xbb\xbf)?(<\?xml|<rdf:RDF|<x(mp)?:x[ma]pmeta)/g) {
- $fmt = 0; # UTF-8 with BOM or unknown encoding without BOM
- } elsif ($buf2 =~ /^(\xfe\xff|\xff\xfe|\xef\xbb\xbf)(<\?xpacket begin=)/g) {
- $double = $1; # double-encoded UTF
- } else {
- return 0; # not recognized XMP or XML
- }
- $bom = 1 if $1;
- if ($2 eq '<?xml') {
- if (defined $fmt and not $fmt and $buf2 =~ /^[^\n\r]*[\n\r]+<\?aid /s) {
- if ($$et{XMP_CAPTURE}) {
- $et->Error("ExifTool does not yet support writing of INX files");
- return 0;
- }
- $type = 'INX';
- } elsif ($buf2 =~ /<x(mp)?:x[ma]pmeta/) {
- $hasXMP = 1;
- } else {
- # identify SVG images and PLIST files by DOCTYPE if available
- if ($buf2 =~ /<!DOCTYPE\s+(\w+)/) {
- if ($1 eq 'svg') {
- $isSVG = 1;
- } elsif ($1 eq 'plist') {
- $type = 'PLIST';
- } else {
- return 0;
- }
- } elsif ($buf2 =~ /<svg[\s>]/) {
- $isSVG = 1;
- } elsif ($buf2 =~ /<rdf:RDF/) {
- $isRDF = 1;
- } elsif ($buf2 =~ /<plist[\s>]/) {
- $type = 'PLIST';
- }
- if ($isSVG and $$et{XMP_CAPTURE}) {
- $et->Error("ExifTool does not yet support writing of SVG images");
- return 0;
- }
- }
- $isXML = 1;
- } elsif ($2 eq '<rdf:RDF') {
- $isRDF = 1; # recognize XMP without x:xmpmeta element
- }
- if ($buff =~ /^\0\0/) {
- $fmt = 'N'; # UTF-32 MM with or without BOM
- } elsif ($buff =~ /^..\0\0/s) {
- $fmt = 'V'; # UTF-32 II with or without BOM
- } elsif (not $fmt) {
- if ($buff =~ /^\0/) {
- $fmt = 'n'; # UTF-16 MM without BOM
- } elsif ($buff =~ /^.\0/s) {
- $fmt = 'v'; # UTF-16 II without BOM
- }
- }
- }
- my $size;
- if ($type) {
- if ($type eq 'PLIST') {
- my $ext = $$et{FILE_EXT};
- $type = $ext if $ext and $ext eq 'MODD';
- $tagTablePtr = GetTagTable('Image::ExifTool::PLIST::Main');
- $$dirInfo{XMPParseOpts}{FoundProc} = \&Image::ExifTool::PLIST::FoundTag;
- }
- } else {
- if ($isSVG) {
- $type = 'SVG';
- } elsif ($isXML and not $hasXMP and not $isRDF) {
- $type = 'XML';
- my $ext = $$et{FILE_EXT};
- $type = $ext if $ext and $ext eq 'COS'; # recognize COS by extension
- }
- }
- $et->SetFileType($type);
- my $fast = $et->Options('FastScan');
- return 1 if $fast and $fast == 3;
- if ($type and $type eq 'INX') {
- # brute force search for first XMP packet in INX file
- # start: '<![CDATA[<?xpacket begin' (24 bytes)
- # end: '<?xpacket end="r"?>]]>' (22 bytes)
- $raf->Seek(0, 0) or return 0;
- $raf->Read($buff, 65536) or return 1;
- for (;;) {
- last if $buff =~ /<!\[CDATA\[<\?xpacket begin/g;
- $raf->Read($buf2, 65536) or return 1;
- $buff = substr($buff, -24) . $buf2;
- }
- $buff = substr($buff, pos($buff) - 15); # (discard '<![CDATA[' and before)
- for (;;) {
- last if $buff =~ /<\?xpacket end="[rw]"\?>\]\]>/g;
- my $n = length $buff;
- $raf->Read($buf2, 65536) or $et->Warn('Missing xpacket end'), return 1;
- $buff .= $buf2;
- pos($buff) = $n - 22; # don't miss end pattern if it was split
- }
- $size = pos($buff) - 3; # (discard ']]>' and after)
- $buff = substr($buff, 0, $size);
- } else {
- # read the entire file
- $raf->Seek(0, 2) or return 0;
- $size = $raf->Tell() or return 0;
- $raf->Seek(0, 0) or return 0;
- $raf->Read($buff, $size) == $size or return 0;
- }
- $dataPt = \$buff;
- $dirStart = 0;
- $dirLen = $dataLen = $size;
- }
- # decode the first layer of double-encoded UTF text (if necessary)
- if ($double) {
- my ($buf2, $fmt);
- $buff = substr($$dataPt, $dirStart + length $double); # remove leading BOM
- Image::ExifTool::SetWarning(undef); # clear old warning
- local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning;
- # assume that character data has been re-encoded in UTF, so re-pack
- # as characters and look for warnings indicating a false assumption
- if ($double eq "\xef\xbb\xbf") {
- require Image::ExifTool::Charset;
- my $uni = Image::ExifTool::Charset::Decompose(undef,$buff,'UTF8');
- $buf2 = pack('C*', @$uni);
- } else {
- if (length($double) == 2) {
- $fmt = ($double eq "\xfe\xff") ? 'n' : 'v';
- } else {
- $fmt = ($double eq "\0\0\xfe\xff") ? 'N' : 'V';
- }
- $buf2 = pack('C*', unpack("$fmt*",$buff));
- }
- if (Image::ExifTool::GetWarning()) {
- $et->Warn('Superfluous BOM at start of XMP');
- $dataPt = \$buff; # use XMP with the BOM removed
- } else {
- $et->Warn('XMP is double UTF-encoded');
- $dataPt = \$buf2; # use the decoded XMP
- }
- $dirStart = 0;
- $dirLen = $dataLen = length $$dataPt;
- }
- # extract XMP as a block if specified
- my $blockName = $$dirInfo{BlockInfo} ? $$dirInfo{BlockInfo}{Name} : 'XMP';
- if (($$et{REQ_TAG_LOOKUP}{lc $blockName} or ($$et{TAGS_FROM_FILE} and
- not $$et{EXCL_TAG_LOOKUP}{lc $blockName})) and not $isSVG)
- {
- $et->FoundTag($$dirInfo{BlockInfo} || 'XMP', substr($$dataPt, $dirStart, $dirLen));
- }
- $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main');
- if ($et->Options('Verbose') and not $$et{XMP_CAPTURE}) {
- my $dirType = $isSVG ? 'SVG' : $$tagTablePtr{GROUPS}{1};
- $et->VerboseDir($dirType, 0, $dirLen);
- }
- #
- # convert UTF-16 or UTF-32 encoded XMP to UTF-8 if necessary
- #
- my $begin = '<?xpacket begin=';
- my $dirEnd = $dirStart + $dirLen;
- pos($$dataPt) = $dirStart;
- delete $$et{XMP_IS_XML};
- delete $$et{XMP_IS_SVG};
- if ($isXML or $isRDF) {
- $$et{XMP_IS_XML} = $isXML;
- $$et{XMP_IS_SVG} = $isSVG;
- $$et{XMP_NO_XPACKET} = 1 + $bom;
- } elsif ($$dataPt =~ /\G\Q$begin\E/gc) {
- delete $$et{XMP_NO_XPACKET};
- } elsif ($$dataPt =~ /<x(mp)?:x[ma]pmeta/gc and
- pos($$dataPt) > $dirStart and pos($$dataPt) < $dirEnd)
- {
- $$et{XMP_NO_XPACKET} = 1 + $bom;
- } else {
- delete $$et{XMP_NO_XPACKET};
- # check for UTF-16 encoding (insert one \0 between characters)
- $begin = join "\0", split //, $begin;
- # must reset pos because it was killed by previous unsuccessful //g match
- pos($$dataPt) = $dirStart;
- if ($$dataPt =~ /\G(\0)?\Q$begin\E\0./sg) {
- # validate byte ordering by checking for U+FEFF character
- if ($1) {
- # should be big-endian since we had a leading \0
- $fmt = 'n' if $$dataPt =~ /\G\xfe\xff/g;
- } else {
- $fmt = 'v' if $$dataPt =~ /\G\0\xff\xfe/g;
- }
- } else {
- # check for UTF-32 encoding (with three \0's between characters)
- $begin =~ s/\0/\0\0\0/g;
- pos($$dataPt) = $dirStart;
- if ($$dataPt !~ /\G(\0\0\0)?\Q$begin\E\0\0\0./sg) {
- $fmt = 0; # set format to zero as indication we didn't find encoded XMP
- } elsif ($1) {
- # should be big-endian
- $fmt = 'N' if $$dataPt =~ /\G\0\0\xfe\xff/g;
- } else {
- $fmt = 'V' if $$dataPt =~ /\G\0\0\0\xff\xfe\0\0/g;
- }
- }
- defined $fmt or $et->Warn('XMP character encoding error');
- }
- if ($fmt) {
- # trim if necessary to avoid converting non-UTF data
- if ($dirStart or $dirEnd != length($$dataPt)) {
- $buff = substr($$dataPt, $dirStart, $dirLen);
- $dataPt = \$buff;
- }
- # convert into UTF-8
- if ($] >= 5.006001) {
- $buff = pack('C0U*', unpack("$fmt*",$$dataPt));
- } else {
- $buff = Image::ExifTool::PackUTF8(unpack("$fmt*",$$dataPt));
- }
- $dataPt = \$buff;
- $dirStart = 0;
- $dirLen = length $$dataPt;
- $dirEnd = $dirStart + $dirLen;
- }
- # avoid scanning for XMP later in case ScanForXMP is set
- $$et{FoundXMP} = 1 if $tagTablePtr eq \%Image::ExifTool::XMP::Main;
- # set XMP parsing options
- $$et{XMPParseOpts} = $$dirInfo{XMPParseOpts};
- # ignore any specified properties (XML hack)
- if ($$dirInfo{IgnoreProp}) {
- %ignoreProp = %{$$dirInfo{IgnoreProp}};
- } else {
- undef %ignoreProp;
- }
- # need to preserve list indices to be able to handle multi-dimensional lists
- my $keepFlat;
- if ($$et{OPTIONS}{Struct}) {
- if ($$et{OPTIONS}{Struct} eq '2') {
- $keepFlat = 1; # preserve flattened tags
- # setting NO_LIST to 0 combines list items in a TAG_EXTRA "NoList" element
- # to allow them to be re-listed later if necessary. A "NoListDel" element
- # is also created for tags that wouldn't have existed.
- $$et{NO_LIST} = 0;
- } else {
- $$et{NO_LIST} = 1;
- }
- }
- # don't generate structures if this isn't real XMP
- $$et{NO_STRUCT} = 1 if $$dirInfo{BlockInfo} or $$dirInfo{NoStruct};
- # parse the XMP
- if (ParseXMPElement($et, $tagTablePtr, $dataPt, $dirStart, $dirEnd)) {
- $rtnVal = 1;
- } elsif ($$dirInfo{DirName} and $$dirInfo{DirName} eq 'XMP') {
- # if DirName was 'XMP' we expect well-formed XMP, so set Warning since it wasn't
- # (but allow empty XMP as written by some PhaseOne cameras)
- my $xmp = substr($$dataPt, $dirStart, $dirLen);
- if ($xmp =~ /^ *\0*$/) {
- $et->Warn('Invalid XMP');
- } else {
- $et->Warn('Empty XMP',1);
- $rtnVal = 1;
- }
- }
- delete $$et{NO_STRUCT};
- # return DataPt if successful in case we want it for writing
- $$dirInfo{DataPt} = $dataPt if $rtnVal and $$dirInfo{RAF};
- # restore structures if necessary
- if ($$et{IsStruct}) {
- require 'Image/ExifTool/XMPStruct.pl';
- RestoreStruct($et, $keepFlat);
- delete $$et{IsStruct};
- }
- # reset NO_LIST flag (must do this _after_ RestoreStruct() above)
- delete $$et{NO_LIST};
- delete $$et{XMPParseOpts};
- delete $$et{curURI};
- delete $$et{curNS};
- delete $$et{xlatNS};
- return $rtnVal;
- }
- 1; #end
- __END__
- =head1 NAME
- Image::ExifTool::XMP - Read XMP meta information
- =head1 SYNOPSIS
- This module is loaded automatically by Image::ExifTool when required.
- =head1 DESCRIPTION
- XMP stands for Extensible Metadata Platform. It is a format based on XML
- that Adobe developed for embedding metadata information in image files.
- This module contains the definitions required by Image::ExifTool to read XMP
- information.
- =head1 AUTHOR
- Copyright 2003-2016, Phil Harvey (phil at owl.phy.queensu.ca)
- This library is free software; you can redistribute it and/or modify it
- under the same terms as Perl itself.
- =head1 REFERENCES
- =over 4
- =item L<http://www.adobe.com/devnet/xmp/>
- =item L<http://www.w3.org/TR/rdf-syntax-grammar/>
- =item L<http://www.iptc.org/IPTC4XMP/>
- =back
- =head1 SEE ALSO
- L<Image::ExifTool::TagNames/XMP Tags>,
- L<Image::ExifTool(3pm)|Image::ExifTool>
- =cut
|