# # $Id: HiXIF.pm,v 1.8 2004/12/09 05:31:04 oneroad Exp $ # =head1 NAME HiXIF - "Hi"gemaru E"XIF" Parser =head1 SYNOPSIS get exif information use HiXIF; my $img = HiXIF->new->info($filepath); print $img->ExifImageWidth,'x',$img->ExifImageHeight; =cut =head1 DESCRIPTION HiXIF is Perl Library to get Exif Information. Most Digital Camera do not return CCD SIZE. But HiXIF.pm include some Digital Camera's Info, and you can get focallength135 (35mm film conversion) . =cut package HiXIF; $|=1; use Symbol qw(gensym); use strict; use vars qw($VERSION); $VERSION = '0.10'; use constant READLONG => 4; use constant READSHORT => 2; use constant UNSIGNED_BYTE => 1; use constant ASCII_STRINGS => 2; use constant UNSIGNED_SHORT => 3; use constant UNSIGNED_LONG => 4; use constant UNSIGNED_RATIONAL => 5; use constant SIGNED_BYTE => 6; use constant UNDEFINED => 7; use constant SIGNED_SHORT => 8; use constant SIGNED_LONG => 9; use constant SIGNED_RATIONAL => 10; use constant SINGLE_FLOAT => 11; use constant DOUBLE_FLOAT => 12; use constant LOCK_EX => 8; use constant O_RDONLY => 0; =head1 Methods =over 4 =item $img = HiXIF-Enew; =cut =item $img-Einfo($filepath); =cut sub new { my $class = shift; my $self = { fh => gensym, readlong => 'N', readshort => 'n', FILEPATH => '', ERRNO => 0, ERRMSG => '', @_, }; bless $self, $class; return $self; } sub info { my $self = shift; $self->{FILEPATH} = shift; if ( $self->{FILEPATH} eq '' ) { $self->{ERRNO} = -1; $self->{ERRMSG} = 'usage: info(filepath)'; } elsif ( ! -e $self->{FILEPATH} ) { $self->{ERRNO} = -2; $self->{ERRMSG} = 'file not found' } else { $self->_getinfo($self->{FILEPATH}); $self->_readable_info unless $self->{ERRNO}; } return $self; } # # local method start # sub _getinfo { my $self = shift; my %TAGNAME = ( '0x010e' => 'ImageDescription', '0x010f' => 'Make', '0x0110' => 'Model', '0x0112' => 'Orientation', '0x011a' => 'Xresolution', '0x011b' => 'YResolution', '0x0128' => 'ResolutionUnit', '0x0131' => 'Software', '0x0132' => 'DateTime', '0x0213' => 'YCbCrPositioning', '0x8822' => 'ExposureProgram', '0x8298' => 'Copyright', '0x829a' => 'ExposureTime', '0x829d' => 'FNumber', '0x8827' => 'ISOSpeedRatings', '0x9003' => 'DateTimeOriginal', '0x9004' => 'DateTimeDigitized', '0x9102' => 'CompressedBitsPerPixel', '0x9201' => 'ShutterSpeedValue', '0x9202' => 'ApertureValue', '0x9203' => 'BrightnessValue', '0x9204' => 'ExposureBiasValue', '0x9205' => 'MaxApertureValue', '0x9207' => 'MeteringMode', '0x9208' => 'LightSource', '0x9209' => 'Flash', '0x920a' => 'FocalLength', '0xa001' => 'ColorSpace', '0xa002' => 'ExifImageWidth', '0xa003' => 'ExifImageHeight', '0xa005' => 'InteroperabilityFDPointer', '0xa20e' => 'FocalPlaneXResolution', '0xa20f' => 'FocalPlaneYResolution', '0xa210' => 'FocalPlaneResolutionUnit', '0xa217' => 'SensingMethod', '0xa405' => 'FocalLength135', ); my %OFFSET = (); my %FORMAT = (); my %LENGTH = (); my %TAGTMP = (); my $offset = 0; my $headoffset = 0; my $tmp; if ( $self->{FILEPATH} eq '' ) { $self->{ERRNO} = -1; $self->{ERRMSG} = 'usage: info(filepath)'; return $self; } $self->{FILESIZE} = -s $self->{FILEPATH}; unless ( sysopen($self->{fh}, $self->{FILEPATH}, O_RDONLY) ) { $self->{ERRNO} = -2; $self->{ERRMSG} = 'cannot open file'; return $self; } unless ( flock($self->{fh}, LOCK_EX) ) { $self->{ERRNO} = -3; $self->{ERRMSG} = 'cannot lock file'; return $self; } # isJPEG sysread($self->{fh},$tmp,READSHORT); unless ( ord(substr($tmp,0,1)) == 0xff and ord(substr($tmp,1,1)) == 0xd8 ) { $self->{ERRNO} = -6; $self->{ERRMSG} = 'not jpeg file'; return $self; } else { while (sysread($self->{fh},$tmp,1)) { my $mark = ord($tmp); last if $mark == 0xd9; last if $mark == 0xda; if ( $mark == 0xff ) { sysread($self->{fh},$tmp,1); $mark = ord($tmp); if ( $mark == 0xc0 or $mark == 0xc2 ) { sysread($self->{fh},$tmp,7); $self->{JpegImageHeight} = unpack("n2",substr($tmp,3,2)); $self->{JpegImageWidth} = unpack("n2",substr($tmp,5,2)); last; } else { sysread($self->{fh},$tmp,2); my $size = unpack("n2",$tmp); sysread($self->{fh},$tmp,$size -2); } } } } # 10240 bytes read (adhoc) 256bytes = too short for my $i ( 0 .. 10240 ) { sysseek($self->{fh},$i+6,0); sysread($self->{fh}, $tmp, READLONG); if ( $tmp eq 'Exif' ) { $headoffset = $i; last; } } unless ( $tmp eq 'Exif' ) { $self->{ERRNO} = -4; $self->{ERRMSG} = 'not exif file'; return $self; } sysseek($self->{fh}, 2, 1); sysread($self->{fh}, $tmp, 2); if ( $tmp eq 'MM' ) { # Motorola (default) $self->{readlong} = 'N'; $self->{readshort} = 'n'; } elsif ( $tmp eq 'II' ) { # Intel $self->{readlong} = 'V'; $self->{readshort} = 'v'; } else { $self->{ERRNO} = -5; $self->{ERRMSG} = 'illegal format'; return $self; } sysseek($self->{fh}, 2, 1); $offset = $self->_myread(UNSIGNED_LONG) - 8; sysseek($self->{fh}, $offset, 1); $offset += 22; $self->_ifd_check(\$offset,\%OFFSET, \%FORMAT, \%LENGTH, \%TAGTMP, \'MainIFD'); sysseek($self->{fh}, $offset, 1); $self->_ifd_check(\$offset,\%OFFSET, \%FORMAT, \%LENGTH, \%TAGTMP, \'SubIFD'); foreach my $key ( keys %OFFSET ) { sysseek($self->{fh}, $OFFSET{$key} + $headoffset, 0); if ( $FORMAT{$key} == 2 ) { sysread($self->{fh}, $TAGTMP{$key}, $LENGTH{$key}); } elsif ( $FORMAT{$key} == 3 or $FORMAT{$key} == 4 or $FORMAT{$key} == 5 or $FORMAT{$key} == 8 or $FORMAT{$key} == 9 or $FORMAT{$key} == 10 ) { $TAGTMP{$key} = $self->_myread($FORMAT{$key}); } } close $self->{fh}; foreach my $key (keys %TAGTMP) { $TAGTMP{$key} =~ s/[^-\w\:\/\s\.\,\*]//g; $TAGTMP{$key} =~ s/\s+$//; my $tagkey = '0x'.sprintf("%04x",$key); if ( $TAGNAME{$tagkey} ) { $self->{$TAGNAME{$tagkey}} = $TAGTMP{$key}; } else { $self->{$tagkey} = $TAGTMP{$key}; } } } sub _readable_info { my $self = shift; my %CCDSIZE = (); while () { chomp; my ($make,$model,$fl,$fl135) = split(/\t/,$_); $CCDSIZE{$make}{$model} = $fl135 / $fl; } # Aper if ( $self->{ApertureValue} ) { $self->{Aper} = sqrt(2)**($self->{ApertureValue}); } elsif ( $self->{FNumber} ) { $self->{Aper} = $self->{FNumber}; } $self->{Aper} = sprintf("%2.2f",$self->{Aper}) if $self->{Aper}; # Shutter if ( $self->{ShutterSpeedValue} ) { $self->{Shutter} = 2**($self->{ShutterSpeedValue}); } elsif ( $self->{ExposureTime} ) { $self->{Shutter} = 1 / $self->{ExposureTime}; } if ( $self->{Shutter} ) { if ( $self->{Shutter} >= 1 ) { $self->{Shutter} = sprintf("1/%d",$self->{Shutter}); } else { $self->{Shutter} = sprintf("%3.1f",1/$self->{Shutter}); } } # FocalLength135 if ( $self->{FocalLength} > 0 and !$self->{FocalLength135} ) { # Perhaps Only CANON # Fuji is wrong unit if ( defined $self->{FocalPlaneXResolution} and $self->{FocalPlaneXResolution} > 0 ) { my $tmp = $self->{ExifImageWidth} / $self->{FocalPlaneXResolution}; if ( $self->{FocalPlaneResolutionUnit} == 1 ) { ; } elsif ( $self->{FocalPlaneResolutionUnit} == 3 ) { ; } else { $tmp = $tmp * 25.4; # inch->mm } $self->{FocalLength135} = sprintf("%d", $self->{FocalLength} * 35 / $tmp); } # Other Make and Model if ( $CCDSIZE{$self->{Make}}{$self->{Model}} ) { $self->{FocalLength135} = sprintf("%d", $self->{FocalLength} * $CCDSIZE{$self->{Make}}{$self->{Model}}); } } } sub _ifd_check { my $self = shift; my $offset = shift; my $OFFSET = shift; my $FORMAT = shift; my $LENGTH = shift; my $TAGTMP = shift; my $isMain = shift; my $tmp; sysread($self->{fh}, $tmp, READSHORT); my $loop = unpack($self->{readshort}, $tmp); for my $i ( 1 .. $loop ) { my $tag = $self->_myread(SIGNED_SHORT); my $format = $self->_myread(SIGNED_SHORT); my $data_num = $self->_myread(UNSIGNED_LONG); if ( ($$isMain eq 'MainIFD') and ($tag == 0x8769) ) { $$offset = $self->_myread(SIGNED_LONG) - $$offset; last; } else { if ( $format == 2 ) { # ascii strings 1 sysread($self->{fh}, my $data, READLONG); if ( $data_num > 4 ) { $data = unpack($self->{readlong}, $data); $$OFFSET{$tag} = $data + 12; $$LENGTH{$tag} = $data_num; $$FORMAT{$tag} = $format; } else { $$TAGTMP{$tag} = unpack("A", $data); } } elsif ( $format == 3 or $format == 8 ) { # unsigned/signed short 2 $$TAGTMP{$tag} = $self->_myread($format); sysseek($self->{fh}, 2, 1); } elsif ( $format == 4 or $format == 9 ) { # unsigned/signed long 4 $$TAGTMP{$tag} = $self->_myread($format); } elsif ( $format == 5 or $format == 10 ) { # unsigned/signed rational 8 $$OFFSET{$tag} = $self->_myread($format-1) + 12; $$LENGTH{$tag} = $data_num; $$FORMAT{$tag} = $format; } # elsif ( $format == 7 ) { # undefined 1 # print STDERR "hoge7\t"; # sysseek($self->{fh}, READLONG, 1) # } else { sysseek($self->{fh}, READLONG, 1); } $$offset += 12; } } } sub _myread { my $self = shift; my $value = shift; my $buf; my $tmp; if ( $value eq UNSIGNED_SHORT ) { sysread($self->{fh}, $tmp, READSHORT); $buf = unpack($self->{readshort}, $tmp); } elsif ( $value eq SIGNED_SHORT ) { sysread($self->{fh}, $tmp, READSHORT); $buf = unpack($self->{readshort}, $tmp); $buf += 65536 if ( $buf < 0 ); } elsif ( $value eq UNSIGNED_LONG ) { sysread($self->{fh}, $tmp, READLONG); $buf = unpack($self->{readlong}, $tmp); } elsif ( $value eq SIGNED_LONG ) { sysread($self->{fh}, $tmp, READLONG); $buf = unpack($self->{readlong}, $tmp); $buf -= 4294967296 if ( $buf > 2147483647 ); } elsif ( $value eq UNSIGNED_RATIONAL ) { sysread($self->{fh}, $tmp, READLONG); $buf = unpack($self->{readlong}, $tmp); sysread($self->{fh}, $tmp, READLONG); $tmp = unpack($self->{readlong}, $tmp); $buf = $buf / $tmp if ( $buf != 0 and $tmp != 0 ); } elsif ( $value eq SIGNED_RATIONAL ) { sysread($self->{fh}, $tmp, READLONG); $buf = unpack($self->{readlong}, $tmp); $buf -= 4294967296 if ( $buf > 2147483647 ); sysread($self->{fh}, $tmp, READLONG); $tmp = unpack($self->{readlong}, $tmp); $tmp -= 4294967296 if ( $tmp > 2147483647 ); $buf = $buf / $tmp if ( $buf != 0 and $tmp != 0 ); } return $buf; } # # local method end # =head1 Accessor After $img->info($filepath); Do $img->Accessor; =item print $img-EMake; =cut =item print $img-EModel; =cut =item print $img-EDateTimeOriginal; =cut =item print $img-EDateTime; =cut =item print $img-EShutter; =cut =item print $img-EAper; =cut =item print $img-EFocalLength; =cut =item print $img-EFocalLength135; =cut =item print $img-EISOSpeedRatings; =cut #=item print $img-EFlash; # #=cut =item print $img-EExposureBiasValue; =cut =item print $img-EExifImageWidth; =cut =item print $img-EExifImageHeight; =cut =item print $img-EJpegImageWidth; =cut =item print $img-EJpegImageHeight; =cut =item print $img-EFILESIZE; =cut =item print $img-EERRNO; =cut =item print $img-EERRMSG; =cut no strict; my @Accessors = qw( ERRNO ERRMSG Make Model DateTime DateTimeOriginal Shutter Aper FocalLength FocalLength135 ExifImageWidth ExifImageHeight JpegImageWidth JpegImageHeight ISOSpeedRatings ExposureBiasValue FILESIZE ); for my $accessor (@Accessors) { my $closure = sub { my $self = shift; $self->{$accessor} = shift if $_[0]; $self->{$accessor}; }; *{$accessor} = $closure; } 1; =head1 COPYRIGHT Copyright 1998- Kawabata, Kazumichi (Higemaru.) This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut # MakeModelflfl135 __DATA__ FUJIFILM FinePix4500 8.3 36 NIKON E5000 7.1 28 NIKON CORPORATION Mod NIKON D1X 24 36 NIKON CORPORATION NIKON D70 24 36 SONY DSC-F828 7.1 28 SONY DSC-F88 6.7 38 SONY DSC-W1 7.9 38 SONY DSC-P8 6 39 SONY DSC-P73 6 39 SONY DSC-P100 7.9 38 SONY DSC-P150 7.9 38 SONY DSC-T1 6.7 38 SONY DSC-T3 6.7 38 SONY DSC-L1 5.1 32 SONY DSC-V3 7 34 RICOH Caplio G4 wide 4.2 28 RICOH Caplio RR211 9.6 49 RICOH Caplio RX 4.3 28 RICOH Caplio GX 5.8 28 RICOH Caplio R1 4.6 28 OLYMPUS CORPORATION E-1 14 28 OLYMPUS IMAGING CORP. E-300 14 28 OLYMPUS CORPORATION C8080WZ 7.1 28 OLYMPUS CORPORATION C5060WZ 5.7 27 OLYMPUS CORPORATION C770UZ 6.3 38 OLYMPUS CORPORATION u30D,S410D,u410D 5.8 35 OLYMPUS CORPORATION u25D 5.8 35 OLYMPUS CORPORATION u15D 6.3 38 OLYMPUS CORPORATION X-3,C-60Z 7.8 38 OLYMPUS CORPORATION X350,D575Z,C360Z 5.8 35