Ignore:
Timestamp:
2021-02-26T19:39:51+13:00 (3 years ago)
Author:
anupama
Message:

Committing the improvements to EmbeddedMetaPlugin's processing of Keywords vs other metadata fields. Keywords were literally stored as arrays of words rather than phrases in PDFs (at least in Diego's sample PDF), whereas other meta fields like Subjects and Creators stored them as arrays of phrases. To get both to work, Kathy updated EXIF to a newer version, to retrieve the actual EXIF values stored in the PDF. And Kathy and Dr Bainbridge came up with a new option that I added called apply_join_before_split_to_metafields that's a regex which can list the metadata fields to apply the join_before_split to and whcih previously always got applied to all metadata fields. Now it's applied to any *Keywords metafields by default, as that's the metafield we have experience of that behaves differently to the others, as it stores by word instead of phrases. Tested on Diego's sample PDF. Diego has double-checked it to works on his sample PDF too, setting the split char to ; and turning on the join_before_split and leaving apply_join_before_split_to_metafields at its default of .*Keywords. File changes are strings.properties for the tooltip, the plugin introducing the option and working with it and Kathy's EXIF updates affecting cpan/File and cpan/Image.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • main/trunk/greenstone2/perllib/cpan/Image/ExifTool/XMP.pm

    r24107 r34921  
    2424#               9) http://www.w3.org/TR/SVG11/
    2525#               10) http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf (Oct 2008)
     26#               11) http://www.extensis.com/en/support/kb_article.jsp?articleNumber=6102211
     27#               12) http://www.cipa.jp/std/documents/e/DC-010-2012_E.pdf
     28#               13) http://www.cipa.jp/std/documents/e/DC-010-2017_E.pdf (changed to
     29#                   http://www.cipa.jp/std/documents/e/DC-X010-2017.pdf)
    2630#
    2731# Notes:      - Property qualifiers are handled as if they were separate
     
    4044
    4145use strict;
    42 use vars qw($VERSION $AUTOLOAD @ISA @EXPORT_OK $xlatNamespace %nsURI %dateTimeInfo
    43             %xmpTableDefaults %specialStruct %sDimensions %sArea %sColorant);
     46use vars qw($VERSION $AUTOLOAD @ISA @EXPORT_OK %stdXlatNS %nsURI %latConv %longConv
     47            %dateTimeInfo %xmpTableDefaults %specialStruct %sDimensions %sArea %sColorant);
    4448use Image::ExifTool qw(:Utils);
    4549use Image::ExifTool::Exif;
     50use Image::ExifTool::GPS;
    4651require Exporter;
    4752
    48 $VERSION = '2.37';
     53$VERSION = '3.38';
    4954@ISA = qw(Exporter);
    5055@EXPORT_OK = qw(EscapeXML UnescapeXML);
     
    5257sub ProcessXMP($$;$);
    5358sub WriteXMP($$;$);
    54 sub CheckXMP($$$);
    55 sub ParseXMPElement($$$;$$$);
     59sub CheckXMP($$$;$);
     60sub ParseXMPElement($$$;$$$$);
    5661sub DecodeBase64($);
     62sub EncodeBase64($;$);
    5763sub SaveBlankInfo($$$;$);
    5864sub ProcessBlankInfo($$$;$);
    5965sub ValidateXMP($;$);
    60 sub UnescapeChar($$);
    61 sub AddFlattenedTags($$);
     66sub ValidateProperty($$;$);
     67sub UnescapeChar($$;$);
     68sub AddFlattenedTags($;$$);
    6269sub FormatXMPDate($);
    6370sub ConvertRational($);
    64 
    65 my %curNS;  # namespaces currently in effect while parsing the file
    66 
    67 # lookup for translating to ExifTool namespaces
    68 # Note: Use $xlatNamespace (only valid during processing) to do the translation
    69 my %stdXlatNS = (
     71sub ConvertRationalList($);
     72sub WriteGSpherical($$$);
     73
     74# lookup for translating to ExifTool namespaces (and family 1 group names)
     75%stdXlatNS = (
    7076    # shorten ugly namespace prefixes
    7177    'Iptc4xmpCore' => 'iptcCore',
     
    7480    'MicrosoftPhoto' => 'microsoft',
    7581    'prismusagerights' => 'pur',
    76 );
    77 
    78 # translate ExifTool namespaces to standard XMP namespace prefixes
     82    'GettyImagesGIFT' => 'getty',
     83);
     84
     85# translate ExifTool XMP family 1 group names back to standard XMP namespace prefixes
    7986my %xmpNS = (
    80     # shorten ugly namespace prefixes
    8187    'iptcCore' => 'Iptc4xmpCore',
    8288    'iptcExt' => 'Iptc4xmpExt',
    83     'photomechanic'=> 'photomech',
     89    'photomech'=> 'photomechanic',
    8490    'microsoft' => 'MicrosoftPhoto',
     91    'getty' => 'GettyImagesGIFT',
    8592    # (prism changed their spec to now use 'pur')
    8693    # 'pur' => 'prismusagerights',
    8794);
    8895
    89 # Lookup to translate our namespace prefixes into URI's.  This list need
    90 # not be complete, but it must contain an entry for each namespace prefix
    91 # (NAMESPACE) for writable tags in the XMP tables or in structures
     96# Lookup to translate standard XMP namespace prefixes into URI's.  This list
     97# need not be complete, but it must contain an entry for each namespace prefix
     98# (NAMESPACE) for writable tags in the XMP tables or in structures that doesn't
     99# define a URI.  Also, the namespace must be defined here for non-standard
     100# namespace prefixes to be recognized.
    92101%nsURI = (
    93102    aux       => 'http://ns.adobe.com/exif/1.0/aux/',
    94103    album     => 'http://ns.adobe.com/album/1.0/',
    95104    cc        => 'http://creativecommons.org/ns#', # changed 2007/12/21 - PH
     105    crd       => 'http://ns.adobe.com/camera-raw-defaults/1.0/',
    96106    crs       => 'http://ns.adobe.com/camera-raw-settings/1.0/',
    97107    crss      => 'http://ns.adobe.com/camera-raw-saved-settings/1.0/',
    98108    dc        => 'http://purl.org/dc/elements/1.1/',
    99109    exif      => 'http://ns.adobe.com/exif/1.0/',
     110    exifEX    => 'http://cipa.jp/exif/1.0/',
    100111    iX        => 'http://ns.adobe.com/iX/1.0/',
    101112    pdf       => 'http://ns.adobe.com/pdf/1.3/',
     
    105116    rdfs      => 'http://www.w3.org/2000/01/rdf-schema#',
    106117    stDim     => 'http://ns.adobe.com/xap/1.0/sType/Dimensions#',
    107     stArea    => 'http://ns.adobe.com/xap/1.0/sType/Area#',
    108118    stEvt     => 'http://ns.adobe.com/xap/1.0/sType/ResourceEvent#',
    109119    stFnt     => 'http://ns.adobe.com/xap/1.0/sType/Font#',
     
    127137    dex       => 'http://ns.optimasc.com/dex/1.0/',
    128138    mediapro  => 'http://ns.iview-multimedia.com/mediapro/1.0/',
     139    expressionmedia => 'http://ns.microsoft.com/expressionmedia/1.0/',
    129140    Iptc4xmpCore => 'http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/',
    130141    Iptc4xmpExt => 'http://iptc.org/std/Iptc4xmpExt/2008-02-29/',
     
    136147    lr        => 'http://ns.adobe.com/lightroom/1.0/',
    137148    DICOM     => 'http://ns.adobe.com/DICOM/',
     149   'drone-dji'=> 'http://www.dji.com/drone-dji/1.0/',
    138150    svg       => 'http://www.w3.org/2000/svg',
    139151    et        => 'http://ns.exiftool.ca/1.0/',
    140     # namespaces defined in XMP2.pl:
     152#
     153# namespaces defined in XMP2.pl:
     154#
    141155    plus      => 'http://ns.useplus.org/ldf/xmp/1.0/',
    142     prism     => 'http://prismstandard.org/namespaces/basic/2.1/',
     156    # (prism recommendations from http://www.prismstandard.org/specifications/3.0/Image_Guide_3.0.htm)
     157    prism     => 'http://prismstandard.org/namespaces/basic/2.0/',
    143158    prl       => 'http://prismstandard.org/namespaces/prl/2.1/',
    144159    pur       => 'http://prismstandard.org/namespaces/prismusagerights/2.1/',
     160    pmi       => 'http://prismstandard.org/namespaces/pmi/2.2/',
     161    prm       => 'http://prismstandard.org/namespaces/prm/3.0/',
    145162    acdsee    => 'http://ns.acdsee.com/iptc/1.0/',
    146163    digiKam   => 'http://www.digikam.org/ns/1.0/',
    147     swf       => 'http://ns.adobe.com/swf/1.0',
     164    swf       => 'http://ns.adobe.com/swf/1.0/',
    148165    cell      => 'http://developer.sonyericsson.com/cell/1.0/',
     166    aas       => 'http://ns.apple.com/adjustment-settings/1.0/',
    149167   'mwg-rs'   => 'http://www.metadataworkinggroup.com/schemas/regions/',
    150168   'mwg-kw'   => 'http://www.metadataworkinggroup.com/schemas/keywords/',
    151169   'mwg-coll' => 'http://www.metadataworkinggroup.com/schemas/collections/',
     170    stArea    => 'http://ns.adobe.com/xmp/sType/Area#',
     171    extensis  => 'http://ns.extensis.com/extensis/1.0/',
     172    ics       => 'http://ns.idimager.com/ics/1.0/',
     173    fpv       => 'http://ns.fastpictureviewer.com/fpv/1.0/',
     174    creatorAtom=>'http://ns.adobe.com/creatorAtom/1.0/',
     175   'apple-fi' => 'http://ns.apple.com/faceinfo/1.0/',
     176    GAudio    => 'http://ns.google.com/photos/1.0/audio/',
     177    GImage    => 'http://ns.google.com/photos/1.0/image/',
     178    GPano     => 'http://ns.google.com/photos/1.0/panorama/',
     179    GSpherical=> 'http://ns.google.com/videos/1.0/spherical/',
     180    GDepth    => 'http://ns.google.com/photos/1.0/depthmap/',
     181    GFocus    => 'http://ns.google.com/photos/1.0/focus/',
     182    GCamera   => 'http://ns.google.com/photos/1.0/camera/',
     183    GCreations=> 'http://ns.google.com/photos/1.0/creations/',
     184    dwc       => 'http://rs.tdwg.org/dwc/index.htm',
     185    GettyImagesGIFT => 'http://xmp.gettyimages.com/gift/1.0/',
     186    LImage    => 'http://ns.leiainc.com/photos/1.0/image/',
     187    Profile   => 'http://ns.google.com/photos/dd/1.0/profile/',
    152188);
    153189
    154190# build reverse namespace lookup
    155 my %uri2ns;
     191my %uri2ns = ( 'http://ns.exiftool.org/1.0/' => 'et' ); # (allow exiftool.org as well as exiftool.ca)
    156192{
    157193    my $ns;
     
    162198
    163199# conversions for GPS coordinates
    164 sub ToDegrees
    165 {
    166     require Image::ExifTool::GPS;
    167     Image::ExifTool::GPS::ToDegrees($_[0], 1);
    168 }
    169 my %latConv = (
    170     ValueConv    => \&ToDegrees,
    171     RawConv => 'require Image::ExifTool::GPS; $val', # to load Composite tags and routines
    172     ValueConvInv => q{
    173         require Image::ExifTool::GPS;
    174         Image::ExifTool::GPS::ToDMS($self, $val, 2, "N");
    175     },
     200%latConv = (
     201    ValueConv    => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
     202    ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val, 2, "N")',
    176203    PrintConv    => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
    177     PrintConvInv => \&ToDegrees,
    178 );
    179 my %longConv = (
    180     ValueConv    => \&ToDegrees,
    181     RawConv => 'require Image::ExifTool::GPS; $val',
    182     ValueConvInv => q{
    183         require Image::ExifTool::GPS;
    184         Image::ExifTool::GPS::ToDMS($self, $val, 2, "E");
    185     },
     204    PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
     205);
     206%longConv = (
     207    ValueConv    => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
     208    ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val, 2, "E")',
    186209    PrintConv    => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
    187     PrintConvInv => \&ToDegrees,
     210    PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
    188211);
    189212%dateTimeInfo = (
     
    191214    Writable => 'date',
    192215    Shift => 'Time',
     216    Validate => 'ValidateXMPDate($val)',
    193217    PrintConv => '$self->ConvertDateTime($val)',
    194218    PrintConvInv => '$self->InverseDateTime($val,undef,1)',
     219);
     220
     221# this conversion allows alternate language support for designated boolean tags
     222my %boolConv = (
     223    PrintConv => {
     224        OTHER => sub { # (inverse conversion is the same)
     225            my $val = shift;
     226            return 'False' if lc $val eq 'false';
     227            return 'True' if lc $val eq 'true';
     228            return $val;
     229        },
     230        True => 'True',
     231        False => 'False',
     232    },
    195233);
    196234
     
    198236# (Note: namespaces with non-standard prefixes aren't currently ignored)
    199237my %ignoreNamespace = ( 'x'=>1, rdf=>1, xmlns=>1, xml=>1, svg=>1, et=>1, office=>1 );
     238
     239# XMP properties to ignore (set dynamically via dirInfo IgnoreProp)
     240my %ignoreProp;
    200241
    201242# these are the attributes that we handle for properties that contain
     
    211252    'rdf:nodeID' => 1,
    212253    'et:toolkit' => 1,
    213     'rdf:xmlns' => 1, # this is presumably the default namespace, which we currently ignore
     254    'rdf:xmlns'  => 1, # this is presumably the default namespace, which we currently ignore
     255    'lastUpdate' => [ 'Image::ExifTool::XMP::XML', 'lastUpdate', 'LastUpdate' ], # found in XML from Sony ILCE-7S MP4
    214256);
    215257
     
    222264    TYPE        => 1, # [optional] rdf:type resource for struct (if used, the StructType flag
    223265                      # will be set automatically for all derived flattened tags when writing)
     266    GROUPS      => 1, # [optional] specifies family group 2 name for the structure
    224267);
    225268# XMP structures (each structure is similar to a tag table so we can
     
    252295    # added May 2010
    253296    originalDocumentID => { }, # (undocumented property written by Adobe InDesign)
     297    # added Aug 2016 (INDD again)
     298    lastURL         => { },
     299    linkForm        => { },
     300    linkCategory    => { },
     301    placedXResolution    => { },
     302    placedYResolution    => { },
     303    placedResolutionUnit => { },
    254304);
    255305my %sResourceEvent = (
     
    287337   'format'     => { },
    288338    image       => {
     339        Avoid => 1,
     340        Groups => { 2 => 'Preview' },
    289341        ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)',
    290342        ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)',
     
    299351   'format'     => { },
    300352    image       => {
     353        Groups => { 2 => 'Preview' },
    301354        ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)',
    302355        ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)',
     
    339392    green       => { Writable => 'integer' },
    340393    blue        => { Writable => 'integer' },
     394    gray        => { Writable => 'integer' },
    341395    L           => { Writable => 'real' },
    342396    A           => { Writable => 'integer' },
    343397    B           => { Writable => 'integer' },
     398    # 'tint' observed in INDD sample - PH
     399    tint        => { Writable => 'integer', Notes => 'not part of 2010 XMP specification' },
     400);
     401my %sSwatchGroup = (
     402    STRUCT_NAME => 'SwatchGroup',
     403    NAMESPACE   => 'xmpG',
     404    groupName   => { },
     405    groupType   => { Writable => 'integer' },
     406    Colorants => {
     407        FlatName => 'SwatchColorant',
     408        Struct => \%sColorant,
     409        List => 'Seq',
     410    },
    344411);
    345412my %sFont = (
     
    356423);
    357424my %sOECF = (
     425    STRUCT_NAME => 'OECF',
    358426    NAMESPACE   => 'exif',
    359     STRUCT_NAME => 'OECF',
    360427    Columns     => { Writable => 'integer' },
    361428    Rows        => { Writable => 'integer' },
     
    368435    STRUCT_NAME => 'CorrectionMask',
    369436    NAMESPACE   => 'crs',
    370     What         => { },
    371     MaskValue    => { Writable => 'real' },
    372     Radius       => { Writable => 'real' },
    373     Flow         => { Writable => 'real' },
    374     CenterWeight => { Writable => 'real' },
     437    # disable List behaviour of flattened Gradient/PaintBasedCorrections
     438    # because these are nested in lists and the flattened tags can't
     439    # do justice to this complex structure
     440    What         => { List => 0 },
     441    MaskValue    => { Writable => 'real', List => 0, FlatName => 'Value' },
     442    Radius       => { Writable => 'real', List => 0 },
     443    Flow         => { Writable => 'real', List => 0 },
     444    CenterWeight => { Writable => 'real', List => 0 },
    375445    Dabs         => { List => 'Seq' },
    376     ZeroX        => { Writable => 'real' },
    377     ZeroY        => { Writable => 'real' },
    378     FullX        => { Writable => 'real' },
    379     FullY        => { Writable => 'real' },
     446    ZeroX        => { Writable => 'real', List => 0 },
     447    ZeroY        => { Writable => 'real', List => 0 },
     448    FullX        => { Writable => 'real', List => 0 },
     449    FullY        => { Writable => 'real', List => 0 },
     450    # new elements used in CircularGradientBasedCorrections CorrectionMasks
     451    # and RetouchAreas Masks
     452    Top          => { Writable => 'real', List => 0 },
     453    Left         => { Writable => 'real', List => 0 },
     454    Bottom       => { Writable => 'real', List => 0 },
     455    Right        => { Writable => 'real', List => 0 },
     456    Angle        => { Writable => 'real', List => 0 },
     457    Midpoint     => { Writable => 'real', List => 0 },
     458    Roundness    => { Writable => 'real', List => 0 },
     459    Feather      => { Writable => 'real', List => 0 },
     460    Flipped      => { Writable => 'boolean', List => 0 },
     461    Version      => { Writable => 'integer', List => 0 },
     462    SizeX        => { Writable => 'real', List => 0 },
     463    SizeY        => { Writable => 'real', List => 0 },
     464    X            => { Writable => 'real', List => 0 },
     465    Y            => { Writable => 'real', List => 0 },
     466    Alpha        => { Writable => 'real', List => 0 },
     467    CenterValue  => { Writable => 'real', List => 0 },
     468    PerimeterValue=>{ Writable => 'real', List => 0 },
     469);
     470my %sCorrectionRangeMask = (
     471    STRUCT_NAME => 'CorrectionRangeMask',
     472    NAMESPACE   => 'crs',
     473    Version     => { },
     474    Type        => { },
     475    ColorAmount => { Writable => 'real' },
     476    LumMin      => { Writable => 'real' },
     477    LumMax      => { Writable => 'real' },
     478    LumFeather  => { Writable => 'real' },
     479    DepthMin    => { Writable => 'real' },
     480    DepthMax    => { Writable => 'real' },
     481    DepthFeather=> { Writable => 'real' },
    380482);
    381483my %sCorrection = (
    382484    STRUCT_NAME => 'Correction',
    383485    NAMESPACE   => 'crs',
    384     What => { },
    385     CorrectionAmount => { Writable => 'real' },
    386     CorrectionActive => { Writable => 'boolean' },
    387     LocalExposure    => { Writable => 'real' },
    388     LocalSaturation  => { Writable => 'real' },
    389     LocalContrast    => { Writable => 'real' },
    390     LocalClarity     => { Writable => 'real' },
    391     LocalSharpness   => { Writable => 'real' },
    392     LocalBrightness  => { Writable => 'real' },
    393     LocalToningHue   => { Writable => 'real' },
    394     LocalToningSaturation => { Writable => 'real' },
    395     CorrectionMasks  => { Struct => \%sCorrectionMask, List => 'Seq' },
    396 );
    397 
    398 # IPTC Extension 1.0 structures
    399 my %sLocationDetails = (
    400     NAMESPACE   => 'Iptc4xmpExt',
    401     STRUCT_NAME => 'LocationDetails',
    402     City         => { },
    403     CountryCode  => { },
    404     CountryName  => { },
    405     ProvinceState=> { },
    406     Sublocation  => { },
    407     WorldRegion  => { },
     486    What => { List => 0 },
     487    CorrectionAmount => { FlatName => 'Amount',     Writable => 'real', List => 0 },
     488    CorrectionActive => { FlatName => 'Active',     Writable => 'boolean', List => 0 },
     489    LocalExposure    => { FlatName => 'Exposure',   Writable => 'real', List => 0 },
     490    LocalSaturation  => { FlatName => 'Saturation', Writable => 'real', List => 0 },
     491    LocalContrast    => { FlatName => 'Contrast',   Writable => 'real', List => 0 },
     492    LocalClarity     => { FlatName => 'Clarity',    Writable => 'real', List => 0 },
     493    LocalSharpness   => { FlatName => 'Sharpness',  Writable => 'real', List => 0 },
     494    LocalBrightness  => { FlatName => 'Brightness', Writable => 'real', List => 0 },
     495    LocalToningHue   => { FlatName => 'Hue',        Writable => 'real', List => 0 },
     496    LocalToningSaturation => { FlatName => 'Saturation',        Writable => 'real', List => 0 },
     497    LocalExposure2012     => { FlatName => 'Exposure2012',      Writable => 'real', List => 0 },
     498    LocalContrast2012     => { FlatName => 'Contrast2012',      Writable => 'real', List => 0 },
     499    LocalHighlights2012   => { FlatName => 'Highlights2012',    Writable => 'real', List => 0 },
     500    LocalShadows2012      => { FlatName => 'Shadows2012',       Writable => 'real', List => 0 },
     501    LocalClarity2012      => { FlatName => 'Clarity2012',       Writable => 'real', List => 0 },
     502    LocalLuminanceNoise   => { FlatName => 'LuminanceNoise',    Writable => 'real', List => 0 },
     503    LocalMoire       => { FlatName => 'Moire',      Writable => 'real', List => 0 },
     504    LocalDefringe    => { FlatName => 'Defringe',   Writable => 'real', List => 0 },
     505    LocalTemperature => { FlatName => 'Temperature',Writable => 'real', List => 0 },
     506    LocalTint        => { FlatName => 'Tint',       Writable => 'real', List => 0 },
     507    LocalHue         => { FlatName => 'Hue',        Writable => 'real', List => 0 },
     508    LocalWhites2012  => { FlatName => 'Whites2012', Writable => 'real', List => 0 },
     509    LocalBlacks2012  => { FlatName => 'Blacks2012', Writable => 'real', List => 0 },
     510    LocalDehaze      => { FlatName => 'Dehaze', Writable => 'real', List => 0 },
     511    LocalTexture     => { FlatName => 'Texture', Writable => 'real', List => 0 },
     512    CorrectionRangeMask => {
     513        FlatName => 'RangeMask',
     514        Struct => \%sCorrectionRangeMask,
     515    },
     516    CorrectionMasks  => {
     517        FlatName => 'Mask',
     518        Struct => \%sCorrectionMask,
     519        List => 'Seq',
     520    },
     521);
     522my %sRetouchArea = (
     523    STRUCT_NAME => 'RetouchArea',
     524    NAMESPACE   => 'crs',
     525    SpotType        => { List => 0 },
     526    SourceState     => { List => 0 },
     527    Method          => { List => 0 },
     528    SourceX         => { Writable => 'real',    List => 0 },
     529    OffsetY         => { Writable => 'real',    List => 0 },
     530    Opacity         => { Writable => 'real',    List => 0 },
     531    Feather         => { Writable => 'real',    List => 0 },
     532    Seed            => { Writable => 'integer', List => 0 },
     533    Masks => {
     534        FlatName => 'Mask',
     535        Struct => \%sCorrectionMask,
     536        List => 'Seq',
     537    },
    408538);
    409539
     
    457587        SubDirectory => { TagTable => 'Image::ExifTool::XMP::photoshop' },
    458588    },
     589    crd => {
     590        Name => 'crd',
     591        SubDirectory => { TagTable => 'Image::ExifTool::XMP::crd' },
     592    },
    459593    crs => {
    460594        Name => 'crs',
    461595        SubDirectory => { TagTable => 'Image::ExifTool::XMP::crs' },
    462596    },
    463     # crss - it would be difficult to add the ability to write this
     597    # crss - it would be tedious to add the ability to write this
    464598    aux => {
    465599        Name => 'aux',
     
    474608        SubDirectory => { TagTable => 'Image::ExifTool::XMP::exif' },
    475609    },
     610    exifEX => {
     611        Name => 'exifEX',
     612        SubDirectory => { TagTable => 'Image::ExifTool::XMP::exifEX' },
     613    },
    476614    iptcCore => {
    477615        Name => 'iptcCore',
     
    491629    plus => {
    492630        Name => 'plus',
    493         SubDirectory => { TagTable => 'Image::ExifTool::XMP::plus' },
     631        SubDirectory => { TagTable => 'Image::ExifTool::PLUS::XMP' },
    494632    },
    495633    cc => {
     
    509647        SubDirectory => { TagTable => 'Image::ExifTool::XMP::MediaPro' },
    510648    },
     649    expressionmedia => {
     650        Name => 'expressionmedia',
     651        SubDirectory => { TagTable => 'Image::ExifTool::XMP::ExpressionMedia' },
     652    },
    511653    microsoft => {
    512654        Name => 'microsoft',
     
    545687        SubDirectory => { TagTable => 'Image::ExifTool::XMP::pur' },
    546688    },
     689    pmi => {
     690        Name => 'pmi',
     691        SubDirectory => { TagTable => 'Image::ExifTool::XMP::pmi' },
     692    },
     693    prm => {
     694        Name => 'prm',
     695        SubDirectory => { TagTable => 'Image::ExifTool::XMP::prm' },
     696    },
    547697    rdf => {
    548698        Name => 'rdf',
     
    569719        SubDirectory => { TagTable => 'Image::ExifTool::XMP::cell' },
    570720    },
     721    aas => {
     722        Name => 'aas',
     723        SubDirectory => { TagTable => 'Image::ExifTool::XMP::aas' },
     724    },
    571725   'mwg-rs' => {
    572726        Name => 'mwg-rs',
    573         SubDirectory => { TagTable => 'Image::ExifTool::XMP::mwg_rs' },
     727        SubDirectory => { TagTable => 'Image::ExifTool::MWG::Regions' },
    574728    },
    575729   'mwg-kw' => {
    576730        Name => 'mwg-kw',
    577         SubDirectory => { TagTable => 'Image::ExifTool::XMP::mwg_kw' },
     731        SubDirectory => { TagTable => 'Image::ExifTool::MWG::Keywords' },
    578732    },
    579733   'mwg-coll' => {
    580734        Name => 'mwg-coll',
    581         SubDirectory => { TagTable => 'Image::ExifTool::XMP::mwg_coll' },
     735        SubDirectory => { TagTable => 'Image::ExifTool::MWG::Collections' },
     736    },
     737    extensis => {
     738        Name => 'extensis',
     739        SubDirectory => { TagTable => 'Image::ExifTool::XMP::extensis' },
     740    },
     741    ics => {
     742        Name => 'ics',
     743        SubDirectory => { TagTable => 'Image::ExifTool::XMP::ics' },
     744    },
     745    fpv => {
     746        Name => 'fpv',
     747        SubDirectory => { TagTable => 'Image::ExifTool::XMP::fpv' },
     748    },
     749    creatorAtom => {
     750        Name => 'creatorAtom',
     751        SubDirectory => { TagTable => 'Image::ExifTool::XMP::creatorAtom' },
     752    },
     753   'apple-fi' => {
     754        Name => 'apple-fi',
     755        SubDirectory => { TagTable => 'Image::ExifTool::XMP::apple_fi' },
     756    },
     757    GAudio => {
     758        Name => 'GAudio',
     759        SubDirectory => { TagTable => 'Image::ExifTool::XMP::GAudio' },
     760    },
     761    GImage => {
     762        Name => 'GImage',
     763        SubDirectory => { TagTable => 'Image::ExifTool::XMP::GImage' },
     764    },
     765    GPano => {
     766        Name => 'GPano',
     767        SubDirectory => { TagTable => 'Image::ExifTool::XMP::GPano' },
     768    },
     769    GSpherical => {
     770        Name => 'GSpherical',
     771        SubDirectory => { TagTable => 'Image::ExifTool::XMP::GSpherical' },
     772    },
     773    GDepth => {
     774        Name => 'GDepth',
     775        SubDirectory => { TagTable => 'Image::ExifTool::XMP::GDepth' },
     776    },
     777    GFocus => {
     778        Name => 'GFocus',
     779        SubDirectory => { TagTable => 'Image::ExifTool::XMP::GFocus' },
     780    },
     781    GCamera => {
     782        Name => 'GCamera',
     783        SubDirectory => { TagTable => 'Image::ExifTool::XMP::GCamera' },
     784    },
     785    GCreations => {
     786        Name => 'GCreations',
     787        SubDirectory => { TagTable => 'Image::ExifTool::XMP::GCreations' },
     788    },
     789    dwc => {
     790        Name => 'dwc',
     791        SubDirectory => { TagTable => 'Image::ExifTool::DarwinCore::Main' },
     792    },
     793    getty => {
     794        Name => 'getty',
     795        SubDirectory => { TagTable => 'Image::ExifTool::XMP::GettyImages' },
     796    },
     797   'drone-dji' => {
     798        Name => 'drone-dji',
     799        SubDirectory => { TagTable => 'Image::ExifTool::DJI::XMP' },
     800    },
     801    LImage => {
     802        Name => 'LImage',
     803        SubDirectory => { TagTable => 'Image::ExifTool::XMP::LImage' },
     804    },
     805    Device => {
     806        Name => 'Device',
     807        SubDirectory => { TagTable => 'Image::ExifTool::XMP::Device' },
     808    },
     809);
     810
     811# hack to allow XML containing Dublin Core metadata to be handled like XMP (eg. EPUB - see ZIP.pm)
     812%Image::ExifTool::XMP::XML = (
     813    GROUPS => { 0 => 'XML', 1 => 'XML', 2 => 'Unknown' },
     814    PROCESS_PROC => \&ProcessXMP,
     815    dc => {
     816        Name => 'dc',
     817        SubDirectory => { TagTable => 'Image::ExifTool::XMP::dc' },
     818    },
     819    lastUpdate => {
     820        Groups => { 2 => 'Time' },
     821        ValueConv => 'Image::ExifTool::XMP::ConvertXMPDate($val)',
     822        PrintConv => '$self->ConvertDateTime($val)',
    582823    },
    583824);
    584825
    585826#
    586 # Tag tables for all XMP schemas:
     827# Tag tables for all XMP namespaces:
    587828#
    588829# Writable - only need to define this for writable tags if not plain text
     
    621862        The "x" namespace is used for the "xmpmeta" wrapper, and may contain an
    622863        "xmptk" attribute that is extracted as the XMPToolkit tag.  When writing,
    623         the XMPToolkit tag is automatically generated by ExifTool unless
     864        the XMPToolkit tag is generated automatically by ExifTool unless
    624865        specifically set to another value.
    625866    },
     
    627868);
    628869
    629 # Dublin Core schema properties (dc)
     870# Dublin Core namespace properties (dc)
    630871%Image::ExifTool::XMP::dc = (
    631872    %xmpTableDefaults,
     
    633874    NAMESPACE   => 'dc',
    634875    TABLE_DESC => 'XMP Dublin Core',
    635     NOTES => 'Dublin Core schema tags.',
     876    NOTES => 'Dublin Core namespace tags.',
    636877    contributor => { Groups => { 2 => 'Author' }, List => 'Bag' },
    637878    coverage    => { },
     
    651892);
    652893
    653 # XMP Basic schema properties (xmp, xap)
     894# XMP namespace properties (xmp, xap)
    654895%Image::ExifTool::XMP::xmp = (
    655896    %xmpTableDefaults,
     
    657898    NAMESPACE   => 'xmp',
    658899    NOTES => q{
    659         XMP Basic schema tags.  If the older "xap", "xapBJ", "xapMM" or "xapRights"
     900        XMP namespace tags.  If the older "xap", "xapBJ", "xapMM" or "xapRights"
    660901        namespace prefixes are found, they are translated to the newer "xmp",
    661902        "xmpBJ", "xmpMM" and "xmpRights" prefixes for use in family 1 group names.
    662903    },
    663     Advisory    => { List => 'Bag' }, # (deprecated)
     904    Advisory    => { List => 'Bag', Notes => 'deprecated' },
    664905    BaseURL     => { },
    665906    # (date/time tags not as reliable as EXIF)
     
    672913    Nickname    => { },
    673914    Rating      => { Writable => 'real', Notes => 'a value from 0 to 5, or -1 for "rejected"' },
    674     Thumbnails  => { Struct => \%sThumbnail, List => 'Alt' },
    675     ThumbnailsHeight => { Name => 'ThumbnailHeight', Flat => 1 },
    676     ThumbnailsWidth  => { Name => 'ThumbnailWidth',  Flat => 1 },
    677     ThumbnailsFormat => { Name => 'ThumbnailFormat', Flat => 1 },
    678     ThumbnailsImage  => { Name => 'ThumbnailImage',  Flat => 1, Avoid => 1 },
     915    RatingPercent=>{ Writable => 'real', Avoid => 1, Notes => 'non-standard' },
     916    Thumbnails  => {
     917        FlatName => 'Thumbnail',
     918        Struct => \%sThumbnail,
     919        List => 'Alt',
     920    },
    679921    # the following written by Adobe InDesign, not part of XMP spec:
    680     PageInfo        => { Struct => \%sPageInfo, List => 'Seq' },
    681     PageInfoPageNumber=>{Name => 'PageImagePageNumber', Flat => 1 },
    682     PageInfoHeight  => { Name => 'PageImageHeight', Flat => 1 },
    683     PageInfoWidth   => { Name => 'PageImageWidth',  Flat => 1 },
    684     PageInfoFormat  => { Name => 'PageImageFormat', Flat => 1 },
    685     PageInfoImage   => { Name => 'PageImage',       Flat => 1 },
    686 );
    687 
    688 # XMP Rights Management schema properties (xmpRights, xapRights)
     922    PageInfo        => {
     923        FlatName => 'PageImage',
     924        Struct => \%sPageInfo,
     925        List => 'Seq',
     926    },
     927    PageInfoImage => { Name => 'PageImage', Flat => 1 },
     928    Title       => { Avoid => 1, Notes => 'non-standard', Writable => 'lang-alt' }, #11
     929    Author      => { Avoid => 1, Notes => 'non-standard', Groups => { 2 => 'Author' } }, #11
     930    Keywords    => { Avoid => 1, Notes => 'non-standard' }, #11
     931    Description => { Avoid => 1, Notes => 'non-standard', Writable => 'lang-alt' }, #11
     932    Format      => { Avoid => 1, Notes => 'non-standard' }, #11
     933);
     934
     935# XMP Rights Management namespace properties (xmpRights, xapRights)
    689936%Image::ExifTool::XMP::xmpRights = (
    690937    %xmpTableDefaults,
    691938    GROUPS => { 1 => 'XMP-xmpRights', 2 => 'Author' },
    692939    NAMESPACE   => 'xmpRights',
    693     NOTES => 'XMP Rights Management schema tags.',
     940    NOTES => 'XMP Rights Management namespace tags.',
    694941    Certificate     => { },
    695942    Marked          => { Writable => 'boolean' },
     
    699946);
    700947
    701 # XMP Note schema properties (xmpNote)
     948# XMP Note namespace properties (xmpNote)
    702949%Image::ExifTool::XMP::xmpNote = (
    703950    %xmpTableDefaults,
    704951    GROUPS => { 1 => 'XMP-xmpNote' },
    705952    NAMESPACE   => 'xmpNote',
    706     NOTES => 'XMP Note schema tags.',
    707     HasExtendedXMP => { Writable => 'boolean', Protected => 2 },
     953    NOTES => 'XMP Note namespace tags.',
     954    HasExtendedXMP => {
     955        Notes => q{
     956            this tag is protected so it is not writable directly.  Instead, it is set
     957            automatically to the GUID of the extended XMP when writing extended XMP to a
     958            JPEG image
     959        },
     960        Protected => 2,
     961    },
    708962);
    709963
    710964# XMP xmpMM ManifestItem struct (ref PH, written by Adobe PDF library 8.0)
    711965my %sManifestItem = (
    712     NAMESPACE => 'stMfs',
    713966    STRUCT_NAME => 'ManifestItem',
     967    NAMESPACE   => 'stMfs',
    714968    linkForm            => { },
    715969    placedXResolution   => { Namespace => 'xmpMM', Writable => 'real' },
     
    721975# the xmpMM Pantry
    722976my %sPantryItem = (
    723     NAMESPACE => undef, # stores any top-level XMP tags
    724977    STRUCT_NAME => 'PantryItem',
     978    NAMESPACE   => undef, # stores any top-level XMP tags
    725979    NOTES => q{
    726980        This structure must have an InstanceID field, but may also contain any other
    727981        XMP properties.
    728982    },
    729     InstanceID => { Namespace => 'xmpMM' },
    730 );
    731 
    732 # XMP Media Management schema properties (xmpMM, xapMM)
     983    InstanceID => { Namespace => 'xmpMM', List => 0 },
     984);
     985
     986# XMP Media Management namespace properties (xmpMM, xapMM)
    733987%Image::ExifTool::XMP::xmpMM = (
    734988    %xmpTableDefaults,
     
    736990    NAMESPACE   => 'xmpMM',
    737991    TABLE_DESC => 'XMP Media Management',
    738     NOTES => 'XMP Media Management schema tags.',
     992    NOTES => 'XMP Media Management namespace tags.',
    739993    DerivedFrom     => { Struct => \%sResourceRef },
    740994    DocumentID      => { },
     
    7591013    RenditionOf     => { Struct => \%sResourceRef }, # (deprecated)
    7601014    SaveID          => { Writable => 'integer' }, # (deprecated)
    761 );
    762 
    763 # XMP Basic Job Ticket schema properties (xmpBJ, xapBJ)
     1015    subject         => { List => 'Seq', Avoid => 1, Notes => 'undocumented' },
     1016);
     1017
     1018# XMP Basic Job Ticket namespace properties (xmpBJ, xapBJ)
    7641019%Image::ExifTool::XMP::xmpBJ = (
    7651020    %xmpTableDefaults,
     
    7671022    NAMESPACE   => 'xmpBJ',
    7681023    TABLE_DESC => 'XMP Basic Job Ticket',
    769     NOTES => 'XMP Basic Job Ticket schema tags.',
     1024    NOTES => 'XMP Basic Job Ticket namespace tags.',
    7701025    # Note: JobRef is a List of structures.  To accomplish this, we set the XMP
    7711026    # List=>'Bag', but since SubDirectory is defined, this tag isn't writable
     
    7751030);
    7761031
    777 # XMP Paged-Text schema properties (xmpTPg)
     1032# XMP Paged-Text namespace properties (xmpTPg)
    7781033%Image::ExifTool::XMP::xmpTPg = (
    7791034    %xmpTableDefaults,
     
    7811036    NAMESPACE   => 'xmpTPg',
    7821037    TABLE_DESC => 'XMP Paged-Text',
    783     NOTES => 'XMP Paged-Text schema tags.',
     1038    NOTES => 'XMP Paged-Text namespace tags.',
    7841039    MaxPageSize         => { Struct => \%sDimensions },
    7851040    NPages              => { Writable => 'integer' },
    786     Fonts               => { Struct => \%sFont, List => 'Bag' },
    787     FontsFontName       => { Flat => 1, Name => 'FontName' },
    788     FontsFontFamily     => { Flat => 1, Name => 'FontFamily' },
    789     FontsFontFace       => { Flat => 1, Name => 'FontFace' },
    790     FontsFontType       => { Flat => 1, Name => 'FontType' },
    791     FontsVersionString  => { Flat => 1, Name => 'FontVersion' },
    792     FontsComposite      => { Flat => 1, Name => 'FontComposite' },
    793     FontsFontFileName   => { Flat => 1, Name => 'FontFileName' },
    794     FontsChildFontFiles => { Flat => 1, Name => 'ChildFontFiles' },
    795     Colorants           => { Struct => \%sColorant, List => 'Seq' },
    796     ColorantsSwatchName => { Flat => 1, Name => 'ColorantSwatchName' },
    797     ColorantsMode       => { Flat => 1, Name => 'ColorantMode' },
    798     ColorantsType       => { Flat => 1, Name => 'ColorantType' },
    799     ColorantsCyan       => { Flat => 1, Name => 'ColorantCyan' },
    800     ColorantsMagenta    => { Flat => 1, Name => 'ColorantMagenta' },
    801     ColorantsYellow     => { Flat => 1, Name => 'ColorantYellow' },
    802     ColorantsBlack      => { Flat => 1, Name => 'ColorantBlack' },
    803     ColorantsRed        => { Flat => 1, Name => 'ColorantRed' },
    804     ColorantsGreen      => { Flat => 1, Name => 'ColorantGreen' },
    805     ColorantsBlue       => { Flat => 1, Name => 'ColorantBlue' },
    806     ColorantsL          => { Flat => 1, Name => 'ColorantL' },
    807     ColorantsA          => { Flat => 1, Name => 'ColorantA' },
    808     ColorantsB          => { Flat => 1, Name => 'ColorantB' },
     1041    Fonts               => {
     1042        FlatName => '',
     1043        Struct => \%sFont,
     1044        List => 'Bag',
     1045    },
     1046    FontsVersionString  => { Name => 'FontVersion',     Flat => 1 },
     1047    FontsComposite      => { Name => 'FontComposite',   Flat => 1 },
     1048    Colorants           => {
     1049        FlatName => 'Colorant',
     1050        Struct => \%sColorant,
     1051        List => 'Seq',
     1052    },
    8091053    PlateNames          => { List => 'Seq' },
    810 );
    811 
    812 # PDF schema properties (pdf)
     1054    # the following found in an AI file:
     1055    HasVisibleTransparency => { Writable => 'boolean' },
     1056    HasVisibleOverprint    => { Writable => 'boolean' },
     1057    SwatchGroups => {
     1058        Struct => \%sSwatchGroup,
     1059        List => 'Seq',
     1060    },
     1061    SwatchGroupsColorants => { Name => 'SwatchGroupsColorants', Flat => 1 },
     1062    SwatchGroupsGroupName => { Name => 'SwatchGroupName',       Flat => 1 },
     1063    SwatchGroupsGroupType => { Name => 'SwatchGroupType',       Flat => 1 },
     1064);
     1065
     1066# PDF namespace properties (pdf)
    8131067%Image::ExifTool::XMP::pdf = (
    8141068    %xmpTableDefaults,
     
    8171071    TABLE_DESC => 'XMP PDF',
    8181072    NOTES => q{
    819         Adobe PDF schema tags.  The official XMP specification defines only
     1073        Adobe PDF namespace tags.  The official XMP specification defines only
    8201074        Keywords, PDFVersion, Producer and Trapped.  The other tags are included
    8211075        because they have been observed in PDF files, but some are avoided when
     
    8361090        PrintConv => { True => 'True', False => 'False', Unknown => 'Unknown' },
    8371091    },
    838     Keywords    => { },
     1092    Keywords    => { Priority => -1 }, # (-1 to get below Priority 0 PDF:Keywords)
    8391093    PDFVersion  => { },
    8401094    Producer    => { Groups => { 2 => 'Author' } },
    8411095);
    8421096
    843 # PDF extension schema properties (pdfx)
     1097# PDF extension namespace properties (pdfx)
    8441098%Image::ExifTool::XMP::pdfx = (
    8451099    %xmpTableDefaults,
     
    8531107);
    8541108
    855 # Photoshop schema properties (photoshop)
     1109# Photoshop namespace properties (photoshop)
    8561110%Image::ExifTool::XMP::photoshop = (
    8571111    %xmpTableDefaults,
     
    8591113    NAMESPACE   => 'photoshop',
    8601114    TABLE_DESC => 'XMP Photoshop',
    861     NOTES => 'Adobe Photoshop schema tags.',
     1115    NOTES => 'Adobe Photoshop namespace tags.',
    8621116    AuthorsPosition => { Groups => { 2 => 'Author' } },
    8631117    CaptionWriter   => { Groups => { 2 => 'Author' } },
     
    8821136    DateCreated     => { Groups => { 2 => 'Time' }, %dateTimeInfo },
    8831137    DocumentAncestors => {
    884         List => 'bag',
    885         Struct => {
    886             STRUCT_NAME => 'Ancestor',
    887             NAMESPACE   => 'photoshop',
    888             AncestorID => { },
    889         },
    890     },
    891     DocumentAncestorsAncestorID => { Name => 'DocumentAncestorID', Flat => 1 },
     1138        List => 'Bag',
     1139        # Contrary to their own XMP specification, Adobe writes this as a simple Bag
     1140        # of strings instead of structures, so comment out the structure definition...
     1141        # FlatName => 'Document',
     1142        # Struct => {
     1143        #     STRUCT_NAME => 'Ancestor',
     1144        #     NAMESPACE   => 'photoshop',
     1145        #     AncestorID  => { },
     1146        # },
     1147    },
    8921148    Headline        => { },
    8931149    History         => { }, #PH (CS3)
     
    9041160    SupplementalCategories  => { List => 'Bag' },
    9051161    TextLayers => {
    906         List => 'seq',
     1162        FlatName => 'Text',
     1163        List => 'Seq',
    9071164        Struct => {
    9081165            STRUCT_NAME => 'Layer',
    9091166            NAMESPACE   => 'photoshop',
    910             LayerName => { },
     1167            LayerName   => { },
    9111168            LayerText => { },
    9121169        },
    9131170    },
    914     TextLayersLayerName => { Flat => 1, Name => 'TextLayerName' },
    915     TextLayersLayerText => { Flat => 1, Name => 'TextLayerText' },
    916     TransmissionReference   => { },
     1171    TransmissionReference => { Notes => 'Now used as a job identifier' },
    9171172    Urgency         => {
    9181173        Writable => 'integer',
     
    9311186        },
    9321187    },
    933 );
    934 
    935 # Photoshop Camera Raw Schema properties (crs) - (ref 8,PH)
     1188    EmbeddedXMPDigest => { },   #PH (LR5)
     1189);
     1190
     1191# Photoshop Camera Raw namespace properties (crs) - (ref 8,PH)
    9361192%Image::ExifTool::XMP::crs = (
    9371193    %xmpTableDefaults,
    9381194    GROUPS => { 1 => 'XMP-crs', 2 => 'Image' },
    9391195    NAMESPACE   => 'crs',
    940     TABLE_DESC => 'Photoshop Camera Raw Schema',
    941     NOTES => 'Photoshop Camera Raw Schema tags.',
     1196    TABLE_DESC => 'Photoshop Camera Raw namespace',
     1197    NOTES => q{
     1198        Photoshop Camera Raw namespace tags.  It is a shame that Adobe pollutes the
     1199        metadata space with these incredibly bulky image editing parameters.
     1200    },
    9421201    AlreadyApplied  => { Writable => 'boolean' }, #PH (written by LightRoom beta 4.1)
    9431202    AutoBrightness  => { Writable => 'boolean' },
     
    9841243    Sharpness       => { Writable => 'integer', Avoid => 1 },
    9851244    Smoothness      => { Writable => 'integer' },
    986     Temperature     => { Writable => 'integer', Avoid => 1, Name => 'ColorTemperature' },
     1245    Temperature     => { Writable => 'integer', Name => 'ColorTemperature' },
    9871246    Tint            => { Writable => 'integer' },
    9881247    ToneCurve       => { List => 'Seq' },
     
    10551314    SharpenEdgeMasking          => { Writable => 'integer' },
    10561315    SharpenRadius               => { Writable => 'real' },
    1057     SplitToningBalance          => { Writable => 'integer' },
    1058     SplitToningHighlightHue     => { Writable => 'integer' },
    1059     SplitToningHighlightSaturation => { Writable => 'integer' },
    1060     SplitToningShadowHue        => { Writable => 'integer' },
    1061     SplitToningShadowSaturation => { Writable => 'integer' },
     1316    SplitToningBalance          => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' },
     1317    SplitToningHighlightHue     => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' },
     1318    SplitToningHighlightSaturation => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' },
     1319    SplitToningShadowHue        => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' },
     1320    SplitToningShadowSaturation => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' },
    10621321    Vibrance                    => { Writable => 'integer' },
    10631322    # new tags written by LR 1.4 (not sure in what version they first appeared)
     
    10901349    # because these are nested in lists and the flattened tags can't
    10911350    # do justice to this complex structure
    1092     GradientBasedCorrections => { Struct => \%sCorrection, List => 'Seq' },
    1093     GradientBasedCorrectionsWhat => {
    1094         Name => 'GradientBasedCorrWhat',
    1095         Flat => 1, List => 0,
    1096     },
    1097     GradientBasedCorrectionsCorrectionAmount => {
    1098         Name => 'GradientBasedCorrAmount',
    1099         Flat => 1, List => 0,
    1100     },
    1101     GradientBasedCorrectionsCorrectionActive => {
    1102         Name => 'GradientBasedCorrActive',
    1103         Flat => 1, List => 0,
    1104     },
    1105     GradientBasedCorrectionsLocalExposure => {
    1106         Name => 'GradientBasedCorrExposure',
    1107         Flat => 1, List => 0,
    1108     },
    1109     GradientBasedCorrectionsLocalSaturation => {
    1110         Name => 'GradientBasedCorrSaturation',
    1111         Flat => 1, List => 0,
    1112     },
    1113     GradientBasedCorrectionsLocalContrast => {
    1114         Name => 'GradientBasedCorrContrast',
    1115         Flat => 1, List => 0,
    1116     },
    1117     GradientBasedCorrectionsLocalClarity => {
    1118         Name => 'GradientBasedCorrClarity',
    1119         Flat => 1, List => 0,
    1120     },
    1121     GradientBasedCorrectionsLocalSharpness => {
    1122         Name => 'GradientBasedCorrSharpness',
    1123         Flat => 1, List => 0,
    1124     },
    1125     GradientBasedCorrectionsLocalBrightness => {
    1126         Name => 'GradientBasedCorrBrightness',
    1127         Flat => 1, List => 0,
    1128     },
    1129     GradientBasedCorrectionsLocalToningHue => {
    1130         Name => 'GradientBasedCorrHue',
    1131         Flat => 1, List => 0,
    1132     },
    1133     GradientBasedCorrectionsLocalToningSaturation => {
    1134         Name => 'GradientBasedCorrSaturation',
    1135         Flat => 1, List => 0,
     1351    GradientBasedCorrections => {
     1352        FlatName => 'GradientBasedCorr',
     1353        Struct => \%sCorrection,
     1354        List => 'Seq',
    11361355    },
    11371356    GradientBasedCorrectionsCorrectionMasks => {
    11381357        Name => 'GradientBasedCorrMasks',
     1358        FlatName => 'GradientBasedCorrMask',
    11391359        Flat => 1
    1140     },
    1141     GradientBasedCorrectionsCorrectionMasksWhat => {
    1142         Name => 'GradientBasedCorrMaskWhat',
    1143         Flat => 1, List => 0,
    1144     },
    1145     GradientBasedCorrectionsCorrectionMasksMaskValue => {
    1146         Name => 'GradientBasedCorrMaskValue',
    1147         Flat => 1, List => 0,
    1148     },
    1149     GradientBasedCorrectionsCorrectionMasksRadius => {
    1150         Name => 'GradientBasedCorrMaskRadius',
    1151         Flat => 1, List => 0,
    1152     },
    1153     GradientBasedCorrectionsCorrectionMasksFlow => {
    1154         Name => 'GradientBasedCorrMaskFlow',
    1155         Flat => 1, List => 0,
    1156     },
    1157     GradientBasedCorrectionsCorrectionMasksCenterWeight => {
    1158         Name => 'GradientBasedCorrMaskCenterWeight',
    1159         Flat => 1, List => 0,
    11601360    },
    11611361    GradientBasedCorrectionsCorrectionMasksDabs => {
     
    11631363        Flat => 1, List => 0,
    11641364    },
    1165     GradientBasedCorrectionsCorrectionMasksZeroX => {
    1166         Name => 'GradientBasedCorrMaskZeroX',
    1167         Flat => 1, List => 0,
    1168     },
    1169     GradientBasedCorrectionsCorrectionMasksZeroY => {
    1170         Name => 'GradientBasedCorrMaskZeroY',
    1171         Flat => 1, List => 0,
    1172     },
    1173     GradientBasedCorrectionsCorrectionMasksFullX => {
    1174         Name => 'GradientBasedCorrMaskFullX',
    1175         Flat => 1, List => 0,
    1176     },
    1177     GradientBasedCorrectionsCorrectionMasksFullY => {
    1178         Name => 'GradientBasedCorrMaskFullY',
    1179         Flat => 1, List => 0,
    1180     },
    1181     PaintBasedCorrections => { Struct => \%sCorrection, List => 'Seq' },
    1182     PaintBasedCorrectionsWhat => {
    1183         Name => 'PaintCorrectionWhat',
    1184         Flat => 1, List => 0,
    1185     },
    1186     PaintBasedCorrectionsCorrectionAmount => {
    1187         Name => 'PaintCorrectionAmount',
    1188         Flat => 1, List => 0,
    1189     },
    1190     PaintBasedCorrectionsCorrectionActive => {
    1191         Name => 'PaintCorrectionActive',
    1192         Flat => 1, List => 0,
    1193     },
    1194     PaintBasedCorrectionsLocalExposure => {
    1195         Name => 'PaintCorrectionExposure',
    1196         Flat => 1, List => 0,
    1197     },
    1198     PaintBasedCorrectionsLocalSaturation => {
    1199         Name => 'PaintCorrectionSaturation',
    1200         Flat => 1, List => 0,
    1201     },
    1202     PaintBasedCorrectionsLocalContrast => {
    1203         Name => 'PaintCorrectionContrast',
    1204         Flat => 1, List => 0,
    1205     },
    1206     PaintBasedCorrectionsLocalClarity => {
    1207         Name => 'PaintCorrectionClarity',
    1208         Flat => 1, List => 0,
    1209     },
    1210     PaintBasedCorrectionsLocalSharpness => {
    1211         Name => 'PaintCorrectionSharpness',
    1212         Flat => 1, List => 0,
    1213     },
    1214     PaintBasedCorrectionsLocalBrightness => {
    1215         Name => 'PaintCorrectionBrightness',
    1216         Flat => 1, List => 0,
    1217     },
    1218     PaintBasedCorrectionsLocalToningHue => {
    1219         Name => 'PaintCorrectionHue',
    1220         Flat => 1, List => 0,
    1221     },
    1222     PaintBasedCorrectionsLocalToningSaturation => {
    1223         Name => 'PaintCorrectionSaturation',
    1224         Flat => 1, List => 0,
     1365    PaintBasedCorrections => {
     1366        FlatName => 'PaintCorrection',
     1367        Struct => \%sCorrection,
     1368        List => 'Seq',
    12251369    },
    12261370    PaintBasedCorrectionsCorrectionMasks => {
    12271371        Name => 'PaintBasedCorrectionMasks',
     1372        FlatName => 'PaintCorrectionMask',
    12281373        Flat => 1,
    1229     },
    1230     PaintBasedCorrectionsCorrectionMasksWhat => {
    1231         Name => 'PaintCorrectionMaskWhat',
    1232         Flat => 1, List => 0,
    1233     },
    1234     PaintBasedCorrectionsCorrectionMasksMaskValue => {
    1235         Name => 'PaintCorrectionMaskValue',
    1236         Flat => 1, List => 0,
    1237     },
    1238     PaintBasedCorrectionsCorrectionMasksRadius => {
    1239         Name => 'PaintCorrectionMaskRadius',
    1240         Flat => 1, List => 0,
    1241     },
    1242     PaintBasedCorrectionsCorrectionMasksFlow => {
    1243         Name => 'PaintCorrectionMaskFlow',
    1244         Flat => 1, List => 0,
    1245     },
    1246     PaintBasedCorrectionsCorrectionMasksCenterWeight => {
    1247         Name => 'PaintCorrectionMaskCenterWeight',
    1248         Flat => 1, List => 0,
    12491374    },
    12501375    PaintBasedCorrectionsCorrectionMasksDabs => {
     
    12521377        Flat => 1, List => 0,
    12531378    },
    1254     PaintBasedCorrectionsCorrectionMasksZeroX => {
    1255         Name => 'PaintCorrectionMaskZeroX',
     1379    # new tags written by LR 3 (thanks Wolfgang Guelcker)
     1380    ProcessVersion                      => { },
     1381    LensProfileEnable                   => { Writable => 'integer' },
     1382    LensProfileSetup                    => { },
     1383    LensProfileName                     => { },
     1384    LensProfileFilename                 => { },
     1385    LensProfileDigest                   => { },
     1386    LensProfileDistortionScale          => { Writable => 'integer' },
     1387    LensProfileChromaticAberrationScale => { Writable => 'integer' },
     1388    LensProfileVignettingScale          => { Writable => 'integer' },
     1389    LensManualDistortionAmount          => { Writable => 'integer' },
     1390    PerspectiveVertical                 => { Writable => 'integer' },
     1391    PerspectiveHorizontal               => { Writable => 'integer' },
     1392    PerspectiveRotate                   => { Writable => 'real'    },
     1393    PerspectiveScale                    => { Writable => 'integer' },
     1394    CropConstrainToWarp                 => { Writable => 'integer' },
     1395    LuminanceNoiseReductionDetail       => { Writable => 'integer' },
     1396    LuminanceNoiseReductionContrast     => { Writable => 'integer' },
     1397    ColorNoiseReductionDetail           => { Writable => 'integer' },
     1398    GrainAmount                         => { Writable => 'integer' },
     1399    GrainSize                           => { Writable => 'integer' },
     1400    GrainFrequency                      => { Writable => 'integer' },
     1401    # new tags written by LR4
     1402    AutoLateralCA                       => { Writable => 'integer' },
     1403    Exposure2012                        => { Writable => 'real' },
     1404    Contrast2012                        => { Writable => 'integer' },
     1405    Highlights2012                      => { Writable => 'integer' },
     1406    Highlight2012                       => { Writable => 'integer' }, # (written by Nikon software)
     1407    Shadows2012                         => { Writable => 'integer' },
     1408    Whites2012                          => { Writable => 'integer' },
     1409    Blacks2012                          => { Writable => 'integer' },
     1410    Clarity2012                         => { Writable => 'integer' },
     1411    PostCropVignetteHighlightContrast   => { Writable => 'integer' },
     1412    ToneCurveName2012                   => { },
     1413    ToneCurveRed                        => { List => 'Seq' },
     1414    ToneCurveGreen                      => { List => 'Seq' },
     1415    ToneCurveBlue                       => { List => 'Seq' },
     1416    ToneCurvePV2012                     => { List => 'Seq' },
     1417    ToneCurvePV2012Red                  => { List => 'Seq' },
     1418    ToneCurvePV2012Green                => { List => 'Seq' },
     1419    ToneCurvePV2012Blue                 => { List => 'Seq' },
     1420    DefringePurpleAmount                => { Writable => 'integer' },
     1421    DefringePurpleHueLo                 => { Writable => 'integer' },
     1422    DefringePurpleHueHi                 => { Writable => 'integer' },
     1423    DefringeGreenAmount                 => { Writable => 'integer' },
     1424    DefringeGreenHueLo                  => { Writable => 'integer' },
     1425    DefringeGreenHueHi                  => { Writable => 'integer' },
     1426    # new tags written by LR5
     1427    AutoWhiteVersion                    => { Writable => 'integer' },
     1428    CircularGradientBasedCorrections => {
     1429        FlatName => 'CircGradBasedCorr',
     1430        Struct => \%sCorrection,
     1431        List => 'Seq',
     1432    },
     1433    CircularGradientBasedCorrectionsCorrectionMasks => {
     1434        Name => 'CircGradBasedCorrMasks',
     1435        FlatName => 'CircGradBasedCorrMask',
     1436        Flat => 1
     1437    },
     1438    CircularGradientBasedCorrectionsCorrectionMasksDabs => {
     1439        Name => 'CircGradBasedCorrMaskDabs',
    12561440        Flat => 1, List => 0,
    12571441    },
    1258     PaintBasedCorrectionsCorrectionMasksZeroY => {
    1259         Name => 'PaintCorrectionMaskZeroY',
     1442    ColorNoiseReductionSmoothness       => { Writable => 'integer' },
     1443    PerspectiveAspect                   => { Writable => 'integer' },
     1444    PerspectiveUpright                  => { Writable => 'integer' },
     1445    RetouchAreas => {
     1446        FlatName => 'RetouchArea',
     1447        Struct => \%sRetouchArea,
     1448        List => 'Seq',
     1449    },
     1450    RetouchAreasMasks => {
     1451        Name => 'RetouchAreaMasks',
     1452        FlatName => 'RetouchAreaMask',
     1453        Flat => 1
     1454    },
     1455    RetouchAreasMasksDabs => {
     1456        Name => 'RetouchAreaMaskDabs',
    12601457        Flat => 1, List => 0,
    12611458    },
    1262     PaintBasedCorrectionsCorrectionMasksFullX => {
    1263         Name => 'PaintCorrectionMaskFullX',
    1264         Flat => 1, List => 0,
    1265     },
    1266     PaintBasedCorrectionsCorrectionMasksFullY => {
    1267         Name => 'PaintCorrectionMaskFullY',
    1268         Flat => 1, List => 0,
    1269     },
    1270     # new tags written by LR 3 (thanks Wolfgang Guelcker)
    1271     ProcessVersion                       => { },
    1272     LensProfileEnable                    => { Writable => 'integer' },
    1273     LensProfileSetup                     => { },
    1274     LensProfileName                      => { },
    1275     LensProfileFilename                  => { },
    1276     LensProfileDigest                    => { },
    1277     LensProfileDistortionScale           => { Writable => 'integer' },
    1278     LensProfileChromaticAberrationScale  => { Writable => 'integer' },
    1279     LensProfileVignettingScale           => { Writable => 'integer' },
    1280     LensManualDistortionAmount           => { Writable => 'integer' },
    1281     PerspectiveVertical                  => { Writable => 'integer' },
    1282     PerspectiveHorizontal                => { Writable => 'integer' },
    1283     PerspectiveRotate                    => { Writable => 'real'    },
    1284     PerspectiveScale                     => { Writable => 'integer' },
    1285     CropConstrainToWarp                  => { Writable => 'integer' },     
    1286     LuminanceNoiseReductionDetail        => { Writable => 'integer' },
    1287     LuminanceNoiseReductionContrast      => { Writable => 'integer' },
    1288     ColorNoiseReductionDetail            => { Writable => 'integer' },
    1289     GrainAmount                          => { Writable => 'integer' },
    1290     GrainSize                            => { Writable => 'integer' },
    1291     GrainFrequency                       => { Writable => 'integer' },
    1292 );
    1293 
    1294 # Tiff schema properties (tiff)
     1459    UprightVersion                      => { Writable => 'integer' },
     1460    UprightCenterMode                   => { Writable => 'integer' },
     1461    UprightCenterNormX                  => { Writable => 'real' },
     1462    UprightCenterNormY                  => { Writable => 'real' },
     1463    UprightFocalMode                    => { Writable => 'integer' },
     1464    UprightFocalLength35mm              => { Writable => 'real' },
     1465    UprightPreview                      => { Writable => 'boolean' },
     1466    UprightTransformCount               => { Writable => 'integer' },
     1467    UprightDependentDigest              => { },
     1468    UprightTransform_0                  => { },
     1469    UprightTransform_1                  => { },
     1470    UprightTransform_2                  => { },
     1471    UprightTransform_3                  => { },
     1472    UprightTransform_4                  => { },
     1473    UprightTransform_5                  => { },
     1474    # more stuff seen in lens profile file (unknown source)
     1475    What => { }, # (with value "LensProfileDefaultSettings")
     1476    LensProfileMatchKeyExifMake         => { },
     1477    LensProfileMatchKeyExifModel        => { },
     1478    LensProfileMatchKeyCameraModelName  => { },
     1479    LensProfileMatchKeyLensInfo         => { },
     1480    LensProfileMatchKeyLensID           => { },
     1481    LensProfileMatchKeyLensName         => { },
     1482    LensProfileMatchKeyIsRaw            => { Writable => 'boolean' },
     1483    LensProfileMatchKeySensorFormatFactor=>{ Writable => 'real' },
     1484    # more stuff (ref forum6993)
     1485    DefaultAutoTone                     => { Writable => 'boolean' },
     1486    DefaultAutoGray                     => { Writable => 'boolean' },
     1487    DefaultsSpecificToSerial            => { Writable => 'boolean' },
     1488    DefaultsSpecificToISO               => { Writable => 'boolean' },
     1489    DNGIgnoreSidecars                   => { Writable => 'boolean' },
     1490    NegativeCachePath                   => { },
     1491    NegativeCacheMaximumSize            => { Writable => 'real' },
     1492    NegativeCacheLargePreviewSize       => { Writable => 'integer' },
     1493    JPEGHandling                        => { },
     1494    TIFFHandling                        => { },
     1495    Dehaze                              => { Writable => 'real' },
     1496    ToneMapStrength                     => { Writable => 'real' },
     1497    # yet more
     1498    PerspectiveX                        => { Writable => 'real' },
     1499    PerspectiveY                        => { Writable => 'real' },
     1500    UprightFourSegmentsCount            => { Writable => 'integer' },
     1501    AutoTone                            => { Writable => 'boolean' },
     1502    Texture                             => { Writable => 'integer' },
     1503    # more stuff (ref forum10721)
     1504    OverrideLookVignette                => { Writable => 'boolean' },
     1505    Look => {
     1506        Struct => {
     1507            STRUCT_NAME => 'Look',
     1508            NAMESPACE   => 'crs',
     1509            Name        => { },
     1510            Amount      => { },
     1511            Cluster     => { },
     1512            UUID        => { },
     1513            SupportsMonochrome => { },
     1514            SupportsAmount     => { },
     1515            SupportsOutputReferred => { },
     1516            Copyright   => { },
     1517            Group       => { Writable => 'lang-alt' },
     1518            Parameters => {
     1519                Struct => {
     1520                    STRUCT_NAME => 'LookParms',
     1521                    NAMESPACE   => 'crs',
     1522                    Version         => { },
     1523                    ProcessVersion  => { },
     1524                    Clarity2012     => { },
     1525                    ConvertToGrayscale => { },
     1526                    CameraProfile   => { },
     1527                    LookTable       => { },
     1528                    ToneCurvePV2012 => { List => 'Seq' },
     1529                },
     1530            },
     1531        }
     1532    },
     1533    # more again (ref forum11258)
     1534    GrainSeed => { },
     1535    ClipboardOrientation => { Writable => 'integer' },
     1536    ClipboardAspectRatio => { Writable => 'integer' },
     1537    PresetType  => { },
     1538    Cluster     => { },
     1539    UUID        => { Avoid => 1 },
     1540    SupportsAmount          => { Writable => 'boolean' },
     1541    SupportsColor           => { Writable => 'boolean' },
     1542    SupportsMonochrome      => { Writable => 'boolean' },
     1543    SupportsHighDynamicRange=> { Writable => 'boolean' },
     1544    SupportsNormalDynamicRange=> { Writable => 'boolean' },
     1545    SupportsSceneReferred   => { Writable => 'boolean' },
     1546    SupportsOutputReferred  => { Writable => 'boolean' },
     1547    CameraModelRestriction  => { },
     1548    Copyright   => { Avoid => 1 },
     1549    ContactInfo => { },
     1550    GrainSeed   => { Writable => 'integer' },
     1551    Name        => { Writable => 'lang-alt', Avoid => 1 },
     1552    ShortName   => { Writable => 'lang-alt' },
     1553    SortName    => { Writable => 'lang-alt' },
     1554    Group       => { Writable => 'lang-alt', Avoid => 1 },
     1555    Description => { Writable => 'lang-alt', Avoid => 1 },
     1556    # new for DNG converter 13.0
     1557    LookName => { NotFlat => 1 }, # (grr... conflicts with "Name" element of "Look" struct!)
     1558    # new for Lightroom CC 2021 (ref forum11745)
     1559    ColorGradeMidtoneHue    => { Writable => 'integer' },
     1560    ColorGradeMidtoneSat    => { Writable => 'integer' },
     1561    ColorGradeShadowLum     => { Writable => 'integer' },
     1562    ColorGradeMidtoneLum    => { Writable => 'integer' },
     1563    ColorGradeHighlightLum  => { Writable => 'integer' },
     1564    ColorGradeBlending      => { Writable => 'integer' },
     1565    ColorGradeGlobalHue     => { Writable => 'integer' },
     1566    ColorGradeGlobalSat     => { Writable => 'integer' },
     1567    ColorGradeGlobalLum     => { Writable => 'integer' },
     1568    # new for Adobe Camera Raw 13 (ref forum11745)
     1569    LensProfileIsEmbedded   => { Writable => 'boolean'},
     1570    AutoToneDigest          => { },
     1571    AutoToneDigestNoSat     => { },
     1572    ToggleStyleDigest       => { },
     1573    ToggleStyleAmount       => { Writable => 'integer' },
     1574);
     1575
     1576# Tiff namespace properties (tiff)
    12951577%Image::ExifTool::XMP::tiff = (
    12961578    %xmpTableDefaults,
     
    12991581    PRIORITY => 0, # not as reliable as actual TIFF tags
    13001582    TABLE_DESC => 'XMP TIFF',
    1301     NOTES => 'EXIF schema for TIFF tags.',
     1583    NOTES => q{
     1584        EXIF namespace for TIFF tags.  See
     1585        L<https://web.archive.org/web/20180921145139if_/http://www.cipa.jp:80/std/documents/e/DC-010-2017_E.pdf>
     1586        for the specification.
     1587    },
    13021588    ImageWidth    => { Writable => 'integer' },
    13031589    ImageLength   => { Writable => 'integer', Name => 'ImageHeight' },
     
    13241610        },
    13251611    },
    1326     YCbCrSubSampling => { PrintConv => \%Image::ExifTool::JPEG::yCbCrSubSampling },
     1612    YCbCrSubSampling => {
     1613        Writable => 'integer',
     1614        List => 'Seq',
     1615        # join the raw values before conversion to allow PrintConv to operate on
     1616        # the combined string as it does for the corresponding EXIF tag
     1617        RawJoin => 1,
     1618        Notes => q{
     1619            while technically this is a list-type tag, for compatibility with its EXIF
     1620            counterpart it is written and read as a simple string
     1621        },
     1622        PrintConv => \%Image::ExifTool::JPEG::yCbCrSubSampling,
     1623    },
    13271624    YCbCrPositioning => {
    13281625        Writable => 'integer',
     
    13431640        },
    13441641    },
    1345     TransferFunction      => { Writable => 'integer',  List => 'Seq' },
     1642    TransferFunction      => { Writable => 'integer',  List => 'Seq', AutoSplit => 1 },
    13461643    WhitePoint            => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
    13471644    PrimaryChromaticities => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
     
    13541651    },
    13551652    ImageDescription => { Writable => 'lang-alt' },
    1356     Make      => { Groups => { 2 => 'Camera' } },
    1357     Model     => { Groups => { 2 => 'Camera' }, Description => 'Camera Model Name' },
     1653    Make => {
     1654        Groups => { 2 => 'Camera' },
     1655        RawConv => '$$self{Make} ? $val : $$self{Make} = $val',
     1656    },
     1657    Model => {
     1658        Groups => { 2 => 'Camera' },
     1659        Description => 'Camera Model Name',
     1660        RawConv => '$$self{Model} ? $val : $$self{Model} = $val',
     1661    },
    13581662    Software  => { },
    13591663    Artist    => { Groups => { 2 => 'Author' } },
     
    13621666);
    13631667
    1364 # Exif schema properties (exif)
     1668# Exif namespace properties (exif)
    13651669%Image::ExifTool::XMP::exif = (
    13661670    %xmpTableDefaults,
     
    13681672    NAMESPACE   => 'exif',
    13691673    PRIORITY => 0, # not as reliable as actual EXIF tags
    1370     NOTES => 'EXIF schema for EXIF tags.',
     1674    NOTES => q{
     1675        EXIF namespace for EXIF tags.  See
     1676        L<https://web.archive.org/web/20180921145139if_/http://www.cipa.jp:80/std/documents/e/DC-010-2017_E.pdf>
     1677        for the specification.
     1678    },
    13711679    ExifVersion     => { },
    13721680    FlashpixVersion => { },
     
    13831691    },
    13841692    ComponentsConfiguration => {
     1693        Writable => 'integer',
    13851694        List => 'Seq',
    1386         Writable => 'integer',
    13871695        AutoSplit => 1,
    13881696        PrintConvColumns => 2,
     
    14161724        Writable => 'rational',
    14171725        PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
    1418         PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)',
     1726        PrintConvInv => '$val',
    14191727    },
    14201728    FNumber => {
    14211729        Writable => 'rational',
    1422         PrintConv => 'sprintf("%.1f",$val)',
     1730        PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)',
    14231731        PrintConvInv => '$val',
    14241732    },
     
    14471755    OECF => {
    14481756        Name => 'Opto-ElectricConvFactor',
     1757        FlatName => 'OECF',
    14491758        Groups => { 2 => 'Camera' },
    14501759        Struct => \%sOECF,
    14511760    },
    1452     OECFColumns => { Flat => 1 },
    1453     OECFRows    => { Flat => 1 },
    1454     OECFNames   => { Flat => 1 },
    1455     OECFValues  => { Flat => 1 },
    14561761    ShutterSpeedValue => {
    14571762        Writable => 'rational',
     
    15121817            STRUCT_NAME => 'Flash',
    15131818            NAMESPACE   => 'exif',
    1514             Fired       => { Writable => 'boolean' },
     1819            Fired       => { Writable => 'boolean', %boolConv },
    15151820            Return => {
    15161821                Writable => 'integer',
     
    15301835                },
    15311836            },
    1532             Function    => { Writable => 'boolean' },
    1533             RedEyeMode  => { Writable => 'boolean' },
     1837            Function    => { Writable => 'boolean', %boolConv },
     1838            RedEyeMode  => { Writable => 'boolean', %boolConv },
    15341839        },
    15351840    },
     
    17132018        Groups => { 2 => 'Location' },
    17142019        Writable => 'rational',
    1715         RawConv => 'require Image::ExifTool::GPS; $val', # to load Composite tags and routines
    17162020        # extricate unsigned decimal number from string
    17172021        ValueConvInv => '$val=~/((?=\d|\.\d)\d*(?:\.\d*)?)/ ? $1 : undef',
     
    17662070    GPSTrack => { Groups => { 2 => 'Location' }, Writable => 'rational' },
    17672071    GPSImgDirectionRef => {
     2072        Groups => { 2 => 'Location' },
    17682073        PrintConv => {
    17692074            M => 'Magnetic North',
     
    18052110        },
    18062111    },
     2112    GPSHPositioningError => { #12
     2113        Description => 'GPS Horizontal Positioning Error',
     2114        Groups => { 2 => 'Location' },
     2115        Writable => 'rational',
     2116        PrintConv => '"$val m"',
     2117        PrintConvInv => '$val=~s/\s*m$//; $val',
     2118    },
    18072119    NativeDigest => { }, #PH
    1808 );
    1809 
    1810 # Auxiliary schema properties (aux) - not fully documented (ref PH)
    1811 %Image::ExifTool::XMP::aux = (
     2120    # new Exif
     2121);
     2122
     2123# Exif extended properties (exifEX, ref 12)
     2124%Image::ExifTool::XMP::exifEX = (
    18122125    %xmpTableDefaults,
    1813     GROUPS => { 1 => 'XMP-aux', 2 => 'Camera' },
    1814     NAMESPACE   => 'aux',
    1815     NOTES => 'Photoshop Auxiliary schema tags.',
    1816     Firmware        => { }, #7
    1817     FlashCompensation => { Writable => 'rational' }, #7
    1818     ImageNumber     => { }, #7
    1819     LensInfo        => { #7
    1820         Notes => '4 rational values giving focal and aperture ranges',
    1821         # convert to floating point values (or 'inf' or 'undef')
    1822         ValueConv => sub {
    1823             my $val = shift;
    1824             my @vals = split ' ', $val;
    1825             return $val unless @vals == 4;
    1826             foreach (@vals) {
    1827                 ConvertRational($_) or return $val;
    1828             }
    1829             return join ' ', @vals;
    1830         },
     2126    GROUPS => { 1 => 'XMP-exifEX', 2 => 'Image' },
     2127    NAMESPACE   => 'exifEX',
     2128    PRIORITY => 0, # not as reliable as actual EXIF tags
     2129    NOTES => q{
     2130        EXIF tags added by the EXIF 2.31 for XMP specification (see
     2131        L<http://www.cipa.jp/std/documents/e/DC-X010-2017.pdf>).
     2132    },
     2133    Gamma                       => { Writable => 'rational' },
     2134    PhotographicSensitivity     => { Writable => 'integer' },
     2135    SensitivityType => {
     2136        Writable => 'integer',
     2137        PrintConv => {
     2138            0 => 'Unknown',
     2139            1 => 'Standard Output Sensitivity',
     2140            2 => 'Recommended Exposure Index',
     2141            3 => 'ISO Speed',
     2142            4 => 'Standard Output Sensitivity and Recommended Exposure Index',
     2143            5 => 'Standard Output Sensitivity and ISO Speed',
     2144            6 => 'Recommended Exposure Index and ISO Speed',
     2145            7 => 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed',
     2146        },
     2147    },
     2148    StandardOutputSensitivity   => { Writable => 'integer' },
     2149    RecommendedExposureIndex    => { Writable => 'integer' },
     2150    ISOSpeed                    => { Writable => 'integer' },
     2151    ISOSpeedLatitudeyyy => {
     2152        Description => 'ISO Speed Latitude yyy',
     2153        Writable => 'integer',
     2154    },
     2155    ISOSpeedLatitudezzz => {
     2156        Description => 'ISO Speed Latitude zzz',
     2157        Writable => 'integer',
     2158    },
     2159    CameraOwnerName     => { Name => 'OwnerName' },
     2160    BodySerialNumber    => { Name => 'SerialNumber', Groups => { 2 => 'Camera' } },
     2161    LensSpecification => {
     2162        Name => 'LensInfo',
     2163        Writable => 'rational',
     2164        Groups => { 2 => 'Camera' },
     2165        List => 'Seq',
     2166        RawJoin => 1, # join list into a string before ValueConv
     2167        ValueConv => \&ConvertRationalList,
    18312168        ValueConvInv => sub {
    18322169            my $val = shift;
     
    18402177                $_ = join '/', @a;
    18412178            }
    1842             return join ' ', @vals;
     2179            return \@vals; # return list reference (List-type tag)
     2180        },
     2181        PrintConv => \&Image::ExifTool::Exif::PrintLensInfo,
     2182        PrintConvInv => \&Image::ExifTool::Exif::ConvertLensInfo,
     2183        Notes => q{
     2184            unfortunately the EXIF 2.3 for XMP specification defined this new tag
     2185            instead of using the existing XMP-aux:LensInfo
     2186        },
     2187    },
     2188    LensMake            => { Groups => { 2 => 'Camera' } },
     2189    LensModel           => { Groups => { 2 => 'Camera' } },
     2190    LensSerialNumber    => { Groups => { 2 => 'Camera' } },
     2191    InteroperabilityIndex => {
     2192        Name => 'InteropIndex',
     2193        Description => 'Interoperability Index',
     2194        PrintConv => {
     2195            R98 => 'R98 - DCF basic file (sRGB)',
     2196            R03 => 'R03 - DCF option file (Adobe RGB)',
     2197            THM => 'THM - DCF thumbnail file',
     2198        },
     2199    },
     2200    # new in Exif 2.31
     2201    Temperature         => { Writable => 'rational', Name => 'AmbientTemperature' },
     2202    Humidity            => { Writable => 'rational' },
     2203    Pressure            => { Writable => 'rational' },
     2204    WaterDepth          => { Writable => 'rational' },
     2205    Acceleration        => { Writable => 'rational' },
     2206    CameraElevationAngle=> { Writable => 'rational' },
     2207    # new in Exif 2.32 (according to the spec, these should use a different namespace
     2208    # URI, but the same namespace prefix... Exactly how is that supposed to work?!!
     2209    # -- I'll just stick with the same URI)
     2210    CompositeImage => { Writable => 'integer',
     2211        PrintConv => {
     2212            0 => 'Unknown',
     2213            1 => 'Not a Composite Image',
     2214            2 => 'General Composite Image',
     2215            3 => 'Composite Image Captured While Shooting',
     2216        },
     2217    },
     2218    CompositeImageCount => { List => 'Seq', Writable => 'integer' },
     2219    CompositeImageExposureTimes => {
     2220        FlatName => 'CompImage',
     2221        Struct => {
     2222            STRUCT_NAME => 'CompImageExp',
     2223            NAMESPACE => 'exifEX',
     2224            TotalExposurePeriod     => { Writable => 'rational' },
     2225            SumOfExposureTimesOfAll => { Writable => 'rational', FlatName => 'SumExposureAll' },
     2226            SumOfExposureTimesOfUsed=> { Writable => 'rational', FlatName => 'SumExposureUsed' },
     2227            MaxExposureTimesOfAll   => { Writable => 'rational', FlatName => 'MaxExposureAll' },
     2228            MaxExposureTimesOfUsed  => { Writable => 'rational', FlatName => 'MaxExposureUsed' },
     2229            MinExposureTimesOfAll   => { Writable => 'rational', FlatName => 'MinExposureAll'  },
     2230            MinExposureTimesOfUsed  => { Writable => 'rational', FlatName => 'MinExposureUsed' },
     2231            NumberOfSequences       => { Writable => 'integer',  FlatName => 'NumSequences' },
     2232            NumberOfImagesInSequences=>{ Writable => 'integer',  FlatName => 'ImagesPerSequence' },
     2233            Values =>   { List => 'Seq', Writable => 'rational' },
     2234        },
     2235    },
     2236);
     2237
     2238# Auxiliary namespace properties (aux) - not fully documented (ref PH)
     2239%Image::ExifTool::XMP::aux = (
     2240    %xmpTableDefaults,
     2241    GROUPS => { 1 => 'XMP-aux', 2 => 'Camera' },
     2242    NAMESPACE   => 'aux',
     2243    NOTES => q{
     2244        Adobe-defined auxiliary EXIF tags.  This namespace existed in the XMP
     2245        specification until it was dropped in 2012, presumably due to the
     2246        introduction of the EXIF 2.3 for XMP specification and the exifEX namespace
     2247        at this time.  For this reason, tags below with equivalents in the
     2248        L<exifEX namespace|/XMP exifEX Tags> are avoided when writing.
     2249    },
     2250    Firmware        => { }, #7
     2251    FlashCompensation => { Writable => 'rational' }, #7
     2252    ImageNumber     => { }, #7
     2253    LensInfo        => { #7
     2254        Notes => '4 rational values giving focal and aperture ranges',
     2255        Avoid => 1,
     2256        # convert to floating point values (or 'inf' or 'undef')
     2257        ValueConv => \&ConvertRationalList,
     2258        ValueConvInv => sub {
     2259            my $val = shift;
     2260            my @vals = split ' ', $val;
     2261            return $val unless @vals == 4;
     2262            foreach (@vals) {
     2263                $_ eq 'inf' and $_ = '1/0', next;
     2264                $_ eq 'undef' and $_ = '0/0', next;
     2265                Image::ExifTool::IsFloat($_) or return $val;
     2266                my @a = Image::ExifTool::Rationalize($_);
     2267                $_ = join '/', @a;
     2268            }
     2269            return join ' ', @vals; # return string (string tag)
    18432270        },
    18442271        # convert to the form "12-20mm f/3.8-4.5" or "50mm f/1.4"
     
    18472274    },
    18482275    Lens            => { },
    1849     OwnerName       => { }, #7
    1850     SerialNumber    => { },
     2276    OwnerName       => { Avoid => 1 }, #7
     2277    SerialNumber    => { Avoid => 1 },
     2278    LensSerialNumber=> { Avoid => 1 },
    18512279    LensID          => {
    18522280        Priority => 0,
     
    18582286    },
    18592287    ApproximateFocusDistance => { Writable => 'rational' }, #PH (LR3)
    1860 );
    1861 
    1862 # IPTC Core schema properties (Iptc4xmpCore) (ref 4)
     2288    # the following new in LR6 (ref forum6497)
     2289    IsMergedPanorama         => { Writable => 'boolean' },
     2290    IsMergedHDR              => { Writable => 'boolean' },
     2291    DistortionCorrectionAlreadyApplied  => { Writable => 'boolean' },
     2292    VignetteCorrectionAlreadyApplied    => { Writable => 'boolean' },
     2293    LateralChromaticAberrationCorrectionAlreadyApplied => { Writable => 'boolean' },
     2294    LensDistortInfo => { }, # (LR 7.5.1, 4 signed rational values)
     2295);
     2296
     2297# IPTC Core namespace properties (Iptc4xmpCore) (ref 4)
    18632298%Image::ExifTool::XMP::iptcCore = (
    18642299    %xmpTableDefaults,
     
    18672302    TABLE_DESC => 'XMP IPTC Core',
    18682303    NOTES => q{
    1869         IPTC Core schema tags.  The actual IPTC Core namespace prefix is
     2304        IPTC Core namespace tags.  The actual IPTC Core namespace prefix is
    18702305        "Iptc4xmpCore", which is the prefix recorded in the file, but ExifTool
    1871         shortens this for the "XMP-iptcCore" family 1 group name. (see
     2306        shortens this for the family 1 group name. (see
    18722307        L<http://www.iptc.org/IPTC4XMP/>)
    18732308    },
     
    18992334    Scene               => { Groups => { 2 => 'Other' }, List => 'Bag' },
    19002335    SubjectCode         => { Groups => { 2 => 'Other' }, List => 'Bag' },
    1901 );
    1902 
    1903 # IPTC Extension schema properties (Iptc4xmpExt) (ref 4)
    1904 %Image::ExifTool::XMP::iptcExt = (
    1905     %xmpTableDefaults,
    1906     GROUPS => { 1 => 'XMP-iptcExt', 2 => 'Author' },
    1907     NAMESPACE   => 'Iptc4xmpExt',
    1908     TABLE_DESC => 'XMP IPTC Extension',
    1909     NOTES => q{
    1910         IPTC Extension schema tags.  The actual namespace prefix is "Iptc4xmpExt",
    1911         but ExifTool shortens this for the "XMP-iptcExt" family 1 group name.
    1912         (see L<http://www.iptc.org/IPTC4XMP/>)
    1913     },
    1914     AddlModelInfo   => { Name => 'AdditionalModelInformation' },
    1915     ArtworkOrObject => {
    1916         Struct => {
    1917             STRUCT_NAME => 'ArtworkOrObjectDetails',
    1918             NAMESPACE   => 'Iptc4xmpExt',
    1919             AOCopyrightNotice => { },
    1920             AOCreator    => { List => 'Seq' },
    1921             AODateCreated=> { Groups => { 2 => 'Time' }, %dateTimeInfo },
    1922             AOSource     => { },
    1923             AOSourceInvNo=> { },
    1924             AOTitle      => { Writable => 'lang-alt' },
    1925         },
    1926         List => 'Bag',
    1927     },
    1928     ArtworkOrObjectAOCopyrightNotice=> { Flat => 1, Name => 'ArtworkCopyrightNotice' },
    1929     ArtworkOrObjectAOCreator        => { Flat => 1, Name => 'ArtworkCreator' },
    1930     ArtworkOrObjectAODateCreated    => { Flat => 1, Name => 'ArtworkDateCreated' },
    1931     ArtworkOrObjectAOSource         => { Flat => 1, Name => 'ArtworkSource' },
    1932     ArtworkOrObjectAOSourceInvNo    => { Flat => 1, Name => 'ArtworkSourceInventoryNo' },
    1933     ArtworkOrObjectAOTitle          => { Flat => 1, Name => 'ArtworkTitle' },
    1934     OrganisationInImageCode => { List => 'Bag' },
    1935     CVterm => {
    1936         Name => 'ControlledVocabularyTerm',
    1937         List => 'Bag',
    1938     },
    1939     LocationShown => {
    1940         Struct => \%sLocationDetails,
    1941         Groups => { 2 => 'Location' },
    1942         List => 'Bag',
    1943     },
    1944     ModelAge                => { List => 'Bag', Writable => 'integer' },
    1945     OrganisationInImageName => { List => 'Bag' },
    1946     PersonInImage           => { List => 'Bag' },
    1947     DigImageGUID            => { Name => 'DigitalImageGUID' },
    1948     DigitalSourcefileType   => {
    1949         Name => 'DigitalSourceFileType',
    1950         Notes => 'now deprecated -- replaced by DigitalSourceType',
    1951     },
    1952     DigitalSourceType       => { Name => 'DigitalSourceType' },
    1953     Event                   => { Writable => 'lang-alt' },
    1954     RegistryId => {
    1955         Struct => {
    1956             STRUCT_NAME => 'RegistryEntryDetails',
    1957             NAMESPACE   => 'Iptc4xmpExt',
    1958             RegItemId    => { },
    1959             RegOrgId     => { },
    1960         },
    1961         List => 'Bag',
    1962     },
    1963     RegistryIdRegItemId         => { Flat => 1, Name => 'RegistryItemID' },
    1964     RegistryIdRegOrgId          => { Flat => 1, Name => 'RegistryOrganisationID' },
    1965     IptcLastEdited          => { Groups => { 2 => 'Time' }, %dateTimeInfo },
    1966     LocationCreated => {
    1967         Struct => \%sLocationDetails,
    1968         Groups => { 2 => 'Location' },
    1969         List => 'Bag',
    1970     },
    1971     MaxAvailHeight  => { Writable => 'integer' },
    1972     MaxAvailWidth   => { Writable => 'integer' },
    1973 );
    1974 
    1975 # Adobe Lightroom schema properties (lr) (ref PH)
     2336    # Copyright - have seen this in a sample (Jan 2021), but I think it is non-standard
     2337);
     2338
     2339# Adobe Lightroom namespace properties (lr) (ref PH)
    19762340%Image::ExifTool::XMP::Lightroom = (
    19772341    %xmpTableDefaults,
     
    19792343    NAMESPACE   => 'lr',
    19802344    TABLE_DESC => 'XMP Adobe Lightroom',
    1981     NOTES => 'Adobe Lightroom "lr" schema tags.',
     2345    NOTES => 'Adobe Lightroom "lr" namespace tags.',
    19822346    privateRTKInfo => { },
    19832347    hierarchicalSubject => { List => 'Bag' },
    19842348);
    19852349
    1986 # Adobe Album schema properties (album) (ref PH)
     2350# Adobe Album namespace properties (album) (ref PH)
    19872351%Image::ExifTool::XMP::Album = (
    19882352    %xmpTableDefaults,
     
    19902354    NAMESPACE   => 'album',
    19912355    TABLE_DESC => 'XMP Adobe Album',
    1992     NOTES => 'Adobe Album schema tags.',
     2356    NOTES => 'Adobe Album namespace tags.',
    19932357    Notes => { },
    19942358);
     
    20022366# Composite XMP tags
    20032367%Image::ExifTool::XMP::Composite = (
    2004     # get latitude/logitude reference from XMP lat/long tags
     2368    # get latitude/longitude reference from XMP lat/long tags
    20052369    # (used to set EXIF GPS position from XMP tags)
    20062370    GPSLatitudeRef => {
    2007         Require => 'XMP:GPSLatitude',
     2371        Require => 'XMP-exif:GPSLatitude',
     2372        Groups => { 2 => 'Location' },
     2373        # Note: Do not Inihibit based on EXIF:GPSLatitudeRef (see forum10192)
    20082374        ValueConv => q{
    20092375            IsFloat($val[0]) and return $val[0] < 0 ? "S" : "N";
    2010             $val[0] =~ /.*([NS])/;
     2376            $val[0] =~ /^.*([NS])/;
    20112377            return $1;
    20122378        },
    2013         PrintConv => {
    2014             N => 'North',
    2015             S => 'South',
    2016         },
     2379        PrintConv => { N => 'North', S => 'South' },
    20172380    },
    20182381    GPSLongitudeRef => {
    2019         Require => 'XMP:GPSLongitude',
     2382        Require => 'XMP-exif:GPSLongitude',
     2383        Groups => { 2 => 'Location' },
    20202384        ValueConv => q{
    20212385            IsFloat($val[0]) and return $val[0] < 0 ? "W" : "E";
    2022             $val[0] =~ /.*([EW])/;
     2386            $val[0] =~ /^.*([EW])/;
    20232387            return $1;
    20242388        },
    2025         PrintConv => {
    2026             E => 'East',
    2027             W => 'West',
     2389        PrintConv => { E => 'East', W => 'West' },
     2390    },
     2391    GPSDestLatitudeRef => {
     2392        Require => 'XMP-exif:GPSDestLatitude',
     2393        Groups => { 2 => 'Location' },
     2394        ValueConv => q{
     2395            IsFloat($val[0]) and return $val[0] < 0 ? "S" : "N";
     2396            $val[0] =~ /^.*([NS])/;
     2397            return $1;
     2398        },
     2399        PrintConv => { N => 'North', S => 'South' },
     2400    },
     2401    GPSDestLongitudeRef => {
     2402        Require => 'XMP-exif:GPSDestLongitude',
     2403        Groups => { 2 => 'Location' },
     2404        ValueConv => q{
     2405            IsFloat($val[0]) and return $val[0] < 0 ? "W" : "E";
     2406            $val[0] =~ /^.*([EW])/;
     2407            return $1;
     2408        },
     2409        PrintConv => { E => 'East', W => 'West' },
     2410    },
     2411    LensID => {
     2412        Notes => 'attempt to convert numerical XMP-aux:LensID stored by Adobe applications',
     2413        Require => {
     2414            0 => 'XMP-aux:LensID',
     2415            1 => 'Make',
     2416        },
     2417        Desire => {
     2418            2 => 'LensInfo',
     2419            3 => 'FocalLength',
     2420            4 => 'LensModel',
     2421            5 => 'MaxApertureValue',
     2422        },
     2423        Inhibit => {
     2424            6 => 'Composite:LensID',    # don't override existing Composite:LensID
     2425        },
     2426        Groups => { 2 => 'Camera' },
     2427        ValueConv => '$val',
     2428        PrintConv => 'Image::ExifTool::XMP::PrintLensID($self, @val)',
     2429    },
     2430    Flash => {
     2431        Notes => 'facilitates copying camera flash information between XMP and EXIF',
     2432        Desire => {
     2433            0 => 'XMP:FlashFired',
     2434            1 => 'XMP:FlashReturn',
     2435            2 => 'XMP:FlashMode',
     2436            3 => 'XMP:FlashFunction',
     2437            4 => 'XMP:FlashRedEyeMode',
     2438            5 => 'XMP:Flash', # handle structured flash information too
     2439        },
     2440        Groups => { 2 => 'Camera' },
     2441        Writable => 1,
     2442        PrintHex => 1,
     2443        SeparateTable => 'EXIF Flash',
     2444        ValueConv => q{
     2445            if (ref $val[5] eq 'HASH') {
     2446                # copy structure fields into value array
     2447                my $i = 0;
     2448                $val[$i++] = $val[5]{$_} foreach qw(Fired Return Mode Function RedEyeMode);
     2449            }
     2450            return((($val[0] and lc($val[0]) eq 'true') ? 0x01 : 0) |
     2451                   (($val[1] || 0) << 1) |
     2452                   (($val[2] || 0) << 3) |
     2453                   (($val[3] and lc($val[3]) eq 'true') ? 0x20 : 0) |
     2454                   (($val[4] and lc($val[4]) eq 'true') ? 0x40 : 0));
     2455        },
     2456        PrintConv => \%Image::ExifTool::Exif::flash,
     2457        WriteAlso => {
     2458            'XMP:FlashFired'      => '$val & 0x01 ? "True" : "False"',
     2459            'XMP:FlashReturn'     => '($val & 0x06) >> 1',
     2460            'XMP:FlashMode'       => '($val & 0x18) >> 3',
     2461            'XMP:FlashFunction'   => '$val & 0x20 ? "True" : "False"',
     2462            'XMP:FlashRedEyeMode' => '$val & 0x40 ? "True" : "False"',
    20282463        },
    20292464    },
     
    20572492# Inputs: 0) string to be unescaped
    20582493#         1) optional hash reference to convert entity names to numbers
     2494#         2) optional character encoding
    20592495# Returns: unescaped string
    20602496my %charNum = ('quot'=>34, 'amp'=>38, 'apos'=>39, 'lt'=>60, 'gt'=>62);
    2061 sub UnescapeXML($;$)
     2497sub UnescapeXML($;$$)
    20622498{
    2063     my ($str, $conv) = @_;
     2499    my ($str, $conv, $enc) = @_;
    20642500    $conv = \%charNum unless $conv;
    2065     $str =~ s/&(#?\w+);/UnescapeChar($1,$conv)/sge;
     2501    $str =~ s/&(#?\w+);/UnescapeChar($1,$conv,$enc)/sge;
    20662502    return $str;
    20672503}
     
    20992535#------------------------------------------------------------------------------
    21002536# Convert XML character reference to UTF-8
    2101 # Inputs: 0) XML character reference stripped of the '&' and ';' (ie. 'quot', '#34', '#x22')
     2537# Inputs: 0) XML character reference stripped of the '&' and ';' (eg. 'quot', '#34', '#x22')
    21022538#         1) hash reference for looking up character numbers by name
     2539#         2) optional character encoding (default 'UTF8')
    21032540# Returns: UTF-8 equivalent (or original character on conversion error)
    2104 sub UnescapeChar($$)
     2541sub UnescapeChar($$;$)
    21052542{
    2106     my ($ch, $conv) = @_;
     2543    my ($ch, $conv, $enc) = @_;
    21072544    my $val = $$conv{$ch};
    21082545    unless (defined $val) {
     
    21162553    }
    21172554    return chr($val) if $val < 0x80;   # simple ASCII
    2118     return pack('C0U', $val) if $] >= 5.006001;
    2119     return Image::ExifTool::PackUTF8($val);
     2555    $val = $] >= 5.006001 ? pack('C0U', $val) : Image::ExifTool::PackUTF8($val);
     2556    $val = Image::ExifTool::Decode(undef, $val, 'UTF8', undef, $enc) if $enc and $enc ne 'UTF8';
     2557    return $val;
    21202558}
    21212559
    21222560#------------------------------------------------------------------------------
    21232561# Does a string contain valid UTF-8 characters?
    2124 # Inputs: 0) string reference
     2562# Inputs: 0) string reference, 1) true to allow last character to be truncated
    21252563# Returns: 0=regular ASCII, -1=invalid UTF-8, 1=valid UTF-8 with maximum 16-bit
    21262564#          wide characters, 2=valid UTF-8 requiring 32-bit wide characters
    21272565# Notes: Changes current string position
    2128 sub IsUTF8($)
     2566# (see http://www.fileformat.info/info/unicode/utf8.htm for help understanding this)
     2567sub IsUTF8($;$)
    21292568{
    2130     my $strPt = shift;
     2569    my ($strPt, $trunc) = @_;
    21312570    pos($$strPt) = 0; # start at beginning of string
    21322571    return 0 unless $$strPt =~ /([\x80-\xff])/g;
     
    21502589            $rtnVal = 2;
    21512590        }
    2152         return -1 unless $$strPt =~ /\G[\x80-\xbf]{$n}/g;
     2591        my $pos = pos $$strPt;
     2592        unless ($$strPt =~ /\G([\x80-\xbf]{$n})/g) {
     2593            return $rtnVal if $trunc and $pos + $n > length $$strPt;
     2594            return -1;
     2595        }
     2596        # the following is ref https://www.cl.cam.ac.uk/%7Emgk25/ucs/utf8_check.c
     2597        if ($n == 2) {
     2598            return -1 if ($ch == 0xe0 and (ord($1) & 0xe0) == 0x80) or
     2599                         ($ch == 0xed and (ord($1) & 0xe0) == 0xa0) or
     2600                         ($ch == 0xef and ord($1) == 0xbf and
     2601                            (ord(substr $1, 1) & 0xfe) == 0xbe);
     2602        } else {
     2603            return -1 if ($ch == 0xf0 and (ord($1) & 0xf0) == 0x80) or
     2604                         ($ch == 0xf4 and ord($1) > 0x8f) or $ch > 0xf4;
     2605        }
    21532606        last unless $$strPt =~ /([\x80-\xff])/g;
    21542607    }
     
    21572610
    21582611#------------------------------------------------------------------------------
    2159 # Fix malformed UTF8 (by replacing bad bytes with '?')
    2160 # Inputs: 0) string reference
     2612# Fix malformed UTF8 (by replacing bad bytes with specified character)
     2613# Inputs: 0) string reference, 1) string to replace each bad byte,
     2614#         may be '' to delete bad bytes, or undef to use '?'
    21612615# Returns: true if string was fixed, and updates string
    2162 sub FixUTF8($)
     2616sub FixUTF8($;$)
    21632617{
    2164     my $strPt = shift;
     2618    my ($strPt, $bad) = @_;
    21652619    my $fixed;
    21662620    pos($$strPt) = 0; # start at beginning of string
     
    21722626        if ($ch >= 0xc2 and $ch < 0xf8) {
    21732627            my $n = $ch < 0xe0 ? 1 : ($ch < 0xf0 ? 2 : 3);
    2174             next if $$strPt =~ /\G[\x80-\xbf]{$n}/g;
    2175         }
    2176         # replace bad character with '?'
    2177         substr($$strPt, $pos-1, 1) = '?';
    2178         pos($$strPt) = $fixed = $pos;
     2628            if ($$strPt =~ /\G([\x80-\xbf]{$n})/g) {
     2629                next if $n == 1;
     2630                if ($n == 2) {
     2631                    next unless ($ch == 0xe0 and (ord($1) & 0xe0) == 0x80) or
     2632                                ($ch == 0xed and (ord($1) & 0xe0) == 0xa0) or
     2633                                ($ch == 0xef and ord($1) == 0xbf and
     2634                                    (ord(substr $1, 1) & 0xfe) == 0xbe);
     2635                } else {
     2636                    next unless ($ch == 0xf0 and (ord($1) & 0xf0) == 0x80) or
     2637                                ($ch == 0xf4 and ord($1) > 0x8f) or $ch > 0xf4;
     2638                }
     2639            }
     2640        }
     2641        # replace bad character
     2642        $bad = '?' unless defined $bad;
     2643        substr($$strPt, $pos-1, 1) = $bad;
     2644        pos($$strPt) = $pos-1 + length $bad;
     2645        $fixed = 1;
    21792646    }
    21802647    return $fixed;
     
    22182685
    22192686#------------------------------------------------------------------------------
    2220 # Generate a name for this XMP tag
     2687# Generate a tag ID for this XMP tag
    22212688# Inputs: 0) tag property name list ref, 1) array ref for receiving structure property list
    22222689#         2) array for receiving namespace list
     
    22302697        # (Note: namespace can be '' for property qualifiers)
    22312698        my ($ns, $nm) = ($prop =~ /(.*?):(.*)/) ? ($1, $2) : ('', $prop);
    2232         if ($ignoreNamespace{$ns}) {
     2699        if ($ignoreNamespace{$ns} or $ignoreProp{$prop}) {
    22332700            # special case: don't ignore rdf numbered items
     2701            # (not technically allowed in XMP, but used in RDF/XML)
    22342702            unless ($prop =~ /^rdf:(_\d+)$/) {
    22352703                # save list index if necessary for structures
     
    22442712            # all uppercase is ugly, so convert it
    22452713            if ($nm !~ /[a-z]/) {
    2246                 my $xlatNS = $$xlatNamespace{$ns} || $ns;
    2247                 my $info = $Image::ExifTool::XMP::Main{$xlatNS};
     2714                my $xlat = $stdXlatNS{$ns} || $ns;
     2715                my $info = $Image::ExifTool::XMP::Main{$xlat};
    22482716                my $table;
    2249                 if (ref $info eq 'HASH' and $info->{SubDirectory}) {
    2250                     $table = GetTagTable($info->{SubDirectory}{TagTable});
     2717                if (ref $info eq 'HASH' and $$info{SubDirectory}) {
     2718                    $table = GetTagTable($$info{SubDirectory}{TagTable});
    22512719                }
    2252                 unless ($table and $table->{$nm}) {
     2720                unless ($table and $$table{$nm}) {
    22532721                    $nm = lc($nm);
    22542722                    $nm =~ s/_([a-z])/\u$1/g;
     
    22782746#------------------------------------------------------------------------------
    22792747# Register namespace for specified user-defined table
    2280 # Inputs: 0) tag or structure table ref
     2748# Inputs: 0) tag/structure table ref
    22812749# Returns: namespace prefix
    22822750sub RegisterNamespace($)
     
    22902758        $ns = $$nsRef[0];
    22912759        $nsURI{$ns} = $$nsRef[1];
     2760        $uri2ns{$$nsRef[1]} = $ns;
    22922761    } else { # must be a hash
    22932762        my @ns = sort keys %$nsRef; # allow multiple namespace definitions
     
    22952764            $ns = pop @ns;
    22962765            if ($nsURI{$ns} and $nsURI{$ns} ne $$nsRef{$ns}) {
    2297                 warn "User-defined namespace prefix '$ns' conflicts with existing namespace\n";
     2766                warn "User-defined namespace prefix '${ns}' conflicts with existing namespace\n";
    22982767            }
    22992768            $nsURI{$ns} = $$nsRef{$ns};
     2769            $uri2ns{$$nsRef{$ns}} = $ns;
    23002770        }
    23012771    }
     
    23052775#------------------------------------------------------------------------------
    23062776# Generate flattened tags and add to table
    2307 # Inputs: 0) tag table ref, 1) tag ID for Struct tag in table
     2777# Inputs: 0) tag table ref, 1) tag ID for Struct tag (if not defined, whole table is done),
     2778#         2) flag to not expand sub-structures
    23082779# Returns: number of tags added (not counting those just initialized)
    23092780# Notes: Must have verified that $$tagTablePtr{$tagID}{Struct} exists before calling this routine
    23102781# - makes sure that the tagInfo Struct is a HASH reference
    2311 sub AddFlattenedTags($$)
     2782sub AddFlattenedTags($;$$)
    23122783{
    23132784    local $_;
    2314     my ($tagTablePtr, $tagID) = @_;
    2315     my $tagInfo = $$tagTablePtr{$tagID};
    2316 
    2317     $$tagInfo{Flattened} and return 0;  # only generate flattened tags once
    2318     $$tagInfo{Flattened} = 1;
    2319 
    2320     my $strTable = $$tagInfo{Struct};
    2321     unless (ref $strTable) { # (allow a structure name for backward compatibility only)
    2322         my $strName = $strTable;
    2323         $strTable = $Image::ExifTool::UserDefined::xmpStruct{$strTable} or return 0;
    2324         $$strTable{STRUCT_NAME} or $$strTable{STRUCT_NAME} = $strName;
    2325         $$tagInfo{Struct} = $strTable;  # replace old-style name with HASH ref
    2326         delete $$tagInfo{SubDirectory}; # deprecated use of SubDirectory in Struct tags
     2785    my ($tagTablePtr, $tagID, $noSubStruct) = @_;
     2786    my $count = 0;
     2787    my @tagIDs;
     2788
     2789    if (defined $tagID) {
     2790        push @tagIDs, $tagID;
     2791    } else {
     2792        foreach $tagID (TagTableKeys($tagTablePtr)) {
     2793            my $tagInfo = $$tagTablePtr{$tagID};
     2794            next unless ref $tagInfo eq 'HASH' and $$tagInfo{Struct};
     2795            push @tagIDs, $tagID;
     2796        }
    23272797    }
    2328     # do not add flattened tags to variable-namespace structures
    2329     return 0 if exists $$strTable{NAMESPACE} and not defined $$strTable{NAMESPACE};
    2330 
    2331     # get family 2 group name for this structure tag
    2332     my ($tagG2, $field);
    2333     $tagG2 = $$tagInfo{Groups}{2} if $$tagInfo{Groups};
    2334     $tagG2 or $tagG2 = $$tagTablePtr{GROUPS}{2};
    2335 
    2336     my $count = 0;
    2337     foreach $field (keys %$strTable) {
    2338         next if $specialStruct{$field};
    2339         my $fieldInfo = $$strTable{$field};
    2340         next if $$fieldInfo{LangCode};  # don't flatten lang-alt tags
    2341         # build a tag ID for the corresponding flattened tag
    2342         my $fieldName = ucfirst($field);
    2343         my $flatID = $tagID . $fieldName;
    2344         my $flatInfo = $$tagTablePtr{$flatID};
    2345         if ($flatInfo) {
    2346             ref $flatInfo eq 'HASH' or warn("$flatInfo is not a HASH!\n"), next; # (to be safe)
    2347             # pre-defined flattened tags should have Flat flag set
    2348             if (not defined $$flatInfo{Flat} and $Image::ExifTool::debug) {
    2349                 warn "Missing Flat flag for $$flatInfo{Name}\n";
    2350             }
    2351             $$flatInfo{Flat} = 0;
    2352             # copy all missing entries from field information
    2353             foreach (keys %$fieldInfo) {
    2354                 # must not copy PropertyPath (but can't delete it afterwards
    2355                 # because the flat tag may already have this set)
    2356                 next if $_ eq 'PropertyPath';
    2357                 $$flatInfo{$_} = $$fieldInfo{$_} unless defined $$flatInfo{$_};
    2358             }
    2359             # NOTE: Do NOT delete Groups because we need them if GotGroups was done
    2360             # --> just override group 2 later according to field group
    2361             # re-generate List flag unless it is set to 0
    2362             delete $$flatInfo{List} if $$flatInfo{List};
    2363         } else {
    2364             # generate new flattened tag information based on structure field
    2365             $flatInfo = { %$fieldInfo, Name => $$tagInfo{Name} . $fieldName, Flat => 0 };
    2366             # add new flattened tag to table
    2367             Image::ExifTool::AddTagToTable($tagTablePtr, $flatID, $flatInfo);
    2368             ++$count;
    2369         }
    2370         # propagate List flag (unless set to 0 in pre-defined flattened tag)
    2371         unless (defined $$flatInfo{List}) {
    2372             $$flatInfo{List} = $$fieldInfo{List} || 1 if $$fieldInfo{List} or $$tagInfo{List};
    2373         }
    2374         # set group 2 name from the first existing family 2 group in the:
    2375         # 1) structure field Groups, 2) structure table GROUPS, 3) structure tag Groups
    2376         if ($$fieldInfo{Groups} and $$fieldInfo{Groups}{2}) {
    2377             $$flatInfo{Groups}{2} = $$fieldInfo{Groups}{2};
    2378         } elsif ($$strTable{GROUPS} and $$strTable{GROUPS}{2}) {
    2379             $$flatInfo{Groups}{2} = $$strTable{GROUPS}{2};
    2380         } else {
    2381             $$flatInfo{Groups}{2} = $tagG2;
    2382         }
    2383         # save reference to top-level structure
    2384         $$flatInfo{RootTagInfo} = $$tagInfo{RootTagInfo} || $tagInfo;
    2385         # recursively generate flattened tags for sub-structures
    2386         next unless $$flatInfo{Struct};
    2387         length($flatID) > 150 and warn("Possible deep recursion for tag $flatID\n"), last;
    2388         # reset flattened tag just in case we flattened hierarchy in the wrong order
    2389         # because we must start from the outtermost structure to get the List flags right
    2390         # (this should only happen when building tag tables)
    2391         delete $$flatInfo{Flattened};
    2392         $count += AddFlattenedTags($tagTablePtr, $flatID);
     2798
     2799    # loop through specified tags
     2800    foreach $tagID (@tagIDs) {
     2801
     2802        my $tagInfo = $$tagTablePtr{$tagID};
     2803
     2804        $$tagInfo{Flattened} and next;  # only generate flattened tags once
     2805        $$tagInfo{Flattened} = 1;
     2806
     2807        my $strTable = $$tagInfo{Struct};
     2808        unless (ref $strTable) { # (allow a structure name for backward compatibility only)
     2809            my $strName = $strTable;
     2810            $strTable = $Image::ExifTool::UserDefined::xmpStruct{$strTable} or next;
     2811            $$strTable{STRUCT_NAME} or $$strTable{STRUCT_NAME} = "XMP $strName";
     2812            $$tagInfo{Struct} = $strTable;  # replace old-style name with HASH ref
     2813            delete $$tagInfo{SubDirectory}; # deprecated use of SubDirectory in Struct tags
     2814        }
     2815
     2816        # get prefix for flattened tag names
     2817        my $flat = (defined $$tagInfo{FlatName} ? $$tagInfo{FlatName} : $$tagInfo{Name});
     2818
     2819        # get family 2 group name for this structure tag
     2820        my ($tagG2, $field);
     2821        $tagG2 = $$tagInfo{Groups}{2} if $$tagInfo{Groups};
     2822        $tagG2 or $tagG2 = $$tagTablePtr{GROUPS}{2};
     2823
     2824        foreach $field (keys %$strTable) {
     2825            next if $specialStruct{$field};
     2826            my $fieldInfo = $$strTable{$field};
     2827            next if $$fieldInfo{LangCode};  # don't flatten lang-alt tags
     2828            next if $$fieldInfo{Struct} and $noSubStruct;   # don't expand sub-structures if specified
     2829            # build a tag ID for the corresponding flattened tag
     2830            my $fieldName = ucfirst($field);
     2831            my $flatField = $$fieldInfo{FlatName} || $fieldName;
     2832            my $flatID = $tagID . $fieldName;
     2833            my $flatInfo = $$tagTablePtr{$flatID};
     2834            if ($flatInfo) {
     2835                ref $flatInfo eq 'HASH' or warn("$flatInfo is not a HASH!\n"), next; # (to be safe)
     2836                # pre-defined flattened tags should have Flat flag set
     2837                if (not defined $$flatInfo{Flat}) {
     2838                    next if $$flatInfo{NotFlat};
     2839                    warn "Missing Flat flag for $$flatInfo{Name}\n" if $Image::ExifTool::debug;
     2840                }
     2841                $$flatInfo{Flat} = 0;
     2842                # copy all missing entries from field information
     2843                foreach (keys %$fieldInfo) {
     2844                    # must not copy PropertyPath (but can't delete it afterwards
     2845                    # because the flat tag may already have this set)
     2846                    next if $_ eq 'PropertyPath' or defined $$flatInfo{$_};
     2847                    # copy the property (making a copy of the Groups hash)
     2848                    $$flatInfo{$_} = $_ eq 'Groups' ? { %{$$fieldInfo{$_}} } : $$fieldInfo{$_};
     2849                }
     2850                # (NOTE: Can NOT delete Groups because we need them if GotGroups was done)
     2851                # re-generate List flag unless it is set to 0
     2852                delete $$flatInfo{List} if $$flatInfo{List};
     2853            } else {
     2854                # generate new flattened tag information based on structure field
     2855                my $flatName = $flat . $flatField;
     2856                $flatInfo = { %$fieldInfo, Name => $flatName, Flat => 0 };
     2857                $$flatInfo{FlatName} = $flatName if $$fieldInfo{FlatName};
     2858                # make a copy of the Groups hash if necessary
     2859                $$flatInfo{Groups} = { %{$$fieldInfo{Groups}} } if $$fieldInfo{Groups};
     2860                # add new flattened tag to table
     2861                AddTagToTable($tagTablePtr, $flatID, $flatInfo);
     2862                ++$count;
     2863            }
     2864            # propagate List flag (unless set to 0 in pre-defined flattened tag)
     2865            unless (defined $$flatInfo{List}) {
     2866                $$flatInfo{List} = $$fieldInfo{List} || 1 if $$fieldInfo{List} or $$tagInfo{List};
     2867            }
     2868            # set group 2 name from the first existing family 2 group in the:
     2869            # 1) structure field Groups, 2) structure table GROUPS, 3) structure tag Groups
     2870            if ($$fieldInfo{Groups} and $$fieldInfo{Groups}{2}) {
     2871                $$flatInfo{Groups}{2} = $$fieldInfo{Groups}{2};
     2872            } elsif ($$strTable{GROUPS} and $$strTable{GROUPS}{2}) {
     2873                $$flatInfo{Groups}{2} = $$strTable{GROUPS}{2};
     2874            } else {
     2875                $$flatInfo{Groups}{2} = $tagG2;
     2876            }
     2877            # save reference to top-level structure
     2878            $$flatInfo{RootTagInfo} = $$tagInfo{RootTagInfo} || $tagInfo;
     2879            # recursively generate flattened tags for sub-structures
     2880            next unless $$flatInfo{Struct};
     2881            length($flatID) > 250 and warn("Possible deep recursion for tag $flatID\n"), last;
     2882            # reset flattened tag just in case we flattened hierarchy in the wrong order
     2883            # because we must start from the outtermost structure to get the List flags right
     2884            # (this should only happen when building tag tables)
     2885            delete $$flatInfo{Flattened};
     2886            $count += AddFlattenedTags($tagTablePtr, $flatID, $$flatInfo{NoSubStruct});
     2887        }
    23932888    }
    23942889    return $count;
     
    23972892#------------------------------------------------------------------------------
    23982893# Get localized version of tagInfo hash
    2399 # Inputs: 0) tagInfo hash ref, 1) language code (ie. "x-default")
     2894# Inputs: 0) tagInfo hash ref, 1) language code (eg. "x-default")
    24002895# Returns: new tagInfo hash ref, or undef if invalid
    24012896sub GetLangInfo($$)
     
    24062901    $langCode =~ tr/_/-/;   # RFC 3066 specifies '-' as a separator
    24072902    my $langInfo = Image::ExifTool::GetLangInfo($tagInfo, $langCode);
    2408     # save reference to source tagInfo hash in case we need to set the PropertyPath later
    2409     $$langInfo{SrcTagInfo} = $tagInfo;
    24102903    return $langInfo;
    24112904}
     
    24302923sub ScanForXMP($$)
    24312924{
    2432     my ($exifTool, $raf) = @_;
     2925    my ($et, $raf) = @_;
    24332926    my ($buff, $xmp);
    24342927    my $lastBuff = '';
    24352928
    2436     $exifTool->VPrint(0,"Scanning for XMP\n");
     2929    $et->VPrint(0,"Scanning for XMP\n");
    24372930    for (;;) {
    24382931        defined $buff or $raf->Read($buff, 65536) or return 0;
     
    24622955        }
    24632956    }
    2464     unless ($exifTool->{VALUE}{FileType}) {
    2465         $exifTool->{FILE_TYPE} = $exifTool->{FILE_EXT};
    2466         $exifTool->SetFileType('<unknown file containing XMP>');
     2957    unless ($$et{VALUE}{FileType}) {
     2958        $$et{FILE_TYPE} = $$et{FILE_EXT};
     2959        $et->SetFileType('<unknown file containing XMP>', undef, '');
    24672960    }
    24682961    my %dirInfo = (
    2469         DataPt => \$xmp,
    2470         DirLen => length $xmp,
     2962        DataPt  => \$xmp,
     2963        DirLen  => length $xmp,
    24712964        DataLen => length $xmp,
    24722965    );
    2473     ProcessXMP($exifTool, \%dirInfo);
     2966    ProcessXMP($et, \%dirInfo);
    24742967    return 1;
     2968}
     2969
     2970#------------------------------------------------------------------------------
     2971# Print conversion for XMP-aux:LensID
     2972# Inputs: 0) ExifTool ref, 1) LensID, 2) Make, 3) LensInfo, 4) FocalLength,
     2973#         5) LensModel, 6) MaxApertureValue
     2974# (yes, this is ugly -- blame Adobe)
     2975sub PrintLensID(@)
     2976{
     2977    local $_;
     2978    my ($et, $id, $make, $info, $focalLength, $lensModel, $maxAv) = @_;
     2979    my ($mk, $printConv);
     2980    my %alt = ( Pentax => 'Ricoh' );    # Pentax changed its name to Ricoh
     2981    # missing: Olympus (no XMP:LensID written by Adobe)
     2982    foreach $mk (qw(Canon Nikon Pentax Sony Sigma Samsung Leica)) {
     2983        next unless $make =~ /$mk/i or ($alt{$mk} and $make =~ /$alt{$mk}/i);
     2984        # get name of module containing the lens lookup (default "Make.pm")
     2985        my $mod = { Sigma => 'SigmaRaw', Leica => 'Panasonic' }->{$mk} || $mk;
     2986        require "Image/ExifTool/$mod.pm";
     2987        # get the name of the lens name lookup (default "makeLensTypes")
     2988        # (canonLensTypes, pentaxLensTypes, nikonLensIDs, etc)
     2989        my $convName = "Image::ExifTool::${mod}::" .
     2990            ({ Nikon => 'nikonLensIDs' }->{$mk} || lc($mk) . 'LensTypes');
     2991        no strict 'refs';
     2992        %$convName or last;
     2993        my $printConv = \%$convName;
     2994        use strict 'refs';
     2995        # sf = short focal
     2996        # lf = long focal
     2997        # sa = max aperture at short focal
     2998        # la = max aperture at long focal
     2999        my ($sf, $lf, $sa, $la);
     3000        if ($info) {
     3001            my @a = split ' ', $info;
     3002            $_ eq 'undef' and $_ = undef foreach @a;
     3003            ($sf, $lf, $sa, $la) = @a;
     3004            # for Sony and ambiguous LensID, $info data may be incorrect:
     3005            # use only if it agrees with $focalLength and $maxAv (ref JR)
     3006            if ($mk eq 'Sony' and
     3007                (($focalLength and (($sf and $focalLength < $sf - 0.5) or
     3008                                    ($lf and $focalLength > $lf + 0.5))) or
     3009                 ($maxAv and (($sa and $maxAv < $sa - 0.15) or
     3010                              ($la and $maxAv > $la + 0.15)))))
     3011            {
     3012                undef $sf;
     3013                undef $lf;
     3014                undef $sa;
     3015                undef $la;
     3016            } elsif ($maxAv) {
     3017                # (using the short-focal-length max aperture in place of MaxAperture
     3018                # is a bad approximation, so don't do this if MaxApertureValue exists)
     3019                undef $sa;
     3020            }
     3021        }
     3022        if ($mk eq 'Pentax' and $id =~ /^\d+$/) {
     3023            # for Pentax, CS4 stores an int16u, but we use 2 x int8u
     3024            $id = join(' ', unpack('C*', pack('n', $id)));
     3025        }
     3026        # Nikon is a special case because Adobe doesn't store the full LensID
     3027        # (Apple Photos does, but we have to convert back to hex)
     3028        if ($mk eq 'Nikon') {
     3029            $id = sprintf('%X', $id);
     3030            $id = "0$id" if length($id) & 0x01;     # pad with leading 0 if necessary
     3031            $id =~ s/(..)/$1 /g and $id =~ s/ $//;  # put spaces between bytes
     3032            my (%newConv, %used);
     3033            my $i = 0;
     3034            foreach (grep /^$id/, keys %$printConv) {
     3035                my $lens = $$printConv{$_};
     3036                next if $used{$lens}; # avoid duplicates
     3037                $used{$lens} = 1;
     3038                $newConv{$i ? "$id.$i" : $id} = $lens;
     3039                ++$i;
     3040            }
     3041            $printConv = \%newConv;
     3042        }
     3043        my $str = $$printConv{$id} || "Unknown ($id)";
     3044        return Image::ExifTool::Exif::PrintLensID($et, $str, $printConv,
     3045                    undef, $id, $focalLength, $sa, $maxAv, $sf, $lf, $lensModel);
     3046    }
     3047    return "Unknown ($id)";
    24753048}
    24763049
     
    24993072    my $val = $_[0];
    25003073    $val =~ m{^(-?\d+)/(-?\d+)$} or return undef;
    2501     if ($2) {
     3074    if ($2 != 0) {
    25023075        $_[0] = $1 / $2; # calculate quotient
    25033076    } elsif ($1) {
     
    25073080    }
    25083081    return 1;
     3082}
     3083
     3084#------------------------------------------------------------------------------
     3085# Convert a string of floating point values to rationals
     3086# Inputs: 0) string of floating point numbers separated by spaces
     3087# Returns: string of rational numbers separated by spaces
     3088sub ConvertRationalList($)
     3089{
     3090    my $val = shift;
     3091    my @vals = split ' ', $val;
     3092    return $val unless @vals == 4;
     3093    foreach (@vals) {
     3094        ConvertRational($_) or return $val;
     3095    }
     3096    return join ' ', @vals;
    25093097}
    25103098
     
    25183106{
    25193107    local $_;
    2520     my ($exifTool, $tagTablePtr, $props, $val, $attrs) = @_;
    2521     my ($lang, @structProps);
    2522     my ($tag, $ns) = GetXMPTagID($props, $exifTool->{OPTIONS}{Struct} ? \@structProps : undef);
     3108    my ($et, $tagTablePtr, $props, $val, $attrs) = @_;
     3109    my ($lang, @structProps, $rawVal, $rational);
     3110    my ($tag, $ns) = GetXMPTagID($props, $$et{OPTIONS}{Struct} ? \@structProps : undef);
    25233111    return 0 unless $tag;   # ignore things that aren't valid tags
    25243112
    25253113    # translate namespace if necessary
    2526     $ns = $$xlatNamespace{$ns} if $$xlatNamespace{$ns};
    2527     my $info = $tagTablePtr->{$ns};
     3114    $ns = $stdXlatNS{$ns} if $stdXlatNS{$ns};
     3115    my $info = $$tagTablePtr{$ns};
    25283116    my ($table, $added, $xns, $tagID);
    25293117    if ($info) {
    2530         $table = $info->{SubDirectory}{TagTable} or warn "Missing TagTable for $tag!\n";
     3118        $table = $$info{SubDirectory}{TagTable} or warn "Missing TagTable for $tag!\n";
    25313119    } elsif ($$props[0] eq 'svg:svg') {
    25323120        if (not $ns) {
     
    25413129    }
    25423130
    2543     # look up this tag in the appropriate table
    2544     $table or $table = 'Image::ExifTool::XMP::other';
    2545     $tagTablePtr = GetTagTable($table);
    2546     if ($$tagTablePtr{NAMESPACE}) {
     3131    my $xmlGroups;
     3132    my $grp0 = $$tagTablePtr{GROUPS}{0};
     3133    if (not $ns and $grp0 ne 'XMP') {
    25473134        $tagID = $tag;
     3135    } elsif ($grp0 eq 'XML' and not $table) {
     3136        # this is an XML table (no namespace lookup)
     3137        $tagID = "$ns:$tag";
    25483138    } else {
    2549         # add XMP namespace prefix to avoid collisions in variable-namespace tables
    2550         $xns = $xmpNS{$ns} || $ns;
    2551         $tagID = "$xns:$tag";
    2552         # add namespace to top-level structure property
    2553         $structProps[0][0] = "$xns:" . $structProps[0][0] if @structProps;
     3139        $xmlGroups = 1 if $grp0 eq 'XML';
     3140        # look up this tag in the appropriate table
     3141        $table or $table = 'Image::ExifTool::XMP::other';
     3142        $tagTablePtr = GetTagTable($table);
     3143        if ($$tagTablePtr{NAMESPACE}) {
     3144            $tagID = $tag;
     3145        } else {
     3146            $xns = $xmpNS{$ns};
     3147            unless (defined $xns) {
     3148                $xns = $ns;
     3149                # validate namespace prefix
     3150                unless ($ns =~ /^[A-Z_a-z\x80-\xff][-.0-9A-Z_a-z\x80-\xff]*$/ or $ns eq '') {
     3151                    $et->Warn("Invalid XMP namespace prefix '${ns}'");
     3152                    # clean up prefix for use as an ExifTool group name
     3153                    $ns =~ tr/-.0-9A-Z_a-z\x80-\xff//dc;
     3154                    $ns =~ /^[A-Z_a-z\x80-\xff]/ or $ns = "ns_$ns";
     3155                    $stdXlatNS{$xns} = $ns;
     3156                    $xmpNS{$ns} = $xns;
     3157                }
     3158            }
     3159            # add XMP namespace prefix to avoid collisions in variable-namespace tables
     3160            $tagID = "$xns:$tag";
     3161            # add namespace to top-level structure property
     3162            $structProps[0][0] = "$xns:" . $structProps[0][0] if @structProps;
     3163        }
    25543164    }
    2555     my $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tagID);
     3165    my $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID);
    25563166
    25573167    $lang = $$attrs{'xml:lang'} if $attrs;
     
    25733183            next unless ref $ti eq 'HASH' and $$ti{Struct};
    25743184            $addedFlat = AddFlattenedTags($tagTablePtr, $t);
    2575             if ($tagInfo) {
    2576                 # all done if we just wanted to initialize the flattened tag
    2577                 if ($$tagInfo{Flat}) {
    2578                     warn "Orphan tagInfo with Flat flag set: $$tagInfo{Name}\n";
    2579                     delete $$tagInfo{Flat};
    2580                 }
    2581                 last NoLoop;
    2582             }
    25833185            # all done if we generated the tag we are looking for
    25843186            $tagInfo = $$tagTablePtr{$tagID} and last NoLoop if $addedFlat;
     
    25973199                next unless ref $ti eq 'HASH';
    25983200                my $strTable = $$ti{Struct} or next;
    2599                 $name = $$ti{Name} . ucfirst($t2);
     3201                my $flat = (defined $$ti{FlatName} ? $$ti{FlatName} : $$ti{Name});
     3202                $name = $flat . ucfirst($t2);
    26003203                # don't continue if structure is known but field is not
    26013204                last if $$strTable{NAMESPACE} or not exists $$strTable{NAMESPACE};
     
    26053208                my $n = $nsList[$i+1];  # namespace of structure field
    26063209                # translate to standard ExifTool namespace
    2607                 $n = $$xlatNamespace{$n} if $$xlatNamespace{$n};
     3210                $n = $stdXlatNS{$n} if $stdXlatNS{$n};
    26083211                my $xn = $xmpNS{$n} || $n;  # standard XMP namespace
    26093212                # no need to continue with variable-namespace logic if
     
    26203223                last unless ref $tg eq 'HASH' and $$tg{SubDirectory};
    26213224                my $tbl = GetTagTable($$tg{SubDirectory}{TagTable}) or last;
    2622                 my $sti = $exifTool->GetTagInfo($tbl, $t2);
     3225                my $sti = $et->GetTagInfo($tbl, $t2);
    26233226                if (not $sti or $$sti{Flat}) {
    26243227                    # again, we must initialize flattened tags if necessary
    26253228                    # (but don't bother to recursively apply full logic to
    2626                     #  allow nest variable-namespace strucures until someone
     3229                    #  allow nested variable-namespace strucures until someone
    26273230                    #  actually wants to do such a silly thing)
    26283231                    my $t3 = '';
     
    26373240                    last unless $sti;
    26383241                }
    2639                 $tagInfo = {
    2640                     %$sti,
    2641                     Name     => $$ti{Name} . $$sti{Name},
    2642                     WasAdded => 1,
    2643                 };
     3242                # generate new tagInfo hash based on existing top-level tag
     3243                $tagInfo = { %$sti, Name => $flat . $$sti{Name} };
    26443244                # be careful not to copy elements we shouldn't...
    26453245                delete $$tagInfo{Description}; # Description will be different
     
    26513251            }
    26523252        }
    2653         $tagInfo or $tagInfo = { Name => $name, WasAdded => 1 };
     3253        # generate a default tagInfo hash if necessary
     3254        $tagInfo or $tagInfo = { Name => $name, IsDefault => 1, Priority => 0 };
    26543255
    26553256        # add tag Namespace entry for tags in variable-namespace tables
    26563257        $$tagInfo{Namespace} = $xns if $xns;
    2657         if ($curNS{$ns} and $curNS{$ns} =~ m{^http://ns.exiftool.ca/(.*?)/(.*?)/}) {
     3258        if ($$et{curURI}{$ns} and $$et{curURI}{$ns} =~ m{^http://ns.exiftool.(?:ca|org)/(.*?)/(.*?)/}) {
    26583259            my %grps = ( 0 => $1, 1 => $2 );
    26593260            # apply a little magic to recover original group names
     
    26833284        #    $$tagInfo{List} = 1;
    26843285        }
    2685         Image::ExifTool::AddTagToTable($tagTablePtr, $tagID, $tagInfo);
    2686         $added = 1;
     3286        # save property list for verbose "adding" message unless this tag already exists
     3287        $added = \@tagList unless $$tagTablePtr{$tagID};
     3288        AddTagToTable($tagTablePtr, $tagID, $tagInfo);
    26873289        last;
    26883290    }
     
    27173319    }
    27183320    # decode from UTF8
    2719     $val = $exifTool->Decode($val, 'UTF8');
     3321    $val = $et->Decode($val, 'UTF8');
    27203322    # convert rational and date values to a more sensible format
    27213323    my $fmt = $$tagInfo{Writable};
    2722     my $new = $$tagInfo{WasAdded};
    2723     if (($fmt or $new)) {
    2724         unless (($new or $fmt eq 'rational') and ConvertRational($val)) {
     3324    my $new = $$tagInfo{IsDefault} && $$et{OPTIONS}{XMPAutoConv};
     3325    if ($fmt or $new) {
     3326        $rawVal = $val; # save raw value for verbose output
     3327        if (($new or $fmt eq 'rational') and ConvertRational($val)) {
     3328            $rational = $rawVal;
     3329        } else {
    27253330            $val = ConvertXMPDate($val, $new) if $new or $fmt eq 'date';
    27263331        }
     3332        if ($$et{XmpValidate} and $fmt and $fmt eq 'boolean') {
     3333            $et->WarnOnce("Boolean value for XMP-$ns:$$tagInfo{Name} should be capitalized",1);
     3334        }
     3335        # protect against large binary data in unknown tags
     3336        $$tagInfo{Binary} = 1 if $new and length($val) > 65536;
    27273337    }
    27283338    # store the value for this tag
    2729     my $key = $exifTool->FoundTag($tagInfo, $val);
     3339    my $key = $et->FoundTag($tagInfo, $val) or return 0;
     3340    # save original components of rational numbers (used when copying)
     3341    $$et{RATIONAL}{$key} = $rational if defined $rational;
     3342    # allow read-only subdirectories (eg. embedded base64 XMP/IPTC in NKSC files)
     3343    if ($$tagInfo{SubDirectory} and not $$et{IsWriting}) {
     3344        my $subdir = $$tagInfo{SubDirectory};
     3345        my $dataPt = ref $$et{VALUE}{$key} ? $$et{VALUE}{$key} : \$$et{VALUE}{$key};
     3346        # process subdirectory information
     3347        my %dirInfo = (
     3348            DirName  => $$subdir{DirName} || $$tagInfo{Name},
     3349            DataPt   => $dataPt,
     3350            DirLen   => length $$dataPt,
     3351            IsExtended => 1, # (hack to avoid Duplicate warning for embedded XMP)
     3352        );
     3353        my $oldOrder = GetByteOrder();
     3354        SetByteOrder($$subdir{ByteOrder}) if $$subdir{ByteOrder};
     3355        my $oldNS = $$et{definedNS};
     3356        delete $$et{definedNS};
     3357        my $subTablePtr = GetTagTable($$subdir{TagTable}) || $tagTablePtr;
     3358        $et->ProcessDirectory(\%dirInfo, $subTablePtr, $$subdir{ProcessProc});
     3359        SetByteOrder($oldOrder);
     3360        $$et{definedNS} = $oldNS;
     3361    }
    27303362    # save structure/list information if necessary
    2731     if (@structProps and (@structProps > 1 or defined $structProps[0][1])) {
    2732         $exifTool->{TAG_EXTRA}{$key}{Struct} = \@structProps;
    2733         $exifTool->{IsStruct} = 1;
     3363    if (@structProps and (@structProps > 1 or defined $structProps[0][1]) and
     3364        not $$et{NO_STRUCT})
     3365    {
     3366        $$et{TAG_EXTRA}{$key}{Struct} = \@structProps;
     3367        $$et{IsStruct} = 1;
    27343368    }
    2735     if ($ns and not $$tagInfo{StaticGroup1}) {
     3369    if ($xmlGroups) {
     3370        $et->SetGroup($key, 'XML', 0);
     3371        $et->SetGroup($key, "XML-$ns", 1);
     3372    } elsif ($ns and not $$tagInfo{StaticGroup1}) {
    27363373        # set group1 dynamically according to the namespace
    2737         $exifTool->SetGroup($key, "$tagTablePtr->{GROUPS}{0}-$ns");
     3374        $et->SetGroup($key, "$$tagTablePtr{GROUPS}{0}-$ns");
    27383375    }
    2739     if ($exifTool->{OPTIONS}{Verbose}) {
     3376    if ($$et{OPTIONS}{Verbose}) {
    27403377        if ($added) {
    2741             my $g1 = $exifTool->GetGroup($key, 1);
    2742             $exifTool->VPrint(0, $exifTool->{INDENT}, "[adding $g1:$tag]\n");
     3378            my $props;
     3379            if (@$added > 1) {
     3380                $$tagInfo{Flat} = 0;    # this is a flattened tag
     3381                my @props = map { $$_[0] } @$added;
     3382                $props = ' (' . join('/',@props) . ')';
     3383            } else {
     3384                $props = '';
     3385            }
     3386            my $g1 = $et->GetGroup($key, 1);
     3387            $et->VPrint(0, $$et{INDENT}, "[adding $g1:$tag]$props\n");
    27433388        }
    27443389        my $tagID = join('/',@$props);
    2745         $exifTool->VerboseInfo($tagID, $tagInfo, Value=>$val);
     3390        $et->VerboseInfo($tagID, $tagInfo, Value => $rawVal || $val);
    27463391    }
    27473392    return 1;
     
    27503395#------------------------------------------------------------------------------
    27513396# Recursively parse nested XMP data element
    2752 # Inputs: 0) ExifTool object reference
    2753 #         1) Pointer to tag table
    2754 #         2) reference to XMP data
    2755 #         3) start of xmp element
    2756 #         4) reference to array of enclosing XMP property names (undef if none)
    2757 #         5) reference to blank node information hash
     3397# Inputs: 0) ExifTool ref, 1) tag table ref, 2) XMP data ref
     3398#         3) offset to start of XMP element, 4) offset to end of XMP element
     3399#         5) reference to array of enclosing XMP property names (undef if none)
     3400#         6) reference to blank node information hash
    27583401# Returns: Number of contained XMP elements
    2759 sub ParseXMPElement($$$;$$$)
     3402sub ParseXMPElement($$$;$$$$)
    27603403{
    2761     my ($exifTool, $tagTablePtr, $dataPt, $start, $propListPt, $blankInfo) = @_;
     3404    local $_;
     3405    my ($et, $tagTablePtr, $dataPt, $start, $end, $propList, $blankInfo) = @_;
    27623406    my ($count, $nItems) = (0, 0);
    2763     my $isWriting = $exifTool->{XMP_CAPTURE};
    2764     my $isSVG = $$exifTool{XMP_IS_SVG};
     3407    my $isWriting = $$et{XMP_CAPTURE};
     3408    my $isSVG = $$et{XMP_IS_SVG};
     3409    my $saveNS;     # save xlatNS lookup if changed for the scope of this element
     3410    my (%definedNS, %usedNS);  # namespaces defined and used in this scope
    27653411
    27663412    # get our parse procs
    27673413    my ($attrProc, $foundProc);
    2768     if ($$exifTool{XMPParseOpts}) {
    2769         $attrProc = $$exifTool{XMPParseOpts}{AttrProc};
    2770         $foundProc = $$exifTool{XMPParseOpts}{FoundProc} || \&FoundXMP;
     3414    if ($$et{XMPParseOpts}) {
     3415        $attrProc = $$et{XMPParseOpts}{AttrProc};
     3416        $foundProc = $$et{XMPParseOpts}{FoundProc} || \&FoundXMP;
    27713417    } else {
    27723418        $foundProc = \&FoundXMP;
    27733419    }
    27743420    $start or $start = 0;
    2775     $propListPt or $propListPt = [ ];
     3421    $end or $end = length $$dataPt;
     3422    $propList or $propList = [ ];
    27763423
    27773424    my $processBlankInfo;
     
    27803427    # keep track of current nodeID at this nesting level
    27813428    my $oldNodeID = $$blankInfo{NodeID};
    2782 
    27833429    pos($$dataPt) = $start;
     3430
     3431    # lookup for translating namespace prefixes
     3432    my $xlatNS = $$et{xlatNS};
     3433
    27843434    Element: for (;;) {
     3435        # all done if there isn't enough data for another element
     3436        # (the smallest possible element is 4 bytes, eg. "<a/>")
     3437        last if pos($$dataPt) > $end - 4;
    27853438        # reset nodeID before processing each element
    27863439        my $nodeID = $$blankInfo{NodeID} = $oldNodeID;
    27873440        # get next element
    2788         last unless $$dataPt =~ m/<([-\w:.\x80-\xff]+)(.*?)>/sg;
    2789         my ($prop, $attrs) = ($1, $2);
    2790         my $val = '';
     3441        last if $$dataPt !~ m{<([?/]?)([-\w:.\x80-\xff]+|!--)([^>]*)>}sg or pos($$dataPt) > $end;
     3442        # (the only reason we match '<[?/]' is to keep from scanning past the
     3443        #  "<?xpacket end..." terminator or other closing token, so
     3444        next if $1;
     3445        my ($prop, $attrs) = ($2, $3);
     3446        # skip comments
     3447        if ($prop eq '!--') {
     3448            next if $attrs =~ /--$/ or $$dataPt =~ /-->/sg;
     3449            last;
     3450        }
     3451        my $valStart = pos($$dataPt);
     3452        my $valEnd;
    27913453        # only look for closing token if this is not an empty element
    2792         # (empty elements end with '/', ie. <a:b/>)
     3454        # (empty elements end with '/', eg. <a:b/>)
    27933455        if ($attrs !~ s/\/$//) {
    27943456            my $nesting = 1;
    27953457            for (;;) {
    27963458# this match fails with perl 5.6.2 (perl bug!), but it works without
    2797 # the '(.*?)', so do it the hard way instead...
     3459# the '(.*?)', so we must do it differently...
    27983460#                $$dataPt =~ m/(.*?)<\/$prop>/sg or last Element;
    27993461#                my $val2 = $1;
    2800                 my $pos = pos($$dataPt);
    2801                 unless ($$dataPt =~ m/<\/$prop>/sg) {
    2802                     $exifTool->Warn("XMP format error (no closing tag for $prop)");
     3462                # find next matching closing token, or the next opening token
     3463                # of a nested same-named element
     3464                if ($$dataPt !~ m{<(/?)$prop([-\w:.\x80-\xff]*)(.*?(/?))>}sg or
     3465                    pos($$dataPt) > $end)
     3466                {
     3467                    $et->Warn("XMP format error (no closing tag for $prop)");
    28033468                    last Element;
    28043469                }
    2805                 my $len = pos($$dataPt) - $pos - length($prop) - 3;
    2806                 my $val2 = substr($$dataPt, $pos, $len);
    2807                 # increment nesting level for each contained similar opening token
    2808                 ++$nesting while $val2 =~ m/<$prop\b.*?(\/?)>/sg and $1 ne '/';
    2809                 $val .= $val2;
    2810                 --$nesting or last;
    2811                 $val .= "</$prop>";
    2812             }
    2813         }
    2814         my $parseResource;
     3470                next if $2; # ignore opening properties with different names
     3471                if ($1) {
     3472                    next if --$nesting;
     3473                    $valEnd = pos($$dataPt) - length($prop) - length($3) - 3;
     3474                    last;   # this element is complete
     3475                }
     3476                # this is a nested opening token (or empty element)
     3477                ++$nesting unless $4;
     3478            }
     3479        } else {
     3480            $valEnd = $valStart;
     3481        }
     3482        $start = pos($$dataPt);         # start from here the next time around
     3483
     3484        # extract property attributes
     3485        my ($parseResource, %attrs, @attrs);
     3486        while ($attrs =~ m/(\S+?)\s*=\s*(['"])(.*?)\2/sg) {
     3487            my ($attr, $val) = ($1, $3);
     3488            # handle namespace prefixes (defined by xmlns:PREFIX, or used with PREFIX:tag)
     3489            if ($attr =~ /(.*?):/) {
     3490                if ($1 eq 'xmlns') {
     3491                    my $ns = substr($attr, 6);
     3492                    my $stdNS = $uri2ns{$val};
     3493                    # keep track of namespace prefixes defined in this scope (for Validate)
     3494                    $$et{definedNS}{$ns} = $definedNS{$ns} = 1 unless $$et{definedNS}{$ns};
     3495                    unless ($stdNS) {
     3496                        my $try = $val;
     3497                        # patch for Nikon NX2 URI bug for Microsoft PhotoInfo namespace
     3498                        $try =~ s{/$}{} or $try .= '/';
     3499                        $stdNS = $uri2ns{$try};
     3500                        if ($stdNS) {
     3501                            $val = $try;
     3502                            $et->WarnOnce("Fixed incorrect URI for xmlns:$ns", 1);
     3503                        } else {
     3504                            # look for same namespace with different version number
     3505                            $try = quotemeta $val; # (note: escapes slashes too)
     3506                            $try =~ s{\\/\d+\\\.\d+(\\/|$)}{\\/\\d+\\\.\\d+$1};
     3507                            my ($good) = grep /^$try$/, keys %uri2ns;
     3508                            if ($good) {
     3509                                $stdNS = $uri2ns{$good};
     3510                                $et->VPrint(0, $$et{INDENT}, "[different $stdNS version: $val]\n");
     3511                            }
     3512                        }
     3513                    }
     3514                    # tame wild namespace prefixes (patches Microsoft stupidity)
     3515                    my $newNS;
     3516                    if ($stdNS) {
     3517                        # use standard namespace prefix if pre-defined
     3518                        if ($stdNS ne $ns) {
     3519                            $newNS = $stdNS;
     3520                        } elsif ($$xlatNS{$ns}) {
     3521                            # this prefix is re-defined to the standard prefix in this scope
     3522                            $newNS = '';
     3523                        }
     3524                    } elsif ($$et{curNS}{$val}) {
     3525                        # use a consistent prefix over the entire XMP for a given namespace URI
     3526                        $newNS = $$et{curNS}{$val} if $$et{curNS}{$val} ne $ns;
     3527                    } else {
     3528                        my $curURI = $$et{curURI};
     3529                        my $curNS = $$et{curNS};
     3530                        my $usedNS = $ns;
     3531                        # use unique prefixes for all namespaces across the entire XMP
     3532                        if ($$curURI{$ns} or $nsURI{$ns}) {
     3533                            # generate a temporary namespace prefix to resolve any conflict
     3534                            my $i = 0;
     3535                            ++$i while $$curURI{"tmp$i"};
     3536                            $newNS = $usedNS = "tmp$i";
     3537                        }
     3538                        # keep track of the namespace prefixes and URI's used in this XMP
     3539                        $$curNS{$val} = $usedNS;
     3540                        $$curURI{$usedNS} = $val;
     3541                    }
     3542                    if (defined $newNS) {
     3543                        # save translation used in containing scope if necessary
     3544                        # create new namespace translation for the scope of this element
     3545                        $saveNS or $saveNS = $xlatNS, $xlatNS = $$et{xlatNS} = { %$xlatNS };
     3546                        if (length $newNS) {
     3547                            # use the new namespace prefix
     3548                            $$xlatNS{$ns} = $newNS;
     3549                            $attr = 'xmlns:' . $newNS;
     3550                            # must go through previous attributes and change prefixes if necessary
     3551                            foreach (@attrs) {
     3552                                next unless /(.*?):/ and $1 eq $ns and $1 ne $newNS;
     3553                                my $newAttr = $newNS . substr($_, length($ns));
     3554                                $attrs{$newAttr} = $attrs{$_};
     3555                                delete $attrs{$_};
     3556                                $_ = $newAttr;
     3557                            }
     3558                        } else {
     3559                            delete $$xlatNS{$ns};
     3560                        }
     3561                    }
     3562                } else {
     3563                    $attr = $$xlatNS{$1} . substr($attr, length($1)) if $$xlatNS{$1};
     3564                    $usedNS{$1} = 1;
     3565                }
     3566            }
     3567            push @attrs, $attr;    # preserve order
     3568            $attrs{$attr} = $val;
     3569        }
     3570        if ($prop =~ /(.*?):/) {
     3571            $usedNS{$1} = 1;
     3572            # tame wild namespace prefixes (patch for Microsoft stupidity)
     3573            $prop = $$xlatNS{$1} . substr($prop, length($1)) if $$xlatNS{$1};
     3574        }
     3575
    28153576        if ($prop eq 'rdf:li') {
     3577            # impose a reasonable maximum on the number of items in a list
     3578            if ($nItems == 1000) {
     3579                my ($tg,$ns) = GetXMPTagID($propList);
     3580                if ($isWriting) {
     3581                    $et->Warn("Excessive number of items for $ns:$tg. Processing may be slow", 1);
     3582                } elsif (not $$et{OPTIONS}{IgnoreMinorErrors}) {
     3583                    $et->Warn("Extracted only 1000 $ns:$tg items. Ignore minor errors to extract all", 2);
     3584                    last;
     3585                }
     3586            }
    28163587            # add index to list items so we can keep them in order
    28173588            # (this also enables us to keep structure elements grouped properly
     
    28233594            # 10,11,12-19,210,211-299,3100,3101-3999,41000...9999999999.
    28243595            $prop .= ' ' . length($nItems) . $nItems;
     3596            # reset LIST_TAGS at the start of the outtermost list
     3597            # (avoids accumulating incorrectly-written elements in a correctly-written list)
     3598            if (not $nItems and not grep /^rdf:li /, @$propList) {
     3599                $$et{LIST_TAGS} = { };
     3600            }
    28253601            ++$nItems;
    28263602        } elsif ($prop eq 'rdf:Description') {
    2827             # trim comments and whitespace from rdf:Description properties only
    2828             $val =~ s/<!--.*?-->//g;
    2829             $val =~ s/^\s*(.*)\s*$/$1/;
    28303603            # remove unnecessary rdf:Description elements since parseType='Resource'
    28313604            # is more efficient (also necessary to make property path consistent)
    2832             $parseResource = 1 if grep /^rdf:Description$/, @$propListPt;
     3605            $parseResource = 1 if grep /^rdf:Description$/, @$propList;
    28333606        } elsif ($prop eq 'xmp:xmpmeta') {
    28343607            # patch MicrosoftPhoto unconformity
    28353608            $prop = 'x:xmpmeta';
    2836         }
    2837 
    2838         # extract property attributes
    2839         my (%attrs, @attrs);
    2840         while ($attrs =~ m/(\S+?)\s*=\s*(['"])(.*?)\2/sg) {
    2841             push @attrs, $1;    # preserve order
    2842             $attrs{$1} = $3;
     3609            $et->Warn('Wrong namespace for xmpmeta') if $$et{XmpValidate};
    28433610        }
    28443611
    28453612        # hook for special parsing of attributes
    2846         $attrProc and &$attrProc(\@attrs, \%attrs, \$prop, \$val);
    2847            
     3613        my $val;
     3614        if ($attrProc) {
     3615            $val = substr($$dataPt, $valStart, $valEnd - $valStart);
     3616            if (&$attrProc(\@attrs, \%attrs, \$prop, \$val)) {
     3617                # the value was changed, so reset $valStart/$valEnd to use $val instead
     3618                $valStart = $valEnd;
     3619            }
     3620        }
     3621
    28483622        # add nodeID to property path (with leading ' #') if it exists
    28493623        if (defined $attrs{'rdf:nodeID'}) {
     
    28553629
    28563630        # push this property name onto our hierarchy list
    2857         push @$propListPt, $prop unless $parseResource;
     3631        push @$propList, $prop unless $parseResource;
    28583632
    28593633        if ($isSVG) {
    28603634            # ignore everything but top level SVG tags and metadata unless Unknown set
    2861             unless ($exifTool->{OPTIONS}{Unknown} > 1 or $exifTool->{OPTIONS}{Verbose}) {
    2862                 if (@$propListPt > 1 and $$propListPt[1] !~ /\b(metadata|desc|title)$/) {
    2863                     pop @$propListPt;
     3635            unless ($$et{OPTIONS}{Unknown} > 1 or $$et{OPTIONS}{Verbose}) {
     3636                if (@$propList > 1 and $$propList[1] !~ /\b(metadata|desc|title)$/) {
     3637                    pop @$propList;
    28643638                    next;
    28653639                }
     
    28673641            if ($prop eq 'svg' or $prop eq 'metadata') {
    28683642                # add svg namespace prefix if missing to ignore these entries in the tag name
    2869                 $$propListPt[-1] = "svg:$prop";
     3643                $$propList[-1] = "svg:$prop";
     3644            }
     3645        } elsif ($$et{XmpIgnoreProps}) { # ignore specified properties for tag name
     3646            foreach (@{$$et{XmpIgnoreProps}}) {
     3647                last unless @$propList;
     3648                pop @$propList if $_ eq $$propList[0];
    28703649            }
    28713650        }
     
    28753654        my ($shortName, $shorthand, $ignored);
    28763655        foreach $shortName (@attrs) {
     3656            next unless defined $attrs{$shortName};
    28773657            my $propName = $shortName;
    28783658            my ($ns, $name);
     
    28913671                $name = $propName;
    28923672            }
    2893             # keep track of the namespace prefixes used
    2894             if ($ns eq 'xmlns') {
    2895                 unless ($attrs{$shortName}) {
    2896                     $exifTool->WarnOnce("Duplicate namespace '$shortName'");
    2897                     next;
    2898                 }
    2899                 $curNS{$name} = $attrs{$shortName};
    2900                 my $stdNS = $uri2ns{$attrs{$shortName}};
    2901                 # translate namespace if non-standard (except 'x' and 'iX')
    2902                 if ($stdNS and $name ne $stdNS and $stdNS ne 'x' and $stdNS ne 'iX') {
    2903                     # make a copy of the standard translations so we can modify it
    2904                     $xlatNamespace = { %stdXlatNS } if $xlatNamespace eq \%stdXlatNS;
    2905                     # translate this namespace prefix to the standard version
    2906                     $$xlatNamespace{$name} = $stdXlatNS{$stdNS} || $stdNS;
     3673            if ($propName eq 'rdf:about') {
     3674                if (not $$et{XmpAbout}) {
     3675                    $$et{XmpAbout} = $attrs{$shortName};
     3676                } elsif ($$et{XmpAbout} ne $attrs{$shortName}) {
     3677                    if ($isWriting) {
     3678                        my $str = "Different 'rdf:about' attributes not handled";
     3679                        unless ($$et{WARNED_ONCE}{$str}) {
     3680                            $et->Error($str, 1);
     3681                            $$et{WARNED_ONCE}{$str} = 1;
     3682                        }
     3683                    } elsif ($$et{XmpValidate}) {
     3684                        $et->WarnOnce("Different 'rdf:about' attributes");
     3685                    }
    29073686                }
    29083687            }
     
    29123691                    my $stdNS = $uri2ns{$attrs{$shortName}};
    29133692                    unless ($stdNS and ($stdNS eq 'x' or $stdNS eq 'iX')) {
    2914                         my $nsUsed = $exifTool->{XMP_NS};
     3693                        my $nsUsed = $$et{XMP_NS};
    29153694                        $$nsUsed{$name} = $attrs{$shortName} unless defined $$nsUsed{$name};
    29163695                    }
     
    29183697                    next;
    29193698                } elsif ($recognizedAttrs{$propName}) {
    2920                     # save UUID to use same ID when writing
    2921                     if ($propName eq 'rdf:about') {
    2922                         if (not $exifTool->{XMP_ABOUT}) {
    2923                             $exifTool->{XMP_ABOUT} = $attrs{$shortName};
    2924                         } elsif ($exifTool->{XMP_ABOUT} ne $attrs{$shortName}) {
    2925                             $exifTool->Error("Different 'rdf:about' attributes not handled", 1);
    2926                         }
    2927                     }
    29283699                    next;
    29293700                }
    29303701            }
    29313702            my $shortVal = $attrs{$shortName};
    2932             if ($ignoreNamespace{$ns}) {
     3703            if ($ignoreNamespace{$ns} or $ignoreProp{$prop}) {
    29333704                $ignored = $propName;
    29343705                # handle special attributes (extract as tags only once if not empty)
    29353706                if (ref $recognizedAttrs{$propName} and $shortVal) {
    29363707                    my ($tbl, $id, $name) = @{$recognizedAttrs{$propName}};
    2937                     my $val = UnescapeXML($shortVal);
    2938                     unless (defined $$exifTool{VALUE}{$name} and $$exifTool{VALUE}{$name} eq $val) {
    2939                         $exifTool->HandleTag(GetTagTable($tbl), $id, $val);
     3708                    my $tval = UnescapeXML($shortVal);
     3709                    unless (defined $$et{VALUE}{$name} and $$et{VALUE}{$name} eq $tval) {
     3710                        $et->HandleTag(GetTagTable($tbl), $id, $tval);
    29403711                    }
    29413712                }
     
    29433714            }
    29443715            delete $attrs{$shortName};  # don't re-use this attribute
    2945             push @$propListPt, $propName;
     3716            push @$propList, $propName;
    29463717            # save this shorthand XMP property
    29473718            if (defined $nodeID) {
    2948                 SaveBlankInfo($blankInfo, $propListPt, $shortVal);
     3719                SaveBlankInfo($blankInfo, $propList, $shortVal);
    29493720            } elsif ($isWriting) {
    2950                 CaptureXMP($exifTool, $propListPt, $shortVal);
     3721                CaptureXMP($et, $propList, $shortVal);
    29513722            } else {
    2952                 &$foundProc($exifTool, $tagTablePtr, $propListPt, $shortVal);
    2953             }
    2954             pop @$propListPt;
     3723                ValidateProperty($et, $propList) if $$et{XmpValidate};
     3724                &$foundProc($et, $tagTablePtr, $propList, $shortVal);
     3725            }
     3726            pop @$propList;
    29553727            $shorthand = 1;
    29563728        }
    29573729        if ($isWriting) {
    2958             if (ParseXMPElement($exifTool, $tagTablePtr, \$val, 0, $propListPt, $blankInfo)) {
    2959                 # undefine value since we found more properties within this one
    2960                 undef $val;
     3730            if (ParseXMPElement($et, $tagTablePtr, $dataPt, $valStart, $valEnd,
     3731                                $propList, $blankInfo))
     3732            {
     3733                # (no value since we found more properties within this one)
    29613734                # set an error on any ignored attributes here, because they will be lost
    2962                 $exifTool->{XMP_ERROR} = "Can't handle XMP attribute '$ignored'" if $ignored;
    2963             }
    2964             if (defined $val and (length $val or not $shorthand)) {
     3735                $$et{XMP_ERROR} = "Can't handle XMP attribute '${ignored}'" if $ignored;
     3736            } elsif (not $shorthand or $valEnd != $valStart) {
     3737                $val = substr($$dataPt, $valStart, $valEnd - $valStart);
     3738                # remove comments and whitespace from rdf:Description only
     3739                if ($prop eq 'rdf:Description') {
     3740                    $val =~ s/<!--.*?-->//g; $val =~ s/^\s+//; $val =~ s/\s+$//;
     3741                }
    29653742                if (defined $nodeID) {
    2966                     SaveBlankInfo($blankInfo, $propListPt, $val, \%attrs);
     3743                    SaveBlankInfo($blankInfo, $propList, $val, \%attrs);
    29673744                } else {
    2968                     CaptureXMP($exifTool, $propListPt, $val, \%attrs);
     3745                    CaptureXMP($et, $propList, $val, \%attrs);
    29693746                }
    29703747            }
    29713748        } else {
    2972             # if element value is empty, take value from 'resource' attribute
    2973             # (preferentially) or 'about' attribute (if no 'resource')
    2974             my $wasEmpty;
    2975             if ($val eq '' and ($attrs =~ /\bresource=(['"])(.*?)\1/ or
    2976                                 $attrs =~ /\babout=(['"])(.*?)\1/))
     3749            # look for additional elements contained within this one
     3750            if ($valStart == $valEnd or
     3751                !ParseXMPElement($et, $tagTablePtr, $dataPt, $valStart, $valEnd,
     3752                                 $propList, $blankInfo))
    29773753            {
    2978                 $val = $2;
    2979                 $wasEmpty = 1;
    2980             }
    2981             # look for additional elements contained within this one
    2982             if (!ParseXMPElement($exifTool, $tagTablePtr, \$val, 0, $propListPt, $blankInfo)) {
     3754                my $wasEmpty;
     3755                unless (defined $val) {
     3756                    $val = substr($$dataPt, $valStart, $valEnd - $valStart);
     3757                    # remove comments and whitespace from rdf:Description only
     3758                    if ($prop eq 'rdf:Description' and $val) {
     3759                        $val =~ s/<!--.*?-->//g; $val =~ s/^\s+//; $val =~ s/\s+$//;
     3760                    }
     3761                    # if element value is empty, take value from RDF 'value' or 'resource' attribute
     3762                    # (preferentially) or 'about' attribute (if no 'value' or 'resource')
     3763                    if ($val eq '' and ($attrs =~ /\brdf:(?:value|resource)=(['"])(.*?)\1/ or
     3764                                        $attrs =~ /\brdf:about=(['"])(.*?)\1/))
     3765                    {
     3766                        $val = $2;
     3767                        $wasEmpty = 1;
     3768                    }
     3769                }
    29833770                # there are no contained elements, so this must be a simple property value
    29843771                # (unless we already extracted shorthand values from this element)
    29853772                if (length $val or not $shorthand) {
    2986                     my $lastProp = $$propListPt[-1];
     3773                    my $lastProp = $$propList[-1];
    29873774                    if (defined $nodeID) {
    2988                         SaveBlankInfo($blankInfo, $propListPt, $val);
     3775                        SaveBlankInfo($blankInfo, $propList, $val);
    29893776                    } elsif ($lastProp eq 'rdf:type' and $wasEmpty) {
    29903777                        # do not extract empty structure types (for now)
    29913778                    } elsif ($lastProp =~ /^et:(desc|prt|val)$/ and ($count or $1 eq 'desc')) {
    2992                         # ignore et:desc, and et:val if preceeded by et:prt
     3779                        # ignore et:desc, and et:val if preceded by et:prt
    29933780                        --$count;
    29943781                    } else {
    2995                         &$foundProc($exifTool, $tagTablePtr, $propListPt, $val, \%attrs);
     3782                        ValidateProperty($et, $propList, \%attrs) if $$et{XmpValidate};
     3783                        &$foundProc($et, $tagTablePtr, $propList, $val, \%attrs);
    29963784                    }
    29973785                }
    29983786            }
    29993787        }
    3000         pop @$propListPt unless $parseResource;
     3788        pop @$propList unless $parseResource;
    30013789        ++$count;
     3790
     3791        # validate namespace prefixes used at this level if necessary
     3792        if ($$et{XmpValidate}) {
     3793            foreach (sort keys %usedNS) {
     3794                next if $$et{definedNS}{$_} or $_ eq 'xml';
     3795                if (defined $$et{definedNS}{$_}) {
     3796                    $et->Warn("XMP namespace $_ is used out of scope");
     3797                } else {
     3798                    $et->Warn("Undefined XMP namespace: $_");
     3799                }
     3800                $$et{definedNS}{$_} = -1;  # (don't warn again for this namespace)
     3801            }
     3802            # reset namespaces that went out of scope
     3803            $$et{definedNS}{$_} = 0 foreach keys %definedNS;
     3804            undef %usedNS;
     3805            undef %definedNS;
     3806        }
     3807
     3808        last if $start >= $end;
     3809        pos($$dataPt) = $start;
     3810        $$dataPt =~ /\G\s+/gc;  # skip white space after closing token
    30023811    }
    30033812#
     
    30053814#
    30063815    if ($processBlankInfo and %{$$blankInfo{Prop}}) {
    3007         ProcessBlankInfo($exifTool, $tagTablePtr, $blankInfo, $isWriting);
     3816        ProcessBlankInfo($et, $tagTablePtr, $blankInfo, $isWriting);
    30083817        %$blankInfo = ();   # free some memory
    30093818    }
     3819    # restore namespace lookup from the containing scope
     3820    $$et{xlatNS} = $saveNS if $saveNS;
     3821
    30103822    return $count;  # return the number of elements found at this level
    30113823}
     
    30273839sub ProcessXMP($$;$)
    30283840{
    3029     my ($exifTool, $dirInfo, $tagTablePtr) = @_;
     3841    my ($et, $dirInfo, $tagTablePtr) = @_;
    30303842    my $dataPt = $$dirInfo{DataPt};
    3031     my ($dirStart, $dirLen, $dataLen);
     3843    my ($dirStart, $dirLen, $dataLen, $double);
    30323844    my ($buff, $fmt, $hasXMP, $isXML, $isRDF, $isSVG);
    30333845    my $rtnVal = 0;
    30343846    my $bom = 0;
    3035     undef %curNS;
     3847
     3848    # namespaces and prefixes currently in effect while parsing the file,
     3849    # and lookup to translate brain-dead-Microsoft-Photo-software prefixes
     3850    $$et{curURI} = { };
     3851    $$et{curNS}  = { };
     3852    $$et{xlatNS} = { };
     3853    $$et{definedNS} = { };
     3854    delete $$et{XmpAbout};
     3855    delete $$et{XmpValidate};   # don't validate by default
     3856    delete $$et{XmpValidateLangAlt};
    30363857
    30373858    # ignore non-standard XMP while in strict MWG compatibility mode
    3038     if ($Image::ExifTool::MWG::strict and not $$exifTool{XMP_CAPTURE} and
    3039         $$exifTool{FILE_TYPE} =~ /^(JPEG|TIFF|PSD)$/)
     3859    if (($Image::ExifTool::MWG::strict or $$et{OPTIONS}{Validate}) and
     3860        not ($$et{XMP_CAPTURE} or $$et{DOC_NUM}) and
     3861        (($$dirInfo{DirName} || '') eq 'XMP' or $$et{FILE_TYPE} eq 'XMP'))
    30403862    {
    3041         my $path = $exifTool->MetadataPath();
    3042         unless ($path =~ /^(JPEG-APP1-XMP|TIFF-IFD0-XMP|PSD-XMP)$/) {
    3043             $exifTool->Warn("Ignored non-standard XMP at $path");
     3863        $$et{XmpValidate} = { } if $$et{OPTIONS}{Validate};
     3864        my $path = $et->MetadataPath();
     3865        my $nonStd;
     3866        if ($$et{FILE_TYPE} =~ /^(JPEG|TIFF|PSD)$/ and $path !~ /^(JPEG-APP1-XMP|TIFF-IFD0-XMP|PSD-XMP)$/) {
     3867            $nonStd = 1;
     3868        }
     3869        if ($nonStd and $Image::ExifTool::MWG::strict) {
     3870            $et->Warn("Ignored non-standard XMP at $path");
    30443871            return 1;
     3872        }
     3873        if ($nonStd) {
     3874            $et->Warn("Non-standard XMP at $path", 1);
     3875        } elsif (not $$dirInfo{IsExtended}) {
     3876            $et->Warn("Duplicate XMP at $path") if $$et{DIR_COUNT}{XMP};
     3877            $$et{DIR_COUNT}{XMP} = ($$et{DIR_COUNT}{XMP} || 0) + 1; # count standard XMP
    30453878        }
    30463879    }
     
    30493882        $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart);
    30503883        $dataLen = $$dirInfo{DataLen} || length($$dataPt);
     3884        # check leading BOM (may indicate double-encoded UTF)
     3885        pos($$dataPt) = $dirStart;
     3886        $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;
    30513887    } else {
    3052         my $type;
     3888        my ($type, $mime, $buf2, $buf3);
    30533889        # read information from XMP file
    30543890        my $raf = $$dirInfo{RAF} or return 0;
    30553891        $raf->Read($buff, 256) or return 0;
    3056         my ($buf2, $buf3, $double);
    30573892        ($buf2 = $buff) =~ tr/\0//d;    # cheap conversion to UTF-8
    3058         # remove leading comments if they exist (ie. ImageIngester)
     3893        # remove leading comments if they exist (eg. ImageIngester)
    30593894        while ($buf2 =~ /^\s*<!--/) {
    30603895            # remove the comment if it is complete
     
    30813916            } elsif ($buf2 =~ /^(\xff\xfe)(<\?xml|<rdf:RDF|<x(mp)?:x[ma]pmeta)/g) {
    30823917                $fmt = 'v';     # UTF-16 or 32 II with BOM
    3083             } elsif ($buf2 =~ /^(\xef\xbb\xbf)?(<\?xml|<rdf:RDF|<x(mp)?:x[ma]pmeta)/g) {
     3918            } elsif ($buf2 =~ /^(\xef\xbb\xbf)?(<\?xml|<rdf:RDF|<x(mp)?:x[ma]pmeta|<svg\b)/g) {
    30843919                $fmt = 0;       # UTF-8 with BOM or unknown encoding without BOM
    30853920            } elsif ($buf2 =~ /^(\xfe\xff|\xff\xfe|\xef\xbb\xbf)(<\?xpacket begin=)/g) {
     
    30903925            $bom = 1 if $1;
    30913926            if ($2 eq '<?xml') {
    3092                 if ($buf2 =~ /<x(mp)?:x[ma]pmeta/) {
     3927                if (defined $fmt and not $fmt and $buf2 =~ /^[^\n\r]*[\n\r]+<\?aid /s) {
     3928                    undef $$et{XmpValidate};    # don't validate INX
     3929                    if ($$et{XMP_CAPTURE}) {
     3930                        $et->Error("ExifTool does not yet support writing of INX files");
     3931                        return 0;
     3932                    }
     3933                    $type = 'INX';
     3934                } elsif ($buf2 =~ /<x(mp)?:x[ma]pmeta/) {
    30933935                    $hasXMP = 1;
    30943936                } else {
    3095                     # identify SVG images by DOCTYPE if available
     3937                    undef $$et{XmpValidate};    # don't validate XML
     3938                    # identify SVG images and PLIST files by DOCTYPE if available
    30963939                    if ($buf2 =~ /<!DOCTYPE\s+(\w+)/) {
    30973940                        if ($1 eq 'svg') {
     
    30993942                        } elsif ($1 eq 'plist') {
    31003943                            $type = 'PLIST';
     3944                        } elsif ($1 eq 'REDXIF') {
     3945                            $type = 'RMD';
     3946                            $mime = 'application/xml';
    31013947                        } else {
    31023948                            return 0;
     
    31063952                    } elsif ($buf2 =~ /<rdf:RDF/) {
    31073953                        $isRDF = 1;
     3954                    } elsif ($buf2 =~ /<plist[\s>]/) {
     3955                        $type = 'PLIST';
    31083956                    }
    3109                     if ($isSVG and $exifTool->{XMP_CAPTURE}) {
    3110                         $exifTool->Error("ExifTool does not yet support writing of SVG images");
     3957                    if ($isSVG and $$et{XMP_CAPTURE}) {
     3958                        $et->Error("ExifTool does not yet support writing of SVG images");
    31113959                        return 0;
    31123960                    }
     
    31183966            if ($buff =~ /^\0\0/) {
    31193967                $fmt = 'N';     # UTF-32 MM with or without BOM
    3120             } elsif ($buff =~ /^..\0\0/) {
     3968            } elsif ($buff =~ /^..\0\0/s) {
    31213969                $fmt = 'V';     # UTF-32 II with or without BOM
    31223970            } elsif (not $fmt) {
    31233971                if ($buff =~ /^\0/) {
    31243972                    $fmt = 'n'; # UTF-16 MM without BOM
    3125                 } elsif ($buff =~ /^.\0/) {
     3973                } elsif ($buff =~ /^.\0/s) {
    31263974                    $fmt = 'v'; # UTF-16 II without BOM
    31273975                }
    31283976            }
    31293977        }
    3130         $raf->Seek(0, 2) or return 0;
    3131         my $size = $raf->Tell() or return 0;
    3132         $raf->Seek(0, 0) or return 0;
    3133         $raf->Read($buff, $size) == $size or return 0;
    3134         # decode the first layer of double-encoded UTF text
    3135         if ($double) {
    3136             $buff = substr($buff, length $double);  # remove leading BOM
    3137             Image::ExifTool::SetWarning(undef);     # clear old warning
    3138             local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning;
    3139             my $tmp;
    3140             # assume that character data has been re-encoded in UTF, so re-pack
    3141             # as characters and look for warnings indicating a false assumption
    3142             if ($double eq "\xef\xbb\xbf") {
    3143                 require Image::ExifTool::Charset;
    3144                 my $uni = Image::ExifTool::Charset::Decompose(undef,$buff,'UTF8');
    3145                 $tmp = pack('C*', @$uni);
    3146             } else {
    3147                 my $fmt = ($double eq "\xfe\xff") ? 'n' : 'v';
    3148                 $tmp = pack('C*', unpack("$fmt*",$buff));
    3149             }
    3150             if (Image::ExifTool::GetWarning()) {
    3151                 $exifTool->Warn('Superfluous BOM at start of XMP');
    3152             } else {
    3153                 $exifTool->Warn('XMP is double UTF-encoded');
    3154                 $buff = $tmp;   # use the decoded XMP
    3155             }
    3156             $size = length $buff;
    3157         }
    3158         $dataPt = \$buff;
    3159         $dirStart = 0;
    3160         $dirLen = $dataLen = $size;
    3161         unless ($type) {
     3978        my $size;
     3979        if ($type) {
     3980            if ($type eq 'PLIST') {
     3981                my $ext = $$et{FILE_EXT};
     3982                $type = $ext if $ext and $ext eq 'MODD';
     3983                $tagTablePtr = GetTagTable('Image::ExifTool::PLIST::Main');
     3984                $$dirInfo{XMPParseOpts}{FoundProc} = \&Image::ExifTool::PLIST::FoundTag;
     3985            }
     3986        } else {
    31623987            if ($isSVG) {
    31633988                $type = 'SVG';
    31643989            } elsif ($isXML and not $hasXMP and not $isRDF) {
    31653990                $type = 'XML';
    3166             }
    3167         }
    3168         $exifTool->SetFileType($type);
    3169     }
    3170 
    3171     # take substring if necessary
    3172     if ($dataLen != $dirStart + $dirLen) {
    3173         $buff = substr($$dataPt, $dirStart, $dirLen);
     3991                my $ext = $$et{FILE_EXT};
     3992                $type = $ext if $ext and $ext eq 'COS'; # recognize COS by extension
     3993            }
     3994        }
     3995        $et->SetFileType($type, $mime);
     3996
     3997        my $fast = $et->Options('FastScan');
     3998        return 1 if $fast and $fast == 3;
     3999
     4000        if ($type and $type eq 'INX') {
     4001            # brute force search for first XMP packet in INX file
     4002            # start: '<![CDATA[<?xpacket begin' (24 bytes)
     4003            # end:   '<?xpacket end="r"?>]]>'   (22 bytes)
     4004            $raf->Seek(0, 0) or return 0;
     4005            $raf->Read($buff, 65536) or return 1;
     4006            for (;;) {
     4007                last if $buff =~ /<!\[CDATA\[<\?xpacket begin/g;
     4008                $raf->Read($buf2, 65536) or return 1;
     4009                $buff = substr($buff, -24) . $buf2;
     4010            }
     4011            $buff = substr($buff, pos($buff) - 15); # (discard '<![CDATA[' and before)
     4012            for (;;) {
     4013                last if $buff =~ /<\?xpacket end="[rw]"\?>\]\]>/g;
     4014                my $n = length $buff;
     4015                $raf->Read($buf2, 65536) or $et->Warn('Missing xpacket end'), return 1;
     4016                $buff .= $buf2;
     4017                pos($buff) = $n - 22;   # don't miss end pattern if it was split
     4018            }
     4019            $size = pos($buff) - 3;     # (discard ']]>' and after)
     4020            $buff = substr($buff, 0, $size);
     4021        } else {
     4022            # read the entire file
     4023            $raf->Seek(0, 2) or return 0;
     4024            $size = $raf->Tell() or return 0;
     4025            $raf->Seek(0, 0) or return 0;
     4026            $raf->Read($buff, $size) == $size or return 0;
     4027        }
    31744028        $dataPt = \$buff;
    31754029        $dirStart = 0;
     4030        $dirLen = $dataLen = $size;
    31764031    }
    3177     # extract XMP as a block if specified
    3178     if (($exifTool->{REQ_TAG_LOOKUP}{xmp} or $exifTool->{OPTIONS}{Binary}) and not $isSVG) {
    3179         $exifTool->FoundTag('XMP', substr($$dataPt, $dirStart, $dirLen));
     4032
     4033    # decode the first layer of double-encoded UTF text (if necessary)
     4034    if ($double) {
     4035        my ($buf2, $fmt);
     4036        $buff = substr($$dataPt, $dirStart + length $double); # remove leading BOM
     4037        Image::ExifTool::SetWarning(undef); # clear old warning
     4038        local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning;
     4039        # assume that character data has been re-encoded in UTF, so re-pack
     4040        # as characters and look for warnings indicating a false assumption
     4041        if ($double eq "\xef\xbb\xbf") {
     4042            require Image::ExifTool::Charset;
     4043            my $uni = Image::ExifTool::Charset::Decompose(undef,$buff,'UTF8');
     4044            $buf2 = pack('C*', @$uni);
     4045        } else {
     4046            if (length($double) == 2) {
     4047                $fmt = ($double eq "\xfe\xff") ? 'n' : 'v';
     4048            } else {
     4049                $fmt = ($double eq "\0\0\xfe\xff") ? 'N' : 'V';
     4050            }
     4051            $buf2 = pack('C*', unpack("$fmt*",$buff));
     4052        }
     4053        if (Image::ExifTool::GetWarning()) {
     4054            $et->Warn('Superfluous BOM at start of XMP');
     4055            $dataPt = \$buff;   # use XMP with the BOM removed
     4056        } else {
     4057            $et->Warn('XMP is double UTF-encoded');
     4058            $dataPt = \$buf2;   # use the decoded XMP
     4059        }
     4060        $dirStart = 0;
     4061        $dirLen = $dataLen = length $$dataPt;
    31804062    }
    3181     if ($exifTool->Options('Verbose') and not $exifTool->{XMP_CAPTURE}) {
    3182         $exifTool->VerboseDir($isSVG ? 'SVG' : 'XMP', 0, $dirLen);
     4063
     4064    # extract XMP/XML as a block if specified
     4065    my $blockName = $$dirInfo{BlockInfo} ? $$dirInfo{BlockInfo}{Name} : 'XMP';
     4066    if (($$et{REQ_TAG_LOOKUP}{lc $blockName} or ($$et{TAGS_FROM_FILE} and
     4067        not $$et{EXCL_TAG_LOOKUP}{lc $blockName})) and
     4068        (($$et{FileType} eq 'XMP' and $blockName eq 'XMP') or
     4069        ($$dirInfo{DirName} and $$dirInfo{DirName} eq $blockName)))
     4070    {
     4071        $et->FoundTag($$dirInfo{BlockInfo} || 'XMP', substr($$dataPt, $dirStart, $dirLen));
     4072    }
     4073
     4074    $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main');
     4075    if ($et->Options('Verbose') and not $$et{XMP_CAPTURE}) {
     4076        my $dirType = $isSVG ? 'SVG' : $$tagTablePtr{GROUPS}{1};
     4077        $et->VerboseDir($dirType, 0, $dirLen);
    31834078    }
    31844079#
     
    31864081#
    31874082    my $begin = '<?xpacket begin=';
     4083    my $dirEnd = $dirStart + $dirLen;
    31884084    pos($$dataPt) = $dirStart;
    3189     delete $$exifTool{XMP_IS_XML};
    3190     delete $$exifTool{XMP_IS_SVG};
     4085    delete $$et{XMP_IS_XML};
     4086    delete $$et{XMP_IS_SVG};
    31914087    if ($isXML or $isRDF) {
    3192         $$exifTool{XMP_IS_XML} = $isXML;
    3193         $$exifTool{XMP_IS_SVG} = $isSVG;
    3194         $$exifTool{XMP_NO_XPACKET} = 1 + $bom;
     4088        $$et{XMP_IS_XML} = $isXML;
     4089        $$et{XMP_IS_SVG} = $isSVG;
     4090        $$et{XMP_NO_XPACKET} = 1 + $bom;
    31954091    } elsif ($$dataPt =~ /\G\Q$begin\E/gc) {
    3196         delete $$exifTool{XMP_NO_XPACKET};
    3197     } elsif ($$dataPt =~ /<x(mp)?:x[ma]pmeta/gc) {
    3198         $$exifTool{XMP_NO_XPACKET} = 1 + $bom;
     4092        delete $$et{XMP_NO_XPACKET};
     4093    } elsif ($$dataPt =~ /<x(mp)?:x[ma]pmeta/gc and
     4094             pos($$dataPt) > $dirStart and pos($$dataPt) < $dirEnd)
     4095    {
     4096        $$et{XMP_NO_XPACKET} = 1 + $bom;
    31994097    } else {
    3200         delete $$exifTool{XMP_NO_XPACKET};
     4098        delete $$et{XMP_NO_XPACKET};
    32014099        # check for UTF-16 encoding (insert one \0 between characters)
    32024100        $begin = join "\0", split //, $begin;
    32034101        # must reset pos because it was killed by previous unsuccessful //g match
    32044102        pos($$dataPt) = $dirStart;
    3205         if ($$dataPt =~ /\G(\0)?\Q$begin\E\0./g) {
     4103        if ($$dataPt =~ /\G(\0)?\Q$begin\E\0./sg) {
    32064104            # validate byte ordering by checking for U+FEFF character
    32074105            if ($1) {
     
    32154113            $begin =~ s/\0/\0\0\0/g;
    32164114            pos($$dataPt) = $dirStart;
    3217             if ($$dataPt !~ /\G(\0\0\0)?\Q$begin\E\0\0\0./g) {
     4115            if ($$dataPt !~ /\G(\0\0\0)?\Q$begin\E\0\0\0./sg) {
    32184116                $fmt = 0;   # set format to zero as indication we didn't find encoded XMP
    32194117            } elsif ($1) {
     
    32244122            }
    32254123        }
    3226         defined $fmt or $exifTool->Warn('XMP character encoding error');
     4124        defined $fmt or $et->Warn('XMP character encoding error');
    32274125    }
    32284126    if ($fmt) {
    32294127        # trim if necessary to avoid converting non-UTF data
    3230         if ($dirStart or $dirLen != length($$dataPt) - $dirStart) {
     4128        if ($dirStart or $dirEnd != length($$dataPt)) {
    32314129            $buff = substr($$dataPt, $dirStart, $dirLen);
    32324130            $dataPt = \$buff;
     
    32414139        $dirStart = 0;
    32424140        $dirLen = length $$dataPt;
     4141        $dirEnd = $dirStart + $dirLen;
    32434142    }
    3244     # initialize namespace translation
    3245     $xlatNamespace = \%stdXlatNS;
    3246 
    32474143    # avoid scanning for XMP later in case ScanForXMP is set
    3248     $$exifTool{FoundXMP} = 1;
     4144    $$et{FoundXMP} = 1 if $tagTablePtr eq \%Image::ExifTool::XMP::Main;
    32494145
    32504146    # set XMP parsing options
    3251     $$exifTool{XMPParseOpts} = $$dirInfo{XMPParseOpts};
     4147    $$et{XMPParseOpts} = $$dirInfo{XMPParseOpts};
     4148
     4149    # ignore any specified properties (XML hack)
     4150    if ($$dirInfo{IgnoreProp}) {
     4151        %ignoreProp = %{$$dirInfo{IgnoreProp}};
     4152    } else {
     4153        undef %ignoreProp;
     4154    }
    32524155
    32534156    # need to preserve list indices to be able to handle multi-dimensional lists
    3254     $$exifTool{NO_LIST} = 1 if $exifTool->Options('Struct');
     4157    my $keepFlat;
     4158    if ($$et{OPTIONS}{Struct}) {
     4159        if ($$et{OPTIONS}{Struct} eq '2') {
     4160            $keepFlat = 1;      # preserve flattened tags
     4161            # setting NO_LIST to 0 combines list items in a TAG_EXTRA "NoList" element
     4162            # to allow them to be re-listed later if necessary.  A "NoListDel" element
     4163            # is also created for tags that wouldn't have existed.
     4164            $$et{NO_LIST} = 0;
     4165        } else {
     4166            $$et{NO_LIST} = 1;
     4167        }
     4168    }
     4169
     4170    # don't generate structures if this isn't real XMP
     4171    $$et{NO_STRUCT} = 1 if $$dirInfo{BlockInfo} or $$dirInfo{NoStruct};
    32554172
    32564173    # parse the XMP
    3257     $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main');
    3258     $rtnVal = 1 if ParseXMPElement($exifTool, $tagTablePtr, $dataPt, $dirStart);
     4174    if (ParseXMPElement($et, $tagTablePtr, $dataPt, $dirStart, $dirEnd)) {
     4175        $rtnVal = 1;
     4176    } elsif ($$dirInfo{DirName} and $$dirInfo{DirName} eq 'XMP') {
     4177        # if DirName was 'XMP' we expect well-formed XMP, so set Warning since it wasn't
     4178        # (but allow empty XMP as written by some PhaseOne cameras)
     4179        my $xmp = substr($$dataPt, $dirStart, $dirLen);
     4180        if ($xmp =~ /^ *\0*$/) {
     4181            $et->Warn('Invalid XMP');
     4182        } else {
     4183            $et->Warn('Empty XMP',1);
     4184            $rtnVal = 1;
     4185        }
     4186    }
     4187    delete $$et{NO_STRUCT};
    32594188
    32604189    # return DataPt if successful in case we want it for writing
     
    32624191
    32634192    # restore structures if necessary
    3264     if ($$exifTool{IsStruct}) {
     4193    if ($$et{IsStruct}) {
    32654194        require 'Image/ExifTool/XMPStruct.pl';
    3266         RestoreStruct($exifTool);
    3267         delete $$exifTool{IsStruct};
     4195        RestoreStruct($et, $keepFlat);
     4196        delete $$et{IsStruct};
    32684197    }
    32694198    # reset NO_LIST flag (must do this _after_ RestoreStruct() above)
    3270     delete $$exifTool{NO_LIST};
    3271 
    3272     undef %curNS;
     4199    delete $$et{NO_LIST};
     4200    delete $$et{XMPParseOpts};
     4201    delete $$et{curURI};
     4202    delete $$et{curNS};
     4203    delete $$et{xlatNS};
     4204    delete $$et{definedNS};
     4205
    32734206    return $rtnVal;
    32744207}
     
    32964229=head1 AUTHOR
    32974230
    3298 Copyright 2003-2011, Phil Harvey (phil at owl.phy.queensu.ca)
     4231Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com)
    32994232
    33004233This library is free software; you can redistribute it and/or modify it
Note: See TracChangeset for help on using the changeset viewer.