Blob Blame History Raw
#!/usr/bin/env python3

#======================================================================

import subprocess, os, glob, datetime, tempfile, argparse
from xml.etree import ElementTree
import re
import argparse
import shutil

#======================================================================
# Function and class definitions
#----------------------------------------------------------------------

#----------------------------------------------------------------------
def find_best(root, tagname):
    texts = [(len(element.text), element.text) for element in root.findall(tagname) if element.attrib.get("lang") == "en"]
    if texts:
        return sorted(texts)[0][1]
    texts = [(len(element.text), element.text) for element in root.findall(tagname)]
    if texts:
        return sorted(texts)[0][1]
    else: 
        return None

def print_x(value):
    return ' class="lenslist-highlight lenslist-check">yes' if value else ' class="lenslist-check">no'

#----------------------------------------------------------------------
# class to hold camera information
class Camera:
    camera_makers = {}
    def __init__(self, element):
        self.maker = find_best(element, "maker")
        self.model = find_best(element, "model")
        variant = find_best(element, "variant");
        if variant:
            self.model = self.model + " " + variant
        self.crop = float(element.find("cropfactor").text)
        self.camera_makers.setdefault(self.maker, set()).add(self.model)
    def __lt__(self, other):
        return (self.maker, self.model) < (other.maker, other.model)

#----------------------------------------------------------------------
# class to hold lens information
class Lens:
    def __init__(self, element, root, camtype):
        self.maker = find_best(element, "maker")
        self.model = find_best(element, "model")
        if camtype == "compact":
            mount = element.find("mount").text
            for camera in root.findall("camera"):
                if camera.find("mount").text == mount:
                    self.maker = find_best(camera, "maker")
                    camname = find_best(camera, "model") 
                    variant = find_best(camera, "variant");
                    if variant:
                        camname = camname + " " + variant
                    break
            self.model = "Fixed lens {}".format(camname)
        try:
            self.crop = float(element.find("cropfactor").text)
        except:
            self.crop = None
        self.distortion = element.find("calibration/distortion") is not None
        self.tca = element.find("calibration/tca") is not None
        self.vignetting = element.find("calibration/vignetting") is not None
    def __lt__(self, other):
        return (self.maker, self.model, self.crop) < (other.maker, other.model, self.crop)

#======================================================================



#======================================================================
# Main routine
#----------------------------------------------------------------------

# set up the commandline parser and define some arguemnts
parser = argparse.ArgumentParser(description='Create a list of all cameras and lenses in the Lensfun database.')
parser.add_argument('db_path', metavar='DB_PATH', help='path to the Lensfun database', default='/usr/share/lensfun/', nargs='?')
parser.add_argument('-g', dest='git', action='store_true', help='use current database from git repository')
parser.add_argument('-t', dest='table_only', action='store_true', help='pure table/list without surrounding header and description')
parser.add_argument('-m', dest='markdown', action='store_true', help='output markdown instead of HTML')
parser.add_argument('-o', dest='outfile', action='store', help='output filename and path', default='./lensfun_coverage.html')

cmdline_args = vars(parser.parse_args())


# decide wether to get the most up to date database from git or use the locally installed database
if cmdline_args['git']==True:
    XmlDBPath = os.path.join(tempfile.gettempdir(), "lensfun")
    print("~ Lensfun database is retrieved directly from git")
    if os.path.isdir(XmlDBPath):
        shutil.rmtree(XmlDBPath)
    subprocess.check_output(["git", "clone", "http://git.code.sf.net/p/lensfun/code", XmlDBPath])
    XmlDBPath = os.path.join(XmlDBPath, "data", "db")
else:
    XmlDBPath = cmdline_args['db_path']
    print("~ Lensfun database is searched in "+XmlDBPath)

# parse the database create a list of camera and lens objects
cameras, lenses = [], []
for filename in glob.glob(os.path.join(XmlDBPath,"*.xml")):
    root = ElementTree.parse(filename)

    camtype_search = re.search('.*/([^-]*)-(.*).xml', filename, re.IGNORECASE)    
    if camtype_search:
        camtype = camtype_search.group(1)
    else:
        camtype = 'generic'

    cameras.extend(Camera(camera_element) for camera_element in root.findall("camera"))
    lenses_list = [Lens(lens_element, root, camtype) for lens_element in root.findall("lens")]
    lenses.extend(lens for lens in lenses_list if lens.distortion or lens.tca or lens.vignetting)

cameras.sort()
lenses.sort(key=lambda Lens: Lens.model.lower())
lenses.sort(key=lambda Lens: Lens.maker.lower())

# finally write the table into the output file
outfile = open(cmdline_args['outfile'], "w")

#----------------------------------------------------------------------
# write HTML table or markdown formatted list
if cmdline_args['markdown'] == False:

    #----------------------------------------------------------------------
    # HTML table
    if cmdline_args['table_only'] == False:
        outfile.write("<html><head><title>Lensfun's coverage</title></head><body><h1>Lensfun coverage</h1><h2>Lenses (count: {})</h2>"
                  "<p>This table was generated on {} from current Lensfun sources.  Your Lensfun version may be older, resulting in "
                  "less coverage.  If your lens is not included, see</p><ul><li><a href='/calibration'>Upload calibration pictures</a>"
                  "</li><li><a href='lens_calibration_tutorial/'>Lens calibration for Lensfun</a></li></ul>\n".format(
                      len(lenses), datetime.date.today()))

    outfile.write("<table border='1'><thead><tr><th>manufacturer</th><th>model</th><th>crop</th><th>dist.</th><th>TCA</th>"
                  "<th>vign.</th></tr></thead><tbody>\n")
    number_of_makers = 0
    previous_maker = None

    for lens in lenses:
        if lens.maker.lower() != previous_maker:
            number_of_makers += 1

        outfile.write("""<tr{}><td>{}</td><td>{}</td><td>{}</td><td{}</td><td{}</td><td{}</td></tr>\n""".format(
            ' class="lenslist-bg1"' if number_of_makers % 2 else ' class="lenslist-bg2"',
            lens.maker if lens.maker.lower() != previous_maker else "", lens.model, lens.crop or "?",
            print_x(lens.distortion), print_x(lens.tca), print_x(lens.vignetting)))
        previous_maker = lens.maker.lower()

    outfile.write("</tbody></table><h2>Cameras</h2><p>Note that new camera models can be added very easily.  "
              "Contact the Lensfun maintainers for this.</p>")

    for maker, cameras in sorted(Camera.camera_makers.items()):
        outfile.write("<p><strong>{}</strong>: {}</p>".format(maker, ", ".join(sorted(cameras))))

    if cmdline_args['table_only'] == False:
        outfile.write("</body></html>")
else:

    #----------------------------------------------------------------------
    # Markdown list

    if cmdline_args['table_only'] == False:
        outfile.write("= Lensfun's coverage = \n\n == Lenses (count: {}) ==\n\n"
                  "This list was generated on {} from current Lensfun sources.  Your Lensfun version may be older, resulting in "
                  "less coverage.  \nIf your lens is not included, see \n\n* <a href='/calibration'>Upload calibration pictures</a>\n"
                  "* <a href='lens_calibration_tutorial/'>Lens calibration for Lensfun</a>\n\n".format(
                      len(lenses), datetime.date.today()))

    number_of_makers = 0
    previous_maker = None

    for lens in lenses:
        if lens.maker.lower() != previous_maker:
            number_of_makers += 1

        if lens.maker.lower() != previous_maker:
            outfile.write("\n== {} ==\n\n".format(lens.maker))

        outfile.write("* {} ({}, {}/{}/{})\n".format(lens.model, lens.crop or "?",
            "D" if lens.distortion else "-", 
            "T" if lens.tca else "-",
            "V" if lens.vignetting else "-"))
        previous_maker = lens.maker.lower()

outfile.close()
print("~ List of lenses was written to "+cmdline_args['outfile'])