/usr/share/perl5/Image/MetaData/JPEG/dumpers/app1_exif.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 | ###########################################################
# 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(:TagsAPP1_Exif);
no integer;
use strict;
use warnings;
###########################################################
# This method dumps an Exif APP1 segment. Basically, it #
# dumps the identifier, the two IFDs and the thumbnail. #
###########################################################
sub dump_app1_exif {
my ($this) = @_;
# dump the identifier (not part of the TIFF header)
my $identifier = $this->search_record('Identifier')->get();
$this->set_data($identifier);
# dump the TIFF header; note that the offset returned by
# dump_TIFF_header is the current position in the newly written
# data area AFTER the identifier (i.e., the base is the base
# of the TIFF header), so it does not start from zero but from the
# value of $ifd0_link. Be aware that its meaning is slightly
# different from $offset in the parser.
my ($header, $offset, $endianness) = $this->dump_TIFF_header();
$this->set_data($header);
# locally set the current endianness to what we have found.
local $this->{endianness} = $endianness;
# dump all the records of the 0th IFD, and update $offset to
# point after the end of the current data area (with respect
# to the TIFF header base). This must be done even if the IFD
# itself is empty (in order to find the next one).
my $ifd1_link = defined $this->search_record('IFD1') ? 0 : 1;
$offset += $this->set_data($this->dump_ifd('IFD0', $offset, $ifd1_link));
# same thing with the 1st IFD. We don't have to worry if this
# IFD is not there, because dump_ifd tests for this case.
$offset += $this->set_data($this->dump_ifd('IFD1', $offset, 1));
# if there is thumbnail data in the main directory of this
# segment, it is time to dump it. Use the reference, because
# this can be quite large (some tens of kilobytes ....)
if (my $th_record = $this->search_record('ThumbnailData')) {
(undef, undef, undef, my $tdataref) = $th_record->get();
$this->set_data($tdataref); }
}
###########################################################
# This method reconstructs a TIFF header and returns a #
# list with all the relevant values. Nothing is written #
# to the data area. Records are searched for in the #
# directory specified by the second argument. #
###########################################################
sub dump_TIFF_header {
my ($this, $dirref) = @_;
# retrieve the endianness, and signature. It is not worth
# setting the temporary segment endianness here, do it later.
my $endianness=$this->search_record('Endianness',$dirref)->get();
my $signature =$this->search_record('Signature',$dirref)->get($endianness);
# create a string containing the TIFF header (we always
# choose the offset of the 0th IFD must to be 8 here).
my $ifd0_len = 8;
my $ifd0_link = pack $endianness eq $BIG_ENDIAN ? "N" : "V", $ifd0_len;
my $header = $endianness . $signature . $ifd0_link;
# return all relevant values in a list
return ($header, $ifd0_len, $endianness);
}
###########################################################
# This is the core of the Exif APP1 dumping method. It #
# takes care to dump a whole IFD, including a special #
# treatement for thumbnails and makernotes. No action is #
# taken unless there is already a directory for this IFD #
# in the structured data area of the segment. #
# ------------------------------------------------------- #
# Special treatement for tags holding an IFD offset (this #
# includes makernotes); these tags are regenerated on the #
# fly (since they are no more stored) and their value is #
# recalculated and written to the raw data area. #
# ------------------------------------------------------- #
# New argument ($next), which specifies how the next_link #
# pointer is to be treated: '0' --> the pointer is dumped #
# with a non zero value; '1' --> the pointer is dumped #
# with value set to zero; '2' -->: the pointer is ignored #
###########################################################
sub dump_ifd {
my ($this, $dirnames, $offset, $next) = @_;
# set the next link flag to zero if it is undefined
$next = 0 unless defined $next;
# retrieve the appropriate record list (specified by a '@' separated
# list of dir names in $dirnames to be interpreted in sequence). If
# this fails, return immediately with a reference to an empty string
my $dirref = $this->search_record_value($dirnames);
return \ (my $ns = '') unless $dirref;
# $short and $long are two useful format strings correctly taking
# into account the IFD endianness. $format is a format string for
# packing an Interoperability array
my $short = $this->{endianness} eq $BIG_ENDIAN ? 'n' : 'v';
my $long = $this->{endianness} eq $BIG_ENDIAN ? 'N' : 'V';
my $format = $short. $short . $long;
# retrieve the record list for this IFD, then eliminate the REFERENCE
# records (added by the parser routine, they were not in the JPEG file).
my @records = grep { $_->{type} != $REFERENCE } @$dirref;
# for each reference record with a non-undef extra field, regenerate
# the corresponding offset record (which can be retraced from the
# "extra" field) and insert it into the @records list with a dummy
# value (0). We can safely use $LONG as record type (new-style offsets).
push @records, map {
my $nt = JPEG_lookup($this->{name}, $dirnames, $_->{extra});
new Image::MetaData::JPEG::Record($nt, $LONG, \ pack($long, 0)) }
grep { $_->{type} == $REFERENCE && $_->{extra} } @$dirref;
# sort the accumulated records with respect to their tags (numeric).
# This is not, strictly speaking mandatory, but the file looks more
# polished after this (am I introducing any gratuitous incompatibility?)
@records = sort { $a->{key} <=> $b->{key} } @records;
# the IFD data area is to be initialised with two bytes specifying
# the number of Interoperability arrays.
my $ifd_content = pack $short, scalar @records;
# Data areas too large for the Interop array will be saved in $extra;
# $remote should point to its beginning (from TIFF header base), so we
# must skip 12 bytes for each Interop. array, 2 bytes for the initial
# count (and 4 bytes for the next IFD link, unless $next is two).
my ($remote, $extra) = ($offset + 2 + 12*@records, '');
$remote += 4 unless $next == 2;
# managing the thumbnail is not trivial. We want to be sure that
# its declared size corresponds to the reality and correct if
# this is not the case (is this a stupid idea?)
if ($dirnames eq 'IFD1' &&
(my $th_record = $this->search_record('ThumbnailData'))) {
(undef, undef, undef, my $tdataref) = $th_record->get();
for ($THTIFF_LENGTH, $THJPEG_LENGTH) {
my $th_len = $this->search_record($_, $dirref);
$th_len->set_value(length $$tdataref) if $th_len; } }
# the following tags can be found only in IFD1 in APP1, and concern
# the thumbnail location. They must be dealt with in a special way.
my %th_tags = ($THTIFF_OFFSET => undef, $THJPEG_OFFSET => undef);
# determine weather this IFD can have subidrectories or not; if so,
# get a special mapping table from %IFD_SUBDIRS (avoid autovivification)
my $path = join '@', $this->{name}, $dirnames;
my $mapping = exists $IFD_SUBDIRS{$path} ? $IFD_SUBDIRS{$path} : undef;
# loop on all selected records and dump them
for my $record (@records) {
# extract all necessary information about this
# Interoperability array, with the correct endianness.
my ($tag, $type, $count, $dataref) = $record->get($this->{endianness});
# calculate the length of the array data, and correct $count
# for string-like records (it had been set to 1 during the
# parsing, it must be the data length in this case).
my $length = length $$dataref;
$count = $length if $record->get_category() eq 'S';
# the last four bytes in an interoperability array are either
# data or an address; prepare a variable for holding this value
my $record_end = '';
# if this IFD1 record specifies the thumbnail location, it needs
# a special treatment, since we cannot yet know where the thumbnail
# will be located. Write a bogus offset now and overwrite it later.
if ($dirnames eq 'IFD1' && exists $th_tags{$tag}) {
$th_tags{$tag} = 8 + length $ifd_content;
$record_end = "\000\000\000\000"; }
# if this Interop array is known to correspond to a subdirectory
# (use %$mapping for this), the subdirectory content is calculated
# on the fly, and stored in this IFD's remote data area. Its offset
# instead is saved at the end of the Interoperability array.
elsif ($mapping && exists $$mapping{$tag}) {
my $is_makernote = ($tag =~ $MAKERNOTE_TAG);
my $extended_dirnames = $dirnames.'@'.$$mapping{$tag};
# MakerNotes require a special treatment, including rewriting
# type and count (one LONG is really many UNDEF bytes); other
# subIFD's are written by a recursive dump_ifd (next link is 0).
my $subifd = $is_makernote ?
$this->dump_makernote($extended_dirnames, $remote) :
$this->dump_ifd($extended_dirnames, $remote, 1);
$type = $UNDEF, $count = length($$subifd) if $is_makernote;
$record_end = pack $long, $remote;
$extra .= $$subifd; $remote += length $$subifd; }
# if the data length is not larger than four bytes, we are ok.
# $$dataref is simply appended (with padding up to 4 bytes,
# AFTER $$dataref, independently of the IFD endianness).
elsif ($length <= 4) { $record_end = $$dataref . "\000"x(4-$length); }
# if $$dataref is too big, it must be packed in the $extra
# section, and its pointer appended here. Remember to update
# $remote for the next record of this type.
else { $record_end = pack $long, $remote;
$remote += $length; $extra .= $$dataref; }
# the interoperability array starts with tag, type and count,
# followed by $record_end (4 bytes): dump into the ifd data area
$ifd_content .= (pack $format, $tag, $type, $count) . $record_end; }
# after the Interop. arrays there can be a link to the next IFD
# (this takes 4 bytes). $next = 0 --> write the next IFD offset,
# = 1 --> write zero, 2 --> do not write these four bytes.
$ifd_content .= pack $long, $remote if $next == 0;
$ifd_content .= pack $long, 0 if $next == 1;
# then, we save the remote data area
$ifd_content .= $extra;
# if the thumbnail offset tags were found during the scan, we
# need to overwrite their values with a meaningful offset now.
for (keys %th_tags) {
next unless my $overwrite = $th_tags{$_};
my $tag_record = $this->search_record($_, $dirref);
$tag_record->set_value($remote);
my $new_offset = $tag_record->get($this->{endianness});
substr($ifd_content, $overwrite, length $new_offset) = $new_offset; }
# return a reference to the scalar which holds the binary dump
# of this IFD (to be saved in the caller routine, I think).
return \$ifd_content;
}
###########################################################
# This routine dumps all kinds of makernotes. Have a look #
# at parse_makernote() for further details. #
###########################################################
sub dump_makernote {
my ($this, $dirnames, $offset) = @_;
# look for a MakerNote subdirectory beginning with $dirnames: the
# actual name has the format appended, e.g., MakerNoteData_Canon.
$dirnames =~ s/(.*@|)([^@]*)/$1/;
my $dirref = $this->search_record_value($dirnames);
$dirnames .= $_->{key}, $dirref = $_->get_value(), last
for (grep{$_->{key}=~/^$2/} @$dirref);
# Also look for the subdir with special information.
my $spcref = $this->search_record_value($dirnames.'@special');
# entering here without the dir and its subdir being present is an error
$this->die('MakerNote subdirs not found') unless $dirref && $spcref;
# read all MakerNote special values (added by the parser routine)
my ($data, $signature, $endianness, $format, $error) =
map { $this->search_record_value($_, $spcref) }
('ORIGINAL', 'SIGNATURE', 'ENDIANNESS', 'FORMAT', 'ERROR');
# die and debug if the format record is not present
$this->die('FORMAT not found') unless $format;
# if the format is unknown or there was an error at parse time, it
# is wiser to return the original, unparsed content of the MakerNote
if ($format =~ /unknown/ || defined $error) {
$this->die('ORIGINAL data not found') unless $data; return \$data; };
# also extract the property table for this MakerNote format
my $hash = $$HASH_MAKERNOTES{$format};
# now, die if the signature or endianness is still undefined
$this->die('Properties not found')unless defined $signature && $endianness;
# in general, the MakerNote's next-IFD link is zero, but some
# MakerNotes do not even have these four bytes: prepare the flag
my $next_flag = exists $$hash{nonext} ? 2 : 1;
# in general, MakerNote's offsets are computed from the APP1 segment
# TIFF base; however, some formats compute offsets from the beginning
# of the MakerNote itself: setup the offset base as required.
$offset = length($signature) + (exists $$hash{mkntstart} ? 0 : $offset);
# initialise the data area with the detected signature
$data = $signature;
# some MakerNotes have a TIFF header on their own, freeing them
# from the relocation problem; values from this header overwrite
# the previously assigned values; records are saved in $mknt_dir.
if (exists $$hash{mkntTIFF}) {
my ($TIFF_header, $TIFF_offset, $TIFF_endianness)
= $this->dump_TIFF_header($spcref);
$this->die('Endianness mismatch') if $endianness ne $TIFF_endianness;
$data .= $TIFF_header; $offset = $TIFF_offset; }
# Unstructured case: the content of the MakerNote is simply
# a sequence of bytes, which must be encoded using $$hash{tags}
if (exists $$hash{nonIFD}) {
$data .= $this->search_record($$_[0], $dirref)->get($endianness)
for map {$$hash{tags}{$_}} sort {$a <=> $b} keys %{$$hash{tags}}; }
# Structured case: the content of the MakerNote can be dumped
# with dump_ifd (change locally the endianness value).
else { local $this->{endianness} = $endianness;
$data .= ${$this->dump_ifd($dirnames, $offset, $next_flag)} };
# return the MakerNote as a binary object
return \$data;
}
# successful load
1;
|