/usr/share/perl5/Image/MetaData/JPEG/parsers/app2.pl is in libimage-metadata-jpeg-perl 0.153-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 | ###########################################################
# A Perl package for showing/modifying JPEG (meta)data. #
# Copyright (C) 2004,2005,2006 Stefano Bettelli #
# See the COPYING and LICENSE files for license terms. #
###########################################################
use Image::MetaData::JPEG::data::Tables qw(:TagsAPP2);
no integer;
use strict;
use warnings;
###########################################################
# This is the entry point for parsing APP2 segments. Such #
# application segments can host at least two formats (see #
# the called subroutines for more details): #
# 1) Flashpix conversion information ("FPXR"). #
# 2) ICC profiles data. #
# This method decides among the various formats and then #
# calls a specific parser. An error is issued if the #
# metadata format is not recognised. #
#=========================================================#
# Ref: "Exchangeable image file format for digital still #
# cameras: Exif Version 2.2", JEITA CP-3451, Apr2002 #
# Jap.Electr.Industry Develop.Assoc. (JEIDA), pag. 65 #
###########################################################
sub parse_app2 {
my ($this) = @_;
# If the data area begins with "FPXR\000", it contains Flashpix data
return $this->parse_app2_flashpix()
if $this->data(0, length $APP2_FPXR_TAG) eq $APP2_FPXR_TAG;
# If it starts with "ICC_PROFILE", well, guess it ....
return $this->parse_app2_ICC_profiles()
if $this->data(0, length $APP2_ICC_TAG) eq $APP2_ICC_TAG;
# if the segment type is unknown, generate an error
$this->die('Incorrect identifier (' . $this->data(0, 6) . ')');
}
###########################################################
# This method parses an APP2 Flashpix extension segment, #
# and is not really reliable, since I have only one exam- #
# ple and very badly written documentation. The FPXR #
# structure, the worst I have ever seen, is as follows: #
#---------------------------------------------------------#
# 5 bytes identifier ("FPXR\000" = 0x4650585200) #
# 1 byte version (always zero?, it is a binary value) #
# 1 byte type (1=Cont. List, 2=Stream Data, 3=reserved)#
#--- Contents List Segment -------------------------------#
# 2 bytes Interoperability count (the list size ...) #
# ---------- multiple times -------------------------- #
# 4 bytes Entity size (0xffffffff for a storage (?)) #
# ... Storage/Stream name (null termin., Unicode) #
# 16 bytes Entity class ID (for storages) (var. size ?) #
#--- Stream Data Segment ---------------------------------#
# 2 bytes index in the Contents List #
# 4 bytes offset to the first byte in the stream (?) #
# ... the actual data stream (to the end?) #
#=========================================================#
# Ref: "Exchangeable image file format for digital still #
# cameras: Exif Version 2.2", JEITA CP-3451, Apr2002 #
# Jap.Electr.Industry Develop.Assoc.(JEIDA), pag.65-67 #
###########################################################
sub parse_app2_flashpix {
my ($this) = @_;
my $offset = 0;
# at least 7 bytes for the identifier, its version and its type
$this->test_size(7, "FPXR header too small");
# decode the identifier (get its length from $APP2_FPXR_TAG)
my $identifier = $this->store_record
('Identifier', $ASCII, $offset, length $APP2_FPXR_TAG)->get_value();
# die if it is not correct
$this->die("Incorrect identifier ($identifier)")
if $identifier ne $APP2_FPXR_TAG;
# decode the version number (is this always zero?) and the data type
$this->store_record('Version', $BYTE, $offset);
my $type = $this->store_record('FPXR_type', $BYTE, $offset)->get_value();
# data type equal to 1 means we are dealing with a Contents List
# structure, listing the storages and streams for the Flashpix image.
if ($type == 1) {
# the first two bytes select the number of entries in the list
my $count = $this->read_record($SHORT, $offset);
for (1..$count) {
# create a separate subdir for each entry (stupid ?), then
# get the entity size and default value (the size refers to
# what we are going to find in future APP2 segments!).
my $subdir = $this->provide_subdirectory('Entity_' . $_);
my $size = $this->store_record($subdir, 'Size', $LONG, $offset);
$this->store_record($subdir, 'DefaultValue', $BYTE, $offset);
# the following entry is a Unicode string (16 bits --> 1 char)
# in little endian format. It terminates with a Unicode null
# char, i.e., "\000\000". Find its length, then store it. The
# string is invalid if it does not begin with Unicode "/".
my $pos=0; $pos+=2 while $this->data($offset+$pos,2) ne "\000\000";
$this->die('Invalid Storage/Stream name (not beginning with /)')
if $this->data($offset, 2) ne "/\000";
$this->store_record($subdir, 'Name', $ASCII, $offset, $pos+2);
# if $size is 0xffffffff, we are dealing with a Storage
# Interoperability Field; I don't know what this means, but
# at this point there should be an "Entity class ID" (16 bytes)
$this->store_record($subdir, 'Class_ID', $UNDEF, $offset, 16)
if $size == 0xffffffff;
} }
# data type equal to 2 means we are dealing with a Stream Data
# segment (there can be more than one such segments).
elsif ($type == 2) {
$this->store_record('ContentsIndex', $SHORT, $offset);
$this->store_record('StreamOffset', $LONG, $offset);
$this->store_record('Data', $UNDEF, $offset, $this->size() - $offset);
}
# type 3 is reserved for the future (let me know ...)
elsif ($type == 3) {
$this->store_record('Unknown', $UNDEF, $offset,$this->size()-$offset);}
# a type different from 1, 2 or 3 is not valid.
else { $this->die("Unknown FPXR type ($type)"); }
# check that there are no spurious data in the segment
$this->test_size(-$offset, "unknown data at segment end");
}
###########################################################
# This method parses an APP2 ICC_PROFILE segment. The #
# profile is defined as a header followed by a tag table #
# followed by a series of tagged elements. This routine #
# parses the overall structure and the profile header, #
# the other tags are read by parse_app2_ICC_tags(). The #
# ICC segment structure is as follows: #
#---------------------------------------------------------#
# 5 bytes identifier ("FPXR\000" = 0x4650585200) #
# 1 byte sequence number of the chunck (starting at 1) #
# 1 byte total number of chunks #
#------- Profile header ----------------------------------#
# 4 bytes profile size (this includes header and data) #
# 4 bytes CMM type signature #
# 4 bytes profile version number #
# 4 bytes profile/device class signature #
# 4 bytes color space signature #
# 4 bytes profile connection space (PCS) signature #
# 12 bytes date and time this profile was created #
# 4 bytes profile file signature #
# 4 bytes profile primary platform signature #
# 4 bytes flags for CMM profile options #
# 4 bytes device manifacturer signature #
# 4 bytes device model signature #
# 8 bytes device attributes #
# 4 bytes rendering intent #
# 12 bytes XYZ values of the illuminant of the PCS #
# 4 bytes profile creator signature #
# 16 bytes profile ID checksum #
# 28 bytes reserved for future expansion (must be zero) #
#------- Tag table ---------------------------------------#
# see parse_app2_ICC_tags() #
#=========================================================#
# Since ICC profile data can easily exceed 64KB, there is #
# a mechanism to divide the profile into smaller chunks. #
# This is the sequence number; every chunk must show the #
# same value for the total number of chunks. #
#=========================================================#
# Ref: "Specification ICC.1:2003-09, File Format for Co- #
# lor Profiles (ver. 4.1.0)", Intern.Color Consort. #
###########################################################
sub parse_app2_ICC_profiles {
my ($this) = @_;
my $offset = 0;
# get the length of the APP2 ICC identifier; then calculate
# the profile header offset (there are two more bytes)
my $id_size = length $APP2_ICC_TAG;
my $header_base = $id_size + 2;
# at least $header_base + 128 bytes (profile header) to start
$this->test_size($header_base + 128, "ICC profile header too small");
# decode the identifier (get its length from $APP2_FPXR_TAG)
my $identifier = $this->store_record
('Identifier', $ASCII, $offset, $id_size)->get_value();
# die if it is not correct
$this->die("Incorrect identifier ($identifier)")
if $identifier ne $APP2_ICC_TAG;
# read the sequence number and the total number of chunks
$this->store_record('SequenceNumber', $BYTE, $offset);
$this->store_record('TotalNumber', $BYTE, $offset);
# read the profile size and check with the real size
# remember to include the (identifier + chunks) bytes
my $size = $this->read_record($LONG, $offset);
$this->test_size(-($size + $header_base), "Incorrect ICC data size");
# prepare a subdirectory for the profile header
my $sd = $this->provide_subdirectory('ProfileHeader');
# read all other entries in the profile header
$this->store_record($sd, 'CMM_TypeSignature', $ASCII, $offset, 4 );
$this->store_record($sd, 'ProfileVersionNumber', $UNDEF, $offset, 4 );
$this->store_record($sd, 'ClassSignature', $ASCII, $offset, 4 );
$this->store_record($sd, 'ColorSpaceSignature', $ASCII, $offset, 4 );
$this->store_record($sd, 'ConnectionSpaceSignature', $ASCII, $offset, 4 );
$this->store_record($sd, 'Year', $SHORT, $offset );
$this->store_record($sd, 'Month', $SHORT, $offset );
$this->store_record($sd, 'Day', $SHORT, $offset );
$this->store_record($sd, 'Hour', $SHORT, $offset );
$this->store_record($sd, 'Minute', $SHORT, $offset );
$this->store_record($sd, 'Second', $SHORT, $offset );
$this->store_record($sd, 'ProfileFileSignature', $ASCII, $offset, 4 );
$this->store_record($sd, 'PrimaryPlatformSignature', $ASCII, $offset, 4 );
$this->store_record($sd, 'CMM_ProfileFlags', $LONG, $offset );
$this->store_record($sd, 'DeviceManifactSignature', $ASCII, $offset, 4 );
$this->store_record($sd, 'DeviceModelSignature', $ASCII, $offset, 4 );
$this->store_record($sd, 'DeviceAttributes', $UNDEF, $offset, 8 );
$this->store_record($sd, 'RenderingIntent', $LONG, $offset );
$this->store_record($sd, 'XYZ_PCS_Illuminant', $UNDEF, $offset, 12);
$this->store_record($sd, 'ProfileCreatorSignature', $ASCII, $offset, 4 );
$this->store_record($sd, 'ProfileID_Checksum', $UNDEF, $offset, 16);
# the last 28 bytes in the profile header are reserved for
# future use, and should contain only zero.
my $reserved = $this->read_record($UNDEF, $offset, 28);
$this->die('Non-zero reserved bytes in profile header')
if $reserved ne "\000" x 28;
# call another method knowing how to read the remaining tags
# (it only needs to know the current offset and where is the
# beginning of the profile header)
return $this->parse_app2_ICC_tags($offset, $header_base);
}
###########################################################
# This method parses the tag table of an APP2 ICC_PROFILE #
# segment (it complements parse_app2_ICC_profiles()). See #
# that routine for more details. The arguments are the #
# current offset in the segment data area and the start #
# of the profile header with respect to the beginning of #
# the segment data area. There are no checks on the over- #
# all size, since it is assumed that this was already #
# controlled by the calling routine. The tag table #
# structure is as follows: #
#---------------------------------------------------------#
# 4 bytes tag count #
# ---------- multiple times ------------------- #
# 4 bytes tag signature (a unique number) #
# 4 bytes tag offset from the profile header start #
# 4 bytes tag size #
#------ Data area of a tag -------------------------------#
# 4 bytes ICC tag type (an ASCII string) #
# 4 bytes reserved for the future ("\000\000\000\000") #
# .... real data area (various encodings). #
#---------------------------------------------------------#
# The first tag data area must immediately follow the tag #
# table. All tagged element data must be padded with #
# nulls by no more than three pad bytes to reach a four #
# bytes boundary. We only store the final part of the tag #
# data area in the record (the ICC type is saved in its #
# extra field). See the code for more details. #
#=========================================================#
# Ref: "Specification ICC.1:2003-09, File Format for Co- #
# lor Profiles (ver. 4.1.0)", Intern.Color Consort. #
###########################################################
sub parse_app2_ICC_tags {
my ($this, $offset, $header_base) = @_;
# read the number of tags in the tag table (don't store it)
my $tags = $this->read_record($LONG, $offset);
# prepare a subdirectory for the tag table
my $tag_table = $this->provide_subdirectory('TagTable');
# repeat the tag-reading algorithm $tags time
for (1..$tags) {
# the 12 bytes in the tag table entry contain the tag code
# (which we are going to use as record key), the pointer to
# the tag data with respect to the profile header beginning
# and the size of this data area. Read and don't store.
my $tag_code = $this->read_record($LONG, $offset);
my $tag_offset = $this->read_record($LONG, $offset);
my $tag_size = $this->read_record($LONG, $offset);
# the first 8 bytes in the tag data area are special; the first
# 4 bytes specify the "ICC type", the following 4 must be zero.
# Read, check the condition, but don't store.
my $tag_desc = $this->data($header_base + $tag_offset , 4);
my $tag_pad = $this->data($header_base + $tag_offset + 4, 4);
$this->die('Non-zero padding in ICC tag')
if $tag_pad ne "\000\000\000\000";
# adjust the tag size and offset to reflect the 8 bytes we read.
# also adjust the offset by adding the profile header base
$tag_size -= 8; $tag_offset += 8 + $header_base;
# a few ICC tag types can be shown with something more
# specific than the UNDEF type (which remains the default)
my $tag_type = $UNDEF;
$tag_type = $ASCII if $tag_desc =~ /text|sig /;
$tag_type = $BYTE if $tag_desc =~ /ui08/;
$tag_type = $SHORT if $tag_desc =~ /ui16|dtim/;
$tag_type = $LONG if $tag_desc =~ /ui32|XYZ |view/;
# depending on the tag type, calculate its length in bytes and
# therefore the number of elements in the data area (the count).
# If the type is variable-length (i.e., if get_size returns
# zero), $tag_count must be indeed equal to $tag_size.
my $tag_length = Image::MetaData::JPEG::Record->get_size($tag_type, 1);
my $tag_count = ($tag_length == 0)? $tag_size : $tag_size/$tag_length;
# now, store the content of the tag data area (minus the first
# 8 bytes) as a record of given key, type and count. Store the
# record in the tag table subdirectory.
$this->store_record($tag_table, $tag_code, $tag_type,
\ $this->data($tag_offset, $tag_size), $tag_count);
# also store the ICC tag type in the record "extra" field
$this->search_record('LAST_RECORD', $tag_table)->{extra} = $tag_desc;
}
}
# successful load
1;
|