line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
2
|
|
|
|
|
|
|
# File: Validate.pm |
3
|
|
|
|
|
|
|
# |
4
|
|
|
|
|
|
|
# Description: Additional metadata validation |
5
|
|
|
|
|
|
|
# |
6
|
|
|
|
|
|
|
# Created: 2017/01/18 - P. Harvey |
7
|
|
|
|
|
|
|
# |
8
|
|
|
|
|
|
|
# Notes: My apologies for the convoluted logic contained herein, but it |
9
|
|
|
|
|
|
|
# is done this way to retro-fit the Validate feature into the |
10
|
|
|
|
|
|
|
# existing ExifTool code while reducing the possibility of |
11
|
|
|
|
|
|
|
# introducing bugs or slowing down processing when this feature |
12
|
|
|
|
|
|
|
# is not used. |
13
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
package Image::ExifTool::Validate; |
16
|
|
|
|
|
|
|
|
17
|
1
|
|
|
1
|
|
9
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
41
|
|
18
|
1
|
|
|
1
|
|
6
|
use vars qw($VERSION %exifSpec); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
64
|
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
$VERSION = '1.19'; |
21
|
|
|
|
|
|
|
|
22
|
1
|
|
|
1
|
|
8
|
use Image::ExifTool qw(:Utils); |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
165
|
|
23
|
1
|
|
|
1
|
|
8
|
use Image::ExifTool::Exif; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
4029
|
|
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
# EXIF table tag ID's which are part of the EXIF 2.32 specification |
26
|
|
|
|
|
|
|
# (with ExifVersion numbers for tags where I can determine the version) |
27
|
|
|
|
|
|
|
# (also used by BuildTagLookup to add underlines in HTML version of EXIF Tag Table) |
28
|
|
|
|
|
|
|
%exifSpec = ( |
29
|
|
|
|
|
|
|
0x1 => 210, |
30
|
|
|
|
|
|
|
0x100 => 1, 0x8298 => 1, 0x9207 => 1, 0xa217 => 1, |
31
|
|
|
|
|
|
|
0x101 => 1, 0x829a => 1, 0x9208 => 1, 0xa300 => 1, |
32
|
|
|
|
|
|
|
0x102 => 1, 0x829d => 1, 0x9209 => 1, 0xa301 => 1, |
33
|
|
|
|
|
|
|
0x103 => 1, 0x8769 => 1, 0x920a => 1, 0xa302 => 1, |
34
|
|
|
|
|
|
|
0x106 => 1, 0x8822 => 1, 0x9214 => 220, 0xa401 => 220, |
35
|
|
|
|
|
|
|
0x10e => 1, 0x8824 => 1, 0x927c => 1, 0xa402 => 220, |
36
|
|
|
|
|
|
|
0x10f => 1, 0x8825 => 200, 0x9286 => 1, 0xa403 => 220, |
37
|
|
|
|
|
|
|
0x110 => 1, 0x8827 => 1, 0x9290 => 1, 0xa404 => 220, |
38
|
|
|
|
|
|
|
0x111 => 1, 0x8828 => 1, 0x9291 => 1, 0xa405 => 220, |
39
|
|
|
|
|
|
|
0x112 => 1, 0x8830 => 230, 0x9292 => 1, 0xa406 => 220, |
40
|
|
|
|
|
|
|
0x115 => 1, 0x8831 => 230, 0x9400 => 231, 0xa407 => 220, |
41
|
|
|
|
|
|
|
0x116 => 1, 0x8832 => 230, 0x9401 => 231, 0xa408 => 220, |
42
|
|
|
|
|
|
|
0x117 => 1, 0x8833 => 230, 0x9402 => 231, 0xa409 => 220, |
43
|
|
|
|
|
|
|
0x11a => 1, 0x8834 => 230, 0x9403 => 231, 0xa40a => 220, |
44
|
|
|
|
|
|
|
0x11b => 1, 0x8835 => 230, 0x9404 => 231, 0xa40b => 220, |
45
|
|
|
|
|
|
|
0x11c => 1, 0x9000 => 1, 0x9405 => 231, 0xa40c => 220, |
46
|
|
|
|
|
|
|
0x128 => 1, 0x9003 => 1, 0xa000 => 1, 0xa460 => 232, |
47
|
|
|
|
|
|
|
0x12d => 1, 0x9004 => 1, 0xa001 => 1, 0xa461 => 232, |
48
|
|
|
|
|
|
|
0x131 => 1, 0x9010 => 231, 0xa002 => 1, 0xa462 => 232, |
49
|
|
|
|
|
|
|
0x132 => 1, 0x9011 => 231, 0xa003 => 1, 0xa420 => 220, |
50
|
|
|
|
|
|
|
0x13b => 1, 0x9012 => 231, 0xa004 => 1, 0xa430 => 230, |
51
|
|
|
|
|
|
|
0x13e => 1, 0x9101 => 1, 0xa005 => 210, 0xa431 => 230, |
52
|
|
|
|
|
|
|
0x13f => 1, 0x9102 => 1, 0xa20b => 1, 0xa432 => 230, |
53
|
|
|
|
|
|
|
0x201 => 1, 0x9201 => 1, 0xa20c => 1, 0xa433 => 230, |
54
|
|
|
|
|
|
|
0x202 => 1, 0x9202 => 1, 0xa20e => 1, 0xa434 => 230, |
55
|
|
|
|
|
|
|
0x211 => 1, 0x9203 => 1, 0xa20f => 1, 0xa435 => 230, |
56
|
|
|
|
|
|
|
0x212 => 1, 0x9204 => 1, 0xa210 => 1, 0xa500 => 221, |
57
|
|
|
|
|
|
|
0x213 => 1, 0x9205 => 1, 0xa214 => 1, |
58
|
|
|
|
|
|
|
0x214 => 1, 0x9206 => 1, 0xa215 => 1, |
59
|
|
|
|
|
|
|
); |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# GPSVersionID numbers when each tag was introduced |
62
|
|
|
|
|
|
|
my %gpsVer = ( |
63
|
|
|
|
|
|
|
0x01 => 2000, 0x09 => 2000, 0x11 => 2000, 0x19 => 2000, |
64
|
|
|
|
|
|
|
0x02 => 2000, 0x0a => 2000, 0x12 => 2000, 0x1a => 2000, |
65
|
|
|
|
|
|
|
0x03 => 2000, 0x0b => 2000, 0x13 => 2000, 0x1b => 2200, |
66
|
|
|
|
|
|
|
0x04 => 2000, 0x0c => 2000, 0x14 => 2000, 0x1c => 2200, |
67
|
|
|
|
|
|
|
0x05 => 2000, 0x0d => 2000, 0x15 => 2000, 0x1d => 2200, |
68
|
|
|
|
|
|
|
0x06 => 2000, 0x0e => 2000, 0x16 => 2000, 0x1e => 2200, |
69
|
|
|
|
|
|
|
0x07 => 2000, 0x0f => 2000, 0x17 => 2000, 0x1f => 2300, |
70
|
|
|
|
|
|
|
0x08 => 2000, 0x10 => 2000, 0x18 => 2000, |
71
|
|
|
|
|
|
|
); |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
# lookup to check version numbers |
74
|
|
|
|
|
|
|
my %verCheck = ( |
75
|
|
|
|
|
|
|
ExifIFD => { ExifVersion => \%exifSpec }, |
76
|
|
|
|
|
|
|
InteropIFD => { ExifVersion => \%exifSpec }, |
77
|
|
|
|
|
|
|
GPS => { GPSVersionID => \%gpsVer }, |
78
|
|
|
|
|
|
|
); |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
# tags standard in various RAW file formats |
81
|
|
|
|
|
|
|
my %otherSpec = ( |
82
|
|
|
|
|
|
|
CR2 => { 0xc5d8 => 1, 0xc5d9 => 1, 0xc5e0 => 1, 0xc640 => 1, 0xc6dc => 1, 0xc6dd => 1 }, |
83
|
|
|
|
|
|
|
NEF => { 0x9216 => 1, 0x9217 => 1 }, |
84
|
|
|
|
|
|
|
DNG => { 0x882a => 1, 0x9211 => 1, 0x9216 => 1 }, |
85
|
|
|
|
|
|
|
ARW => { 0x7000 => 1, 0x7001 => 1, 0x7010 => 1, 0x7011 => 1, 0x7020 => 1, 0x7031 => 1, |
86
|
|
|
|
|
|
|
0x7032 => 1, 0x7034 => 1, 0x7035 => 1, 0x7036 => 1, 0x7037 => 1, 0x7038 => 1, |
87
|
|
|
|
|
|
|
0x7310 => 1, 0x7313 => 1, 0x7316 => 1, 0x74c7 => 1, 0x74c8 => 1, 0xa500 => 1 }, |
88
|
|
|
|
|
|
|
RW2 => { All => 1 }, # ignore all unknown tags in RW2 |
89
|
|
|
|
|
|
|
RWL => { All => 1 }, |
90
|
|
|
|
|
|
|
RAF => { All => 1 }, # (temporary) |
91
|
|
|
|
|
|
|
DCR => { All => 1 }, |
92
|
|
|
|
|
|
|
KDC => { All => 1 }, |
93
|
|
|
|
|
|
|
JXR => { All => 1 }, |
94
|
|
|
|
|
|
|
SRW => { 0xa010 => 1, 0xa011 => 1, 0xa101 => 1, 0xa102 => 1 }, |
95
|
|
|
|
|
|
|
NRW => { 0x9216 => 1, 0x9217 => 1 }, |
96
|
|
|
|
|
|
|
X3F => { 0xa500 => 1 }, |
97
|
|
|
|
|
|
|
); |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
# standard format for tags (not necessary for exifSpec or GPS tags where Writable is defined) |
100
|
|
|
|
|
|
|
my %stdFormat = ( |
101
|
|
|
|
|
|
|
ExifIFD => { |
102
|
|
|
|
|
|
|
0xa002 => 'int(16|32)u', |
103
|
|
|
|
|
|
|
0xa003 => 'int(16|32)u', |
104
|
|
|
|
|
|
|
}, |
105
|
|
|
|
|
|
|
InteropIFD => { |
106
|
|
|
|
|
|
|
0x01 => 'string', |
107
|
|
|
|
|
|
|
0x02 => 'undef', |
108
|
|
|
|
|
|
|
0x1000 => 'string', |
109
|
|
|
|
|
|
|
0x1001 => 'int(16|32)u', |
110
|
|
|
|
|
|
|
0x1002 => 'int(16|32)u', |
111
|
|
|
|
|
|
|
}, |
112
|
|
|
|
|
|
|
IFD => { |
113
|
|
|
|
|
|
|
# TIFF, EXIF, XMP, IPTC, ICC_Profile and PrintIM standard tags: |
114
|
|
|
|
|
|
|
0xfe => 'int32u', 0x11f => 'rational64u', 0x14a => 'int32u', 0x205 => 'int16u', |
115
|
|
|
|
|
|
|
0xff => 'int16u', 0x120 => 'int32u', 0x14c => 'int16u', 0x206 => 'int16u', |
116
|
|
|
|
|
|
|
0x100 => 'int(16|32)u', 0x121 => 'int32u', 0x14d => 'string', 0x207 => 'int32u', |
117
|
|
|
|
|
|
|
0x101 => 'int(16|32)u', 0x122 => 'int16u', 0x14e => 'int16u', 0x208 => 'int32u', |
118
|
|
|
|
|
|
|
0x107 => 'int16u', 0x123 => 'int16u', 0x150 => 'int(8|16)u', 0x209 => 'int32u', |
119
|
|
|
|
|
|
|
0x108 => 'int16u', 0x124 => 'int32u', 0x151 => 'string', 0x211 => 'rational64u', |
120
|
|
|
|
|
|
|
0x109 => 'int16u', 0x125 => 'int32u', 0x152 => 'int16u', 0x212 => 'int16u', |
121
|
|
|
|
|
|
|
0x10a => 'int16u', 0x129 => 'int16u', 0x153 => 'int16u', 0x213 => 'int16u', |
122
|
|
|
|
|
|
|
0x10d => 'string', 0x13c => 'string', 0x154 => '.*', 0x214 => 'rational64u', |
123
|
|
|
|
|
|
|
0x111 => 'int(16|32)u', 0x13d => 'int16u', 0x155 => '.*', 0x2bc => 'int8u', |
124
|
|
|
|
|
|
|
0x116 => 'int(16|32)u', 0x140 => 'int16u', 0x156 => 'int16u', 0x828d => 'int16u', |
125
|
|
|
|
|
|
|
0x117 => 'int(16|32)u', 0x141 => 'int16u', 0x15b => 'undef', 0x828e => 'int8u', |
126
|
|
|
|
|
|
|
0x118 => 'int16u', 0x142 => 'int(16|32)u', 0x200 => 'int16u', 0x83bb => 'int32u', |
127
|
|
|
|
|
|
|
0x119 => 'int16u', 0x143 => 'int(16|32)u', 0x201 => 'int32u', 0x8649 => 'int8u', |
128
|
|
|
|
|
|
|
0x11d => 'string', 0x144 => 'int32u', 0x202 => 'int32u', 0x8773 => 'undef', |
129
|
|
|
|
|
|
|
0x11e => 'rational64u', 0x145 => 'int(16|32)u', 0x203 => 'int16u', 0xc4a5 => 'undef', |
130
|
|
|
|
|
|
|
# Windows Explorer tags: |
131
|
|
|
|
|
|
|
0x9c9b => 'int8u', 0x9c9d => 'int8u', 0x9c9f => 'int8u', |
132
|
|
|
|
|
|
|
0x9c9c => 'int8u', 0x9c9e => 'int8u', |
133
|
|
|
|
|
|
|
# GeoTiff tags: |
134
|
|
|
|
|
|
|
0x830e => 'double', 0x8482 => 'double', 0x87af => 'int16u', 0x87b1 => 'string', |
135
|
|
|
|
|
|
|
0x8480 => 'double', 0x85d8 => 'double', 0x87b0 => 'double', |
136
|
|
|
|
|
|
|
# DNG tags: |
137
|
|
|
|
|
|
|
0xc615 => '(string|int8u)', 0xc6d3 => '', |
138
|
|
|
|
|
|
|
0xc61a => '(int16u|int32u|rational64u)', 0xc6f4 => '(string|int8u)', |
139
|
|
|
|
|
|
|
0xc61d => 'int(16|32)u', 0xc6f6 => '(string|int8u)', |
140
|
|
|
|
|
|
|
0xc61f => '(int16u|int32u|rational64u)', 0xc6f8 => '(string|int8u)', |
141
|
|
|
|
|
|
|
0xc620 => '(int16u|int32u|rational64u)', 0xc6fe => '(string|int8u)', |
142
|
|
|
|
|
|
|
0xc628 => '(int16u|rational64u)', 0xc716 => '(string|int8u)', |
143
|
|
|
|
|
|
|
0xc634 => 'int8u', 0xc717 => '(string|int8u)', |
144
|
|
|
|
|
|
|
0xc640 => '', 0xc718 => '(string|int8u)', |
145
|
|
|
|
|
|
|
0xc660 => '', 0xc71e => 'int(16|32)u', |
146
|
|
|
|
|
|
|
0xc68b => '(string|int8u)', 0xc71f => 'int(16|32)u', |
147
|
|
|
|
|
|
|
0xc68d => 'int(16|32)u', 0xc791 => 'int(16|32)u', |
148
|
|
|
|
|
|
|
0xc68e => 'int(16|32)u', 0xc792 => 'int(16|32)u', |
149
|
|
|
|
|
|
|
0xc6d2 => '', 0xc793 => '(int16u|int32u|rational64u)', |
150
|
|
|
|
|
|
|
}, |
151
|
|
|
|
|
|
|
); |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
# generate lookup for any IFD |
154
|
|
|
|
|
|
|
my %stdFormatAnyIFD = map { %{$stdFormat{$_}} } keys %stdFormat; |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
# tag values to validate based on file type (from EXIF specification) |
157
|
|
|
|
|
|
|
# - validation code may access $val and %val, and returns 1 on success, |
158
|
|
|
|
|
|
|
# or error message otherwise ('' for a generic message) |
159
|
|
|
|
|
|
|
# - entry is undef if tag must not exist (same as 'not defined $val' in code) |
160
|
|
|
|
|
|
|
my %validValue = ( |
161
|
|
|
|
|
|
|
JPEG => { |
162
|
|
|
|
|
|
|
IFD0 => { |
163
|
|
|
|
|
|
|
0x100 => undef, # ImageWidth |
164
|
|
|
|
|
|
|
0x101 => undef, # ImageLength |
165
|
|
|
|
|
|
|
0x102 => undef, # BitsPerSample |
166
|
|
|
|
|
|
|
0x103 => undef, # Compression |
167
|
|
|
|
|
|
|
0x106 => undef, # PhotometricInterpretation |
168
|
|
|
|
|
|
|
0x111 => undef, # StripOffsets |
169
|
|
|
|
|
|
|
0x115 => undef, # SamplesPerPixel |
170
|
|
|
|
|
|
|
0x116 => undef, # RowsPerStrip |
171
|
|
|
|
|
|
|
0x117 => undef, # StripByteCounts |
172
|
|
|
|
|
|
|
0x11a => 'defined $val', # XResolution |
173
|
|
|
|
|
|
|
0x11b => 'defined $val', # YResolution |
174
|
|
|
|
|
|
|
0x11c => undef, # PlanarConfiguration |
175
|
|
|
|
|
|
|
0x128 => '$val =~ /^[123]$/', # ResolutionUnit |
176
|
|
|
|
|
|
|
0x201 => undef, # JPEGInterchangeFormat |
177
|
|
|
|
|
|
|
0x202 => undef, # JPEGInterchangeFormatLength |
178
|
|
|
|
|
|
|
0x212 => undef, # YCbCrSubSampling |
179
|
|
|
|
|
|
|
0x213 => '$val =~ /^[12]$/', # YCbCrPositioning |
180
|
|
|
|
|
|
|
}, |
181
|
|
|
|
|
|
|
IFD1 => { |
182
|
|
|
|
|
|
|
0x100 => undef, # ImageWidth |
183
|
|
|
|
|
|
|
0x101 => undef, # ImageLength |
184
|
|
|
|
|
|
|
0x102 => undef, # BitsPerSample |
185
|
|
|
|
|
|
|
0x103 => '$val == 6', # Compression |
186
|
|
|
|
|
|
|
0x106 => undef, # PhotometricInterpretation |
187
|
|
|
|
|
|
|
0x111 => undef, # StripOffsets |
188
|
|
|
|
|
|
|
0x115 => undef, # SamplesPerPixel |
189
|
|
|
|
|
|
|
0x116 => undef, # RowsPerStrip |
190
|
|
|
|
|
|
|
0x117 => undef, # StripByteCounts |
191
|
|
|
|
|
|
|
0x11a => 'defined $val', # XResolution |
192
|
|
|
|
|
|
|
0x11b => 'defined $val', # YResolution |
193
|
|
|
|
|
|
|
0x11c => undef, # PlanarConfiguration |
194
|
|
|
|
|
|
|
0x128 => '$val =~ /^[123]$/', # ResolutionUnit |
195
|
|
|
|
|
|
|
0x201 => 'defined $val', # JPEGInterchangeFormat |
196
|
|
|
|
|
|
|
0x202 => 'defined $val', # JPEGInterchangeFormatLength |
197
|
|
|
|
|
|
|
0x212 => undef, # YCbCrSubSampling |
198
|
|
|
|
|
|
|
}, |
199
|
|
|
|
|
|
|
ExifIFD => { |
200
|
|
|
|
|
|
|
0x9000 => 'defined $val and $val =~ /^\d{4}$/', # ExifVersion |
201
|
|
|
|
|
|
|
0x9101 => 'defined $val', # ComponentsConfiguration |
202
|
|
|
|
|
|
|
0xa000 => 'defined $val', # FlashpixVersion |
203
|
|
|
|
|
|
|
0xa001 => '$val == 1 or $val == 0xffff', # ColorSpace |
204
|
|
|
|
|
|
|
0xa002 => 'defined $val', # PixelXDimension |
205
|
|
|
|
|
|
|
0xa003 => 'defined $val', # PixelYDimension |
206
|
|
|
|
|
|
|
}, |
207
|
|
|
|
|
|
|
GPS => { |
208
|
|
|
|
|
|
|
0x00 => 'defined $val and $val =~ /^\d \d \d \d$/', # GPSVersionID |
209
|
|
|
|
|
|
|
0x1b => 'not defined $val or $val =~ /^(GPS|CELLID|WLAN|MANUAL)$/', # GPSProcessingMethod |
210
|
|
|
|
|
|
|
}, |
211
|
|
|
|
|
|
|
InteropIFD => { }, # (needed for ExifVersion check) |
212
|
|
|
|
|
|
|
}, |
213
|
|
|
|
|
|
|
TIFF => { |
214
|
|
|
|
|
|
|
IFD0 => { |
215
|
|
|
|
|
|
|
0x100 => 'defined $val', # ImageWidth |
216
|
|
|
|
|
|
|
0x101 => 'defined $val', # ImageLength |
217
|
|
|
|
|
|
|
# (default is 1) 0x102 => 'defined $val', # BitsPerSample |
218
|
|
|
|
|
|
|
0x103 => q{ |
219
|
|
|
|
|
|
|
not defined $val or $val =~ /^(1|5|6|32773)$/ or |
220
|
|
|
|
|
|
|
($val == 2 and (not defined $val{0x102} or $val{0x102} == 1)); |
221
|
|
|
|
|
|
|
}, # Compression |
222
|
|
|
|
|
|
|
0x106 => '$val =~ /^[0123]$/', # PhotometricInterpretation |
223
|
|
|
|
|
|
|
0x111 => 'defined $val', # StripOffsets |
224
|
|
|
|
|
|
|
# SamplesPerPixel |
225
|
|
|
|
|
|
|
0x115 => q{ |
226
|
|
|
|
|
|
|
my $pi = $val{0x106} || 0; |
227
|
|
|
|
|
|
|
my $xtra = ($val{0x152} ? scalar(split ' ', $val{0x152}) : 0); |
228
|
|
|
|
|
|
|
if ($pi == 2 or $pi == 6) { |
229
|
|
|
|
|
|
|
return $val == 3 + $xtra; |
230
|
|
|
|
|
|
|
} elsif ($pi == 5) { |
231
|
|
|
|
|
|
|
return $val == 4 + $xtra; |
232
|
|
|
|
|
|
|
} else { |
233
|
|
|
|
|
|
|
return 1; |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
}, |
236
|
|
|
|
|
|
|
0x116 => 'defined $val', # RowsPerStrip |
237
|
|
|
|
|
|
|
0x117 => 'defined $val', # StripByteCounts |
238
|
|
|
|
|
|
|
0x11a => 'defined $val', # XResolution |
239
|
|
|
|
|
|
|
0x11b => 'defined $val', # YResolution |
240
|
|
|
|
|
|
|
0x128 => 'not defined $val or $val =~ /^[123]$/', # ResolutionUnit |
241
|
|
|
|
|
|
|
# ColorMap (must be palette image with correct number of colors) |
242
|
|
|
|
|
|
|
0x140 => q{ |
243
|
|
|
|
|
|
|
return '' if defined $val{0x106} and $val{0x106} == 3 xor defined $val; |
244
|
|
|
|
|
|
|
return 1 if not defined $val or length($val) == 6 * 2 ** ($val{0x102} || 0); |
245
|
|
|
|
|
|
|
return 'Invalid count for'; |
246
|
|
|
|
|
|
|
}, |
247
|
|
|
|
|
|
|
0x201 => undef, # JPEGInterchangeFormat |
248
|
|
|
|
|
|
|
0x202 => undef, # JPEGInterchangeFormatLength |
249
|
|
|
|
|
|
|
}, |
250
|
|
|
|
|
|
|
ExifIFD => { |
251
|
|
|
|
|
|
|
0x9000 => 'defined $val', # ExifVersion |
252
|
|
|
|
|
|
|
0x9101 => undef, # ComponentsConfiguration |
253
|
|
|
|
|
|
|
0x9102 => undef, # CompressedBitsPerPixel |
254
|
|
|
|
|
|
|
0xa000 => 'defined $val', # FlashpixVersion |
255
|
|
|
|
|
|
|
0xa001 => '$val == 1 or $val == 0xffff', # ColorSpace |
256
|
|
|
|
|
|
|
0xa002 => undef, # PixelXDimension |
257
|
|
|
|
|
|
|
0xa003 => undef, # PixelYDimension |
258
|
|
|
|
|
|
|
}, |
259
|
|
|
|
|
|
|
InteropIFD => { |
260
|
|
|
|
|
|
|
0x0001 => undef, # InteropIndex |
261
|
|
|
|
|
|
|
}, |
262
|
|
|
|
|
|
|
GPS => { |
263
|
|
|
|
|
|
|
0x00 => 'defined $val and $val =~ /^\d \d \d \d$/', # GPSVersionID |
264
|
|
|
|
|
|
|
0x1b => '$val =~ /^(GPS|CELLID|WLAN|MANUAL)$/', # GPSProcessingMethod |
265
|
|
|
|
|
|
|
}, |
266
|
|
|
|
|
|
|
}, |
267
|
|
|
|
|
|
|
); |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
# validity ranges for constrained date/time fields |
270
|
|
|
|
|
|
|
my @validDateField = ( |
271
|
|
|
|
|
|
|
[ 'Month', 1, 12 ], |
272
|
|
|
|
|
|
|
[ 'Day', 1, 31 ], |
273
|
|
|
|
|
|
|
[ 'Hour', 0, 23 ], |
274
|
|
|
|
|
|
|
[ 'Minutes', 0, 59 ], |
275
|
|
|
|
|
|
|
[ 'Seconds', 0, 59 ], |
276
|
|
|
|
|
|
|
[ 'TZhr', 0, 14 ], |
277
|
|
|
|
|
|
|
[ 'TZmin', 0, 59 ], |
278
|
|
|
|
|
|
|
); |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
# "Validate" tag information |
281
|
|
|
|
|
|
|
my %validateInfo = ( |
282
|
|
|
|
|
|
|
Groups => { 0 => 'ExifTool', 1 => 'ExifTool', 2 => 'ExifTool' }, |
283
|
|
|
|
|
|
|
Notes => q{ |
284
|
|
|
|
|
|
|
generated only if specifically requested. Requesting this tag automatically |
285
|
|
|
|
|
|
|
enables the API L option, imposing |
286
|
|
|
|
|
|
|
additional validation checks when extracting metadata. Returns the number |
287
|
|
|
|
|
|
|
of errors, warnings and minor warnings encountered. Note that the Validate |
288
|
|
|
|
|
|
|
feature focuses mainly on validation of EXIF/TIFF metadata |
289
|
|
|
|
|
|
|
}, |
290
|
|
|
|
|
|
|
PrintConv => { |
291
|
|
|
|
|
|
|
'0 0 0' => 'OK', |
292
|
|
|
|
|
|
|
OTHER => sub { |
293
|
|
|
|
|
|
|
my @val = split ' ', shift; |
294
|
|
|
|
|
|
|
my @rtn; |
295
|
|
|
|
|
|
|
push @rtn, sprintf('%d Error%s', $val[0], $val[0] == 1 ? '' : 's') if $val[0]; |
296
|
|
|
|
|
|
|
push @rtn, sprintf('%d Warning%s', $val[1], $val[1] == 1 ? '' : 's') if $val[1]; |
297
|
|
|
|
|
|
|
if ($val[2]) { |
298
|
|
|
|
|
|
|
my $str = ($val[1] == $val[2] ? ($val[1] == 1 ? '' : 'all ') : "$val[2] "); |
299
|
|
|
|
|
|
|
$rtn[-1] .= " (${str}minor)"; |
300
|
|
|
|
|
|
|
} |
301
|
|
|
|
|
|
|
return join(' and ', @rtn); |
302
|
|
|
|
|
|
|
}, |
303
|
|
|
|
|
|
|
}, |
304
|
|
|
|
|
|
|
); |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
# add "Validate" tag to Extra table |
307
|
|
|
|
|
|
|
AddTagToTable(\%Image::ExifTool::Extra, Validate => \%validateInfo, 1); |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
310
|
|
|
|
|
|
|
# Validate the raw value of a tag |
311
|
|
|
|
|
|
|
# Inputs: 0) ExifTool ref, 1) tag key, 2) raw tag value |
312
|
|
|
|
|
|
|
# Returns: nothing, but issues a minor Warning if a problem was detected |
313
|
|
|
|
|
|
|
sub ValidateRaw($$$) |
314
|
|
|
|
|
|
|
{ |
315
|
213
|
|
|
213
|
0
|
451
|
my ($self, $tag, $val) = @_; |
316
|
213
|
|
|
|
|
407
|
my $tagInfo = $$self{TAG_INFO}{$tag}; |
317
|
213
|
|
|
|
|
275
|
my $wrn; |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
# evaluate Validate code if specified |
320
|
213
|
100
|
|
|
|
499
|
if ($$tagInfo{Validate}) { |
321
|
3
|
|
|
|
|
22
|
local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning; |
322
|
3
|
|
|
|
|
7
|
undef $Image::ExifTool::evalWarning; |
323
|
|
|
|
|
|
|
#### eval Validate ($self, $val, $tagInfo) |
324
|
3
|
|
|
|
|
185
|
my $wrn = eval $$tagInfo{Validate}; |
325
|
3
|
|
33
|
|
|
19
|
my $err = $Image::ExifTool::evalWarning || $@; |
326
|
3
|
50
|
33
|
|
|
21
|
if ($wrn or $err) { |
327
|
0
|
|
|
|
|
0
|
my $name = $$tagInfo{Table}{GROUPS}{0} . ':' . Image::ExifTool::GetTagName($tag); |
328
|
0
|
0
|
|
|
|
0
|
$self->Warn("Validate $name: $err", 1) if $err; |
329
|
0
|
0
|
|
|
|
0
|
$self->Warn("$wrn for $name", 1) if $wrn; |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
} |
332
|
|
|
|
|
|
|
# check for unknown values in PrintConv lookup for all standard EXIF tags |
333
|
213
|
100
|
66
|
|
|
960
|
if (ref $$tagInfo{PrintConv} eq 'HASH' and ($$tagInfo{Table}{SHORT_NAME} eq 'GPS::Main' or |
|
|
|
100
|
|
|
|
|
334
|
|
|
|
|
|
|
($$tagInfo{Table} eq \%Image::ExifTool::Exif::Main and $exifSpec{$$tagInfo{TagID}}))) |
335
|
|
|
|
|
|
|
{ |
336
|
18
|
|
|
|
|
65
|
my $prt = $self->GetValue($tag, 'PrintConv'); |
337
|
18
|
50
|
33
|
|
|
119
|
$wrn = 'Unknown value for' if $prt and $prt =~ /^Unknown \(/; |
338
|
|
|
|
|
|
|
} |
339
|
213
|
50
|
|
|
|
465
|
$wrn = 'Undefined value for' if $val eq 'undef'; |
340
|
213
|
50
|
|
|
|
642
|
if ($wrn) { |
341
|
0
|
|
|
|
|
0
|
my $name = $$self{DIR_NAME} . ':' . Image::ExifTool::GetTagName($tag); |
342
|
0
|
|
|
|
|
0
|
$self->Warn("$wrn $name", 1); |
343
|
|
|
|
|
|
|
} |
344
|
|
|
|
|
|
|
} |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
347
|
|
|
|
|
|
|
# Validate raw EXIF date/time value |
348
|
|
|
|
|
|
|
# Inputs: 0) date/time value |
349
|
|
|
|
|
|
|
# Returns: error string |
350
|
|
|
|
|
|
|
sub ValidateExifDate($) |
351
|
|
|
|
|
|
|
{ |
352
|
3
|
|
|
3
|
0
|
7
|
my $val = shift; |
353
|
3
|
50
|
0
|
|
|
22
|
if ($val =~ /^\d{4}:(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/) { |
|
|
0
|
|
|
|
|
|
354
|
3
|
|
|
|
|
22
|
my @a = ($1,$2,$3,$4,$5); |
355
|
3
|
|
|
|
|
5
|
my ($i, @bad); |
356
|
3
|
|
|
|
|
14
|
for ($i=0; $i<@a; ++$i) { |
357
|
15
|
50
|
33
|
|
|
95
|
next if $a[$i] eq ' ' or ($a[$i] >= $validDateField[$i][1] and $a[$i] <= $validDateField[$i][2]); |
|
|
|
33
|
|
|
|
|
358
|
0
|
|
|
|
|
0
|
push @bad, $validDateField[$i][0]; |
359
|
|
|
|
|
|
|
} |
360
|
3
|
50
|
|
|
|
12
|
return join('+', @bad) . ' out of range' if @bad; |
361
|
|
|
|
|
|
|
# the EXIF specification allows blank fields or an entire blank value |
362
|
|
|
|
|
|
|
} elsif ($val ne ' : : : : ' and $val ne ' ') { |
363
|
0
|
|
|
|
|
0
|
return 'Invalid date/time format'; |
364
|
|
|
|
|
|
|
} |
365
|
3
|
|
|
|
|
23
|
return undef; # OK! |
366
|
|
|
|
|
|
|
} |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
369
|
|
|
|
|
|
|
# Validate EXIF-reformatted XMP date/time value |
370
|
|
|
|
|
|
|
# Inputs: 0) date/time value |
371
|
|
|
|
|
|
|
# Returns: error string |
372
|
|
|
|
|
|
|
sub ValidateXMPDate($) |
373
|
|
|
|
|
|
|
{ |
374
|
0
|
|
|
0
|
0
|
0
|
my $val = shift; |
375
|
0
|
0
|
0
|
|
|
0
|
if ($val =~ /^\d{4}$/ or |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
376
|
|
|
|
|
|
|
$val =~ /^\d{4}:(\d{2})$/ or |
377
|
|
|
|
|
|
|
$val =~ /^\d{4}:(\d{2}):(\d{2})$/ or |
378
|
|
|
|
|
|
|
$val =~ /^\d{4}:(\d{2}):(\d{2}) (\d{2}):(\d{2})()(Z|[-+](\d{2}):(\d{2}))?$/ or |
379
|
|
|
|
|
|
|
$val =~ /^\d{4}:(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})(Z|[-+](\d{2}):(\d{2}))?$/ or |
380
|
|
|
|
|
|
|
$val =~ /^\d{4}:(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})\.?\d*(Z|[-+](\d{2}):(\d{2}))?$/) |
381
|
|
|
|
|
|
|
{ |
382
|
0
|
|
|
|
|
0
|
my @a = ($1,$2,$3,$4,$5,$7,$8); |
383
|
0
|
|
|
|
|
0
|
my ($i, @bad); |
384
|
0
|
|
|
|
|
0
|
for ($i=0; $i<@a; ++$i) { |
385
|
0
|
0
|
|
|
|
0
|
last unless defined $a[$i]; |
386
|
0
|
0
|
0
|
|
|
0
|
next if $a[$i] eq '' or ($a[$i] >= $validDateField[$i][1] and $a[$i] <= $validDateField[$i][2]); |
|
|
|
0
|
|
|
|
|
387
|
0
|
|
|
|
|
0
|
push @bad, $validDateField[$i][0]; |
388
|
|
|
|
|
|
|
} |
389
|
0
|
0
|
|
|
|
0
|
return join('+', @bad) . ' out of range' if @bad; |
390
|
|
|
|
|
|
|
} else { |
391
|
0
|
|
|
|
|
0
|
return 'Invalid date/time format'; |
392
|
|
|
|
|
|
|
} |
393
|
0
|
|
|
|
|
0
|
return undef; # OK! |
394
|
|
|
|
|
|
|
} |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
397
|
|
|
|
|
|
|
# Validate EXIF tag |
398
|
|
|
|
|
|
|
# Inputs: 0) ExifTool ref, 1) tag table ref, 2) tag ID, 3) tagInfo ref, |
399
|
|
|
|
|
|
|
# 4) previous tag ID, 5) IFD name, 6) number of values, 7) value format string |
400
|
|
|
|
|
|
|
# Returns: Nothing, but sets Warning tags if any problems are found |
401
|
|
|
|
|
|
|
sub ValidateExif($$$$$$$$) |
402
|
|
|
|
|
|
|
{ |
403
|
63
|
|
|
63
|
0
|
204
|
my ($et, $tagTablePtr, $tag, $tagInfo, $lastTag, $ifd, $count, $formatStr) = @_; |
404
|
|
|
|
|
|
|
|
405
|
63
|
50
|
|
|
|
152
|
$et->WarnOnce("Entries in $ifd are out of order") if $tag <= $lastTag; |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
# (get tagInfo for unknown tags if Unknown option not used) |
408
|
63
|
50
|
66
|
|
|
164
|
if (not defined $tagInfo and $$tagTablePtr{$tag} and ref $$tagTablePtr{$tag} eq 'HASH') { |
|
|
|
33
|
|
|
|
|
409
|
0
|
|
|
|
|
0
|
$tagInfo = $$tagTablePtr{$tag}; |
410
|
|
|
|
|
|
|
} |
411
|
63
|
100
|
33
|
|
|
172
|
if (defined $tagInfo) { |
|
|
50
|
|
|
|
|
|
412
|
61
|
|
66
|
|
|
136
|
my $ti = $tagInfo || $$tagTablePtr{$tag}; |
413
|
61
|
100
|
|
|
|
137
|
$ti = $$ti[-1] if ref $ti eq 'ARRAY'; |
414
|
61
|
|
66
|
|
|
191
|
my $stdFmt = $stdFormat{$ifd} || $stdFormat{IFD}; |
415
|
61
|
100
|
66
|
|
|
661
|
if (defined $$stdFmt{All} or ($tagTablePtr eq \%Image::ExifTool::Exif::Main and |
|
|
50
|
33
|
|
|
|
|
|
|
50
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
416
|
|
|
|
|
|
|
($exifSpec{$tag} or $$stdFmt{$tag} or |
417
|
|
|
|
|
|
|
($tag >= 0xc612 and $tag <= 0xc7b5 and not defined $$stdFmt{$tag}))) or # (DNG tags) |
418
|
|
|
|
|
|
|
$$tagTablePtr{SHORT_NAME} eq 'GPS::Main') |
419
|
|
|
|
|
|
|
{ |
420
|
59
|
|
66
|
|
|
217
|
my $wgp = $$ti{WriteGroup} || $$tagTablePtr{WRITE_GROUP}; |
421
|
59
|
0
|
66
|
|
|
333
|
if ($wgp and $wgp ne $ifd and $wgp ne 'All' and not $$ti{OffsetPair} and |
|
|
|
66
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
0
|
|
|
|
|
422
|
|
|
|
|
|
|
($ifd =~ /^(Sub|Profile)?IFD\d*$/ xor $wgp =~ /^(Sub)?IFD\d*$/) and |
423
|
|
|
|
|
|
|
($$ti{Writable} or $$ti{WriteGroup}) and $ifd !~ /^SRF\d+$/) |
424
|
|
|
|
|
|
|
{ |
425
|
0
|
|
|
|
|
0
|
$et->Warn(sprintf('Wrong IFD for 0x%.4x %s (should be %s not %s)', $tag, $$ti{Name}, $wgp, $ifd)); |
426
|
|
|
|
|
|
|
} |
427
|
59
|
|
100
|
|
|
233
|
my $fmt = $$stdFmt{$tag} || $$ti{Writable}; |
428
|
59
|
0
|
66
|
|
|
890
|
if ($fmt and $formatStr !~ /^$fmt$/ and (not $tagInfo or |
|
|
|
0
|
|
|
|
|
|
|
|
33
|
|
|
|
|
429
|
|
|
|
|
|
|
not $$tagInfo{IsOffset} or $Image::ExifTool::Exif::intFormat{$formatStr})) |
430
|
|
|
|
|
|
|
{ |
431
|
0
|
|
|
|
|
0
|
$et->Warn(sprintf('Non-standard format (%s) for %s 0x%.4x %s', $formatStr, $ifd, $tag, $$ti{Name})) |
432
|
|
|
|
|
|
|
} |
433
|
|
|
|
|
|
|
} elsif ($stdFormatAnyIFD{$tag}) { |
434
|
0
|
0
|
0
|
|
|
0
|
if ($$ti{Writable} || $$ti{WriteGroup}) { |
435
|
0
|
|
0
|
|
|
0
|
my $wgp = $$ti{WriteGroup} || $$tagTablePtr{WRITE_GROUP}; |
436
|
0
|
0
|
0
|
|
|
0
|
if ($wgp and $wgp ne $ifd) { |
437
|
0
|
|
|
|
|
0
|
$et->Warn(sprintf('Wrong IFD for 0x%.4x %s (should be %s not %s)', $tag, $$ti{Name}, $wgp, $ifd)); |
438
|
|
|
|
|
|
|
} |
439
|
|
|
|
|
|
|
} |
440
|
|
|
|
|
|
|
} elsif (not $otherSpec{$$et{VALUE}{FileType}} or |
441
|
|
|
|
|
|
|
(not $otherSpec{$$et{VALUE}{FileType}}{$tag} and not $otherSpec{$$et{VALUE}{FileType}}{All})) |
442
|
|
|
|
|
|
|
{ |
443
|
0
|
0
|
0
|
|
|
0
|
if ($tagTablePtr eq \%Image::ExifTool::Exif::Main or $$tagInfo{Unknown}) { |
444
|
0
|
|
|
|
|
0
|
$et->Warn(sprintf('Non-standard %s tag 0x%.4x %s', $ifd, $tag, $$ti{Name}), 1); |
445
|
|
|
|
|
|
|
} |
446
|
|
|
|
|
|
|
} |
447
|
|
|
|
|
|
|
# change expected count from read Format to Writable size |
448
|
61
|
|
|
|
|
189
|
my $tiCount = $$ti{Count}; |
449
|
61
|
100
|
|
|
|
196
|
if ($tiCount) { |
450
|
4
|
50
|
66
|
|
|
36
|
if ($$ti{Format} and $$ti{Writable} and |
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
451
|
|
|
|
|
|
|
$Image::ExifTool::Exif::formatNumber{$$ti{Format}} and |
452
|
|
|
|
|
|
|
$Image::ExifTool::Exif::formatNumber{$$ti{Writable}}) |
453
|
|
|
|
|
|
|
{ |
454
|
1
|
|
|
|
|
3
|
my $s1 = $Image::ExifTool::Exif::formatSize[$Image::ExifTool::Exif::formatNumber{$$ti{Format}}]; |
455
|
1
|
|
|
|
|
3
|
my $s2 = $Image::ExifTool::Exif::formatSize[$Image::ExifTool::Exif::formatNumber{$$ti{Writable}}]; |
456
|
1
|
|
|
|
|
4
|
$tiCount = int($tiCount * $s1 / $s2); |
457
|
|
|
|
|
|
|
} |
458
|
4
|
50
|
66
|
|
|
22
|
if ($tiCount > 0 and $count != $tiCount) { |
459
|
0
|
|
|
|
|
0
|
$et->Warn(sprintf('Non-standard count (%d) for %s 0x%.4x %s', $count, $ifd, $tag, $$ti{Name})); |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
} |
462
|
|
|
|
|
|
|
} elsif (not $otherSpec{$$et{VALUE}{FileType}} or |
463
|
|
|
|
|
|
|
(not $otherSpec{$$et{VALUE}{FileType}}{$tag} and not $otherSpec{$$et{VALUE}{FileType}}{All})) |
464
|
|
|
|
|
|
|
{ |
465
|
0
|
|
|
|
|
0
|
$et->Warn(sprintf('Unknown %s tag 0x%.4x', $ifd, $tag), 1); |
466
|
|
|
|
|
|
|
} |
467
|
|
|
|
|
|
|
} |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
470
|
|
|
|
|
|
|
# Validate image data offsets/sizes |
471
|
|
|
|
|
|
|
# Inputs: 0) ExifTool ref, 1) offset info hash ref (arrays of tagInfo/value pairs, keyed by tagID) |
472
|
|
|
|
|
|
|
# 2) directory name, 3) optional flag for minor warning |
473
|
|
|
|
|
|
|
sub ValidateOffsetInfo($$$;$) |
474
|
|
|
|
|
|
|
{ |
475
|
5
|
|
|
5
|
0
|
9
|
local $_; |
476
|
5
|
|
|
|
|
15
|
my ($et, $offsetInfo, $dirName, $minor) = @_; |
477
|
|
|
|
|
|
|
|
478
|
5
|
50
|
|
|
|
19
|
my $fileSize = $$et{VALUE}{FileSize} or return; |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
# (don't test RWZ files and some other file types) |
481
|
5
|
50
|
|
|
|
16
|
return if $$et{DontValidateImageData}; |
482
|
|
|
|
|
|
|
# (Minolta A200 uses wrong byte order for these) |
483
|
5
|
0
|
33
|
|
|
15
|
return if $$et{TIFF_TYPE} eq 'MRW' and $dirName eq 'IFD0' and $$et{Model} =~ /^DiMAGE A200/; |
|
|
|
33
|
|
|
|
|
484
|
|
|
|
|
|
|
# (don't test 3FR, RWL or RW2 files) |
485
|
5
|
50
|
|
|
|
25
|
return if $$et{TIFF_TYPE} =~ /^(3FR|RWL|RW2)$/; |
486
|
|
|
|
|
|
|
|
487
|
5
|
|
|
|
|
28
|
Image::ExifTool::Exif::ValidateImageData($et, $offsetInfo, $dirName); |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
# loop through all offsets |
490
|
5
|
|
|
|
|
17
|
while (%$offsetInfo) { |
491
|
16
|
|
|
|
|
56
|
my ($id1) = sort keys %$offsetInfo; |
492
|
16
|
|
|
|
|
36
|
my $offsets = $$offsetInfo{$id1}; |
493
|
16
|
|
|
|
|
28
|
delete $$offsetInfo{$id1}; |
494
|
16
|
100
|
|
|
|
46
|
next unless ref $offsets eq 'ARRAY'; |
495
|
6
|
|
|
|
|
15
|
my $id2 = $$offsets[0]{OffsetPair}; |
496
|
6
|
100
|
66
|
|
|
40
|
unless (defined $id2 and $$offsetInfo{$id2}) { |
497
|
2
|
50
|
33
|
|
|
17
|
unless ($$offsets[0]{NotRealPair} or (defined $id2 and $id2 == -1)) { |
|
|
|
33
|
|
|
|
|
498
|
2
|
100
|
|
|
|
8
|
my $corr = $$offsets[0]{IsOffset} ? 'size' : 'offset'; |
499
|
2
|
50
|
|
|
|
6
|
$et->Warn("$dirName:$$offsets[0]{Name} is missing the corresponding $corr tag") unless $minor; |
500
|
|
|
|
|
|
|
} |
501
|
2
|
|
|
|
|
11
|
next; |
502
|
|
|
|
|
|
|
} |
503
|
4
|
|
|
|
|
10
|
my $sizes = $$offsetInfo{$id2}; |
504
|
4
|
|
|
|
|
8
|
delete $$offsetInfo{$id2}; |
505
|
4
|
50
|
|
|
|
13
|
($sizes, $offsets) = ($offsets, $sizes) if $$sizes[0]{IsOffset}; |
506
|
4
|
|
|
|
|
14
|
my @offsets = split ' ', $$offsets[1]; |
507
|
4
|
|
|
|
|
12
|
my @sizes = split ' ', $$sizes[1]; |
508
|
4
|
50
|
|
|
|
11
|
if (@sizes != @offsets) { |
509
|
|
|
|
|
|
|
$et->Warn(sprintf('Wrong number of values in %s 0x%.4x %s', |
510
|
0
|
|
|
|
|
0
|
$dirName, $$offsets[0]{TagID}, $$offsets[0]{Name}), $minor); |
511
|
0
|
|
|
|
|
0
|
next; |
512
|
|
|
|
|
|
|
} |
513
|
4
|
|
|
|
|
10
|
while (@offsets) { |
514
|
4
|
|
|
|
|
8
|
my $start = pop @offsets; |
515
|
4
|
|
|
|
|
11
|
my $end = $start + pop @sizes; |
516
|
4
|
50
|
|
|
|
12
|
$et->WarnOnce("$dirName:$$offsets[0]{Name} is zero", $minor) if $start == 0; |
517
|
4
|
50
|
|
|
|
10
|
$et->WarnOnce("$dirName:$$sizes[0]{Name} is zero", $minor) if $start == $end; |
518
|
4
|
50
|
|
|
|
25
|
next unless $end > $fileSize; |
519
|
0
|
0
|
|
|
|
0
|
if ($start >= $fileSize) { |
520
|
0
|
0
|
|
|
|
0
|
if ($start == 0xffffffff) { |
521
|
0
|
|
|
|
|
0
|
$et->Warn("$dirName:$$offsets[0]{Name} is invalid (0xffffffff)", $minor); |
522
|
|
|
|
|
|
|
} else { |
523
|
0
|
|
|
|
|
0
|
$et->Warn("$dirName:$$offsets[0]{Name} is past end of file", $minor); |
524
|
|
|
|
|
|
|
} |
525
|
|
|
|
|
|
|
} else { |
526
|
0
|
|
|
|
|
0
|
$et->Warn("$dirName:$$offsets[0]{Name}+$$sizes[0]{Name} runs past end of file", $minor); |
527
|
|
|
|
|
|
|
} |
528
|
0
|
|
|
|
|
0
|
last; |
529
|
|
|
|
|
|
|
} |
530
|
|
|
|
|
|
|
} |
531
|
|
|
|
|
|
|
} |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
534
|
|
|
|
|
|
|
# Finish Validating tags |
535
|
|
|
|
|
|
|
# Inputs: 0) ExifTool ref, 1) True to generate Validate tag |
536
|
|
|
|
|
|
|
sub FinishValidate($$) |
537
|
|
|
|
|
|
|
{ |
538
|
1
|
|
|
1
|
0
|
3
|
local $_; |
539
|
1
|
|
|
|
|
4
|
my ($et, $mkTag) = @_; |
540
|
|
|
|
|
|
|
|
541
|
1
|
|
50
|
|
|
7
|
my $fileType = $$et{FILE_TYPE} || ''; |
542
|
1
|
50
|
|
|
|
7
|
$fileType = $$et{TIFF_TYPE} if $fileType eq 'TIFF'; |
543
|
|
|
|
|
|
|
|
544
|
1
|
50
|
|
|
|
5
|
if ($validValue{$fileType}) { |
545
|
0
|
|
|
|
|
0
|
my ($grp, $tag, %val); |
546
|
0
|
|
|
|
|
0
|
local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning; |
547
|
0
|
|
|
|
|
0
|
foreach $grp (sort keys %{$validValue{$fileType}}) { |
|
0
|
|
|
|
|
0
|
|
548
|
0
|
0
|
|
|
|
0
|
next unless $$et{FOUND_DIR}{$grp}; |
549
|
0
|
|
|
|
|
0
|
my ($key, %val, %info, $minor, $verTag, $ver, $vstr); |
550
|
0
|
|
|
|
|
0
|
my $verCheck = $verCheck{$grp}; |
551
|
0
|
0
|
|
|
|
0
|
if ($verCheck) { |
552
|
0
|
|
|
|
|
0
|
($verTag) = keys %$verCheck; |
553
|
0
|
|
|
|
|
0
|
($ver = $$et{VALUE}{$verTag}) =~ tr/0-9//dc; # (remove non-digits) |
554
|
0
|
0
|
|
|
|
0
|
undef $ver unless $ver =~ /^\d{4}$/; # (already warned if invalid version) |
555
|
|
|
|
|
|
|
} |
556
|
|
|
|
|
|
|
# get all tags in this group |
557
|
0
|
|
|
|
|
0
|
foreach $key (sort keys %{$$et{VALUE}}) { |
|
0
|
|
|
|
|
0
|
|
558
|
0
|
0
|
|
|
|
0
|
next unless $et->GetGroup($key, 1) eq $grp; |
559
|
0
|
0
|
0
|
|
|
0
|
next if $$et{TAG_EXTRA}{$key} and $$et{TAG_EXTRA}{$key}{G3}; # ignore sub-documents |
560
|
|
|
|
|
|
|
# fill in %val lookup with values based on tag ID |
561
|
0
|
|
|
|
|
0
|
my $tag = $$et{TAG_INFO}{$key}{TagID}; |
562
|
0
|
|
|
|
|
0
|
$val{$tag} = $$et{VALUE}{$key}; |
563
|
|
|
|
|
|
|
# save TagInfo ref for later |
564
|
0
|
|
|
|
|
0
|
$info{$tag} = $$et{TAG_INFO}{$key}; |
565
|
0
|
0
|
|
|
|
0
|
next unless defined $ver; |
566
|
0
|
|
|
|
|
0
|
my $chk = $$verCheck{$verTag}; |
567
|
0
|
0
|
0
|
|
|
0
|
next if not defined $$chk{$tag} or $$chk{$tag} == 1 or $ver >= $$chk{$tag}; |
|
|
|
0
|
|
|
|
|
568
|
0
|
0
|
|
|
|
0
|
if ($verTag eq 'GPSVersionID') { |
569
|
0
|
|
|
|
|
0
|
($vstr = $$chk{$tag}) =~ s/^(\d)(\d)(\d)/$1.$2.$3./; |
570
|
|
|
|
|
|
|
} else { |
571
|
0
|
|
|
|
|
0
|
$vstr = sprintf('%.4d', $$chk{$tag}); |
572
|
|
|
|
|
|
|
} |
573
|
|
|
|
|
|
|
$et->Warn(sprintf('%s tag 0x%.4x %s requires %s %s or higher', |
574
|
0
|
|
|
|
|
0
|
$grp, $tag, $$et{TAG_INFO}{$key}{Name}, $verTag, $vstr)); |
575
|
|
|
|
|
|
|
} |
576
|
|
|
|
|
|
|
# make quick lookup for values based on tag ID |
577
|
0
|
|
|
|
|
0
|
my $validValue = $validValue{$fileType}{$grp}; |
578
|
0
|
|
|
|
|
0
|
foreach $tag (sort { $a <=> $b } keys %$validValue) { |
|
0
|
|
|
|
|
0
|
|
579
|
0
|
|
|
|
|
0
|
my $val = $val{$tag}; |
580
|
0
|
|
|
|
|
0
|
my ($pre, $post); |
581
|
0
|
0
|
|
|
|
0
|
if (defined $$validValue{$tag}) { |
582
|
|
|
|
|
|
|
#### eval ($val, %val) |
583
|
0
|
|
|
|
|
0
|
my $result = eval $$validValue{$tag}; |
584
|
0
|
0
|
|
|
|
0
|
if (not defined $result) { |
|
|
0
|
|
|
|
|
|
585
|
0
|
|
|
|
|
0
|
$pre = 'Internal error validating'; |
586
|
|
|
|
|
|
|
} elsif ($result eq '') { |
587
|
0
|
0
|
|
|
|
0
|
$pre = defined $val ? 'Invalid value for' : "Missing required $fileType"; |
588
|
|
|
|
|
|
|
} else { |
589
|
0
|
0
|
|
|
|
0
|
next if $result eq '1'; |
590
|
0
|
|
|
|
|
0
|
$pre = $result; |
591
|
|
|
|
|
|
|
} |
592
|
|
|
|
|
|
|
} else { |
593
|
0
|
0
|
|
|
|
0
|
next unless defined $val; |
594
|
0
|
|
|
|
|
0
|
$post = "is not allowed in $fileType"; |
595
|
0
|
|
|
|
|
0
|
$minor = 1; |
596
|
|
|
|
|
|
|
} |
597
|
0
|
|
|
|
|
0
|
my $name; |
598
|
0
|
0
|
|
|
|
0
|
if ($info{$tag}) { |
599
|
0
|
|
|
|
|
0
|
$name = $info{$tag}{Name}; |
600
|
|
|
|
|
|
|
} else { |
601
|
0
|
0
|
|
|
|
0
|
my $table = 'Image::ExifTool::'.($grp eq 'GPS' ? 'GPS' : 'Exif').'::Main'; |
602
|
0
|
|
|
|
|
0
|
my $tagInfo = GetTagTable($table)->{$tag}; |
603
|
0
|
0
|
|
|
|
0
|
$tagInfo = $$tagInfo[0] if ref $tagInfo eq 'ARRAY'; |
604
|
0
|
0
|
|
|
|
0
|
$name = $tagInfo ? $$tagInfo{Name} : ''; |
605
|
|
|
|
|
|
|
} |
606
|
0
|
0
|
0
|
|
|
0
|
next if $$et{WrongFormat} and $$et{WrongFormat}{"$grp:$name"}; |
607
|
0
|
0
|
|
|
|
0
|
$pre ? ($pre .= ' ') : ($pre = ''); |
608
|
0
|
0
|
|
|
|
0
|
$post ? ($post = ' '.$post) : ($post = ''); |
609
|
0
|
|
|
|
|
0
|
$et->Warn(sprintf('%s%s tag 0x%.4x %s%s', $pre, $grp, $tag, $name, $post), $minor); |
610
|
|
|
|
|
|
|
} |
611
|
|
|
|
|
|
|
} |
612
|
|
|
|
|
|
|
} |
613
|
|
|
|
|
|
|
# validate file extension |
614
|
1
|
50
|
|
|
|
6
|
if ($$et{FILENAME} ne '') { |
615
|
1
|
50
|
|
|
|
13
|
my $fileExt = ($$et{FILENAME} =~ /^.*\.([^.]+)$/s) ? uc($1) : ''; |
616
|
1
|
|
|
|
|
6
|
my $extFileType = Image::ExifTool::GetFileType($fileExt); |
617
|
1
|
50
|
33
|
|
|
10
|
if ($extFileType and $extFileType ne $fileType) { |
618
|
1
|
|
|
|
|
5
|
my $normExt = $$et{VALUE}{FileTypeExtension}; |
619
|
1
|
50
|
33
|
|
|
9
|
if ($normExt and $normExt ne $fileExt) { |
620
|
0
|
|
|
|
|
0
|
my $lkup = $Image::ExifTool::fileTypeLookup{$fileExt}; |
621
|
0
|
0
|
0
|
|
|
0
|
if (ref $lkup or $lkup ne $normExt) { |
622
|
0
|
|
|
|
|
0
|
$et->Warn("File has wrong extension (should be $normExt, not $fileExt)"); |
623
|
|
|
|
|
|
|
} |
624
|
|
|
|
|
|
|
} |
625
|
|
|
|
|
|
|
} |
626
|
|
|
|
|
|
|
} |
627
|
|
|
|
|
|
|
# issue warning if FastScan option used |
628
|
1
|
50
|
|
|
|
5
|
$et->Warn('Validation incomplete because FastScan option used') if $et->Options('FastScan'); |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
# generate Validate tag if necessary |
631
|
1
|
50
|
|
|
|
4
|
if ($mkTag) { |
632
|
1
|
|
|
|
|
4
|
my (@num, $key); |
633
|
|
|
|
|
|
|
push @num, $$et{VALUE}{Error} ? ($$et{DUPL_TAG}{Error} || 0) + 1 : 0, |
634
|
1
|
50
|
0
|
|
|
9
|
$$et{VALUE}{Warning} ? ($$et{DUPL_TAG}{Warning} || 0) + 1 : 0, 0; |
|
|
50
|
0
|
|
|
|
|
635
|
1
|
|
|
|
|
3
|
for ($key = 'Warning'; ; ) { |
636
|
1
|
50
|
33
|
|
|
6
|
++$num[2] if $$et{VALUE}{$key} and $$et{VALUE}{$key} =~ /^\[minor\]/i; |
637
|
1
|
50
|
|
|
|
7
|
$key = $et->NextTagKey($key) or last; |
638
|
|
|
|
|
|
|
} |
639
|
1
|
|
|
|
|
9
|
$et->FoundTag(Validate => "@num"); |
640
|
|
|
|
|
|
|
} |
641
|
|
|
|
|
|
|
} |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
1; # end |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
__END__ |