require
5.004;
use
vars
qw($VERSION $RELEASE @ISA %EXPORT_TAGS $AUTOLOAD @fileTypes %allTables
@tableOrder $exifAPP1hdr $xmpAPP1hdr $psAPP13hdr $psAPP13old
$myAPP5hdr @loadAllTables %UserDefined)
;
$VERSION
=
'6.76'
;
$RELEASE
=
''
;
@ISA
=
qw(Exporter)
;
%EXPORT_TAGS
= (
Public
=> [
qw(
ImageInfo GetTagName GetShortcuts GetAllTags GetWritableTags
GetAllGroups GetDeleteGroups GetFileType CanWrite CanCreate
)
],
DataAccess
=> [
qw(
ReadValue GetByteOrder SetByteOrder ToggleByteOrder Get8u Get8s Get16u
Get16s Get32u Get32s GetFloat GetDouble GetFixed32s Write WriteValue
Tell Set8u Set8s Set16u Set32u
)
],
Utils
=> [
qw(
GetTagTable TagTableKeys GetTagInfoList GenerateTagIDs SetFileType
HtmlDump
)
],
Vars
=> [
qw(
%allTables @tableOrder @fileTypes
)
],
);
Exporter::export_ok_tags(
keys
%EXPORT_TAGS
);
sub
SetNewValue($;$$%);
sub
SetNewValuesFromFile($$;@);
sub
GetNewValues($;$$);
sub
CountNewValues($);
sub
SaveNewValues($);
sub
RestoreNewValues($);
sub
WriteInfo($$;$$);
sub
SetFileModifyDate($$;$);
sub
SetFileName($$;$);
sub
GetAllTags(;$);
sub
GetWritableTags(;$);
sub
GetAllGroups($);
sub
GetNewGroups($);
sub
GetDeleteGroups();
sub
InsertTagValues($$$;$);
sub
IsWritable($);
sub
GetNewFileName($$);
sub
LoadAllTables();
sub
GetNewTagInfoList($;$);
sub
GetNewTagInfoHash($@);
sub
Get64s($$);
sub
Get64u($$);
sub
GetExtended($$);
sub
DecodeBits($$);
sub
EncodeBits($$);
sub
HexDump($;$%);
sub
DumpTrailer($$);
sub
DumpUnknownTrailer($$);
sub
VerboseInfo($$$%);
sub
VerboseDir($$;$$);
sub
VPrint($$@);
sub
Rationalize($;$);
sub
Write($@);
sub
ProcessTrailers($$);
sub
WriteTrailerBuffer($$$);
sub
Tell($);
sub
WriteValue($$;$$$$);
sub
WriteDirectory($$$;$);
sub
WriteBinaryData($$$);
sub
CheckBinaryData($$$);
sub
WriteTIFF($$$);
@loadAllTables
=
qw(
PhotoMechanic Exif GeoTiff CanonRaw KyoceraRaw MinoltaRaw SigmaRaw JPEG
Jpeg2000 BMP BMP PICT PNG MNG MIFF PDF PostScript Photoshop::Header
Panasonic::Raw Sony::SR2SubIFD ID3 Vorbis FLAC APE APE::NewHeader
APE::OldHeader MPC MPEG::Audio MPEG::Video QuickTime QuickTime::ImageFile
Flash Real::Media Real::Audio Real::Metafile RIFF AIFF ASF DICOM MIE HTML
)
;
@fileTypes
=
qw(JPEG CRW TIFF GIF MRW RAF X3F JP2 PNG MIE MIFF PS PDF PSD XMP
BMP PPM RIFF AIFF ASF MOV MPEG Real SWF OGG FLAC APE MPC ICC
HTML VRD QTIF FPX PICT MP3 DICM RAW)
;
my
@writeTypes
=
qw(JPEG TIFF GIF CRW MRW ORF PNG MIE PSD XMP PPM EPS PS ICC
VRD)
;
my
@createTypes
=
qw(XMP ICC MIE)
;
my
%fileTypeLookup
= (
ACR
=> [
'DICM'
,
'American College of Radiology ACR-NEMA'
],
AI
=> [[
'PDF'
,
'PS'
],
'Adobe Illustrator (PDF-like or PS-like)'
],
AIF
=> [
'AIFF'
,
'Audio Interchange File Format'
],
AIFC
=> [
'AIFF'
,
'Audio Interchange File Format Compressed'
],
AIFF
=> [
'AIFF'
,
'Audio Interchange File Format'
],
APE
=> [
'APE'
,
"Monkey's Audio format"
],
ARW
=> [
'TIFF'
,
'Sony Alpha RAW format (TIFF-like)'
],
ASF
=> [
'ASF'
,
'Microsoft Advanced Systems Format'
],
AVI
=> [
'RIFF'
,
'Audio Video Interleaved (RIFF-based)'
],
BMP
=> [
'BMP'
,
'Windows BitMaP'
],
CIFF
=> [
'CRW'
,
'Camera Image File Format (same as CRW)'
],
CR2
=> [
'TIFF'
,
'Canon RAW 2 format (TIFF-like)'
],
CRW
=> [
'CRW'
,
'Canon RAW format'
],
DC3
=> [
'DICM'
,
'DICOM image file'
],
DCM
=> [
'DICM'
,
'DICOM image file'
],
DIB
=> [
'BMP'
,
'Device Independent Bitmap (aka. BMP)'
],
DIC
=> [
'DICM'
,
'DICOM image file'
],
DICM
=> [
'DICM'
,
'DICOM image file'
],
DNG
=> [
'TIFF'
,
'Digital Negative (TIFF-like)'
],
DOC
=> [
'FPX'
,
'Microsoft Word Document (FPX-like)'
],
EPS
=> [
'EPS'
,
'Encapsulated PostScript Format'
],
EPSF
=> [
'EPS'
,
'Encapsulated PostScript Format'
],
ERF
=> [
'TIFF'
,
'Epson Raw Format (TIFF-like)'
],
FLAC
=> [
'FLAC'
,
'Free Lossless Audio Codec'
],
FPX
=> [
'FPX'
,
'FlashPix'
],
GIF
=> [
'GIF'
,
'Compuserve Graphics Interchange Format'
],
HTM
=> [
'HTML'
,
'HyperText Markup Language'
],
HTML
=> [
'HTML'
,
'HyperText Markup Language'
],
ICC
=> [
'ICC'
,
'International Color Consortium'
],
ICM
=> [
'ICC'
,
'International Color Consortium'
],
JNG
=> [
'PNG'
,
'JPG Network Graphics (PNG-like)'
],
JP2
=> [
'JP2'
,
'JPEG 2000 file'
],
JPEG
=> [
'JPEG'
,
'Joint Photographic Experts Group'
],
JPG
=> [
'JPEG'
,
'Joint Photographic Experts Group'
],
JPX
=> [
'JP2'
,
'JPEG 2000 file'
],
M4A
=> [
'MOV'
,
'MPG4 Audio (QuickTime-based)'
],
MIE
=> [
'MIE'
,
'Meta Information Encapsulation format'
],
MIF
=> [
'MIFF'
,
'Magick Image File Format'
],
MIFF
=> [
'MIFF'
,
'Magick Image File Format'
],
MNG
=> [
'PNG'
,
'Multiple-image Network Graphics (PNG-like)'
],
MOS
=> [
'TIFF'
,
'Creo Leaf Mosaic (TIFF-like)'
],
MOV
=> [
'MOV'
,
'Apple QuickTime movie'
],
MP3
=> [
'MP3'
,
'MPEG Layer 3 audio (uses ID3 information)'
],
MP4
=> [
'MOV'
,
'MPEG Layer 4 video (QuickTime-based)'
],
MPC
=> [
'MPC'
,
'Musepack Audio'
],
MPEG
=> [
'MPEG'
,
'MPEG audio/video format 1'
],
MPG
=> [
'MPEG'
,
'MPEG audio/video format 1'
],
MRW
=> [
'MRW'
,
'Minolta RAW format'
],
NEF
=> [
'TIFF'
,
'Nikon (RAW) Electronic Format (TIFF-like)'
],
OGG
=> [
'OGG'
,
'Ogg Vorbis audio file'
],
ORF
=> [
'ORF'
,
'Olympus RAW format'
],
PBM
=> [
'PPM'
,
'Portable BitMap (PPM-like)'
],
PCT
=> [
'PICT'
,
'Apple PICTure'
],
PDF
=> [
'PDF'
,
'Adobe Portable Document Format'
],
PEF
=> [
'TIFF'
,
'Pentax (RAW) Electronic Format (TIFF-like)'
],
PGM
=> [
'PPM'
,
'Portable Gray Map (PPM-like)'
],
PICT
=> [
'PICT'
,
'Apple PICTure'
],
PNG
=> [
'PNG'
,
'Portable Network Graphics'
],
PPM
=> [
'PPM'
,
'Portable Pixel Map'
],
PPT
=> [
'FPX'
,
'Microsoft PowerPoint presentation (FPX-like)'
],
PS
=> [
'PS'
,
'PostScript'
],
PSD
=> [
'PSD'
,
'PhotoShop Drawing'
],
QIF
=> [
'QTIF'
,
'QuickTime Image File'
],
QT
=> [
'MOV'
,
'QuickTime movie'
],
QTI
=> [
'QTIF'
,
'QuickTime Image File'
],
QTIF
=> [
'QTIF'
,
'QuickTime Image File'
],
RA
=> [
'Real'
,
'Real Audio'
],
RAF
=> [
'RAF'
,
'FujiFilm RAW Format'
],
RAM
=> [
'Real'
,
'Real Audio Metafile'
],
RAW
=> [
'RAW'
,
'Kyocera Contax N Digital RAW or Panasonic RAW'
],
RIF
=> [
'RIFF'
,
'Resource Interchange File Format'
],
RIFF
=> [
'RIFF'
,
'Resource Interchange File Format'
],
RM
=> [
'Real'
,
'Real Media'
],
RMVB
=> [
'Real'
,
'Real Media Variable Bitrate'
],
RPM
=> [
'Real'
,
'Real Media Plug-in Metafile'
],
RV
=> [
'Real'
,
'Real Video'
],
SR2
=> [
'TIFF'
,
'Sony RAW Format 2 (TIFF-like)'
],
SRF
=> [
'TIFF'
,
'Sony RAW Format (TIFF-like)'
],
SWF
=> [
'SWF'
,
'Shockwave Flash'
],
THM
=> [
'JPEG'
,
'Canon Thumbnail (aka. JPG)'
],
TIF
=> [
'TIFF'
,
'Tagged Image File Format'
],
TIFF
=> [
'TIFF'
,
'Tagged Image File Format'
],
VRD
=> [
'VRD'
,
'Canon VRD Recipe Data (written by DPP)'
],
WAV
=> [
'RIFF'
,
'WAVeform (Windows digital audio format)'
],
WDP
=> [
'TIFF'
,
'Windows Media Photo (TIFF-based)'
],
WMA
=> [
'ASF'
,
'Windows Media Audio (ASF-based)'
],
WMV
=> [
'ASF'
,
'Windows Media Video (ASF-based)'
],
X3F
=> [
'X3F'
,
'Sigma RAW format'
],
XHTML
=> [
'HTML'
,
'Extensible HyperText Markup Language'
],
XLS
=> [
'FPX'
,
'Microsoft Excel worksheet (FPX-like)'
],
XMP
=> [
'XMP'
,
'Extensible Metadata Platform data file'
],
);
my
%mimeType
= (
AIFF
=>
'audio/aiff'
,
APE
=>
'audio/x-monkeys-audio'
,
ASF
=>
'video/x-ms-asf'
,
ARW
=>
'image/x-raw'
,
AVI
=>
'video/avi'
,
BMP
=>
'image/bmp'
,
CR2
=>
'image/x-raw'
,
CRW
=>
'image/x-raw'
,
EPS
=>
'application/postscript'
,
ERF
=>
'image/x-raw'
,
DICM
=>
'application/dicom'
,
DNG
=>
'image/x-raw'
,
DOC
=>
'application/msword'
,
FLAC
=>
'audio/flac'
,
FPX
=>
'image/vnd.fpx'
,
GIF
=>
'image/gif'
,
HTML
=>
'text/html'
,
JNG
=>
'image/jng'
,
JP2
=>
'image/jpeg2000'
,
JPEG
=>
'image/jpeg'
,
M4A
=>
'audio/mp4'
,
MIE
=>
'application/x-mie'
,
MIFF
=>
'application/x-magick-image'
,
MNG
=>
'video/mng'
,
MOS
=>
'image/x-raw'
,
MOV
=>
'video/quicktime'
,
MP3
=>
'audio/mpeg'
,
MP4
=>
'video/mp4'
,
MPC
=>
'audio/x-musepack'
,
MPEG
=>
'video/mpeg'
,
MRW
=>
'image/x-raw'
,
NEF
=>
'image/x-raw'
,
OGG
=>
'audio/x-ogg'
,
ORF
=>
'image/x-raw'
,
PBM
=>
'image/x-portable-bitmap'
,
PDF
=>
'application/pdf'
,
PEF
=>
'image/x-raw'
,
PGM
=>
'image/x-portable-graymap'
,
PICT
=>
'image/pict'
,
PNG
=>
'image/png'
,
PPM
=>
'image/x-portable-pixmap'
,
PPT
=>
'application/vnd.ms-powerpoint'
,
PS
=>
'application/postscript'
,
PSD
=>
'application/photoshop'
,
QTIF
=>
'image/x-quicktime'
,
RA
=>
'audio/x-pn-realaudio'
,
RAF
=>
'image/x-raw'
,
RAM
=>
'audio/x-pn-realaudio'
,
RAW
=>
'image/x-raw'
,
RM
=>
'application/vnd.rn-realmedia'
,
RMVB
=>
'application/vnd.rn-realmedia-vbr'
,
RPM
=>
'audio/x-pn-realaudio-plugin'
,
RV
=>
'video/vnd.rn-realvideo'
,
SR2
=>
'image/x-raw'
,
SRF
=>
'image/x-raw'
,
SWF
=>
'application/x-shockwave-flash'
,
TIFF
=>
'image/tiff'
,
WAV
=>
'audio/x-wav'
,
WDP
=>
'image/vnd.ms-photo'
,
WMA
=>
'audio/x-ms-wma'
,
WMV
=>
'video/x-ms-wmv'
,
X3F
=>
'image/x-raw'
,
XLS
=>
'application/vnd.ms-excel'
,
XMP
=>
'application/rdf+xml'
,
);
my
%moduleName
= (
CRW
=>
'CanonRaw'
,
DICM
=>
'DICOM'
,
EPS
=>
'PostScript'
,
ICC
=>
'ICC_Profile'
,
FPX
=>
'FlashPix'
,
JP2
=>
'Jpeg2000'
,
JPEG
=>
''
,
MOV
=>
'QuickTime'
,
MP3
=>
'ID3'
,
MRW
=>
'MinoltaRaw'
,
OGG
=>
'Vorbis'
,
ORF
=>
'Olympus'
,
PS
=>
'PostScript'
,
PSD
=>
'Photoshop'
,
QTIF
=>
'QuickTime'
,
RAF
=>
'FujiFilm'
,
RAW
=>
'KyoceraRaw'
,
SWF
=>
'Flash'
,
TIFF
=>
''
,
VRD
=>
'CanonVRD'
,
X3F
=>
'SigmaRaw'
,
);
my
@defaultWriteGroups
=
qw(EXIF IPTC XMP MakerNotes Photoshop ICC_Profile)
;
my
%allGroupsExifTool
= (
0
=>
'ExifTool'
,
1
=>
'ExifTool'
,
2
=>
'ExifTool'
);
$exifAPP1hdr
=
"Exif\0\0"
;
$psAPP13hdr
=
"Photoshop 3.0\0"
;
$psAPP13old
=
'Adobe_Photoshop2.5:'
;
sub
DummyWriteProc {
return
1; }
%Image::ExifTool::previewImageTagInfo
= (
Name
=>
'PreviewImage'
,
Writable
=>
'undef'
,
WriteCheck
=>
'$val eq "none" ? undef : $self->CheckImage(\$val)'
,
DataTag
=>
'PreviewImage'
,
ValueConv
=>
'$self->ValidateImage(\$val,$tag)'
,
ValueConvInv
=>
'$val eq "" and $val="none"; $val'
,
);
%Image::ExifTool::Extra
= (
GROUPS
=> {
0
=>
'File'
,
1
=>
'File'
,
2
=>
'Image'
},
DID_TAG_ID
=> 1,
WRITE_PROC
=> \
&DummyWriteProc
,
Comment
=> {
Name
=>
'Comment'
,
Notes
=>
'comment embedded in JPEG, GIF89a or PPM/PGM/PBM image'
,
Writable
=> 1,
WriteGroup
=>
'Comment'
,
Priority
=> 0,
},
Directory
=> {
Name
=>
'Directory'
,
Writable
=> 1,
Protected
=> 1,
ValueConvInv
=>
'$_=$val; tr/\\\\/\//; m{[^/]$} and $_ .= "/"; $_'
,
},
FileName
=> {
Name
=>
'FileName'
,
Writable
=> 1,
Protected
=> 1,
ValueConvInv
=>
'$val=~tr/\\\\/\//; $val'
,
},
FileSize
=> {
Name
=>
'FileSize'
,
PrintConv
=>
sub
{
my
$val
=
shift
;
$val
< 2048 and
return
"$val bytes"
;
$val
< 2097152 and
return
sprintf
(
'%.0f kB'
,
$val
/ 1024);
return
sprintf
(
'%.0f MB'
,
$val
/ 1048576);
},
},
FileType
=> {
Name
=>
'FileType'
},
FileModifyDate
=> {
Name
=>
'FileModifyDate'
,
Description
=>
'File Modification Date/Time'
,
Notes
=>
'the filesystem modification time'
,
Groups
=> {
2
=>
'Time'
},
Writable
=> 1,
Shift
=>
'Time'
,
ValueConv
=>
'ConvertUnixTime($val,"local")'
,
ValueConvInv
=>
'GetUnixTime($val,"local")'
,
PrintConv
=>
'$self->ConvertDateTime($val)'
,
PrintConvInv
=>
'$val'
,
},
MIMEType
=> {
Name
=>
'MIMEType'
},
ImageWidth
=> {
Name
=>
'ImageWidth'
},
ImageHeight
=> {
Name
=>
'ImageHeight'
},
XResolution
=> {
Name
=>
'XResolution'
},
YResolution
=> {
Name
=>
'YResolution'
},
MaxVal
=> {
Name
=>
'MaxVal'
},
EXIF
=> {
Name
=>
'EXIF'
,
Notes
=>
'the full EXIF data block'
,
Groups
=> {
0
=>
'EXIF'
},
Binary
=> 1,
},
ICC_Profile
=> {
Name
=>
'ICC_Profile'
,
Notes
=>
'the full ICC_Profile data block'
,
Groups
=> {
0
=>
'ICC_Profile'
},
Flags
=> [
'Writable'
,
'Protected'
,
'Binary'
],
WriteCheck
=>
q{
require Image::ExifTool::ICC_Profile;
return Image::ExifTool::ICC_Profile::ValidateICC(\$val);
}
,
},
XMP
=> {
Name
=>
'XMP'
,
Notes
=>
'the full XMP data block'
,
Groups
=> {
0
=>
'XMP'
},
Flags
=> [
'Writable'
,
'Binary'
],
WriteCheck
=>
q{
require Image::ExifTool::XMP;
return Image::ExifTool::XMP::CheckXMP($self, $tagInfo, \$val);
}
,
},
CanonVRD
=> {
Name
=>
'CanonVRD'
,
Notes
=>
'the full Canon DPP VRD trailer block'
,
Groups
=> {
0
=>
'CanonVRD'
},
Binary
=> 1,
},
ExifToolVersion
=> {
Name
=>
'ExifToolVersion'
,
Description
=>
'ExifTool Version Number'
,
Groups
=> \
%allGroupsExifTool
},
Encryption
=> {
Name
=>
'Encryption'
},
Error
=> {
Name
=>
'Error'
,
Priority
=> 0,
Groups
=> \
%allGroupsExifTool
},
Warning
=> {
Name
=>
'Warning'
,
Priority
=> 0,
Groups
=> \
%allGroupsExifTool
},
);
%allTables
= ( );
@tableOrder
= ( );
my
$didTagID
;
my
$evalWarning
;
%Image::ExifTool::Composite
= (
GROUPS
=> {
0
=>
'Composite'
,
1
=>
'Composite'
},
DID_TAG_ID
=> 1,
WRITE_PROC
=> \
&DummyWriteProc
,
);
%Image::ExifTool::JFIF::Main
= (
PROCESS_PROC
=> \
&Image::ExifTool::ProcessBinaryData
,
WRITE_PROC
=> \
&Image::ExifTool::WriteBinaryData
,
CHECK_PROC
=> \
&Image::ExifTool::CheckBinaryData
,
GROUPS
=> {
0
=>
'JFIF'
,
1
=>
'JFIF'
,
2
=>
'Image'
},
0
=> {
Name
=>
'JFIFVersion'
,
Format
=>
'int8u[2]'
,
PrintConv
=>
'$val=~tr/ /./;$val'
,
},
2
=> {
Name
=>
'ResolutionUnit'
,
Writable
=> 1,
PrintConv
=> {
0
=>
'None'
,
1
=>
'inches'
,
2
=>
'cm'
,
},
Priority
=> -1,
},
3
=> {
Name
=>
'XResolution'
,
Format
=>
'int16u'
,
Writable
=> 1,
Priority
=> -1,
},
5
=> {
Name
=>
'YResolution'
,
Format
=>
'int16u'
,
Writable
=> 1,
Priority
=> -1,
},
);
%Image::ExifTool::JFIF::Extension
= (
GROUPS
=> {
0
=>
'JFIF'
,
1
=>
'JFIF'
,
2
=>
'Image'
},
0x10
=> {
Name
=>
'ThumbnailImage'
,
ValueConv
=>
'$self->ValidateImage(\$val,$tag)'
,
},
);
my
%specialTags
= (
PROCESS_PROC
=>1,
WRITE_PROC
=>1,
CHECK_PROC
=>1,
GROUPS
=>1,
DATAMEMBER
=>1,
FORMAT
=>1,
FIRST_ENTRY
=>1,
TAG_PREFIX
=>1,
PRINT_CONV
=>1,
DID_TAG_ID
=>1,
WRITABLE
=>1,
NOTES
=>1,
IS_OFFSET
=>1,
EXTRACT_UNKNOWN
=>1,
NAMESPACE
=>1,
PREFERRED
=>1,
PARENT
=>1,
PRIORITY
=>1,
WRITE_GROUP
=>1,
LANG_INFO
=>1,
VARS
=>1,
);
sub
new
{
local
$_
;
my
$that
=
shift
;
my
$class
=
ref
(
$that
) ||
$that
||
'Image::ExifTool'
;
my
$self
=
bless
{},
$class
;
GetTagTable(
"Image::ExifTool::Exif::Main"
);
$self
->ClearOptions();
$self
->{VALUE} = { };
$self
->{DEL_GROUP} = { };
$self
->SetNewGroups(
@defaultWriteGroups
);
return
$self
;
}
sub
ImageInfo($;@)
{
local
$_
;
my
$self
;
if
(
ref
$_
[0] and UNIVERSAL::isa(
$_
[0],
'Image::ExifTool'
)) {
$self
=
shift
;
}
else
{
$self
= new Image::ExifTool;
}
my
%saveOptions
= %{
$self
->{OPTIONS}};
$self
->{FILENAME} =
$self
->{RAF} =
undef
;
$self
->ParseArguments(
@_
);
$self
->ExtractInfo(
undef
);
my
$info
=
$self
->GetInfo(
undef
);
$self
->{OPTIONS} = \
%saveOptions
;
return
$info
;
}
sub
Options($$;@)
{
local
$_
;
my
$self
=
shift
;
my
$oldVal
;
while
(
@_
) {
my
$param
=
shift
;
$oldVal
=
$self
->{OPTIONS}->{
$param
};
last
unless
@_
;
$self
->{OPTIONS}->{
$param
} =
shift
;
}
return
$oldVal
;
}
sub
ClearOptions($)
{
local
$_
;
my
$self
=
shift
;
$self
->{OPTIONS} = {
Binary
=> 0,
Charset
=>
'UTF8'
,
Composite
=> 1,
Duplicates
=> 1,
HtmlDump
=> 0,
PrintConv
=> 1,
Sort
=>
'Input'
,
TextOut
=> \
*STDOUT
,
Unknown
=> 0,
Verbose
=> 0,
};
}
sub
ExtractInfo($;@)
{
local
$_
;
my
$self
=
shift
;
my
$options
=
$self
->{OPTIONS};
my
%saveOptions
;
if
(
defined
$_
[0]) {
%saveOptions
= %{
$self
->{OPTIONS}};
$self
->{FILENAME} =
undef
;
$self
->{RAF} =
undef
;
$self
->ParseArguments(
@_
);
}
$self
->Init();
delete
$self
->{MAKER_NOTE_FIXUP};
delete
$self
->{MAKER_NOTE_BYTE_ORDER};
delete
$self
->{DONE_ID3};
my
$filename
=
$self
->{FILENAME};
my
$raf
=
$self
->{RAF};
$self
->FoundTag(
'ExifToolVersion'
,
"$VERSION$RELEASE"
);
local
*EXIFTOOL_FILE
;
unless
(
$raf
) {
if
(
defined
$filename
and
$filename
ne
''
) {
unless
(
$filename
eq
'-'
) {
my
$name
=
$filename
;
$name
=~ /\|$/ and
$name
=~ s/.*?
"(.*)"
.*/$1/;
my
$dir
;
if
(
eval
'require File::Basename'
) {
$dir
= File::Basename::dirname(
$name
);
$name
= File::Basename::basename(
$name
);
}
else
{
$name
=~
tr
/\\/\//;
if
(
$name
=~ s/(.*)\///) {
$dir
=
length
($1) ? $1 :
'/'
;
}
}
$self
->FoundTag(
'FileName'
,
$name
);
$self
->FoundTag(
'Directory'
,
$dir
)
if
defined
$dir
and
length
$dir
;
}
if
(
open
(EXIFTOOL_FILE,
$filename
)) {
my
$filePt
= \
*EXIFTOOL_FILE
;
$raf
= new File::RandomAccess(
$filePt
);
$raf
->{TESTED} = -1
if
$filename
eq
'-'
or
$filename
=~ /\|$/;
$self
->{RAF} =
$raf
;
}
else
{
$self
->Error(
'Error opening file'
);
}
}
else
{
$self
->Error(
'No file specified'
);
}
}
if
(
$raf
) {
if
(
$raf
->{FILE_PT} and -f
$raf
->{FILE_PT}) {
my
$fileSize
= -s _;
my
$fileTime
= -M _;
$self
->FoundTag(
'FileSize'
,
$fileSize
)
if
defined
$fileSize
;
$self
->FoundTag(
'FileModifyDate'
, $^T -
$fileTime
*(24*3600))
if
defined
$fileTime
;
}
my
$tiffType
;
$self
->{FILE_EXT} = GetFileExtension(
$filename
);
my
@fileTypeList
= GetFileType(
$filename
);
if
(
@fileTypeList
) {
my
$pat
=
join
'|'
,
@fileTypeList
;
push
@fileTypeList
,
grep
(!/^(
$pat
)$/,
@fileTypes
);
$tiffType
=
$self
->{FILE_EXT};
}
else
{
@fileTypeList
=
@fileTypes
;
$tiffType
=
'TIFF'
;
}
push
@fileTypeList
,
''
;
$raf
->BinMode();
my
$pos
=
$raf
->Tell();
my
%dirInfo
= (
RAF
=>
$raf
,
Base
=>
$pos
);
for
(;;) {
my
$type
=
shift
@fileTypeList
;
unless
(
$type
) {
unless
(
defined
$type
) {
$self
->Error(GetFileType(
$filename
) ?
'File format error'
:
'Unknown file type'
);
last
;
}
my
$buff
;
$raf
->Read(
$buff
, 1024);
next
unless
$buff
=~ /(\xff\xd8\xff|MM\0\x2a|II\x2a\0)/g;
$type
= ($1 eq
"\xff\xd8\xff"
) ?
'JPEG'
:
'TIFF'
;
my
$skip
=
pos
(
$buff
) -
length
($1);
$dirInfo
{Base} =
$pos
+
$skip
;
$raf
->Seek(
$pos
+
$skip
, 0);
$self
->Warn(
"Skipped unknown $skip byte header"
);
}
$self
->{FILE_TYPE} =
$type
;
$dirInfo
{Parent} = (
$type
eq
'TIFF'
) ?
$tiffType
:
$type
;
my
$module
=
$moduleName
{
$type
};
$module
=
$type
unless
defined
$module
;
my
$func
=
"Process$type"
;
if
(
$module
) {
require
"Image/ExifTool/$module.pm"
;
$func
=
"Image::ExifTool::${module}::$func"
;
}
no
strict
'refs'
;
&$func
(
$self
, \
%dirInfo
) and
last
;
unless
(
$raf
->Seek(
$pos
, 0)) {
$self
->Error(
'Error seeking in file'
);
last
;
}
}
if
(
defined
$self
->{EXIF_DATA} and
$self
->{REQ_TAG_LOOKUP}->{exif}) {
$self
->FoundTag(
'EXIF'
,
$self
->{EXIF_DATA});
}
$self
->BuildCompositeTags()
if
$options
->{Composite};
if
(
$self
->{HTML_DUMP}) {
my
$pos
=
$self
->Options(
'HtmlDumpBase'
);
$pos
= (
$self
->{FIRST_EXIF_POS} || 0)
unless
defined
$pos
;
my
$dataPt
=
defined
$self
->{EXIF_DATA} ? \
$self
->{EXIF_DATA} :
undef
;
undef
$dataPt
if
defined
$self
->{EXIF_POS} and
$pos
!=
$self
->{EXIF_POS};
$self
->{HTML_DUMP}->Print(
$raf
,
$dataPt
,
$pos
,
$self
->{OPTIONS}->{TextOut},
$self
->{OPTIONS}->{HtmlDump},
$self
->{FILENAME} ?
"HTML Dump ($self->{FILENAME})"
:
'HTML Dump'
);
}
$raf
->Close()
if
$filename
;
}
%saveOptions
and
$self
->{OPTIONS} = \
%saveOptions
;
return
exists
$self
->{VALUE}->{Error} ? 0 : 1;
}
sub
GetInfo($;@)
{
local
$_
;
my
$self
=
shift
;
my
%saveOptions
;
unless
(
@_
and not
defined
$_
[0]) {
%saveOptions
= %{
$self
->{OPTIONS}};
$self
->{FILENAME} =
''
unless
defined
$self
->{FILENAME};
$self
->ParseArguments(
@_
);
}
my
$rtnTags
=
$self
->SetFoundTags();
my
(
%info
,
%ignored
);
my
$conv
=
$self
->{OPTIONS}->{PrintConv} ?
'PrintConv'
:
'ValueConv'
;
foreach
(
@$rtnTags
) {
my
$val
=
$self
->GetValue(
$_
,
$conv
);
defined
$val
or
$ignored
{
$_
} = 1,
next
;
$info
{
$_
} =
$val
;
}
my
$reqTags
=
$self
->{REQUESTED_TAGS} || [ ];
if
(
%ignored
and not
@$reqTags
) {
my
@goodTags
;
foreach
(
@$rtnTags
) {
push
@goodTags
,
$_
unless
$ignored
{
$_
};
}
$rtnTags
=
$self
->{FOUND_TAGS} = \
@goodTags
;
}
if
(
$self
->{IO_TAG_LIST}) {
my
$sortOrder
=
$self
->{OPTIONS}->{Sort};
unless
(
@$reqTags
or (
$sortOrder
and
$sortOrder
ne
'Input'
)) {
$sortOrder
=
'File'
;
}
@{
$self
->{IO_TAG_LIST}} =
$self
->GetTagList(
$rtnTags
,
$sortOrder
);
}
%saveOptions
and
$self
->{OPTIONS} = \
%saveOptions
;
return
\
%info
;
}
sub
CombineInfo($;@)
{
local
$_
;
my
$self
=
shift
;
my
(
%combinedInfo
,
$info
);
if
(
$self
->{OPTIONS}->{Duplicates}) {
while
(
$info
=
shift
) {
my
$key
;
foreach
$key
(
keys
%$info
) {
$combinedInfo
{
$key
} =
$$info
{
$key
};
}
}
}
else
{
my
(
%haveInfo
,
$tag
);
while
(
$info
=
shift
) {
foreach
$tag
(
keys
%$info
) {
my
$tagName
= GetTagName(
$tag
);
next
if
$haveInfo
{
$tagName
};
$haveInfo
{
$tagName
} = 1;
$combinedInfo
{
$tag
} =
$$info
{
$tag
};
}
}
}
return
\
%combinedInfo
;
}
sub
GetTagList($;$$)
{
local
$_
;
my
(
$self
,
$info
,
$sortOrder
) =
@_
;
my
$foundTags
;
if
(
ref
$info
eq
'HASH'
) {
my
@tags
=
keys
%$info
;
$foundTags
= \
@tags
;
}
elsif
(
ref
$info
eq
'ARRAY'
) {
$foundTags
=
$info
;
}
my
$fileOrder
=
$self
->{FILE_ORDER};
if
(
$foundTags
) {
foreach
(
@$foundTags
) {
next
if
defined
$$fileOrder
{
$_
};
$$fileOrder
{
$_
} = 999;
}
}
else
{
$sortOrder
=
$info
if
$info
and not
$sortOrder
;
$foundTags
=
$self
->{FOUND_TAGS} ||
$self
->SetFoundTags() or
return
undef
;
}
$sortOrder
or
$sortOrder
=
$self
->{OPTIONS}->{Sort};
return
@$foundTags
unless
$sortOrder
and
$sortOrder
ne
'Input'
;
if
(
$sortOrder
eq
'Alpha'
) {
return
sort
@$foundTags
;
}
elsif
(
$sortOrder
=~ /^Group(\d*)/) {
my
$family
= $1 || 0;
my
(
%groupCount
,
%groupOrder
);
my
$numGroups
= 0;
my
$tag
;
foreach
$tag
(
sort
{
$$fileOrder
{
$a
} <=>
$$fileOrder
{
$b
} }
@$foundTags
) {
my
$group
=
$self
->GetGroup(
$tag
,
$family
);
my
$num
=
$groupCount
{
$group
};
$num
or
$num
=
$groupCount
{
$group
} = ++
$numGroups
;
$groupOrder
{
$tag
} =
$num
;
}
return
sort
{
$groupOrder
{
$a
} <=>
$groupOrder
{
$b
} or
$$fileOrder
{
$a
} <=>
$$fileOrder
{
$b
} }
@$foundTags
;
}
else
{
return
sort
{
$$fileOrder
{
$a
} <=>
$$fileOrder
{
$b
} }
@$foundTags
;
}
}
sub
GetFoundTags($;$)
{
local
$_
;
my
(
$self
,
$sortOrder
) =
@_
;
my
$foundTags
=
$self
->{FOUND_TAGS} ||
$self
->SetFoundTags() or
return
undef
;
return
$self
->GetTagList(
$foundTags
,
$sortOrder
);
}
sub
GetRequestedTags($)
{
local
$_
;
return
@{
$_
[0]->{REQUESTED_TAGS}};
}
sub
GetValue($$;$)
{
local
$_
;
my
(
$self
,
$tag
,
$type
) =
@_
;
my
$value
=
$self
->{VALUE}->{
$tag
};
return
wantarray
? () :
undef
unless
defined
$value
;
my
(
@convTypes
,
$tagInfo
);
$type
or
$type
=
$self
->{OPTIONS}->{PrintConv} ?
'PrintConv'
:
'ValueConv'
;
unless
(
$type
eq
'Raw'
) {
$tagInfo
=
$self
->{TAG_INFO}->{
$tag
};
push
@convTypes
,
'ValueConv'
;
push
@convTypes
,
'PrintConv'
unless
$type
eq
'ValueConv'
;
}
my
(
@val
,
@prt
,
@raw
,
$convType
,
$valueConv
);
foreach
$convType
(
@convTypes
) {
last
if
ref
$value
eq
'SCALAR'
;
my
$conversion
=
$$tagInfo
{
$convType
};
unless
(
defined
$conversion
) {
if
(
$convType
eq
'ValueConv'
) {
next
unless
$$tagInfo
{Binary};
$conversion
=
'\$val'
;
}
else
{
next
unless
defined
(
$conversion
=
$tagInfo
->{Table}->{PRINT_CONV});
}
}
$valueConv
=
$value
if
$type
eq
'Both'
and
$convType
eq
'PrintConv'
;
my
(
$i
,
$val
,
$vals
,
@values
);
if
(
ref
$value
eq
'ARRAY'
) {
$i
= 0;
$vals
=
$value
;
$val
=
$$vals
[0];
}
else
{
$val
=
$value
;
}
for
(;;) {
if
(
ref
$conversion
eq
'HASH'
) {
unless
(
defined
(
$value
=
$$conversion
{
$val
})) {
if
(
$$conversion
{BITMASK}) {
$value
= DecodeBits(
$val
,
$$conversion
{BITMASK});
}
else
{
if
(
$$tagInfo
{PrintHex} and
$val
and IsInt(
$val
) and
$convType
eq
'PrintConv'
)
{
$val
=
sprintf
(
'0x%x'
,
$val
);
}
$value
=
"Unknown ($val)"
;
}
}
}
else
{
local
$SIG
{
'__WARN__'
} =
sub
{
$evalWarning
=
$_
[0]; };
undef
$evalWarning
;
if
(
ref
(
$conversion
) eq
'CODE'
) {
$value
=
&$conversion
(
$val
,
$self
);
}
else
{
if
(
ref
$val
eq
'HASH'
and not
@val
) {
foreach
(
keys
%$val
) {
$raw
[
$_
] =
$self
->{VALUE}->{
$$val
{
$_
}};
(
$val
[
$_
],
$prt
[
$_
]) =
$self
->GetValue(
$$val
{
$_
},
'Both'
);
next
if
defined
$val
[
$_
] or not
$tagInfo
->{Require}->{
$_
};
return
wantarray
? () :
undef
;
}
}
$value
=
eval
$conversion
;
$@ and
$evalWarning
= $@;
}
if
(
$evalWarning
) {
chomp
$evalWarning
;
$evalWarning
=~ s/ at \(
eval
.*//s;
delete
$SIG
{
'__WARN__'
};
warn
"$convType $tag: $evalWarning\n"
;
}
}
last
unless
$vals
;
push
@values
,
$value
if
defined
$value
;
if
(++
$i
>=
scalar
(
@$vals
)) {
$value
= \
@values
if
@values
;
last
;
}
$val
=
$$vals
[
$i
];
}
return
wantarray
? () :
undef
unless
defined
$value
;
}
if
(
$type
eq
'Both'
) {
$valueConv
=
$value
unless
defined
$valueConv
;
return
(
$valueConv
,
$value
);
}
if
(
ref
$value
eq
'ARRAY'
) {
return
@$value
if
wantarray
;
return
$value
if
@convTypes
< 2 or
$self
->{OPTIONS}->{List};
$value
=
join
', '
,
@$value
;
}
return
$value
;
}
sub
GetTagID($$)
{
local
$_
;
my
(
$self
,
$tag
) =
@_
;
my
$tagInfo
=
$self
->{TAG_INFO}->{
$tag
};
if
(
$tagInfo
) {
GenerateAllTagIDs();
defined
$$tagInfo
{TagID} and
return
$$tagInfo
{TagID};
}
return
''
;
}
sub
GetDescription($$)
{
local
$_
;
my
(
$self
,
$tag
) =
@_
;
my
$tagInfo
=
$self
->{TAG_INFO}->{
$tag
};
my
$desc
;
$desc
=
$$tagInfo
{Description}
if
$tagInfo
;
unless
(
$desc
) {
$desc
= MakeDescription(GetTagName(
$tag
));
$$tagInfo
{Description} =
$desc
if
$tagInfo
;
}
return
$desc
;
}
sub
GetGroup($$;$)
{
local
$_
;
my
(
$self
,
$tag
,
$family
) =
@_
;
my
(
$tagInfo
,
@groups
,
$extra
);
if
(
ref
$tag
eq
'HASH'
) {
$tagInfo
=
$tag
;
$tag
=
$tagInfo
->{Name};
}
else
{
$tagInfo
=
$self
->{TAG_INFO}->{
$tag
} or
return
''
;
}
my
$groups
=
$$tagInfo
{Groups};
unless
(
$$tagInfo
{GotGroups}) {
my
$tagTablePtr
=
$$tagInfo
{Table};
if
(
$tagTablePtr
) {
$groups
or
$groups
=
$$tagInfo
{Groups} = { };
foreach
(
keys
%{
$$tagTablePtr
{GROUPS}}) {
$$groups
{
$_
} or
$$groups
{
$_
} =
$tagTablePtr
->{GROUPS}->{
$_
};
}
}
$$tagInfo
{GotGroups} = 1;
}
if
(
defined
$family
and
$family
>= 0) {
return
$$groups
{
$family
} ||
'Other'
unless
$family
== 1;
$groups
[
$family
] =
$$groups
{
$family
};
}
else
{
return
$$groups
{0}
unless
wantarray
;
foreach
(0..2) {
$groups
[
$_
] =
$$groups
{
$_
}; }
}
if
(
$extra
=
$self
->{GROUP1}->{
$tag
}) {
if
(
$extra
=~ /^\+(.*)/) {
$groups
[1] .= $1;
}
else
{
$groups
[1] =
$extra
;
}
}
if
(
$family
) {
return
$groups
[1]
if
$family
== 1;
if
(
$groups
[1] =~ /^MIE(\d*)-(.+?)(\d*)$/) {
push
@groups
,
'MIE'
. ($1 ||
'1'
);
push
@groups
,
'MIE'
. ($1 ?
''
:
'1'
) .
"-$2$3"
;
push
@groups
,
"MIE$1-$2"
. ($3 ?
''
:
'1'
);
push
@groups
,
'MIE'
. ($1 ?
''
:
'1'
) .
"-$2"
. ($3 ?
''
:
'1'
);
}
}
return
@groups
;
}
sub
GetGroups($;$$)
{
local
$_
;
my
$self
=
shift
;
my
$info
=
shift
;
my
$family
;
if
(
ref
$info
ne
'HASH'
) {
$family
=
$info
;
$info
=
$self
->{VALUE};
}
else
{
$family
=
shift
;
}
$family
= 0
unless
defined
$family
;
my
(
$tag
,
%groups
);
foreach
$tag
(
keys
%$info
) {
$groups
{
$self
->GetGroup(
$tag
,
$family
) } = 1;
}
return
sort
keys
%groups
;
}
sub
SetNewGroups($;@)
{
local
$_
;
my
(
$self
,
@groups
) =
@_
;
@groups
or
@groups
=
@defaultWriteGroups
;
my
$count
=
@groups
;
my
%priority
;
foreach
(
@groups
) {
$priority
{
lc
(
$_
)} =
$count
--;
}
$priority
{file} = 10;
$priority
{composite} = 10;
$self
->{WRITE_PRIORITY} = \
%priority
;
$self
->{WRITE_GROUPS} = \
@groups
;
}
sub
BuildCompositeTags($)
{
local
$_
;
my
$self
=
shift
;
if
(
defined
%UserDefined
and
$UserDefined
{
'Image::ExifTool::Composite'
}) {
AddCompositeTags(
$UserDefined
{
'Image::ExifTool::Composite'
},1);
delete
$UserDefined
{
'Image::ExifTool::Composite'
};
}
my
@tagList
=
sort
keys
%Image::ExifTool::Composite
;
my
%tagsUsed
;
my
$rawValue
=
$self
->{VALUE};
for
(;;) {
my
%notBuilt
;
foreach
(
@tagList
) {
$notBuilt
{
$_
} = 1;
}
my
@deferredTags
;
my
$tag
;
COMPOSITE_TAG:
foreach
$tag
(
@tagList
) {
next
if
$specialTags
{
$tag
};
my
$tagInfo
=
$self
->GetTagInfo(\
%Image::ExifTool::Composite
,
$tag
);
next
unless
$tagInfo
;
my
(
%tagKey
,
$type
,
$found
);
foreach
$type
(
'Require'
,
'Desire'
) {
my
$req
=
$$tagInfo
{
$type
} or
next
;
my
$index
;
foreach
$index
(
keys
%$req
) {
my
$reqTag
=
$$req
{
$index
};
if
(
$reqTag
=~ /(.+?):(.+)/) {
my
(
$reqGroup
,
$name
) = ($1, $2);
my
$family
;
$family
= $1
if
$reqGroup
=~ s/^(\d+)//;
my
$i
= 0;
for
(;;++
$i
) {
$reqTag
=
$name
;
$reqTag
.=
" ($i)"
if
$i
;
last
unless
defined
$$rawValue
{
$reqTag
};
my
@groups
=
$self
->GetGroup(
$reqTag
,
$family
);
last
if
grep
{
$reqGroup
eq
$_
}
@groups
;
}
}
elsif
(
$notBuilt
{
$reqTag
}) {
push
@deferredTags
,
$tag
;
next
COMPOSITE_TAG;
}
if
(
defined
$$rawValue
{
$reqTag
}) {
$found
= 1;
}
else
{
$type
eq
'Require'
and
next
COMPOSITE_TAG;
}
$tagKey
{
$index
} =
$reqTag
;
}
}
delete
$notBuilt
{
$tag
};
next
unless
$found
;
foreach
(
keys
%tagKey
) {
next
unless
$Image::ExifTool::Composite
{
$tagKey
{
$_
}};
my
$keyRef
= \
$tagKey
{
$_
};
$tagsUsed
{
$$keyRef
} or
$tagsUsed
{
$$keyRef
} = [ ];
push
@{
$tagsUsed
{
$$keyRef
}},
$keyRef
;
}
my
$key
=
$self
->FoundTag(
$tagInfo
, \
%tagKey
);
next
unless
defined
$key
and
$tagsUsed
{
$key
};
foreach
(@{
$tagsUsed
{
$key
}}) {
$$_
=
$self
->{MOVED_KEY};
}
delete
$tagsUsed
{
$key
};
}
last
unless
@deferredTags
;
if
(
@deferredTags
==
@tagList
) {
warn
"Circular dependency in Composite tags\n"
;
last
;
}
@tagList
=
@deferredTags
;
}
}
sub
GetTagName($)
{
local
$_
;
$_
[0] =~ /^(\S+)/;
return
$1;
}
sub
GetShortcuts()
{
local
$_
;
return
sort
keys
%Image::ExifTool::Shortcuts::Main
;
}
sub
GetFileType(;$$)
{
local
$_
;
my
(
$file
,
$desc
) =
@_
;
return
sort
keys
%fileTypeLookup
unless
defined
$file
;
my
$fileType
;
my
$fileExt
= GetFileExtension(
$file
);
$fileExt
=
uc
(
$file
)
unless
$fileExt
;
$fileExt
and
$fileType
=
$fileTypeLookup
{
$fileExt
};
return
$$fileType
[1]
if
$desc
;
$fileType
=
$$fileType
[0];
if
(
wantarray
) {
return
()
unless
$fileType
;
return
@$fileType
if
ref
$fileType
eq
'ARRAY'
;
}
elsif
(
$fileType
) {
$fileType
=
$fileExt
if
ref
$fileType
eq
'ARRAY'
;
}
return
$fileType
;
}
sub
CanWrite($)
{
local
$_
;
my
$file
=
shift
or
return
undef
;
my
$type
= GetFileType(
$file
) or
return
undef
;
return
scalar
(
grep
/^
$type
$/,
@writeTypes
);
}
sub
CanCreate($)
{
local
$_
;
my
$file
=
shift
or
return
undef
;
my
$type
= GetFileType(
$file
) or
return
undef
;
return
scalar
(
grep
/^
$type
$/,
@createTypes
);
}
sub
Init($)
{
local
$_
;
my
$self
=
shift
;
foreach
(
keys
%$self
) {
/[a-z]/ and
delete
$self
->{
$_
};
}
delete
$self
->{FOUND_TAGS};
delete
$self
->{EXIF_DATA};
delete
$self
->{EXIF_POS};
delete
$self
->{FIRST_EXIF_POS};
delete
$self
->{EXIF_BYTE_ORDER};
delete
$self
->{HTML_DUMP};
$self
->{FILE_ORDER} = { };
$self
->{VALUE} = { };
$self
->{TAG_INFO} = { };
$self
->{GROUP1} = { };
$self
->{PRIORITY} = { };
$self
->{PROCESSED} = { };
$self
->{DIR_COUNT} = { };
$self
->{NUM_FOUND} = 0;
$self
->{CHANGED} = 0;
$self
->{INDENT} =
' '
;
$self
->{PRIORITY_DIR} =
''
;
$self
->{TIFF_TYPE} =
''
;
$self
->{CameraMake} =
''
;
$self
->{CameraModel}=
''
;
$self
->{CameraType} =
''
;
if
(
$self
->Options(
'HtmlDump'
)) {
$self
->{HTML_DUMP} = new Image::ExifTool::HtmlDump;
}
$self
->{OPTIONS}->{TextOut} = \
*STDOUT
unless
ref
$self
->{OPTIONS}->{TextOut};
}
sub
ParseArguments($;@)
{
my
$self
=
shift
;
my
$options
=
$self
->{OPTIONS};
my
@exclude
;
my
@oldGroupOpts
=
grep
/^Group/,
keys
%{
$self
->{OPTIONS}};
my
$wasExcludeOpt
;
$self
->{REQUESTED_TAGS} = [ ];
$self
->{REQ_TAG_LOOKUP} = { };
$self
->{IO_TAG_LIST} =
undef
;
while
(
@_
) {
my
$arg
=
shift
;
if
(
ref
$arg
) {
if
(
ref
$arg
eq
'ARRAY'
) {
$self
->{IO_TAG_LIST} =
$arg
;
foreach
(
@$arg
) {
if
(/^-(.*)/) {
push
@exclude
, $1;
}
else
{
push
@{
$self
->{REQUESTED_TAGS}},
$_
;
}
}
}
elsif
(
ref
$arg
eq
'HASH'
) {
my
$opt
;
foreach
$opt
(
keys
%$arg
) {
if
(
@oldGroupOpts
and
$opt
=~ /^Group/) {
foreach
(
@oldGroupOpts
) {
delete
$options
->{
$_
};
}
undef
@oldGroupOpts
;
}
$options
->{
$opt
} =
$$arg
{
$opt
};
$opt
eq
'Exclude'
and
$wasExcludeOpt
= 1;
}
}
elsif
(
ref
$arg
eq
'SCALAR'
or UNIVERSAL::isa(
$arg
,
'GLOB'
)) {
next
if
defined
$self
->{RAF};
if
(
ref
$arg
eq
'SCALAR'
and
eval
'require Encode; Encode::is_utf8($$arg)'
) {
my
$buff
=
pack
(
'C*'
,
unpack
(
'U*'
,
$$arg
));
$arg
= \
$buff
;
}
$self
->{RAF} = new File::RandomAccess(
$arg
);
$self
->{FILENAME} =
''
;
}
else
{
warn
"Don't understand ImageInfo argument $arg\n"
;
}
}
elsif
(
defined
$self
->{FILENAME}) {
if
(
$arg
=~ /^-(.*)/) {
push
@exclude
, $1;
}
else
{
push
@{
$self
->{REQUESTED_TAGS}},
$arg
;
}
}
else
{
$self
->{FILENAME} =
$arg
;
}
}
if
(@{
$self
->{REQUESTED_TAGS}}) {
ExpandShortcuts(
$self
->{REQUESTED_TAGS});
foreach
(@{
$self
->{REQUESTED_TAGS}}) {
$self
->{REQ_TAG_LOOKUP}->{
lc
(/.+?:(.+)/ ? $1 :
$_
)} = 1;
}
}
if
(
@exclude
or
$wasExcludeOpt
) {
if
(
$options
->{Exclude}) {
if
(
ref
$options
->{Exclude} eq
'ARRAY'
) {
push
@exclude
, @{
$options
->{Exclude}};
}
else
{
push
@exclude
,
$options
->{Exclude};
}
}
$options
->{Exclude} = \
@exclude
;
ExpandShortcuts(
$options
->{Exclude});
}
}
sub
SetFoundTags($)
{
my
$self
=
shift
;
my
$options
=
$self
->{OPTIONS};
my
$reqTags
=
$self
->{REQUESTED_TAGS} || [ ];
my
$duplicates
=
$options
->{Duplicates};
my
$exclude
=
$options
->{Exclude};
my
$fileOrder
=
$self
->{FILE_ORDER};
my
@groupOptions
=
sort
grep
/^Group/,
keys
%$options
;
my
$doDups
=
$duplicates
||
$exclude
||
@groupOptions
;
my
(
$tag
,
$rtnTags
);
if
(
@$reqTags
) {
$rtnTags
or
$rtnTags
= [ ];
my
$tagHash
=
$self
->{VALUE};
my
$reqTag
;
foreach
$reqTag
(
@$reqTags
) {
my
(
@matches
,
$group
,
$family
,
$allGrp
,
$allTag
);
if
(
$reqTag
=~ /^(\d+)?(.+?):(.+)/) {
(
$family
,
$group
,
$tag
) = ($1, $2, $3);
$allGrp
= 1
if
$group
=~ /^(\*|all)$/i;
$family
= -1
unless
defined
$family
;
}
else
{
$tag
=
$reqTag
;
$family
= -1;
}
if
(
defined
$tagHash
->{
$reqTag
} and not
$doDups
) {
$matches
[0] =
$tag
;
}
elsif
(
$tag
=~ /^(\*|all)$/i) {
if
(
$doDups
or
$allGrp
) {
@matches
=
keys
%$tagHash
;
}
else
{
@matches
=
grep
(!/ /,
keys
%$tagHash
);
}
next
unless
@matches
;
$allTag
= 1;
}
elsif
(
$doDups
or
defined
$group
) {
@matches
=
grep
(/^
$tag
(\s|$)/i,
keys
%$tagHash
);
}
else
{
(
$matches
[0]) =
grep
/^
$tag
$/i,
keys
%$tagHash
;
defined
$matches
[0] or
undef
@matches
;
}
if
(
defined
$group
and not
$allGrp
) {
my
@grpMatches
;
foreach
(
@matches
) {
my
@groups
=
$self
->GetGroup(
$_
,
$family
);
next
unless
grep
/^
$group
$/i,
@groups
;
push
@grpMatches
,
$_
;
}
@matches
=
@grpMatches
;
next
unless
@matches
or not
$allTag
;
}
if
(
@matches
> 1) {
@matches
=
sort
{
$$fileOrder
{
$a
} <=>
$$fileOrder
{
$b
} }
@matches
;
unless
(
$doDups
or
$allTag
or
$allGrp
) {
$tag
=
shift
@matches
;
my
$oldPriority
=
$self
->{PRIORITY}->{
$tag
} || 1;
foreach
(
@matches
) {
my
$priority
=
$self
->{PRIORITY}->{
$_
};
$priority
= 1
unless
defined
$priority
;
next
unless
$priority
>=
$oldPriority
;
$tag
=
$_
;
$oldPriority
=
$priority
|| 1;
}
@matches
= (
$tag
);
}
}
elsif
(not
@matches
) {
$matches
[0] =
"$tag (0)"
;
$self
->{FILE_ORDER}->{
$matches
[0]} = 999;
}
push
@$rtnTags
,
@matches
;
}
}
else
{
my
@allTags
;
if
(
$doDups
) {
@allTags
=
keys
%{
$self
->{VALUE}};
}
else
{
foreach
(
keys
%{
$self
->{VALUE}}) {
push
@allTags
,
$_
unless
/ /;
}
}
$rtnTags
= \
@allTags
;
}
while
((
$exclude
or
@groupOptions
) and
@$rtnTags
) {
if
(
$exclude
) {
my
@filteredTags
;
EX_TAG:
foreach
$tag
(
@$rtnTags
) {
my
$tagName
= GetTagName(
$tag
);
my
@matches
=
grep
/(^|:)(
$tagName
|\*|all)$/i,
@$exclude
;
foreach
(
@matches
) {
next
EX_TAG
unless
/^(\d+)?(.+?):/;
my
(
$family
,
$group
) = ($1, $2);
next
EX_TAG
if
$group
=~ /^(\*|all)$/i;
$family
= -1
unless
defined
$family
;
my
@groups
=
$self
->GetGroup(
$tag
,
$family
);
next
EX_TAG
if
grep
/^
$group
$/i,
@groups
;
}
push
@filteredTags
,
$tag
;
}
$rtnTags
= \
@filteredTags
;
last
if
$duplicates
and not
@groupOptions
;
}
my
(
%keepTags
,
%wantGroup
,
$family
,
$groupOpt
);
my
$allGroups
= 1;
my
$wantOrder
= 0;
foreach
$groupOpt
(
@groupOptions
) {
$groupOpt
=~ /^Group(\d*)/ or
next
;
$family
= $1 || 0;
$wantGroup
{
$family
} or
$wantGroup
{
$family
} = { };
my
$groupList
;
if
(
ref
$options
->{
$groupOpt
} eq
'ARRAY'
) {
$groupList
=
$options
->{
$groupOpt
};
}
else
{
$groupList
= [
$options
->{
$groupOpt
} ];
}
foreach
(
@$groupList
) {
++
$wantOrder
;
my
(
$groupName
,
$want
);
if
(/^-(.*)/) {
$groupName
= $1;
$want
= 0;
}
else
{
$groupName
=
$_
;
$want
=
$wantOrder
;
$allGroups
= 0;
}
$wantGroup
{
$family
}->{
$groupName
} =
$want
;
}
}
my
(
@tags
,
%bestTag
);
GR_TAG:
foreach
$tag
(
@$rtnTags
) {
my
$wantTag
=
$allGroups
;
foreach
$family
(
keys
%wantGroup
) {
my
$group
=
$self
->GetGroup(
$tag
,
$family
);
my
$wanted
=
$wantGroup
{
$family
}->{
$group
};
next
unless
defined
$wanted
;
next
GR_TAG
unless
$wanted
;
next
if
$wantTag
and
$wantTag
<
$wanted
;
$wantTag
=
$wanted
;
}
next
unless
$wantTag
;
if
(
$duplicates
) {
push
@tags
,
$tag
;
}
else
{
my
$tagName
= GetTagName(
$tag
);
my
$bestTag
=
$bestTag
{
$tagName
};
if
(
defined
$bestTag
) {
next
if
$wantTag
>
$keepTags
{
$bestTag
};
if
(
$wantTag
==
$keepTags
{
$bestTag
}) {
if
(
$tag
=~ / \((\d+)\)$/) {
my
$tagNum
= $1;
next
if
$bestTag
!~ / \((\d+)\)$/ or $1 >
$tagNum
;
}
}
delete
$keepTags
{
$bestTag
};
}
$keepTags
{
$tag
} =
$wantTag
;
$bestTag
{
$tagName
} =
$tag
;
}
}
unless
(
$duplicates
) {
foreach
$tag
(
@$rtnTags
) {
push
@tags
,
$tag
if
$keepTags
{
$tag
};
}
}
$rtnTags
= \
@tags
;
last
;
}
return
$self
->{FOUND_TAGS} =
$rtnTags
;
}
sub
DoAutoLoad(@)
{
my
$autoload
=
shift
;
my
@callInfo
=
split
(/::/,
$autoload
);
my
$file
=
'Image/ExifTool/Write'
;
return
if
$callInfo
[
$#callInfo
] eq
'DESTROY'
;
if
(
@callInfo
== 4) {
$file
.=
"$callInfo[2].pl"
;
}
else
{
$file
.=
'r.pl'
;
}
eval
"require '$file'"
or
die
"Error while attempting to call $autoload\n$@\n"
;
unless
(
defined
&$autoload
) {
my
@caller
=
caller
(0);
die
"Undefined subroutine $autoload called at $caller[1] line $caller[2]\n"
;
}
no
strict
'refs'
;
return
&$autoload
(
@_
);
}
sub
AUTOLOAD
{
return
DoAutoLoad(
$AUTOLOAD
,
@_
);
}
sub
Warn($$;$)
{
my
(
$self
,
$str
,
$ignorable
) =
@_
;
if
(
$ignorable
) {
return
0
if
$self
->{OPTIONS}->{IgnoreMinorErrors};
$str
=
"[minor] $str"
;
}
$self
->FoundTag(
'Warning'
,
$str
);
return
1;
}
sub
Error($$;$)
{
my
(
$self
,
$str
,
$ignorable
) =
@_
;
if
(
$ignorable
) {
if
(
$self
->{OPTIONS}->{IgnoreMinorErrors}) {
$self
->Warn(
$str
);
return
0;
}
$str
=
"[minor] $str"
;
}
$self
->FoundTag(
'Error'
,
$str
);
return
1;
}
sub
ExpandShortcuts($)
{
my
$tagList
=
shift
||
return
;
my
@expandedTags
;
my
(
$entry
,
$tag
);
foreach
$entry
(
@$tagList
) {
(
$tag
=
$entry
) =~ s/^-//;
my
(
$match
) =
grep
/^\Q
$tag
\E$/i,
keys
%Image::ExifTool::Shortcuts::Main
;
if
(
$match
) {
if
(
$tag
eq
$entry
) {
push
@expandedTags
, @{
$Image::ExifTool::Shortcuts::Main
{
$match
}};
}
else
{
foreach
(@{
$Image::ExifTool::Shortcuts::Main
{
$match
}}) {
/^-/ and
next
;
push
@expandedTags
,
"-$_"
;
}
}
}
else
{
push
@expandedTags
,
$entry
;
}
}
@$tagList
=
@expandedTags
;
}
sub
AddCompositeTags($;$)
{
local
$_
;
my
(
$add
,
$overwrite
) =
@_
;
my
$module
;
unless
(
ref
$add
) {
$module
=
$add
;
$add
.=
'::Composite'
;
no
strict
'refs'
;
$add
= \
%$add
;
}
my
$defaultGroups
=
$$add
{GROUPS};
if
(
$defaultGroups
) {
$defaultGroups
->{0} or
$defaultGroups
->{0} =
'Composite'
;
$defaultGroups
->{1} or
$defaultGroups
->{1} =
'Composite'
;
$defaultGroups
->{2} or
$defaultGroups
->{2} =
'Other'
;
}
else
{
$defaultGroups
=
$$add
{GROUPS} = {
0
=>
'Composite'
,
1
=>
'Composite'
,
2
=>
'Other'
};
}
SetupTagTable(
$add
);
my
$tagID
;
foreach
$tagID
(
keys
%$add
) {
next
if
$specialTags
{
$tagID
};
my
$tagInfo
=
$$add
{
$tagID
};
my
$tag
=
$$tagInfo
{Name};
$$tagInfo
{Module} =
$module
if
$$tagInfo
{Writable};
my
(
$t
,
$n
);
while
(
$Image::ExifTool::Composite
{
$tag
} and not
$overwrite
) {
$n
?
$n
+= 1 :
$n
= 2,
$t
=
$tag
;
$tag
=
"${t}_$n"
;
}
$$tagInfo
{Table} = \
%Image::ExifTool::Composite
;
$Image::ExifTool::Composite
{
$tag
} =
$tagInfo
;
my
$groups
=
$$tagInfo
{Groups};
$groups
or
$groups
=
$$tagInfo
{Groups} = { };
foreach
(
keys
%$defaultGroups
) {
$$groups
{
$_
} or
$$groups
{
$_
} =
$$defaultGroups
{
$_
};
}
$$tagInfo
{GotGroups} = 1;
}
}
sub
ExpandFlags($)
{
my
$tagInfo
=
shift
;
my
$flags
=
$$tagInfo
{Flags};
if
(
ref
$flags
eq
'ARRAY'
) {
foreach
(
@$flags
) {
$$tagInfo
{
$_
} = 1;
}
}
elsif
(
ref
$flags
eq
'HASH'
) {
my
$key
;
foreach
$key
(
keys
%$flags
) {
$$tagInfo
{
$key
} =
$$flags
{
$key
};
}
}
else
{
$$tagInfo
{
$flags
} = 1;
}
}
sub
SetupTagTable($)
{
my
$tagTablePtr
=
shift
;
my
$tagID
;
foreach
$tagID
(TagTableKeys(
$tagTablePtr
)) {
my
@infoArray
= GetTagInfoList(
$tagTablePtr
,
$tagID
);
my
$tagInfo
;
foreach
$tagInfo
(
@infoArray
) {
$$tagInfo
{Table} =
$tagTablePtr
;
my
$tag
=
$$tagInfo
{Name};
unless
(
defined
$tag
) {
$tag
=
$tagID
;
$$tagInfo
{Name} =
ucfirst
(
$tag
);
}
$$tagInfo
{Flags} and ExpandFlags(
$tagInfo
);
}
}
}
sub
IsFloat($) {
return
1
if
$_
[0] =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/;
return
0
unless
$_
[0] =~ /^([+-]?)(?=\d|,\d)\d*(,\d*)?([Ee]([+-]?\d+))?$/;
$_
[0] =~
tr
/,/./;
return
1;
}
sub
IsInt($) {
return
scalar
(
$_
[0] =~ /^[+-]?\d+$/); }
sub
IsHex($) {
return
scalar
(
$_
[0] =~ /^(0x)?[0-9a-f]{1,8}$/i); }
sub
RoundFloat($$)
{
my
(
$val
,
$sig
) =
@_
;
$val
== 0 and
return
0;
my
$sign
=
$val
< 0 ? (
$val
=-
$val
, -1) : 1;
my
$log
=
log
(
$val
) /
log
(10);
my
$exp
=
int
(
$log
) -
$sig
+ (
$log
> 0 ? 1 : 0);
return
$sign
*
int
(10 ** (
$log
-
$exp
) + 0.5) * 10 **
$exp
;
}
my
$swapBytes
;
my
$swapWords
;
my
$currentByteOrder
=
'MM'
;
my
%unpackMotorola
= (
S
=>
'n'
,
L
=>
'N'
,
C
=>
'C'
,
c
=>
'c'
);
my
%unpackIntel
= (
S
=>
'v'
,
L
=>
'V'
,
C
=>
'C'
,
c
=>
'c'
);
my
%unpackStd
=
%unpackMotorola
;
sub
SwapBytes($$)
{
return
$_
[0]
unless
$swapBytes
;
my
(
$val
,
$bytes
) =
@_
;
my
$newVal
=
''
;
$newVal
.=
substr
(
$val
,
$bytes
, 1)
while
$bytes
--;
return
$newVal
;
}
sub
SwapWords($)
{
return
$_
[0]
unless
$swapWords
and
length
(
$_
[0]) == 8;
return
substr
(
$_
[0],4,4) .
substr
(
$_
[0],0,4)
}
sub
DoUnpackStd(@)
{
$_
[2] and
return
unpack
(
"x$_[2] $unpackStd{$_[0]}"
, ${
$_
[1]});
return
unpack
(
$unpackStd
{
$_
[0]}, ${
$_
[1]});
}
sub
DoPackStd(@)
{
my
$val
=
pack
(
$unpackStd
{
$_
[0]},
$_
[1]);
$_
[2] and
substr
(${
$_
[2]},
$_
[3],
length
(
$val
)) =
$val
;
return
$val
;
}
sub
DoUnpack(@)
{
my
(
$bytes
,
$template
,
$dataPt
,
$pos
) =
@_
;
my
$val
;
if
(
$swapBytes
) {
$val
=
''
;
$val
.=
substr
(
$$dataPt
,
$pos
+
$bytes
,1)
while
$bytes
--;
}
else
{
$val
=
substr
(
$$dataPt
,
$pos
,
$bytes
);
}
defined
(
$val
) or
return
undef
;
return
unpack
(
$template
,
$val
);
}
sub
DoUnpackDbl(@)
{
my
(
$template
,
$dataPt
,
$pos
) =
@_
;
my
$val
=
substr
(
$$dataPt
,
$pos
,8);
defined
(
$val
) or
return
undef
;
return
unpack
(
$template
, SwapWords(SwapBytes(
$val
, 8)));
}
sub
Get8s($$) {
return
DoUnpackStd(
'c'
,
@_
); }
sub
Get8u($$) {
return
DoUnpackStd(
'C'
,
@_
); }
sub
Get16s($$) {
return
DoUnpack(2,
's'
,
@_
); }
sub
Get16u($$) {
return
DoUnpackStd(
'S'
,
@_
); }
sub
Get32s($$) {
return
DoUnpack(4,
'l'
,
@_
); }
sub
Get32u($$) {
return
DoUnpackStd(
'L'
,
@_
); }
sub
GetFloat($$) {
return
DoUnpack(4,
'f'
,
@_
); }
sub
GetDouble($$) {
return
DoUnpackDbl(
'd'
,
@_
); }
sub
GetRational32s($$)
{
my
(
$dataPt
,
$pos
) =
@_
;
my
$denom
= Get16s(
$dataPt
,
$pos
+ 2) or
return
'inf'
;
return
RoundFloat(Get16s(
$dataPt
,
$pos
) /
$denom
, 7);
}
sub
GetRational32u($$)
{
my
(
$dataPt
,
$pos
) =
@_
;
my
$denom
= Get16u(
$dataPt
,
$pos
+ 2) or
return
'inf'
;
return
RoundFloat(Get16u(
$dataPt
,
$pos
) /
$denom
, 7);
}
sub
GetRational64s($$)
{
my
(
$dataPt
,
$pos
) =
@_
;
my
$denom
= Get32s(
$dataPt
,
$pos
+ 4) or
return
'inf'
;
return
RoundFloat(Get32s(
$dataPt
,
$pos
) /
$denom
, 7);
}
sub
GetRational64u($$)
{
my
(
$dataPt
,
$pos
) =
@_
;
my
$denom
= Get32u(
$dataPt
,
$pos
+ 4) or
return
'inf'
;
return
RoundFloat(Get32u(
$dataPt
,
$pos
) /
$denom
, 7);
}
sub
GetFixed16s($$)
{
my
(
$dataPt
,
$pos
) =
@_
;
my
$val
= Get16s(
$dataPt
,
$pos
) / 0x100;
return
int
(
$val
* 1000 + (
$val
<0 ? -0.5 : 0.5)) / 1000;
}
sub
GetFixed16u($$)
{
my
(
$dataPt
,
$pos
) =
@_
;
return
int
((Get16u(
$dataPt
,
$pos
) / 0x100) * 1000 + 0.5) / 1000;
}
sub
GetFixed32s($$)
{
my
(
$dataPt
,
$pos
) =
@_
;
my
$val
= Get32s(
$dataPt
,
$pos
) / 0x10000;
return
int
(
$val
* 1e5 + (
$val
>0 ? 0.5 : -0.5)) / 1e5;
}
sub
GetFixed32u($$)
{
my
(
$dataPt
,
$pos
) =
@_
;
return
int
((Get32u(
$dataPt
,
$pos
) / 0x10000) * 1e5 + 0.5) / 1e5;
}
sub
Set8s(@) {
return
DoPackStd(
'c'
,
@_
); }
sub
Set8u(@) {
return
DoPackStd(
'C'
,
@_
); }
sub
Set16u(@) {
return
DoPackStd(
'S'
,
@_
); }
sub
Set32u(@) {
return
DoPackStd(
'L'
,
@_
); }
sub
GetByteOrder() {
return
$currentByteOrder
; }
sub
SetByteOrder($)
{
my
$order
=
shift
;
if
(
$order
eq
'MM'
) {
%unpackStd
=
%unpackMotorola
;
}
elsif
(
$order
eq
'II'
) {
%unpackStd
=
%unpackIntel
;
}
else
{
return
0;
}
my
$val
=
unpack
(
'S'
,
'A '
);
my
$nativeOrder
;
if
(
$val
== 0x4120) {
$nativeOrder
=
'MM'
;
}
elsif
(
$val
== 0x2041) {
$nativeOrder
=
'II'
;
}
else
{
warn
sprintf
(
"Unknown native byte order! (pattern %x)\n"
,
$val
);
return
0;
}
$currentByteOrder
=
$order
;
$swapBytes
= (
$order
ne
$nativeOrder
);
my
$pack1d
=
pack
(
'd'
, 1);
$swapWords
= (
$pack1d
eq
"\0\0\x0f\xf3\0\0\0\0"
or
$pack1d
eq
"\0\0\xf0\x3f\0\0\0\0"
);
return
1;
}
sub
ToggleByteOrder()
{
SetByteOrder(GetByteOrder() eq
'II'
?
'MM'
:
'II'
);
}
my
%formatSize
= (
int8s
=> 1,
int8u
=> 1,
int16s
=> 2,
int16u
=> 2,
int32s
=> 4,
int32u
=> 4,
int64s
=> 8,
int64u
=> 8,
rational32s
=> 4,
rational32u
=> 4,
rational64s
=> 8,
rational64u
=> 8,
fixed16s
=> 2,
fixed16u
=> 2,
fixed32s
=> 4,
fixed32u
=> 4,
float
=> 4,
double
=> 8,
extended
=> 10,
string
=> 1,
binary
=> 1,
'undef'
=> 1,
ifd
=> 4,
);
my
%readValueProc
= (
int8s
=> \
&Get8s
,
int8u
=> \
&Get8u
,
int16s
=> \
&Get16s
,
int16u
=> \
&Get16u
,
int32s
=> \
&Get32s
,
int32u
=> \
&Get32u
,
int64s
=> \
&Get64s
,
int64u
=> \
&Get64u
,
rational32s
=> \
&GetRational32s
,
rational32u
=> \
&GetRational32u
,
rational64s
=> \
&GetRational64s
,
rational64u
=> \
&GetRational64u
,
fixed16s
=> \
&GetFixed16s
,
fixed16u
=> \
&GetFixed16u
,
fixed32s
=> \
&GetFixed32s
,
fixed32u
=> \
&GetFixed32u
,
float
=> \
&GetFloat
,
double
=> \
&GetDouble
,
extended
=> \
&GetExtended
,
ifd
=> \
&Get32u
,
);
sub
FormatSize($) {
return
$formatSize
{
$_
[0]}; }
sub
ReadValue($$$$$)
{
my
(
$dataPt
,
$offset
,
$format
,
$count
,
$size
) =
@_
;
my
$len
=
$formatSize
{
$format
};
unless
(
$len
) {
warn
"Unknown format $format"
;
$len
= 1;
}
unless
(
$count
) {
return
''
if
defined
$count
or
$size
<
$len
;
$count
=
int
(
$size
/
$len
);
}
if
(
$len
*
$count
>
$size
) {
$count
=
int
(
$size
/
$len
);
$count
< 1 and
return
undef
;
}
my
@vals
;
my
$proc
=
$readValueProc
{
$format
};
if
(
$proc
) {
for
(;;) {
push
@vals
,
&$proc
(
$dataPt
,
$offset
);
last
if
--
$count
<= 0;
$offset
+=
$len
;
}
}
else
{
$vals
[0] =
substr
(
$$dataPt
,
$offset
,
$count
);
$vals
[0] =~ s/\0.*//s
if
$format
eq
'string'
;
}
if
(
wantarray
) {
return
@vals
;
}
elsif
(
@vals
> 1) {
return
join
(
' '
,
@vals
);
}
else
{
return
$vals
[0];
}
}
sub
DecodeBits($$)
{
my
(
$bits
,
$lookup
) =
@_
;
my
$outStr
=
''
;
my
$i
;
for
(
$i
=0;
$i
<32; ++
$i
) {
next
unless
$bits
& (1 <<
$i
);
$outStr
.=
', '
if
$outStr
;
if
(
$$lookup
{
$i
}) {
$outStr
.=
$$lookup
{
$i
};
}
else
{
$outStr
.=
"[$i]"
;
}
}
return
$outStr
||
'(none)'
;
}
sub
ValidateImage($$$)
{
my
(
$self
,
$imagePt
,
$tag
) =
@_
;
return
undef
if
$$imagePt
eq
'none'
;
unless
(
$$imagePt
=~ /^(Binary data|\xff\xd8\xff)/ or
$$imagePt
=~ s/^.(\xd8\xff\xdb)/\xff$1/ or
$self
->Options(
'IgnoreMinorErrors'
))
{
if
(
$self
->{REQ_TAG_LOOKUP}->{
lc
(
$tag
)}) {
$self
->Warn(
"$tag is not a valid JPEG image"
,1);
return
undef
;
}
}
return
$imagePt
;
}
sub
MakeDescription($;$)
{
my
(
$tag
,
$tagID
) =
@_
;
my
$desc
=
ucfirst
(
$tag
);
$desc
=~
tr
/_/ /;
$desc
=~ s/([a-z])([A-Z\d])/$1 $2/g;
$desc
=~ s/([A-Z])([A-Z][a-z])/$1 $2/g;
$desc
=~ s/(\d)([A-Z]\S)/$1 $2/g;
$desc
=~ s/ 0x ([\dA-Fa-f])/ 0x$1/g;
$desc
.=
' '
.
$tagID
if
defined
$tagID
;
return
$desc
;
}
sub
Printable($;$)
{
my
(
$self
,
$outStr
,
$unlimited
) =
@_
;
return
'(undef)'
unless
defined
$outStr
;
$outStr
=~
tr
/\x01-\x1f\x7f-\xff/./;
$outStr
=~ s/\x00//g;
if
(
length
(
$outStr
) > 60 and not
$unlimited
and
$self
->{OPTIONS}->{Verbose} < 4) {
$outStr
=
substr
(
$outStr
,0,54) .
'[snip]'
;
}
return
$outStr
;
}
sub
ConvertDateTime($$)
{
my
(
$self
,
$date
) =
@_
;
my
$dateFormat
=
$self
->{OPTIONS}->{DateFormat};
if
(
$dateFormat
) {
if
(
$date
=~ /^(\d+):(\d+):(\d+)\s+(\d+):(\d+):(\d+)/ and
eval
'require POSIX'
) {
$date
= POSIX::strftime(
$dateFormat
, $6, $5, $4, $3, $2-1, $1-1900);
}
elsif
(
$self
->{OPTIONS}->{StrictDate}) {
undef
$date
;
}
}
return
$date
;
}
sub
ConvertUnixTime($;$)
{
my
$time
=
shift
;
return
'0000:00:00 00:00:00'
if
$time
== 0;
my
@tm
=
shift
() ?
localtime
(
$time
) :
gmtime
(
$time
);
return
sprintf
(
"%4d:%.2d:%.2d %.2d:%.2d:%.2d"
,
$tm
[5]+1900,
$tm
[4]+1,
$tm
[3],
$tm
[2],
$tm
[1],
$tm
[0]);
}
sub
GetUnixTime($;$)
{
my
$timeStr
=
shift
;
return
0
if
$timeStr
eq
'0000:00:00 00:00:00'
;
my
@tm
= (
$timeStr
=~ /^(\d+):(\d+):(\d+)\s+(\d+):(\d+):(\d+)/);
return
undef
unless
@tm
== 6;
return
undef
unless
eval
'require Time::Local'
;
$tm
[0] -= 1900;
$tm
[1] -= 1;
@tm
=
reverse
@tm
;
return
shift
() ? Time::Local::timelocal(
@tm
) : Time::Local::timegm(
@tm
);
}
sub
HtmlDump($$$$;$$)
{
my
$self
=
shift
;
$self
->{HTML_DUMP} and
$self
->{HTML_DUMP}->Add(
@_
);
}
my
%jpegMarker
= (
0x01
=>
'TEM'
,
0xc0
=>
'SOF0'
,
0xc4
=>
'DHT'
,
0xc8
=>
'JPGA'
,
0xcc
=>
'DAC'
,
0xd0
=>
'RST0'
,
0xd8
=>
'SOI'
,
0xd9
=>
'EOI'
,
0xda
=>
'SOS'
,
0xdb
=>
'DQT'
,
0xdc
=>
'DNL'
,
0xdd
=>
'DRI'
,
0xde
=>
'DHP'
,
0xdf
=>
'EXP'
,
0xe0
=>
'APP0'
,
0xf0
=>
'JPG0'
,
0xfe
=>
'COM'
,
);
sub
JpegMarkerName($)
{
my
$marker
=
shift
;
my
$markerName
=
$jpegMarker
{
$marker
};
unless
(
$markerName
) {
$markerName
=
$jpegMarker
{
$marker
& 0xf0};
if
(
$markerName
and
$markerName
=~ /^([A-Z]+)\d+$/) {
$markerName
= $1 . (
$marker
& 0x0f);
}
else
{
$markerName
=
sprintf
(
"marker 0x%.2x"
,
$marker
);
}
}
return
$markerName
;
}
sub
IdentifyTrailer($;$)
{
my
$raf
=
shift
;
my
$offset
=
shift
|| 0;
my
$pos
=
$raf
->Tell();
my
(
$buff
,
$type
,
$len
);
while
(
$raf
->Seek(-
$offset
, 2) and (
$len
=
$raf
->Tell()) > 0) {
$len
= 64
if
$len
> 64;
$raf
->Seek(-
$len
, 1) and
$raf
->Read(
$buff
,
$len
) ==
$len
or
last
;
if
(
$buff
=~ /AXS(!|\*).{8}$/s) {
$type
=
'AFCP'
;
}
elsif
(
$buff
=~ /\xa1\xb2\xc3\xd4$/) {
$type
=
'FotoStation'
;
}
elsif
(
$buff
=~ /cbipcbbl$/) {
$type
=
'PhotoMechanic'
;
}
elsif
(
$buff
=~ /^CANON OPTIONAL DATA\0/) {
$type
=
'CanonVRD'
;
}
elsif
(
$buff
=~ /~\0\x04\0zmie~\0\0\x06.{4}[\x10\x18]\x04$/s or
$buff
=~ /~\0\x04\0zmie~\0\0\x0a.{8}[\x10\x18]\x08$/s)
{
$type
=
'MIE'
;
}
last
;
}
$raf
->Seek(
$pos
, 0);
return
$type
? {
RAF
=>
$raf
,
DirName
=>
$type
} :
undef
;
}
sub
ProcessJPEG($$)
{
my
(
$self
,
$dirInfo
) =
@_
;
my
(
$ch
,
$s
,
$length
);
my
$verbose
=
$self
->{OPTIONS}->{Verbose};
my
$out
=
$self
->{OPTIONS}->{TextOut};
my
$raf
=
$$dirInfo
{RAF};
my
$htmlDump
=
$self
->{HTML_DUMP};
my
%dumpParms
= (
Out
=>
$out
);
my
(
$success
,
$icc_profile
,
$wantPreview
,
$trailInfo
);
return
0
unless
$raf
->Read(
$s
, 2) == 2 and
$s
eq
"\xff\xd8"
;
$dumpParms
{MaxLen} = 128
if
$verbose
< 4;
$self
->SetFileType();
$self
->HtmlDump(0, 2,
'JPEG header'
,
'SOI Marker'
);
my
$oldsep
= $/;
$/ =
"\xff"
;
my
(
$nextMarker
,
$nextSegDataPt
,
$nextSegPos
,
$combinedSegData
,
$dumpEnd
);
Marker:
for
(;;) {
my
$marker
=
$nextMarker
;
my
$segDataPt
=
$nextSegDataPt
;
my
$segPos
=
$nextSegPos
;
undef
$nextMarker
;
undef
$nextSegDataPt
;
unless
(
$marker
and (
$marker
==0xd9 or (
$marker
==0xda and not
$wantPreview
))) {
my
$buff
;
$raf
->ReadLine(
$buff
) or
last
;
for
(;;) {
$raf
->Read(
$ch
, 1) or
last
Marker;
$nextMarker
=
ord
(
$ch
);
last
unless
$nextMarker
== 0xff;
}
if
((
$nextMarker
& 0xf0) == 0xc0 and
(
$nextMarker
== 0xc0 or
$nextMarker
& 0x03))
{
last
unless
$raf
->Read(
$buff
, 7) == 7;
$nextSegDataPt
= \
$buff
;
}
elsif
(
$nextMarker
!=0xd9 and
$nextMarker
!=0x00 and
$nextMarker
!=0x01 and
(
$nextMarker
<0xd0 or
$nextMarker
>0xd7))
{
last
unless
$raf
->Read(
$s
, 2) == 2;
my
$len
=
unpack
(
'n'
,
$s
);
last
unless
defined
(
$len
) and
$len
>= 2;
$nextSegPos
=
$raf
->Tell();
$len
-= 2;
last
unless
$raf
->Read(
$buff
,
$len
) ==
$len
;
$nextSegDataPt
= \
$buff
;
}
next
unless
defined
$marker
;
}
my
$hdr
=
"\xff"
.
chr
(
$marker
);
my
$markerName
= JpegMarkerName(
$marker
);
if
((
$marker
& 0xf0) == 0xc0 and (
$marker
== 0xc0 or
$marker
& 0x03)) {
$verbose
and
print
$out
"JPEG $markerName:\n"
;
my
(
$h
,
$w
) =
unpack
(
'n'
x2,
substr
(
$$segDataPt
, 3));
$self
->FoundTag(
'ImageWidth'
,
$w
);
$self
->FoundTag(
'ImageHeight'
,
$h
);
next
;
}
elsif
(
$marker
== 0xd9) {
$verbose
and
print
$out
"JPEG EOI\n"
;
my
$pos
=
$raf
->Tell();
if
(
$htmlDump
and
$dumpEnd
) {
$self
->HtmlDump(
$dumpEnd
,
$pos
-2-
$dumpEnd
,
'[JPEG Image Data]'
,
undef
, 0x08);
$self
->HtmlDump(
$pos
-2, 2,
'JPEG EOI'
,
undef
);
$dumpEnd
= 0;
}
$success
= 1;
if
(
$wantPreview
and
$self
->{VALUE}->{PreviewImageStart}) {
my
$buff
;
if
(
$raf
->Read(
$buff
, 1024) and (
$buff
=~ /\xff\xd8\xff./g or
(
$self
->{CameraMake} =~ /Minolta/i and
$buff
=~ /.\xd8\xff\xdb/g)))
{
my
$start
=
$self
->{VALUE}->{PreviewImageStart};
my
$actual
=
$pos
+
pos
(
$buff
) - 4;
if
(
$start
ne
$actual
and
$verbose
> 1) {
print
$out
"(Fixed PreviewImage location: $start -> $actual)\n"
;
}
$self
->{VALUE}->{PreviewImageStart} =
$actual
;
}
$raf
->Seek(
$pos
, 0);
}
my
$fromEnd
= 0;
if
(
$trailInfo
) {
$$trailInfo
{ScanForAFCP} = 1;
$self
->ProcessTrailers(
$trailInfo
);
$fromEnd
=
$$trailInfo
{Offset};
undef
$trailInfo
;
}
if
(
$verbose
or
$htmlDump
) {
$raf
->Seek(0, 2);
my
$endPos
=
$raf
->Tell() -
$fromEnd
;
$self
->DumpUnknownTrailer({
RAF
=>
$raf
,
DataPos
=>
$pos
,
DirLen
=>
$endPos
-
$pos
})
if
$endPos
>
$pos
;
}
last
;
}
elsif
(
$marker
== 0xda) {
$verbose
and
print
$out
"JPEG SOS\n"
;
unless
(
$self
->Options(
'FastScan'
)) {
$trailInfo
= IdentifyTrailer(
$raf
);
if
(
$trailInfo
and
$verbose
< 3 and not
$htmlDump
) {
$self
->ProcessTrailers(
$trailInfo
) and
undef
$trailInfo
;
}
if
(
$wantPreview
) {
my
$buff
;
my
$curPos
=
$raf
->Tell();
if
(
$raf
->Seek(
$self
->GetValue(
'PreviewImageStart'
), 0) and
$raf
->Read(
$buff
, 4) == 4 and
$buff
=~ /^.\xd8\xff[\xc4\xdb\xe0-\xef]/)
{
undef
$wantPreview
;
}
$raf
->Seek(
$curPos
, 0) or
last
;
}
next
if
$trailInfo
or
$wantPreview
or
$verbose
> 2 or
$htmlDump
;
}
$success
= 1;
last
;
}
elsif
(
$marker
==0x00 or
$marker
==0x01 or (
$marker
>=0xd0 and
$marker
<=0xd7)) {
$verbose
and
$marker
and
print
$out
"JPEG $markerName:\n"
;
next
;
}
my
$dumpType
=
''
;
$length
=
length
(
$$segDataPt
);
if
(
$verbose
) {
print
$out
"JPEG $markerName ($length bytes):\n"
;
if
(
$verbose
> 2) {
my
%extraParms
= (
Addr
=>
$segPos
);
$extraParms
{MaxLen} = 128
if
$verbose
== 4;
HexDump(
$segDataPt
,
undef
,
%dumpParms
,
%extraParms
);
}
}
if
(
$marker
== 0xe0) {
if
(
$$segDataPt
=~ /^JFIF\0/) {
$dumpType
=
'JFIF'
;
my
%dirInfo
= (
DataPt
=>
$segDataPt
,
DataPos
=>
$segPos
,
DirStart
=> 5,
DirLen
=>
$length
- 5,
);
SetByteOrder(
'MM'
);
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::JFIF::Main'
);
$self
->ProcessDirectory(\
%dirInfo
,
$tagTablePtr
);
}
elsif
(
$$segDataPt
=~ /^JFXX\0\x10/) {
$dumpType
=
'JFXX'
;
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::JFIF::Extension'
);
my
$tagInfo
=
$self
->GetTagInfo(
$tagTablePtr
, 0x10);
$self
->FoundTag(
$tagInfo
,
substr
(
$$segDataPt
, 6));
}
elsif
(
$$segDataPt
=~ /^(II|MM).{4}HEAPJPGM/s) {
$dumpType
=
'CIFF'
;
my
%dirInfo
= (
RAF
=> new File::RandomAccess(
$segDataPt
),
);
$self
->{SET_GROUP1} =
'CIFF'
;
Image::ExifTool::CanonRaw::ProcessCRW(
$self
, \
%dirInfo
);
delete
$self
->{SET_GROUP1};
}
}
elsif
(
$marker
== 0xe1) {
if
(
$$segDataPt
=~ /^Exif\0/) {
my
$hdrLen
=
length
(
$exifAPP1hdr
);
my
%dirInfo
= (
Parent
=>
$markerName
,
DataPt
=>
$segDataPt
,
DataPos
=>
$segPos
,
DirStart
=>
$hdrLen
,
Base
=>
$segPos
+
$hdrLen
,
);
if
(
$htmlDump
) {
$self
->HtmlDump(
$segPos
-4, 4,
'APP1 header'
,
"Data size: $length bytes"
);
$self
->HtmlDump(
$segPos
,
$hdrLen
,
'Exif header'
,
'APP1 data type: Exif'
);
$dumpEnd
=
$segPos
+
$length
;
undef
$dumpType
;
}
$self
->ProcessTIFF(\
%dirInfo
);
my
$start
=
$self
->GetValue(
'PreviewImageStart'
);
my
$length
=
$self
->GetValue(
'PreviewImageLength'
);
if
(
$start
and
$length
and
$start
+
$length
>
$self
->{EXIF_POS} +
length
(
$self
->{EXIF_DATA}) and
$self
->{REQ_TAG_LOOKUP}->{previewimage})
{
$wantPreview
= 1;
}
}
else
{
my
$processed
;
if
(
$$segDataPt
=~ /^http/ or
$$segDataPt
=~ /<exif:/) {
$dumpType
=
'XMP'
;
my
$start
= (
$$segDataPt
=~ /^
$xmpAPP1hdr
/) ?
length
(
$xmpAPP1hdr
) : 0;
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::XMP::Main'
);
my
%dirInfo
= (
Base
=> 0,
DataPt
=>
$segDataPt
,
DataPos
=>
$segPos
,
DataLen
=>
$length
,
DirStart
=>
$start
,
DirLen
=>
$length
-
$start
,
Parent
=>
$markerName
,
);
$processed
=
$self
->ProcessDirectory(\
%dirInfo
,
$tagTablePtr
);
}
if
(
$verbose
and not
$processed
) {
$self
->Warn(
"Ignored EXIF block length $length (bad header)"
);
}
}
}
elsif
(
$marker
== 0xe2) {
if
(
$$segDataPt
=~ /^ICC_PROFILE\0/) {
$dumpType
=
'ICC_Profile'
;
my
$block_num
=
ord
(
substr
(
$$segDataPt
, 12, 1));
my
$blocks_tot
=
ord
(
substr
(
$$segDataPt
, 13, 1));
$icc_profile
=
''
if
$block_num
== 1;
if
(
defined
$icc_profile
) {
$icc_profile
.=
substr
(
$$segDataPt
, 14);
if
(
$block_num
==
$blocks_tot
) {
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::ICC_Profile::Main'
);
my
%dirInfo
= (
DataPt
=> \
$icc_profile
,
DataPos
=>
$segPos
+ 14,
DataLen
=>
length
(
$icc_profile
),
DirStart
=> 0,
DirLen
=>
length
(
$icc_profile
),
Parent
=>
$markerName
,
);
$self
->ProcessDirectory(\
%dirInfo
,
$tagTablePtr
);
undef
$icc_profile
;
}
}
}
elsif
(
$$segDataPt
=~ /^FPXR\0/) {
$dumpType
=
'FPXR'
;
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::FlashPix::Main'
);
my
%dirInfo
= (
DataPt
=>
$segDataPt
,
DataPos
=>
$segPos
,
DataLen
=>
$length
,
DirStart
=> 0,
DirLen
=>
$length
,
Parent
=>
$markerName
,
LastFPXR
=> not (
$nextMarker
==
$marker
and
$$nextSegDataPt
=~/^FPXR\0/),
);
$self
->ProcessDirectory(\
%dirInfo
,
$tagTablePtr
);
}
}
elsif
(
$marker
== 0xe3) {
if
(
$$segDataPt
=~ /^(Meta|META|Exif)\0\0/) {
my
%dirInfo
= (
Parent
=>
$markerName
,
DataPt
=>
$segDataPt
,
DataPos
=>
$segPos
,
DirStart
=> 6,
Base
=>
$segPos
+ 6,
);
if
(
$htmlDump
) {
$self
->HtmlDump(
$segPos
-4, 10,
'APP3 Meta header'
);
$dumpEnd
=
$segPos
+
$length
;
undef
$dumpType
;
}
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::Kodak::Meta'
);
$self
->ProcessTIFF(\
%dirInfo
,
$tagTablePtr
);
}
}
elsif
(
$marker
== 0xe5) {
if
(
$$segDataPt
=~ /^RMETA\0/) {
$dumpType
=
'Ricoh RMETA'
;
my
%dirInfo
= (
Parent
=>
$markerName
,
DataPt
=>
$segDataPt
,
DataPos
=>
$segPos
,
DirStart
=> 6,
Base
=>
$segPos
+ 6,
);
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::Ricoh::RMETA'
);
$self
->ProcessDirectory(\
%dirInfo
,
$tagTablePtr
);
}
}
elsif
(
$marker
== 0xe6) {
if
(
$$segDataPt
=~ /^EPPIM\0/) {
my
%dirInfo
= (
Parent
=>
$markerName
,
DataPt
=>
$segDataPt
,
DataPos
=>
$segPos
,
DirStart
=> 6,
Base
=>
$segPos
+ 6,
);
if
(
$htmlDump
) {
$self
->HtmlDump(
$segPos
-4, 10,
'APP6 EPPIM header'
);
$dumpEnd
=
$segPos
+
$length
;
undef
$dumpType
;
}
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::JPEG::APP6'
);
$self
->ProcessTIFF(\
%dirInfo
,
$tagTablePtr
);
}
}
elsif
(
$marker
== 0xe8) {
if
(
$$segDataPt
=~ /^SPIFF\0/ and
$length
== 32) {
$dumpType
=
'SPIFF'
;
my
%dirInfo
= (
DataPt
=>
$segDataPt
,
DataPos
=>
$segPos
,
DirStart
=> 6,
DirLen
=>
$length
- 6,
);
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::JPEG::APP8'
);
$self
->ProcessDirectory(\
%dirInfo
,
$tagTablePtr
);
}
}
elsif
(
$marker
== 0xea) {
if
(
$$segDataPt
=~ /^UNICODE\0/) {
my
$comment
=
$self
->Unicode2Byte(
substr
(
$$segDataPt
,8),
'MM'
);
$self
->FoundTag(
'Comment'
,
$comment
);
}
}
elsif
(
$marker
== 0xec) {
if
(
$$segDataPt
=~ /^Ducky\0/) {
$dumpType
=
'Ducky'
;
my
%dirInfo
= (
DataPt
=>
$segDataPt
,
DataPos
=>
$segPos
,
DirStart
=> 5,
DirLen
=>
$length
- 5,
);
SetByteOrder(
'MM'
);
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::APP12::Ducky'
);
$self
->ProcessDirectory(\
%dirInfo
,
$tagTablePtr
);
}
else
{
my
%dirInfo
= (
DataPt
=>
$segDataPt
);
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::APP12::Main'
);
$self
->ProcessDirectory(\
%dirInfo
,
$tagTablePtr
) and
$dumpType
=
'Picture Info'
;
}
}
elsif
(
$marker
== 0xed) {
my
$isOld
;
if
(
$$segDataPt
=~ /^
$psAPP13hdr
/ or (
$$segDataPt
=~ /^
$psAPP13old
/ and
$isOld
=1)) {
$dumpType
=
'Photoshop'
;
if
(
defined
$combinedSegData
) {
$combinedSegData
.=
substr
(
$$segDataPt
,
length
(
$psAPP13hdr
));
$segDataPt
= \
$combinedSegData
;
$length
=
length
$combinedSegData
;
}
if
(
$nextMarker
==
$marker
and
$$nextSegDataPt
=~ /^
$psAPP13hdr
/) {
$combinedSegData
=
$$segDataPt
unless
defined
$combinedSegData
;
next
;
}
my
$hdrlen
=
$isOld
? 27 : 14;
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::Photoshop::Main'
);
my
%dirInfo
= (
DataPt
=>
$segDataPt
,
DataPos
=>
$segPos
,
DataLen
=>
$length
,
DirStart
=>
$hdrlen
,
DirLen
=>
$length
-
$hdrlen
,
Parent
=>
$markerName
,
);
$self
->ProcessDirectory(\
%dirInfo
,
$tagTablePtr
);
undef
$combinedSegData
;
}
elsif
(
$$segDataPt
=~ /^\x1c\x02/) {
}
else
{
$self
->Warn(
'Unknown APP13 data'
);
}
}
elsif
(
$marker
== 0xee) {
if
(
$$segDataPt
=~ /^Adobe/) {
$dumpType
=
'Adobe'
;
SetByteOrder(
'MM'
);
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::JPEG::APP14'
);
my
%dirInfo
= (
DataPt
=>
$segDataPt
,
DataPos
=>
$segPos
,
DirStart
=> 5,
DirLen
=>
$length
- 5,
);
$self
->ProcessDirectory(\
%dirInfo
,
$tagTablePtr
);
}
}
elsif
(
$marker
== 0xef) {
if
(
$$segDataPt
=~ /^Q\s*(\d+)/ and
$length
== 4) {
$dumpType
=
'GraphicConverter'
;
my
$tagTablePtr
= GetTagTable(
'Image::ExifTool::JPEG::APP15'
);
$self
->HandleTag(
$tagTablePtr
,
'Q'
, $1);
}
}
elsif
(
$marker
== 0xfe) {
$self
->FoundTag(
'Comment'
,
$$segDataPt
);
}
elsif
((
$marker
& 0xf0) != 0xe0) {
undef
$dumpType
;
}
if
(
$htmlDump
and
defined
$dumpType
) {
my
$desc
=
$markerName
. (
$dumpType
?
" $dumpType"
:
''
) .
' segment'
;
$self
->HtmlDump(
$segPos
-4,
$length
+4,
$desc
,
undef
, 0x08);
$dumpEnd
=
$segPos
+
$length
;
}
undef
$$segDataPt
;
}
$/ =
$oldsep
;
$success
or
$self
->Warn(
'JPEG format error'
);
return
1;
}
sub
ProcessTIFF($$;$)
{
my
(
$self
,
$dirInfo
,
$tagTablePtr
) =
@_
;
my
$dataPt
=
$$dirInfo
{DataPt};
my
$fileType
=
$$dirInfo
{Parent} ||
''
;
my
$raf
=
$$dirInfo
{RAF};
my
$base
=
$$dirInfo
{Base} || 0;
my
$outfile
=
$$dirInfo
{OutFile};
my
(
$length
,
$err
,
$canonSig
);
if
(
$raf
) {
if
(
$outfile
) {
$raf
->Seek(0, 0) or
return
0;
if
(
$base
) {
$raf
->Read(
$$dataPt
,
$base
) ==
$base
or
return
0;
Write(
$outfile
,
$$dataPt
) or
$err
= 1;
}
}
else
{
$raf
->Seek(
$base
, 0) or
return
0;
}
$raf
->Read(
$self
->{EXIF_DATA}, 8) == 8 or
return
0;
}
elsif
(
$dataPt
) {
my
$dirStart
=
$$dirInfo
{DirStart} || 0;
$self
->{EXIF_DATA} =
substr
(${
$$dirInfo
{DataPt}},
$dirStart
);
}
elsif
(
$outfile
) {
$self
->{EXIF_DATA} =
"MM\0\x2a\0\0\0\x08"
;
}
else
{
$self
->{EXIF_DATA} =
''
;
}
$self
->{FIRST_EXIF_POS} =
$base
unless
defined
$self
->{FIRST_EXIF_POS};
$self
->{EXIF_POS} =
$base
;
$dataPt
= \
$self
->{EXIF_DATA};
SetByteOrder(
substr
(
$$dataPt
,0,2)) or
return
0;
$self
->{EXIF_BYTE_ORDER} = GetByteOrder();
my
$identifier
= Get16u(
$dataPt
, 2);
my
$offset
= Get32u(
$dataPt
, 4);
$offset
>= 8 or
return
0;
if
(
$self
->{HTML_DUMP}) {
my
$o
= (GetByteOrder() eq
'II'
) ?
'Little'
:
'Big'
;
$self
->HtmlDump(
$base
, 4,
"TIFF header"
,
"Byte order: $o endian"
, 0);
$self
->HtmlDump(
$base
+4, 4,
"IFD0 pointer"
,
sprintf
(
"Offset: 0x%.4x"
,
$offset
), 0);
}
if
(
$raf
) {
if
(
$identifier
== 0x2a and
$offset
>= 16) {
$raf
->Read(
$canonSig
, 8) == 8 or
return
0;
$$dataPt
.=
$canonSig
;
if
(
$canonSig
=~ /^CR\x02\0/) {
$fileType
=
'CR2'
;
}
else
{
undef
$canonSig
;
}
}
elsif
(
$identifier
== 0x55 and
$fileType
=~ /^(RAW|TIFF)$/) {
$fileType
=
'RAW'
;
$tagTablePtr
= GetTagTable(
'Image::ExifTool::Panasonic::Raw'
);
}
elsif
(Get8u(
$dataPt
, 2) == 0xbc and
$fileType
eq
'TIFF'
) {
$fileType
=
'WDP'
;
}
if
(
$fileType
and not
$self
->{VALUE}->{FileType}) {
$self
->SetFileType(
$fileType
);
}
}
$self
->{TIFF_TYPE} =
$fileType
;
$tagTablePtr
or
$tagTablePtr
= GetTagTable(
'Image::ExifTool::Exif::Main'
);
my
%dirInfo
= (
Base
=>
$base
,
DataPt
=>
$dataPt
,
DataLen
=>
length
$$dataPt
,
DataPos
=> 0,
DirStart
=>
$offset
,
DirLen
=>
length
$$dataPt
,
RAF
=>
$raf
,
DirName
=>
'IFD0'
,
Parent
=>
$fileType
,
ImageData
=> 1,
);
unless
(
$outfile
) {
$self
->ProcessDirectory(\
%dirInfo
,
$tagTablePtr
);
if
(
$self
->{VALUE}->{GeoTiffDirectory}) {
Image::ExifTool::GeoTiff::ProcessGeoTiff(
$self
);
}
if
(
$raf
) {
my
$trailInfo
= IdentifyTrailer(
$raf
);
if
(
$trailInfo
) {
$$trailInfo
{ScanForAFCP} = 1;
$self
->ProcessTrailers(
$trailInfo
);
}
}
return
1;
}
if
(
$$dirInfo
{NoTiffEnd}) {
delete
$self
->{TIFF_END};
}
else
{
$self
->{TIFF_END} = 0;
}
if
(
$canonSig
) {
$dirInfo
{OutFile} =
$outfile
;
Image::ExifTool::CanonRaw::WriteCR2(
$self
, \
%dirInfo
,
$tagTablePtr
) or
$err
= 1;
}
else
{
$dirInfo
{NewDataPos} = 8;
$dirInfo
{PreserveImagePadding} = 1
if
$fileType
eq
'ORF'
or
$identifier
!= 0x2a;
my
$newData
=
$self
->WriteDirectory(\
%dirInfo
,
$tagTablePtr
);
if
(not
defined
$newData
) {
$err
= 1;
}
elsif
(
length
(
$newData
)) {
my
$offset
= 8;
my
$header
=
substr
(
$$dataPt
, 0, 4) . Set32u(
$offset
);
Write(
$outfile
,
$header
,
$newData
) or
$err
= 1;
undef
$newData
;
}
if
(
ref
$dirInfo
{ImageData} and not
$err
) {
$self
->CopyImageData(
$dirInfo
{ImageData},
$outfile
) or
$err
= 1;
delete
$dirInfo
{ImageData};
}
}
if
(
$raf
and
$self
->{TIFF_END} and not
$err
) {
my
(
$buf
,
$trailInfo
);
$raf
->Seek(0, 2) or
$err
= 1;
my
$extra
=
$raf
->Tell() -
$self
->{TIFF_END};
for
(;;) {
last
unless
$extra
> 12;
$raf
->Seek(
$self
->{TIFF_END});
$trailInfo
= IdentifyTrailer(
$raf
);
last
unless
$trailInfo
;
my
$tbuf
=
''
;
$$trailInfo
{OutFile} = \
$tbuf
;
$$trailInfo
{ScanForAFCP} = 1;
unless
(
$self
->ProcessTrailers(
$trailInfo
)) {
undef
$trailInfo
;
$err
= 1;
last
;
}
$extra
=
$$trailInfo
{DataPos} -
$self
->{TIFF_END};
last
;
}
if
(
$extra
> 0 and
$self
->{TIFF_END} & 0x01) {
$raf
->Seek(
$self
->{TIFF_END}, 0) or
$err
= 1;
$raf
->Read(
$buf
, 1) or
$err
= 1;
$buf
eq
"\0"
and --
$extra
, ++
$self
->{TIFF_END};
}
if
(
$extra
> 0) {
if
(
$self
->{DEL_GROUP}->{Trailer}) {
$self
->VPrint(0,
" Deleting trailer ($extra bytes)\n"
);
++
$self
->{CHANGED};
}
else
{
$self
->VPrint(0,
" Preserving unknown trailer ($extra bytes)\n"
);
$raf
->Seek(
$self
->{TIFF_END}, 0) or
$err
= 1;
while
(
$extra
) {
my
$n
=
$extra
< 65536 ?
$extra
: 65536;
$raf
->Read(
$buf
,
$n
) ==
$n
or
$err
= 1,
last
;
Write(
$outfile
,
$buf
) or
$err
= 1,
last
;
$extra
-=
$n
;
}
}
}
$self
->WriteTrailerBuffer(
$trailInfo
,
$outfile
) or
$err
= 1
if
$trailInfo
;
}
delete
$self
->{TIFF_END};
return
$err
? -1 : 1;
}
sub
TagTableKeys($)
{
local
$_
;
my
$tagTablePtr
=
shift
;
my
@keyList
;
foreach
(
keys
%$tagTablePtr
) {
push
(
@keyList
,
$_
)
unless
$specialTags
{
$_
};
}
return
@keyList
;
}
sub
GetTagTable($)
{
my
$tableName
=
shift
or
return
undef
;
my
$table
=
$allTables
{
$tableName
};
unless
(
$table
) {
no
strict
'refs'
;
unless
(
defined
%$tableName
) {
if
(
$tableName
=~ /(.*)::/) {
my
$module
= $1;
unless
(
eval
"require $module"
) {
$@ and
warn
$@;
}
}
unless
(
defined
%$tableName
) {
warn
"Can't find table $tableName\n"
;
return
undef
;
}
}
no
strict
'refs'
;
$table
= \
%$tableName
;
my
$defaultGroups
=
$$table
{GROUPS};
$defaultGroups
or
$defaultGroups
=
$$table
{GROUPS} = { };
unless
(
$$defaultGroups
{0} and
$$defaultGroups
{1}) {
if
(
$tableName
=~ /Image::.*?::([^:]*)/) {
$$defaultGroups
{0} = $1
unless
$$defaultGroups
{0};
$$defaultGroups
{1} = $1
unless
$$defaultGroups
{1};
}
else
{
$$defaultGroups
{0} =
$tableName
unless
$$defaultGroups
{0};
$$defaultGroups
{1} =
$tableName
unless
$$defaultGroups
{1};
}
}
$$defaultGroups
{2} =
'Other'
unless
$$defaultGroups
{2};
unless
(
$$table
{TAG_PREFIX}) {
my
$tagPrefix
;
if
(
$tableName
=~ /Image::.*?::(.*)::Main/ ||
$tableName
=~ /Image::.*?::(.*)/) {
(
$tagPrefix
= $1) =~ s/::/_/g;
}
else
{
$tagPrefix
=
$tableName
;
}
$$table
{TAG_PREFIX} =
$tagPrefix
;
}
SetupTagTable(
$table
);
if
(
defined
%UserDefined
and
$UserDefined
{
$tableName
}) {
my
$tagID
;
foreach
$tagID
(TagTableKeys(
$UserDefined
{
$tableName
})) {
my
$tagInfo
=
$UserDefined
{
$tableName
}->{
$tagID
};
if
(
ref
$tagInfo
eq
'HASH'
) {
$$tagInfo
{Name} or
$$tagInfo
{Name} =
ucfirst
(
$tagID
);
}
else
{
$tagInfo
= {
Name
=>
$tagInfo
};
}
if
(
$$table
{WRITABLE} and not
defined
$$tagInfo
{Writable} and
not
$$tagInfo
{SubDirectory})
{
$$tagInfo
{Writable} =
$$table
{WRITABLE};
}
delete
$$table
{
$tagID
};
AddTagToTable(
$table
,
$tagID
,
$tagInfo
);
}
}
GenerateTagIDs(
$table
)
if
$didTagID
;
push
@tableOrder
,
$tableName
;
$allTables
{
$tableName
} =
$table
;
}
return
$table
;
}
sub
ProcessDirectory($$$;$)
{
my
(
$self
,
$dirInfo
,
$tagTablePtr
,
$processProc
) =
@_
;
return
0
unless
$tagTablePtr
and
$dirInfo
;
$processProc
or
$processProc
=
$$tagTablePtr
{PROCESS_PROC};
$$dirInfo
{DirName} or
$$dirInfo
{DirName} =
$tagTablePtr
->{GROUPS}->{0};
if
(
defined
$$dirInfo
{DirStart} and
defined
$$dirInfo
{DataPos}) {
my
$addr
=
$$dirInfo
{DirStart} +
$$dirInfo
{DataPos} + (
$$dirInfo
{Base}||0);
if
(
$self
->{PROCESSED}->{
$addr
}) {
$self
->Warn(
"$$dirInfo{DirName} pointer references previous $self->{PROCESSED}->{$addr} directory"
);
return
0;
}
$self
->{PROCESSED}->{
$addr
} =
$$dirInfo
{DirName};
}
$processProc
or
$processProc
= \
&Image::ExifTool::Exif::ProcessExif
;
my
$oldOrder
= GetByteOrder();
my
$oldIndent
=
$self
->{INDENT};
my
$oldDir
=
$self
->{DIR_NAME};
$self
->{INDENT} .=
'| '
;
$self
->{DIR_NAME} =
$$dirInfo
{DirName};
my
$rtnVal
=
&$processProc
(
$self
,
$dirInfo
,
$tagTablePtr
);
$self
->{INDENT} =
$oldIndent
;
$self
->{DIR_NAME} =
$oldDir
;
SetByteOrder(
$oldOrder
);
return
$rtnVal
;
}
sub
GetFileExtension($)
{
my
$filename
=
shift
;
my
$fileExt
;
if
(
$filename
and
$filename
=~ /.*\.(.+)$/) {
$fileExt
=
uc
($1);
$fileExt
eq
'TIF'
and
$fileExt
=
'TIFF'
;
}
return
$fileExt
;
}
sub
GetTagInfoList($$)
{
my
(
$tagTablePtr
,
$tagID
) =
@_
;
my
$tagInfo
=
$$tagTablePtr
{
$tagID
};
if
(
ref
$tagInfo
eq
'HASH'
) {
return
(
$tagInfo
);
}
elsif
(
ref
$tagInfo
eq
'ARRAY'
) {
return
@$tagInfo
;
}
elsif
(
$tagInfo
) {
$tagInfo
=
$$tagTablePtr
{
$tagID
} = {
Name
=>
$tagInfo
};
return
(
$tagInfo
);
}
return
();
}
sub
GetTagInfo($$$;$)
{
my
(
$self
,
$tagTablePtr
,
$tagID
,
$valPt
) =
@_
;
my
@infoArray
= GetTagInfoList(
$tagTablePtr
,
$tagID
);
my
$tagInfo
;
foreach
$tagInfo
(
@infoArray
) {
my
$condition
=
$$tagInfo
{Condition};
if
(
$condition
) {
return
''
if
$condition
=~ /\
$valPt
\b/ and not
$valPt
;
my
$oldVal
=
$self
->{VALUE}->{
$$tagInfo
{Name}};
unless
(
eval
$condition
) {
$@ and
warn
"Condition $$tagInfo{Name}: $@"
;
next
;
}
}
if
(
$$tagInfo
{Unknown} and not
$self
->{OPTIONS}->{Unknown}) {
return
undef
;
}
return
$tagInfo
;
}
if
(not
$tagInfo
and
$self
->{OPTIONS}->{Unknown} and
$tagID
=~ /^\d+$/) {
my
$printConv
;
if
(
defined
$$tagTablePtr
{PRINT_CONV}) {
$printConv
=
$$tagTablePtr
{PRINT_CONV};
}
else
{
$printConv
=
'length($val) > 60 ? substr($val,0,55) . "[...]" : $val'
;
}
my
$hex
=
sprintf
(
"0x%.4x"
,
$tagID
);
my
$prefix
=
$$tagTablePtr
{TAG_PREFIX};
$tagInfo
= {
Name
=>
"${prefix}_$hex"
,
Description
=> MakeDescription(
$prefix
,
$hex
),
Unknown
=> 1,
Writable
=> 0,
PrintConv
=>
$printConv
,
};
AddTagToTable(
$tagTablePtr
,
$tagID
,
$tagInfo
);
}
else
{
undef
$tagInfo
;
}
return
$tagInfo
;
}
sub
AddTagToTable($$$)
{
my
(
$tagTablePtr
,
$tagID
,
$tagInfo
) =
@_
;
if
(
$$tagInfo
{Groups}) {
foreach
(
keys
%{
$$tagTablePtr
{GROUPS}}) {
next
if
$tagInfo
->{Groups}->{
$_
};
$tagInfo
->{Groups}->{
$_
} =
$tagTablePtr
->{GROUPS}->{
$_
};
}
}
else
{
$$tagInfo
{Groups} =
$$tagTablePtr
{GROUPS};
}
$$tagInfo
{Flags} and ExpandFlags(
$tagInfo
);
$$tagInfo
{GotGroups} = 1,
$$tagInfo
{Table} =
$tagTablePtr
;
$$tagInfo
{TagID} =
$tagID
;
unless
(
$$tagInfo
{Name}) {
my
$prefix
=
$$tagTablePtr
{TAG_PREFIX};
$$tagInfo
{Name} =
"${prefix}_$tagID"
;
$$tagInfo
{Description} = MakeDescription(
$prefix
,
$tagID
);
}
$$tagTablePtr
{
$tagID
} =
$tagInfo
unless
defined
$$tagTablePtr
{
$tagID
};
}
sub
HandleTag($$$$;%)
{
my
(
$self
,
$tagTablePtr
,
$tag
,
$val
,
%parms
) =
@_
;
my
$verbose
=
$self
->{OPTIONS}->{Verbose};
my
$tagInfo
=
$parms
{TagInfo} ||
$self
->GetTagInfo(
$tagTablePtr
,
$tag
);
my
$dataPt
=
$parms
{DataPt};
my
$subdir
;
if
(
$tagInfo
) {
$subdir
=
$$tagInfo
{SubDirectory}
}
else
{
return
undef
unless
$verbose
;
}
unless
(
defined
$val
or
$subdir
) {
my
$start
=
$parms
{Start} || 0;
my
$size
=
$parms
{Size} || 0;
if
(
$dataPt
and
$start
>= 0 and
$start
+
$size
<=
length
(
$$dataPt
)) {
$val
=
substr
(
$$dataPt
,
$start
,
$size
);
}
else
{
my
$name
=
$tagInfo
?
$$tagInfo
{Name} :
"tag $tag"
;
$self
->Warn(
"Error extracting value for $name"
);
return
undef
;
}
}
if
(
$verbose
) {
$parms
{Value} =
$val
;
$parms
{Table} =
$tagTablePtr
;
$self
->VerboseInfo(
$tag
,
$tagInfo
,
%parms
);
}
if
(
$tagInfo
) {
if
(
$subdir
) {
$dataPt
or
$dataPt
= \
$val
;
my
%dirInfo
= (
DirName
=>
$$tagInfo
{Name},
DataPt
=>
$dataPt
,
DataLen
=>
length
$$dataPt
,
DataPos
=>
$parms
{DataPos},
DirStart
=>
$parms
{Start},
DirLen
=>
$parms
{Size},
Parent
=>
$parms
{Parent},
);
my
$subTablePtr
= GetTagTable(
$$subdir
{TagTable}) ||
$tagTablePtr
;
$self
->ProcessDirectory(\
%dirInfo
,
$subTablePtr
,
$$subdir
{ProcessProc});
}
else
{
return
$self
->FoundTag(
$tagInfo
,
$val
);
}
}
return
undef
;
}
sub
FoundTag($$$)
{
local
$_
;
my
(
$self
,
$tagInfo
,
$value
) =
@_
;
my
$tag
;
if
(
ref
$tagInfo
eq
'HASH'
) {
$tag
=
$$tagInfo
{Name} or
warn
(
"No tag name\n"
),
return
undef
;
}
else
{
$tag
=
$tagInfo
;
$tagInfo
=
$self
->GetTagInfo(GetTagTable(
'Image::ExifTool::Extra'
),
$tag
);
$tagInfo
or
$tagInfo
= {
Name
=>
$tag
,
Groups
=> \
%allGroupsExifTool
};
$self
->{OPTIONS}->{Verbose} and
$self
->VerboseInfo(
undef
,
$tagInfo
,
Value
=>
$value
);
}
my
$rawValueHash
=
$self
->{VALUE};
if
(
$$tagInfo
{RawConv}) {
my
$conv
=
$$tagInfo
{RawConv};
my
$val
=
$value
;
my
@val
;
if
(
ref
$val
eq
'HASH'
) {
foreach
(
keys
%$val
) {
$val
[
$_
] =
$$rawValueHash
{
$$val
{
$_
}}; }
}
if
(
ref
(
$conv
) eq
'CODE'
) {
$value
=
&$conv
(
$val
,
$self
);
}
else
{
$value
=
eval
$conv
;
$@ and
warn
"RawConv: $@\n"
;
}
return
undef
unless
defined
$value
;
}
my
$priority
=
$$tagInfo
{Priority};
defined
$priority
or
$priority
=
$tagInfo
->{Table}->{PRIORITY};
if
(
defined
$rawValueHash
->{
$tag
}) {
if
(
$$tagInfo
{List} and
$tagInfo
eq
$self
->{TAG_INFO}->{
$tag
} and
not
$self
->{NO_LIST})
{
if
(
ref
$rawValueHash
->{
$tag
} ne
'ARRAY'
) {
$rawValueHash
->{
$tag
} = [
$rawValueHash
->{
$tag
} ];
}
push
@{
$rawValueHash
->{
$tag
}},
$value
;
return
$tag
;
}
my
$nextTag
= NextTagKey(
$rawValueHash
,
$tag
);
my
$oldPriority
=
$self
->{PRIORITY}->{
$tag
} || 1;
$priority
= 1
if
not
defined
$priority
or
(
$priority
== 0 and
$self
->{DIR_NAME} and
$self
->{PRIORITY_DIR} and
$self
->{DIR_NAME} eq
$self
->{PRIORITY_DIR});
if
(
$priority
>=
$oldPriority
) {
$self
->{MOVED_KEY} =
$nextTag
;
$self
->{PRIORITY}->{
$nextTag
} =
$self
->{PRIORITY}->{
$tag
};
$rawValueHash
->{
$nextTag
} =
$rawValueHash
->{
$tag
};
$self
->{FILE_ORDER}->{
$nextTag
} =
$self
->{FILE_ORDER}->{
$tag
};
$self
->{TAG_INFO}->{
$nextTag
} =
$self
->{TAG_INFO}->{
$tag
};
if
(
$self
->{GROUP1}->{
$tag
}) {
$self
->{GROUP1}->{
$nextTag
} =
$self
->{GROUP1}->{
$tag
};
delete
$self
->{GROUP1}->{
$tag
};
}
}
else
{
$tag
=
$nextTag
;
}
$self
->{PRIORITY}->{
$tag
} =
$priority
;
}
elsif
(
$priority
) {
$self
->{PRIORITY}->{
$tag
} =
$priority
;
}
$rawValueHash
->{
$tag
} =
$value
;
$self
->{FILE_ORDER}->{
$tag
} = ++
$self
->{NUM_FOUND};
$self
->{TAG_INFO}->{
$tag
} =
$tagInfo
;
$self
->{GROUP1}->{
$tag
} =
$self
->{SET_GROUP1}
if
$self
->{SET_GROUP1};
return
$tag
;
}
sub
NextTagKey($$)
{
my
(
$info
,
$tag
) =
@_
;
if
(
exists
$$info
{
$tag
}) {
my
$name
=
$tag
;
my
$i
;
for
(
$i
=1; ; ++
$i
) {
$tag
=
"$name ($i)"
;
last
unless
exists
$$info
{
$tag
};
}
}
return
$tag
;
}
sub
SetPriorityDir($)
{
my
$self
=
shift
;
$self
->{PRIORITY_DIR} =
$self
->{DIR_NAME}
unless
$self
->{PRIORITY_DIR};
}
sub
SetGroup1($$$)
{
my
(
$self
,
$tagKey
,
$extra
) =
@_
;
$self
->{GROUP1}->{
$tagKey
} =
$extra
;
}
sub
GenerateTagIDs($)
{
my
$table
=
shift
;
unless
(
$$table
{DID_TAG_ID}) {
$$table
{DID_TAG_ID} = 1;
my
(
$tagID
,
$tagInfo
);
foreach
$tagID
(
keys
%$table
) {
next
if
$specialTags
{
$tagID
};
my
@infoArray
= GetTagInfoList(
$table
,
$tagID
);
foreach
$tagInfo
(
@infoArray
) {
$$tagInfo
{TagID} =
$tagID
;
}
}
}
}
sub
GenerateAllTagIDs()
{
unless
(
$didTagID
) {
my
$tableName
;
foreach
$tableName
(
keys
%allTables
) {
GenerateTagIDs(
$allTables
{
$tableName
});
}
$didTagID
= 1;
}
}
sub
DeleteTag($$)
{
my
(
$self
,
$tag
) =
@_
;
delete
$self
->{VALUE}->{
$tag
};
delete
$self
->{FILE_ORDER}->{
$tag
};
delete
$self
->{TAG_INFO}->{
$tag
};
delete
$self
->{GROUP1}->{
$tag
};
}
sub
SetFileType($;$)
{
my
$self
=
shift
;
my
$baseType
=
$self
->{FILE_TYPE};
my
$fileType
=
shift
||
$baseType
;
my
$mimeType
=
$mimeType
{
$fileType
};
$mimeType
=
$mimeType
{
$baseType
}
unless
$mimeType
or
$baseType
eq
'TIFF'
;
$self
->FoundTag(
'FileType'
,
$fileType
);
$self
->FoundTag(
'MIMEType'
,
$mimeType
||
'application/unknown'
);
}
sub
ModifyMimeType($;$)
{
my
(
$self
,
$mime
) =
@_
;
$mime
=~ m{/} or
$mime
=
$mimeType
{
$mime
} or
return
;
my
$old
=
$self
->{VALUE}->{MIMEType};
if
(
defined
$old
) {
my
(
$a
,
$b
) =
split
'/'
,
$old
;
my
(
$c
,
$d
) =
split
'/'
,
$mime
;
$d
=~ s/^x-//;
$self
->{VALUE}->{MIMEType} =
"$c/$b-$d"
;
$self
->VPrint(0,
" Modified MIMEType = $c/$b-$d\n"
);
}
else
{
$self
->FoundTag(
'MIMEType'
,
$mime
);
}
}
sub
VPrint($$@)
{
my
$self
=
shift
;
my
$level
=
shift
;
if
(
$self
->{OPTIONS}->{Verbose} and
$self
->{OPTIONS}->{Verbose} >
$level
) {
my
$out
=
$self
->{OPTIONS}->{TextOut};
print
$out
@_
;
}
}
sub
VerboseDump($$;%)
{
my
$self
=
shift
;
my
$dataPt
=
shift
;
if
(
$self
->{OPTIONS}->{Verbose} and
$self
->{OPTIONS}->{Verbose} > 2) {
HexDump(
$dataPt
,
undef
,
Out
=>
$self
->{OPTIONS}->{TextOut},
MaxLen
=>
$self
->{OPTIONS}->{Verbose} < 4 ? 96 :
undef
,
@_
);
}
}
sub
ExtractBinary($$$;$)
{
my
(
$self
,
$offset
,
$length
,
$tag
) =
@_
;
if
(
$tag
and not
$self
->{OPTIONS}->{Binary} and
not
$self
->{REQ_TAG_LOOKUP}->{
lc
(
$tag
)})
{
return
"Binary data $length bytes"
;
}
my
$buff
;
unless
(
$self
->{RAF}->Seek(
$offset
,0)
and
$self
->{RAF}->Read(
$buff
,
$length
) ==
$length
)
{
$tag
or
$tag
=
'binary data'
;
$self
->Warn(
"Error reading $tag from file"
);
return
undef
;
}
return
$buff
;
}
sub
ProcessBinaryData($$$)
{
my
(
$self
,
$dirInfo
,
$tagTablePtr
) =
@_
;
my
$dataPt
=
$$dirInfo
{DataPt};
my
$offset
=
$$dirInfo
{DirStart} || 0;
my
$size
=
$$dirInfo
{DirLen} || (
length
(
$$dataPt
) -
$offset
);
my
$base
=
$$dirInfo
{Base} || 0;
my
$verbose
=
$self
->{OPTIONS}->{Verbose};
my
$unknown
=
$self
->{OPTIONS}->{Unknown};
my
$dataPos
;
my
$defaultFormat
=
$$tagTablePtr
{FORMAT} ||
'int8u'
;
my
$increment
=
$formatSize
{
$defaultFormat
};
unless
(
$increment
) {
warn
"Unknown format $defaultFormat\n"
;
$defaultFormat
=
'int8u'
;
$increment
=
$formatSize
{
$defaultFormat
};
}
my
@tags
;
if
(
$unknown
> 1 and
defined
$$tagTablePtr
{FIRST_ENTRY}) {
@tags
= (
$$tagTablePtr
{FIRST_ENTRY}..(
int
(
$size
/
$increment
) - 1));
}
elsif
(
$$dirInfo
{DataMember}) {
@tags
= @{
$$dirInfo
{DataMember}};
$verbose
= 0;
}
else
{
@tags
=
sort
{
$a
<=>
$b
} TagTableKeys(
$tagTablePtr
);
}
if
(
$verbose
) {
$self
->VerboseDir(
'BinaryData'
,
undef
,
$size
);
$dataPos
=
$$dirInfo
{DataPos} || 0;
}
my
$index
;
my
$nextIndex
= 0;
my
%val
;
foreach
$index
(
@tags
) {
my
$tagInfo
;
if
(
$$tagTablePtr
{
$index
}) {
$tagInfo
=
$self
->GetTagInfo(
$tagTablePtr
,
$index
) or
next
;
next
if
$$tagInfo
{Unknown} and
$$tagInfo
{Unknown} >
$unknown
;
}
else
{
next
unless
$unknown
> 1;
next
if
$index
<
$nextIndex
;
$tagInfo
=
$self
->GetTagInfo(
$tagTablePtr
,
$index
) or
next
;
$$tagInfo
{Unknown} = 2;
}
my
$count
= 1;
my
$format
=
$$tagInfo
{Format};
my
$entry
=
$index
*
$increment
;
if
(
$format
) {
if
(
$format
=~ /(.*)\[(.*)\]/) {
$format
= $1;
$count
= $2;
$count
=
eval
$count
;
$@ and
warn
(
"Format $$tagInfo{Name}: $@"
),
next
;
next
if
$count
< 0;
}
elsif
(
$format
eq
'string'
) {
$count
= (
$size
>
$entry
) ?
$size
-
$entry
: 0;
}
}
else
{
$format
=
$defaultFormat
;
}
if
(
$unknown
> 1) {
$nextIndex
=
$index
+ (
$formatSize
{
$format
} *
$count
) /
$increment
;
}
my
$val
= ReadValue(
$dataPt
,
$entry
+
$offset
,
$format
,
$count
,
$size
-
$entry
);
next
unless
defined
$val
;
if
(
$verbose
) {
my
$len
=
$count
* (
$formatSize
{
$format
} || 1);
$len
>
$size
-
$entry
and
$len
=
$size
-
$entry
;
$self
->VerboseInfo(
$index
,
$tagInfo
,
Table
=>
$tagTablePtr
,
Value
=>
$val
,
DataPt
=>
$dataPt
,
Size
=>
$len
,
Start
=>
$entry
+
$offset
,
Addr
=>
$entry
+
$offset
+
$base
+
$dataPos
,
Format
=>
$format
,
Count
=>
$count
,
);
}
$val
+=
$base
if
$$tagInfo
{IsOffset};
$val
{
$index
} =
$val
;
$self
->FoundTag(
$tagInfo
,
$val
);
}
return
1;
}
unless
(
$Image::ExifTool::noConfig
) {
my
$config
=
'.ExifTool_config'
;
my
$home
=
$ENV
{EXIFTOOL_HOME} ||
$ENV
{HOME} ||
(
$ENV
{HOMEDRIVE} ||
''
) . (
$ENV
{HOMEPATH} ||
''
) ||
'.'
;
my
$file
=
"$home/$config"
;
-r
$file
or
$file
= ($0 =~ /(.*[\\\/])/ ? $1 :
'./'
) .
$config
;
if
(-r
$file
) {
eval
"require '$file'"
;
$@ and
$_
=$@, s/Compilation failed.*//s,
warn
$_
;
}
}
1;