Photos mit Übersicht


- Veröffentlicht in freeX 06/05 -



Digitale Photos sind schnell gemacht, auch das Speichern bzw. die Übertragung auf die Festplatte bereitet in der Regel keine größeren Probleme mehr. Nur das Archivieren der digitalen Schnappschüsse kann sich zu einer »längerfristigen Beschäftigung ausdehnen«.

Sind die Bilder erstmal auf der Festplatte geht der Spass los, die Bilder werden umbenannt, Aufnahmen im Hochformat gegebenenfalls gedreht, vereinzelte »kosmetische Operationen« sowie die Ablage in einem digitalen Photoalbum. Das ganze klingt nach viel Arbeit und mehreren verregneten Wochenenden, vieles lässt sich aber auch (halb-) automatisch mit etwas Perl und den entsprechenden Modulen erledigen.




Das im weiteren Verlauf vorgestellte Skript photoalbum.pl generiert aus verschiedenen JPGs ein Photoalbum mit folgenden Eigenschaften:

Bei Aufruf des Skriptes ist das Verzeichnis in dem sich die Bilder befinden über die Option --dir anzugeben.

$ photoalbum.pl --dir island/

In dem angegebenen Verzeichnis werden nicht nur die Bilder gelesen, sondern auch sämtliche weitere Dateien angelegt.

Die Angabe eines Verzeichnisse ist erforderlich, als optionale Argumente sind --title und --archive zulässig. Letzteres legt aus den Originalbildern ein TAR-Archiv an und wird ohne weitere Parameter aufgerufen. Der Option --title muss dagegen ein »Titel« folgen.

$ photoalbum.pl --dir island/ --title "Island" --archive

Der »Titel« wird an verschiedenen Stellen als Überschrift genutzt, erfolgt keine Angabe wird der default-Wert (»Photo«) verwandt.

Über die Option --help bzw. bei fehlerhaftem Aufruf des Skriptes wird eine »Kurzinfo« und Hinweise zum Aufruf ausgegeben.

Die Optionen können, da sie alle eindeutig sind, auch abgekürzt aufgerufen werden.

$ photoalbum.pl -d island/ -t "Island" -a



Module einbinden

Am Anfang des Skriptes photoalbum.pl werden verschiedene Zusatzmodule in das weitere Geschehen eingebunden. Getopt::Long wertet die bei Skriptaufruf übergebenen Optionen aus, mit File::Copy lassen sich Dateien kopieren, Image::Magick ist für die Bildbearbeitung zuständig, IO::File für das Anlegen der Dateien, Date::Manip gibt Datumsangaben im gewünschten Format aus und mittels Archive::Tar lassen sich Dateien bequem archivieren.

Die Module gehören meist zum Umfang einer »Standardinstallation«, gegebenenfalls lassen sie sich aber auch bequem vom CPAN holen und nachinstallieren.

Dazu sind jeweils die folgenden Aufrufe nach dem Entpacken erforderlich:

$ perl Makefile.PL
$ make
$ make test
$ su - 
Password: <passwort>
# make install



Photos lesen

Die Folgezeilen enthalten verschiedene Variablen denen vereinzelt Werte zugewiesen werden. Der Zugriff auf die Bilder erfolgt objektorientiert, in Zeile 29 wird über die Methode new() ein neues Objekt generiert, über das die einzelnen Bilder im weiteren Verlauf bearbeitet werden.

Ab Zeile 30 werden die möglichen Optionen angegeben. Über GetOptions wird der Name der Option (dir für -dir), ob (und welches) weitere Argument anzugeben ist (=s für einen String) sowie eine Referenz auf eine Variable, die gesetzt wird oder den übergebenen Wert enthält, wenn die Option verwendet wird. Die Referenz muss nicht zwingend die gleiche Bezeichnung wie den Namen der Option haben. Die Angabe

GetOptions(
    ....
    "dir=s" => \my $dir
    ....
);

akzeptiert bei Aufruf eine Option namens dir die als Wert einen String enthalten muss. Dieser wird der Variablen $dir zugewiesen.

Um die Photos eines Verzeichnisses bearbeiten zu können, ist dieses bei Aufruf anzugeben, die beiden weiteren Angaben sind optional. Erfolgt keine Verzeichnisangabe wird die Ausführung beendet und der Skriptaufruf angezeigt (Zeile 38). Andernfalls wird das angegebene Verzeichnis ausgewertet und alle darin enthaltenen JPGs werden in dem Array @photos abgelegt.

In der folgenden Schleife wird den eingelesenen Bilder zunächst ein neuer Dateiname über die Funktion get_file_name() zugewiesen. Dieser setzt sich aus dem Wert von $photo_name, einer fortlaufenden Nummer (die mit führenden Nullen »vierstellig« aufgefüllt wird) sowie der Dateiendung zusammen, also beispielsweise photo_0007.jpg. Wurde bei Skriptaufruf die Option --archive angegeben werden die Dateien kopiert (Zeile 48), anderfalls einfach umbenannt (Zeile 51).

In Zeile 54 macht sich eine »Besonderheit« des Moduls Image::Magick bemerkbar. Statt bei einem auftretenden Fehler einen »falschen Wert« (false, 0) und im Erfolgsfall einen » wahren Wert« (true, 1) zurückzugeben, liefert Image::Magick bei Fehlern einen Fehler-Code und wenn alles erwartungsgemäß lief einen Leerstring (bzw. 0) zurück. Bei den Fehler-Codes handelt es sich um eine dreistellige Zahl. Bei einer Warnung ist die erste Ziffer eine »3«, ist die erste Ziffer eine »4« geht es um einen Fehler.

Die Fehlerauswertung könnte alternativ auch über

$fehler = $pictures->Read("bild.jpg")

die "Bild konnte nicht gelesen werden\n" if ($fehler);

erfolgen.

Über die Methode Read können sowohl einzelne als auch mehrere Bilder gelesen werden.




Bearbeiten der Photos

Nachdem alle Bildobjekte des Verzeichnisses gelesen wurden können diese über weitere Methoden bearbeitet werden. Für das Photoalbum werden jeweils ein Thumbnail sowie das Photo in »Originalgröße« benötigt. Diese beiden Größen werden am Anfang des Skriptes über $thumb_size bzw. $photo_size gesetzt. In der folgenden Schleife (Zeile 59 - 74) wird zunächst der Name der Eingabedatei $photo zugewiesen, hieraus wird die Bezeichnung der Thumbnails abgeleitet bevor die Methoden Resize und Write zum Zuge kommen. Dabei wird die Größe der Bilder entsprechend angepasst und das Ergebnis schliesslich unter den zuvor ermittelten Dateinamen auf die Festplatte geschrieben. Der Dateiname der jeweiligen Photos nebst zugehörigen Thumbnails wird letztlich in dem Hash %photos gespeichert, der zu einem späteren Zeitpunkt zum Einsatz kommt.

Über die Methode Get können noch weitere Attribute abgefragt werden, hierzu gehören unter anderem:

Analog hierzu können über die Methode Set verschiedene Attribute gesetzt werden. Welche Attribute das sind und alle weiteren, die abgefragt werden können, lassen sich in der Dokumentation zu PerlMagick nachlesen.




Index-Datei anlegen

Nachdem die sich in dem Verzeichnis befindlichen JPGs bearbeitet und die jeweiligen Thumbnails angelegt wurden, können als nächstes die HTML-Dateien generiert werden.

Der Wert zu $no_photos liefert die Anzahl der Photos, Zeile 77 stellt sicher dass der - sofern angegeben - Titel als solcher verwendet wird. In Zeile 79/80 wird die »Haupt-Datei« index.html angelegt und zum Schreiben geöffnet. In dieser Datei erscheinen nach einer Überschrift (dem »Titel«) alle Thumbnails in einer Tabelle.

Aber der Reihe nach. Über die Funktion html_header() wird der Kopf der HTML-Datei(en) geschrieben. Als Argumente werden Filhandle und Titel übergeben. In der Folgezeile wird die Überschrift der Tabelle geschrieben und selbige angelegt.

In jeder Zeile der Tabelle befinden sich fünf Zellen, dieser Wert wurde am Anfang des Skriptes über $cols festgelegt. Sind fünf Zellen mit Inhalt gefüllt, wird die aktuelle Zeile beendet und eine neue begonnen (Zeile 90 - 94).

Der Inhalt einer Zelle besteht aus dem Thumbnail ($photos{$key}) sowie dem Verweis zu der zugehörigen HTML-Datei (mit dem grösseren Einzelphoto). Dazu wird zunächst aus dem Dateinamen des Photos ($key) der Name der HTML-Datei abgeleitet, der Verweis in eine Zelle geschrieben und diese mit dem Thumbnail gefüllt (Zeile 96 - 100). Anschliessend wird nur noch der Zähler um Eins erhöht, damit auch in der nächsten Runde nach fünf Zellen Schluss ist und eine neue Zeile begonnen wird.

Sind alle Photos auf diese Weise abgearbeitet wird die letzte Zeile und die Tabelle geschlossen. Über den Funktionsaufruf html_close() wird das heutige Datum sowie die URL und Mail-Adresse als Referenz angegeben. Die beiden Werte lassen sich über $home bzw. $mail anpassen.

Letztlich ist die Bearbeitung von index.html abgeschlossen und die Datei kann gleichfalls geschlossen werden. Die Datei hat beispielsweise folgendes Aussehen:




Anlegen der Einzeldateien

Die zu den Thumbnails gehörigen »Einzeldateien« werden in den letzten Zeilen (103 - 111) der Schleife generiert. Der Dateiname unterscheidet sich nur durch die Dateiendung (*.html statt *.jpg) und wurde als solcher bereits verwandt ($html).

Jede der Dateien enthält drei Verweise:

Letzteres lässt sich einfach dadurch ermitteln dass der Index der Datei um eins erhöht wird (Zeile 103/104), der Name der Datei wird gleichfalls über die Funktion get_file_name() gebildet. Eine »nächste Datei« existiert nicht für das letzte Photo, daher wird hier wieder auf die Index-Datei verwiesen (Zeile 105).

Das vorige Bild lässt sich auch relativ einfach ermitteln, in dem ersten Schleifendurchlauf (wo es noch kein »voriges Bild« geben kann), wird auf die Index-Datei verwiesen (Zeile 107). In allen weiteren Runden wird der Wert nachdem die Datei angelegt wurde, einer Zwischenvariablen zugewiesen (Zeile 111).

Die einzelnen Dateien werden über den Aufruf der Funktion html_files() erzeugt. Bei Aufruf werden der Name der aktuellen HTML-Datei ($html), deren Überschrift ($album), die laufende Nummer ($index), sowie der Name der vorigen HTML-Datei ($last_file) des Photos ($key) und der nächsten HTML-Datei ($next_file) als Parameter übergeben.

In der Funktion wird die übergebene Datei angelegt und zum Schreiben geöffnet, die Funktion html_header aufgerufen, die Überschrift geschrieben, die übergebenen Verweise gesetzt, das Bild ausgegeben bevor die Datei letztlich wieder über die Funktion html_close() geschlossen wird.

Das Resultat stellt sich wie folgt dar:




Photos in ein anderes Format konvertieren

Für die Verwendung der Bilder in einem Photoalbum bietet sich JPG als Format quasi an. Dies bedeutet jedoch nicht zwingend, dass die Bilder auch in diesem Format vorliegen. Oftmals werden diese bei der Aufnahme im TIFF-Format gespeichert. Das häufig genutzte TIFF-Format bietet eine hohe Auflösung und keinerlei Verlust duch Komprimierung (da keine durchgeführt wird) und ist somit relativ speicherintensiv. Aus diesem Grund wurde hier das »speicherfreundliche« JPG-Format verwendet.

Das Konvertieren in ein anderes Format erweist sich als relativ einfach. Zunächst muss das Bild eingelesen werden, handelt es sich bei der Endung der Ausgabedatei um eine andere als die der Eingabedatei konvertiert PerlMagick die Ausgabedatei automatisch in das angegebene Format. Somit ist für das Konvertieren lediglich der Aufruf der zwei Methoden Read und Write erforderlich.

my $photos = Image::Magick->new();

foreach my $datei (@ARGV) {

    $photos->Read($datei) && die "Bild konnte nicht gelesen werden\n";
}

foreach my $photo (@$photos) {

    (my $new = $photo->Get('base-filename')) =~ s/tif$/jpg/;

    unless (-e $new) {

        $photo->Write($new) && die "Bild konnte nicht erzeugt werden\n";
    }
}

Das Skript tiff2jpg.pl liest die bei Aufruf angegebenen Bilder ein und konvertiert die übergebenen TIFFs in das JPG-Format (sofern die entsprechende Datei noch nicht existiert).




Bildbearbeitung

PerlMagick bietet noch eine Fülle weiterer Methoden über die sich Format und Aussehen der Bilder beeinflussen lassen. Erste Anlaufstelle ist auch hier die beigefügte Dokumentation sowie auf

http://www.imagemagick.org/script/examples.php

wo die »Auswirkungen« einzelner Methoden an einem Beispiel sehr anschaulich dargestellt werden.

Für das Photoalbum ist unter anderem die Methode Rotate interessant, welche dazu genutzt werden kann Bilder die im Hochformat aufgenommen wurden, um eine Vierteldrehung zu drehen.

In der folgenden schleife werden alle Bilder die unter @photos abgelegt wurden um 90 Grad gedreht:

my $pictures = Image::Magick->new();

foreach (@photos) {

    $pictures->Read($_) && die "Bild '$_' konnte nicht gelesen werden\n";
    $pictures->Rotate(degrees => 90);
    $pictures->Write($_) && die "Bild '$_' konnte nicht erzeugt werden\n";
}

Auf der angegebenen Seite kann ein Beispielskript examples.pl herunter geladen werden, welches zum Ausprobieren der weiteren Methoden genutzt werden kann. Mit diesen lassen sich unter anderem die Bildschärfe heraufsetzen, Bildrauschen reduzieren, Bilder beschriften, Rahmen ziehen und einiges mehr.




Informationsauswertung

Bei einer größeren Anzahl von Bildern kann es »leicht unübersichtlich« werden, wenn die einzelnen Photos nur mit einer laufenden Nummer gekennzeichnet werden. Hier bietet sich die Auswertung der sogenannten EXIF-Daten an.

Der EXIF-Standard (Exchangeable Image File Format) wird von den meisten digitalen Kameras unterstützt und liefert verschiedene Informationen zu Hersteller, Kamera, Brennweite, Belichtung, Datum und Uhrzeit der Aufnahme, usw. Diese Informationen werden - normalerweise - zusammen mit den Graphikdaten in der gleichen (JPG-, TIFF-) Datei gespeichert.

Das Perl-Modul Image::Info liest die gewünschten Informationen - sofern vorhanden - aus den jeweiligen Bilddateien aus. Das folgende Skript liest das Datum aus und gibt einen Wert der Form dd-mm-yyyy_hh-mm-ss zurück:

use Image::Info qw(image_info);


my $info = image_info("bild.jpg");

die "Datei konnte nicht ausgewertet werden: $fehler\n" 
    if (my $fehler = $info->{error});

(my $datum = $info->{DateTime}) =~ 
    s/(\d{4}):(\d{2}):(\d{2})\s(\d{2}):(\d{2}):(\d{2})/$3-$2-$1_$4-$5-$6/;

print $datum, "\n";

Leider verfahren auch hier die Hersteller unterschiedlich, sodass nicht von einem einheitlichen Umgang mit EXIF-Informationen ausgegangen werden kann. Weitere Angaben, auch zu »Herstellerspezifischen Besonderheiten« finden sich unter:

http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html

Die in der Dokumentation zu Image::Info angegebene Adresse existiert nicht mehr.

Eine Stolperfalle ist das Bearbeiten der Photos mit diversen Graphikprogrammen (unter anderem Gimp). Sind die Photos erstmal bearbeitet und abgespeichert, sind auch meist die EXIF-Daten auf der Strecke geblieben.




001: #!/usr/pkg/bin/perl -w
002: #
003: # Thomas Lingmann, 2005 tl@noatun.net
004: #
005: 
006: use Getopt::Long;
007: use File::Copy;
008: use Image::Magick;
009: use IO::File;
010: use Date::Manip;
011: use Archive::Tar;
012: 
013: use strict;
014: 
015: my $home       = "http://172.16.1.1/~tl/index.html";
016: my $mail       = "tl\@noatun.net";
017: my $photo_name = "photo_";     # Bezeichnung Photos
018: my $thumb_name = "thumbnail_"; # Bezeichnung Thumbnails
019: my $photo_size = "640x480";    # Groesse Photos
020: my $thumb_size = "120x79";     # Groesse Thumbnails
021: my $color      = "#e2e2e2";    # Hintergrundfarbe HTML
022: my $cols       = 5;            # Anzahl Spalten (= Bilder) einer Reihe
023: my $c          = 0;            # Variable zum Zaehlen der Spalten
024: my $index      = 1;            # Variable zur Indexierung der Bilder
025: my %photos     = ();           # Hash zum Einlesen der Photos
026: my $last_file;                 # Dateiname des vorigen Bildes
027: my $next_file;                 # Name des naechsten Bildes
028:
029: my $pictures = Image::Magick->new();
030: 
031: GetOptions(
032:     "archive" => \my $archive,
033:     "dir=s"   => \my $dir,
034:     "title=s" => \my $title,
035:     "help"    => \&usage
036: );
037: 
038: die usage() unless defined $dir;
039: 
040: chdir "$dir" || die "Konnte nicht nach '$dir' wechseln: $!";
041: my @photos = glob "*.jpg";
042: 
043: foreach my $file (sort { $a cmp $b } @photos) {
044: 
045:     my $new = get_file_name($photo_name, $index, ".jpg");
046: 
047:     if ($archive) {
048: 	     copy($file, $new) || die "Konnte Datei nicht kopieren\n";
049:     }
050:     else {
051:   	     move($file, $new) || die "Konnte Datei nicht umbenennen\n";
052:     }
053: 
054:     $pictures->Read($new) && die "'$new' konnte nicht gelesen werden\n";
055:     $index++;
056: }
057: 
058: 
059: foreach my $picture (@$pictures) {
060: 
061:     my $photo = $picture->Get('base-filename');
062: 
063:     (my $thumb = $photo) =~ s/$photo_name/$thumb_name/;
064: 
065:     $picture->Resize(geometry => $photo_size);
066:     $picture->Write($photo) && 
067: 	     die "Bild '$photo' konnte nicht erzeugt werden\n";
068: 
069:     $picture->Resize(geometry => $thumb_size);
070:     $picture->Write($thumb) && 
071: 	     die "Bild '$thumb' konnte nicht erzeugt werden\n";
072: 
073:     $photos{$photo} = $thumb;
074: }
075: 
076: my $no_photos = keys %photos;
077: my $album = $title ? $title : "Photos";
078: 
079: my $fh_index = IO::File->new(">index.html") || 
080:     die "Datei 'index.html' konnte nicht angelegt werden: $!\n";
081: 
082: html_header($fh_index, $album);
083: 
084: print $fh_index "<h1>$album</h1>\n\n";
085: print $fh_index "<table border=1>\n\n";
086: print $fh_index "<tr>\n";
087: 
088: foreach my $key (sort keys %photos) {
089: 
090:     if ($c == $cols) {
091: 
092: 	     print $fh_index "</tr>\n<tr>\n";
093: 	     $c = 0;
094:     }
095: 
096:     (my $html = $key) =~ s/jpg/html/;
097: 
098:     print $fh_index "<td width='120' align='center'><a href='$html'>";
099:     print $fh_index "<img src='$photos{$key}' hspace=2 vspace=2 alt=''>";
100:     print $fh_index "</a></td>\n\n";
101:     $c++;
102: 
103:     (my $index = $html) =~ s/($photo_name)([0]+)(\d+)(.\w+)/$3/;
104:     my $next = get_file_name($photo_name, $index + 1, ".html");
105:     $next_file = $index < $no_photos ? $next : "index.html";
106: 
107:     $last_file = "index.html" if ($index == 1);
108: 
109:     html_files($html, $album, $index, $last_file, $key, $next_file); 
110: 
111:     $last_file = $html;
112: }
113: 
114: print $fh_index "</tr>\n";
115: print $fh_index "</table>\n\n";
116: 
117: html_close($fh_index);
118: 
119: $fh_index->close();
120: 
121: 
122: if ($archive) {
123: 
124:     my $tar = Archive::Tar->new;
125:     my $file_name = $album . &UnixDate("today", "_%Y_%m_%d.tar");
126: 
127:     $file_name =~ s/(\w+)/\L$1/gi;
128: 
129:     foreach (@photos) {
130: 
131: 	     $tar->add_files($_);
132: 	     unlink $_ || warn "Konnte '$_' nicht loeschen: $!\n";
133:     }
134: 
135:     $tar->write($file_name) || 
136: 	     die "Archiv konnte nicht angelegt werden: $!\n";
137: }
138: 
139: 
140: 
141: sub get_file_name {
142: 
143:     my ($bez, $index, $suffix) = @_;
144: 
145:     $bez . sprintf('%04d', $index) . $suffix;
146: }
147: 
148: 
149: sub html_header {
150: 
151:     my ($fh, $title) = @_;
152: 
153:     select $fh;
154: 
155:     print "<!DOCTYPE html PUBLIC";
156:     print " \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n\n";
157:     print "<html>\n";
158:     print "<head>\n";
159:     print "  <title>$title</title>\n";
160:     print "  <meta http-equiv=\"content-type\" ";
161:     print "content=\"text/html; charset=ISO-8859-1\">\n";
162:     print "</head>\n\n";
163: 
164:     print "<body bgcolor='$color'>\n\n";
165: }
166: 
167: 
168: sub html_close {
169: 
170:     my ($fh) = @_;
171:     my $date = &UnixDate("today", "%d-%m-%Y");
172: 
173:     select $fh;
174: 
175:     print "<hr/>\n";
176:     print "<em>Datei angelegt am: $date<br/>\n";
177:     print "WWW: <a href='$home'>Home</a><br/>\n";
178:     print "Mail: <a href='mailto:$mail'>$mail</a>\n";
179:     print "</em>\n\n";
180: 
181:     print "</body>\n";
182:     print "</html>\n";
183: }
184: 
185: 
186: sub html_files {
187: 
188:     my ($file, $title, $index, $last, $photo, $next) = @_;
189: 
190:     my $fh = IO::File->new(">$file") || 
191: 	     die "Datei '$file' konnte nicht angelegt werden: $!\n";
192: 
193:     select $fh;
194: 
195:     html_header($fh, $album);
196: 
197:     print "<h2>Bild $index in $title </h2>\n\n";
198:     print "<table border=0>\n\n";
199:     print "<tr>\n";
200:     print "<td><p>\n";
201:     print "<a href='$last'>Zur&uuml;ck</a>";
202:     print " | <a href='index.html'>Index</a>";
203:     print " | <a href='$next'>Weiter</a>\n";
204:     print "</p></td>\n";
205:     print "</tr>\n";
206:     print "<tr>\n";
207:     print "<td><img src='$photo' alt=''></td>\n";
208:     print "</tr>\n";
209:     print "</table>\n\n";
210: 
211:     html_close($fh);
212: 
213:     $fh->close();
214: }
215: 
216: 
217: sub usage {
218: 
219:     $0 =~ s#.*/##g;
220: 
221:     print "Aufruf: $0 --dir <verzeichnis> [--archiv] [--title <>]\n";
222:     print "                  [--help]\n";
223: 
224:     print "  --archiv   erzeugt TAR-Archiv aus den 'Originalphotos'\n";
225:     print "  --dir      einzulesendes Verzeichnis\n";
226:     print "  --title    Angabe einer Ueberschrift\n";
227:     print "  --help     diese Seite\n";
228: 
229:     exit 0;
230: }



| Home | Artikel | Bücher | Touren | Photos | Impressum |
Rücksprachen, Anmerkungen, Anregungen, .... bitte an info@noatun.net