Fotos umbenennen mit Python

n_e_r_d

Cadet 4th Year
Registriert
Dez. 2021
Beiträge
97
Guten Abend,

ich habe bereits folgendes Python-Programm geschrieben um meine Foto-Ordner umzubenennen:

Code:
pfad = r"C:/Users/Benutzername/Test"
os.chdir(pfad)

for i in range(1,120):
    if i in range(1, 10):
        a = '000'
    if i in range(10, 100):
        a = '00'
    if i in range(100, 1000):
        a = '0'
    if i in range(1000, 10000):
        a = ''

    for j in ["JPG", "PNG", "ARW", "HEIC", "MOV", "AEE"]:
        files = "IMG_" + a + str(i) + '.' + j
        if os.path.exists(files):
            with exiftool.ExifTool() as et:
                datum_ori = et.get_tag("EXIF:DateTimeOriginal", files)
                datum_mod = et.get_tag("FileModifyDate", files)
                if datum_ori != None: #Damit bricht Programm nicht ab, wenn Datei kommt, die kein "EXIF:DateTimeOriginal" hat, sondern überspringt die Datei einfach
                    datum_ori_2 = datum_ori.split(" ")[0].replace(":","-") #Ohne Uhrzeit
                    os.rename(files, pfad + "/" + datum_ori_2 + '_iPhoneX_' + a + str(i) + "." + j)
                else:
                    if datum_mod != None:
                        datum_mod_2 = datum_mod.split(" ")[0].replace(":","-") #Ohne Uhrzeit
                        os.rename(files, pfad + "/" + datum_mod_2 + '_iPhoneX_' + a + str(i) + "." + j)

Dieses Programm funktioniert auch, ist aber meiner Meinung nach eher langsam und ich vermute, dass man es noch stark optimieren kann, wie zum Beispiel:

1. Das Programm geht ja bei jedem Durchlauf (also 120 mal) alle Dateiendungen "JPG", "PNG", "ARW", "HEIC", "MOV", "AEE" durch. Wie könnte ich das vermeiden, indem ich sie direkt auslese?

2. Das Programm geht ja bei jedem Durchlauf (also 120 mal) alle Dateien im Ordner durch und überprüft, ob deren Dateinamen mit "files" übereinstimmt.

Würde ich die Dateien im Ordner der Reihe nach durchgehen können, wäre es vermutlich deutlich schneller. Aber wie funktioniert das?

Vielen Dank im Voraus

n_e_r_d
 
Du ermittelst den Inhalt eines Ordners per Brute-Force? Warum nicht einfach die Dateien in einem Ordner auflisten, bspw. mit os.listdir() ?
 
  • Gefällt mir
Reaktionen: 0x8100 und n_e_r_d
Du erstellst zuerst 120 Dateinamen mit ".JPG", 120 Dateinamen mit ".PNG" usw. und schaust dann, ob diese Dateien überhaupt existieren.
Eleganter wäre es wohl andersherum: Durch alle Datei iterieren und dabei prüfen, ob die Endung, bzw. der ganze Dateiname mit deinem gewünschten Format übereinstimmt.
 
  • Gefällt mir
Reaktionen: n_e_r_d
DaZpoon schrieb:
Du ermittelst den Inhalt eines Ordners per Brute-Force? Warum nicht einfach die Dateien in einem Ordner auflisten, bspw. mit os.listdir() ?
Das hab ich schon mal probiert und dann lief bei einem großen Ordner das Programm ewig, änderte aber keine Dateinamen. Kannst du mir bitte mal explizit sagen was ich ändern soll?

Hier mein Vorschlag, der aber so nicht funktioniert:

Code:
pfad = r"C:/Users/Benutzername/Test"
os.chdir(pfad)
a = os.listdir()

with exiftool.ExifTool() as et:
    metadata = et.get_metadata_batch(a)
    
for k in range(0,120):
    dateiname = a[k]
    print(dateiname)
    en = dateiname.split(".")[1] #damit wird Dateiendung ausgegeben (sofern Dateiname sonst keinen Punkt enthält)
    h = dateiname.split(".")[0] # Das was vor dem Punkt steht
    g = h.split("IMG")[1] # Gibt die Zahl aus
    g1 = int(g)
    
    for d in metadata:
        if g1 in range(1, 10):
            a = '000'
        if g1 in range(10, 100):
            a = '00'
        if g1 in range(100, 1000):
            a = '0'
        if g1 in range(1000, 10000):
            a = ''

        files = pfad + "/IMG" + g + "." + en

        if os.path.exists(files):
            with exiftool.ExifTool() as et:
                datum_ori = et.get_tag("EXIF:DateTimeOriginal", files)
                datum_mod = et.get_tag("FileModifyDate", files)
                if datum_ori != None: #Damit bricht Programm nicht ab, wenn Datei kommt, die kein "EXIF:DateTimeOriginal" hat, sondern überspringt die Datei einfach
                    datum_ori_2 = datum_ori.split(" ")[0].replace(":","-") #Ohne Uhrzeit
                    os.rename(files, pfad + "/" + datum_ori_2 + '_iPhoneX_' + a + str(i) + "." + j)
                else:
                    if datum_mod != None:
                        datum_mod_2 = datum_mod.split(" ")[0].replace(":","-") #Ohne Uhrzeit
                        os.rename(files, pfad + "/" + datum_mod_2 + '_iPhoneX_' + a + str(i) + "." + j)
 
Zuletzt bearbeitet:
Code:
    if i in range(1, 10):
        a = '000'
    if i in range(10, 100):
        a = '00'
    if i in range(100, 1000):
        a = '0'
    if i in range(1000, 10000):
        a = ''

    for j in ["JPG", "PNG", "ARW", "HEIC", "MOV", "AEE"]:
        files = "IMG_" + a + str(i) + '.' + j

das ist nicht so schön, das geht eleganter:

Code:
for i in [1, 12, 123, 1234, 12345]:
   print("%05d" % i)

00001
00012
00123
01234
12345

siehe auch https://stackoverflow.com/a/134951 für andere format-varianten.
 
  • Gefällt mir
Reaktionen: n_e_r_d
Ich hab das zwar noch nie gemacht, aber eventuell kannst du dein Programm "profilen", dh. ermitteln wo es die meiste Zeit verbringt. An der Stelle kannst du dann gegebenenfalls was ändern.
 
  • Gefällt mir
Reaktionen: n_e_r_d
@n_e_r_d : Bist du schon weiter gekommen? Sollte ja eigentlich genügend Beispiele im Netz geben dafür.
 
schasi schrieb:
Ich hab das zwar noch nie gemacht, aber eventuell kannst du dein Programm "profilen", dh. ermitteln wo es die meiste Zeit verbringt. An der Stelle kannst du dann gegebenenfalls was ändern.
Wie genau funktioniert das?
Ergänzung ()

DaZpoon schrieb:
@n_e_r_d : Bist du schon weiter gekommen? Sollte ja eigentlich genügend Beispiele im Netz geben dafür.
Bei dem Beispielcode, den ich oben angegeben hab, stimmt wohl in Zeile 9 etwas nicht.

Wenn ich das Programm durchlaufen lasse, so printet er für k=1 einfach nur "0" und leider nicht den Namen der zweiten Datei im Ordner. Warum?
Ergänzung ()

Ich habe nun den Tipp von 0x8100 angewendet und auf einmal funktioniert das ganze Programm einwandfrei:
Code:
pfad = r"C:/Users/Benutzername/Test"
os.chdir(pfad) #"change direction" wechselt in den angegeben Pfad
a = os.listdir() #erzeugt aus den Dateien im Ordner "Test" eine Liste
print(a)

with exiftool.ExifTool() as et:
    metadata = et.get_metadata_batch(a) #Wichtig: In den Klammern muss eine Liste stehen

for k in range(0,20):
    dateiname = a[k]
    en = dateiname.split(".")[1] #damit wird Dateiendung ausgegeben (sofern Dateiname sonst keinen Punkt enthält)
    h = dateiname.split(".")[0] # Das was vor dem Punkt steht
    g = h.split("IMG")[1] # Gibt die Zahl aus
    g1 = int(g)
    
    for d in metadata:
        files = pfad + "/IMG" + g + "." + en

        if os.path.exists(files):
            with exiftool.ExifTool() as et:
                datum_ori = et.get_tag("EXIF:DateTimeOriginal", files)
                datum_mod = et.get_tag("FileModifyDate", files)
                #datum2 = datum.split(" ")[0].replace(":","-")
                if datum_ori != None: #Damit bricht Programm nicht ab, wenn Datei kommt, die kein "EXIF:DateTimeOriginal" hat, sondern überspringt die Datei einfach
                    datum_ori_2 = datum_ori.split(" ")[0].replace(":","-") #Ohne Uhrzeit
                    #datum_ori_2 = datum_ori.replace(":","-").replace(" ","_") #Mit Uhrzeit
                    os.rename(files, pfad + "/" + datum_ori_2 + '_iPhoneX_' + "%04d" % g1 + "." + en) #durch "%04d" wird die Integer-Zahl g1 mit 4 Stellen angegeben (& somit, falls nötig, mit Nullen aufgefüllt)
                else:
                    if datum_mod != None:
                        datum_mod_2 = datum_mod.split(" ")[0].replace(":","-") #Ohne Uhrzeit
                        #datum_mod_2 = datum_mod.replace(":","-").replace(" ","_") #Mit Uhrzeit
                        os.rename(files, pfad + "/" + datum_mod_2 + '_iPhoneX_' + "%04d" % g1 + "." + en)

Ich verstehe zwar nicht wieso das jetzt mein vorheriges Problem behebt, aber gut.

Seht ihr noch weitere Punkte, die ich verbessern kann? Gerne her damit :)

Viele Grüße

n_e_r_d
 
Zuletzt bearbeitet:
Man muss auch wissen was bei
Python:
if a in range(1000, 10000)
passiert. Python legt eine Enumeration mit 9000 Zahlen an und sucht dann darin, ob a enthalten ist. Das klingt aufwendig.
Auch dein neuer Code ist irgendwie immer rückwärts gedacht (was manchmal auch gut sein kann). Hier mal mein Code zum Auflisten und filtern aller Dateien in einem Verzeichnis. Die Funktion gibt jeweils die vollen Pfade zurück. Das kannst du dann auf eine eigens entwickelte Funktion zum Auslesen der EXIF Daten und umbenennen losschicken.

Python:
import os
import re

def getCandidates(path):
        # Alles akzepieren was dem Muster IMG_####.(JPG...) entspricht
        imageFileRegEx = re.compile(r"IMG_[0-9]+\.(JPG|ARW|HEIC|MOV|AEE)", re.IGNORECASE)

        def toFullPath(fileName):
                return os.path.join(path, fileName)

        # Eine Filterfunktion, hier ggf. weiteres Herausfiltern
        def isCandidate(candidateName):
                if not os.path.isfile(toFullPath(candidateName)):
                        return False
                return imageFileRegEx.match(candidateName)

        return [toFullPath(candidate) for candidate in os.listdir(path) if isCandidate(candidate)]
 
  • Gefällt mir
Reaktionen: n_e_r_d
Ehrlich Leute, das tut doch schon etwas weh. Ich vermute mal, dass ein korrekter Aufruf von os.scandir(path) wesentlich schneller und vor allem schöner ist als diese "Optimierungen". Irre ich mich?
 
  • Gefällt mir
Reaktionen: n_e_r_d
@DaZpoon Das mit dem Filtern wollte ich jetzt als nächstes probieren:
Code:
pfad = r"C:/Users/Benutzername/Test"
os.chdir(pfad) # "change direction" wechselt in den angegeben Pfad
a = os.listdir() # Erzeugt aus den Dateien im Ordner "Test" eine Liste

with exiftool.ExifTool() as et:
    metadata = et.get_metadata_batch(a) # Wichtig: In den Klammern muss eine Liste stehen

for k in range(0,50):
    #  if a[k] == "*IMG*":
        if "IMG" in a[k]:
        en = a[k].split(".")[1] # Damit wird Dateiendung ausgegeben (sofern Dateiname sonst keinen Punkt enthält); a[k] ist der Dateiname der k-ten Datei in der Liste a
        g = a[k].split(".")[0].split("IMG_")[1] # Gibt die Zahl im Dateinamen als String aus; split(".")[0] wird vor split("IMG")[1] ausgeführt
        g1 = int(g)
    else:
        continue #überspringt den Rest dieser for-Schleife für dieses k
 
    for d in metadata:
        files = pfad + "/IMG_" + g + "." + en
        if os.path.exists(files):
            with exiftool.ExifTool() as et:
                datum_ori = et.get_tag("EXIF:DateTimeOriginal", files)
                datum_mod = et.get_tag("FileModifyDate", files)
                if datum_ori != None: #Damit bricht Programm nicht ab, wenn Datei kommt, die kein "EXIF:DateTimeOriginal" hat, sondern überspringt die Datei einfach
                    datum_ori_2 = datum_ori.split(" ")[0].replace(":","-") #Ohne Uhrzeit
                    os.rename(files, pfad + "/" + datum_ori_2 + '_iPhoneX_' + "%04d" % g1 + "." + en) #durch "%04d" wird die Integer-Zahl g1 mit 4 Stellen angegeben (& somit, falls nötig, mit Nullen aufgefüllt)
                else:
                    if datum_mod != None:
                        datum_mod_2 = datum_mod.split(" ")[0].replace(":","-") #Ohne Uhrzeit
                        os.rename(files, pfad + "/" + datum_mod_2 + '_iPhoneX_' + "%04d" % g1 + "." + en)

Leider habe ich den Verdacht, dass "continue" meine komplette for-Schleife abbricht. Meines Wissens soll sie aber doch eigentlich nur das aktuelle k überspringen und nicht die gesamte for-Schleife oder irre ich mich?

Deinen Vorschlage mit der Funktion werde ich morgen probieren. Danke :)
 
blöderidiot schrieb:
tust du nicht :) angenommen ich habe ein verzeichnis mit bildern mit namen im format "DSC0123456.jpg" dann geht dieses script alle bilder durch und gibt einfach nur ein paar sachen aus, die man für ein rename gebrauchen könnte.

Python:
import exif
import os


path = "/path/to/picture/folder"
counter = 0

for entry in os.scandir(path):

    try:
        image = exif.Image(entry.path)
        datetime_original = image.get('datetime_original', None)

        file_name, file_ext = os.path.splitext(entry.path)
        number = file_name.split("DSC")[1]

        if datetime_original:
            datetime_original = datetime_original.replace(":", "_").replace(" ", "-")
        else:
            # irgendwas anderes
            datetime_original = "no_date"

        # print, alternativ rename
        print(datetime_original, number, "%05d" % counter, file_ext)
        counter += 1

    except:
        print("skipped file/dir: %s" % entry.path)
 
n_e_r_d schrieb:
Leider habe ich den Verdacht, dass "continue" meine komplette for-Schleife abbricht.
Jedenfals passen die beiden Schleifen nicht zusammen.
"k" iteriert über alle Datein "os.listdir()"
"d" iteriert über alle metadata
Das klappt nur zufällig, wenn sowohl für alle Dateien metadaten gefunden werden können wie auch, wenn die Sortierung identisch ist.

Und dazu wird das ganze noch unnötig langsam, da Du erst die Metadaten für alle Files liest und dann die Daten für jedes File nochmals einzeln.
 
@0x8100 Ich hab diesen Code mal probiert, aber er meckert schon beim Importieren von exif in Zeile 1. Er sagt:

import exif
ModuleNotFoundError: No module named 'exif'

Muss ich zuerst dieses Modul vom Internet runterladen?
Ergänzung ()

gymfan schrieb:
Jedenfals passen die beiden Schleifen nicht zusammen.
"k" iteriert über alle Datein "os.listdir()"
"d" iteriert über alle metadata
Das klappt nur zufällig, wenn sowohl für alle Dateien metadaten gefunden werden können wie auch, wenn die Sortierung identisch ist.

Und dazu wird das ganze noch unnötig langsam, da Du erst die Metadaten für alle Files liest und dann die Daten für jedes File nochmals einzeln.
Das heißt ich soll die Iterierung über d weglassen?
 
pip install exif

ich wollte das exiftool bei mir nicht installieren, da das irgendwelche weiteren abhängigkeiten ausserhalb von python hatte. falls du pycharm zum programmieren benutzt, schlägt er dir das auch direkt zum installieren vor.
 
n_e_r_d schrieb:
Das heißt ich soll die Iterierung über d weglassen?
Ich würde eher die Version von 0x8100 nehmen und dort die EXIF-Daten über exiftool lesen.

Die API-Beschreibungen von "exif" und "pyexiftool" sind offen im Internet zu finden und lassen sich damit problemlos vergleichen, wenn es Dir schon nicht gelingt ein
image.get('datetime_original', None)
durch ein
et.get_tag("EXIF:DateTimeOriginal", files)
zu ersetzen (und natürlich noch ein paar andere Zeilen, die Du in Deinem eigenen Programm auch verstehen musst)..

0x8100 schrieb:
Nur dass dies mit >50% seiner Bilder nicht funktioniert, wie wir hier schon mit viel Aufwand heraus gefunden hatten:
https://www.computerbase.de/forum/threads/erstellungsdatum-von-raw-auslesen.2063417/

  • es kann keine "ARW" (Sony RAW) lesen, da "exif" (mind. einen) Bug enthält der die Byte-Order der TIFF-Dateistruktur nicht korrekt auswertet. Damit können nahezu keine RAWs gelesen werden.
  • es kann keine "HEIC" (Apples Lieblingsformat) lesen, da man dort mal wieder ein neues Container-Format erfinden musste.
 
gymfan schrieb:
Nur dass dies mit >50% seiner Bilder nicht funktioniert
ist mir vollkommen egal, für die hilfestellung in diesem thread installier ich mir nicht sonstwas. ausserdem hat das verwendete exif modul keine relevanz für das problem mit dem eigentlichen programmaablauf hier.
 
Vielleicht irre ich mich ja auch, aber was mich hier wundert ist, warum wird für das Problem nicht das genommen, was das System bietet, der WSH (Windows Scripting Host)?

Es ist schon eine Weile her, daß ich damit gearbeitet habe, aber vor ein paar Jahren habe ich mal ein Beispiel für jemand erstellt, um zu zeigen was es leistet. Es ist nur ein Progrämmchen. Es ging darum, daß man auch unter Windows löschen kann, ohne das eine Warnmeldung erfolgt. Hat mit dem Problem hier nichts zu tun, und soll halt nur ein Beispiel sein.

Das Programm erstellt ein Verzeichnis, zeigt es an, wartet fünf Sekunden, und löscht es dann wieder. Das Laufwerk muß man wohl an sein System anpassen.

Code:
'Windows Scripting Host Beispiel, 15. April 2019
'Erstellen und Loeschen eines Ordners ohne Abfrage

'Variablendeklaration erzwingen
Option Explicit

Dim Pfad, OrdnerAnzeigen
Dim fso, fo

'Ordner Beispiel erstellen
Pfad = "E:\TEMP\Beispiel"

'FileSystemObject erzeugen fuer Zugriff
Set fso = WScript.CreateObject("Scripting.FileSystemObject")

'Ordner erstellen
Set fo = fso.CreateFolder(Pfad)

'Neu erstellten Ordner anzeigen
OrdnerAnzeigen = fso.GetParentFolderName(Pfad)
Set fo = fso.GetFolder(OrdnerAnzeigen)

'5 Sekunden warten
WScript.Sleep 5000

'Ordner ohne Nachfrage loeschen
fso.DeleteFolder(Pfad)
WScript.Quit
'***Ende
 
Zurück
Oben