#!/usr/bin/perl

# Dies ist ein Test-Skript, welches ich bei einem LUG-Abend
# verwendet habe, um eine Einfuehrung in Perl zu geben.
# Dieses Skript enthaelt mehr als an dem Abend gezeigt wurde.
#
# Es koennen Fehler enthalten sein, es wird nicht auf moegliche
# Sicherheitsluecken und Fehlerbehandlung geachtet.
# In keinem Fall sind die Loesungen optimal. Ausserdem ist die
# Anzahl der Kommentare aeusserst unnatuerlich, aber fuer
# Anfaenger notwendig, da es sonst keine Dokumentation gibt.
#
# Es ist reines Anschauungsmaterial und darf nur als solches
# verwendet werden. Insbesondere darf es nicht ausgefuehrt werden.
# Wer dem zuwider handelt, ist fuer saemtliche Folgen selbst
# verantwortlich.
#
# (c) 2003-12-10 Josef Angstenberger

print "DigiCam-Rename (2003-12-10)\n";
print "YaLUG Test-Version (yalug (at) jtxa (dot) de)\n\n";

# keine "on-the-fly" Variablen erlauben,
#  alle muessen explizit vor einer Verwendung deklariert werden
use strict;
# zusaetzliche Warnungen einschalten (entspricht -w in der CMD line)
use warnings;

# Modul um an Informationen aus den Bildern heranzukommen,
# es wird nur die Funktion image_info importiert.
use Image::Info qw(image_info);

# wird fuer die Berechnung des Wochentags benoetigt
use Time::Local;

# Ein nuetzliches Modul beim Entwickeln.
# z.B. kann folgendes in der Subroutine getDateJpg nuetzlich sein
#   print Dumper $info;
use Data::Dumper;

# Ein einfacher Skalar, wird als Beginn eines jeden Dateinamens verwendet.
my $Prefix = '';  # z.B.   my $Prefix = 'hochzeit_';
# In diesem Array werden alle Umbenennungen aufgefuehrt. Wird nur fuer
# die LOG Datei verwendet.
my @AllRenamed;

# Aufruf der eigenen Funktion fuer alle Dateien mit Endung jpg.
RenameSuffix('jpg');
# Und da mit der Kamera auch Videos und Ton moeglich sind, zwei weitere Endungen.
RenameSuffix('avi');
RenameSuffix('wav');

# Schreiben der LOG Datei
WriteLog();

# Die wichtigste Funktion, eigentlich die Hauptschleife des Programms.
sub RenameSuffix {
	# Einzelne Uebergabeparameter kann man auch per shift-Befehl holen.
	my $Suffix = shift;   # entspricht    my $Suffix = shift @_;
	# Das Array mit allen Dateinamen fuer das uebergebene Suffix fuellen.
	my @Files  = glob("*.$Suffix");

	# Und nun fuer jedes Element des Arrays die Schleife ausfuehren.
	# Dabei wird das aktuelle Element dem Skalar $Filename zugewiesen.
	foreach my $Filename (@Files) {
		# Der neue Dateiname kann einen Praefix enthalten (s.o.).
		my $NewName = $Prefix;
		
		# Je nach Suffix werden unterschiedliche Routinen aufgerufen,
		# um an das Datum zu gelangen.

		# Fuer JPG-Dateien gibt es eine spezielle Routine.
		if ($Suffix eq 'jpg') {
			$NewName .= getDateJpg($Filename);
		}
		# Fuer alle anderen wird es anhand des Dateidatums bestimmt.
		else {
			$NewName .= getDate($Filename);
		}

		# Eine Besonderheit meiner CANON-Digitalkamera. Wenn ich Panorama-Aufnahmen mache,
		# sind die Dateinamen folgendermassen: STA_xxxx.jpg, STB_xxxx.jpg, STC_xxxx.jpg usw.
		if ($Filename =~ m/^ST(\w)/) {
			# Um diese Information nicht zu verlieren, wird der jeweilige Buchstabe an den
			# Dateinamen angehaengt.
			$NewName .= "_$1";
		}
		# Und falls die Datei schon mal umbenannt wurde, haengt dieser Buchstabe am Ende.
		elsif ($Filename =~ m/_(\w)\.jpg$/) {
			# Deshalb muss man ihn dann auch an den neuen Namen anhaengen, sonst wuerde
			# die Information, bei einem zweiten Umbenennen verloren gehen.
			$NewName .= "_$1";
		}
		
		# So der Dateiname ist jetzt fast komplett, deshalb noch die Endung anhaengen.
		$NewName .= ".$Suffix";
		
		# Und zur Info die geplante Umbenennung auf den Bildschirm ausgeben.
		print "$Filename -> $NewName\n";

		# Nur wenn die Datei noch nicht unbenannt wurde:
		if ($Filename ne $NewName) {
			# Die Umbennungsaktion in der LOG Datei vermerken. Deshalb
			# ein neues Element fuer das Array.
			push @AllRenamed, "$Filename -> $NewName\n";
# Die "Schadensroutine". Auskommentiert damit nichts passieren kann.
#			rename($_, $NewName);
		}
		# ansonsten alles lassen, wie es ist.
		else {
			# Und auch hier wieder ein Kommentar fuer die LOG Datei.
			push @AllRenamed, "Not renamed: $Filename\n";
		}
	}
}

# Funktion liefert das Datum fuer eine Bilddatei zurueck.
# Dies geschieht mit Hilfe der EXIF-Daten in einer JPG-Datei.
sub getDateJpg {
	# Uebergabeparameter ist ein Dateiname.
	my ($Filename) = @_;
	# Ein Array mit den Kuerzeln fuer alle Wochentage.
	my @Weekday = qw(So Mo Di Mi Do Fr Sa);
	# Die importierte Funktion aufrufen, sie liefert eine Hash-Referenz.
	my $info = image_info($Filename);
	if (my $error = $info->{error}) {
		die "Can't parse image info: $error\n";
	}
	# Hier, die schon vorher erwaehnte Hilfe.
#   print Dumper $info;

	# Es gibt bei meinen Bildern 3 verwertbare Daten.
	#	'DateTimeDigitized'
	#	'DateTimeOriginal'
	#	'DateTime'
	# Ich habe mich fuer DateTimeDigitized entschieden.
    my $Time = $info->{'DateTimeDigitized'};

    # Variablen fuer die einzelnen Datums-Informationen
	my ($sec,$min,$hour,$mday,$mon,$year);
    # Da das Datum schon als ASCII vorliegt, muss es mit Hilfe eines
    # regulaeren Ausdrucks in die Einzelteile zerlegt werden.
    # Preisfrage: Wie ist das Datum zusammengesetzt? Anhand der naechsten
    # Zeilen kann man das Raetsel loesen.
	if ($Time =~ m/(\d+)\:(\d+)\:(\d+)\s+(\d+)\:(\d+)\:(\d+)/) {
		$year = $1 - 1900;
		$mon  = $2 - 1;
		$mday = $3;
		$hour = $4;
		$min  = $5;
		$sec  = $6;
	}
	else {
		# Wenn das Datum nicht dem obigen Format entspricht, wird
		# einfach abgebrochen.
		die "Date not parseable";
	}
	# Und hier die Aufloesung: JJJJ:MM:TT hh:mm:ss
	# Also etwas gewoehnungsbeduerftig das Datum mit : getrennt.

	# hier wird mit der importierten Funktion timelocal in Unix-Zeit umgerechnet,
	# und dann wieder zurueck mit localtime.
	# Macht keinen Sinn?
	# Doch, ich erspar mir das haendische Ausrechnen des Wochentags.
    @_ = localtime(timelocal($sec, $min, $hour, $mday, $mon, $year));
    my $wday = $_[6];

	# Zum Abschluss wird das Datum nach meinen Wuenschen formatiert zurueckgegeben.
	return sprintf('%04d%02d%02d_%s_%02d%02d%02d',($year+1900),($mon+1),$mday,$Weekday[$wday],$hour,$min,$sec);
}

# Funktion liefert das Datum fuer eine Datei zurueck.
sub getDate {
	# Uebergabeparameter ist ein Dateiname.
	my ($Filename) = @_;
	# Ein Array mit den Kuerzeln fuer alle Wochentage.
	my @Weekday = qw(So Mo Di Mi Do Fr Sa);
	# Alle Dateiinformationen holen. Eigentlich wird nur $mtime (Modification time) benoetigt.
	my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($Filename);
	# Die Zeit im Unix-Format in verstaendliche Zahlen wandeln.
	# Wie man hier sieht, braucht man in der Klammer nicht fuer jedes Array-Element eine Variable angeben.
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,,) = localtime $mtime;
	# Und korrekt formatiert zurueckgeben.
	return sprintf('%04d%02d%02d_%s_%02d%02d%02d',($year+1900),($mon+1),$mday,$Weekday[$wday],$hour,$min,$sec);
}

# Schreiben der LOG Datei
sub WriteLog {
	# Oeffnen der LOG Datei (fixer Name) und im Fehlerfall Aufruf von die().
	# Um die Fehlerabfrage zu testen, einfach einer bestehenden LOG Datei die Schreibrechte entziehen.
	open(OUTPUT, '>>digicam_rename.log') or die 'LOG-Datei konnte nicht geschrieben werden. Exit';

	# OUTPUT ist das Filehandle, man kann es einfach bei print hinzufuegen (wie bei BASIC damals).
	# Allerdings muss man darauf achten, dass nach dem Handle kein Komma kommt.
	
	# $0 ist die Variable die den Namen des Programms (unser Skript) enthaelt.
	# Das ist einfache eine Info-Zeile, wann das Programm ausgefuehrt wurde. time() liefert die aktuelle Zeit.
	print OUTPUT "\n$0 at ".localtime(time())."\n";
	# Eine einfache Moeglichkeit etwas vielfach auszugeben: 'String' x Anzahl
	print OUTPUT '-'x78 . "\n";
	# Arrays muss man nicht erst in einen Skalar umwandeln, sondern kann ihn direkt ausgeben.
	print OUTPUT @AllRenamed;
	# Ein abschliessendes Return. Je nachdem unter welchem OS Perl ausgefuehrt wird, resultiert daraus
	# ein Linefeed, ein Carriage Return oder beides.
	print OUTPUT "\n";

	# Schliessen der Datei. Wird am Ende eines Programms zwar automatisch geschlossen, aber korrekterweise
	# macht man das natuerlich selber.
	close(OUTPUT);
}

