diff options
-rwxr-xr-x | bin/pyggle | 223 | ||||
-rw-r--r-- | share/html_start | 15 |
2 files changed, 168 insertions, 70 deletions
@@ -38,6 +38,35 @@ def rotate_image(image, exif_tag): return image +def rotate_preview(filename, exif_tag): + if "Image Orientation" not in exif_tag: + return + + orientation = exif_tag["Image Orientation"].values + rotation = None + + if 3 in orientation: + rotation = "-1" + if 6 in orientation: + rotation = "-9" + if 8 in orientation: + rotation = "-2" + + if not rotation: + return + + subprocess.run( + [ + "exiftran", + "-i", + rotation, + filename, + ], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + def format_f(value, precision=1): if value % 1 == 0: return f"{value:.0f}" @@ -257,6 +286,7 @@ class Thumbnail: with_gps=False, group_key_template=None, file_key_template=None, + have_thumbnail=dict(), ): self.filename = filename self.jpegname = None @@ -285,45 +315,65 @@ class Thumbnail: except FileNotFoundError: pass - thumb_filename = filename.replace("/", "-") - self.thumbname = f".thumbnails/{thumb_filename}" - if not self.thumbname.lower().endswith((".jpeg", ".jpg")): - self.thumbname += ".jpg" - - im = rotate_image(im, self.exif_tag) - im.thumbnail((self.size * 4, self.size * 2)) - im = im.convert("RGB") - im.save(self.thumbname, "JPEG") - - if filename.lower().endswith((".cr2", ".cr3", ".rw2")): - try: - jpegname = f".thumbnails/{thumb_filename}.p.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, - ] - ) - self.jpegname = jpegname - except FileNotFoundError: - pass + have_subthumb = False + + if "/" in filename: + sub_dirname, sub_filename = filename.rsplit("/", 1) + sub_thumbname = sub_filename + if not sub_thumbname.lower().endswith((".jpeg", ".jpg")): + sub_thumbname += ".jpg" + if os.path.exists(f"{sub_dirname}/.thumbnails/{sub_thumbname}"): + self.thumbname = f"{sub_dirname}/.thumbnails/{sub_thumbname}" + have_subthumb = True + if os.path.exists(f"{sub_dirname}/.thumbnails/{sub_filename}.p.jpg"): + self.jpegname = f"{sub_dirname}/.thumbnails/{sub_filename}.p.jpg" + + if not have_subthumb: + thumb_filename = filename.replace("/", "-") + self.thumbname = f".thumbnails/{thumb_filename}" + if not self.thumbname.lower().endswith((".jpeg", ".jpg")): + self.thumbname += ".jpg" + + if not have_thumbnail.pop(self.thumbname, False): + if not filename.lower().endswith((".cr2", ".cr3", ".rw2")): + im = rotate_image(im, self.exif_tag) + + im.thumbnail((self.size * 4, self.size * 2)) + im = im.convert("RGB") + im.save(self.thumbname, "JPEG") + + if filename.lower().endswith((".cr2", ".cr3", ".rw2")): + try: + jpegname = f".thumbnails/{thumb_filename}.p.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, + ] + ) + self.jpegname = jpegname + 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)) @@ -518,10 +568,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 @@ -534,7 +586,9 @@ 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 @@ -618,18 +672,22 @@ def copy_files(base_dir): def write_gallery( - file_buf, filename, thumbnails, group=None, files=list(), this_file=None + file_buf, + filename, + thumbnails, + group=None, + this_file=None, + prev_file=None, + next_file=None, ): - if files: + if prev_file or next_file: nav_buf = '<div class="nav">' - for link_target in files: - link_class = "" - if link_target == this_file: - link_class = "this-file" - nav_buf += ( - f'<a class="{link_class}" href="{link_target}.html">{link_target}</a>\n' - ) + if prev_file: + nav_buf += f'<a href="{prev_file}-r.html">{prev_file}</a>\n' + nav_buf += f'<a class="this-file" href="{this_file}.html">{this_file}</a>\n' + if next_file: + nav_buf += f'<a href="{next_file}.html">{next_file}</a>\n' nav_buf += "</div>" file_buf += nav_buf @@ -640,7 +698,14 @@ def write_gallery( prev_heading = thumbnail.group_key file_buf += thumbnail.to_html(i, args.with_detail_page) - if files: + if prev_file or next_file: + nav_buf = '<div class="nav">' + if prev_file: + nav_buf += f'<a href="{prev_file}-r.html">{prev_file}</a>\n' + nav_buf += f'<a class="this-file" href="{this_file}.html">{this_file}</a>\n' + if next_file: + nav_buf += f'<a href="{next_file}.html">{next_file}</a>\n' + nav_buf += "</div>" file_buf += nav_buf with open(f"{base_dir}/share/html_end", "r") as f: @@ -700,6 +765,7 @@ 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", @@ -748,9 +814,17 @@ if __name__ == "__main__": os.makedirs(".thumbnails", exist_ok=True) + rm_thumbnail = dict( + map(lambda k: (".thumbnails/" + k, 1), os.listdir(".thumbnails")) + ) + 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) @@ -766,7 +840,12 @@ if __name__ == "__main__": filenames = args.images thumbnails = list() - for i, filename in enumerate(ProgressBar(max=len(filenames)).iter(filenames)): + 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: @@ -830,6 +909,7 @@ if __name__ == "__main__": with_gps=args.with_nominatim, group_key_template=args.group, file_key_template=args.group_files, + have_thumbnail=rm_thumbnail, ) thumbnails.append(thumbnail) @@ -877,6 +957,9 @@ if __name__ == "__main__": + [filename] ) + for rm_file in rm_thumbnail.keys(): + os.remove(rm_file) + if args.sort == "time": thumbnails = list( sorted(thumbnails, key=lambda t: t.exif_dt, reverse=args.reverse) @@ -886,6 +969,16 @@ if __name__ == "__main__": for thumbnail in thumbnails: with open(f"{base_dir}/share/html_detail_start", "r") as f: detail_html_start = f.read() + if args.cdn: + detail_html_start = detail_html_start.replace( + 'href=".data/css/', f'href="{args.cdn}/css/' + ) + detail_html_start = detail_html_start.replace( + 'src=".data/js/', f'src="{args.cdn}/js/' + ) + detail_html_start = detail_html_start.replace( + "path = '.data/css/'", f"path = '{args.cdn}/css/'" + ) with open(f"{base_dir}/share/html_detail_end", "r") as f: detail_html_end = f.read() for thumbnail in thumbnails: @@ -893,14 +986,32 @@ 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 thumbnail_key in thumbnail_keys: + for i, thumbnail_key in enumerate(thumbnail_keys): + prev_key = i > 0 and thumbnail_keys[i - 1] or None + next_key = i + 1 < len(thumbnail_keys) and thumbnail_keys[i + 1] or None write_gallery( html_buf, f"{thumbnail_key}.html", list(filter(lambda t: t.file_key == thumbnail_key, thumbnails)), group=args.group, - files=thumbnail_keys, this_file=thumbnail_key, + prev_file=prev_key, + next_file=next_key, + ) + write_gallery( + html_buf, + f"{thumbnail_key}-r.html", + reversed( + list(filter(lambda t: t.file_key == thumbnail_key, thumbnails)) + ), + group=args.group, + this_file=thumbnail_key, + prev_file=prev_key, + next_file=next_key, ) diff --git a/share/html_start b/share/html_start index 68be715..c44b353 100644 --- a/share/html_start +++ b/share/html_start @@ -16,21 +16,8 @@ old.href = path; } } - var otherTheme = { - 'dark': 'light', - 'light': 'dark', - }; - var currentTheme = localStorage.getItem('theme'); - if (!otherTheme.hasOwnProperty(currentTheme)) { - currentTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; - } + const currentTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; addStyleSheet(currentTheme, 'theme'); - - function toggleTheme() { - currentTheme = otherTheme[currentTheme] || 'light'; - localStorage.setItem('theme', currentTheme); - addStyleSheet(currentTheme, 'theme'); - } </script> <script src=".data/js/glightbox.min.js"></script> </head> |