Umlaute in Dateinamen finden

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
fon77
User
Beiträge: 17
Registriert: Freitag 10. April 2009, 20:58

Hallo,

ich wollte mir ein kleines Python-Skript zusammentippen, dass mir alle Umlaute in den Dateinamen eines Verzeichnis umwandelt (also z. Bsp. "blöd.py" -> "bloed.py"). Eigentlich dachte ich, dass das ja eine ganz einfache Aufgabe ist, aber leider funktioniert mein kurzer Code (der erstmal nur die Dateinamen mit Umlauten auflisten soll) nicht:


Code: Alles auswählen

#! /usr/bin/env python
# coding: utf-8

import sys,  os,  re

def main():
    if len(sys.argv) > 1:
        pfad = sys.argv[1]
    else:
        pfad = os.getcwd()
    print "Suche Dateien und Verzeichnisse mit Umlauten in",  pfad
    liste = os.listdir(pfad)
    for n in liste:
        umlaute = 0
        filename = unicode (n)

        # RegEx findet keinen Match
        match = re.search("[äöüÄÖÜß]", filename)
        if match <> None:
            print filename,  match
        
        # Alternative bricht wegen UnicodeDecodeError ab
        for c in filename:
            if c in "äöüÄÖÜß":
                print filename
        
main()
Im untersuchten Verzeichnis gibt es beispielsweise die Datei "204 - Tatzes Rückkehr 5.mp3". Leider findet aber der regEx diese Datei nicht, und die Alternative wie im Code gezeigt bricht mit einem UnicodeDecodeError ab.

Was mach ich falsch??? :cry:
Zuletzt geändert von fon77 am Freitag 10. April 2009, 21:52, insgesamt 1-mal geändert.
BlackJack

@fon77: Der Quelltext ist UTF-8 kodiert, der reguläre Ausdruck ist aber eine folge von Bytes, d.h. es wird nicht nach den Buchstaben, oder besser Bytes gesucht die da angezeigt werden, sondern eben die in denen die kodiert sind *einzeln*. Das geht natürlich nicht.

Allerdings sollte das beim `unicode()`-Aufruf grundsätzlich krachen, wenn eine Datei mit Zeichen ausserhalb von ASCII im Verzeichnis liegt.

Bitte die Seite(n) über Unicode und Kodierungen im Wiki lesen.
fon77
User
Beiträge: 17
Registriert: Freitag 10. April 2009, 20:58

Alles klar! Ich hab jetzt meinen RegEx so definiert:

Code: Alles auswählen

regex = u"[äöüÄÖÜß]"
und die Suche findet jetzt auch alle passenden Dateinamen:

Code: Alles auswählen

match = re.search(regex, filename)
Vielen Dank für die Hilfe!
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Eine Regular Expression ist nicht sonderlich gut geeignet. Ich würde dafür ein dict nehmen.

Code: Alles auswählen

#!/usr/bin/env python      
# encoding: utf-8          
import sys                 
import os                  
from operator import methodcaller


translation_table = {
        u'ä': u'ae', 
        u'ö': u'oe', 
        u'ü': u'ue', 
        }


def translate(string):
    return reduce(lambda x, y: unicode.replace(x, *y),
                  [string] + translation_table.items())


def main():
    try:
        _, path = sys.argv
    except ValueError:
        path = os.getcwd()
    for f_or_dir in map(methodcaller('decode', 'utf-8'), os.listdir(path)):
        if any(char in translation_table for char in f_or_dir):
            os.rename(f_or_dir, translate(f_or_dir))


if __name__ == '__main__':
    main()
BlackJack

@DasIch: Man könnte natürlich auch die `translate()`-Methode auf `unicode`-Exemplaren verwenden, statt das selber zu erfinden. :-)
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Die Neugier lässt mich fragen, warum fon77 denn bei Harry Potter Hörbüchern die Umlaute entfernen will. Und was ist, wenn da andere Sonderzeichen vorkommen, wie é oder ñ? Welches antike Dateisystem kommt heutzutage denn nicht mehr um dem vollen Unicode-Zeichenraum klar?

Nun, auch Python hat da seine Probleme.

Wollt ihr sehen, was Python 2.6 aus dem Dateinamen "Äßé" auf dem Mac macht? 'A\xcc\x88\xc3\x9fe\xcc\x81' Für Umlaute und andere Sonderzeichen gibt es nämlich zwei Kodierungen im Unicode-Zeichensatz. Auf dem Mac hat man sich entschieden, die ansonsten ungebräuchliche Variante zu wählen, wo das Grundzeichen A von dem Zeichen u+0308, den Umlautpunkten (¨), gefolgt wird bzw. das e von dem Zeichen u+0301, dem Akzent (´). Man würde eigentlich '\xc3\x84\xc3\x9f\xc3\xa9' erwarten.

Beide Repräsentation gelten allerdings nicht als gleich. Daher muss man normalisieren, etwa in dem man die Kodierung aus dem Dateisystem in die kanonische Form überführt, die ansonsten von Python benutzt wird.

Der korrekte Code, um einen Dateinamen einzulesen, lautet damit:

Code: Alles auswählen

names = [unicodedata.normalize("NFC", n.decode("utf-8")) for n from os.listdir(...)]
Inbesondere das ``unicode(n)`` ist einfach nur falsch.

Stefan

PS: Mir ist der Unterschied zwischen NFC und NFKC nicht klar. Laut FAQ ist sie für drei exotische Buchstaben anders, aber warum das so ich, sagen sie nicht... Ich finde, NFC reicht, weil W3C sich auch für diese Variante entschieden hat.
BlackJack

@sma: Die Formulierung "was Python […] aus dem Dateinamen "Äßé" auf dem Mac macht" verstehe ich nicht!? Python macht da gar nichts, das liest einfach die Bytes aus, die das Betriebssystem liefert. Und natürlich kann man nicht einfach `unicode()` ohne eine konkrete Kodierung auf beliebige Bytes loslassen. Das ist auf allen Systemen so.

Wenn man von MacOS keine Bytes sondern Unicode als Dateinamen anfragt, bekommt man dann auch "decomposed" Unicode? Und wenn man beim Datei anlegen "composed" übergibt und das wieder abfragt, funktioniert der "round trip"?
fon77
User
Beiträge: 17
Registriert: Freitag 10. April 2009, 20:58

sma hat geschrieben:Die Neugier lässt mich fragen, warum fon77 denn bei Harry Potter Hörbüchern die Umlaute entfernen will.
Naja, ich fürchte fon77 hatte da eine kleine Schnapsidee... ;)

Ich hab mir ein Programm geschrieben, mit dem ich mir eine Playlist zusammenstellen und diese abspielen kann. Im Programm gibt es auch eine Checkbox - wenn diese aktiviert ist, wird der Rechner nach Abarbeitung der Playlist runtergefahren. (Ich lieg gern abends im Bett und höre noch ein Kapitel eines Hörbuchs - da ist es schon schön, wenn der Rechner automatisch runterfährt zum Schluss). Leider hatte das Programm Probleme mit Dateien mit Umlauten - da ich das irgendwie nicht behoben bekommen hab dachte ich dass ich einfach den umgekehrten Weg nehme und alle Umlaute in Dateinamen umbenenne... *hüstel* Wie gesagt, eigentlich 'ne dämliche Idee...

Ich werd mich dann mal daran machen, die Tipps hier in meinem Hörspielplayer einzubauen, damit es auch so funktioniert!

Was mich aber doch wundert ist, dass wenn ich mein Programm von Eric4 aus starte alles wunderbar funktioniert (auch die alte verkorkste Version) - wenn ich es jedoch direkt von der Konsolte aus starte gibt's Probleme... Hmm...
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Deswegen würde ich bei Encoding Kram den Code immer über den Python Interpreter direkt ausführen und auch nicht den Code in der Python Shell ausprobieren, sonst kann es zu ziemlich seltsamen Verhalten kommen.
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

BlackJack hat geschrieben:Wenn man von MacOS keine Bytes sondern Unicode als Dateinamen anfragt, bekommt man dann auch "decomposed" Unicode? Und wenn man beim Datei anlegen "composed" übergibt und das wieder abfragt, funktioniert der "round trip"?
Wenn ich dich richtig verstanden habe, möchtest du das wissen:

Code: Alles auswählen

>>> fn = u'F\xc4' # FÄ
>>> f = open(fn, "w")
...
>>> glob("*")
['FA\xcc\x88']
...
>>> f = open(fn.encode("utf8"), "w") # 'F\xc3\x84'
...
>>> glob("*")
['FA\xcc\x88']
BlackJack

Fast. Wie sieht's denn mit einem ``os.listdir(u'.')`` aus? Was kommt da an Unicode-Zeichenkette zurück?
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Auch ein `os.listdir(u'.')` liefert decomposed unicode und muss normalisiert werden. Ein "Roundtrip" funktioniert nicht, denn OS X scheint jede Eingabe auf "decomposed" zu normalisieren. Windows macht es AFAIK genau anders herum. Es bleibt also schwierig :)

Stefan
fon77
User
Beiträge: 17
Registriert: Freitag 10. April 2009, 20:58

Wenn ich nochmal an das alte Thema anknüpfen dürfte... ;)

Ich habe also ein kleines Programm auf Qt-Basis geschrieben, mit dem ich mir eine Playlist erstellen und diese abspielen kann. Die Playlist wird gefüllt über die getOpenFileNames-Methode von QFileDialog, welche eine QStringTable zurückliefert.

Der Code dafür sieht so aus:

Code: Alles auswählen

    def slot_addfiles(self):
        list_filenames = QFileDialog.getOpenFileNames (self, "Dateien hinzufuegen...", "/home/frank/daten/hoerbuecher/", "MP3 Dateien (*.mp3)")
        if  not list_filenames.isEmpty():
            for n in list_filenames:
                n = unicode (n)
                n = unicodedata.normalize("NFC", n.decode("utf-8"))                
                filename = os.path.basename(n)
                self.listbox.addItem (filename)
                listitem = [n, filename]
                self.collect_files.append(listitem)
            self.sortlist()
Da die Funktion basename die QStrings nicht mag (stürzt bei 'rfind' ab), wollte ich das Ganze einfach in Unicode umwandeln. Ich bekomme aber trotz decode und normalize immernoch den Fehler

Code: Alles auswählen

unhandled UnicodeEncodeError
"('ascii', u'/home/frank/daten/hoerbuecher/Harry Potter und die Heiligt\xfcmer des Todes/CD05/CD05 - 06 - Die Hochzeit.mp3', 58, 59, 'ordinal not in range(128)')"
Da ist er wieder, der Harry Potter... ;)

Wie genau muss ich meinen Quellcode ändern damit das funktioniert?? Ich hab mich ja nun schon Etliches durchgelesen, stehe aber immernoch auf'm Schlauch... Vielleicht kapier ich es ja wenn ich die Lösung meines Problems sehe???

Danke für Hilfe
fon77
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Die Lösung wäre zum Beispiel das Lesen der Dokumentation: ``unicodedata.normalize`` erwartet als zweites Argument einen Unicode-String, was du aber übergibst ist ein Bytestring.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
fon77
User
Beiträge: 17
Registriert: Freitag 10. April 2009, 20:58

Öhhmm... zu deutsch? ;)

Muss ich die Decodierung aus der normalize-Funktion herausnehmen und nur n als Parameter übergeben? Muss ich die Dekodierung dann danach nochmal ausführen??
BlackJack

Das war "deutsch". Du musst Dir über den Unterschied zwischen den Typen `unicode` und `str` klar werden und en- bzw. dekodieren bedeutet, also was da jeweils reingeht und was rauskommt.
fon77
User
Beiträge: 17
Registriert: Freitag 10. April 2009, 20:58

Yay, es funktioniert! Es musste tatsächlich nur das n.decode("utf-8") durch n ersetzt werden. Läuft wie geschmiert!

Danke für die Hilfe!!
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Hö? Du versuchst doch n zuerst mit einem normalen unicode() in Unicode umzuwandeln. Das funktioniert nur für ASCII. Die Fehlermeldung da deutet auf einen Fehler im unicode() Aufruf hin. unicodedata.normalize gibt eine andere Fehlermeldung bei Fehlern. unicode.decode(<Bytestring Kodierung>) hat keine Wirkung, nur str.decode tut etwas (nämlich nach einem Dekodierungsformat Dinge umwandeln, das muss nicht unbedingt str -> unicode sein, zb geht auch str.decode("bz2"), um bzip2 zu dekodieren - gut). Insofern musst du n einmal dekodieren mit dem korrektem Encoding (sieht nach iso-8859-15 aus).
BlackJack

@str1442: Das mit dem `unicode()` ohne Kodierungsangabe passt schon. `n` ist ja kein `str` sondern ein `QString` der selbst "Unicode" ist, aber eben in einer C++-GUI-Bibliothek die eine andere API hat als die Methoden, die `unicode` zur Verfügung stellt.
Antworten