mirror of
https://github.com/greflm13/StaticGalleryBuilder.git
synced 2026-02-05 11:09:26 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0973868782 | |||
| cd06c526af | |||
| d155b05798 | |||
| 3352894e4d | |||
| d743ede95d | |||
| 9403e84d78 | |||
| 961d79754e | |||
| 383dd59851 |
20
README.md
20
README.md
@@ -44,20 +44,20 @@ The script supports several command-line options to customize its behavior. Belo
|
||||
|
||||
### Options
|
||||
|
||||
- `-h, --help`: Show the help message and exit.
|
||||
- `-p ROOT, --root-directory ROOT`: Specify the root folder where the images are stored. This option is required.
|
||||
- `-w URL, --web-root-url URL`: Specify the base URL for the web root of the image hosting site. This option is required.
|
||||
- `-t TITLE, --site-title TITLE`: Specify the title of the image hosting site. This option is required.
|
||||
- `-r, --regenerate-thumbnails`: Regenerate thumbnails even if they already exist.
|
||||
- `-n, --non-interactive-mode`: Run in non-interactive mode, disabling progress bars.
|
||||
- `-l LICENSE, --license-type LICENSE`: Specify the license type for the images. Choices are `cc-zero`, `cc-by`, `cc-by-sa`, `cc-by-nd`, `cc-by-nc`, `cc-by-nc-sa`, and `cc-by-nc-nd`.
|
||||
- `-a AUTHOR, --author-name AUTHOR`: Specify the name of the author of the images. Default is "Author".
|
||||
- `-e EXTENSION, --file-extensions EXTENSION`: Specify the file extensions to include. This option can be specified multiple times.
|
||||
- `-l LICENSE, --license-type LICENSE`: Specify the license type for the images. Choices are `cc-zero`, `cc-by`, `cc-by-sa`, `cc-by-nd`, `cc-by-nc`, `cc-by-nc-sa`, and `cc-by-nc-nd`.
|
||||
- `-m, --web-manifest`: Generate a web manifest file.
|
||||
- `-n, --non-interactive-mode`: Run in non-interactive mode, disabling progress bars.
|
||||
- `-p ROOT, --root-directory ROOT`: Specify the root folder where the images are stored. **(This option is required)**.
|
||||
- `-t TITLE, --site-title TITLE`: Specify the title of the image hosting site. **(This option is required)**.
|
||||
- `-w URL, --web-root-url URL`: Specify the base URL for the web root of the image hosting site. **(This option is required)**.
|
||||
- `--exclude-folder FOLDER`: Specify folders to exclude from processing. This option can be specified multiple times.
|
||||
- `--ignore-other-files`: Ignore files that do not match the specified extensions.
|
||||
- `--regenerate-thumbnails`: Regenerate thumbnails even if they already exist.
|
||||
- `--reread-metadata`: Reread image metadata if it already exists.
|
||||
- `--theme-path PATH`: Specify the path to the CSS theme file. Default is the provided default theme.
|
||||
- `--use-fancy-folders`: Enable fancy folder view instead of the default Apache directory listing.
|
||||
- `--ignore-other-files`: Ignore files that do not match the specified extensions.
|
||||
- `--exclude-folder FOLDER`: Specify folders to exclude from processing. This option can be specified multiple times.
|
||||
- `-m, --web-manifest`: Generate a web manifest file.
|
||||
|
||||
### Examples
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
"cc-by-nc-sa",
|
||||
"-n",
|
||||
"-m",
|
||||
"-r"
|
||||
"--regenerate-thumbnails",
|
||||
"--reread-metadata",
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"name": "Testfolder",
|
||||
@@ -57,12 +58,13 @@
|
||||
"-t",
|
||||
"Pictures",
|
||||
"--theme",
|
||||
"themes/catpuccin.css",
|
||||
"themes/default.css",
|
||||
"--use-fancy-folders",
|
||||
"--web-manifest",
|
||||
"-n",
|
||||
"-m",
|
||||
"-r",
|
||||
// "--regenerate-thumbnails",
|
||||
// "--reread-metadata",
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"name": "woek",
|
||||
@@ -147,6 +149,9 @@
|
||||
"python.analysis.inlayHints.callArgumentNames": "off",
|
||||
"python.analysis.inlayHints.functionReturnTypes": false,
|
||||
"python.analysis.inlayHints.variableTypes": false,
|
||||
"yaml.schemas": {
|
||||
"https://raw.githubusercontent.com/pamburus/hl/master/schema/json/config.schema.json": "file:///home/user/git/github.com/greflm13/StaticGalleryBuilder/hl_config.yaml"
|
||||
},
|
||||
},
|
||||
"tasks": {
|
||||
"version": "2.0.0",
|
||||
@@ -218,6 +223,21 @@
|
||||
"clear": true
|
||||
},
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"command": "LESS=-SR hl logs/latest.jsonl --config hl_config.yaml",
|
||||
"isBackground": false,
|
||||
"label": "View Latest Log",
|
||||
"problemMatcher": [],
|
||||
"type": "shell",
|
||||
"presentation": {
|
||||
"echo": false,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "dedicated",
|
||||
"showReuseMessage": false,
|
||||
"clear": true
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
@@ -196,11 +196,13 @@ def main() -> None:
|
||||
lock_file = os.path.join(args.root_directory, ".lock")
|
||||
if os.path.exists(lock_file):
|
||||
print("Another instance of this program is running.")
|
||||
logger.info("nother instance of this program is running")
|
||||
logger.error("another instance of this program is running")
|
||||
exit()
|
||||
|
||||
try:
|
||||
Path(lock_file).touch()
|
||||
if args.reread_metadata:
|
||||
logger.warning("reread metadata flag is set to true, all image metadata will be reread")
|
||||
if args.regenerate_thumbnails:
|
||||
logger.warning("regenerate thumbnails flag is set to true, all thumbnails will be regenerated")
|
||||
if os.path.exists(os.path.join(args.root_directory, ".thumbnails")):
|
||||
@@ -212,7 +214,6 @@ def main() -> None:
|
||||
icons(args)
|
||||
|
||||
if args.generate_webmanifest:
|
||||
logger.info("generating webmanifest")
|
||||
print("Generating webmanifest...")
|
||||
webmanifest(args)
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ figure {
|
||||
.footer a img {
|
||||
height: 22px !important;
|
||||
margin-left: 3px;
|
||||
vertical-align: text-bottom
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
@@ -157,12 +157,12 @@ figure {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Create eight equal columns that sits next to each other */
|
||||
.column {
|
||||
-ms-flex: 12.5%;
|
||||
flex: 12.5%;
|
||||
max-width: 12.5%;
|
||||
padding: 0 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.column img {
|
||||
@@ -187,7 +187,6 @@ figure {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Responsive layout - makes a four column-layout instead of eight columns */
|
||||
@media screen and (max-width: 1000px) {
|
||||
.column {
|
||||
-ms-flex: 25%;
|
||||
@@ -209,7 +208,6 @@ figure {
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive layout - makes a two column-layout instead of four columns */
|
||||
@media screen and (max-width: 800px) {
|
||||
.column {
|
||||
-ms-flex: 50%;
|
||||
@@ -239,7 +237,6 @@ figure {
|
||||
}
|
||||
}
|
||||
|
||||
/* 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%;
|
||||
|
||||
@@ -272,13 +272,14 @@ a.pswp__share--download:hover {
|
||||
color: #BBB; }
|
||||
|
||||
.pswp__caption__center {
|
||||
text-align: left;
|
||||
text-align: center;
|
||||
max-width: 420px;
|
||||
margin: 0 auto;
|
||||
font-size: 13px;
|
||||
padding: 10px;
|
||||
line-height: 20px;
|
||||
color: #CCC; }
|
||||
color: #CCC;
|
||||
font-weight: bold; }
|
||||
|
||||
.pswp__caption--empty {
|
||||
display: none; }
|
||||
|
||||
73
hl_config.yaml
Normal file
73
hl_config.yaml
Normal file
@@ -0,0 +1,73 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/pamburus/hl/master/schema/json/config.schema.json
|
||||
$schema: https://raw.githubusercontent.com/pamburus/hl/master/schema/json/config.schema.json
|
||||
|
||||
# Time format, see https://man7.org/linux/man-pages/man1/date.1.html for details.
|
||||
time-format: "%b %d %T.%3N"
|
||||
|
||||
# Time zone name, see column "TZ identifier" at
|
||||
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones page.
|
||||
time-zone: "Europe/Vienna"
|
||||
|
||||
# Settings for fields processing.
|
||||
fields:
|
||||
# Configuration of the predefined set of fields.
|
||||
predefined:
|
||||
time:
|
||||
show: auto
|
||||
names: ["asctime"]
|
||||
logger:
|
||||
names: ["defaultlogger", "consolelogger"]
|
||||
level:
|
||||
show: auto
|
||||
variants:
|
||||
- names: ["levelname"]
|
||||
values:
|
||||
debug: ["DEBUG"]
|
||||
info: ["INFO"]
|
||||
warning: ["WARNING", "WARN"]
|
||||
error: ["ERROR", "FATAL", "CRITICAL"]
|
||||
- names: ["levelno"]
|
||||
values:
|
||||
debug: [10]
|
||||
info: [20]
|
||||
warning: [30]
|
||||
error: [40, 50]
|
||||
message:
|
||||
names: ["message"]
|
||||
caller:
|
||||
names: ["funcName"]
|
||||
caller-file:
|
||||
names: ["filename"]
|
||||
caller-line:
|
||||
names: ["lineno"]
|
||||
# List of wildcard field names to ignore.
|
||||
ignore: ["_*"]
|
||||
# List of exact field names to hide.
|
||||
hide: ["pathname", "created", "levelno", "taskname", "relativeCreated", "thread", "process", "msecs"]
|
||||
|
||||
# Formatting settings.
|
||||
formatting:
|
||||
flatten: always
|
||||
punctuation:
|
||||
logger-name-separator: ":"
|
||||
field-key-value-separator: "="
|
||||
string-opening-quote: "'"
|
||||
string-closing-quote: "'"
|
||||
source-location-separator: "→ "
|
||||
hidden-fields-indicator: " ..."
|
||||
level-left-separator: "│"
|
||||
level-right-separator: "│"
|
||||
input-number-prefix: "#"
|
||||
input-number-left-separator: ""
|
||||
input-number-right-separator: " │ "
|
||||
input-name-left-separator: ""
|
||||
input-name-right-separator: " │ "
|
||||
input-name-clipping: "··"
|
||||
input-name-common-part: "··"
|
||||
array-separator: " "
|
||||
|
||||
# Number of processing threads, configured automatically based on CPU count if not specified.
|
||||
concurrency: ~
|
||||
|
||||
# Currently selected theme.
|
||||
theme: "neutral"
|
||||
@@ -2,7 +2,14 @@ from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
import os
|
||||
import argparse
|
||||
from rich_argparse import RichHelpFormatter, HelpPreviewAction
|
||||
|
||||
try:
|
||||
from rich_argparse import RichHelpFormatter, HelpPreviewAction
|
||||
|
||||
RICH = True
|
||||
except ModuleNotFoundError:
|
||||
RICH = False
|
||||
|
||||
|
||||
from modules.logger import logger
|
||||
|
||||
@@ -58,6 +65,7 @@ class Args:
|
||||
license_type: Optional[str]
|
||||
non_interactive_mode: bool
|
||||
regenerate_thumbnails: bool
|
||||
reread_metadata: bool
|
||||
root_directory: str
|
||||
site_title: str
|
||||
theme_path: str
|
||||
@@ -75,6 +83,7 @@ class Args:
|
||||
result["license_type"] = self.license_type
|
||||
result["non_interactive_mode"] = self.non_interactive_mode
|
||||
result["regenerate_thumbnails"] = self.regenerate_thumbnails
|
||||
result["reread_metadata"] = self.reread_metadata
|
||||
result["root_directory"] = self.root_directory
|
||||
result["site_title"] = self.site_title
|
||||
result["theme_path"] = self.theme_path
|
||||
@@ -98,19 +107,24 @@ def parse_arguments(version: str) -> Args:
|
||||
An instance of the Args class containing the parsed arguments.
|
||||
"""
|
||||
# fmt: off
|
||||
if RICH:
|
||||
parser = argparse.ArgumentParser(description="Generate HTML files for a static image hosting website.", formatter_class=RichHelpFormatter)
|
||||
else:
|
||||
parser = argparse.ArgumentParser(description="Generate HTML files for a static image hosting website.")
|
||||
parser.add_argument("-a", "--author-name", help="Name of the author of the images.", default=DEFAULT_AUTHOR, type=str, dest="author_name", metavar="AUTHOR")
|
||||
parser.add_argument("-e", "--file-extensions", help="File extensions to include (can be specified multiple times).", action="append", dest="file_extensions", metavar="EXTENSION")
|
||||
parser.add_argument("-l", "--license-type", help="Specify the license type for the images.", choices=["cc-zero", "cc-by", "cc-by-sa", "cc-by-nd", "cc-by-nc", "cc-by-nc-sa", "cc-by-nc-nd"], default=None, dest="license_type", metavar="LICENSE")
|
||||
parser.add_argument("-m", "--web-manifest", help="Generate a web manifest file.", action="store_true", default=False, dest="generate_webmanifest")
|
||||
parser.add_argument("-n", "--non-interactive-mode", help="Run in non-interactive mode, disabling progress bars.", action="store_true", default=False, dest="non_interactive_mode")
|
||||
parser.add_argument("-p", "--root-directory", help="Root directory containing the images.", required=True, type=str, dest="root_directory", metavar="ROOT")
|
||||
parser.add_argument("-r", "--regenerate-thumbnails", help="Regenerate thumbnails even if they already exist.", action="store_true", default=False, dest="regenerate_thumbnails")
|
||||
parser.add_argument("-t", "--site-title", help="Title of the image hosting site.", required=True, type=str, dest="site_title", metavar="TITLE")
|
||||
parser.add_argument("-w", "--web-root-url", help="Base URL of the web root for the image hosting site.", required=True, type=str, dest="web_root_url", metavar="URL")
|
||||
parser.add_argument("--exclude-folder", help="Folders to exclude from processing, globs supported (can be specified multiple times).", action="append", dest="exclude_folders", metavar="FOLDER")
|
||||
if RICH:
|
||||
parser.add_argument("--generate-help-preview", action=HelpPreviewAction, path="help.svg", )
|
||||
parser.add_argument("--ignore-other-files", help="Ignore files that do not match the specified extensions.", action="store_true", default=False, dest="ignore_other_files")
|
||||
parser.add_argument("--regenerate-thumbnails", help="Regenerate thumbnails even if they already exist.", action="store_true", default=False, dest="regenerate_thumbnails")
|
||||
parser.add_argument("--reread-metadata", help="Reread image metadata", action="store_true", default=False, dest="reread_metadata")
|
||||
parser.add_argument("--theme-path", help="Path to the CSS theme file.", default=DEFAULT_THEME_PATH, type=str, dest="theme_path", metavar="PATH")
|
||||
parser.add_argument("--use-fancy-folders", help="Enable fancy folder view instead of the default Apache directory listing.", action="store_true", default=False, dest="use_fancy_folders")
|
||||
parser.add_argument("--version", action="version", version=f"%(prog)s {version}")
|
||||
@@ -125,6 +139,7 @@ def parse_arguments(version: str) -> Args:
|
||||
license_type=parsed_args.license_type,
|
||||
non_interactive_mode=parsed_args.non_interactive_mode,
|
||||
regenerate_thumbnails=parsed_args.regenerate_thumbnails,
|
||||
reread_metadata=parsed_args.reread_metadata,
|
||||
root_directory=parsed_args.root_directory,
|
||||
site_title=parsed_args.site_title,
|
||||
theme_path=parsed_args.theme_path,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import os
|
||||
import re
|
||||
import urllib.parse
|
||||
import fnmatch
|
||||
import json
|
||||
from typing import Any, Dict, List, Tuple
|
||||
from datetime import datetime
|
||||
|
||||
import numpy as np
|
||||
from tqdm.auto import tqdm
|
||||
@@ -89,11 +91,13 @@ def get_image_info(item: str, folder: str) -> Dict[str, Any]:
|
||||
Returns:
|
||||
Dict[str, Any]: A dictionary containing image width, height, and EXIF data.
|
||||
"""
|
||||
with Image.open(os.path.join(folder, item)) as img:
|
||||
logger.info("extracting image information", extra={"file": item})
|
||||
file = os.path.join(folder, item)
|
||||
with Image.open(file) as img:
|
||||
logger.info("extracting image information", extra={"file": file})
|
||||
exif = img.getexif()
|
||||
width, height = img.size
|
||||
if exif:
|
||||
logger.info("extracting EXIF data", extra={"file": file})
|
||||
ifd = exif.get_ifd(ExifTags.IFD.Exif)
|
||||
exifdatas = dict(exif.items()) | ifd
|
||||
exifdata = {}
|
||||
@@ -101,7 +105,7 @@ def get_image_info(item: str, folder: str) -> Dict[str, Any]:
|
||||
tag = ExifTags.TAGS.get(tag_id, tag_id)
|
||||
content = exifdatas.get(tag_id)
|
||||
if isinstance(content, bytes):
|
||||
content = content.hex(" ")
|
||||
content = "0x" + content.hex()
|
||||
if isinstance(content, TiffImagePlugin.IFDRational):
|
||||
content = content.limit_rational(1000000)
|
||||
if isinstance(content, tuple):
|
||||
@@ -111,15 +115,22 @@ def get_image_info(item: str, folder: str) -> Dict[str, Any]:
|
||||
newtuple = newtuple + (i.limit_rational(1000000),)
|
||||
if newtuple:
|
||||
content = newtuple
|
||||
if tag in ["DateTime", "DateTimeOriginal", "DateTimeDigitized"]:
|
||||
epr = r'\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2}'
|
||||
if re.match(epr, content):
|
||||
content = datetime.strptime(content, "%Y:%m:%d %H:%M:%S").strftime("%Y-%m-%d %H:%M:%S")
|
||||
else:
|
||||
content = None
|
||||
exifdata[tag] = content
|
||||
if "Orientation" in exifdata and exifdata["Orientation"] in [6, 8]:
|
||||
logger.info("image is rotated", extra={"file": file})
|
||||
width, height = height, width
|
||||
for key in ["PrintImageMatching", "UserComment", "MakerNote"]:
|
||||
if key in exifdata:
|
||||
del exifdata[key]
|
||||
return {"width": width, "height": height, "exifdata": exifdata}
|
||||
else:
|
||||
return {"width": width, "height": height}
|
||||
return {"width": width, "height": height, "exifdata": None}
|
||||
|
||||
|
||||
def process_image(item: str, folder: str, _args: Args, baseurl: str, sizelist: Dict[str, Dict[str, int]], raw: List[str]) -> Dict[str, Any]:
|
||||
@@ -138,7 +149,7 @@ def process_image(item: str, folder: str, _args: Args, baseurl: str, sizelist: D
|
||||
Dict[str, Any]: Dictionary containing image details for HTML rendering.
|
||||
"""
|
||||
extsplit = os.path.splitext(item)
|
||||
if item not in sizelist or _args.regenerate_thumbnails:
|
||||
if item not in sizelist or _args.reread_metadata:
|
||||
sizelist[item] = get_image_info(item, folder)
|
||||
|
||||
image = {
|
||||
@@ -147,6 +158,7 @@ def process_image(item: str, folder: str, _args: Args, baseurl: str, sizelist: D
|
||||
"name": item,
|
||||
"width": sizelist[item]["width"],
|
||||
"height": sizelist[item]["height"],
|
||||
"exifdata": sizelist[item]["exifdata"],
|
||||
}
|
||||
path = os.path.join(_args.root_directory, ".thumbnails", baseurl, item + ".jpg")
|
||||
if not os.path.exists(path) or _args.regenerate_thumbnails:
|
||||
@@ -155,12 +167,15 @@ def process_image(item: str, folder: str, _args: Args, baseurl: str, sizelist: D
|
||||
thumbnails.append((folder, item, _args.root_directory))
|
||||
|
||||
for _raw in raw:
|
||||
if os.path.exists(os.path.join(folder, extsplit[0] + _raw)):
|
||||
url = urllib.parse.quote(extsplit[0]) + _raw
|
||||
file = os.path.join(folder, extsplit[0] + _raw)
|
||||
if os.path.exists(file):
|
||||
url = f"{_args.web_root_url}{baseurl}{urllib.parse.quote(extsplit[0])}{_raw}"
|
||||
if _raw in (".tif", ".tiff"):
|
||||
image["tiff"] = f"{_args.web_root_url}{baseurl}{url}"
|
||||
logger.info("tiff file found", extra={"file": file})
|
||||
image["tiff"] = url
|
||||
else:
|
||||
image["raw"] = f"{_args.web_root_url}{baseurl}{url}"
|
||||
logger.info("raw file found", extra={"file": file, "extension": _raw})
|
||||
image["raw"] = url
|
||||
return image
|
||||
|
||||
|
||||
@@ -299,7 +314,6 @@ def create_html_file(folder: str, title: str, foldername: str, images: List[Dict
|
||||
"""
|
||||
html_file = os.path.join(folder, "index.html")
|
||||
logger.info("generating html file with jinja2", extra={"path": html_file})
|
||||
image_chunks = np.array_split(images, 8) if images else []
|
||||
header = os.path.basename(folder) or title
|
||||
parent = None if not foldername else f"{_args.web_root_url}{urllib.parse.quote(foldername.removesuffix(folder.split('/')[-1] + '/'))}"
|
||||
if parent and _args.web_root_url.startswith("file://"):
|
||||
@@ -331,7 +345,7 @@ def create_html_file(folder: str, title: str, foldername: str, images: List[Dict
|
||||
header=header,
|
||||
license=license_info,
|
||||
subdirectories=subfolders,
|
||||
images=image_chunks,
|
||||
images=images,
|
||||
info=_info,
|
||||
allimages=images,
|
||||
webmanifest=_args.generate_webmanifest,
|
||||
|
||||
@@ -48,14 +48,14 @@ def log_format(keys):
|
||||
|
||||
def rotate_log_file(compress=False):
|
||||
"""
|
||||
Renames the existing 'latest.jsonl' file to a timestamped file based on the first log entry's asctime.
|
||||
Optionally compresses the old log file using gzip.
|
||||
Truncates the 'latest.jsonl' file after optionally compressing its contents to a timestamped file.
|
||||
The 'latest.jsonl' file is not deleted or moved, just emptied.
|
||||
|
||||
Args:
|
||||
compress (bool): If True, compress the old log file using gzip.
|
||||
"""
|
||||
if os.path.exists(LATEST_LOG_FILE):
|
||||
with open(LATEST_LOG_FILE, "r", encoding="utf-8") as f:
|
||||
with open(LATEST_LOG_FILE, "r+", encoding="utf-8") as f:
|
||||
first_line = f.readline()
|
||||
try:
|
||||
first_log = json.loads(first_line)
|
||||
@@ -67,7 +67,10 @@ def rotate_log_file(compress=False):
|
||||
safe_timestamp = first_timestamp.replace(":", "-").replace(" ", "_")
|
||||
old_log_filename = os.path.join(LOG_DIR, f"{safe_timestamp}.jsonl")
|
||||
|
||||
os.rename(LATEST_LOG_FILE, old_log_filename)
|
||||
# Write contents to the new file
|
||||
with open(old_log_filename, "w", encoding="utf-8") as old_log_file:
|
||||
f.seek(0) # Go back to the beginning of the file
|
||||
shutil.copyfileobj(f, old_log_file)
|
||||
|
||||
if compress:
|
||||
with open(old_log_filename, "rb") as f_in:
|
||||
@@ -75,6 +78,10 @@ def rotate_log_file(compress=False):
|
||||
shutil.copyfileobj(f_in, f_out)
|
||||
os.remove(old_log_filename)
|
||||
|
||||
# Truncate the original file
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
|
||||
|
||||
def setup_logger(level=logging.INFO):
|
||||
"""
|
||||
|
||||
@@ -178,8 +178,8 @@ def create_icons_from_svg(files: List[str], iconspath: str, _args: Args) -> List
|
||||
List[Icon]
|
||||
List of icons created from the SVG file.
|
||||
"""
|
||||
logger.info("creating icons for web application", extra={"iconspath": iconspath})
|
||||
svg = [file for file in files if file.endswith(".svg")][0]
|
||||
logger.info("creating icons for web application", extra={"iconspath": iconspath, "svg": svg})
|
||||
icon_list = [
|
||||
{"src": f"{_args.web_root_url}.static/icons/{svg}", "type": "image/svg+xml", "sizes": "512x512", "purpose": "maskable"},
|
||||
{"src": f"{_args.web_root_url}.static/icons/{svg}", "type": "image/svg+xml", "sizes": "512x512", "purpose": "any"},
|
||||
@@ -188,7 +188,7 @@ def create_icons_from_svg(files: List[str], iconspath: str, _args: Args) -> List
|
||||
tmpimg = BytesIO()
|
||||
sizes = size.split("x")
|
||||
iconpath = os.path.join(iconspath, os.path.splitext(svg)[0] + "-" + size + ".png")
|
||||
logger.info("converting svg to png", extra={"iconpath": iconpath, "size": size})
|
||||
logger.info("converting svg to png", extra={"svg": svg, "size": size})
|
||||
cairosvg.svg2png(
|
||||
url=os.path.join(iconspath, svg),
|
||||
write_to=tmpimg,
|
||||
@@ -240,7 +240,7 @@ def create_icons_from_png(iconspath: str, web_root_url: str) -> List[Icon]:
|
||||
continue
|
||||
with Image.open(os.path.join(iconspath, icon)) as iconfile:
|
||||
iconsize = f"{iconfile.size[0]}x{iconfile.size[1]}"
|
||||
logger.info("using icon", extra={"icon": icon, "size": iconsize})
|
||||
logger.info("using icon", extra={"iconspath": iconspath, "icon": icon, "size": iconsize})
|
||||
icon_list.append({"src": f"{web_root_url}.static/icons/{icon}", "sizes": iconsize, "type": "image/png", "purpose": "maskable"})
|
||||
icon_list.append({"src": f"{web_root_url}.static/icons/{icon}", "sizes": iconsize, "type": "image/png", "purpose": "any"})
|
||||
return icon_list
|
||||
@@ -255,6 +255,8 @@ def webmanifest(_args: Args) -> None:
|
||||
_args : Args
|
||||
Parsed command-line arguments.
|
||||
"""
|
||||
logger.info("generating webmanifest")
|
||||
|
||||
iconspath = os.path.join(_args.root_directory, ".static", "icons")
|
||||
files = os.listdir(iconspath)
|
||||
icon_list = create_icons_from_svg(files, iconspath, _args) if SVGSUPPORT and any(file.endswith(".svg") for file in files) else create_icons_from_png(iconspath, _args.web_root_url)
|
||||
|
||||
@@ -56,9 +56,8 @@
|
||||
{% if images %}
|
||||
{%- set ns = namespace(count = 0) -%}
|
||||
<div class="row">
|
||||
{%- for imageblock in images %}
|
||||
{%- for image in images %}
|
||||
<div class="column">
|
||||
{%- for image in imageblock %}
|
||||
<figure>
|
||||
<img src="{{ image.thumbnail }}" alt="{{ image.name }}" onclick="openSwipe({{ ns.count }})" />
|
||||
{%- set ns.count = ns.count + 1 %}
|
||||
@@ -71,7 +70,6 @@
|
||||
{%- endif %}
|
||||
</figcaption>
|
||||
</figure>
|
||||
{%- endfor %}
|
||||
</div>
|
||||
{%- endfor %}
|
||||
</div>
|
||||
@@ -147,7 +145,11 @@
|
||||
var pswpElement = document.querySelectorAll('.pswp')[0];
|
||||
var items = [
|
||||
{%- for image in allimages %}
|
||||
{%- if image.exifdata.DateTime %}
|
||||
{ src: "{{ image.url }}", w: {{ image.width }}, h: {{ image.height }}, msrc: "{{ image.thumbnail }}", title: "Captured: {{ image.exifdata.DateTime }}" },
|
||||
{%- else %}
|
||||
{ src: "{{ image.url }}", w: {{ image.width }}, h: {{ image.height }}, msrc: "{{ image.thumbnail }}" },
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
];
|
||||
var re = /pid=(\d+)/;
|
||||
@@ -178,8 +180,7 @@
|
||||
}
|
||||
|
||||
function topFunction() {
|
||||
document.body.scrollTop = 0;
|
||||
document.documentElement.scrollTop = 0;
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
</script>
|
||||
{%- endif %}
|
||||
|
||||
BIN
test/example/DSC03508.ARW
Executable file
BIN
test/example/DSC03508.ARW
Executable file
Binary file not shown.
4
view-latest-log.sh
Executable file
4
view-latest-log.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
LESS=-SR hl logs/latest.jsonl --config hl_config.yaml
|
||||
|
||||
3
view-logs.sh
Executable file
3
view-logs.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
LESS=-SR hl $(ls -tr logs/*.{jsonl,jsonl.gz}) --config hl_config.yaml
|
||||
Reference in New Issue
Block a user