Bilder sortieren funktioniert nur ein paar Mal, dann ist Schluss

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
harlekyn
User
Beiträge: 7
Registriert: Mittwoch 30. März 2016, 14:34
Wohnort: Bonn
Kontaktdaten:

Guten Abend,

leider versuche ich vergebens ein Problem in meinem Skript zu beheben. Könntet ihr mir vielleicht einen kleinen Denkanstoß zu kommen lassen?

Funktionsbeschreibung:
Blöd wie ich bin habe ich meine Bilder - Festplatte gelöscht und danach versucht alles wieder herzustellen. Dies hat auch zum Größtenteil geklappt, aber alle Bilder hießen jetzt nach folgendem Prinzip 686468416321.jpg ... Faul wie man nun mal bei 1000+ Bildern ist, dachte ich mir, dass ich einfach die Bilder von Python sortieren lasse.... Funktioniert so weit ganz gut.... So ist der Ablauf:

Ablauf:
-> Skript rennt durch alle Ordner und Unterordner von dem Pfad in der Settings.ini
-> Sucht und findet .jpg-Dateien ( Hoffe auch irgendwann PNG / AVI / etc hinzuzufügen)
-> Schaut in den EXIF - Metadaten ( Python-Library: pyexiv2 ) und wandelt das DateTime um in einen lesbaren String
-> Nennt das Bild um ( z.B. 2016-05-13-17-34-22.jpg ) und verschiebt es in den Skript-Ordner
-> Rennt weiter zum nächsten, bis alle jpgs weg sind

Problem:
Dies funktioniert bei dem Beispiel Ordner (gepackt im Beispiel.7z) und zuhause auch bei ein paar Bildern. Beim Beispiel-Ordner einwandfrei, aber zuhause nur knapp 20 Bilder lang und dann war Schluss. Andere Ordner / Bilder entfernen haben leider nicht geholfen. Frage ist nun, habe ich einen Denkfehler? :K

Könntet ihr mir helfen? Gerne nehme ich auch Verbesserungen für meinen Code entgegen. Ach ja, das ganze soll auf Windows 7 laufen.

URL zum Github Projekt: https://github.com/harlekyn/Py_Bilder

Beste Grüße und euch noch nen schönes Wochenende!

Euer harlekyn


PS: Ich bitte von Sprüchen wie "Google doch" und anderen Sprüchen abzusehen.

Code: Alles auswählen

import os
import re
import shutil
import pyexiv2      # ExIf Daten auslesen
import ConfigParser


#
## Ort auslesen

config = ConfigParser.ConfigParser()
config.read('settings.ini')

ort = config.get('Allgemein', 'Ort')
typ = config.get('Allgemein', 'Typ')

print '[*] Einstellung: ' + ort
print '[*] Einstellung: ' + typ

#
## Funktion: Umbenennen

def umbenennen(original):
    metadata = pyexiv2.ImageMetadata(original)
    metadata.read()
    tag = metadata['Exif.Image.DateTime']
    date_pic = tag.value
    neu = '20' + date_pic.strftime("%y-%m-%d-%H-%M-%S")
    
    try:
        os.rename(original, neu + '.jpg')
    except OSError as e:
        print ('[-] Error: %s' % e)
        try:
            os.rename(original, neu + '_Copy.jpg')
        except OSError as e:
            print ('[-] 2.Error: %s' % e)
            try:
                os.rename(original, neu + '_Copy2.jpg')
            except OSError as e:
                print ('[-] 3.Error: %s' % e)

#
## Ordner durchsuchen

for root, path, files in os.walk(ort):
    for filename in files:
        string1 = str(filename)    # Umwandeln in String
        string2 = re.sub(r'\'', '', string1) # Entfernen von '
        string3 = re.sub(r'\[', '', string2) # Entfernen von [
        string4 = re.sub(r'\]', '', string3) # Entfernen von ]

        if string4.endswith(typ):
            datei = os.path.join(root, string4)
            print '[*] Datei: ' + datei
            umbenennen(datei)
Zuletzt geändert von Anonymous am Freitag 13. Mai 2016, 18:05, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

harlekyn hat geschrieben:Beim Beispiel-Ordner einwandfrei, aber zuhause nur knapp 20 Bilder lang und dann war Schluss.
Was meinst du mit Schluss?
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@harlekyn: Was heißt "war Schluß"? Gib es eine Fehlermeldung? Sind alle Dateien abgearbeitet?

In Deiner settings.ini sollten nur einfache Backslashes stehen. Das gibt es ja keine Escape-Sequenzen. Was passiert, wenn es sich um keine gültige Jpeg-Datei handelt oder Metadaten fehlerhaft sind? Da solltest Du entsprechende Exceptions abfangen, dass das Programm nach einer Meldung mit der nächsten Datei weiter machen kann. Bei strftime gibt es %Y damit das Programm im nächsten Jahrhundert auch noch läuft. Deinen try-except-Baum beim Umbenennen solltest Du durch eine Schleife ersetzen, dann kannst Du auch bis _copy34982 gehen. Wenn Du den Typ schon variabel angibst, dann sollte der auch beim Umbenennen benutzt werden. Man kann auch Variablen mehrfach verwenden. Das durchnummerieren von nichtssagenden Variablennamen (string1, ...) macht die Sache nicht besser. filename ist bereits ein String und wenn er komische Zeichen wie Eckige Klammern enthält, sollte man die nicht entfernen, denn sonst ist das kein gültiger Dateiname mehr.
Benutzeravatar
harlekyn
User
Beiträge: 7
Registriert: Mittwoch 30. März 2016, 14:34
Wohnort: Bonn
Kontaktdaten:

Entschuldigung, hatte vergessen den Fehlerlog einzubinden. :oops:

Hier der Log:

Code: Alles auswählen

[+] Datei: C:\\Program Files\\2\\recup_dir.636\f2498104782.jpg

Traceback (most recent call last):
  File "C:\Users\xxx\Desktop\Py_Bilder\bilder.py", line 58, in <module>
    umbenennen(datei)
  File "C:\Users\xxx\Desktop\Py_Bilder\bilder.py", line 27, in umbenennen
    tag = metadata['Exif.Image.DateTime']
  File "C:\Python27\lib\site-packages\pyexiv2\metadata.py", line 209, in __getitem__
    return getattr(self, '_get_%s_tag' % family)(key)
  File "C:\Python27\lib\site-packages\pyexiv2\metadata.py", line 169, in _get_exif_tag
    _tag = self._image._getExifTag(key)
KeyError: 'Tag not set'
Das komische ist ja das es vorher funktioniert hat und erst nach ca 20 Stk. ging es dann gar nicht mehr. Das Bild kann ich mit einem EXIF Tool auch lesen, sprich es gibt ein Datum... Zusätzlich geht es dann bei den Bildern, die vorher funktioniert haben auch nicht mehr. Ist das Skript vielleicht zu schnell? Kurze Atempause einfügen?

@Sirius3:
Dank dir, ich vermerke mir die Verbesserungen und korrigiere sie.
Zuletzt geändert von harlekyn am Freitag 13. Mai 2016, 23:52, insgesamt 1-mal geändert.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Der Fehler sagt dir doch recht eindeutig dass das Bild kein Datum hat, zumindest nicht unter dem Namen den du erwartest.
Benutzeravatar
harlekyn
User
Beiträge: 7
Registriert: Mittwoch 30. März 2016, 14:34
Wohnort: Bonn
Kontaktdaten:

Das komische ist aber, dass die anderen dann auch nicht mehr funktionieren und halt das ein reines EXIF Programm alle benötigten Daten anzeigt.

Alle nachfolgen Bilder klappen dann auch nicht mehr.
Benutzeravatar
kbr
User
Beiträge: 1510
Registriert: Mittwoch 15. Oktober 2008, 09:27

harlekyn hat geschrieben:Das komische ist aber, dass die anderen dann auch nicht mehr funktionieren und halt das ein reines EXIF Programm alle benötigten Daten anzeigt.

Alle nachfolgen Bilder klappen dann auch nicht mehr.
Was auch immer 'nicht mehr funktionieren' und 'klappt nicht' im Detail bedeuten mögen, es schaut so aus, als ob Deine Methode an die EXIF Daten zu gelangen, fehlerhaft ist. Kurzes googeln führt schnell zu der Erkenntnis, dass die von Dir benutze Library eine Deprecation Warning aufweist und nicht mehr weiter entwickelt wird. Vielleicht solltest Du es mit 'pillow' versuchen. Ein zweites kurzes googeln mit den Stichworten 'pillow' und 'exif' führt jedenfalls recht schnell zu Treffern.
Benutzeravatar
harlekyn
User
Beiträge: 7
Registriert: Mittwoch 30. März 2016, 14:34
Wohnort: Bonn
Kontaktdaten:

kbr hat geschrieben:Was auch immer 'nicht mehr funktionieren' und 'klappt nicht' im Detail bedeuten mögen, es schaut so aus, als ob Deine Methode an die EXIF Daten zu gelangen, fehlerhaft ist.
Ich hatte oben den Fehlerlog gepostet, aber ja, es wunderte mich nur das die Bilder vorher funktionieren, dann nach dem Fehler aber leider nicht mehr.
kbr hat geschrieben:Kurzes googeln führt schnell zu der Erkenntnis, dass die von Dir benutze Library eine Deprecation Warning aufweist und nicht mehr weiter entwickelt wird. Vielleicht solltest Du es mit 'pillow' versuchen. Ein zweites kurzes googeln mit den Stichworten 'pillow' und 'exif' führt jedenfalls recht schnell zu Treffern.
Danke für den Tipp schaue ich mir mal an. Stackoverflow und co via Google hatten immer PIL oder PYEXIFV2 empfohlen. Pillow hingegen hatte ich nicht gefunden, schaue es mir auf deinen Hinweis dann mal an.
BlackJack

@harlekyn: Daran das ein Programm nachdem es mit einer Ausnahme abbricht, nicht mehr weiterläuft, ist gar nichts komisch.

Und wenn eine andere Software einen Zeitstempel bei dem betroffenen Bild anzeigt, bist Du sicher das es das 'Exif.Image.DateTime'-Tag ist, was dort angezeigt wird? Es gibt in EXIF noch andere Tags um Datum und Zeit zu speichern. Lass Dir bei dem betroffenen Bild doch einfach mal alle Tags ausgeben.
Benutzeravatar
harlekyn
User
Beiträge: 7
Registriert: Mittwoch 30. März 2016, 14:34
Wohnort: Bonn
Kontaktdaten:

BlackJack hat geschrieben:@harlekyn: Daran das ein Programm nachdem es mit einer Ausnahme abbricht, nicht mehr weiterläuft, ist gar nichts komisch.

Und wenn eine andere Software einen Zeitstempel bei dem betroffenen Bild anzeigt, bist Du sicher das es das 'Exif.Image.DateTime'-Tag ist, was dort angezeigt wird? Es gibt in EXIF noch andere Tags um Datum und Zeit zu speichern. Lass Dir bei dem betroffenen Bild doch einfach mal alle Tags ausgeben.
Danke, werde ich mal machen! Ich muss das Bild mal genauer untersuchen.
BlackJack

@harlekyn: Noch ein paar Anmerkungen zu den Quelltext:

`re` und `shutil` werden importiert, aber nicht verwendet.

Auf Modulebene sollten keine Variablen und kein Hauptprogramm stehen. Das steht üblicherweise in einer Funktion die `main()` heisst.

Kommentare sollten dem Leser einen Mehrwert zum Code bieten. Vor einer Funktionsdefinition zu kommentieren ``# Funktion: funktionsname`` bringt dem Leser nichts. Faustregel: Kommentare sollten nicht beschreiben *was* passiert, denn das steht da ja schon als Code, sondern *warum* es so passiert wie der Code das macht, sofern das nicht offensichtlich ist.

`path` ist in der Schleife im Hauptprogramm der falsche Name. Da wird nicht ein Pfad dran gebunden sondern potentiell viele Verzeichnisnamen. `files` und `datei` sind auch nicht ganz passend, denn es handelt sich nicht um Dateiobjekte sondern um Datei*namen*.

Man könnte dann ungefähr bei so etwas heraus kommen (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
import ConfigParser
import os
 
import pyexiv2
 

def rename(old_path, typ):
    metadata = pyexiv2.ImageMetadata(old_path)
    metadata.read()
    new_filename = format(
        metadata['Exif.Image.DateTime'].value, '%Y-%m-%d-%H-%M-%S'
    )
    
    for i, copy_name in enumerate(['', '_Copy', '_Copy2'], 1):
        try:
            os.rename(old_path, '{}{}{}'.format(new_filename, copy_name, typ))
            break
        except OSError as error:
            print('[-] {0}.Error: {1}'.format(i, error))


def main(): 
    config = ConfigParser.ConfigParser()
    config.read('settings.ini')
    base_path = config.get('Allgemein', 'Ort')
    extension = config.get('Allgemein', 'Typ')
    print('[*] Einstellung:', base_path)
    print('[*] Einstellung:', extension)
     
    for root, _, filenames in os.walk(base_path):
        for filename in filenames:
            if filename.endswith(extension):
                path = os.path.join(root, filename)
                print('[*] Datei:', path)
                try:
                    rename(path, extension)
                except Exception as error:
                    print('[-]', error)


if __name__ == '__main__':
    main()
Benutzeravatar
harlekyn
User
Beiträge: 7
Registriert: Mittwoch 30. März 2016, 14:34
Wohnort: Bonn
Kontaktdaten:

BlackJack hat geschrieben:@harlekyn: Noch ein paar Anmerkungen zu den Quelltext:

`re` und `shutil` werden importiert, aber nicht verwendet.

Auf Modulebene sollten keine Variablen und kein Hauptprogramm stehen. Das steht üblicherweise in einer Funktion die `main()` heisst.

Kommentare sollten dem Leser einen Mehrwert zum Code bieten. Vor einer Funktionsdefinition zu kommentieren ``# Funktion: funktionsname`` bringt dem Leser nichts. Faustregel: Kommentare sollten nicht beschreiben *was* passiert, denn das steht da ja schon als Code, sondern *warum* es so passiert wie der Code das macht, sofern das nicht offensichtlich ist.

`path` ist in der Schleife im Hauptprogramm der falsche Name. Da wird nicht ein Pfad dran gebunden sondern potentiell viele Verzeichnisnamen. `files` und `datei` sind auch nicht ganz passend, denn es handelt sich nicht um Dateiobjekte sondern um Datei*namen*.

Man könnte dann ungefähr bei so etwas heraus kommen (ungetestet):
Vielen Dank, werde ich mir mal zu gemüte führen. Bisher habe ich noch nichts wegen der Main-Funktion gelesen, außer halt c++. Rein optisch halber oder ein muss? Die Kommentare haben für mich in der Hinsicht eigentlich nur eine schnelle Übersicht geboten, wo fängt was an. Ansonsten schreibe ich bei Bedarf sie einfach dahinter, siehe Import. re und shutil waren noch "import-Leichen" hatte sie wegen PIL noch drin, mein Fehler sie rauszunehmen.

Vielen Dank nochmal, werde mich bessern!
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

main-Funktionen sind nicht reine Optik. Der Grund ist, dass aus Skripten auch Module werden koennen, also von ihnen importiert wird.

Eine nicht durch das idiom

Code: Alles auswählen

if __name__ == "__main__":
     main()
geschuetzte Ausfuehrung wuerde dann einen Seiteneffekt (naemlich die Ausfuehrung) bedeuten - das will man aber dann nicht.
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

harlekyn hat geschrieben:Bisher habe ich noch nichts wegen der Main-Funktion gelesen, außer halt c++. Rein optisch halber oder ein muss?
Wie du vielleicht schon selber bemerkt hast, laufen Python-Programme auch ohne `main()`-Funktion, zumal der Python-Compiler überhaupt keine Funktionalität zum selbständigen Erkennen von `main()` eingebaut hat. In vielen Programmen wird aber dennoch eine `main()`-Funktion als Einstieg genutzt. Oft findet man die Umsetzung dann in dieser Form:

Code: Alles auswählen

def main():
    # Code

if __name__ == '__main__':
    main()
Sieht anfangs etwas verwirrend aus. Es sorgt dafür, dass in einem Programm, welches von der Kommandozeile gestartet wird (``python meinprog.py``), automatisch die `main()`-Funktion aufgerufen wird. Grund: Programme, die so ausgeführt werden, haben automatisch den Wert "__main__" für ihr `__name__`-Attribut.

Der eigentliche Vorteil zeigt sich, wenn man den Code als Modul importiert (``import meinprog``). Durch den Einsatz der `main()`-Funktion in Verbindung mit dem gezeigten `if`-Konstrukt kommt es dann nämlich *nicht* zum automatischen Aufrufen des Codes, da dort ``__name__ == '__main__'`` einen anderen Wert hat.

Somit könnte man nach dem Importieren z.B. einzelne Funktionen des Moduls testen und hat selber die Kontrolle darüber, wann welcher Code ausgeführt wird. Manchmal sind Module auch so gestaltet, dass sie aus Python heraus wie eine Bibliothek genutzt werden können und dass sie in der `main()`-Funktion zusätzlich einen CLI-Parser einsetzen, um die Funktionalität auch über Optionen auf der Kommandozeile nutzbar zu machen. Da wäre es wenig hilfreich, wenn der CLI-Parser auch bei einem Import anspringen würde.
BlackJack

@harlekyn: Keine Variablen auf Modulebene und damit auch das Hauptprogramm in einer Funktion würde ich als „muss” ansehen. Es ist übersichtlicher (und damit ist nichts „optisches“ gemeint, es geht nicht um „schön“, sondern um leicht(er) verständlich) und sicherer, weil es nicht passieren kann, dass man aus versehen Werte von aussen in Funktionen oder Methoden benutzt auf die diese eigentlich so nicht zugreifen sollten.

Zudem kann man mit dem ``if __name__ …`` am Ende dafür sorgen, dass man das Modul importieren kann, ohne dass das Programm automatisch gleich abläuft. Das ist zum einen zur Fehlersuche nützlich, weil man ein Modul dann in einer Python-Shell importieren, und einzelne Funktionen oder Klassen live ausprobieren kann, zum anderen erwarten dieses effektfreie Importieren einige Werkzeuge zur statischen Analyse, Dokumentationsgenerierung, und welche die (Unit-)Tests zusammensammeln und dann ausführen.
Benutzeravatar
harlekyn
User
Beiträge: 7
Registriert: Mittwoch 30. März 2016, 14:34
Wohnort: Bonn
Kontaktdaten:

Vielen Dank an alle für die Erklärung! Ich werde es mir merken für die Zukunft.
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

snafu hat geschrieben:Der eigentliche Vorteil zeigt sich, wenn man den Code als Modul importiert (``import meinprog``). Durch den Einsatz der `main()`-Funktion in Verbindung mit dem gezeigten `if`-Konstrukt kommt es dann nämlich *nicht* zum automatischen Aufrufen des Codes, da dort ``__name__ == '__main__'`` einen anderen Wert hat.
Sollte natürlich heißen:
"(...) da dort `__name__` einen anderen Wert hat."
Antworten