File Coverage

blib/lib/Image/ExifTool/APP12.pm
Criterion Covered Total %
statement 100 104 96.1
branch 36 46 78.2
condition 4 8 50.0
subroutine 6 6 100.0
pod 0 3 0.0
total 146 167 87.4


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