Umwandeln einer Liste mit Directories in eine Liste mit Unterlisten

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
Benutzeravatar
qdox
User
Beiträge: 12
Registriert: Sonntag 6. März 2016, 12:14

Hallo zusammen,

am besten beschreibe ich das Problem mit einem Beispiel.

Ich möchte die Tags von MP3 und Bild-Dateien mit ExifTool auslesen und in einer CSV-Datei speichern. Die hat den Vorteil, dass ich dann direkt in LibreOffice weiter arbeiten kann.

Das funktioniert auch soweit. Auslesen, speichern und auch wieder einlesen ist mit wenig Aufwand möglich.

Hier mal mein bisheriges Ergebnis, wobei das nicht alle Tags sind und es nicht das eigentliche Verzeichnis ist, in dem die Dateien liegen. Aber der Übersichtlichkeit halber habe ich das mal eingeschränkt.

Code: Alles auswählen

import exiftool, csv
import glob

tags = ["SourceFile","ID3:Album","ID3:Artist","ID3:Track","ID3:Title"]
verzeichnis = "/tmp/"
csvdatei = verzeichnis + "tags.csv"
dateien = glob.glob(verzeichnis  + "*.mp3")

with exiftool.ExifTool() as et:
	auslese = et.get_tags_batch(tags,dateien)

print(auslese)

with open(csvdatei , "w") as cd:
	cd2 = csv.DictWriter(cd, tags)
	cd2.writeheader()
	cd2.writerows(auslese)

with open(csvdatei , "r") as cd:
	liste = list(csv.reader(cd))

print(liste)
Das Ergebnis sieht so aus:

Auslese der Tags in eine Liste mit Directories:

[codebox=text file=Unbenannt.txt][{'ID3:Artist': 'The BossHoss', 'SourceFile': '/tmp/10 - lady jd.mp3', 'ID3:Title': 'Lady JD', 'ID3:Track': '10/11', 'ID3:Album': 'Dos Bros - Platinum Edition'}, {'ID3:Artist': 'The BossHoss', 'SourceFile': '/tmp/02 - dos bros.mp3', 'ID3:Title': 'Dos Bros', 'ID3:Track': '2/11', 'ID3:Album': 'Dos Bros - Platinum Edition'}, {'ID3:Artist': 'The BossHoss', 'SourceFile': '/tmp/07 - she is a little b.mp3', 'ID3:Title': 'She Is a Little B', 'ID3:Track': '7/11', 'ID3:Album': 'Dos Bros - Platinum Edition'}][/code]

Wieder eingelesene Daten aus der CSV-Datei:

[codebox=text file=Unbenannt.txt][['SourceFile', 'ID3:Album', 'ID3:Artist', 'ID3:Track', 'ID3:Title'], ['/tmp/10 - lady jd.mp3', 'Dos Bros - Platinum Edition', 'The BossHoss', '10/11', 'Lady JD'], ['/tmp/02 - dos bros.mp3', 'Dos Bros - Platinum Edition', 'The BossHoss', '2/11', 'Dos Bros'], ['/tmp/07 - she is a little b.mp3', 'Dos Bros - Platinum Edition', 'The BossHoss', '7/11', 'She Is a Little B']][/code]

Nur möchte ich einige Daten vor dem Schreiben in die CSV-Datei noch ändern. Z.B. möchte ich bei der Track-Nr. den Schrägstrich und die Gesamtanzahl nach dem Schrägstrich entfernen. Dazu wäre es vorteilhaft, die ausgelesene Liste schon vor dem Speichern umzuwandeln.

Ja, ich könnte die Modifikation auch schon in der Directory-Liste vornehmen.

Code: Alles auswählen

for i in auslese:
	i["ID3:Track"] = i["ID3:Track"].split("/")[0]

print(auslese)
Dann erhalte ich dieses Ergebnis:

[codebox=text file=Unbenannt.txt][{'ID3:Artist': 'The BossHoss', 'SourceFile': '/tmp/10 - lady jd.mp3', 'ID3:Title': 'Lady JD', 'ID3:Track': '10', 'ID3:Album': 'Dos Bros - Platinum Edition'}, {'ID3:Artist': 'The BossHoss', 'SourceFile': '/tmp/02 - dos bros.mp3', 'ID3:Title': 'Dos Bros', 'ID3:Track': '2', 'ID3:Album': 'Dos Bros - Platinum Edition'}, {'ID3:Artist': 'The BossHoss', 'SourceFile': '/tmp/07 - she is a little b.mp3', 'ID3:Title': 'She Is a Little B', 'ID3:Track': '7', 'ID3:Album': 'Dos Bros - Platinum Edition'}][/code]

Aber der andere Weg gefällt mir besser.

Gibt es eine einfache Möglichkeit der Umwandlung?

Viele Grüße
BlackJack

@qdox: Welcher andere Weg? Das was Du machst/vogeschlagen hast ist wohl der einfachste Weg. Ich würde das bloss nicht `i` nennen, denn das ist gerade in einer Schleife ein Name der nur an ganze Zahlen gebunden werden sollte, es sei denn man wird gerne von anderen Programmierern gehasst. ;-)

Sonstiges: Pfadteile setzt man mit `os.path.join()` zusammen und nicht mit ``+``.

`dateien` ist nicht an Dateien gebunden sondern an Datei*namen*.

`auslese` klingt auch falsch. Irgendwie nach Wein. :-D

Die kurzen Namen und dann auch noch mit Nummern dran sind auch keine gute Idee. `liste` ist zu generisch.
Benutzeravatar
qdox
User
Beiträge: 12
Registriert: Sonntag 6. März 2016, 12:14

Hallo BlackJack,

also der Reihe nach:

Der "andere Weg" ist der, die Directory-Liste gleich in eine Liste mit Unterlisten umzuwandeln (ohne Umweg über das Speichern und wieder einlese) und damit weiter zu arbeiten. Ob ich dass dann so mache oder doch bei der Directory-Liste bleibe, weiß ich noch nicht. Aber zumindest will ich mir das mal anschauen und vor allem ausprobieren. Versuch macht kluch ... ;-)

Das `i` für einen Zähler habe ich mir irgendwann in den guten alten C64er-Zeiten angewöhnt. Hat glaube ich irgendwann mal in einer Computerzeitung gestanden, dass man für Schleifen die Namen i, j, usw. nehmen soll.
Einstellige Variablen benutze ich nur, wenn es sich um kurze Schleifen handelt und ich diese Variable für den Rest des Programms nicht mehr brauche. Ansonsten versuche ich mir sinnvolle Variablennamen auszudenken. Ob die Namen sich im Nachhinein noch als sinnvoll herausstellen, steht dann auf einem anderen Blatt.

Kurze Variablennamen sind mir eigentlich lieber. Weniger Buchstaben und ohne Unterstrich und die Vertipperwahrscheinlichkeit sinkt. Dass das das Lesen des Codes erschweren kann ist dann leider so. Genau so blöd sind aber lange Namen, bei denen man sich vertippt hat. Da suche ich regelmäßig und lange.

`os.path.join()` hatte ich kurzfristig aus dem Gedächtnis verloren. Jetzt weiß ich es wieder, mal sehen wie lange das vorhält.

Jetzt stellt sich mir nur noch die Frage, ob es einen Weg gibt, die Listen wie oben beschrieben umzuwandeln,ohne das aufwändig zu programmieren. Mir fällt dazu nichts ein und ich grübele schon seit Tagen.

Und während ich das hier so schön schreibe, fällt mir noch eine Frage ein:

Wenn ich dann doch mit den Directories arbeite, kann ich dann die Directorys in der Liste nach den Directory-Schlüsseln sortieren?

Mal als kleines Beispiel (wieder mit kurzen Variablenname :-)):

Code: Alles auswählen

a={"x":"a1","y":"a2"}
b={"x":"b1","y":"b2"}
c={"x":"c1","y":"c2"}
l=[a,c,b]
print(l)
ergibt:

[codebox=text file=Unbenannt.txt][{'x': 'a1', 'y': 'a2'}, {'x': 'c1', 'y': 'c2'}, {'x': 'b1', 'y': 'b2'}][/code]
Nun soll nach dem Schlüssel "x" sortiert werden. Also zuerst das Dictonary mit den a, dann b und am Ende c.

Ein einfache `sort()` funktioniert da erwartungsgemäß nicht. Es kommt `TypeError: unorderable types: dict() < dict()`

Viele Grüße
BlackJack

@qdox: Natürlich kann man die Liste mit den Wörterbüchern auch ohne den Umweg über eine CSV-Datei in eine Liste von Listen umwandeln. Wie immer beim Programmieren: Teile das Problem in Teilprobleme auf. Solange immer weiter bis man die Teilprobleme mit jeweils einer Funktion mit ein paar Zeilen Code lösen kann. Probleme mit Listen oder generell Sequenzen oder noch genereller iterierbaren Objekten lassen sich zum Beispiel oft auf ein Teilproblem reduzieren das man für jedes einzelne Element einzeln lösen kann. Wenn Du also die Umwandlung von *einem* Wörterbuch in *eine* Liste als Funktion lösen kannst, dann kannst Du die hernehmen und auf jedes Element anwenden und hast somit die Umwandlung der ganzen Liste gelöst. Und aufwändig ist das bestimmt nicht. Das ist *ein* Ausdruck mit einer „list comprehension“ und dem Wörterbuch und einer Liste mit den Schlüsseln als Eingabe.

Die Namen `i`, `j`, und `k` verwendet man üblicherweise als Indexwerte, auch in Python, also für ganze Zahlen. Und genau dafür verwendet man sie auch beim C64 in ``FOR``-Schleifen um sie dann als Index in Arrays zu verwenden. Zumal man diese Namen im CBM BASIC V2 sowieso nur für Zahlen verwenden kann und auch gar keine anderen Datentypen ausser Gleitkomma- und ganzen Zahlen und Zeichenketten, und jeweils Arrays von diesen drei Typen gibt. Also keine Wörterbücher oder anderes. Deshalb entsteht da auch keine Verwirrung deswegen. Selbst für Zeichenketten kann man `i` nicht verwenden, denn an einem `i$` sieht man am $ das es sich um eine Zeichenkette handelt und nicht um eine Zahl, also besteht keine Verwechslungsgefahr.

Gegen Vertipper bei langen Namen hilft eigentlich jeder vernünftige Editor mit einer Autovervollständigungsfunktion. Die muss noch nicht einmal intelligent sein, es reicht die offenen oder zum Projekt gehörenden Dateien als Basis zu verwenden. Die Gefahr einen falschen Buchstaben zu erwischen und damit dann erst einmal unbemerkt einen ein- oder zweibuchstabigen Namen aus versehen für verschiedene Dinge zu verwenden schätze ich als höher ein.

Für das sortieren musst Du eine Funktion für das `key`-Argument von `sort()` angeben, die den Schlüssel nach dem sortiert werden soll aus einem einzelnen Element ermittelt. `operator.itemgetter()` ist in Deinem Fall sehr nützlich.
Benutzeravatar
qdox
User
Beiträge: 12
Registriert: Sonntag 6. März 2016, 12:14

Hallo BlackJack,

bevor ich jetzt den Rechner für dieses Wochenende aus mache, noch schnell ein großes Dankeschön.

`operator.itemgetter()` ist mir bisher noch nicht über den Weg gelaufen aber du hast recht, das ist genau das was ich brauche. Woher weißt du das alles? Ich kann mir das nicht merken und bin schon froh, wenn ich mich soweit an gehörtes oder gelesenes erinnere, dass ich zielgerichtet suchen kann. :?

Wegen der direkten Umwandlung: klar kann ich das selbst programmieren. Ich hatte nur gehofft, da gibt es schon was fertiges. Aus Erfahrung weiß ich mittlerweile: Wenn ich gerade was fertig habe, läuft mir eine bestehende Lösung über den Weg. Und diesmal war ich faul und habe vorher gefragt.

Und jetzt noch eine schlechte Ausrede für die kurzen Variablennamen ;-) :
Beim C64 habe ich mir die angewöhnt, weil da die Variablen auf 2 Buchstaben begrenzt waren. Man konnte zwar längere verwenden aber nur die ersten beiden Buchstaben wurden berücksichtigt. za, zahn, zahl waren demnach die gleiche Variable. Mit $ am Ende für Strings war das genau so.
Gut, das ist jetzt kein Grund sich nicht weiter zu entwickeln. Ich sach ja: schlechte Ausrede. :twisted:

Zu guter Letzt noch ein schönes Restwochenende

Viele Grüße
BlackJack

@qdox: `operator.itemgetter()` direkt kann man öfter mal gebrauchen und ansonsten habe ich immer im Hinterkopf das es für alle Operatoren von Python im `operator`-Modul eine Funktion gibt die das gleich tut und als Argument für funktionale Programmierung verwendet werden kann. Beim sortieren, oder mit `map()` oder `filter)` (bzw. `imap()` und `ifilter` aus dem `itertools`-Modul), oder auch bei `itertools.groupby()` sind `itemgetter()`, `attrgetter()`, oder `methodcaller()` manchmal sehr praktisch. Davor hatte ich für so etwas immer einen ``lambda``-Ausdruck geschrieben.

Mich hat die Beschränkung auf zwei signifikante Buchtaben beim C64-Basic damals schon genervt. Man hatte ja immer parallel zu einem Programm einen Zettel wo man notiert hat, was die Variablennamen bedeuten, wenn das Programm etwas grösser wurde. Ich war superglücklich das man die Beschränkung bei den üblichen Assemblern dort dann schon nicht mehr hatte.
Antworten