Nieuws:

Welkom, Gast. Alsjeblieft inloggen of registreren.
Heb je de activerings-mail niet ontvangen?

Auteur Topic: eerste programmaatje/script gemaakt om een onjuist type foto, juist te maken.  (gelezen 4808 keer)

Offline _Walter_

  • Lid
Ik ben zo blij. Ik neem aan dat er veel commentaar gegeven kan worden, maar ik ben erg trots op mezelf dat ik uit mijn hoofd (op één ding na, maar daar heb ik nog geen tijd voor gehad) een python script heb geschreven die doet wat ik wil!

Probleem: de foto's van mijn telefoon HTC Sensation worden niet goed geïmporteerd in Shotwell. De datum bevat in plaats van : een / . Er zijn meer mensen met dit probleem en ik zag in het forum van Yorba, de makers van Shotwell, een shell scriptje om deze bestanden om te zetten met de juiste datumformat. Je moest dan wel exiftool installeren. Wat niet zomaar gaat: met apt-get install exiftool gebeurd er niets. Via software centrum wel te vinden als lib.
 
Leuke bijkomstigheid:
Ik maak gebruik van Ubuntu One voor mijn foto's. Zeer recent bestaat er een mooie app voor o.a. Android. Als je die installeerd en met je telefoon een foto maakt, kun je het automatisch laten uploaden naar U1. Echter komt het dan in de folder /home/gebruiker/Pictures telefoonnaam/ terecht. Je moest ze hierna dus nog importeren in Shotwell.

Wat doet het script/programma dan?

Om de 100 seconden wordt het aantal bestanden geteld in de directory waar de foto's terechtkomen via Ubuntu One. Als er een bestand bij is gekomen, wordt het omgezet naar een bestand met de juiste datum. Aangezien ik toch bezig was en de strings met de datum in Python had, kon ik directories aanmaken met jaar/maand/datum in mijn afbeeldingendirectory. Dus na omzetten wordt het bestand daarheen verplaatst.

Resultaat:
Als ik op de markt of weet ik veel waar een foto maak, komt hij vanzelf in de juiste directory te staan met de juiste datum. Ik hoef ze niet meer te importeren in Shotwell, want die monitort de afbeelingendirectory zelf.

Het exiftoolgebeuren heb ik dus niet zelf bedacht.

Wat heb ik geleerd?
De module os.popen te gebruiken.

Wat ging mis?
Het werkte steeds niet, totdat ik erachter kwam dat ik was vergeten de module shutil te importeren om het bestand te verplaatsten.

Hoe gebruik ik het?
Ik laat het automatisch opstarten voorkeuren --> Opstarttoepassingen met als commando 'python blahblah.py

Groetjes,
Walter


Jammer dat ik (nog) geen python ken. Maar het is al leuk om te lezen dat je voor dergelijke dingen scripts kan maken. :)

Een paar opmerkingen om uw eerste script nog beter te maken :).

 - Lees eens PEP8, dit is de code-style gids van Python: http://www.python.org/dev/peps/pep-0008/
 - os.popen (en varianten) zijn deprecated, gebruik liever de subprocess module: http://docs.python.org/library/subprocess.html#module-subprocess
 - In plaats van een if/else blok in de for-lus, kan ook "if bestand in bestanden: continue", zonder else erachter, dat bespaart een hele reeks ingesprongen code
 - Breek de grote try...except blok in kleinere stukken met een goede foutbeschrijving. Als er nu iets misloopt in het manipuleren van de strings of het aanmaken van mappen of het kopieren van bestanden, krijgt ge telkens dezelfde nietszeggende foutmelding;
 - De functie kopieren() is hier overbodig
 - Controleren op True/False doet ge best niet met de == operator, maar kan gewoon zo: if os.path.exist('/path/file'): do_something()
 - De variabele "loop" is overbodig, een oneindige while-lus aanroepen gaat het makkelijkste zo: while True: do_something()

Dit zijn wat opmerkingen met er snel overheen te kijken. Ikzelf zou het ook wat meer opbreken in aparte functies voor wat meer overzicht en controle.

Offline MKe

  • Lid
Idee: Je kunt ook het progje versimpelen en het door de cron laten aanroepen op gezette tijden i.p.v. een oneindige loop te laten draaien. Cron is een functie binnen linux voor het schedulen van bepaalde tasks. Op die manier wordt het programma ook weer gestart als het op een of andere reden ergens in de loop is vastgelopen om wat voor reden dan ook. Op het moment dat de reden van vastlopen is opgeheven, zal hij zijn taak gewoon weer doen. Hier is cron trouwens gewoon voor bedoeld.

Moet het scriptje trouwens niet beginnen met een #! regel? b.v. #!/usr/bin/env python
Global variabelen zoals jouw 'bestanden' is meestal niet zo'n goed idee, maar met een simpel scriptje kom je er wel mee weg zoals hier. Beter is het om variabelen mee te geven aan een functie.
Trouwens daar nog een vraag over. Klopt het dat je de namen van de bestanden 'oneindig' in het geheugen houd? Gaat dat niet werken als een soort geheugenlek, een variabele die oneindig blijft doorgroeien? Zou het dus niet beter zijn de foto's te verplaatsen als je klaar bent met hernoemen, zodat hij verwijderd wordt uit de 'ontvangstmap'? OP die manier hoef je ook niet te checken of er nu meer bestanden zijn dan in de vorige ronde, maar kun je gewoon de bewerking toepassen op alle bestanden in de map. Het is me niet helemaal duidelijk of shutil dat doet met de move commando.

Om de 100 seconden wordt het aantal bestanden geteld in de directory waar de foto's terechtkomen via Ubuntu One. Als er een bestand bij is gekomen, wordt het omgezet naar een bestand met de juiste datum.

Euhm, ik denk dat je dat veel beter met inotify/fsevents gedaan had...
I use a Unix-based system, that means I'll get laid as often as I have to reboot.
LibSylph
SeySayux.net

Offline _Walter_

  • Lid
Ik sta er steeds weer versteld van, wat een gewdige mensen er hier op het forum aanwezig zijn. Bedankt voor jullie uitgebreide feedback!

Hier leer ik snel mee. Ik heb aan mijn vrouw een pythonboek gevraagd voor m'n verjaardag, dus dan zal ik netter leren schrijven.

Ik heb alleen erg weinig tijd, maar ik zal het geheel nog eens doornemen met jullie commentaren.

Offline _Walter_

  • Lid
Hoi hoi, ik ben gisteren even bezig geweest en heb zoveel mogelijk van jullie commentaar meegenomen. Ik zal even op een ieder van jullie reageren:

@Thomas de Graaff - naar mijn mening is Python niet echt heel moeilijk om te leren. Goed het internet afstruinen en je zult merken dat je een krachtige 'tool' in handen hebt.

@Nunslaughter
1)Bedankt voor de link. Erg fijne informatie, vooral omdat ze ook zoveel moeite hebben gestoken in de foute voorbeelden. Ik begrijp alleen nog niet alle logica. Zoals bijvoorbeeld bij het importeren. waarom niet import glob,os,sys,etcetera maar liever alles apart?
2) bedankt voor de tip: 'if iets' dit zit nu in mijn systeem in scheelt een hoop typewerk, evenals natuurlijk if not. Evenals 'While True'.
3) Subprocess was pittig voor me. En heb ik hier nog niet toe kunnen passen. Maar aldoende leert men, zeg maar. Ik heb wel vandaag in een ander bestand subprocess.call() kunnen gebruiken, maar in dit geval met de lange regel 'exiftool blahblah | nogwat | etcetera' was het nog wat lastig.

@MKe
1) Ik heb toch besloten de while loop erin te houden. Ik kan het uiteraard helemaal mis hebben, maar het lijkt me sneller om de loop te gebruiken in plaats van steeds te Python interpeter opnieuw op te starten.
2) Ik heb de #! regel toegevoegd, wel handig, want anders gebeuren er soms van die 'rare' dingen.
3) Ik heb nog meer van je tips in me opgenomen, maar ik blijf je niet in de hemel prijzen :-)
4) shutil.move doet gewoon verplaatsten van (bron,doel). in feite eerst kopieren en als dat succesvol verloopt bron verwijderen, geloof ik. Sheelt weer wat tikwerk.

@SySayux
inotify/fsevents daar heb ik even over ingelezen, maar het was gisteren nog te moeilijk voor me om toe te passen dus ik heb het nog even op time.sleep() gehouden.

Hij loopt nu wel als een speer! Ik heb gisteren even 580 nog te sorteren foto's die niet gecorrigeerd hoeven te worden in de directory gezet. En de werden redelijk snel allemaal verplaatst. Ik denk ook dat ik voorlopig de nieuwe foto's gewoon in die folder zet en zo process. Als ik namelijk importeer met shotwell wil hij wel eens vastlopen en de automatische import gaat wel goed.

Bedankt voor de tips! Het is heel leerzaam voor me geweest!

Ik zal hieronder de verbeterde code plaatsen. Ik moet bekennend dat ik nog een lang try-except blok heb, maar dat komt later nog wel, als ik weer meer geleerd heb! ;-)

Groet, Walter

#!/usr/bin/env python

""" Dit Python script heb ik voor mezelf geschreven om automatisch bestanden in de Ubuntu One directory
om te zetten. Namelijk de datum van mijn HTC Sensation foto's bevat een / in plaats van :. Zoals 2011/08/01
Shotwell wil deze bestanden niet juist importeren omdat het er normaal gesproken zo uit ziet: 2011:08:01. Op internet in
een forum van Yorba (makers van shotwell) zag ik een shell scriptje, gebruik makend van exiftool om de slash
voor een dubbele punt te veranderen.
Met dit python script liet ik dat automatisch gebeuren. Om de ongeveer anderhalve minuut wordt er gekeken of er een nieuwe foto staat.
En vervolgens wordt deze omgezet met exiftool.
Toen ik eenmaal toch bezig was en de datum had in strings was het natuurlijk ook mogelijk om ze over te kopieren naar
mijn Afbeeldingen directory met in in dit voorbeeldgeval de directory ~/Afbeeldingen/2011/08/01 """


import os
import time
import glob
import shutil

# hier eerst de benodigde directories ingeven
teldir = '~/Pictures - HTC Sensation Z710e/'  # De directory waar mijn foto's middels Ubuntu One terechtkomen
afbeeldingendir = '~/Afbeeldingen/' # hier staan mijn afbeeldingen in folders op datum gesorteerd.

# ~ omzetten naar /home/gebruiker
teldir = os.path.expanduser(teldir)
afbeeldingendir = os.path.expanduser(afbeeldingendir)

os.chdir(teldir)

def datum_corrigeren(datum):
datum = datum.replace('/',':')
os.system('exiftool -P -overwrite_original_in_place -DateTimeOriginal="'+datum+'" '+bestand)  #de datum veranderen naar het normale format met exiftool

while True:  # oneindige loop, om het komende process te herhalen

# hier start een for loop om bestanden te vinden in de directorie waar de foto's van mijn telefoon terechtkomen
for bestand in glob.glob('*'):  # een wildcard is te gebruiken, omdat er alleen maar foto's in deze directory komen te staan
try:
print bestand
inlezen_datum = os.popen('exiftool "'+bestand+'" | grep "Date/Time Original" | cut -c35-')
datum = inlezen_datum.read()  #uitvoer van exiftool inlezen, dus de datum

# datum corrigeren, indien nodig
if '/' in datum:
try: datum_corrigeren(datum)
except:
print 'Omzetten datum niet gelukt voor bestand '+bestand+'.'
break

# datum uit elkaar halen, om directories aan te maken
datum = datum.split(':')
jaar = datum[0]
maand = datum[1]
dag = datum[2]
dag = dag[:2]  # hier een :2, dus de eerste twee karakters. In de verzameling staat naamelijk hierna nog een spatie en dan de eerste twee decimalen van de tijd en die willen we niet.

# hier de directories aanmaken op jaar/maand/dag waar de foto terecht gaat komen
if not os.path.exists(afbeeldingendir+jaar):
os.mkdir(afbeeldingendir+jaar)
if not os.path.exists(afbeeldingendir+jaar+'/'+maand):
os.mkdir(afbeeldingendir+jaar+'/'+maand)
if not os.path.exists(afbeeldingendir+jaar+'/'+maand+'/'+dag):
os.mkdir(afbeeldingendir+jaar+'/'+maand+'/'+dag)

# het bestand verplaatsen naar de nieuwe dir in de afbeeldingenmap.
shutil.move(bestand,afbeeldingendir+jaar+'/'+maand+'/'+dag+'/'+bestand)
except: pass
time.sleep(100)




@Nunslaughter
1)Bedankt voor de link. Erg fijne informatie, vooral omdat ze ook zoveel moeite hebben gestoken in de foute voorbeelden. Ik begrijp alleen nog niet alle logica. Zoals bijvoorbeeld bij het importeren. waarom niet import glob,os,sys,etcetera maar liever alles apart?
2) bedankt voor de tip: 'if iets' dit zit nu in mijn systeem in scheelt een hoop typewerk, evenals natuurlijk if not. Evenals 'While True'.
3) Subprocess was pittig voor me. En heb ik hier nog niet toe kunnen passen. Maar aldoende leert men, zeg maar. Ik heb wel vandaag in een ander bestand subprocess.call() kunnen gebruiken, maar in dit geval met de lange regel 'exiftool blahblah | nogwat | etcetera' was het nog wat lastig.

1) Zet die link maar in uw bladwijzers :). Veel heeft te maken met leesbaarheid, dus alle imports op een nieuwe regel geeft een veel beter overzicht.
Vandaar nog even een opmerking:
try: do_something()
except Exception:
    do_error_handling()
Dit is zoiets wat bij grotere projecten best onleesbaar kan worden, zeker bij het doorzoeken van code. Zet beter alles op een nieuwe regel.
Dit is misschien muggenziften, maar beter vroeg geleerd, dan later proberen af te leren :).

3) Subprocess is inderdaad een hele brok, maar wel flink verbeterd tegenover de oudere functies. Waarschijnlijk is het sowieso beter om een Python module te gebruiken voor de exif data in plaats van een extern programma aan te roepen. Even zoeken in het Software Centrum bracht python-pyexif2 naar boven.

Nog even wat dingetjes:
 - De datum splitsen kan misschien makkelijker:
jaar, maand, dag = datum.split(':')Dan moet er niet meer in de list gezocht worden naar de data. Als er meer data in de datum string zit, gaat ge een ValueError krijgen omdat ge dan teveel data in de 3 variabelen wilt steken, probeer dan het volgende:
jaar, maand, dag, _ = datum.split(':', 3)Zie volgend stukje als voorbeeld:
>>> s = 'a:b:c:d:e:f'
>>> a, b, c = s.split(':')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack
>>> a, b, c, _ = s.split(':', 3)
>>>

 - De grote try...except blijft een slecht idee. Laat op zijn minst een goede foutmelding zien, in plaats van gewoon door te gaan. Verander "except: pass" bijvoorbeeld naar:
except Exception as exc:
    print "Er liep iets mis bij het verwerken van de data:\n", exc

Hetzelfde eigenlijk bij de try...except bij het corrigeren van de datum. Print de traceback zodat ge tenminste weet wat er fout gelopen is en het script eventueel kunt aanpassen zodat zulke data wel goed behandeld word. Hier wilt ge ook een "continue" in plaats van een "break" zodat hij naar het volgende bestand gaat in plaats van de lus te verbreken.

 - Het declareren van de paden kan best in 1 regel:
teldir = os.path.expanduser('~/Pictures - HTC Sensation Z710e/')
# Hetzelfde voor afbeeldingendir

 - Het aanmaken van de directories kan ook met:
try:
    os.makedirs(afbeeldingendir+jaar+'/'+maand+'/'+dag)
except OSError:
    # Deze map bestaat al, gewoon doorgaan dus
    pass
Dit zal meteen alle missende onderliggende mappen bijmaken.

En als ge de shutil.move() regel nog even in een try...except zet, kan die grote try...except weg aangezien de rest van de code sowieso geen exception zal gooien.

Zo, hopelijk hebt ge wat aan deze tips :).

Offline _Walter_

  • Lid
Bedankt weer, voor de tips!

Ik heb het bestand inmiddels weer wat aangepast. Ik moest wel. Er stond een behoorlijke fout in. Ik heb het inmiddels hersteld en gelijk weer wat tips meegenomen.

Even een spelletje:
Wie raad het foutje?

Offline Ronnie

  • Lid
    • ronnie.vd.c
In plaats van elke x seconden kijken of het aantal bestanden is gewijzigd kun je ook naar het 'bestand aangemaakt' event luisteren en daarop reageren. Voor een voorbeeld zie:
http://stackoverflow.com/questions/182197/how-do-i-watch-a-file-for-changes-using-python/473471#473471

====
Een string waarin je variabelen wilt zetten kun je ook op de volgende manier doen:

bestand = 'Mijn bestand'
'exiftool "%s" | grep "Date/Time Original" | cut -c35-' % (bestand)

of
mijn_bestand = 'Mijn bestand'
'exiftool "%(bestand)s" | grep "Date/Time Original" | cut -c35-' % {'bestand': mijn_bestand}

Voor meer informatie over string formatting: http://docs.python.org/library/stdtypes.html#string-formatting
Het voordeel hiervan is bijvoorbeeld het vertalen van een string kan dan veel gemakkelijker, omdat niet in alle talen de naam van het bestand op dezelfde plaats in de zin hoort te staan.

====
Voor de datum en tijd kun je ook de datetime module gebruiken:
http://docs.python.org/library/datetime.html#datetime.datetime.strptime
http://docs.python.org/library/datetime.html#strftime-and-strptime-behavior

datum = datetime.datetime.strptime(inlezen_datum.read(), '%Y:%m:%d')
dan kun je daarna het jaar,maand en dag verkrijgen met datum.year, datum.month, datum.day

====
Ook vind ik het zelf altijd prettiger als ook de variabele namen in het Engels geschreven zijn. Dan kan elke programmeur het script lezen.
« Laatst bewerkt op: 2011/08/25, 00:26:00 door Ronnie »
Ben je ook blij dat Ubuntu zo toegankelijk en gratis is, en wil je graag net als ik iets terugdoen, kijk dan eens rond bij mwanzo, dé poort naar het bijdragen aan Ubuntu en haar gemeenschap!

Documentatie Terminal

Offline _Walter_

  • Lid
Oh oh oh.... Wat heb ik nog een lange weg te gaan.
Maar wat een heerlijke programmeertaal is dat Python toch!

Het foutjes zat hem in de functie, die in dit geval ook niet nodig was. Daarin staat datum = datum.replace.....blah.

Maar na de functie geldt dat niet meer. Dus ging het kopieren niet goed.
Hier de verbeterde versie.
#!/usr/bin/env python

""" Dit Python script heb ik voor mezelf geschreven om automatisch bestanden in de Ubuntu One directory
om te zetten. Namelijk de datum van mijn HTC Sensation foto's bevat een / in plaats van :. Zoals 2011/08/01
Shotwell wil deze bestanden niet juist importeren omdat het er normaal gesproken zo uit ziet: 2011:08:01. Op internet in
een forum van Yorba (makers van shotwell) zag ik een shell scriptje, gebruik makend van exiftool om de slash
voor een dubbele punt te veranderen.
Met dit python script liet ik dat automatisch gebeuren. Om de ongeveer anderhalve minuut wordt er gekeken of er een nieuwe foto staat.
En vervolgens wordt deze omgezet met exiftool.
Toen ik eenmaal toch bezig was en de datum had in strings was het natuurlijk ook mogelijk om ze over te kopieren naar
mijn Afbeeldingen directory met in in dit voorbeeldgeval de directory ~/Afbeeldingen/2011/08/01 """


import os
import time
import glob
import shutil

# hier eerst de benodigde directories ingeven
teldir = os.path.expanduser('~/Pictures - HTC Sensation Z710e/')  # De directory waar mijn foto's middels Ubuntu One terechtkomen
afbeeldingendir = os.path.expanduser('~/Afbeeldingen/') # hier staan mijn afbeeldingen in folders op datum gesorteerd.

os.chdir(teldir)

while True:  # oneindige loop, om het komende proces te herhalen

# hier start een for loop om bestanden te vinden in de directorie waar de foto's van mijn telefoon terechtkomen
for bestand in glob.glob('*'):  # een wildcard is te gebruiken, omdat er alleen maar foto's in deze directory komen te staan
try:
print bestand
inlezen_datum = os.popen('exiftool "'+bestand+'" | grep "Date/Time Original" | cut -c35-')
datum = inlezen_datum.read()  #uitvoer van exiftool inlezen, dus de datum

# datum corrigeren, indien nodig
if '/' in datum:
try:
datum = datum.replace('/',':')
os.system('exiftool -P -overwrite_original_in_place -DateTimeOriginal="'+datum+'" '+bestand)  #de datum veranderen naar het normale format met exiftool
except:
print 'Omzetten datum niet gelukt voor bestand '+bestand+'.'
break

# datum uit elkaar halen, om directories aan te maken
datum = datum.split(':')
jaar = datum[0]
maand = datum[1]
dag = datum[2]
dag = dag[:2]  # hier een :2, dus de eerste twee karakters. In de verzameling staat namelijk hierna nog een spatie en dan de eerste twee decimalen van de tijd en die willen we niet.

# hier de directories aanmaken op jaar/maand/dag waar de foto terecht gaat komen
nieuwe_locatie = afbeeldingendir+jaar+'/'+maand+'/'+dag
try:
os.makedirs(nieuwe_locatie)
except OSError:
# Deze map bestaat al, gewoon doorgaan dus
pass

# het bestand verplaatsen naar de nieuwe dir in de afbeeldingenmap.
shutil.move(bestand,nieuwe_locatie)
except: pass
time.sleep(100)