The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#------------------------------------------------------------------------------
# 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 => \&ProcessAPP12,
    GROUPS => { 0 => 'APP12', 1 => 'PictureInfo', 2 => 'Image' },
    PRIORITY => 0,
    NOTES => 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 => {
        ValueConv => '$val=~s/^[A-Za-z ]*//;$val',  # Agfa leads with an 'F'
        PrintConv => 'sprintf("%.1f",$val)',
    },
    Aperture => {
        PrintConv => 'sprintf("%.1f",$val)',
    },
    TimeDate => {
        Name => 'DateTimeOriginal',
        Description => 'Date/Time Original',
        Groups => { 2 => 'Time' },
        ValueConv => '$val=~/^\d+$/ ? ConvertUnixTime($val) : $val',
        PrintConv => '$self->ConvertDateTime($val)',
    },
    Shutter => {
        Name => 'ExposureTime',
        ValueConv => '$val * 1e-6',
        PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
    },
    shtr => {
        Name => 'ExposureTime',
        ValueConv => '$val * 1e-6',
        PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
    },
   'Serial#'    => {
        Name => 'SerialNumber',
        Groups => { 2 => 'Camera' },
    },
    Flash       => { PrintConv => { 0 => 'Off', 1 => 'On' } },
    Macro       => { PrintConv => { 0 => 'Off', 1 => 'On' } },
    StrobeTime  => { },
    Ytarget     => { Name => 'YTarget' },
    ylevel      => { Name => 'YLevel' },
    FocusPos    => { },
    FocusMode   => { },
    Quality     => { },
    ExpBias     => 'ExposureCompensation',
    FWare       => 'FirmwareVersion',
    StrobeTime  => { },
    Resolution  => { },
    Protect     => { },
    ConTake     => { },
    ImageSize   => { PrintConv => '$val=~tr/-/x/;$val' },
    ColorMode   => { },
    Zoom        => { },
    ZoomPos     => { },
    LightS      => { },
    Type        => {
        Name => 'CameraType',
        Groups => { 2 => 'Camera' },
        DataMember => 'CameraType',
        RawConv => '$self->{CameraType} = $val',
    },
    Version     => { Groups => { 2 => 'Camera' } },
    ID          => { Groups => { 2 => 'Camera' } },
);

# APP12 segment written in Photoshop "Save For Web" images
# (from tests with Photoshop 7 files - PH/1)
%Image::ExifTool::APP12::Ducky = (
    PROCESS_PROC => \&ProcessDucky,
    WRITE_PROC => \&WriteDucky,
    GROUPS => { 0 => 'Ducky', 1 => 'Ducky', 2 => 'Image' },
    WRITABLE => 'string',
    NOTES => q{
        Photoshop uses the JPEG APP12 "Ducky" segment to store some information in
        "Save for Web" images.
    },
    1 => { #PH
        Name => 'Quality',
        Priority => 0,
        Avoid => 1,
        Writable => 'int32u',
        ValueConv => 'unpack("N",$val)',    # 4-byte integer
        ValueConvInv => 'pack("N",$val)',
        PrintConv => '"$val%"',
        PrintConvInv => '$val=~/(\d+)/ ? $1 : undef',
    },
    2 => { #1
        Name => 'Comment',
        Priority => 0,
        Avoid => 1,
        # (ignore 4-byte character count at start of value)
        ValueConv => '$self->Decode(substr($val,4),"UCS2","MM")',
        ValueConvInv => 'pack("N",length $val) . $self->Encode($val,"UCS2","MM")',
    },
    3 => { #PH
        Name => 'Copyright',
        Priority => 0,
        Avoid => 1,
        Groups => { 2 => 'Author' },
        # (ignore 4-byte character count at start of value)
        ValueConv => '$self->Decode(substr($val,4),"UCS2","MM")',
        ValueConvInv => 'pack("N",length $val) . $self->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->GetNewTagInfoHash($tagTablePtr);
    my @addTags = sort { $a <=> $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 <= $dirEnd) {
            $tag = Get16u($dataPt, $pos);
            $len = Get16u($dataPt, $pos + 2);
            $pos += 4;
            if ($pos + $len > $dirEnd) {
                $et->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->GetNewValueHash($tagInfo);
            my $isNew;
            if (defined $val) {
                if ($et->IsOverwriting($nvHash, $val)) {
                    $et->VerboseValue("- Ducky:$$tagInfo{Name}", $val);
                    $isNew = 1;
                }
            } else {
                next unless $$nvHash{IsCreating};
                $isNew = 1;
            }
            if ($isNew) {
                $val = $et->GetNewValue($nvHash);
                ++$$et{CHANGED};
                next unless defined $val;   # next if tag is being deleted
                $et->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 > $dirEnd;
        my $tag = Get16u($dataPt, $pos);
        my $len = Get16u($dataPt, $pos + 2);
        $pos += 4;
        if ($pos + $len > $dirEnd) {
            $et->Warn('Invalid Ducky block length');
            last;
        }
        my $val = substr($$dataPt, $pos, $len);
        $et->HandleTag($tagTablePtr, $tag, $val,
            DataPt => $dataPt,
            DataPos => $$dirInfo{DataPos},
            Start => $pos,
            Size => $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->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->VerboseDir($1) if $verbose;
            $section = ($token =~ /\[(\S+) ?Info\]/i) ? $1 : '';
            $success = 1;
            next;
        }
        $et->VerboseDir($$dirInfo{DirName}) if $verbose and not $success;
        $success = 1;
        my ($tag, $val) = ($token =~ /(\S+)=(.+)/);
        my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
        $verbose and $et->VerboseInfo($tag, $tagInfo, Value => $val);
        unless ($tagInfo) {
            # add new tag to table
            $tagInfo = { Name => ucfirst $tag };
            # put in Camera group if information in "Camera" section
            $$tagInfo{Groups} = { 2 => 'Camera' } if $section =~ /camera/i;
            AddTagToTable($tagTablePtr, $tag, $tagInfo);
        }
        $et->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-2018, Phil Harvey (phil at owl.phy.queensu.ca)

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

=head1 ACKNOWLEDGEMENTS

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

=head1 SEE ALSO

L<Image::ExifTool::TagNames/APP12 Tags>,
L<Image::ExifTool(3pm)|Image::ExifTool>

=cut