#!/usr/bin/env python3 import os import argparse import urllib.parse import shutil from multiprocessing import Pool from string import Template from pathlib import Path import numpy as np from tqdm.auto import tqdm _ROOT = "/data/pictures/" _WEBROOT = "https://pictures.example.com/" _FOLDERICON = "https://www.svgrepo.com/show/400249/folder.svg" _ROOTTITLE = "Pictures" _FAVICON = "favicon.ico" imgext = [".jpg", ".jpeg"] rawext = [".3fr", ".ari", ".arw", ".bay", ".braw", ".crw", ".cr2", ".cr3", ".cap", ".data", ".dcs", ".dcr", ".dng", ".drf", ".eip", ".erf", ".fff", ".gpr", ".iiq", ".k25", ".kdc", ".mdc", ".mef", ".mos", ".mrw", ".nef", ".nrw", ".obm", ".orf", ".pef", ".ptx", ".pxn", ".r3d", ".raf", ".raw", ".rwl", ".rw2", ".rwz", ".sr2", ".srf", ".srw", ".tif", ".tiff", ".x3f"] excludes = [".lock", "favicon.ico", "index.html", "Galleries", ".previews", "Archives"] thumbnails: list[tuple[str, str]] = [] HTMLHEADER = """ $title """ def thumbnail_convert(arguments: tuple[str, str]): folder, item = arguments if not os.path.exists(os.path.join(args.root, ".previews", folder.removeprefix(args.root), os.path.splitext(item)[0]) + ".jpg") or args.regenerate: if shutil.which("magick"): os.system(f'magick "{os.path.join(folder, item)}" -quality 75% -define jpeg:size=1024x1024 -define jpeg:extent=100kb -thumbnail 512x512 -auto-orient "{os.path.join(args.root, ".previews", folder.removeprefix(args.root), os.path.splitext(item)[0])}.jpg"') else: os.system(f'convert "{os.path.join(folder, item)}" -quality 75% -define jpeg:size=1024x1024 -define jpeg:extent=100kb -thumbnail 512x512 -auto-orient "{os.path.join(args.root, ".previews", folder.removeprefix(args.root), os.path.splitext(item)[0])}.jpg"') def listfolder(folder: str, title: str): if not args.non_interactive: pbar.desc = f"Generating html files - {folder}" pbar.update(0) items: list[str] = os.listdir(folder) items.sort() images: list[str] = [] subfolders: list[str] = [] if not os.path.exists(os.path.join(args.root, ".previews", folder.removeprefix(args.root))): os.mkdir(os.path.join(args.root, ".previews", folder.removeprefix(args.root))) temp_obj = Template(HTMLHEADER) contains_files = False for item in items: if item not in excludes: if os.path.isdir(os.path.join(folder, item)): subfolders.extend([f'
Folder icon
{item}
']) listfolder(os.path.join(folder, item), item) else: if not args.non_interactive: pbar.desc = f"Generating html files - {folder}" pbar.update(0) contains_files = True if os.path.splitext(item)[1].lower() in imgext: image = f'
{item}
{item}' if not os.path.exists(os.path.join(args.root, ".previews", folder.removeprefix(args.root), item)): thumbnails.append((folder, item)) for raw in rawext: if os.path.exists(os.path.join(folder, os.path.splitext(item)[0] + raw)): if raw in (".tif", ".tiff"): image += f': TIFF' else: image += f': RAW' elif os.path.exists(os.path.join(folder, os.path.splitext(item)[0] + raw.upper())): if raw in (".tif", ".tiff"): image += f': TIFF' else: image += f': RAW' image += "
" images.extend([image]) if not args.non_interactive: pbar.desc = f"Generating html files - {folder}" pbar.update(0) if len(images) > 0 or (args.fancyfolders and not contains_files): with open(os.path.join(folder, "index.html"), "w", encoding="utf-8") as f: f.write(temp_obj.substitute(title=title, favicon=f"{args.root}/{_FAVICON}")) f.write('
\n') f.write(f"

{title}

\n") f.write('
\n') for subfolder in subfolders: f.write(subfolder) f.write("\n") f.write("
\n") f.write("
\n") if len(images) > 0: f.write('
\n') for chunk in np.array_split(images, 8): f.write('
\n') for image in chunk: f.write(f" {image}\n") f.write("
\n") f.write("
\n") f.write(" \n") f.close() else: if os.path.exists(os.path.join(folder, "index.html")): os.remove(os.path.join(folder, "index.html")) if not args.non_interactive: pbar.update(1) def gettotal(folder): global total if not args.non_interactive: pbar.desc = f"Traversing filesystem - {folder}" pbar.update(0) items: list[str] = os.listdir(folder) items.sort() for item in items: if item not in excludes: if os.path.isdir(os.path.join(folder, item)): total += 1 if not args.non_interactive: pbar.update(1) gettotal(os.path.join(folder, item)) def main(): global total global args global pbar total = 0 # Parse command-line arguments parser = argparse.ArgumentParser(description="Generate html files for static image host.") parser.add_argument("-p", "--root", help="Root folder", default=_ROOT, required=False, type=str, dest="root") parser.add_argument("-w", "--webroot", help="Webroot url", default=_WEBROOT, required=False, type=str, dest="webroot") parser.add_argument("-i", "--foldericon", help="Foldericon url", default=_FOLDERICON, required=False, type=str, dest="foldericon", metavar="ICON") parser.add_argument("-r", "--regenerate", help="Regenerate thumbnails", action="store_true", default=False, required=False, dest="regenerate") parser.add_argument("-n", "--non-interactive", help="Disable interactive mode", action="store_true", default=False, required=False, dest="non_interactive") parser.add_argument("--fancyfolders", help="Use fancy folders instead of default apache ones", action="store_true", default=False, required=False, dest="fancyfolders") args = parser.parse_args() if not args.root.endswith("/"): args.root += "/" if not args.webroot.endswith("/"): args.webroot += "/" if not os.path.exists(os.path.join(args.root, ".previews")): os.mkdir(os.path.join(args.root, ".previews")) if os.path.exists(os.path.join(args.root, ".lock")): print("Another instance of this program is running.") exit() try: Path(os.path.join(args.root, ".lock")).touch() if args.non_interactive: print("Generating html files...") listfolder(args.root, _ROOTTITLE) with Pool(os.cpu_count()) as p: print("Generating thumbnails...") p.map(thumbnail_convert, thumbnails) else: pbar = tqdm(desc="Traversing filesystem", unit=" folders", ascii=True, dynamic_ncols=True) gettotal(args.root) pbar.close() pbar = tqdm(total=total + 1, desc="Generating html files", unit=" files", ascii=True, dynamic_ncols=True) listfolder(args.root, _ROOTTITLE) pbar.close() with Pool(os.cpu_count()) as p: for r in tqdm(p.imap_unordered(thumbnail_convert, thumbnails), total=len(thumbnails), desc="Generating thumbnails", unit=" files", ascii=True, dynamic_ncols=True): pass finally: os.remove(os.path.join(args.root, ".lock")) if __name__ == "__main__": main()