diff options
| -rwxr-xr-x | bin/pyggle | 295 |
1 files changed, 162 insertions, 133 deletions
@@ -11,8 +11,8 @@ import exifread import json import os import PIL +from multiprocessing import Pool from PIL import Image -from progress.bar import Bar import shutil import subprocess import sys @@ -73,10 +73,6 @@ def format_f(value, precision=1): return f"{value:.{precision}f}" -class ProgressBar(Bar): - suffix = "%(percent).0f%% [%(elapsed_td)s/%(eta_td)s]" - - class GPSData: def __init__(self, lat, lon, location): self.lat = lat @@ -141,7 +137,7 @@ class ImageHTML: self, f_num, exposure, focal_length, focal_length35, crop_factor, iso ): entries = list() - if f_num is not None: + if f_num is not None and float(f_num) > 0: self.f_num = f"""<span class="fnumber">f/{format_f(f_num)}</span>""" entries.append(self.f_num) @@ -160,7 +156,7 @@ class ImageHTML: ) entries.append(self.exposure) - if focal_length is not None: + if focal_length is not None and float(focal_length) > 0: 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)" @@ -296,6 +292,8 @@ class Thumbnail: self.group_key = None self.file_key = None + self.kept_thumbnails = list() + with open(filename, "rb") as f: try: self.exif_tag = exifread.process_file(f) @@ -334,7 +332,13 @@ class Thumbnail: if not self.thumbname.lower().endswith((".jpeg", ".jpg")): self.thumbname += ".jpg" - if not have_thumbnail.pop(self.thumbname, False): + if have_thumbnail.pop(f".thumbnails/{thumb_filename}.p.jpg", None): + self.jpegname = f".thumbnails/{thumb_filename}.p.jpg" + self.kept_thumbnails.append(self.jpegname) + + if have_thumbnail.pop(self.thumbname, False): + self.kept_thumbnails.append(self.thumbname) + else: if not filename.lower().endswith((".cr2", ".cr3", ".rw2")): im = rotate_image(im, self.exif_tag) @@ -342,7 +346,9 @@ class Thumbnail: im = im.convert("RGB") im.save(self.thumbname, "JPEG") - if filename.lower().endswith((".cr2", ".cr3", ".rw2")): + if not self.jpegname and filename.lower().endswith( + (".cr2", ".cr3", ".rw2") + ): try: jpegname = f".thumbnails/{thumb_filename}.p.jpg" subprocess.run( @@ -372,8 +378,6 @@ class Thumbnail: rotate_preview(jpegname, self.exif_tag) except FileNotFoundError: pass - elif have_thumbnail.pop(f".thumbnails/{thumb_filename}.p.jpg", None): - self.jpegname = f".thumbnails/{thumb_filename}.p.jpg" if args.with_detail_page and 0: self.average_color = im.resize((1, 1)).getpixel((0, 0)) @@ -410,7 +414,7 @@ class Thumbnail: def _get_caption(self): try: - with open(f".captions/{filename}.txt", "r") as f: + with open(f".captions/{self.filename}.txt", "r") as f: self.html.set_caption(f.read()) except FileNotFoundError: pass @@ -568,10 +572,12 @@ class Thumbnail: global location_cache global geocoder - latlon = f"{lat:.3f}/{lon:.3f}" + latlon = f"{lat:.4f}/{lon:.4f}" - if latlon in location_cache: - self.gps = GPSData(lat, lon, location_cache[latlon]) + if str(args.nominatim_zoom) in location_cache.get(latlon, dict()): + self.gps = GPSData( + lat, lon, location_cache[latlon][str(args.nominatim_zoom)] + ) self.html.set_gps(self.gps) return @@ -584,9 +590,14 @@ class Thumbnail: try: res = geocoder.reverse((lat, lon), zoom=args.nominatim_zoom) location = res.address.split(",")[0] - location_cache[latlon] = location + if latlon not in location_cache: + location_cache[latlon] = dict() + location_cache[latlon][str(args.nominatim_zoom)] = location except TypeError as e: location = latlon + except Exception as e: + print(e) + return self.gps = GPSData(lat, lon, location) self.html.set_gps(self.gps) @@ -605,7 +616,7 @@ class Thumbnail: try: lens = self.exif_tag["EXIF LensModel"] - if lens: + if lens and str(lens): model += f" + {lens}" except KeyError: # Unknown or built-in lens @@ -711,6 +722,120 @@ def write_gallery( f.write(file_buf) +def _make_thumbnail(args): + filename, args, thumb_arg = args + try: + im = Image.open(filename) + except PIL.UnidentifiedImageError: + try: + im = Image.open(f".thumbnail.for.{filename}") + except FileNotFoundError: + im = None + except PIL.UnidentifiedImageError: + # perhaps raise a warning? + im = None + + if not im: + try: + _, jpegname = tempfile.mkstemp(suffix="jpg") + subprocess.run( + [ + "exiftool", + "-quiet", + "-binary", + "-tagOut!", + jpegname, + "-PreviewImage", + filename, + ] + ) + # JpgFromRaw tends to have higher resolution, so overwrite PreviewImage if it is present + subprocess.run( + [ + "exiftool", + "-quiet", + "-binary", + "-tagOut!", + jpegname, + "-JpgFromRaw", + filename, + ] + ) + + im = Image.open(jpegname) + os.remove(jpegname) + except FileNotFoundError: + im = None + except PIL.UnidentifiedImageError: + # perhaps raise a warning? + os.remove(jpegname) + im = None + + if not im: + return None, None + + try: + im_copy = im.copy() + except Exception as e: + print(f"Cannot load image '{filename}': {e}", file=sys.stderr) + return None, None + + thumbnail = Thumbnail( + filename, + im_copy, + size=args.size, + group_key_template=args.group, + file_key_template=args.group_files, + **thumb_arg, + ) + + if ( + args.edit_in_place + and args.resize + and (im.size[0] > args.resize or im.size[1] > args.resize) + ): + subprocess.run( + [ + "mogrify", + "-resize", + f"{args.resize}x{args.resize}", + filename, + ] + ) + + exiftool_args = list() + + if args.caption_to_exif and thumbnail.html.caption: + exiftool_args.append(f"-File:Comment={thumbnail.html.caption}") + + if args.exif_copyright: + exiftool_args.append(f"-EXIF:Copyright={args.exif_copyright}") + + if args.scrub_metadata: + exiftool_args.extend( + [ + "-EXIF:SerialNumber=", + "-EXIF:LensSerialNumber=", + "-Makernotes:all=", + "-geotag=", + "-ThumbnailImage=", + ] + ) + + if args.edit_in_place and exiftool_args: + subprocess.run( + [ + "exiftool", + "-q", + "-overwrite_original", + ] + + exiftool_args + + [filename] + ) + + return (filename, thumbnail) + + if __name__ == "__main__": parser = argparse.ArgumentParser( @@ -761,7 +886,6 @@ if __name__ == "__main__": default=16, help="Zoom Level for reverse geocoding", ) - parser.add_argument("--quiet", action="store_true", help="Do not show progress bar") parser.add_argument( "--resize", metavar="N", @@ -817,6 +941,10 @@ if __name__ == "__main__": if not args.cdn: copy_files(f"{base_dir}/share") + if args.with_nominatim and os.path.exists(".thumbnails/location_cache.json"): + with open(".thumbnails/location_cache.json", "r") as f: + location_cache = json.load(f) + with open(f"{base_dir}/share/html_start", "r") as f: html_buf = f.read().replace("<!-- $title -->", args.title) @@ -832,122 +960,19 @@ if __name__ == "__main__": filenames = args.images thumbnails = list() - if args.quiet: - file_iter = filenames - else: - file_iter = ProgressBar(max=len(filenames)).iter(filenames) - - for i, filename in enumerate(file_iter): - try: - im = Image.open(filename) - except PIL.UnidentifiedImageError: - try: - im = Image.open(f".thumbnail.for.{filename}") - except FileNotFoundError: - im = None - except PIL.UnidentifiedImageError: - # perhaps raise a warning? - im = None - - if not im: - try: - _, jpegname = tempfile.mkstemp(suffix="jpg") - subprocess.run( - [ - "exiftool", - "-quiet", - "-binary", - "-tagOut!", - jpegname, - "-PreviewImage", - filename, - ] - ) - # JpgFromRaw tends to have higher resolution, so overwrite PreviewImage if it is present - subprocess.run( - [ - "exiftool", - "-quiet", - "-binary", - "-tagOut!", - jpegname, - "-JpgFromRaw", - filename, - ] - ) - - im = Image.open(jpegname) - os.remove(jpegname) - except FileNotFoundError: - im = None - except PIL.UnidentifiedImageError: - # perhaps raise a warning? - os.remove(jpegname) - im = None - - if not im: - continue - - try: - im_copy = im.copy() - except Exception as e: - print(f"Cannot load image '{filename}': {e}", file=sys.stderr) - continue - - thumbnail = Thumbnail( - filename, - im_copy, - size=args.size, - with_gps=args.with_nominatim, - group_key_template=args.group, - file_key_template=args.group_files, - have_thumbnail=rm_thumbnail, - ) - thumbnails.append(thumbnail) - - if ( - args.edit_in_place - and args.resize - and (im.size[0] > args.resize or im.size[1] > args.resize) - ): - subprocess.run( - [ - "mogrify", - "-resize", - f"{args.resize}x{args.resize}", - filename, - ] - ) - - exiftool_args = list() - - if args.caption_to_exif and thumbnail.html.caption: - exiftool_args.append(f"-File:Comment={thumbnail.html.caption}") - - if args.exif_copyright: - exiftool_args.append(f"-EXIF:Copyright={args.exif_copyright}") - - if args.scrub_metadata: - exiftool_args.extend( - [ - "-EXIF:SerialNumber=", - "-EXIF:LensSerialNumber=", - "-Makernotes:all=", - "-geotag=", - "-ThumbnailImage=", - ] - ) + mkthumb_args = list( + map(lambda fn: (fn, args, {"have_thumbnail": rm_thumbnail}), filenames) + ) + with Pool() as pool: + raw_thumbnails = pool.map(_make_thumbnail, mkthumb_args) - if args.edit_in_place and exiftool_args: - subprocess.run( - [ - "exiftool", - "-q", - "-overwrite_original", - ] - + exiftool_args - + [filename] - ) + for filename, thumbnail in raw_thumbnails: + if thumbnail is not None: + if args.with_nominatim: + thumbnail._get_gps() + thumbnails.append(thumbnail) + for kept_thumbnail in thumbnail.kept_thumbnails: + rm_thumbnail.pop(kept_thumbnail) for rm_file in rm_thumbnail.keys(): os.remove(rm_file) @@ -978,6 +1003,10 @@ if __name__ == "__main__": write_gallery(html_buf, "index.html", thumbnails, group=args.group) + if args.with_nominatim: + with open(".thumbnails/location_cache.json", "w") as f: + json.dump(location_cache, f) + if args.group_files != "none": thumbnail_keys = list(sorted(set(map(lambda t: t.file_key, thumbnails)))) for i, thumbnail_key in enumerate(thumbnail_keys): |
