[16842] | 1 | #------------------------------------------------------------------------------
|
---|
| 2 | # File: APP12.pm
|
---|
| 3 | #
|
---|
| 4 | # Description: Read APP12 meta information
|
---|
| 5 | #
|
---|
| 6 | # Revisions: 10/18/2005 - P. Harvey Created
|
---|
| 7 | #
|
---|
| 8 | # References: 1) Heinrich Giesen private communication
|
---|
| 9 | #------------------------------------------------------------------------------
|
---|
| 10 |
|
---|
| 11 | package Image::ExifTool::APP12;
|
---|
| 12 |
|
---|
| 13 | use strict;
|
---|
| 14 | use vars qw($VERSION);
|
---|
| 15 | use Image::ExifTool qw(:DataAccess);
|
---|
| 16 |
|
---|
[24107] | 17 | $VERSION = '1.08';
|
---|
[16842] | 18 |
|
---|
| 19 | sub ProcessAPP12($$$);
|
---|
| 20 | sub ProcessDucky($$$);
|
---|
| 21 | sub WriteDucky($$$);
|
---|
| 22 |
|
---|
| 23 | # APP12 tags (ref PH)
|
---|
| 24 | %Image::ExifTool::APP12::PictureInfo = (
|
---|
| 25 | PROCESS_PROC => \&ProcessAPP12,
|
---|
| 26 | GROUPS => { 0 => 'APP12', 1 => 'PictureInfo', 2 => 'Image' },
|
---|
| 27 | NOTES => q{
|
---|
| 28 | The JPEG APP12 "Picture Info" segment was used by some older cameras, and
|
---|
| 29 | contains ASCII-based meta information. Below are some tags which have been
|
---|
| 30 | observed Agfa and Polaroid images, however ExifTool will extract information
|
---|
| 31 | from any tags found in this segment.
|
---|
| 32 | },
|
---|
| 33 | FNumber => {
|
---|
| 34 | ValueConv => '$val=~s/^[A-Za-z ]*//;$val', # Agfa leads with an 'F'
|
---|
| 35 | PrintConv => 'sprintf("%.1f",$val)',
|
---|
| 36 | },
|
---|
| 37 | Aperture => {
|
---|
| 38 | PrintConv => 'sprintf("%.1f",$val)',
|
---|
| 39 | },
|
---|
| 40 | TimeDate => {
|
---|
| 41 | Name => 'DateTimeOriginal',
|
---|
| 42 | Description => 'Date/Time Original',
|
---|
| 43 | Groups => { 2 => 'Time' },
|
---|
| 44 | ValueConv => '$val=~/^\d+$/ ? ConvertUnixTime($val) : $val',
|
---|
| 45 | PrintConv => '$self->ConvertDateTime($val)',
|
---|
| 46 | },
|
---|
| 47 | Shutter => {
|
---|
| 48 | Name => 'ExposureTime',
|
---|
| 49 | ValueConv => '$val * 1e-6',
|
---|
| 50 | PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
---|
| 51 | },
|
---|
| 52 | shtr => {
|
---|
| 53 | Name => 'ExposureTime',
|
---|
| 54 | ValueConv => '$val * 1e-6',
|
---|
| 55 | PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
---|
| 56 | },
|
---|
| 57 | 'Serial#' => {
|
---|
| 58 | Name => 'SerialNumber',
|
---|
| 59 | Groups => { 2 => 'Camera' },
|
---|
| 60 | },
|
---|
| 61 | Flash => { PrintConv => { 0 => 'Off', 1 => 'On' } },
|
---|
| 62 | Macro => { PrintConv => { 0 => 'Off', 1 => 'On' } },
|
---|
| 63 | StrobeTime => { },
|
---|
| 64 | Ytarget => { Name => 'YTarget' },
|
---|
| 65 | ylevel => { Name => 'YLevel' },
|
---|
| 66 | FocusPos => { },
|
---|
| 67 | FocusMode => { },
|
---|
| 68 | Quality => { },
|
---|
| 69 | ExpBias => 'ExposureCompensation',
|
---|
| 70 | FWare => 'FirmwareVersion',
|
---|
| 71 | StrobeTime => { },
|
---|
| 72 | Resolution => { },
|
---|
| 73 | Protect => { },
|
---|
| 74 | ConTake => { },
|
---|
| 75 | ImageSize => { PrintConv => '$val=~tr/-/x/;$val' },
|
---|
| 76 | ColorMode => { },
|
---|
| 77 | Zoom => { },
|
---|
| 78 | ZoomPos => { },
|
---|
| 79 | LightS => { },
|
---|
| 80 | Type => {
|
---|
| 81 | Name => 'CameraType',
|
---|
| 82 | Groups => { 2 => 'Camera' },
|
---|
| 83 | DataMember => 'CameraType',
|
---|
| 84 | RawConv => '$self->{CameraType} = $val',
|
---|
| 85 | },
|
---|
| 86 | Version => { Groups => { 2 => 'Camera' } },
|
---|
| 87 | ID => { Groups => { 2 => 'Camera' } },
|
---|
| 88 | );
|
---|
| 89 |
|
---|
| 90 | # APP12 segment written in Photoshop "Save For Web" images
|
---|
| 91 | # (from tests with Photoshop 7 files - PH/1)
|
---|
| 92 | %Image::ExifTool::APP12::Ducky = (
|
---|
| 93 | PROCESS_PROC => \&ProcessDucky,
|
---|
| 94 | WRITE_PROC => \&WriteDucky,
|
---|
| 95 | GROUPS => { 0 => 'Ducky', 1 => 'Ducky', 2 => 'Image' },
|
---|
| 96 | WRITABLE => 'string',
|
---|
| 97 | NOTES => q{
|
---|
| 98 | Photoshop uses the JPEG APP12 "Ducky" segment to store some information in
|
---|
| 99 | "Save for Web" images.
|
---|
| 100 | },
|
---|
| 101 | 1 => { #PH
|
---|
| 102 | Name => 'Quality',
|
---|
| 103 | Priority => 0,
|
---|
| 104 | Avoid => 1,
|
---|
| 105 | Writable => 'int32u',
|
---|
| 106 | ValueConv => 'unpack("N",$val)', # 4-byte integer
|
---|
| 107 | ValueConvInv => 'pack("N",$val)',
|
---|
| 108 | PrintConv => '"$val%"',
|
---|
| 109 | PrintConvInv => '$val=~/(\d+)/ ? $1 : undef',
|
---|
| 110 | },
|
---|
| 111 | 2 => { #1
|
---|
| 112 | Name => 'Comment',
|
---|
| 113 | Priority => 0,
|
---|
| 114 | Avoid => 1,
|
---|
| 115 | # (ignore 4-byte character count at start of value)
|
---|
[24107] | 116 | ValueConv => '$self->Decode(substr($val,4),"UCS2","MM")',
|
---|
| 117 | ValueConvInv => 'pack("N",length $val) . $self->Encode($val,"UCS2","MM")',
|
---|
[16842] | 118 | },
|
---|
| 119 | 3 => { #PH
|
---|
| 120 | Name => 'Copyright',
|
---|
| 121 | Priority => 0,
|
---|
| 122 | Avoid => 1,
|
---|
| 123 | Groups => { 2 => 'Author' },
|
---|
| 124 | # (ignore 4-byte character count at start of value)
|
---|
[24107] | 125 | ValueConv => '$self->Decode(substr($val,4),"UCS2","MM")',
|
---|
| 126 | ValueConvInv => 'pack("N",length $val) . $self->Encode($val,"UCS2","MM")',
|
---|
[16842] | 127 | },
|
---|
| 128 | );
|
---|
| 129 |
|
---|
| 130 | #------------------------------------------------------------------------------
|
---|
| 131 | # Write APP12 Ducky segment
|
---|
| 132 | # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
---|
| 133 | # Returns: New directory data or undefined on error
|
---|
| 134 | sub WriteDucky($$$)
|
---|
| 135 | {
|
---|
| 136 | my ($exifTool, $dirInfo, $tagTablePtr) = @_;
|
---|
| 137 | $exifTool or return 1; # allow dummy access to autoload this package
|
---|
| 138 | my $dataPt = $$dirInfo{DataPt};
|
---|
| 139 | my $pos = $$dirInfo{DirStart};
|
---|
| 140 | my $newTags = $exifTool->GetNewTagInfoHash($tagTablePtr);
|
---|
| 141 | my @addTags = sort { $a <=> $b } keys(%$newTags);
|
---|
| 142 | my ($dirEnd, %doneTags);
|
---|
| 143 | if ($dataPt) {
|
---|
| 144 | $dirEnd = $pos + $$dirInfo{DirLen};
|
---|
| 145 | } else {
|
---|
| 146 | my $tmp = '';
|
---|
| 147 | $dataPt = \$tmp;
|
---|
| 148 | $pos = $dirEnd = 0;
|
---|
| 149 | }
|
---|
| 150 | my $newData = '';
|
---|
| 151 | SetByteOrder('MM');
|
---|
| 152 | # process all data blocks in Ducky segment
|
---|
| 153 | for (;;) {
|
---|
| 154 | my ($tag, $len, $val);
|
---|
| 155 | if ($pos + 4 <= $dirEnd) {
|
---|
| 156 | $tag = Get16u($dataPt, $pos);
|
---|
| 157 | $len = Get16u($dataPt, $pos + 2);
|
---|
| 158 | $pos += 4;
|
---|
| 159 | if ($pos + $len > $dirEnd) {
|
---|
| 160 | $exifTool->Warn('Invalid Ducky block length');
|
---|
| 161 | return undef;
|
---|
| 162 | }
|
---|
| 163 | $val = substr($$dataPt, $pos, $len);
|
---|
| 164 | $pos += $len;
|
---|
| 165 | } else {
|
---|
| 166 | last unless @addTags;
|
---|
| 167 | $tag = pop @addTags;
|
---|
| 168 | next if $doneTags{$tag};
|
---|
| 169 | }
|
---|
| 170 | $doneTags{$tag} = 1;
|
---|
| 171 | my $tagInfo = $$newTags{$tag};
|
---|
| 172 | if ($tagInfo) {
|
---|
[24107] | 173 | my $nvHash = $exifTool->GetNewValueHash($tagInfo);
|
---|
[16842] | 174 | my $isNew;
|
---|
| 175 | if (defined $val) {
|
---|
[24107] | 176 | if (Image::ExifTool::IsOverwriting($nvHash, $val)) {
|
---|
| 177 | $exifTool->VerboseValue("- Ducky:$$tagInfo{Name}", $val);
|
---|
[16842] | 178 | $isNew = 1;
|
---|
| 179 | }
|
---|
| 180 | } else {
|
---|
[24107] | 181 | next unless Image::ExifTool::IsCreating($nvHash);
|
---|
[16842] | 182 | $isNew = 1;
|
---|
| 183 | }
|
---|
| 184 | if ($isNew) {
|
---|
[24107] | 185 | $val = Image::ExifTool::GetNewValues($nvHash);
|
---|
[16842] | 186 | ++$exifTool->{CHANGED};
|
---|
| 187 | next unless defined $val; # next if tag is being deleted
|
---|
[24107] | 188 | $exifTool->VerboseValue("+ Ducky:$$tagInfo{Name}", $val);
|
---|
[16842] | 189 | }
|
---|
| 190 | }
|
---|
| 191 | $newData .= pack('nn', $tag, length $val) . $val;
|
---|
| 192 | }
|
---|
| 193 | $newData .= "\0\0" if length $newData;
|
---|
| 194 | return $newData;
|
---|
| 195 | }
|
---|
| 196 |
|
---|
| 197 | #------------------------------------------------------------------------------
|
---|
| 198 | # Process APP12 Ducky segment (ref PH)
|
---|
| 199 | # Inputs: 0) ExifTool object reference, 1) Directory information ref, 2) tag table ref
|
---|
| 200 | # Returns: 1 on success, 0 if this wasn't a recognized Ducky segment
|
---|
| 201 | # Notes: This segment has the following format:
|
---|
| 202 | # 1) 5 bytes: "Ducky"
|
---|
| 203 | # 2) multiple data blocks (all integers are big endian):
|
---|
| 204 | # a) 2 bytes: block type (0=end, 1=Quality, 2=Comment, 3=Copyright)
|
---|
| 205 | # b) 2 bytes: block length (N)
|
---|
| 206 | # c) N bytes: block data
|
---|
| 207 | sub ProcessDucky($$$)
|
---|
| 208 | {
|
---|
| 209 | my ($exifTool, $dirInfo, $tagTablePtr) = @_;
|
---|
| 210 | my $dataPt = $$dirInfo{DataPt};
|
---|
| 211 | my $pos = $$dirInfo{DirStart};
|
---|
| 212 | my $dirEnd = $pos + $$dirInfo{DirLen};
|
---|
| 213 | SetByteOrder('MM');
|
---|
| 214 | # process all data blocks in Ducky segment
|
---|
| 215 | for (;;) {
|
---|
| 216 | last if $pos + 4 > $dirEnd;
|
---|
| 217 | my $tag = Get16u($dataPt, $pos);
|
---|
| 218 | my $len = Get16u($dataPt, $pos + 2);
|
---|
| 219 | $pos += 4;
|
---|
| 220 | if ($pos + $len > $dirEnd) {
|
---|
| 221 | $exifTool->Warn('Invalid Ducky block length');
|
---|
| 222 | last;
|
---|
| 223 | }
|
---|
| 224 | my $val = substr($$dataPt, $pos, $len);
|
---|
| 225 | $exifTool->HandleTag($tagTablePtr, $tag, $val,
|
---|
| 226 | DataPt => $dataPt,
|
---|
| 227 | DataPos => $$dirInfo{DataPos},
|
---|
| 228 | Start => $pos,
|
---|
| 229 | Size => $len,
|
---|
| 230 | );
|
---|
| 231 | $pos += $len;
|
---|
| 232 | }
|
---|
| 233 | return 1;
|
---|
| 234 | }
|
---|
| 235 |
|
---|
| 236 | #------------------------------------------------------------------------------
|
---|
| 237 | # Process APP12 Picture Info segment (ref PH)
|
---|
| 238 | # Inputs: 0) ExifTool object reference, 1) Directory information ref, 2) tag table ref
|
---|
| 239 | # Returns: 1 on success, 0 if this wasn't a recognized APP12
|
---|
| 240 | sub ProcessAPP12($$$)
|
---|
| 241 | {
|
---|
| 242 | my ($exifTool, $dirInfo, $tagTablePtr) = @_;
|
---|
| 243 | my $dataPt = $$dirInfo{DataPt};
|
---|
| 244 | my $dirStart = $$dirInfo{DirStart} || 0;
|
---|
| 245 | my $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart);
|
---|
| 246 | if ($dirLen != $dirStart + length($$dataPt)) {
|
---|
| 247 | my $buff = substr($$dataPt, $dirStart, $dirLen);
|
---|
| 248 | $dataPt = \$buff;
|
---|
| 249 | } else {
|
---|
| 250 | pos($$dataPt) = $$dirInfo{DirStart};
|
---|
| 251 | }
|
---|
| 252 | my $verbose = $exifTool->Options('Verbose');
|
---|
| 253 | my $success = 0;
|
---|
| 254 | my $section = '';
|
---|
| 255 | pos($$dataPt) = 0;
|
---|
| 256 |
|
---|
| 257 | # this regular expression is a bit complex, but basically we are looking for
|
---|
| 258 | # section headers (ie. "[Camera Info]") and tag/value pairs (ie. "tag=value",
|
---|
| 259 | # where "value" may contain white space), separated by spaces or CR/LF.
|
---|
| 260 | # (APP12 uses CR/LF, but Olympus TextualInfo is similar and uses spaces)
|
---|
| 261 | while ($$dataPt =~ /(\[.*?\]|[\w#-]+=[\x20-\x7e]+?(?=\s*([\n\r\0]|[\w#-]+=|\[|$)))/g) {
|
---|
| 262 | my $token = $1;
|
---|
| 263 | # was this a section name?
|
---|
| 264 | if ($token =~ /^\[(.*)\]/) {
|
---|
| 265 | $exifTool->VerboseDir($1) if $verbose;
|
---|
| 266 | $section = ($token =~ /\[(\S+) ?Info\]/i) ? $1 : '';
|
---|
| 267 | $success = 1;
|
---|
| 268 | next;
|
---|
| 269 | }
|
---|
| 270 | $exifTool->VerboseDir($$dirInfo{DirName}) if $verbose and not $success;
|
---|
| 271 | $success = 1;
|
---|
| 272 | my ($tag, $val) = ($token =~ /(\S+)=(.+)/);
|
---|
| 273 | my $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tag);
|
---|
| 274 | $verbose and $exifTool->VerboseInfo($tag, $tagInfo, Value => $val);
|
---|
| 275 | unless ($tagInfo) {
|
---|
| 276 | # add new tag to table
|
---|
[24107] | 277 | $tagInfo = { Name => ucfirst $tag };
|
---|
[16842] | 278 | # put in Camera group if information in "Camera" section
|
---|
| 279 | $$tagInfo{Groups} = { 2 => 'Camera' } if $section =~ /camera/i;
|
---|
| 280 | Image::ExifTool::AddTagToTable($tagTablePtr, $tag, $tagInfo);
|
---|
| 281 | }
|
---|
| 282 | $exifTool->FoundTag($tagInfo, $val);
|
---|
| 283 | }
|
---|
| 284 | return $success;
|
---|
| 285 | }
|
---|
| 286 |
|
---|
| 287 |
|
---|
| 288 | 1; #end
|
---|
| 289 |
|
---|
| 290 | __END__
|
---|
| 291 |
|
---|
| 292 | =head1 NAME
|
---|
| 293 |
|
---|
| 294 | Image::ExifTool::APP12 - Read APP12 meta information
|
---|
| 295 |
|
---|
| 296 | =head1 SYNOPSIS
|
---|
| 297 |
|
---|
| 298 | This module is loaded automatically by Image::ExifTool when required.
|
---|
| 299 |
|
---|
| 300 | =head1 DESCRIPTION
|
---|
| 301 |
|
---|
| 302 | This module contains definitions required by Image::ExifTool to interpret
|
---|
| 303 | APP12 meta information.
|
---|
| 304 |
|
---|
| 305 | =head1 AUTHOR
|
---|
| 306 |
|
---|
[24107] | 307 | Copyright 2003-2011, Phil Harvey (phil at owl.phy.queensu.ca)
|
---|
[16842] | 308 |
|
---|
| 309 | This library is free software; you can redistribute it and/or modify it
|
---|
| 310 | under the same terms as Perl itself.
|
---|
| 311 |
|
---|
| 312 | =head1 ACKNOWLEDGEMENTS
|
---|
| 313 |
|
---|
| 314 | Thanks to Heinrich Giesen for his help decoding APP12 "Ducky" information.
|
---|
| 315 |
|
---|
| 316 | =head1 SEE ALSO
|
---|
| 317 |
|
---|
| 318 | L<Image::ExifTool::TagNames/APP12 Tags>,
|
---|
| 319 | L<Image::ExifTool(3pm)|Image::ExifTool>
|
---|
| 320 |
|
---|
| 321 | =cut
|
---|