#!/usr/bin/python
import os
import sys
import elementtree.ElementTree
import kimdaba_album
# tool for finding old .capt or .xml files, slurping them into the
# kimdaba index.xml, and purging the old files.
# sample foo.jpg.xml looks like:
# <?xml version="1.0" encoding="UTF-8"?>
# <image><description>
# <field name="title">xmas wreath</field>
# </description>
# <bins></bins>
# <exif></exif>
# </image>
#
# more complete form:
# <?xml version="1.0" encoding="UTF-8"?><image><description>
# <field name="location">Concord, MA</field>
# <field name="people"></field>
# <field name="description">Snow still on the ground in late march.</field>
# <field name="event"></field>
# <field name="title">Snow in the yard</field>
# </description>
# <bins></bins>
# <exif>
# <tag name="MeteringMode">
# </tag>
# <tag name="JPEG_Type">
# Baseline
# </tag>
# </exif>
# </image>
# <exif> is entirely stuff in the .jpg
# <bins> is stuff pushed back in later
# so ignore both... but where is requested rotation stored???
#
#
# foo.jpg.capt looks like:
# title: xmas wreath
#
# capts (mixed-case tag names):
# Description
# Title
# Event
# Location
# Mail (only one value, and not *about* the picture)
# People (comma separated, ? means unk. or partly unk)
#
# (generate mappings in advance...)
def get_fields_from_xml(pathpart):
print "GET:", pathpart
xstuff = elementtree.ElementTree.ElementTree(file=pathpart + ".xml")
# <field name="description">Snow still on the ground in late march.</field>
fields = {}
for field in xstuff.findall("description/field"):
fields[field.get("name")] = field.text
return fields
known_fields = set(["location", "people", "description", "event", "title", "mail"])
# map title-cased capt values to corresponding Persons options on the
# kimdaba side
peoplemap = {
}
# get this from options/option off the top level...
# however, to get a clean run you can prime it with values here
known_people = set()
def load_known_people(options):
# (album.findall("options/option"))
for option in options:
if option.get("name") == "Persons":
for value in option.findall("value"):
known_people.add(value.get("value"))
print known_people
def add_img_keyword(img, kind, value):
assert kind in set(["Keywords", "Persons", "Locations"])
for optionset in img.findall("options/option"):
if optionset.get("name") == kind:
# avoid duplicates?
optionset.append(optionset.makeelement("value", dict(value=value)))
return True
else:
if not img.findall("options"):
img.append(img.makeelement("options", dict()))
options = img.find("options")
options.append(options.makeelement("option", dict(name=kind)))
optionset = img.find("options/option")
optionset.append(optionset.makeelement("value", dict(value=value)))
return True
def add_img_person(img, person):
return add_img_keyword(img, "Persons", person)
class converter:
@classmethod
def convert(cls, img, fieldname, fieldvalue):
methname = "convert_%s" % fieldname
if not hasattr(cls, methname):
raise KeyError("No converter for %s" % fieldname)
return getattr(cls, methname)(img, fieldvalue)
@classmethod
def convert_location(cls, img, fieldvalue):
print "WHERE:", fieldvalue
@classmethod
def convert_people(cls, img, fieldvalue):
if fieldvalue == "sassafras foxy":
fieldvalue = "sassafras, foxy"
for person in fieldvalue.split(","):
person = person.strip().title()
if person in peoplemap:
add_img_person(img, peoplemap[person])
elif person in known_people:
add_img_person(img, person)
else:
print "WHO:", person
return True # only if not printed?
@classmethod
def convert_description(cls, img, fieldvalue):
img.set("description", fieldvalue)
return True
@classmethod
def convert_event(cls, img, fieldvalue):
print "EVENT:", fieldvalue
# sometimes this appends to description...
@classmethod
def convert_title(cls, img, fieldvalue):
img.set("label", fieldvalue)
return True
@classmethod
def convert_mail(cls, img, fieldvalue):
# the mail tag was a hint that one of the tools should
# *send it as email* to that person, not that it was anything
# about the picture, so just discard it explicitly.
return
def convert_fields(img, fields):
did_something = False
for field in fields:
did_something = converter.convert(img, field, fields[field]) or did_something
return did_something
def process_bins(workdir, fake=False):
"""Process any bins/capt remnants into the xml file."""
# grab the filenames, then walk the xml
has_xml = set([os.path.join(workdir, f.replace(".xml", "", 1))
for f in os.listdir(workdir)
if f.endswith(".jpg.xml")])
albumfile = kimdaba_album.kimdaba_default_album()
album = kimdaba_album.parse(albumfile)
load_known_people(album.findall("options/option"))
did_something = False
nuke_remnants = []
for img in album.findall("images/image"):
pathpart = img.get("file")
if pathpart in has_xml:
fields = get_fields_from_xml(pathpart)
# make sure we don't have anything strange...
unkfields = set(fields) - known_fields
if unkfields:
print "Unknown fields", unkfields, "in", pathpart
raise Exception("Bad Field %s" % unkfields)
# mapper class?
if convert_fields(img, fields):
did_something = True
nuke_remnants.append(pathpart)
if did_something:
if not fake:
kimdaba_album.safe_replace(albumfile, album)
for remnant in nuke_remnants:
os.remove(remnant + ".capt")
os.remove(remnant + ".xml")
print "NOW re-run kimdaba, to get the new keywords in the menu"
else:
print "diff -u", albumfile, "/tmp/c2k.xml"
album.write("/tmp/c2k.xml")
for remnant in nuke_remnants:
print "rm", remnant, ".capt/.xml"
if __name__ == "__main__":
no_act = False
if "--no-act" in sys.argv:
no_act = True
sys.argv.remove("--no-act")
prog, workpath = sys.argv
# workpath should be the yeardir; we should be in
# the dir with index.xml when we run this
process_bins(workpath, fake=no_act)