package ExifDate; # Zweckbestimmung: Auslesen Aufnahmedatum in IFD0 use strict; use warnings; use IO::File; require Time::Local; use IO::String; sub new{ my $class = shift; my $file = shift; # lokale Datei oder binary 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; }; } # Jeder Eintrag einer IFD hat eine Länge von 12 Bytes # Lese an $entryOffset 2, 2, 4, 4 Bytes # |_________ Daten des Eintrags (bei einfachen Daten) oder Verweis auf die Daten # |____________ Länge der Daten des Eintrags als 32-Bit-Wert # |_______________ Datentyp des Eintrags # |__________________ Typ des Eintrags als 16-Bit-Wert (der sog. Marker) 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; # Hilfreich beim Entwickeln # my $info = sprintf "MARKER: 0x%02X Typ:%d Len:%d Val:%d\n", $marker, $type, $len, $val; # MARKER: 0x132 Typ:2 Len:20 Val:2376 #print "len: $len\n"; # 20 2022-06-08 06:59:55 return unpack "A*", $self->readon($len, $val + $self->{tiffOffset}); } sub readon{ my $self = shift; my $fh = $self->{FH}; # hier das FileHandle 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; } # ExifIFDPointer' => 'MARKER: 0x8769 Typ:4 Len:1 Val:118 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; # Hilfreich beim Entwickeln #my $info = sprintf "MARKER: 0x%02X Typ:%d Len:%d Val:%d", $marker, $type, $len, $val; 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); #printf "%04X\n", $m; if( $m == 0x8769 ){ $self->{ExifIFDPointer} = $self->readTagValue($entryOffset); } return $entryOffset if $marker == $m; } # versuche ExifPointer zu finden # 0x8769 => "ExifIFDPointer", 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;