package ExifDate;
use strict;
use warnings;
use IO::File;
require Time::Local;
use IO::String;
sub new{
my $class = shift;
my $file = shift;
return eval{
my $self = bless{}, $class;
if( -f $file ){
$self->{FH} = IO::File->new;
$self->{FH}->open($file, O_BINARY|O_RDONLY) or die $^E;
}
else{
$self->{FH} = IO::String->new;
$self->{FH}->print( $file );
$self->{FH}->seek(0,0);
}
$self->{offsetIFD0} = $self->findIFD0;
if( $self->{offsDateTime} = $self->findMarker($self->{offsetIFD0}, 0x0132) ){
$self->{DateTime} = $self->readDateTime($self->{offsDateTime});
}
else{
die "Kein Datum in Bilddatei\n";
}
$self;
};
}
sub readDateTime{
my $self = shift;
my $offs = shift;
my $rec = $self->readon(12,$offs);
my $mask = $self->{bigEnd} ? "nnNN" : "vvVV";
my ($marker, $type, $len, $val) = unpack $mask, $rec;
return unpack "A*", $self->readon($len, $val + $self->{tiffOffset});
}
sub readon{
my $self = shift;
my $fh = $self->{FH};
my $len = shift;
my $offs = shift;
$offs = defined $offs ? $offs : $fh->tell;
$fh->seek($offs,0);
$fh->read(my $buffer, $len);
return $buffer;
}
sub findIFD0{
my $self = shift;
my $IntelString = pack("C8", 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49);
my $MotorolaString = pack("C8", 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4D, 0x4D);
my $offset = 0;
my @fifo = ();
for( $offset = 0; $offset < 50; $offset++ ){
my $byt = $self->readon(1);
push @fifo, $byt;
shift @fifo if scalar @fifo > 8;
if( join('', @fifo) eq $IntelString){
$self->{format} = 'Intel';
$self->{bigEnd} = 0;
last;
}
elsif( join('', @fifo) eq $MotorolaString){
$self->{format} = 'Motorola';
$self->{bigEnd} = 1;
last;
}
}
die "Keine Exif-Daten in Bilddatei\n" if $offset > 49;
$self->{tiffOffset} = $offset - 1;
$self->{IFD0pointer} = $self->getUint32($self->{tiffOffset}+4);
die "Not Valid Tiff-Data\n" if $self->{IFD0pointer} < 8;
return $self->{IFD0pointer} + $self->{tiffOffset};
}
sub getUint16{
my $self = shift;
my $offs = shift;
my $bin = $self->readon(2,$offs);
return $self->{bigEnd} ? unpack "n", $bin : unpack "v", $bin;
}
sub getUint32{
my $self = shift;
my $offs = shift;
my $bin = $self->readon(4,$offs);
return $self->{bigEnd} ? unpack "N", $bin : unpack "V", $bin;
}
sub readTagValue{
my $self = shift;
my $offs = shift;
my $rec = $self->{FH}->readon(12,$offs);
my $mask = $self->{bigEnd} ? "nnNN" : "vvVV";
my ($marker, $type, $len, $val) = unpack $mask, $rec;
return $val;
}
sub findMarker{
my $self = shift;
my $dirStart = shift;
my $marker = shift;
my $entries = $self->getUint16($dirStart);
for (my $i = 0; $i < $entries; $i++) {
my $entryOffset = $dirStart + $i*12 + 2;
my $m = $self->getUint16($entryOffset);
if( $m == 0x8769 ){
$self->{ExifIFDPointer} = $self->readTagValue($entryOffset);
}
return $entryOffset if $marker == $m;
}
return;
}
sub DateTime{
my $self = shift;
my($date, $time) = split /\s+/, $self->{DateTime};
$date =~ s/:/-/g;
return "$date $time";
}
sub gmtime{
my $self = shift;
my ($date, $xtime) = split /\s/, $self->DateTime;
my ($year, $mon, $mday) = split "-", $date;
my ($hour, $min, $sec) = split ":", $xtime;
my $time = Time::Local::timegm( $sec, $min, $hour, $mday, $mon-1, $year );
return wantarray ? ($sec, $min, $hour, $mday, $mon, $year) : $time;
}
1;
__END__
package main;
use strict;
use Data::Dumper;
use IO::File;
my $file = "/fotoarchiv/2022/06/08.06.2022_003.jpg";
my $fh = IO::File->new();
$fh->open($file, O_BINARY|O_RDONLY) or die $!;
read($fh, my $bin, -s $fh);
print ExifDate->new($bin)->DateTime, "\n", join "\n",ExifDate->new($file)->gmtime;