diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/README.md b/README.md index dbda13c..0aefc02 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,15 @@ - Python 3.x - `numpy` library - `tqdm` library +- `Jinja2` library +- `ImageMagick` ## Installation Install the required libraries using pip: ```sh -pip install numpy tqdm +pip install numpy tqdm Jinja2 ``` ## Usage @@ -71,7 +73,7 @@ To include a license, author, and custom title: - The root and webroot paths must point to the same folder, one on the filesystem and one on the webserver. Use absolute paths. - Ensure that ImageMagick is installed and accessible in your system for thumbnail generation. -- The script generates the preview thumbnails in a `.previews` subdirectory within the root folder. +- The script generates the preview thumbnails in a `.thumbnails` subdirectory within the root folder. ## License diff --git a/cclicense.py b/cclicense.py index 9aeb0f5..aaa453a 100644 --- a/cclicense.py +++ b/cclicense.py @@ -1,4 +1,4 @@ -def licenseswitch(cclicense: str): +def licenseswitch(cclicense: str) -> str: switch = { "cc-zero": """
@@ -124,7 +124,7 @@ def licenseswitch(cclicense: str): return switch.get(cclicense, "") -def licenseurlswitch(cclicense: str): +def licenseurlswitch(cclicense: str) -> str: switch = { "cc-zero": "https://creativecommons.org/publicdomain/zero/1.0/", "cc-by": "https://creativecommons.org/licenses/by/4.0/", @@ -136,3 +136,58 @@ def licenseurlswitch(cclicense: str): } return switch.get(cclicense, "") + + +def licensenameswitch(cclicense: str) -> str: + switch = { + "cc-zero": "CC0 1.0", + "cc-by": "CC BY 4.0", + "cc-by-sa": "CC BY-SA 4.0", + "cc-by-nd": "CC BY-ND 4.0", + "cc-by-nc": "CC BY-NC 4.0", + "cc-by-nc-sa": "CC BY-NC-SA 4.0", + "cc-by-nc-nd": "CC BY-NC-ND 4.0", + } + + return switch.get(cclicense, "") + + +def licensepicswitch(cclicense: str) -> list[str]: + switch = { + "cc-zero": [ + "https://mirrors.creativecommons.org/presskit/icons/cc.svg", + "https://mirrors.creativecommons.org/presskit/icons/zero.svg", + ], + "cc-by": [ + "https://mirrors.creativecommons.org/presskit/icons/cc.svg", + "https://mirrors.creativecommons.org/presskit/icons/by.svg", + ], + "cc-by-sa": [ + "https://mirrors.creativecommons.org/presskit/icons/cc.svg", + "https://mirrors.creativecommons.org/presskit/icons/by.svg", + "https://mirrors.creativecommons.org/presskit/icons/sa.svg", + ], + "cc-by-nd": [ + "https://mirrors.creativecommons.org/presskit/icons/cc.svg", + "https://mirrors.creativecommons.org/presskit/icons/by.svg", + "https://mirrors.creativecommons.org/presskit/icons/nd.svg", + ], + "cc-by-nc": [ + "https://mirrors.creativecommons.org/presskit/icons/cc.svg", + "https://mirrors.creativecommons.org/presskit/icons/by.svg", + "https://mirrors.creativecommons.org/presskit/icons/nc.svg", + ], + "cc-by-nc-sa": [ + "https://mirrors.creativecommons.org/presskit/icons/cc.svg", + "https://mirrors.creativecommons.org/presskit/icons/by.svg", + "https://mirrors.creativecommons.org/presskit/icons/nc.svg", + "https://mirrors.creativecommons.org/presskit/icons/sa.svg", + ], + "cc-by-nc-nd": [ + "https://mirrors.creativecommons.org/presskit/icons/cc.svg", + "https://mirrors.creativecommons.org/presskit/icons/by.svg", + "https://mirrors.creativecommons.org/presskit/icons/nd.svg", + ], + } + + return switch.get(cclicense, "") diff --git a/files/favicon.ico b/files/favicon.ico new file mode 100644 index 0000000..18818b9 Binary files /dev/null and b/files/favicon.ico differ diff --git a/files/global.css b/files/global.css new file mode 100644 index 0000000..9f1300a --- /dev/null +++ b/files/global.css @@ -0,0 +1,180 @@ +* { + box-sizing: border-box; +} + +body { + margin: 0; + margin-top: 32px; + margin-bottom: 56px; + font-family: Arial; +} + +.folders { + text-align: center; + display: -ms-flexbox; + /* IE10 */ + display: flex; + -ms-flex-wrap: wrap; + /* IE10 */ + flex-wrap: wrap; + justify-content: space-evenly; + overflow: hidden; +} + +.folders figure { + margin-bottom: 32px; + margin-top: 50px; +} + +.header h1 { + font-size: 2.5em; + font-weight: bold; + text-align: center; +} + +.folders img { + width: 100px; + vertical-align: middle; +} + +.folders figcaption { + width: 120px; + font-size: smaller; + text-align: center; +} + +.row { + display: -ms-flexbox; + /* IE10 */ + display: flex; + -ms-flex-wrap: wrap; + /* IE10 */ + flex-wrap: wrap; + padding: 0 2px; +} + +figure { + margin: 0; +} + +/* Create four equal columns that sits next to each other */ +.column { + -ms-flex: 12.5%; + /* IE10 */ + flex: 12.5%; + max-width: 12.5%; + padding: 0 4px; +} + +.column img { + margin-top: 20px; + vertical-align: middle; + width: 100%; +} + +/* Responsive layout - makes a four column-layout instead of eight columns */ +@media screen and (max-width: 1000px) { + .column { + -ms-flex: 25%; + flex: 25%; + max-width: 25%; + } + + .folders img { + width: 80px; + } + + .folders figcaption { + width: 100px; + font-size: small; + } +} + +/* Responsive layout - makes a two column-layout instead of four columns */ +@media screen and (max-width: 800px) { + .column { + -ms-flex: 50%; + flex: 50%; + max-width: 50%; + } + + .folders img { + width: 60px; + } + + .folders figcaption { + width: 80px; + font-size: x-small; + } +} + +/* Responsive layout - makes the two columns stack on top of each other instead of next to each other */ +@media screen and (max-width: 600px) { + .column { + -ms-flex: 100%; + flex: 100%; + max-width: 100%; + } + + .folders img { + width: 40px; + } + + .folders figcaption { + width: 60px; + font-size: xx-small; + } +} + +.caption { + padding-top: 4px; + text-align: center; + font-style: italic; + font-size: 12px; + width: 100%; + display: block; +} + +.license { + position: fixed; + bottom: 0; + width: 100%; + background-color: lightgrey; + padding: 12px; +} + +.navbar { + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + position: fixed; + top: 0; + width: 100%; + background-color: #333; +} + +.navbar li { + float: left; +} + +.navbar li a { + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} + +.navbar li span { + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} + +/* Change the link color to #111 (black) on hover */ +.navbar li a:hover { + background-color: #111; +} \ No newline at end of file diff --git a/generate_html.old.py b/generate_html.old.py new file mode 100755 index 0000000..30a3a1d --- /dev/null +++ b/generate_html.old.py @@ -0,0 +1,392 @@ +#!/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 + +import cclicense + +environment = Environment(loader=FileSystemLoader("templates/")) + +_ROOT = "/data/pictures/" +_WEBROOT = "https://pictures.example.com/" +_FOLDERICON = "https://www.svgrepo.com/show/400249/folder.svg" +_ROOTTITLE = "Pictures" +_FAVICON = "favicon.ico" +_AUTHOR = "Author" +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, "index.html", ".previews"] +notlist = ["Galleries", "Archives"] + +thumbnails: list[tuple[str, str]] = [] + +HTMLHEADER = """ + + + + + + $title + + + + +""" + +NAVBAR = """ + +""" + + +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))) + + body = Template(HTMLHEADER) + navbar = Template(NAVBAR) + 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}
']) + if item not in notlist: + listfolder(os.path.join(folder, item), os.path.join(folder, item).removeprefix(args.root)) + 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(body.substitute(title=title, favicon=f"{args.webroot}{_FAVICON}")) + f.write('
\n') + if folder == args.root: + f.write(f"

{os.path.basename(folder)}

\n") + else: + if args.license: + f.write(navbar.substitute(home=args.webroot, parent=f"{args.webroot}{urllib.parse.quote(folder.removeprefix(args.root).removesuffix(folder.split('/')[-1]))}", title=os.path.basename(folder), license=f'
  • License
  • \n')) + else: + f.write(navbar.substitute(home=args.webroot, parent=f"{args.webroot}{urllib.parse.quote(folder.removeprefix(args.root).removesuffix(folder.split('/')[-1]))}", title=os.path.basename(folder), license="")) + 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") + if args.license: + f.write(_cclicense.substitute(webroot=args.webroot, title=args.title, author=args.author)) + 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) + if item not in notlist: + gettotal(os.path.join(folder, item)) + + +def main(): + global total + global args + global pbar + global _cclicense + + 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("-l", "--license", help="License", default=None, required=False, choices=["cc-zero", "cc-by", "cc-by-sa", "cc-by-nd", "cc-by-nc", "cc-by-nc-sa", "cc-by-nc-nd"], dest="license") + parser.add_argument("-a", "--author", help="Author", default=_AUTHOR, required=False, type=str, dest="author") + parser.add_argument("-t", "--title", help="Title", default=_ROOTTITLE, required=False, type=str, dest="title") + 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 args.license: + _cclicense = Template(cclicense.licenseswitch(args.license)) + + 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, args.title) + + 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, args.title) + 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() diff --git a/generate_html.py b/generate_html.py index bfa0264..da5f94c 100755 --- a/generate_html.py +++ b/generate_html.py @@ -4,9 +4,9 @@ import argparse import urllib.parse import shutil from multiprocessing import Pool -from string import Template from pathlib import Path import numpy as np +from jinja2 import Environment, FileSystemLoader from tqdm.auto import tqdm import cclicense @@ -17,219 +17,32 @@ _ROOT = "/data/pictures/" _WEBROOT = "https://pictures.example.com/" _FOLDERICON = "https://www.svgrepo.com/show/400249/folder.svg" _ROOTTITLE = "Pictures" -_FAVICON = "favicon.ico" +_STATICFILES = os.path.join(os.path.abspath(os.path.dirname(__file__)), "files") +_FAVICON = ".static/favicon.ico" +_STYLE = ".static/global.css" _AUTHOR = "Author" -imgext = [".jpg", ".jpeg"] +# fmt: off 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, - "index.html", - ".previews", -] +imgext = [".jpg", ".jpeg"] +excludes = [".lock", "index.html", ".thumbnails", ".static"] notlist = ["Galleries", "Archives"] +# fmt: on thumbnails: list[tuple[str, str]] = [] -HTMLHEADER = """ - - - - - - $title - - - - -""" - -NAVBAR = """ - -""" - 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: + path = os.path.join(args.root, ".thumbnails", folder.removeprefix(args.root), os.path.splitext(item)[0]) + ".jpg" + if not os.path.exists(path) 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"') + os.system( + f'magick "{os.path.join(folder, item)}" -quality 75% -define jpeg:size=1024x1024 -define jpeg:extent=100kb -thumbnail 512x512 -auto-orient "{path}"' + ) 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"') + os.system( + f'convert "{os.path.join(folder, item)}" -quality 75% -define jpeg:size=1024x1024 -define jpeg:extent=100kb -thumbnail 512x512 -auto-orient "{path}"' + ) def listfolder(folder: str, title: str): @@ -238,74 +51,86 @@ def listfolder(folder: str, title: str): pbar.update(0) items: list[str] = os.listdir(folder) items.sort() - images: list[str] = [] - subfolders: list[str] = [] + images: list[dict] = [] + subfolders: list[dict] = [] - 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))) + foldername = folder.removeprefix(args.root) + + if not os.path.exists(os.path.join(args.root, ".thumbnails", foldername)): + os.mkdir(os.path.join(args.root, ".thumbnails", foldername)) - body = Template(HTMLHEADER) - navbar = Template(NAVBAR) 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}
    ']) + subfolder = {"url": f"{args.webroot}{urllib.parse.quote(foldername)}/{urllib.parse.quote(item)}", "name": item} + subfolders.extend([subfolder]) if item not in notlist: listfolder(os.path.join(folder, item), os.path.join(folder, item).removeprefix(args.root)) else: + baseurl = urllib.parse.quote(foldername) + "/" + extsplit = os.path.splitext(item) 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)): + if extsplit[1].lower() in imgext: + image = { + "url": f"{args.webroot}{baseurl}{urllib.parse.quote(item)}", + "thumbnail": f"{args.webroot}.thumbnails/{baseurl}{urllib.parse.quote(extsplit[0])}.jpg", + "name": item, + } + if not os.path.exists(os.path.join(args.root, ".thumbnails", foldername, item)): thumbnails.append((folder, item)) for raw in rawext: - if os.path.exists(os.path.join(folder, os.path.splitext(item)[0] + raw)): + if os.path.exists(os.path.join(folder, extsplit[0] + raw)): + url = urllib.parse.quote(extsplit[0]) + raw if raw in (".tif", ".tiff"): - image += f': TIFF' + image["tiff"] = f"{args.webroot}{baseurl}{url}" 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 += "
    " + image["raw"] = f"{args.webroot}{baseurl}{url}" 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): + imagechunks = [] + if len(images) > 0: + for chunk in np.array_split(images, 8): + imagechunks.append(chunk) with open(os.path.join(folder, "index.html"), "w", encoding="utf-8") as f: - f.write(body.substitute(title=title, favicon=f"{args.webroot}{_FAVICON}")) - f.write('
    \n') - if folder == args.root: - f.write(f"

    {os.path.basename(folder)}

    \n") + header = os.path.basename(folder) + if header == "": + header = title + if foldername == "": + parent = None else: - if args.license: - f.write(navbar.substitute(home=args.webroot, parent=f"{args.webroot}{urllib.parse.quote(folder.removeprefix(args.root).removesuffix(folder.split('/')[-1]))}", title=os.path.basename(folder), license=f'
  • License
  • \n')) - else: - f.write(navbar.substitute(home=args.webroot, parent=f"{args.webroot}{urllib.parse.quote(folder.removeprefix(args.root).removesuffix(folder.split('/')[-1]))}", title=os.path.basename(folder), license="")) - 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") + parent = f"{args.webroot}{urllib.parse.quote(foldername.removesuffix(folder.split('/')[-1]))}" if args.license: - f.write(_cclicense.substitute(webroot=args.webroot, title=args.title, author=args.author)) - f.write(" \n") + _license = { + "project": args.title, + "author": args.author, + "type": cclicense.licensenameswitch(args.license), + "url": cclicense.licenseurlswitch(args.license), + "pics": cclicense.licensepicswitch(args.license), + } + else: + _license = None + + html = environment.get_template("index.html.j2") + content = html.render( + title=title, + favicon=f"{args.webroot}{_FAVICON}", + stylesheet=f"{args.webroot}{_STYLE}", + theme=None, + root=args.webroot, + parent=parent, + header=header, + license=_license, + subdirectories=subfolders, + images=imagechunks, + ) + f.write(content) f.close() else: if os.path.exists(os.path.join(folder, "index.html")): @@ -335,12 +160,14 @@ def gettotal(folder): def main(): + global rawext global total global args global pbar global _cclicense total = 0 + # fmt: off # 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") @@ -353,16 +180,19 @@ def main(): parser.add_argument("-t", "--title", help="Title", default=_ROOTTITLE, required=False, type=str, dest="title") 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() + # fmt: on 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 args.license: - _cclicense = Template(cclicense.licenseswitch(args.license)) + if not os.path.exists(os.path.join(args.root, ".thumbnails")): + os.mkdir(os.path.join(args.root, ".thumbnails")) + tmprawext = [] + for raw in rawext: + tmprawext.append(raw) + tmprawext.append(raw.upper()) + rawext = tmprawext if os.path.exists(os.path.join(args.root, ".lock")): print("Another instance of this program is running.") @@ -370,6 +200,9 @@ def main(): try: Path(os.path.join(args.root, ".lock")).touch() + print("Copying static files...") + shutil.copytree(_STATICFILES, os.path.join(args.root, ".static"), dirs_exist_ok=True) + if args.non_interactive: print("Generating html files...") listfolder(args.root, args.title) @@ -387,7 +220,14 @@ def main(): 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): + 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")) diff --git a/simple-picture-server.code-workspace b/simple-picture-server.code-workspace new file mode 100644 index 0000000..4823704 --- /dev/null +++ b/simple-picture-server.code-workspace @@ -0,0 +1,62 @@ +{ + "folders": [ + { + "path": "./", + "name": "Simple Picture Server" + } + ], + "settings": { + "files.associations": { + "**/*.html.j2": "jinja-html", + "**/*.css.j2": "jinja-css", + "**/*.css": "css", + }, + "python.analysis.inlayHints.callArgumentNames": "off", + "python.analysis.inlayHints.functionReturnTypes": false, + "python.analysis.inlayHints.variableTypes": false, + "pylint.args": [ + "--disable=C0111", + "--disable=C0301", + "--good-names-rgxs=^[_a-z][_a-z0-9]?$" + ], + "editor.formatOnSave": false, + "black-formatter.interpreter": [ + "/usr/bin/python3" + ], + "black-formatter.args": [ + "-l 140" + ], + "gitblame.inlineMessageEnabled": true, + "gitblame.inlineMessageFormat": "${author.name}, ${time.ago} • ${commit.summary}", + "gitblame.statusBarMessageFormat": "${author.name} (${time.ago})", + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "[css]": { + "editor.defaultFormatter": "vscode.css-language-features" + }, + "[jinja-html]": { + "editor.defaultFormatter": "vscode.html-language-features" + }, + "[jinja-css]": { + "editor.defaultFormatter": "vscode.css-language-features" + }, + "html.format.templating": true, + "html.format.wrapAttributes": "preserve", + "html.format.wrapLineLength": 200, + "html.format.indentHandlebars": true + }, + "extensions": { + "recommendations": [ + "ms-python.black-formatter", + "ms-python.python", + "ms-python.vscode-pylance", + "ms-python.debugpy", + "ms-python.pylint", + "samuelcolvin.jinjahtml", + "vscode.html-language-features", + "vscode.css-language-features", + "waderyan.gitblame", + ] + } +} \ No newline at end of file diff --git a/templates/index.html.j2 b/templates/index.html.j2 new file mode 100644 index 0000000..be69249 --- /dev/null +++ b/templates/index.html.j2 @@ -0,0 +1,78 @@ + + + + + + + {{ title }} + + + {%- if theme %} + + {%- endif %} + + + +
    + + {% if subdirectories %} +
    + {%- for subdirectory in subdirectories %} +
    + Folder icon +
    {{ subdirectory.name }}
    +
    + {%- endfor %} +
    + {%- endif %} +
    + {% if images %} +
    + {%- for imageblock in images %} +
    + {%- for image in imageblock %} +
    + {{ image.name }} +
    {{ image.name }}{% if image.tiff %} + TIFF{% endif %}{% if image.raw %} + RAW{% endif %} +
    +
    + {%- endfor %} +
    + {%- endfor %} +
    + {%- endif %} + {% if license %} + {%- if 'CC' in license.type %} +
    + {%- if license.type == 'CC0 1.0' %} + {{ license.project }} by {{ license.author }} is marked with + CC0 1.0 + {%- for pic in license.pics %} + + {%- endfor %} + + {%- else %} + {{ license.project }} by {{ license.author }} is licensed under + {{ license.type }} + {%- for pic in license.pics %} + + {%- endfor %} + + {%- endif %} +
    + {%- endif %} + {%- endif %} + + + \ No newline at end of file