summaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rwxr-xr-xbin/pyggle362
1 files changed, 211 insertions, 151 deletions
diff --git a/bin/pyggle b/bin/pyggle
index 3fb5e66..ecef2b1 100755
--- a/bin/pyggle
+++ b/bin/pyggle
@@ -19,10 +19,6 @@ geocoder = None
location_cache = dict()
-class ProgressBar(Bar):
- suffix = "%(percent).0f%% [%(elapsed_td)s/%(eta_td)s]"
-
-
def rotate_image(image, exif_tag):
if "Image Orientation" not in exif_tag:
return image
@@ -45,159 +41,245 @@ def format_f(value, precision=1):
return f"{value:.{precision}f}"
-def format_gps(exif_tag):
- try:
- lat = exif_tag["GPS GPSLatitude"]
- latref = exif_tag["GPS GPSLatitudeRef"].values[0]
- lon = exif_tag["GPS GPSLongitude"]
- lonref = exif_tag["GPS GPSLongitudeRef"].values[0]
- except KeyError:
- return None
-
- try:
- lat = (
- float(lat.values[0])
- + float(lat.values[1]) / 60
- + float(lat.values[2]) / 3600
- )
- lon = (
- float(lon.values[0])
- + float(lon.values[1]) / 60
- + float(lon.values[2]) / 3600
- )
- except (IndexError, ZeroDivisionError):
- return None
+class ProgressBar(Bar):
+ suffix = "%(percent).0f%% [%(elapsed_td)s/%(eta_td)s]"
- if abs(lat) < 0.01 and abs(lon) < 0.01:
- return None
- if latref == "S":
- lat = -lat
+class GPSData:
+ def __init__(self, lat, lon, location):
+ self.lat = lat
+ self.lon = lon
+ self.location = location
+
+
+class ThumbnailHTML:
+ def __init__(self):
+ self.gps = None
+ self.datetime = None
+ self.file_link = None
+ self.make = None
+ self.focus = None
+
+ def set_datetime(self, dt):
+ self.datetime = dt.strftime("""<span class="datetime">%d.%m.%Y %H:%M</span>""")
+
+ def set_focus(self, f_num, exposure, focal_length, focal_length35, iso):
+ entries = list()
+ if f_num is not None:
+ entries.append(f"""<span class="fnumber">f/{format_f(f_num)}</span>""")
+
+ if exposure is not None:
+ if exposure >= 1:
+ entries.append(
+ f"""<span class="exposure">{format_f(exposure)}s</span>"""
+ )
+ elif exposure >= 1e-3:
+ entries.append(
+ f"""<span class="exposure">{format_f(exposure * 1e3)}ms</span>"""
+ )
+ else:
+ entries.append(
+ f"""<span class="exposure">{format_f(exposure * 1e6)}µs</span>"""
+ )
+
+ if focal_length is not None:
+ entry = f"{format_f(focal_length)}mm"
+ if focal_length35 is not None and focal_length35 != focal_length:
+ entry += f" (≙ {format_f(focal_length35)}mm)"
+ entries.append(f"""<span class="focal">{entry}</span>""")
+
+ if iso is not None:
+ entries.append(f"""<span class="iso">ISO{iso}</span>""")
+
+ self.focus = " ".join(entries)
+
+ def set_gps(self, gps):
+ self.gps = f"""<span class="gps"><a href="https://www.openstreetmap.org/?mlat={gps.lat}&mlon={gps.lon}#map=13/{gps.lat}/{gps.lon}">{gps.location}</a></span>"""
+
+ def set_makemodel(self, make, model):
+ self.make = f"""<span class="makemodel">{make} {model}</span>"""
+
+ def to_html(self, index, filename, thumbname):
+ exif_lines = (self.datetime, self.gps, self.make, self.focus)
+ exif_html = """ <span class="sep">•</span> """.join(filter(bool, exif_lines))
+
+ buf = """<div class="image-container">\n"""
+ buf += f"""<a href="{filename}" class="glightbox" data-gallery="gallery1" data-description=".gdesc{index}">"""
+ buf += f"""<img src="{thumbname}" alt="{filename}" />"""
+ buf += "</a>"
+ buf += "</div>"
+ buf += f"""<div class="glightbox-desc gdesc{index}">\n"""
+ buf += (
+ f"""<p><span class="download"><a href="{filename}">{filename}</a></span>"""
+ )
+ buf += f"""<span class="sep">•</span>{exif_html}</p>\n"""
+ buf += "</div>\n"
- if lonref == "W":
- lon = -lon
+ return buf
- global location_cache
- global geocoder
- latlon = f"{lat:.3f}/{lon:.3f}"
+class Thumbnail:
+ def __init__(self, filename, im, size=250, with_gps=False):
+ self.filename = filename
+ self.size = size
+ self.exif_dt = None
+ self.gps = None
- if latlon in location_cache:
- return f"""<span class="gps"><a href="https://www.openstreetmap.org/?mlat={lat}&mlon={lon}#map=13/{lat}/{lon}">{location_cache[latlon]}</a></span>"""
+ with open(filename, "rb") as f:
+ self.exif_tag = exifread.process_file(f)
- if geocoder is None:
- from geopy.geocoders import Nominatim
+ self.thumbname = f".thumbnails/{filename}"
+ if not filename.lower().endswith((".jpeg", ".jpg")):
+ self.thumbname += ".jpg"
- geocoder = Nominatim(user_agent="pyggle +https://github.com/derf/pyggle")
+ im = rotate_image(im, self.exif_tag)
+ im.thumbnail((self.size * 2, self.size * 2))
+ im.convert("RGB").save(self.thumbname, "JPEG")
- # zoom level: 12/13 -> city, 14/15 -> district, 16/17 -> street, 18 -> house no
- try:
- res = geocoder.reverse((lat, lon), zoom=args.nominatim_zoom)
- except TypeError as e:
- return None
+ self.html = ThumbnailHTML()
- location = res.address.split(",")[0]
- location_cache[latlon] = location
+ self._get_datetime()
+ self._get_focus()
+ self._get_makemodel()
- return f"""<span class="gps"><a href="https://www.openstreetmap.org/?mlat={lat}&mlon={lon}#map=13/{lat}/{lon}">{location}</a></span>"""
+ if with_gps:
+ self._get_gps()
+ def _get_datetime(self):
+ dt = None
-def format_fsi(exif_tag):
- entries = list()
+ try:
+ dt = datetime.strptime(
+ self.exif_tag["EXIF DateTimeOriginal"].values, "%Y:%m:%d %H:%M:%S"
+ )
+ except (KeyError, ValueError):
+ try:
+ dt = datetime.strptime(
+ self.exif_tag["Image DateTimeOriginal"].values, "%Y:%m:%d %H:%M:%S"
+ )
+ except (KeyError, ValueError):
+ pass
+
+ if dt:
+ self.exif_dt = dt
+ self.html.set_datetime(dt)
+
+ def _get_focus(self):
+ entries = list()
+
+ f_num = None
+ exposure = None
+ focal_length = None
+ focal_length35 = None
+ iso = None
- try:
- f_num = float(exif_tag["EXIF FNumber"].values[0])
- entries.append(f"""<span class="fnumber">f/{format_f(f_num)}</span>""")
- except (KeyError, ZeroDivisionError):
- pass
+ try:
+ f_num = float(self.exif_tag["EXIF FNumber"].values[0])
+ except (KeyError, ZeroDivisionError):
+ pass
- try:
- exposure = float(exif_tag["EXIF ExposureTime"].values[0])
- if exposure >= 1:
- entries.append(f"""<span class="exposure">{format_f(exposure)}s</span>""")
- elif exposure >= 1e-3:
- entries.append(
- f"""<span class="exposure">{format_f(exposure * 1e3)}ms</span>"""
- )
- else:
- entries.append(
- f"""<span class="exposure">{format_f(exposure * 1e6)}µs</span>"""
- )
- except (KeyError, ZeroDivisionError):
- pass
+ try:
+ exposure = float(self.exif_tag["EXIF ExposureTime"].values[0])
+ except (KeyError, ZeroDivisionError):
+ pass
- try:
- focal_length = float(exif_tag["EXIF FocalLength"].values[0])
- entry = f"{format_f(focal_length)}mm"
try:
- focal_length35 = float(exif_tag["EXIF FocalLengthIn35mmFilm"].values[0])
- entry += f" (≙ {format_f(focal_length35)}mm)"
+ focal_length = float(self.exif_tag["EXIF FocalLength"].values[0])
+ focal_length35 = float(
+ self.exif_tag["EXIF FocalLengthIn35mmFilm"].values[0]
+ )
except (KeyError, ZeroDivisionError):
pass
- entries.append(f"""<span class="focal">{entry}</span>""")
- except (KeyError, ZeroDivisionError):
- pass
- try:
- iso = exif_tag["EXIF ISOSpeedRatings"].values[0]
- entries.append(f"""<span class="iso">ISO{iso}</span>""")
- except KeyError:
- pass
+ try:
+ iso = self.exif_tag["EXIF ISOSpeedRatings"].values[0]
+ except KeyError:
+ pass
- return " ".join(entries)
+ self.html.set_focus(f_num, exposure, focal_length, focal_length35, iso)
+ def _get_gps(self):
+ try:
+ lat = self.exif_tag["GPS GPSLatitude"]
+ latref = self.exif_tag["GPS GPSLatitudeRef"].values[0]
+ lon = self.exif_tag["GPS GPSLongitude"]
+ lonref = self.exif_tag["GPS GPSLongitudeRef"].values[0]
+ except KeyError:
+ return
-def format_make_model_lens(exif_tag):
- try:
- make = exif_tag["Image Make"].values
- model = exif_tag["Image Model"].values
- except KeyError:
- return None
+ try:
+ lat = (
+ float(lat.values[0])
+ + float(lat.values[1]) / 60
+ + float(lat.values[2]) / 3600
+ )
+ lon = (
+ float(lon.values[0])
+ + float(lon.values[1]) / 60
+ + float(lon.values[2]) / 3600
+ )
+ except (IndexError, ZeroDivisionError):
+ return
- if model.startswith(make):
- model = model[len(make) :]
- if model[0] == " ":
- model = model[1:]
+ if abs(lat) < 0.01 and abs(lon) < 0.01:
+ return
- try:
- lens = exif_tag["EXIF LensModel"]
- if lens:
- model += f" + {lens}"
- except KeyError:
- # Unknown or built-in lens
- pass
+ if latref == "S":
+ lat = -lat
- return f"""<span class="makemodel">{make} {model}</span>"""
+ if lonref == "W":
+ lon = -lon
+ global location_cache
+ global geocoder
-def format_exif(exif_tag):
- exif_lines = list()
- dt = None
+ latlon = f"{lat:.3f}/{lon:.3f}"
- try:
- dt = datetime.strptime(
- exif_tag["EXIF DateTimeOriginal"].values, "%Y:%m:%d %H:%M:%S"
- )
- except (KeyError, ValueError):
+ if latlon in location_cache:
+ self.gps = GPSData(lat, lon, location_cache[latlon])
+ self.html.set_gps(self.gps)
+ return
+
+ if geocoder is None:
+ from geopy.geocoders import Nominatim
+
+ geocoder = Nominatim(user_agent="pyggle +https://github.com/derf/pyggle")
+
+ # zoom level: 12/13 -> city, 14/15 -> district, 16/17 -> street, 18 -> house no
try:
- dt = datetime.strptime(
- exif_tag["Image DateTimeOriginal"].values, "%Y:%m:%d %H:%M:%S"
- )
- except (KeyError, ValueError):
- pass
+ res = geocoder.reverse((lat, lon), zoom=args.nominatim_zoom)
+ location = res.address.split(",")[0]
+ location_cache[latlon] = location
+ except TypeError as e:
+ location = latlon
- if dt:
- exif_lines.append(
- dt.strftime("""<span class="datetime">%d.%m.%Y %H:%M</span>""")
- )
+ self.gps = GPSData(lat, lon, location)
+ self.html.set_gps(self.gps)
- if args.with_nominatim:
- exif_lines.append(format_gps(exif_tag))
+ def _get_makemodel(self):
+ try:
+ make = self.exif_tag["Image Make"].values
+ model = self.exif_tag["Image Model"].values
+ except KeyError:
+ return
- exif_lines.append(format_make_model_lens(exif_tag))
- exif_lines.append(format_fsi(exif_tag))
+ if model.startswith(make):
+ model = model[len(make) :]
+ if model[0] == " ":
+ model = model[1:]
- return """ <span class="sep">•</span> """.join(filter(bool, exif_lines))
+ try:
+ lens = self.exif_tag["EXIF LensModel"]
+ if lens:
+ model += f" + {lens}"
+ except KeyError:
+ # Unknown or built-in lens
+ pass
+
+ self.html.set_makemodel(make, model)
+
+ def to_html(self, index):
+ return self.html.to_html(i, self.filename, self.thumbname)
def copy_files(base_dir):
@@ -230,20 +312,6 @@ def copy_files(base_dir):
f.write(main_css)
-def create_thumbnail_html(index, thumbname, filename, title):
- buf = """<div class="image-container">\n"""
- buf += f"""<a href="{filename}" class="glightbox" data-gallery="gallery1" data-description=".gdesc{index}">"""
- buf += f"""<img src="{thumbname}" alt="{filename}" />"""
- buf += "</a>"
- buf += "</div>"
- buf += f"""<div class="glightbox-desc gdesc{index}">\n"""
- buf += f"""<p><span class="download"><a href="{filename}">{filename}</a></span>"""
- buf += f"""<span class="sep">•</span>{title}</p>\n"""
- buf += "</div>\n"
-
- return buf
-
-
if __name__ == "__main__":
parser = argparse.ArgumentParser(
@@ -283,28 +351,20 @@ if __name__ == "__main__":
html_buf += f.read()
filenames = args.images
+ thumbnails = list()
for i, filename in enumerate(ProgressBar(max=len(filenames)).iter(filenames)):
- with open(filename, "rb") as f:
- exif_tag = exifread.process_file(f)
-
try:
im = Image.open(filename)
except PIL.UnidentifiedImageError:
continue
- im = rotate_image(im, exif_tag)
-
- im.thumbnail((args.size * 2, args.size * 2))
-
- thumbname = f".thumbnails/{filename}"
-
- if not filename.lower().endswith((".jpeg", ".jpg")):
- thumbname += ".jpg"
-
- im.convert("RGB").save(thumbname, "JPEG")
+ thumbnails.append(
+ Thumbnail(filename, im, size=args.size, with_gps=args.with_nominatim)
+ )
- html_buf += create_thumbnail_html(i, thumbname, filename, format_exif(exif_tag))
+ for i, thumbnail in enumerate(thumbnails):
+ html_buf += thumbnail.to_html(i)
with open(f"{base_dir}/share/html_end", "r") as f:
html_buf += f.read()