911 lines
30 KiB
Perl
911 lines
30 KiB
Perl
|
#------------------------------------------------------------------------------
|
||
|
# File: CanonRaw.pm
|
||
|
#
|
||
|
# Description: Read Canon RAW (CRW) meta information
|
||
|
#
|
||
|
# Revisions: 11/25/2003 - P. Harvey Created
|
||
|
# 12/02/2003 - P. Harvey Completely reworked and figured out many
|
||
|
# more tags
|
||
|
#
|
||
|
# References: 1) http://www.cybercom.net/~dcoffin/dcraw/
|
||
|
# 2) http://www.wonderland.org/crw/
|
||
|
# 3) http://xyrion.org/ciff/CIFFspecV1R04.pdf
|
||
|
# 4) Dave Nicholson private communication (PowerShot S30)
|
||
|
#------------------------------------------------------------------------------
|
||
|
|
||
|
package Image::ExifTool::CanonRaw;
|
||
|
|
||
|
use strict;
|
||
|
use vars qw($VERSION $AUTOLOAD %crwTagFormat);
|
||
|
use Image::ExifTool qw(:DataAccess :Utils);
|
||
|
use Image::ExifTool::Exif;
|
||
|
use Image::ExifTool::Canon;
|
||
|
|
||
|
$VERSION = '1.58';
|
||
|
|
||
|
sub WriteCRW($$);
|
||
|
sub ProcessCanonRaw($$$);
|
||
|
sub WriteCanonRaw($$$);
|
||
|
sub CheckCanonRaw($$$);
|
||
|
sub InitMakerNotes($);
|
||
|
sub SaveMakerNotes($);
|
||
|
sub BuildMakerNotes($$$$$$);
|
||
|
|
||
|
# formats for CRW tag types (($tag >> 8) & 0x38)
|
||
|
# Note: don't define format for undefined types
|
||
|
%crwTagFormat = (
|
||
|
0x00 => 'int8u',
|
||
|
0x08 => 'string',
|
||
|
0x10 => 'int16u',
|
||
|
0x18 => 'int32u',
|
||
|
# 0x20 => 'undef',
|
||
|
# 0x28 => 'undef',
|
||
|
# 0x30 => 'undef',
|
||
|
);
|
||
|
|
||
|
# Canon raw file tag table
|
||
|
# Note: Tag ID's have upper 2 bits set to zero, since these 2 bits
|
||
|
# just specify the location of the information
|
||
|
%Image::ExifTool::CanonRaw::Main = (
|
||
|
GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
|
||
|
PROCESS_PROC => \&ProcessCanonRaw,
|
||
|
WRITE_PROC => \&WriteCanonRaw,
|
||
|
CHECK_PROC => \&CheckCanonRaw,
|
||
|
WRITABLE => 1,
|
||
|
0x0000 => { Name => 'NullRecord', Writable => 'undef' }, #3
|
||
|
0x0001 => { #3
|
||
|
Name => 'FreeBytes',
|
||
|
Format => 'undef',
|
||
|
Binary => 1,
|
||
|
},
|
||
|
0x0032 => { Name => 'CanonColorInfo1', Writable => 0 },
|
||
|
0x0805 => [
|
||
|
# this tag is found in more than one directory...
|
||
|
{
|
||
|
Condition => '$self->{DIR_NAME} eq "ImageDescription"',
|
||
|
Name => 'CanonFileDescription',
|
||
|
Writable => 'string[32]',
|
||
|
},
|
||
|
{
|
||
|
Name => 'UserComment',
|
||
|
Writable => 'string[256]',
|
||
|
},
|
||
|
],
|
||
|
0x080a => {
|
||
|
Name => 'CanonRawMakeModel',
|
||
|
Writable => 0,
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::CanonRaw::MakeModel' },
|
||
|
},
|
||
|
0x080b => { Name => 'CanonFirmwareVersion', Writable => 'string[32]' },
|
||
|
0x080c => { Name => 'ComponentVersion', Writable => 'string' }, #3
|
||
|
0x080d => { Name => 'ROMOperationMode', Writable => 'string[8]' }, #3
|
||
|
0x0810 => { Name => 'OwnerName', Writable => 'string[32]' },
|
||
|
0x0815 => { Name => 'CanonImageType', Writable => 'string[32]' },
|
||
|
0x0816 => { Name => 'OriginalFileName', Writable => 'string[32]' },
|
||
|
0x0817 => { Name => 'ThumbnailFileName', Writable => 'string[32]' },
|
||
|
0x100a => { #3
|
||
|
Name => 'TargetImageType',
|
||
|
Writable => 'int16u',
|
||
|
PrintConv => {
|
||
|
0 => 'Real-world Subject',
|
||
|
1 => 'Written Document',
|
||
|
},
|
||
|
},
|
||
|
0x1010 => { #3
|
||
|
Name => 'ShutterReleaseMethod',
|
||
|
Writable => 'int16u',
|
||
|
PrintConv => {
|
||
|
0 => 'Single Shot',
|
||
|
2 => 'Continuous Shooting',
|
||
|
},
|
||
|
},
|
||
|
0x1011 => { #3
|
||
|
Name => 'ShutterReleaseTiming',
|
||
|
Writable => 'int16u',
|
||
|
PrintConv => {
|
||
|
0 => 'Priority on shutter',
|
||
|
1 => 'Priority on focus',
|
||
|
},
|
||
|
},
|
||
|
0x1016 => { Name => 'ReleaseSetting', Writable => 'int16u' }, #3
|
||
|
0x101c => { Name => 'BaseISO', Writable => 'int16u' }, #3
|
||
|
0x1028=> { #PH
|
||
|
Name => 'CanonFlashInfo',
|
||
|
Writable => 'int16u',
|
||
|
Count => 4,
|
||
|
Unknown => 1,
|
||
|
},
|
||
|
0x1029 => {
|
||
|
Name => 'CanonFocalLength',
|
||
|
Writable => 0,
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::Canon::FocalLength' },
|
||
|
},
|
||
|
0x102a => {
|
||
|
Name => 'CanonShotInfo',
|
||
|
Writable => 0,
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::Canon::ShotInfo' },
|
||
|
},
|
||
|
0x102c => {
|
||
|
Name => 'CanonColorInfo2',
|
||
|
Writable => 0,
|
||
|
# for the S30, the following information has been decoded: (ref 4)
|
||
|
# offset 66: int32u - shutter half press time in ms
|
||
|
# offset 70: int32u - image capture time in ms
|
||
|
# offset 74: int16u - custom white balance flag (0=Off, 512=On)
|
||
|
},
|
||
|
0x102d => {
|
||
|
Name => 'CanonCameraSettings',
|
||
|
Writable => 0,
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::Canon::CameraSettings' },
|
||
|
},
|
||
|
0x1030 => { #4
|
||
|
Name => 'WhiteSample',
|
||
|
Writable => 0,
|
||
|
SubDirectory => {
|
||
|
Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)',
|
||
|
TagTable => 'Image::ExifTool::CanonRaw::WhiteSample',
|
||
|
},
|
||
|
},
|
||
|
0x1031 => {
|
||
|
Name => 'SensorInfo',
|
||
|
Writable => 0,
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::Canon::SensorInfo' },
|
||
|
},
|
||
|
# this tag has only be verified for the 10D in CRW files, but the D30 and D60
|
||
|
# also produce CRW images and have CustomFunction information in their JPEG's
|
||
|
0x1033 => [
|
||
|
{
|
||
|
Name => 'CustomFunctions10D',
|
||
|
Condition => '$self->{Model} =~ /EOS 10D/',
|
||
|
SubDirectory => {
|
||
|
Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)',
|
||
|
TagTable => 'Image::ExifTool::CanonCustom::Functions10D',
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
Name => 'CustomFunctionsD30',
|
||
|
Condition => '$self->{Model} =~ /EOS D30\b/',
|
||
|
SubDirectory => {
|
||
|
Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)',
|
||
|
TagTable => 'Image::ExifTool::CanonCustom::FunctionsD30',
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
Name => 'CustomFunctionsD60',
|
||
|
Condition => '$self->{Model} =~ /EOS D60\b/',
|
||
|
SubDirectory => {
|
||
|
# the stored size in the D60 apparently doesn't include the size word:
|
||
|
Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size-2,$size)',
|
||
|
# (D60 custom functions are basically the same as D30)
|
||
|
TagTable => 'Image::ExifTool::CanonCustom::FunctionsD30',
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
Name => 'CustomFunctionsUnknown',
|
||
|
SubDirectory => {
|
||
|
Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)',
|
||
|
TagTable => 'Image::ExifTool::CanonCustom::FuncsUnknown',
|
||
|
},
|
||
|
},
|
||
|
],
|
||
|
0x1038 => {
|
||
|
Name => 'CanonAFInfo',
|
||
|
Writable => 0,
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::Canon::AFInfo' },
|
||
|
},
|
||
|
0x1093 => {
|
||
|
Name => 'CanonFileInfo',
|
||
|
SubDirectory => {
|
||
|
Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)',
|
||
|
TagTable => 'Image::ExifTool::Canon::FileInfo',
|
||
|
},
|
||
|
},
|
||
|
0x10a9 => {
|
||
|
Name => 'ColorBalance',
|
||
|
Writable => 0,
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::Canon::ColorBalance' },
|
||
|
},
|
||
|
0x10b5 => { #PH
|
||
|
Name => 'RawJpgInfo',
|
||
|
SubDirectory => {
|
||
|
Validate => 'Image::ExifTool::Canon::Validate($dirData,$subdirStart,$size)',
|
||
|
TagTable => 'Image::ExifTool::CanonRaw::RawJpgInfo',
|
||
|
},
|
||
|
},
|
||
|
0x10ae => {
|
||
|
Name => 'ColorTemperature',
|
||
|
Writable => 'int16u',
|
||
|
},
|
||
|
0x10b4 => {
|
||
|
Name => 'ColorSpace',
|
||
|
Writable => 'int16u',
|
||
|
PrintConv => {
|
||
|
1 => 'sRGB',
|
||
|
2 => 'Adobe RGB',
|
||
|
0xffff => 'Uncalibrated',
|
||
|
},
|
||
|
},
|
||
|
0x1803 => { #3
|
||
|
Name => 'ImageFormat',
|
||
|
Writable => 0,
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::CanonRaw::ImageFormat' },
|
||
|
},
|
||
|
0x1804 => { Name => 'RecordID', Writable => 'int32u' }, #3
|
||
|
0x1806 => { #3
|
||
|
Name => 'SelfTimerTime',
|
||
|
Writable => 'int32u',
|
||
|
ValueConv => '$val / 1000',
|
||
|
ValueConvInv => '$val * 1000',
|
||
|
PrintConv => '"$val s"',
|
||
|
PrintConvInv => '$val=~s/\s*s.*//;$val',
|
||
|
},
|
||
|
0x1807 => {
|
||
|
Name => 'TargetDistanceSetting',
|
||
|
Format => 'float',
|
||
|
PrintConv => '"$val mm"',
|
||
|
PrintConvInv => '$val=~s/\s*mm$//;$val',
|
||
|
},
|
||
|
0x180b => [
|
||
|
{
|
||
|
# D30
|
||
|
Name => 'SerialNumber',
|
||
|
Condition => '$$self{Model} =~ /EOS D30\b/',
|
||
|
Writable => 'int32u',
|
||
|
PrintConv => 'sprintf("%x-%.5d",$val>>16,$val&0xffff)',
|
||
|
PrintConvInv => '$val=~/(.*)-(\d+)/ ? (hex($1)<<16)+$2 : undef',
|
||
|
},
|
||
|
{
|
||
|
# all EOS models (D30, 10D, 300D)
|
||
|
Name => 'SerialNumber',
|
||
|
Condition => '$$self{Model} =~ /EOS/',
|
||
|
Writable => 'int32u',
|
||
|
PrintConv => 'sprintf("%.10d",$val)',
|
||
|
PrintConvInv => '$val',
|
||
|
},
|
||
|
{
|
||
|
# this is not SerialNumber for PowerShot models (but what is it?) - PH
|
||
|
Name => 'UnknownNumber',
|
||
|
Unknown => 1,
|
||
|
},
|
||
|
],
|
||
|
0x180e => {
|
||
|
Name => 'TimeStamp',
|
||
|
Writable => 0,
|
||
|
SubDirectory => {
|
||
|
TagTable => 'Image::ExifTool::CanonRaw::TimeStamp',
|
||
|
},
|
||
|
},
|
||
|
0x1810 => {
|
||
|
Name => 'ImageInfo',
|
||
|
Writable => 0,
|
||
|
SubDirectory => {
|
||
|
TagTable => 'Image::ExifTool::CanonRaw::ImageInfo',
|
||
|
},
|
||
|
},
|
||
|
0x1813 => { #3
|
||
|
Name => 'FlashInfo',
|
||
|
Writable => 0,
|
||
|
SubDirectory => {
|
||
|
TagTable => 'Image::ExifTool::CanonRaw::FlashInfo',
|
||
|
},
|
||
|
},
|
||
|
0x1814 => { #3
|
||
|
Name => 'MeasuredEV',
|
||
|
Notes => q{
|
||
|
this is the Canon name for what could better be called MeasuredLV, and
|
||
|
should be close to the calculated LightValue for a proper exposure with most
|
||
|
models
|
||
|
},
|
||
|
Format => 'float',
|
||
|
ValueConv => '$val + 5',
|
||
|
ValueConvInv => '$val - 5',
|
||
|
},
|
||
|
0x1817 => {
|
||
|
Name => 'FileNumber',
|
||
|
Writable => 'int32u',
|
||
|
Groups => { 2 => 'Image' },
|
||
|
PrintConv => '$_=$val;s/(\d+)(\d{4})/$1-$2/;$_',
|
||
|
PrintConvInv => '$_=$val;s/-//;$_',
|
||
|
},
|
||
|
0x1818 => { #3
|
||
|
Name => 'ExposureInfo',
|
||
|
Groups => { 1 => 'CIFF' }, # (only so CIFF shows up in group lists)
|
||
|
Writable => 0,
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::CanonRaw::ExposureInfo' },
|
||
|
},
|
||
|
0x1834 => { #PH
|
||
|
Name => 'CanonModelID',
|
||
|
Writable => 'int32u',
|
||
|
PrintHex => 1,
|
||
|
Notes => q{
|
||
|
this is the complete list of model ID numbers, but note that many of these
|
||
|
models do not produce CRW images
|
||
|
},
|
||
|
SeparateTable => 'Canon CanonModelID',
|
||
|
PrintConv => \%Image::ExifTool::Canon::canonModelID,
|
||
|
},
|
||
|
0x1835 => {
|
||
|
Name => 'DecoderTable',
|
||
|
Writable => 0,
|
||
|
SubDirectory => { TagTable => 'Image::ExifTool::CanonRaw::DecoderTable' },
|
||
|
},
|
||
|
0x183b => { #PH
|
||
|
# display format for serial number
|
||
|
Name => 'SerialNumberFormat',
|
||
|
Writable => 'int32u',
|
||
|
PrintHex => 1,
|
||
|
PrintConv => {
|
||
|
0x90000000 => 'Format 1',
|
||
|
0xa0000000 => 'Format 2',
|
||
|
},
|
||
|
},
|
||
|
0x2005 => {
|
||
|
Name => 'RawData',
|
||
|
Writable => 0,
|
||
|
Binary => 1,
|
||
|
},
|
||
|
0x2007 => {
|
||
|
Name => 'JpgFromRaw',
|
||
|
Groups => { 2 => 'Preview' },
|
||
|
Writable => 'resize', # 'resize' allows this value to change size
|
||
|
Permanent => 0,
|
||
|
RawConv => '$self->ValidateImage(\$val,$tag)',
|
||
|
},
|
||
|
0x2008 => {
|
||
|
Name => 'ThumbnailImage',
|
||
|
Groups => { 2 => 'Preview' },
|
||
|
Writable => 'resize', # 'resize' allows this value to change size
|
||
|
WriteCheck => '$self->CheckImage(\$val)',
|
||
|
Permanent => 0,
|
||
|
RawConv => '$self->ValidateImage(\$val,$tag)',
|
||
|
},
|
||
|
# the following entries are subdirectories
|
||
|
# (any 0x28 and 0x30 tag types are handled automatically by the decoding logic)
|
||
|
0x2804 => {
|
||
|
Name => 'ImageDescription',
|
||
|
SubDirectory => { },
|
||
|
Writable => 0,
|
||
|
},
|
||
|
0x2807 => { #3
|
||
|
Name => 'CameraObject',
|
||
|
SubDirectory => { },
|
||
|
Writable => 0,
|
||
|
},
|
||
|
0x3002 => { #3
|
||
|
Name => 'ShootingRecord',
|
||
|
SubDirectory => { },
|
||
|
Writable => 0,
|
||
|
},
|
||
|
0x3003 => { #3
|
||
|
Name => 'MeasuredInfo',
|
||
|
SubDirectory => { },
|
||
|
Writable => 0,
|
||
|
},
|
||
|
0x3004 => { #3
|
||
|
Name => 'CameraSpecification',
|
||
|
SubDirectory => { },
|
||
|
Writable => 0,
|
||
|
},
|
||
|
0x300a => { #3
|
||
|
Name => 'ImageProps',
|
||
|
SubDirectory => { },
|
||
|
Writable => 0,
|
||
|
},
|
||
|
0x300b => {
|
||
|
Name => 'ExifInformation',
|
||
|
SubDirectory => { },
|
||
|
Writable => 0,
|
||
|
},
|
||
|
);
|
||
|
|
||
|
# Canon binary data blocks
|
||
|
%Image::ExifTool::CanonRaw::MakeModel = (
|
||
|
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||
|
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||
|
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||
|
DATAMEMBER => [ 0, 6 ], # indices of data members to extract when writing
|
||
|
WRITABLE => 1,
|
||
|
FORMAT => 'string',
|
||
|
GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
|
||
|
# (can't specify a first entry because this isn't
|
||
|
# a simple binary table with fixed offsets)
|
||
|
0 => {
|
||
|
Name => 'Make',
|
||
|
Format => 'string[6]', # "Canon\0"
|
||
|
DataMember => 'Make',
|
||
|
RawConv => '$self->{Make} = $val',
|
||
|
},
|
||
|
6 => {
|
||
|
Name => 'Model',
|
||
|
Format => 'string', # no size = to the end of the data
|
||
|
Description => 'Camera Model Name',
|
||
|
DataMember => 'Model',
|
||
|
RawConv => '$self->{Model} = $val',
|
||
|
},
|
||
|
);
|
||
|
|
||
|
%Image::ExifTool::CanonRaw::TimeStamp = (
|
||
|
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||
|
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||
|
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||
|
WRITABLE => 1,
|
||
|
FORMAT => 'int32u',
|
||
|
FIRST_ENTRY => 0,
|
||
|
GROUPS => { 0 => 'MakerNotes', 2 => 'Time' },
|
||
|
0 => {
|
||
|
Name => 'DateTimeOriginal',
|
||
|
Description => 'Date/Time Original',
|
||
|
Shift => 'Time',
|
||
|
ValueConv => 'ConvertUnixTime($val)',
|
||
|
ValueConvInv => 'GetUnixTime($val)',
|
||
|
PrintConv => '$self->ConvertDateTime($val)',
|
||
|
PrintConvInv => '$self->InverseDateTime($val)',
|
||
|
},
|
||
|
1 => { #3
|
||
|
Name => 'TimeZoneCode',
|
||
|
Format => 'int32s',
|
||
|
ValueConv => '$val / 3600',
|
||
|
ValueConvInv => '$val * 3600',
|
||
|
},
|
||
|
2 => { #3
|
||
|
Name => 'TimeZoneInfo',
|
||
|
Notes => 'set to 0x80000000 if TimeZoneCode is valid',
|
||
|
},
|
||
|
);
|
||
|
|
||
|
%Image::ExifTool::CanonRaw::ImageFormat = (
|
||
|
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||
|
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||
|
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||
|
WRITABLE => 1,
|
||
|
FORMAT => 'int32u',
|
||
|
FIRST_ENTRY => 0,
|
||
|
GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
|
||
|
0 => {
|
||
|
Name => 'FileFormat',
|
||
|
Flags => 'PrintHex',
|
||
|
PrintConv => {
|
||
|
0x00010000 => 'JPEG (lossy)',
|
||
|
0x00010002 => 'JPEG (non-quantization)',
|
||
|
0x00010003 => 'JPEG (lossy/non-quantization toggled)',
|
||
|
0x00020001 => 'CRW',
|
||
|
},
|
||
|
},
|
||
|
1 => {
|
||
|
Name => 'TargetCompressionRatio',
|
||
|
Format => 'float',
|
||
|
},
|
||
|
);
|
||
|
|
||
|
%Image::ExifTool::CanonRaw::RawJpgInfo = (
|
||
|
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||
|
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||
|
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||
|
WRITABLE => 1,
|
||
|
FORMAT => 'int16u',
|
||
|
FIRST_ENTRY => 1,
|
||
|
GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
|
||
|
# 0 => 'RawJpgInfoSize',
|
||
|
1 => { #PH
|
||
|
Name => 'RawJpgQuality',
|
||
|
PrintConv => {
|
||
|
1 => 'Economy',
|
||
|
2 => 'Normal',
|
||
|
3 => 'Fine',
|
||
|
5 => 'Superfine',
|
||
|
},
|
||
|
},
|
||
|
2 => { #PH
|
||
|
Name => 'RawJpgSize',
|
||
|
PrintConv => {
|
||
|
0 => 'Large',
|
||
|
1 => 'Medium',
|
||
|
2 => 'Small',
|
||
|
},
|
||
|
},
|
||
|
3 => 'RawJpgWidth', #PH
|
||
|
4 => 'RawJpgHeight', #PH
|
||
|
);
|
||
|
|
||
|
%Image::ExifTool::CanonRaw::FlashInfo = (
|
||
|
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||
|
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||
|
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||
|
WRITABLE => 1,
|
||
|
FORMAT => 'float',
|
||
|
FIRST_ENTRY => 0,
|
||
|
GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
|
||
|
0 => 'FlashGuideNumber',
|
||
|
1 => 'FlashThreshold',
|
||
|
);
|
||
|
|
||
|
%Image::ExifTool::CanonRaw::ExposureInfo = (
|
||
|
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||
|
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||
|
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||
|
WRITABLE => 1,
|
||
|
FORMAT => 'float',
|
||
|
FIRST_ENTRY => 0,
|
||
|
GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
|
||
|
0 => 'ExposureCompensation',
|
||
|
1 => {
|
||
|
Name => 'ShutterSpeedValue',
|
||
|
ValueConv => 'abs($val)<100 ? 1/(2**$val) : 0',
|
||
|
ValueConvInv => '$val>0 ? -log($val)/log(2) : -100',
|
||
|
PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
|
||
|
PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)',
|
||
|
},
|
||
|
2 => {
|
||
|
Name => 'ApertureValue',
|
||
|
ValueConv => '2 ** ($val / 2)',
|
||
|
ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0',
|
||
|
PrintConv => 'sprintf("%.1f",$val)',
|
||
|
PrintConvInv => '$val',
|
||
|
},
|
||
|
);
|
||
|
|
||
|
%Image::ExifTool::CanonRaw::ImageInfo = (
|
||
|
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||
|
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||
|
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||
|
FORMAT => 'int32u',
|
||
|
FIRST_ENTRY => 0,
|
||
|
GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
|
||
|
# Note: Don't make these writable (except rotation) because it confuses
|
||
|
# Canon decoding software if the are changed
|
||
|
0 => 'ImageWidth', #3
|
||
|
1 => 'ImageHeight', #3
|
||
|
2 => { #3
|
||
|
Name => 'PixelAspectRatio',
|
||
|
Format => 'float',
|
||
|
},
|
||
|
3 => {
|
||
|
Name => 'Rotation',
|
||
|
Format => 'int32s',
|
||
|
Writable => 'int32s',
|
||
|
},
|
||
|
4 => 'ComponentBitDepth', #3
|
||
|
5 => 'ColorBitDepth', #3
|
||
|
6 => 'ColorBW', #3
|
||
|
);
|
||
|
|
||
|
# ref 4
|
||
|
%Image::ExifTool::CanonRaw::DecoderTable = (
|
||
|
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||
|
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||
|
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||
|
GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
|
||
|
FORMAT => 'int32u',
|
||
|
FIRST_ENTRY => 0,
|
||
|
0 => 'DecoderTableNumber',
|
||
|
2 => 'CompressedDataOffset',
|
||
|
3 => 'CompressedDataLength',
|
||
|
);
|
||
|
|
||
|
# ref 1/4
|
||
|
%Image::ExifTool::CanonRaw::WhiteSample = (
|
||
|
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
||
|
WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
|
||
|
CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
|
||
|
GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
|
||
|
FORMAT => 'int16u',
|
||
|
FIRST_ENTRY => 1,
|
||
|
1 => 'WhiteSampleWidth',
|
||
|
2 => 'WhiteSampleHeight',
|
||
|
3 => 'WhiteSampleLeftBorder',
|
||
|
4 => 'WhiteSampleTopBorder',
|
||
|
5 => 'WhiteSampleBits',
|
||
|
# this is followed by the encrypted white sample values (ref 1)
|
||
|
);
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# AutoLoad our writer routines when necessary
|
||
|
#
|
||
|
sub AUTOLOAD
|
||
|
{
|
||
|
return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_);
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# Process Raw file directory
|
||
|
# Inputs: 0) ExifTool object reference
|
||
|
# 1) directory information reference, 2) tag table reference
|
||
|
# Returns: 1 on success
|
||
|
sub ProcessCanonRaw($$$)
|
||
|
{
|
||
|
my ($et, $dirInfo, $rawTagTable) = @_;
|
||
|
my $blockStart = $$dirInfo{DirStart};
|
||
|
my $blockSize = $$dirInfo{DirLen};
|
||
|
my $raf = $$dirInfo{RAF} or return 0;
|
||
|
my $buff;
|
||
|
my $verbose = $et->Options('Verbose');
|
||
|
my $buildMakerNotes = $et->Options('MakerNotes');
|
||
|
|
||
|
# 4 bytes at end of block give directory position within block
|
||
|
$raf->Seek($blockStart+$blockSize-4, 0) or return 0;
|
||
|
$raf->Read($buff, 4) == 4 or return 0;
|
||
|
my $dirOffset = Get32u(\$buff,0) + $blockStart;
|
||
|
$raf->Seek($dirOffset, 0) or return 0;
|
||
|
$raf->Read($buff, 2) == 2 or return 0;
|
||
|
my $entries = Get16u(\$buff,0); # get number of entries in directory
|
||
|
# read the directory (10 bytes per entry)
|
||
|
$raf->Read($buff, 10 * $entries) == 10 * $entries or return 0;
|
||
|
|
||
|
$verbose and $et->VerboseDir('CIFF', $entries);
|
||
|
my $index;
|
||
|
for ($index=0; $index<$entries; ++$index) {
|
||
|
my $pt = 10 * $index;
|
||
|
my $tag = Get16u(\$buff, $pt);
|
||
|
my $size = Get32u(\$buff, $pt+2);
|
||
|
my $valuePtr = Get32u(\$buff, $pt+6);
|
||
|
my $ptr = $valuePtr + $blockStart; # all pointers relative to block start
|
||
|
if ($tag & 0x8000) {
|
||
|
$et->Warn('Bad CRW directory entry');
|
||
|
return 1;
|
||
|
}
|
||
|
my $tagID = $tag & 0x3fff; # get tag ID
|
||
|
my $tagType = ($tag >> 8) & 0x38; # get tag type
|
||
|
my $valueInDir = ($tag & 0x4000); # flag for value in directory
|
||
|
my $tagInfo = $et->GetTagInfo($rawTagTable, $tagID);
|
||
|
if (($tagType==0x28 or $tagType==0x30) and not $valueInDir) {
|
||
|
# this type of tag specifies a raw subdirectory
|
||
|
my $name;
|
||
|
$tagInfo and $name = $$tagInfo{Name};
|
||
|
$name or $name = sprintf("CanonRaw_0x%.4x", $tag);
|
||
|
my %subdirInfo = (
|
||
|
DirName => $name,
|
||
|
DataLen => 0,
|
||
|
DirStart => $ptr,
|
||
|
DirLen => $size,
|
||
|
Nesting => $$dirInfo{Nesting} + 1,
|
||
|
RAF => $raf,
|
||
|
Parent => $$dirInfo{DirName},
|
||
|
);
|
||
|
if ($verbose) {
|
||
|
my $fakeInfo = { Name => $name, SubDirectory => { } };
|
||
|
$et->VerboseInfo($tagID, $fakeInfo,
|
||
|
'Index' => $index,
|
||
|
'Size' => $size,
|
||
|
'Start' => $ptr,
|
||
|
);
|
||
|
}
|
||
|
$et->ProcessDirectory(\%subdirInfo, $rawTagTable);
|
||
|
next;
|
||
|
}
|
||
|
my ($valueDataPos, $count, $subdir);
|
||
|
my $format = $crwTagFormat{$tagType};
|
||
|
if ($tagInfo) {
|
||
|
$subdir = $$tagInfo{SubDirectory};
|
||
|
$format = $$tagInfo{Format} if $$tagInfo{Format};
|
||
|
$count = $$tagInfo{Count};
|
||
|
}
|
||
|
# get value data
|
||
|
my ($value, $delRawConv);
|
||
|
if ($valueInDir) { # is the value data in the directory?
|
||
|
# this type of tag stores the value in the 'size' and 'ptr' fields
|
||
|
$valueDataPos = $dirOffset + $pt + 4;
|
||
|
$size = 8;
|
||
|
$value = substr($buff, $pt+2, $size);
|
||
|
# set count to 1 by default for normal values in directory
|
||
|
$count = 1 if not defined $count and $format and
|
||
|
$format ne 'string' and not $subdir;
|
||
|
} else {
|
||
|
$valueDataPos = $ptr;
|
||
|
if ($size <= 512 or ($verbose > 2 and $size <= 65536)
|
||
|
or ($tagInfo and ($$tagInfo{SubDirectory}
|
||
|
or grep(/^$$tagInfo{Name}$/i, $et->GetRequestedTags()) )))
|
||
|
{
|
||
|
# read value if size is small or specifically requested
|
||
|
# or if this is a SubDirectory
|
||
|
unless ($raf->Seek($ptr, 0) and $raf->Read($value, $size) == $size) {
|
||
|
$et->Warn(sprintf("Error reading %d bytes from 0x%x",$size,$ptr));
|
||
|
next;
|
||
|
}
|
||
|
} else {
|
||
|
$value = "Binary data $size bytes";
|
||
|
if ($tagInfo) {
|
||
|
if ($et->Options('Binary') or $verbose) {
|
||
|
# read the value anyway
|
||
|
unless ($raf->Seek($ptr, 0) and $raf->Read($value, $size) == $size) {
|
||
|
$et->Warn(sprintf("Error reading %d bytes from 0x%x",$size,$ptr));
|
||
|
next;
|
||
|
}
|
||
|
}
|
||
|
# force this to be a binary (scalar reference)
|
||
|
$$tagInfo{RawConv} = '\$val';
|
||
|
$delRawConv = 1;
|
||
|
}
|
||
|
$size = length $value;
|
||
|
undef $format;
|
||
|
}
|
||
|
}
|
||
|
# set count from tagInfo count if necessary
|
||
|
if ($format and not $count) {
|
||
|
# set count according to format and size
|
||
|
my $fnum = $Image::ExifTool::Exif::formatNumber{$format};
|
||
|
my $fsiz = $Image::ExifTool::Exif::formatSize[$fnum];
|
||
|
$count = int($size / $fsiz);
|
||
|
}
|
||
|
if ($verbose) {
|
||
|
my $val = $value;
|
||
|
$format and $val = ReadValue(\$val, 0, $format, $count, $size);
|
||
|
$et->VerboseInfo($tagID, $tagInfo,
|
||
|
Table => $rawTagTable,
|
||
|
Index => $index,
|
||
|
Value => $val,
|
||
|
DataPt => \$value,
|
||
|
DataPos => $valueDataPos,
|
||
|
Size => $size,
|
||
|
Format => $format,
|
||
|
Count => $count,
|
||
|
);
|
||
|
}
|
||
|
if ($buildMakerNotes) {
|
||
|
# build maker notes information if requested
|
||
|
BuildMakerNotes($et, $tagID, $tagInfo, \$value, $format, $count);
|
||
|
}
|
||
|
next unless defined $tagInfo;
|
||
|
|
||
|
if ($subdir) {
|
||
|
my $name = $$tagInfo{Name};
|
||
|
my $newTagTable;
|
||
|
if ($$subdir{TagTable}) {
|
||
|
$newTagTable = GetTagTable($$subdir{TagTable});
|
||
|
unless ($newTagTable) {
|
||
|
warn "Unknown tag table $$subdir{TagTable}\n";
|
||
|
next;
|
||
|
}
|
||
|
} else {
|
||
|
warn "Must specify TagTable for SubDirectory $name\n";
|
||
|
next;
|
||
|
}
|
||
|
my $subdirStart = 0;
|
||
|
#### eval Start ()
|
||
|
$subdirStart = eval $$subdir{Start} if $$subdir{Start};
|
||
|
my $dirData = \$value;
|
||
|
my %subdirInfo = (
|
||
|
Name => $name,
|
||
|
DataPt => $dirData,
|
||
|
DataLen => $size,
|
||
|
DataPos => $valueDataPos,
|
||
|
DirStart => $subdirStart,
|
||
|
DirLen => $size - $subdirStart,
|
||
|
Nesting => $$dirInfo{Nesting} + 1,
|
||
|
RAF => $raf,
|
||
|
DirName => $name,
|
||
|
Parent => $$dirInfo{DirName},
|
||
|
);
|
||
|
#### eval Validate ($dirData, $subdirStart, $size)
|
||
|
if (defined $$subdir{Validate} and not eval $$subdir{Validate}) {
|
||
|
$et->Warn("Invalid $name data");
|
||
|
} else {
|
||
|
$et->ProcessDirectory(\%subdirInfo, $newTagTable, $$subdir{ProcessProc});
|
||
|
}
|
||
|
} else {
|
||
|
# convert to specified format if necessary
|
||
|
$format and $value = ReadValue(\$value, 0, $format, $count, $size);
|
||
|
# save the information
|
||
|
$et->FoundTag($tagInfo, $value);
|
||
|
delete $$tagInfo{RawConv} if $delRawConv;
|
||
|
}
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------------------------
|
||
|
# get information from raw file
|
||
|
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
||
|
# Returns: 1 if this was a valid Canon RAW file
|
||
|
sub ProcessCRW($$)
|
||
|
{
|
||
|
my ($et, $dirInfo) = @_;
|
||
|
my ($buff, $sig);
|
||
|
my $raf = $$dirInfo{RAF};
|
||
|
my $buildMakerNotes = $et->Options('MakerNotes');
|
||
|
|
||
|
$raf->Read($buff,2) == 2 or return 0;
|
||
|
SetByteOrder($buff) or return 0;
|
||
|
$raf->Read($buff,4) == 4 or return 0;
|
||
|
$raf->Read($sig,8) == 8 or return 0; # get file signature
|
||
|
$sig =~ /^HEAP(CCDR|JPGM)/ or return 0; # validate signature
|
||
|
my $hlen = Get32u(\$buff, 0);
|
||
|
|
||
|
$raf->Seek(0, 2) or return 0; # seek to end of file
|
||
|
my $filesize = $raf->Tell() or return 0;
|
||
|
|
||
|
# initialize maker note data if building maker notes
|
||
|
$buildMakerNotes and InitMakerNotes($et);
|
||
|
|
||
|
# set the FileType tag unless already done (eg. APP0 CIFF record in JPEG image)
|
||
|
$et->SetFileType();
|
||
|
|
||
|
# build directory information for main raw directory
|
||
|
my %dirInfo = (
|
||
|
DataLen => 0,
|
||
|
DirStart => $hlen,
|
||
|
DirLen => $filesize - $hlen,
|
||
|
Nesting => 0,
|
||
|
RAF => $raf,
|
||
|
Parent => 'CRW',
|
||
|
);
|
||
|
|
||
|
# process the raw directory
|
||
|
my $rawTagTable = GetTagTable('Image::ExifTool::CanonRaw::Main');
|
||
|
my $oldIndent = $$et{INDENT};
|
||
|
$$et{INDENT} .= '| ';
|
||
|
unless (ProcessCanonRaw($et, \%dirInfo, $rawTagTable)) {
|
||
|
$et->Warn('CRW file format error');
|
||
|
}
|
||
|
$$et{INDENT} = $oldIndent;
|
||
|
|
||
|
# finish building maker notes if necessary
|
||
|
$buildMakerNotes and SaveMakerNotes($et);
|
||
|
|
||
|
# process trailers if they exist in CRW file (not in CIFF information!)
|
||
|
if ($$et{FILE_TYPE} eq 'CRW') {
|
||
|
my $trailInfo = Image::ExifTool::IdentifyTrailer($raf);
|
||
|
$et->ProcessTrailers($trailInfo) if $trailInfo;
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
1; # end
|
||
|
|
||
|
__END__
|
||
|
|
||
|
=head1 NAME
|
||
|
|
||
|
Image::ExifTool::CanonRaw - Read Canon RAW (CRW) meta information
|
||
|
|
||
|
=head1 SYNOPSIS
|
||
|
|
||
|
This module is loaded automatically by Image::ExifTool when required.
|
||
|
|
||
|
=head1 DESCRIPTION
|
||
|
|
||
|
This module contains definitions required by Image::ExifTool to interpret
|
||
|
meta information from Canon CRW raw files. These files are written directly
|
||
|
by some Canon cameras, and contain meta information similar to that found in
|
||
|
the EXIF Canon maker notes.
|
||
|
|
||
|
=head1 NOTES
|
||
|
|
||
|
The CR2 format written by some Canon cameras is very different the CRW
|
||
|
format processed by this module. (CR2 is TIFF-based and uses standard EXIF
|
||
|
tags.)
|
||
|
|
||
|
=head1 AUTHOR
|
||
|
|
||
|
Copyright 2003-2018, Phil Harvey (phil at owl.phy.queensu.ca)
|
||
|
|
||
|
This library is free software; you can redistribute it and/or modify it
|
||
|
under the same terms as Perl itself.
|
||
|
|
||
|
=head1 REFERENCES
|
||
|
|
||
|
=over 4
|
||
|
|
||
|
=item L<http://www.cybercom.net/~dcoffin/dcraw/>
|
||
|
|
||
|
=item L<http://www.wonderland.org/crw/>
|
||
|
|
||
|
=item L<http://xyrion.org/ciff/>
|
||
|
|
||
|
=item L<http://owl.phy.queensu.ca/~phil/exiftool/canon_raw.html>
|
||
|
|
||
|
=back
|
||
|
|
||
|
=head1 ACKNOWLEDGEMENTS
|
||
|
|
||
|
Thanks to Dave Nicholson for decoding a number of new tags.
|
||
|
|
||
|
=head1 SEE ALSO
|
||
|
|
||
|
L<Image::ExifTool::TagNames/CanonRaw Tags>,
|
||
|
L<Image::ExifTool::Canon(3pm)|Image::ExifTool::Canon>,
|
||
|
L<Image::ExifTool(3pm)|Image::ExifTool>
|
||
|
|
||
|
=cut
|
||
|
|