<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">#------------------------------------------------------------------------------
# File:         APP12.pm
#
# Description:  Read APP12 meta information
#
# Revisions:    10/18/2005 - P. Harvey Created
#
# References:   1) Heinrich Giesen private communication
#------------------------------------------------------------------------------

package Image::ExifTool::APP12;

use strict;
use vars qw($VERSION);
use Image::ExifTool qw(:DataAccess :Utils);

$VERSION = '1.13';

sub ProcessAPP12($$$);
sub ProcessDucky($$$);
sub WriteDucky($$$);

# APP12 tags (ref PH)
%Image::ExifTool::APP12::PictureInfo = (
    PROCESS_PROC =&gt; \&amp;ProcessAPP12,
    GROUPS =&gt; { 0 =&gt; 'APP12', 1 =&gt; 'PictureInfo', 2 =&gt; 'Image' },
    PRIORITY =&gt; 0,
    NOTES =&gt; q{
        The JPEG APP12 "Picture Info" segment was used by some older cameras, and
        contains ASCII-based meta information.  Below are some tags which have been
        observed Agfa and Polaroid images, however ExifTool will extract information
        from any tags found in this segment.
    },
    FNumber =&gt; {
        ValueConv =&gt; '$val=~s/^[A-Za-z ]*//;$val',  # Agfa leads with an 'F'
        PrintConv =&gt; 'sprintf("%.1f",$val)',
    },
    Aperture =&gt; {
        PrintConv =&gt; 'sprintf("%.1f",$val)',
    },
    TimeDate =&gt; {
        Name =&gt; 'DateTimeOriginal',
        Description =&gt; 'Date/Time Original',
        Groups =&gt; { 2 =&gt; 'Time' },
        ValueConv =&gt; '$val=~/^\d+$/ ? ConvertUnixTime($val) : $val',
        PrintConv =&gt; '$self-&gt;ConvertDateTime($val)',
    },
    Shutter =&gt; {
        Name =&gt; 'ExposureTime',
        ValueConv =&gt; '$val * 1e-6',
        PrintConv =&gt; 'Image::ExifTool::Exif::PrintExposureTime($val)',
    },
    shtr =&gt; {
        Name =&gt; 'ExposureTime',
        ValueConv =&gt; '$val * 1e-6',
        PrintConv =&gt; 'Image::ExifTool::Exif::PrintExposureTime($val)',
    },
   'Serial#'    =&gt; {
        Name =&gt; 'SerialNumber',
        Groups =&gt; { 2 =&gt; 'Camera' },
    },
    Flash       =&gt; { PrintConv =&gt; { 0 =&gt; 'Off', 1 =&gt; 'On' } },
    Macro       =&gt; { PrintConv =&gt; { 0 =&gt; 'Off', 1 =&gt; 'On' } },
    StrobeTime  =&gt; { },
    Ytarget     =&gt; { Name =&gt; 'YTarget' },
    ylevel      =&gt; { Name =&gt; 'YLevel' },
    FocusPos    =&gt; { },
    FocusMode   =&gt; { },
    Quality     =&gt; { },
    ExpBias     =&gt; 'ExposureCompensation',
    FWare       =&gt; 'FirmwareVersion',
    StrobeTime  =&gt; { },
    Resolution  =&gt; { },
    Protect     =&gt; { },
    ConTake     =&gt; { },
    ImageSize   =&gt; { PrintConv =&gt; '$val=~tr/-/x/;$val' },
    ColorMode   =&gt; { },
    Zoom        =&gt; { },
    ZoomPos     =&gt; { },
    LightS      =&gt; { },
    Type        =&gt; {
        Name =&gt; 'CameraType',
        Groups =&gt; { 2 =&gt; 'Camera' },
        DataMember =&gt; 'CameraType',
        RawConv =&gt; '$self-&gt;{CameraType} = $val',
    },
    Version     =&gt; { Groups =&gt; { 2 =&gt; 'Camera' } },
    ID          =&gt; { Groups =&gt; { 2 =&gt; 'Camera' } },
);

# APP12 segment written in Photoshop "Save For Web" images
# (from tests with Photoshop 7 files - PH/1)
%Image::ExifTool::APP12::Ducky = (
    PROCESS_PROC =&gt; \&amp;ProcessDucky,
    WRITE_PROC =&gt; \&amp;WriteDucky,
    GROUPS =&gt; { 0 =&gt; 'Ducky', 1 =&gt; 'Ducky', 2 =&gt; 'Image' },
    WRITABLE =&gt; 'string',
    NOTES =&gt; q{
        Photoshop uses the JPEG APP12 "Ducky" segment to store some information in
        "Save for Web" images.
    },
    1 =&gt; { #PH
        Name =&gt; 'Quality',
        Priority =&gt; 0,
        Avoid =&gt; 1,
        Writable =&gt; 'int32u',
        ValueConv =&gt; 'unpack("N",$val)',    # 4-byte integer
        ValueConvInv =&gt; 'pack("N",$val)',
        PrintConv =&gt; '"$val%"',
        PrintConvInv =&gt; '$val=~/(\d+)/ ? $1 : undef',
    },
    2 =&gt; { #1
        Name =&gt; 'Comment',
        Priority =&gt; 0,
        Avoid =&gt; 1,
        # (ignore 4-byte character count at start of value)
        ValueConv =&gt; '$self-&gt;Decode(substr($val,4),"UCS2","MM")',
        ValueConvInv =&gt; 'pack("N",length $val) . $self-&gt;Encode($val,"UCS2","MM")',
    },
    3 =&gt; { #PH
        Name =&gt; 'Copyright',
        Priority =&gt; 0,
        Avoid =&gt; 1,
        Groups =&gt; { 2 =&gt; 'Author' },
        # (ignore 4-byte character count at start of value)
        ValueConv =&gt; '$self-&gt;Decode(substr($val,4),"UCS2","MM")',
        ValueConvInv =&gt; 'pack("N",length $val) . $self-&gt;Encode($val,"UCS2","MM")',
    },
);

#------------------------------------------------------------------------------
# Write APP12 Ducky segment
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
# Returns: New directory data or undefined on error
sub WriteDucky($$$)
{
    my ($et, $dirInfo, $tagTablePtr) = @_;
    $et or return 1;    # allow dummy access to autoload this package
    my $dataPt = $$dirInfo{DataPt};
    my $pos = $$dirInfo{DirStart};
    my $newTags = $et-&gt;GetNewTagInfoHash($tagTablePtr);
    my @addTags = sort { $a &lt;=&gt; $b } keys(%$newTags);
    my ($dirEnd, %doneTags);
    if ($dataPt) {
        $dirEnd = $pos + $$dirInfo{DirLen};
    } else {
        my $tmp = '';
        $dataPt = \$tmp;
        $pos = $dirEnd = 0;
    }
    my $newData = '';
    SetByteOrder('MM');
    # process all data blocks in Ducky segment
    for (;;) {
        my ($tag, $len, $val);
        if ($pos + 4 &lt;= $dirEnd) {
            $tag = Get16u($dataPt, $pos);
            $len = Get16u($dataPt, $pos + 2);
            $pos += 4;
            if ($pos + $len &gt; $dirEnd) {
                $et-&gt;Warn('Invalid Ducky block length');
                return undef;
            }
            $val = substr($$dataPt, $pos, $len);
            $pos += $len;
        } else {
            last unless @addTags;
            $tag = pop @addTags;
            next if $doneTags{$tag};
        }
        $doneTags{$tag} = 1;
        my $tagInfo = $$newTags{$tag};
        if ($tagInfo) {
            my $nvHash = $et-&gt;GetNewValueHash($tagInfo);
            my $isNew;
            if (defined $val) {
                if ($et-&gt;IsOverwriting($nvHash, $val)) {
                    $et-&gt;VerboseValue("- Ducky:$$tagInfo{Name}", $val);
                    $isNew = 1;
                }
            } else {
                next unless $$nvHash{IsCreating};
                $isNew = 1;
            }
            if ($isNew) {
                $val = $et-&gt;GetNewValue($nvHash);
                ++$$et{CHANGED};
                next unless defined $val;   # next if tag is being deleted
                $et-&gt;VerboseValue("+ Ducky:$$tagInfo{Name}", $val);
            }
        }
        $newData .= pack('nn', $tag, length $val) . $val;
    }
    $newData .= "\0\0" if length $newData;
    return $newData;
}

#------------------------------------------------------------------------------
# Process APP12 Ducky segment (ref PH)
# Inputs: 0) ExifTool object reference, 1) Directory information ref, 2) tag table ref
# Returns: 1 on success, 0 if this wasn't a recognized Ducky segment
# Notes: This segment has the following format:
#   1) 5 bytes: "Ducky"
#   2) multiple data blocks (all integers are big endian):
#      a) 2 bytes: block type (0=end, 1=Quality, 2=Comment, 3=Copyright)
#      b) 2 bytes: block length (N)
#      c) N bytes: block data
sub ProcessDucky($$$)
{
    my ($et, $dirInfo, $tagTablePtr) = @_;
    my $dataPt = $$dirInfo{DataPt};
    my $pos = $$dirInfo{DirStart};
    my $dirEnd = $pos + $$dirInfo{DirLen};
    SetByteOrder('MM');
    # process all data blocks in Ducky segment
    for (;;) {
        last if $pos + 4 &gt; $dirEnd;
        my $tag = Get16u($dataPt, $pos);
        my $len = Get16u($dataPt, $pos + 2);
        $pos += 4;
        if ($pos + $len &gt; $dirEnd) {
            $et-&gt;Warn('Invalid Ducky block length');
            last;
        }
        my $val = substr($$dataPt, $pos, $len);
        $et-&gt;HandleTag($tagTablePtr, $tag, $val,
            DataPt =&gt; $dataPt,
            DataPos =&gt; $$dirInfo{DataPos},
            Start =&gt; $pos,
            Size =&gt; $len,
        );
        $pos += $len;
    }
    return 1;
}

#------------------------------------------------------------------------------
# Process APP12 Picture Info segment (ref PH)
# Inputs: 0) ExifTool object reference, 1) Directory information ref, 2) tag table ref
# Returns: 1 on success, 0 if this wasn't a recognized APP12
sub ProcessAPP12($$$)
{
    my ($et, $dirInfo, $tagTablePtr) = @_;
    my $dataPt = $$dirInfo{DataPt};
    my $dirStart = $$dirInfo{DirStart} || 0;
    my $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart);
    if ($dirLen != $dirStart + length($$dataPt)) {
        my $buff = substr($$dataPt, $dirStart, $dirLen);
        $dataPt = \$buff;
    } else {
        pos($$dataPt) = $$dirInfo{DirStart};
    }
    my $verbose = $et-&gt;Options('Verbose');
    my $success = 0;
    my $section = '';
    pos($$dataPt) = 0;

    # this regular expression is a bit complex, but basically we are looking for
    # section headers (eg. "[Camera Info]") and tag/value pairs (eg. "tag=value",
    # where "value" may contain white space), separated by spaces or CR/LF.
    # (APP12 uses CR/LF, but Olympus TextualInfo is similar and uses spaces)
    while ($$dataPt =~ /(\[.*?\]|[\w#-]+=[\x20-\x7e]+?(?=\s*([\n\r\0]|[\w#-]+=|\[|$)))/g) {
        my $token = $1;
        # was this a section name?
        if ($token =~ /^\[(.*)\]/) {
            $et-&gt;VerboseDir($1) if $verbose;
            $section = ($token =~ /\[(\S+) ?Info\]/i) ? $1 : '';
            $success = 1;
            next;
        }
        $et-&gt;VerboseDir($$dirInfo{DirName}) if $verbose and not $success;
        $success = 1;
        my ($tag, $val) = ($token =~ /(\S+)=(.+)/);
        my $tagInfo = $et-&gt;GetTagInfo($tagTablePtr, $tag);
        $verbose and $et-&gt;VerboseInfo($tag, $tagInfo, Value =&gt; $val);
        unless ($tagInfo) {
            # add new tag to table
            $tagInfo = { Name =&gt; ucfirst $tag };
            # put in Camera group if information in "Camera" section
            $$tagInfo{Groups} = { 2 =&gt; 'Camera' } if $section =~ /camera/i;
            AddTagToTable($tagTablePtr, $tag, $tagInfo);
        }
        $et-&gt;FoundTag($tagInfo, $val);
    }
    return $success;
}


1;  #end

__END__

=head1 NAME

Image::ExifTool::APP12 - Read APP12 meta information

=head1 SYNOPSIS

This module is loaded automatically by Image::ExifTool when required.

=head1 DESCRIPTION

This module contains definitions required by Image::ExifTool to interpret
APP12 meta information.

=head1 AUTHOR

Copyright 2003-2023, Phil Harvey (philharvey66 at gmail.com)

This library is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=head1 ACKNOWLEDGEMENTS

Thanks to Heinrich Giesen for his help decoding APP12 "Ducky" information.

=head1 SEE ALSO

L&lt;Image::ExifTool::TagNames/APP12 Tags&gt;,
L&lt;Image::ExifTool(3pm)|Image::ExifTool&gt;

=cut
</pre></body></html>