Mit Inkscape automatisiert Bilder bearbeiten

Ich habe das Script in Perl geschrieben, weil ich die Sprache mag und diese über die regulären Ausdrücke viele Möglichkeiten bietet, Textstellen zu lokalisieren und zu verändern.

Mein Script beginnt im Grunde so:

unless ($ARGV[2]){
  print "Usage: $0 <infile-svg> <song-title> <bg-image>\n";
  exit 0;
}

Das ist nichts anderes als eine kurze Gebrauchsanweisung für das Script, die ausgegeben wird, wenn das Script ohne irgendwelche Parameter gestartet wird. Es verlangt beim Start drei Parameter:

  1. Eine SVG-Datei (nämlich unsere zuvor entworfene Vorlage)
  2. Einen Song-Titel
  3. Ein Hintergrund-Bild

Das bedeutet also, dass ich den Bandnamen aus der Vorlage per Script nicht verändere, sondern aus der Vorlage übernehme. Hintergrund dieser Entscheidung ist nämlich: Sollte ich für eine andere Band Plattencover exportieren wollen, muss sowieso die Schriftart einmalig angepassst werden. Bei dieser Gelegenheit ist das Anpassen in Inkscape unumgänglich und eine Automation an dieser Stelle nicht zielführend. Man kann natürlich die Vereinbarung treffen, dass beispielsweise immer eine serifenlose Schrift genommen wird, dann wäre das unter Umständen lohnenswert, aber ach… So geht’s auch.

Also, das Script kümmert sich um den viermaligen Export des Songtitels (das ist ja das Wesentliche) und freundlicherweise noch um die Einbindung des Hintergrundbildes.

Wie geht es weiter?

# hier wird der Prefix der Ausgabedatei bestimmt
my $outfile = "cover_";

# der Songtitel wird von einigen Sonderzeichen gereinigt,
# in Kleinbuchstaben umgewandelt und an den Prefix angehängt
my $titleMod = $ARGV[1];
$titleMod = lc($titleMod);
$titleMod =~ s/\ /_/g; # bei Bedarf um andere Zeichen erweitern...
$titleMod =~ s/\'//g;
$outfile .= $titleMod;

Bevor es weitergeht, muss die Vorlage eingelesen werden. Ich lese die SVG-Datei zeilenweise in ein Array ein, das gibt mir in der Verarbeitung die Möglichkeit, zwischen den Zeilen hin und her zu hüpfen:

my @inf; # input-file
open IN,"<$infile" or die "Cant open $infile: $!\n";
while(<IN>){push @inf, $_;}
close IN;

Jetzt wird’s spannend, denn jetzt kommt der eigentliche Witz, den ich hier exemplarisch für die PLAYALONG-Ebene darstelle:

# L-PLAYALONG
my @pa = @inf; 
for(my $i = 0; $i < @pa; $i++){
  if($pa[$i] =~ /.*inkscape:label="L-PLAYALONG".*/){
    $pa[$i+1] =~ s/none/inline/;
  }
  elsif($pa[$i] =~ /.*SONGTITLE.*/){
    $pa[$i] =~ s/SONGTITLE/$ARGV[1]/;
  }
  elsif($pa[$i] =~ /.*BILD.*/){
    $pa[$i] =~ s/BILD/$image/;
    $pa[$i+1] =~ s/BILD/$image/;
  }
}
open OF,">out.svg";
foreach my $p(@pa){
  print OF $p;
}
close OF;
system "/usr/bin/inkscape --file=out.svg --without-gui  --export-dpi=72 --export-area-page --export-png=".$outfile."_playalong.png";
system "/usr/bin/convert ".$outfile."_playalong.png ".$outfile."_playalong.jpg";
system "/bin/rm -f out.svg";

Passiert ne ganze Menge, was? Das ganze mal Stück für Stück. Zunächst kopiere ich das eingelesene SVG-Array in ein weiteres, um mir das Original zu erhalten.

Randbemerkung: Auch hier sei kurz angemerkt, dass der zu diesem Zweck akquirierte Speicher absehbar überschaubar genutzt wird. Es gibt sicher Lösungen, die ihrem Design nach weniger speicherintensiv sind, ist bei dem geringen Datenvolumen im RAM hier aber irrelevant. Mir ist grundsätzlich daran gelegen, wenig speicherhungrige Anwendungen zu schreiben, aber alles dann, wenn es wirklich notwendig ist.

Nun gut, Array kopiert, huh – gefährlich! Was passiert dann? Ich nehme mir eine gute alte for-Schleife und wandere durch das neue Array. Das SVG wurde ja zeilenweise eingelesen, also wandere ich jetzt von Zeile zu Zeile. Warum keine foreach-Schleife? Ganz einfach, und hatte ich sogar zuerst, aber dann trat der Fall auf, dass ich, während ich in einer Zeile war, etwas in der darauffolgenden Zeile bearbeiten wollte (kommen wir gleich noch drauf), und dafür ist der index $i nützlich.

my @pa = @inf;
for(my $i = 0; $i < @pa; $i++){

Jetzt suche ich mir die PLAYALOG-Ebene, um diese auf sichtbar zu schalten:

  if($pa[$i] =~ /.*inkscape:label="L-PLAYALONG".*/){
    $pa[$i+1] =~ s/none/inline/;
  }

In diesem Beispiel meiner Vorlage heisst die Ebene L-PLAYALONG. Ich suche mit Hilfe eines regulären Ausdrucks nach einer Zeile, in der dieses Label vorkommt. In Inkscape abgespeicherte SVGs haben die Angewohnheit, ihre Style-Informationen in die nächste Zeile zu schreiben. Das macht einerseits den XML-Code schön lesbar, andererseits führte mich das dazu, die gute alte for-Schleife und ihren kleinen Index zu bemühen, um eben dort auf die nächste Zeile im Array zugreifen zu können und diese von display:none auf display:inline zu setzen. Gesagt getan!

Als nächstes passe ich in jeder gefundenen Zeile das Label SONGTITLE auf den dem Script übergebenen Songtitel an. Oben in den Beispielen hiess der Songtitel Count Blessings, den hätte man natürlich auch als Label so stehen lassen und benutzen können, aber ich fand SONGTITLE einfach label-mäßiger.

  elsif($pa[$i] =~ /.*SONGTITLE.*/){
    $pa[$i] =~ s/SONGTITLE/$ARGV[1]/;
  }

Zu guter letzt wird noch das Hintergrundbild verarbeitet. Wie bereits oben erwähnt, gehe ich davon aus, dass alle verwendeten Bilder im aktuellen Verzeichnis liegen, daher beschränke ich mich im Script darauf, lediglich den Dateinamen anzupassen. Das Label BILD dient hierfür als matchpoint im regulären Ausdruck:

elsif($pa[$i] =~ /.*BILD.*/){
    $pa[$i] =~ s/BILD/$image/;
    $pa[$i+1] =~ s/BILD/$image/;
  }

Nachdem alle Werte für die entsprechenden Felder aus dem Vorlagen-Array angepasst sind, schreibe ich ein temporäres SVG in die Datei out.svg:

open OF,">out.svg";
foreach my $p(@pa){
  print OF $p;
}

Inkscape im non-interaktiven Modus

Nach so viel Vorbereitung wird es endlich Zeit, Inkscape den Job machen zu lassen. Ich starte aus dem Perl-Script heraus Inkscape ohne die Verwendung der GUI, dafür mit einigen Parametern, die ich nachfolgend erläutere:

system "/usr/bin/inkscape \
--file=out.svg \
--without-gui  \
--export-dpi=72 \
--export-area-page \
--export-png=".$outfile."_playalong.png";

Über die Funktion system wird in Perl ein anderes Programm aufgerufen, in diesm Fall nämlich, wer hätte das gedacht, Inkscape. Ich übergebe noch weitere Optionen an Inkscape nämlich:

  • –file=out.svg: die Datei, die ich über das Script zuvor mit den gewünschten Werten versehen und geschrieben habe
  • –without-gui: die grafische Benutzeroberfläche von Inkscape wird nicht gestartet
  • –export-dpi=72: die zu exportierende PNG-Datei soll eine Auflösung von 72dpi haben
  • –export-area-page: die gesamte in Inkscape definierte Seite soll der Exportbereich sein. In Inkscape lässt sich der Exportbereich festlegen. So kann zum Beispiel nur die aktuelle Auswahl, bestimmte Elemente (per ID referenzierbar), die gesamte Zeichnung (wenn Elemente über die Seite hinausragen werden diese mit exportiert), oder eben das, was sich auf der Seite befindet (Seite) als Exportbereich festgelegt werden.
  • –export-png=: das bestimmt, dass eine PNG-Datei geschrieben werden soll.
  • „.$outfile.“_playalong.png“;: hier lege ich den auszugebenden Dateinamen der PNG-Datei fest. Die Variable $outfile setzt sich zusammen aus dem Prefix cover, gefolgt vom bereinigten Songtitel in Kleinbuchstaben. Daran angehangen wird _playalong, weil es sich um den Export des Playalong-Covers handelt.
    Der gesamte Dateiname für einen Song mit dem Titel „My Sooper Dooper Song“ würde also lauten:
    cover_my_sooper_dooper_song_playalong.png.

Damit ist die Cover-Datei für das Playalong grundsätzlich geschrieben. Ich möchte daran erinnern, dass der gesamte Vorgang des Datenaustausches und des Ebenen-Einblendens im Script noch drei Mal wiederholt wird, und zwar für das Audio-MP3, das Music-Sheet und die Collection, jeweils für die aktuell in Frage kommende Ebene. Nach jedem dieser Vorgänge wird eine PNG-Datei geschrieben mit dem jeweils angehängten Kürzel für die aktivierte Ebene.

Die PNG-Dateien können noch heruntergerechnet und in JPEG-Dateien umgewandelt werden, dies geschieht mittels convert, hier zu sehen am Beispiel der Playalong-Datei. Auch dieser Befehl wird aus dem Script heraus aufgerufen.

system "/usr/bin/convert ".$outfile."_playalong.png ".$outfile."_playalong.jpg";

Das war der Durchgang für die Ebene Playalong, anschliessend wird noch aufgeräumt, damit nichts durcheinander gerät, bevor es in die nächste Runde, nämlich zur nächsten Ebene geht:

system "/bin/rm -f out.svg";

Am Ende gibt es vier Dateien, die als Produktbilder verwendet werden können. Für den Titel „My Sooper Dooper Song“ würden zum Beispiel folgende Dateien entstehen:

cover_my_sooper_dooper_song_playalong.jpg
cover_my_sooper_dooper_song_audio.jpg
cover_my_sooper_dooper_song_collection.jpg
cover_my_sooper_dooper_song_sheet.jpg

Zusammenfassung/ Resumee

Noch einmal zusammengefasst, so geht’s:

  1. Vorlage in Inkscape bauen, darin Felder (Labels) und Ebenen definieren
  2. Vorlage als unkomprimiertes SVG speichern
  3. Perl-Script zum Ersetzen der Labels über die Vorlage laufen lassen
  4. Am Ende vier JPG-Dateien in Empfang nehmen 🙂

Auch wenn das ganze sehr auf meinen persönlichen Anwendungsfall gemünzt ist, glaube ich, dass es doch einige Anhaltspunkte und Grundlagen zur Erweiterung oder individuellen Anpassung bietet. Ich habe zu diesem Zweck eine Blanko-Vorlage erstellt, die ihr hier zusammen mit dem Script herunterladen und an eure Bedürfnisse anpassen könnt.

In diesem Script werden vier Blanko-Banderole-Ebenen verarbeitet (Banderole1-4), diese werden tatsächlich auch in einer for-Schleife (juhuu!) verarbeitet und sogar der Bandname kann angepasst werden. Zieht’s euch rein!

Ich übernehme keine Verantwortung für irgendeinen Murks, den ihr damit baut, arbeitet mit Backups! Alles ohne jede Garantie, aber dafür for free, auch in jeglicher Hinsicht bezüglich der Verwendung! Creditz welcome!

Viel Spaß damit!

Links

Musik Individuell – musik-individuell.de

InkScape – https://inkscape.org/de/

Perl – https://www.perl.org/