Samstag, 15. August 2015

Python: PIL/Pillow - Bild anzeigen mit Image.show() ( xv vs. xli vs. command vs. imagemagick)

Möchte man sich mit Hilfe von Python ein Bild anzeigen lassen, dann geht das mit drei Zeilen Code und dem Pillow-Modul. Pillow ist ein aktiv entwickelter "friendly fork" von PIL (=Python Image Library). PIL bzw. Pillow sind der quasi-Standard unter Python, wenn es das Handling und Manipulieren von Grafik-Dateien geht.

Die folgenden drei Codezeilen zeigen das Bild "foo.png" an:

>>> from PIL import Image
>>> im = Image.open('foo.png')
>>> im.show()

So zumindest die Theorie. In der Praxis passiert aber zumindest unter Ubuntu 14.04 und auch unter Raspbian nichts. Nach dem Aufruf von im.show() wird sofort wieder der Prompt des Python-Interpreters angezeigt. Keine Fehlermeldung, aber auch kein Bild.

Ein Blick in die Doku von Pillow brachte dann ans Tageslicht, dass die show-Funktion den Bildbetrachter xv aufruft. Dieser war in den 90er Jahren des vorherigen Jahrhunderts wohl mal der de-facto Bildbetrachter auf Unix-System, ist heute jedoch (ziemlich) veraltet. Und damit auch in den meisten Distributionen wie Debian, Ubuntu etc. nicht mehr enthalten.

Um trotzdem Bilder mittels show() anzeigen zu können, gibt es zwei alternative Wege:

Einer ist, das Paket imagemagick zu installieren. Dieses ist für quasi alle Linux-Distributionen in den Paketquellen enthalten.
Danach funktioniert der Aufruf von show(), es wird display aus dem ImageMagick-Paket zur Anzeige des Bilds verwendet. Leider ist dieser Weg in der Doku von Pillow nicht dokumentiert. Ein Blick in den Quellcode von von Image zeigt aber, dass Pillow auf Linux-Systemen explizit nach display sucht und diese - sofern vorhanden - verwendet.

Eine andere Alternative ist im Raspberry Pi Forum zu finden. Hier wird also das Paket xli (xli="command line tool for viewing images in X11") installiert und ein Softlink von xv auf xli angelegt:

$ sudo apt-get install xli
$ cd /usr/local/bin
$ sudo ln -s /usr/bin/xli xv


Dies funktioniert sowohl unter Raspbian als auch unter Ubuntu (und vermutlich auch bei den meisten anderen Linux-Distros, die xli in den Paketquellen haben.

Die show-Funktion kennt das optionale Argument command=, mit dem der Bildbetrachter explizit vorgegeben werden. So steht es jedenfalls in der Dokumentation. Allerdings hat das auf keinem meiner Systeme funktioniert.

Montag, 3. August 2015

Python: Ausrichten von Paragraphen in Platypus / ReportLab

Paragraphen von Reportlabs Playtypus sind dazu da, "normalen" Text aufzunehmen und dann im PDF-Dokument darzustellen. Und wie in jeder anderen Textdarstellung auch hat der Text in diesen Paragraphen natürlich auch einen Ausrichtung. Diese ist per Voreinstellung linksbündig.

Nun gibt es aber auch genug Anwendungsfälle, wo man den Text nicht linksbündig ausrichten möchte. So bietet es sich bei längern Textpassagen an, den Text als Blocksatz zu formatieren, damit das rechte Textende nicht "flattert".

Das ist natürlich auch mit ReportLab / Platypus auch ohne Probleme möglich. Es werden alle vier gängigen Textformatierungen unterstützt: linksbündig, rechtsbündig, zentriert und Blocksatz.

Wie das geht, ist im folgenden Beispiel gezeigt. Für rechtsbündig, zentriert und Blocksatz wird einfach das bestehenden Stylesheet um neue Styles erweitert, die die entsprechenden Ausrichtung haben:

from reportlab.platypus import Paragraph, SimpleDocTemplate
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY
from reportlab.lib.pagesizes import A4

text = '''Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam 
voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita 
kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.'''

text_short = 'Lorem ipsum dolor sit amet'

styles = getSampleStyleSheet()
styles.add(ParagraphStyle(name='RightAlign',
                          parent=ParagraphStyle('Normal'),
                          alignment=TA_RIGHT))
styles.add(ParagraphStyle(name='CenterAlign',
                          parent=ParagraphStyle('Normal'),
                          alignment=TA_CENTER))
styles.add(ParagraphStyle(name='JustifyAlign',
                          parent=ParagraphStyle('Normal'),
                          alignment=TA_JUSTIFY))
doc = SimpleDocTemplate('align_test.pdf', pagesize=A4)
story = []
story.append(Paragraph(text_short, styles['Normal']))
story.append(Paragraph(text_short, styles['RightAlign']))
story.append(Paragraph(text_short, styles['CenterAlign']))
story.append(Paragraph(text, styles['JustifyAlign']))
doc.build(story)

Führt man den obigen Code aus, dann enthält dieser vier Paragraphen: der erste ist linksbündig ausgerichtet, der zweite rechtsbündig, der dritte zentriert und der vierte als Blocksatz.

Intern verwendet ReportLab für die Ausrichtung Integer-Werte, die importierten Konstanten TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY stellen diese einfach nur dar, wie man leicht im interaktiven Python-Interpreter sehen kann:

>>> from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY
>>> TA_CENTER
1
>>> TA_LEFT
0
>>> TA_RIGHT
2
>>> TA_JUSTIFY
4

>>>

Im obigen Codebeispiel könnte man also statt

styles.add(ParagraphStyle(name='JustifyAlign',
                          parent=ParagraphStyle('Normal'),
                          alignment=TA_JUSTIFY))
auch schreiben:

styles.add(ParagraphStyle(name='JustifyAlign',
                          parent=ParagraphStyle('Normal'),
                          alignment=4))

Das Ergebnis ist das gleich und man würde den Import der TA_* Konstanten "sparen".

Samstag, 1. August 2015

WTForms: Daten für SelectField nachträglich einfügen

WTForms gehört in der Python-Welt zu den populäreren Modulen zum Generieren und Validieren von HTML-Formularen für Webanwendungen.

Hier im Blogeintrag geht es aber nicht um WTForms im gesamten - die offizielle Doku ist ja so wie so recht ausführlich - sondern "nur" um das nachträgliche Hinzufügen von Auswahlmöglichkeiten für ein SelectField (=Auswahlfeld). Wobei das im folgenden gezeigt auch für das RadioField gilt.

Vorab noch: Alle folgenden Beispiele sind mit WTForms 2.0.2 unter Python 3.4 getestet. Der Einfachheit wird alles im interaktiven Python-Interpreter auf der Kommandozeile durchgespielt. Das Vorgehen in realen Webanwendungen ist aber identisch.

Eine einfaches Formular mit einem SelectField sieht z.B. so aus:

>>> from wtforms import Form, StringField, SelectField
>>> class TestForm(Form):
...     name = StringField('name')
...     gender = SelectField('gender',

            choices=[('m', 'male'),
                     ('f', 'female')])
>>>

Zuerst werden die benötigten Klassen importiert, dann wird eine eigene Formularklasse Namens TestForm definiert, welche die zwei Felder name und gender hat. gender ist dabei ein  Auswahlfeld.

Die Auswahlmöglichkeiten werden über das choices-Attribute festgelegt. Diesem wird eine Liste von Tupeln zuwiesen, wobei das erste Element der value der Option ist und das zweite Element der angezeigte Text.

Erzeugt man nun eine neue Instanz der Klasse

>>> my_form = TestForm()

kann man sich das HTML-Markup, welche das Formular erzeugt, auch direkt auf der Kommandozeile anzeigen lassen, z.B. für gender:

>>> str(my_form.gender)
'<select id="gender" name="gender"><option value="m">male</option><option value="f">female</option></select>'

>>>

Jetzt gibt es aber natürlich auch genug Anwendungsfälle, wo man in einer Webanwendung ein Auswahlfeld dynamisch befüllen will und somit die Werte bei der Definition der Klasse noch nicht kennt.

Auch das ist natürlich mit WTForms problemlos möglich und zwar indem man das choices-Attribut erst später mit Daten füttert. Dies wird im folgenden Beispiel gezeigt:

>>> class TestForm2(Form):
...     name = StringField('name')
...     fav_color = SelectField('favourite color')
...
>>> form2 = TestForm2()


Wie zu sehen ist, wird für das SelectField kein Wert für choices übergeben. Folglich kann auch kein HTML generiert werden, stattdessen wird ein Fehler geworfen:

>>> str(form2.fav_color)
Traceback (most recent call last):
...
TypeError: 'NoneType' object is not iterable
>>>


Versorgt man das choices-Attribute mit Daten, dann funktioniert alles:

>>> my_choices = [('w', 'white'),
                  ('r', 'red'),
                  ('b', 'blue')]
>>> form2.fav_color.choices = my_choices
>>> str(form2.fav_color)
'<select id="fav_color" name="fav_color"><option value="w">white</option><option value="r">red</option><option value="b">blue</option></select>'

>>>

In einer realen Anwendung würde die Liste natürlich nicht hard-coded im Quelltext stehen, sondern z.B. aus einer Datenbankabfrage kommen.

Wie andere Field-Klassen auch kennt SelectField ebenfalls einen Default-Wert, was sich im HTML-Formular dann so darstellt, dass diese Option vorausgewählt ist.

Wollte man z.B. im ersten Beispiel female als Default-Wert haben, dann müsste die Zeile im Code so geändert werden:

gender = SelectField('gender',
            choices=[('m', 'male'),
                     ('f', 'female')],
            default='f')

Will man beim 2. Beispiel  die Farbe weiß als Default-Wert setzen, dann muss man das default-Attribute ebenfalls mit einem Wert versorgen:

>>> form2.fav_color.default='w'
>>> str(form2.fav_color)
'<select id="fav_color" name="fav_color"><option value="w">white</option><option value="r">red</option><option value="b">blue</option></select>'
>>> form2.fav_color.default
'w'
>>>


Das alleine reicht aber nicht, wie im obigen Code zu sehen ist, hat sich am generierten HTML aber nichts geändert, obwohl der Default-Wert korrekt gesetzt ist.

Damit das HTML wie gewünscht ist, muss ein weiterer Schritt durchgeführt werden, und zwar der Aufruf der process-Methode der Klasse: :

>>> form2.process()
>>> str(form2.fav_color)
'<select id="fav_color" name="fav_color"><option selected value="w">white</option><option value="r">red</option><option value="b">blue</option></select>'
>>>


So ist auch der Default-Wert korrekt gesetzt.

Sonntag, 26. Juli 2015

Python: Tabellen in ReportLab

ReportLab ist die wohl leistungsfähigste Bibliothek für Python, wenn es um das Erzeugen von PDF-Dateien geht. Von High-Level Zugriffen via Platypus (=Page Layout and Typography Using Scripts) bis zu Low-Level mit direktem Zugriff auf den Canvas bietet ReportLab alles.

Tabellen unterstützt Platypus mit Hilfe der Table-Klasse (welche ein Flowable darstellt) direkt und die Verwendung ist auch erst Mal nicht weiter schwierig.

Es soll ein einfaches PDF-Dokument mit Hilfe von ReportLab gebaut werden. Das Dokument besteht aus einem Absatz Text, einer Tabelle und einem Absatz Text nach der Tabelle.

Der Code dafür sieht im einfachsten Fall so aus:

from reportlab.platypus import Paragraph, Table, SimpleDocTemplate
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import A4


my_data = [['foo', 'bar', 'spam'],
           ['egg', 123, 'python'],
           ['a bit longer', 666, '9B']]
          
styles = getSampleStyleSheet()
doc = SimpleDocTemplate('table_test.pdf', pagesize=A4)
story = []
story.append(Paragraph('Text vor Tabelle', styles['Normal']))
story.append(Table(my_data))
story.append(Paragraph('Text nach Tabelle', styles['Normal']))
doc.build(story)


my_data enthält die Daten für die Tabelle, der Rest ist weitestgehend selbsterklärend.

Nur das das Dokument nicht gerade "hübsch". Das Ergebnis ist im folgenden Screenshot zu sehen:

Standardmäßig werden für die Tabelle keine Rahmen gezeichnet und die Tabelle ist horizontal auf der Seite zentriert. Außerdem ist der Text sehr nah an der Tabelle.

Das ist im folgenden Code korrigiert:

from reportlab.platypus import Paragraph, Table, TableStyle, SimpleDocTemplate
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors

my_data = [['foo', 'bar', 'spam'],
           ['egg', 123, 'python'],
           ['a bit longer', 666, '9B']]
          
styles = getSampleStyleSheet()
doc = SimpleDocTemplate('table_test.pdf', pagesize=A4)
story = []
story.append(Paragraph('Text vor Tabelle', styles['Normal']))
t = Table(my_data)
t.hAlign = 'LEFT'
t.spaceBefore =  10
t.spaceAfter = 10
t.setStyle(TableStyle(

    [('BOX', (0,0), (-1,-1), 0.5, colors.black),
     ('INNERGRID', (0,0), (-1,-1), 0.5, colors.black)]))
story.append(t)
story.append(Paragraph('Text nach Tabelle', styles['Normal']))
doc.build(story)


Das ganze liefert als Ergebnis folgendes:
Über das Attribute hAlign wird die Tabelle die Ausrichtung der Tabelle bestimmt, der Wert LEFT richtet sie linksbündig aus. Mit den Attributen spaceBefore und SpaceAfter wird zusätzlicher Leerraum (hier: 10 px) eingefügt.

Die Tabelle an sich wird über die setStyle-Methode formatiert, welche als Wert die TableStyle-Klasse übergeben bekommt. Diese erlaubt einen detaillierten Eingriff in die Formatierung der Tabelle.

Die Werte für TableStyle werden als Liste von Tupeln übergeben. Im obigen Beispiel wird mit BOX ein Rahmen um die Tabelle gezeichnet und INNERGRID zeichnet den inneren Rahmen, beide in schwarz.
Die Tupel (0, 0), (-1, -1) definieren den Geltungsbereich der Formatierung. (0,0) ist dabei die Zelle links oben, (-1, -1) die Zelle rechts unten - somit ist also die ganze Tabelle abgedeckt.

Möchte man z.B. das innere Gitter mur für einen Teil der Zellen zeichnen und in der mittleren Zelle soll die Schrift rot sein, so ist die Codezeile wie folgt anzupassen:

t.setStyle(TableStyle(
    [('BOX', (0,0), (-1,-1), 0.5, colors.black
     ('INNERGRID', (0,0), (1,1), 0.5, colors.black),
     ('TEXTCOLOR', (1,1), (1,1), colors.red)]))

Und das Ergebnis sieht dann so aus:


TableStyle bietet eine Vielzahl weiterer Möglichkeiten, dass Aussehen der Tabelle zu beeinflussen. Eine komplette Übersicht ist im Kapitel 7 der ReportLab Dokumentation zu finden.

So ist es z.B. auch möglich, Zellen innerhalb einer Spalte oder Zeile zusammenzufassen. Möchte man z.B. die drei Zellen der ersten Zeile zusammenfassen, so ist der obiger Code wie folgt zu ändern:

...
my_data = [['Überschrift', '', ''],
           ['egg', 123, 'python'],
           ['a bit longer', 666, '9B']]

....
t.setStyle(TableStyle(
    [('BOX', (0,0), (-1,-1), 0.5, colors.black),
     ('INNERGRID', (0,0), (-1,-1), 0.5, colors.black),
     ('SPAN', (0, 0), (-1, 0))]))


Das sieht im Ergebnis so aus:

Die bisher generierten Tabellen ermitteln die Breite der Spalten und Höhe der Zeilen automatisch, aber auch das kann natürlich beeinflusst werden. Möchte man z.B. eine fixe Spaltenbreite  von 100 px haben, so ist im Code die Zeile wie folgt zu ändern:

t = Table(my_data, colWidths=100)

Weitere Möglichkeit sind ebenfalls im Kapitel 7 der Dokumentation zu finden.

In den obigen Beispielen wird der Table-Klasse einfach eine Liste von Strings bzw. Integern übergeben. Man kann aber auch eine Liste von Flowables übergeben, was sich anbieten, wenn man z.B. längeren Text oder Grafiken in der Tabelle unterbringen möchte.

Des Weiteren gibt es noch die Klasse LongTable, welche anstellen von Table verwendet werden kann. Das Ergebnis ist laut Dokumentation das gleiche, allerdings soll LongTable bei sehr langen Tabelle etwas performanter sein.

Wie hier gezeigt wird, ist das Generieren einer Tabelle in einem mit Platypus gebauten PDF-Dokuments in ReportLab nicht weiter schwierig. Wie bei ReportLab üblich kann man auf Wunsch aber auch sehr detailliert in die Gestaltung und das Layout der Tabelle eingreifen.

Alle Beispiele aus diesem Blogbeitrag sind unter Python 3.4 und ReportLab 3.0 erstellt.

Sonntag, 19. Juli 2015

Grafikfehler auf Lenovo T450 und die passende Abhilfe

Seit ein paar Monaten nutze ich beruflich ein Lenovo T450 Thinkpad in der Varianten mit Intel Grafik (und ohne zusätzlich Nvidia Grafik).

Ubuntu 14.04 läuft darunter auch ohne Probleme, nur einen kleinen "Schönheitsfehler" gab es: nach dem Login war manchmal die Beschriftung der Symbole / Dateien auf dem Desktop unvollständig. Unvollständig heißt, dass einzelne oder ganz selten auch alle Buchstaben fehlten. Das kam bei ca. 50% der Logins vor. "Meine" Abhilfe war: abmelden, neu anmelden und alles war ok - zumindest in 95% der Fälle. Selten musst ich mich auch nochmal abmelden und einloggen, damit alles war, wie es sein sollte.

Auch mit dem oben beschriebenen Grafikfehler war das System uneingeschränkt benutzbar. Das Fehlen der Buchstaben trat z.B. auch im Menü der Systemeinstellung und in der Tab-Beschriftung des Firefox auf. Der Inhalt der Programmfenster an sich (z.B. die vom Firefox dargestellte Seite, die Liste der Lieder in Quod Libet etc.) war aber immer korrekt und fehlerlos.

Der Grafikfehler war zwar etwas nervig, aber die "Lösung" "Ausloggen, Einloggen" war so schnell und funktionierend, dass ich nie weiter für den Grund des Problems gesucht habe.

Anfang Juli hatte ich dann im Planet von ubuntuusers.de einen Beitrag des Intux-Blogs gelesen, in dem genau das gleiche Problem und eine passende Lösung für ein Thinkpad E550 beschrieben wurde.

Und das ist auch genau die Lösung, die beim Thinkpad T450 mit Intel GraKa funktioniert. Das ganze geht unter Ubuntu 14.04 in vier Schritten:
  • die Verzeichnis /etc/X11/xorg.conf.d/ anlegen
  • darin die Datei 20-intel.conf anlegen
  • die Datei mit folgendem Inhalt befüllen
    Section "Device"
       Identifier  "Intel Graphics"
       Driver      "intel"
       Option      "AccelMethod"  "uxa"
    EndSection
  • abmelden und wieder anmelden
Zumindest bei mir sind seitdem die oben beschrieben Grafikfehler nicht mehr aufgetreten.

An diese Stelle nochmal Danke an den Intux-Blog und zum Abschluss nochmal der volle Link auf den Blogartikel: http://www.intux.de/2015/07/das-thinkpad-e550-und-die-grafik/

Sonntag, 28. Juni 2015

Mair 1 Festival 2015, 2. Tag - Expire, Nasty, Suicidal Tendencies, Terror

Am, 27.6.2015, dem 2. Tag des Mair 1 Festivals 2015, ging es für mich so früh wie noch nie zum Mair 1, nämlich bereits um 14:30 Uhr. Der Grund: um 14.50 Uhr war der Auftritt von Expire - und den wollte ich nicht verpassen.

Als ich ankam, waren auf der großen Bühne gerade (die mir bis dato völlig unbekannten) Bury My Regrets zugange. Das ist eine deutsche Hardcore Band, welche ein solides, gut anzusehende und -hörendes Konzert ablieferten.

Jetzt kam Expire auf die kleinere Bühne. Die CDs von denen gefallen mir sehr gut, also war da schon eine gewisse Erwartungshaltung für das Konzert vorhanden.
Und die wurde nicht enttäuscht. Live hatte die Band genau so viel Power wie auf CD und mehr Energie als ein Dampfhammer mit Überdruck. Aufgrund der (für ein Festival) relativ frühen Stunde waren zwar noch nicht so viel Leute vor Ort, aber das vorhandene Publikum ging trotzdem voll bei dem sehr guter Auftritt von Expire mit.
Expire live auf der Bühne
Danach ging es dann erst Mal wieder nach Hause, weil mich dann erst so richtig wieder die Bands interessierten, die am Abend spielten.

Gegen 19.50 Uhr war ich dann auch wieder auf dem Festivalgelände. Zum dem Zeitpunkt waren noch Caliban auf der großen Bühne. Davon habe ich allerdings nichts gesehen, weil gerade am Eingang zum Festivalgelände eine Motocross Freestyle Show begann. Und so fliegende Motorräder aus der Nähe zu betrachten ist schon recht beeindruckend.

Um 20 Uhr starteten dann Nasty ihre Konzert. Die kannte ich bisher nur vom Namen. Die Band aus dem deutschsprachigen Teil von Belgien lieferten eine gute Show mit viel Interaktion mit dem Publikum ab. Das ging auch ziemlich mit, auch wenn ich so das Gefühl hatte, dass der entscheidende Funk nicht überspringen wollte.

Um 20.50 Uhr war es dann so weit - die Suicidal Tendencies betraten die große Bühne. Lebende Legenden, Veteranen des Hardcore, Inkarnation des Skatepunk und ich behaupte mal so was wie die heimlichen Headliner des Mair 1 2015.
Und: die Show war super gut, ohne wenn und aber. Mike Muir flitze eine Stunde lang wie ein angestochenes Schwein über die Bühne und auch der Rest der Band war voll dabei. Suicidal Style halt.
Zum Song "Possessed to Skate" holte man sich dann noch mal schnell ca. 50 Leute aus dem Publikum auf die Bühne, um dort abzufeiern.
Insgesamt lieferten die Suicidal Tendencies einen tollen, kurzweiligen Auftritt.
ST auf der Bühne des Mair 1
Auf der kleineren Bühne folgten dann Terror. Die Band kannte ich bis dahin auch nur vom Namen und hatte aufgrund dessen eher Metalcore vermutet. Ist es aber nicht - Terror spielt Hardcore-Punk mit leicht metall-lastigem Sound.
Aufgrund einer Rückverletzung des Sängers spielte Terror wohl in leicht veränderter Besetzung, was der Sache aber keine Abbruch tat. Es war eine energie-geladene Hardcore Show, hat mir gut gefallen. Vielleicht sollte ich mir doch mal eine CD von denen kaufen.
Terror live auf dem Mair 1
Den Abschluss des Mair 1 bildeten Millencolin. Und obwohl ich ja gerne Punkrock - auch von schwedischen Bands - höre, konnte ich mit Millencolin noch nie was anfangen. Und, was soll ich sagen: das war auch hier live auf dem Mair 1 nicht anders. Drei Songs angeschaut und nach Hause gegangen. Das ist nichts für mich.

Das Mair 1 war auf jeden Fall auch 2015 ein gutes Festival, was einen Besuch wert war. Ich freue mich schon auf das Mair 1 2016!

Samstag, 27. Juni 2015

Mair 1 Festival 2015, 1. Tag - Strung Out, Agnostic Front, Breakdown of Sanity

Am 26. und 27.6.2015 war es wieder so weit: das Mair 1 Festival 2015 findet statt, an der bekannten Location in der Nähe von Montabaur.

Das Lineup ist wie in den Jahren zuvor schon ziemlich gut, was die Musikrichtungen Hardcore, Metalcore und Punk betrifft, auch wenn mich dieses Jahr die Bands nicht sooo ansprechen (im Vergleich zu einigen anderen Lineups aus den Vorjahren).

Nichts desto trotz ging es auch dieses Jahr wieder zum Mair 1 - ist ja in der Nähe, die Atmosphäre ist immer entspannt und die Organisation sehr gut.

Als ich am 1. Festivaltag ankam, spielten gerade die Emil Bulls auf der großen Bühne ihre letzten beiden Stücke. Klang ganz ok, mehr kann ich dazu nicht sagen.

Danach kamen Breakdown of Sanity auf die kleinere Bühne. Diese Band war mit bis dato völlig unbekannt, aber nach den ersten paar Takten war klar: das ist wohl Metalcore mit Hang zum düsteren - also musikalisch nicht ganz so mein Fall.
Die Show war gut und kam auch beim Publikum sehr gut an, aber leider war der Sound bei der ersten paar Liedern schlecht - außer der wummernden Double-Bass (der in allen Liedern ausgiebig zum Einsatz kommt) und dem Gesang war nicht viel zu hören. Zum Glück wurde das nach drei oder vier Liedern besser und der Sound war so, wie er sein sollte.

Danach waren dann die Bands an der Reihe, weswegen ich heute eigentlich zum Mair 1 gefahren war: Agnostic Front und Strung Out.

Pünktlich betraten Agnostic Front um 20.50 Uhr die große Bühne. Das Urgestein der New York Hardcore fackelte nicht lange und gab ab dem ersten Song Vollgas, so dass der Funke auch direkt auf's Publikum übersprang.
Und wer schon, wie Agnostic Front, seit übr 30 Jahren im Geschäft ist, der weiß natürlich auch, wie man eine gute Hardcore Show aufzieht und spielt. Und so war dieses Konzert eine kurzweilig, unterhaltsame Stunde. Der letzte Song war nochmal ein Highlight, da Agnostic Front den Ramons Song "Blitzkrieg Bop" brachten. Sehr schön.
Agonstic Front auf der Bühne
Dann waren Strung Out auf der kleineren Bühne an der Reihe. Von denen habe ich nur eine CD, das bereits 1992 erschienene Album "Suburban Teenage Wasteland Blues". Und da Strung Out in deren Songs doch trotz kalifornischem Punk Rock einen Hang zum dunklen und melancholischen hat war ich zugegebener Maßen etwas skeptisch, wie die Show werden würde.

Um es direkt zu sagen: die Skepsis war völlig unbegründet. Strung Out spielten ein mitreisendes, Enerigie-gelandenes Konzert vom ersten bis zum letzten Takt. Sehr guter Sound und auch der von der Band oft eingesetzte mehrstimmige Gesang kam live klar und druckvoll rüber.
Unter Strich muss ich dann auch sagen: Strung Out war mit Sicherheit einer der besten Auftritte, die ich bisher auf dem Mair 1 gesehen habe. Super gut.

Strung Out auf der Bühne
Den Abschluss machten dann die Guana Apes. Musikalisch so wie so nicht mein Fall und ehrlich gesagt fand ich die Band aus der Sparte "Rock mit Hang zum Mainstream" auf dem Mair 1 auch ein wenig deplatziert. Zwei Songs habe ich mir angeschaut, bevor ich gefahren bin. Publikum war aber definitiv noch genug da, also an Popularität mangelt es den Guana Apes scheinbar nicht.

Auch wenn ich ja "nur" drei Bands ganz gesehen hatte war's ein guter 1. Tag auf dem Mair 1 2015. Schon alleine wegen des tollen Konzerts von Strung Out.

Donnerstag, 30. Oktober 2014

Erst Upgrade, dann Neuinstallation - von Ubuntu 12.04 nach 14.04

Letztes Wochenende war es soweit: das Update von Ubuntu 12.04 auf 14.04 stand auf dem Laptop an, den ich beruflich nutze (ein Lenovo T410). Die Prozedur ist ja nicht weiter schwierig und z.B. auch im Wiki von ubuntuusers.de beschrieben.

Dank schnellem Internetzugang ging das ganze auch recht zügig von statten und das Upgrade lief problemlos und fehlerfrei durch.

Nach dem Reboot lief auch alles rund, also alles gut. Dachte ich zumindest. Beim ersten Starten von Empathy unter Trusty war die Darstellung der Chats auf die Standardansicht zurückgesetzt, besser gefällt mir aber eine andere die Darstellung.

Kein Problem, kann man ja umstellen. Nur: im Empathy-Fenster fehlte das komplette Menü "Empathy", welches auch den Menüpunkt "Einstellungen" enthält. Da ich auf meinem privaten Laptop aber schon länger Ubuntu 14.04 installiert habe war ich mir sehr sicher, dass das Menü normalerweise da ist. Hm...

Nun, es gibt schlimmeres, von daher erst Mal weiter gemacht. Aber dieses Phänomen wollte dann später doch untersucht werden. Also mal testweise den Editor "gedit" gestartet. Alle Menüpunkte vorhanden, aber im Menü "Bearbeiten" fehlte auch hier der Punkt "Einstellungen". Das gleiche auch in Rhythmbox - der Menüpunkt zum erreichen der Einstellungen fehlt ebenfalls.

Da mir so gar nicht danach war, diesen Fehler in den Untiefen des Systems zu suchen, habe ich kurzerhand die Installations-DVD heruntergeladen, die wenigen Daten, die noch nicht gesichert waren, gesichert und dann Ubuntu 14.04 neu installiert.

Das lief - wie nicht anders zu erwarten - problemlos und das neue System war jetzt wirklich einsatzbereit. Inklusive der Einstellungs-Menüs, die sind jetzt wieder vorhanden.

Die Neuinstallation ist für mich übrigens nicht weiter tragisch, da ich auf dem beruflich genutzten Laptop quasi ein "Vanilla-Ubuntu" (=keine Anpassungen) nutze. Ich habe zusätzlich nur ein paar Pakete aus den offiziellen Quellen (Apache, MySQL, CouchDB) und ein paar Python-Module installiert, sonst nichts. Und die Dateien im persönlichen Ordner (Musik, Code, Bilder, Dokumente für die Arbeit) werden so wie so regelmäßig gesichert.

Wer das gleiche Problem auch schon hatten und eine Lösung ohne Neuinstallation kennt, der kann dies gerne als Kommentar zu diesem Blogbeitrag posten.

Samstag, 18. Oktober 2014

Python: ListFlowables in ReportLab

ReportLab ist die wohl leistungsfähigste Bibliothek für Python, wenn es um das Erzeugen von PDF-Dateien geht. Von High-Level Zugriffen via Platypus (=Page Layout and Typography Using Scripts) bis zu Low-Level mit direktem Zugriff auf den Canvas bietet ReportLab alles.

Allerdings hat es doch bis zu ReportLab Version 2.6 (erschienen im Oktober 2012) gedauert, bis das direkte Erzeugen von nummerierten und unnummerierten Listen möglich war. Dies war zwar vorher prinzipiell auch möglich, aber nur über Umwege (z.B. bei unnummerierten Listen durch das manuelle hinzufügen eines Bullet Points vor dem Text).

Mit dem seit ReportLab 2.6 vorhandenen Flowable namens "ListFlowable" geht das aber wesentlich einfacher.

Neulich, bei der Überarbeitung eines meiner älteren Python-Skripte, welches auch ReportLab einsetzt, habe ich auch auf diese Flowable umgestellt.

Da die Doku von ReportLab zu diesem Thema - leider - etwas dünn ist, habe ich hier in diesem Blogebeitrag mal ein paar Sachen dazu aufgeschrieben.

Grundsätzlich ist die Sache recht simpel. Nach dem Import der benötigten Module erstellt man ein ListFlowable, welches die Punkte der Liste als ListItems enthält. Der Quelltext für eine Liste mit drei Punkten sieht so aus:

from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import A4
from reportlab.platypus import Paragraph, SimpleDocTemplate, ListFlowable, \
                               ListItem

styles = getSampleStyleSheet()
style = styles['Normal']
story = []

t1 = ListFlowable([ListItem(Paragraph('foo', style)),
                   ListItem(Paragraph('bar', style)),
                   ListItem(Paragraph('spamegg', style))])
story.append(t1)
doc = SimpleDocTemplate('testdoc.pdf', size=A4)
doc.build(story)

So wird eine nummerierte Liste erzeugt, der Standard des ListFlowables, wenn keine weiteren Vorgaben gemacht werden.

Möchte man eine unnummerierte Liste erzeugen, muss dem ListFlowable das Argument bulletType und start mitgegeben werden:

t1 = ListFlowable([ListItem(Paragraph('foo', style)),
                   ListItem(Paragraph('bar', style)),
                   ListItem(Paragraph('spamegg', style))],
                   bulletType='bullet',
                   start='circle')

Möchte man statt Kreisen als Bullet Points z.B. Quadrate haben, dann ersetzt man einfach start='circle' durch start='square'.

Wer aus den obigen Beispielen ein PDF generiert wird feststellen, dass sowohl die Zahl als auch die Bullet Points ziemlich groß sind, größer als der Text, formatiert mit dem Beispiel-Stylesheet aus ReportLab. Fügt man noch das Argument bulletFontSize hinzu, gefolgt von der gewünschten Schriftgröße (also z.B. bulletFontSize=8), dann werden Schrift bzw. Bullet Points in der angegebenen Schriftgröße dargestellt.
Alternativ kann man für unnummerierten Listen auch folgendes angeben: start='bulletchar'. Dann wird als Zeichen standardmäßig ein kleiner Punkt (HTML: &middot;) verwendet.

Bei nummerierten Listen kann aber ReportLab aber nicht nur mit "normalen" Zahlen zählen, sondern auch:
bulletType='i' # römische Zahlen
bulletType='a' # Zählung: a, b , c, ...
bulletType='A' # Zählung: A, B, C, ...

Mit dem Argument start kann  man den Beginn der Zählung bei nummerierten Listen den Beginn der Zahlung festlegen. bulletType='A', start='10' würde z..B. die Zählung der Liste bei J starten lassen.

Weiterhin werden standardmäßig alle Listen linksbündig ausgerichtet. Möchte man die Listen gegenüber dem Text etwas einrücken, dann muss man die für die Aufzählungszeichen und den zugehörigen Text separat machen, wie z.B.:

p1 = Paragraph('etwas Text', style)
t1 = ListFlowable([ListItem(Paragraph('foo', style), leftIndent=25),
                   ListItem(Paragraph('bar', style), leftIndent=25),
                   ListItem(Paragraph('spamegg', style),leftIndent=25)],
                   bulletType='bullet',
                   start='bulletchar',
                   leftIdent=10)
story.append(p1)
story.append(t1)
story.append(p1)

Und das ListFlowable kann natürlich auch verschachtelt werden, für verschachtelte Liste. Eine Liste mit zwei Ebenen sieht z.B. so aus:

t1 = ListFlowable([ListItem(Paragraph('foo', style)),
                   ListItem(Paragraph('bar', style)),
                   ListFlowable([ListItem(Paragraph('spam', style)),
                                 ListItem(Paragraph('egg', style))],
                                 bulletType='a'),
                   ListItem(Paragraph('more', style))])

Ein paar weiter Tipps zum ListFlowable und dessen Formatierungsmöglichkeiten gibt es auch in einem Thread bei stackoverflow.

Dienstag, 29. Juli 2014

Bob Mould "Beauty & Ruin" - Rezension

Am 3.6.2014 war es mal wieder so weit: Bob Mould, legendärer Frontmann von Hüsker Dü, hat sein 13. Studioalbum namens "Beauty & Ruin" veröffentlicht.

Darauf enthalten sind 12 Tracks mit einer Gesamtspielzeit von 36 Minuten. Nach dem letzten, sehr guten Release "Silverage" aus dem Jahr 2012 ist es spannend zu sehen, ob Bob Mould nochmals so ein fulminantes Album abliefern kann.

Der erste, spontante Eindruck meinerseits beim 1. Lied: "Hm, klingt ja wie Sugar" (was ja keineswegs schlecht ist). Musikalisch schwankt das ganze Album dann immer zwischen Bob Mould und Sugar, wobei auf dem Album auch zwei, drei ruhiger Stücken enthalten sind.

Insgesamt ist das alles aber sehr gut zu hören und auch das ganze Album ist in sich sehr "rund". Aber: das Album hinterlässt keine bleibenden Erinnerung. Weder positiv noch negativ. Während bei "Siverage" eine Vielzahl der Lieder im Ohr bleiben, ist dies bei "Beauty & Ruin" nicht so.

Nichts desto trotz macht jeder neue Hördurchgang Spaß und langweilig wird's auch nicht. Nur die Nachhaltigkeit fehlt. Von daher ein sehr gutes und solides Album von Bob Mould, was absolut hörenswert ist. Aber auch kein echtes Highlight.