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'
{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}'
+ 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 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'
{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}'
- 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 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 images %}
+
+ {%- for imageblock in images %}
+
+ {%- for image in imageblock %}
+
+
+ {{ image.name }}{% if image.tiff %}
+ TIFF {% endif %}{% if image.raw %}
+ RAW {% endif %}
+
+
+ {%- endfor %}
+
+ {%- endfor %}
+
+ {%- endif %}
+ {% if license %}
+ {%- if 'CC' in license.type %}
+
+ {%- endif %}
+ {%- endif %}
+
+
+
\ No newline at end of file