14 Commits

17 changed files with 375 additions and 146 deletions

View File

@@ -12,7 +12,7 @@ jobs:
- name: Install Dependencies
run: pip install -r requirements.txt
- name: Build Package
run: pyinstaller builder.py modules/*.py -n StaticGalleryBuilder -F --add-data files:files --add-data templates:templates --add-data .version:.
run: pyinstaller builder.py modules/*.py -n StaticGalleryBuilder-$(cat .version)-linux -F --add-data files:files --add-data templates:templates --add-data .version:.
- name: Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
@@ -20,4 +20,4 @@ jobs:
make_latest: true
generate_release_notes: true
files: |
dist/StaticGalleryBuilder
dist/StaticGalleryBuilder*

View File

@@ -1 +1 @@
2.3.2
2.4.0

View File

@@ -26,13 +26,14 @@ Python script to generate static HTML files for website galleries.
- `Pillow` library
- `rich_argparse` library
- `cairosvg` library (for SVG to PNG icon conversion)
- `python-json-logger` library (for logging)
## Installation
Install the required libraries using pip:
```sh
pip install numpy tqdm Jinja2 Pillow rich-argparse cairosvg
pip install numpy tqdm Jinja2 Pillow rich-argparse cairosvg python-json-logger
```
## Usage
@@ -43,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

View File

@@ -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",
@@ -157,6 +162,14 @@
"label": "Delete Lockfile",
"problemMatcher": [],
"type": "shell",
"presentation": {
"echo": false,
"reveal": "never",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": true
}
},
{
"command": "rm -f /home/user/woek/Pictures/.lock",
@@ -164,7 +177,68 @@
"label": "Delete Lockfile 2",
"problemMatcher": [],
"type": "shell",
"presentation": {
"echo": false,
"reveal": "never",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": true
}
},
{
"command": "pyinstaller builder.py modules/*.py -n StaticGalleryBuilder-$(cat .version)-linux -F --add-data files:files --add-data templates:templates --add-data .version:.",
"isBackground": false,
"label": "Build",
"problemMatcher": [],
"type": "shell",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": false
},
"group": {
"kind": "build",
"isDefault": true
},
"dependsOn": [
"Clean"
]
},
{
"command": "rm -rf build dist",
"isBackground": true,
"label": "Clean",
"problemMatcher": [],
"type": "shell",
"presentation": {
"echo": true,
"reveal": "never",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"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
}
}
],
},
}

View File

@@ -34,7 +34,6 @@ RAW_EXTENSIONS = [
]
IMG_EXTENSIONS = [".jpg", ".jpeg", ".png"]
NOT_LIST = ["*/Galleries/*", "Archives"]
LOG_FILE = os.path.join(SCRIPTDIR, "log.json")
# fmt: on
pbardict: Dict[str, tqdm] = {}
@@ -197,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")):
@@ -213,7 +214,6 @@ def main() -> None:
icons(args)
if args.generate_webmanifest:
logger.info("generating webmanifest")
print("Generating webmanifest...")
webmanifest(args)

View File

@@ -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%;

View File

@@ -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; }

142
help.svg
View File

@@ -19,118 +19,118 @@
font-weight: 700;
}
.terminal-3756623299-matrix {
.terminal-2271905219-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
.terminal-3756623299-title {
.terminal-2271905219-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
.terminal-3756623299-r1 { fill: #ff8700 }
.terminal-3756623299-r2 { fill: #c5c8c6 }
.terminal-3756623299-r3 { fill: #808080 }
.terminal-3756623299-r4 { fill: #68a0b3 }
.terminal-3756623299-r5 { fill: #00af87 }
.terminal-2271905219-r1 { fill: #ff8700 }
.terminal-2271905219-r2 { fill: #c5c8c6 }
.terminal-2271905219-r3 { fill: #808080 }
.terminal-2271905219-r4 { fill: #68a0b3 }
.terminal-2271905219-r5 { fill: #00af87 }
</style>
<defs>
<clipPath id="terminal-3756623299-clip-terminal">
<clipPath id="terminal-2271905219-clip-terminal">
<rect x="0" y="0" width="1463.0" height="755.4" />
</clipPath>
<clipPath id="terminal-3756623299-line-0">
<clipPath id="terminal-2271905219-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-1">
<clipPath id="terminal-2271905219-line-1">
<rect x="0" y="25.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-2">
<clipPath id="terminal-2271905219-line-2">
<rect x="0" y="50.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-3">
<clipPath id="terminal-2271905219-line-3">
<rect x="0" y="74.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-4">
<clipPath id="terminal-2271905219-line-4">
<rect x="0" y="99.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-5">
<clipPath id="terminal-2271905219-line-5">
<rect x="0" y="123.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-6">
<clipPath id="terminal-2271905219-line-6">
<rect x="0" y="147.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-7">
<clipPath id="terminal-2271905219-line-7">
<rect x="0" y="172.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-8">
<clipPath id="terminal-2271905219-line-8">
<rect x="0" y="196.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-9">
<clipPath id="terminal-2271905219-line-9">
<rect x="0" y="221.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-10">
<clipPath id="terminal-2271905219-line-10">
<rect x="0" y="245.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-11">
<clipPath id="terminal-2271905219-line-11">
<rect x="0" y="269.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-12">
<clipPath id="terminal-2271905219-line-12">
<rect x="0" y="294.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-13">
<clipPath id="terminal-2271905219-line-13">
<rect x="0" y="318.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-14">
<clipPath id="terminal-2271905219-line-14">
<rect x="0" y="343.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-15">
<clipPath id="terminal-2271905219-line-15">
<rect x="0" y="367.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-16">
<clipPath id="terminal-2271905219-line-16">
<rect x="0" y="391.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-17">
<clipPath id="terminal-2271905219-line-17">
<rect x="0" y="416.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-18">
<clipPath id="terminal-2271905219-line-18">
<rect x="0" y="440.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-19">
<clipPath id="terminal-2271905219-line-19">
<rect x="0" y="465.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-20">
<clipPath id="terminal-2271905219-line-20">
<rect x="0" y="489.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-21">
<clipPath id="terminal-2271905219-line-21">
<rect x="0" y="513.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-22">
<clipPath id="terminal-2271905219-line-22">
<rect x="0" y="538.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-23">
<clipPath id="terminal-2271905219-line-23">
<rect x="0" y="562.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-24">
<clipPath id="terminal-2271905219-line-24">
<rect x="0" y="587.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-25">
<clipPath id="terminal-2271905219-line-25">
<rect x="0" y="611.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-26">
<clipPath id="terminal-2271905219-line-26">
<rect x="0" y="635.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-27">
<clipPath id="terminal-2271905219-line-27">
<rect x="0" y="660.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-28">
<clipPath id="terminal-2271905219-line-28">
<rect x="0" y="684.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="terminal-3756623299-line-29">
<clipPath id="terminal-2271905219-line-29">
<rect x="0" y="709.1" width="1464" height="24.65"/>
</clipPath>
</defs>
@@ -142,40 +142,40 @@
<circle cx="44" cy="0" r="7" fill="#28c840"/>
</g>
<g transform="translate(9, 41)" clip-path="url(#terminal-3756623299-clip-terminal)">
<g transform="translate(9, 41)" clip-path="url(#terminal-2271905219-clip-terminal)">
<g class="terminal-3756623299-matrix">
<text class="terminal-3756623299-r1" x="0" y="20" textLength="73.2" clip-path="url(#terminal-3756623299-line-0)">Usage:</text><text class="terminal-3756623299-r3" x="85.4" y="20" textLength="122" clip-path="url(#terminal-3756623299-line-0)">builder.py</text><text class="terminal-3756623299-r2" x="207.4" y="20" textLength="24.4" clip-path="url(#terminal-3756623299-line-0)">&#160;[</text><text class="terminal-3756623299-r4" x="231.8" y="20" textLength="24.4" clip-path="url(#terminal-3756623299-line-0)">-h</text><text class="terminal-3756623299-r2" x="256.2" y="20" textLength="24.4" clip-path="url(#terminal-3756623299-line-0)">]&#160;</text><text class="terminal-3756623299-r4" x="280.6" y="20" textLength="24.4" clip-path="url(#terminal-3756623299-line-0)">-p</text><text class="terminal-3756623299-r5" x="317.2" y="20" textLength="48.8" clip-path="url(#terminal-3756623299-line-0)">ROOT</text><text class="terminal-3756623299-r4" x="378.2" y="20" textLength="24.4" clip-path="url(#terminal-3756623299-line-0)">-w</text><text class="terminal-3756623299-r5" x="414.8" y="20" textLength="36.6" clip-path="url(#terminal-3756623299-line-0)">URL</text><text class="terminal-3756623299-r4" x="463.6" y="20" textLength="24.4" clip-path="url(#terminal-3756623299-line-0)">-t</text><text class="terminal-3756623299-r5" x="500.2" y="20" textLength="61" clip-path="url(#terminal-3756623299-line-0)">TITLE</text><text class="terminal-3756623299-r2" x="561.2" y="20" textLength="24.4" clip-path="url(#terminal-3756623299-line-0)">&#160;[</text><text class="terminal-3756623299-r4" x="585.6" y="20" textLength="24.4" clip-path="url(#terminal-3756623299-line-0)">-r</text><text class="terminal-3756623299-r2" x="610" y="20" textLength="36.6" clip-path="url(#terminal-3756623299-line-0)">]&#160;[</text><text class="terminal-3756623299-r4" x="646.6" y="20" textLength="24.4" clip-path="url(#terminal-3756623299-line-0)">-n</text><text class="terminal-3756623299-r2" x="671" y="20" textLength="36.6" clip-path="url(#terminal-3756623299-line-0)">]&#160;[</text><text class="terminal-3756623299-r4" x="707.6" y="20" textLength="24.4" clip-path="url(#terminal-3756623299-line-0)">-l</text><text class="terminal-3756623299-r5" x="744.2" y="20" textLength="85.4" clip-path="url(#terminal-3756623299-line-0)">LICENSE</text><text class="terminal-3756623299-r2" x="829.6" y="20" textLength="36.6" clip-path="url(#terminal-3756623299-line-0)">]&#160;[</text><text class="terminal-3756623299-r4" x="866.2" y="20" textLength="24.4" clip-path="url(#terminal-3756623299-line-0)">-a</text><text class="terminal-3756623299-r5" x="902.8" y="20" textLength="73.2" clip-path="url(#terminal-3756623299-line-0)">AUTHOR</text><text class="terminal-3756623299-r2" x="976" y="20" textLength="36.6" clip-path="url(#terminal-3756623299-line-0)">]&#160;[</text><text class="terminal-3756623299-r4" x="1012.6" y="20" textLength="24.4" clip-path="url(#terminal-3756623299-line-0)">-e</text><text class="terminal-3756623299-r5" x="1049.2" y="20" textLength="109.8" clip-path="url(#terminal-3756623299-line-0)">EXTENSION</text><text class="terminal-3756623299-r2" x="1159" y="20" textLength="36.6" clip-path="url(#terminal-3756623299-line-0)">]&#160;[</text><text class="terminal-3756623299-r4" x="1195.6" y="20" textLength="24.4" clip-path="url(#terminal-3756623299-line-0)">-m</text><text class="terminal-3756623299-r2" x="1220" y="20" textLength="12.2" clip-path="url(#terminal-3756623299-line-0)">]</text><text class="terminal-3756623299-r2" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-3756623299-line-0)">
</text><text class="terminal-3756623299-r2" x="0" y="44.4" textLength="231.8" clip-path="url(#terminal-3756623299-line-1)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;[</text><text class="terminal-3756623299-r4" x="231.8" y="44.4" textLength="146.4" clip-path="url(#terminal-3756623299-line-1)">--theme-path</text><text class="terminal-3756623299-r5" x="390.4" y="44.4" textLength="48.8" clip-path="url(#terminal-3756623299-line-1)">PATH</text><text class="terminal-3756623299-r2" x="439.2" y="44.4" textLength="36.6" clip-path="url(#terminal-3756623299-line-1)">]&#160;[</text><text class="terminal-3756623299-r4" x="475.8" y="44.4" textLength="231.8" clip-path="url(#terminal-3756623299-line-1)">--use-fancy-folders</text><text class="terminal-3756623299-r2" x="707.6" y="44.4" textLength="36.6" clip-path="url(#terminal-3756623299-line-1)">]&#160;[</text><text class="terminal-3756623299-r4" x="744.2" y="44.4" textLength="244" clip-path="url(#terminal-3756623299-line-1)">--ignore-other-files</text><text class="terminal-3756623299-r2" x="988.2" y="44.4" textLength="36.6" clip-path="url(#terminal-3756623299-line-1)">]&#160;[</text><text class="terminal-3756623299-r4" x="1024.8" y="44.4" textLength="195.2" clip-path="url(#terminal-3756623299-line-1)">--exclude-folder</text><text class="terminal-3756623299-r5" x="1232.2" y="44.4" textLength="73.2" clip-path="url(#terminal-3756623299-line-1)">FOLDER</text><text class="terminal-3756623299-r2" x="1305.4" y="44.4" textLength="12.2" clip-path="url(#terminal-3756623299-line-1)">]</text><text class="terminal-3756623299-r2" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-3756623299-line-1)">
</text><text class="terminal-3756623299-r2" x="0" y="68.8" textLength="231.8" clip-path="url(#terminal-3756623299-line-2)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;[</text><text class="terminal-3756623299-r4" x="231.8" y="68.8" textLength="109.8" clip-path="url(#terminal-3756623299-line-2)">--version</text><text class="terminal-3756623299-r2" x="341.6" y="68.8" textLength="12.2" clip-path="url(#terminal-3756623299-line-2)">]</text><text class="terminal-3756623299-r2" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-3756623299-line-2)">
</text><text class="terminal-3756623299-r2" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-3756623299-line-3)">
</text><text class="terminal-3756623299-r2" x="0" y="117.6" textLength="671" clip-path="url(#terminal-3756623299-line-4)">Generate&#160;HTML&#160;files&#160;for&#160;a&#160;static&#160;image&#160;hosting&#160;website.</text><text class="terminal-3756623299-r2" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-3756623299-line-4)">
</text><text class="terminal-3756623299-r2" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-3756623299-line-5)">
</text><text class="terminal-3756623299-r1" x="0" y="166.4" textLength="97.6" clip-path="url(#terminal-3756623299-line-6)">Options:</text><text class="terminal-3756623299-r2" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-3756623299-line-6)">
</text><text class="terminal-3756623299-r4" x="24.4" y="190.8" textLength="24.4" clip-path="url(#terminal-3756623299-line-7)">-h</text><text class="terminal-3756623299-r2" x="48.8" y="190.8" textLength="24.4" clip-path="url(#terminal-3756623299-line-7)">,&#160;</text><text class="terminal-3756623299-r4" x="73.2" y="190.8" textLength="73.2" clip-path="url(#terminal-3756623299-line-7)">--help</text><text class="terminal-3756623299-r2" x="292.8" y="190.8" textLength="378.2" clip-path="url(#terminal-3756623299-line-7)">show&#160;this&#160;help&#160;message&#160;and&#160;exit</text><text class="terminal-3756623299-r2" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-3756623299-line-7)">
</text><text class="terminal-3756623299-r4" x="24.4" y="215.2" textLength="24.4" clip-path="url(#terminal-3756623299-line-8)">-p</text><text class="terminal-3756623299-r2" x="48.8" y="215.2" textLength="24.4" clip-path="url(#terminal-3756623299-line-8)">,&#160;</text><text class="terminal-3756623299-r4" x="73.2" y="215.2" textLength="195.2" clip-path="url(#terminal-3756623299-line-8)">--root-directory</text><text class="terminal-3756623299-r5" x="280.6" y="215.2" textLength="48.8" clip-path="url(#terminal-3756623299-line-8)">ROOT</text><text class="terminal-3756623299-r2" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-3756623299-line-8)">
</text><text class="terminal-3756623299-r2" x="292.8" y="239.6" textLength="451.4" clip-path="url(#terminal-3756623299-line-9)">Root&#160;directory&#160;containing&#160;the&#160;images.</text><text class="terminal-3756623299-r2" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-3756623299-line-9)">
</text><text class="terminal-3756623299-r4" x="24.4" y="264" textLength="24.4" clip-path="url(#terminal-3756623299-line-10)">-w</text><text class="terminal-3756623299-r2" x="48.8" y="264" textLength="24.4" clip-path="url(#terminal-3756623299-line-10)">,&#160;</text><text class="terminal-3756623299-r4" x="73.2" y="264" textLength="170.8" clip-path="url(#terminal-3756623299-line-10)">--web-root-url</text><text class="terminal-3756623299-r5" x="256.2" y="264" textLength="36.6" clip-path="url(#terminal-3756623299-line-10)">URL</text><text class="terminal-3756623299-r2" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-3756623299-line-10)">
</text><text class="terminal-3756623299-r2" x="292.8" y="288.4" textLength="634.4" clip-path="url(#terminal-3756623299-line-11)">Base&#160;URL&#160;of&#160;the&#160;web&#160;root&#160;for&#160;the&#160;image&#160;hosting&#160;site.</text><text class="terminal-3756623299-r2" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-3756623299-line-11)">
</text><text class="terminal-3756623299-r4" x="24.4" y="312.8" textLength="24.4" clip-path="url(#terminal-3756623299-line-12)">-t</text><text class="terminal-3756623299-r2" x="48.8" y="312.8" textLength="24.4" clip-path="url(#terminal-3756623299-line-12)">,&#160;</text><text class="terminal-3756623299-r4" x="73.2" y="312.8" textLength="146.4" clip-path="url(#terminal-3756623299-line-12)">--site-title</text><text class="terminal-3756623299-r5" x="231.8" y="312.8" textLength="61" clip-path="url(#terminal-3756623299-line-12)">TITLE</text><text class="terminal-3756623299-r2" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-3756623299-line-12)">
</text><text class="terminal-3756623299-r2" x="292.8" y="337.2" textLength="390.4" clip-path="url(#terminal-3756623299-line-13)">Title&#160;of&#160;the&#160;image&#160;hosting&#160;site.</text><text class="terminal-3756623299-r2" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-3756623299-line-13)">
</text><text class="terminal-3756623299-r4" x="24.4" y="361.6" textLength="24.4" clip-path="url(#terminal-3756623299-line-14)">-r</text><text class="terminal-3756623299-r2" x="48.8" y="361.6" textLength="24.4" clip-path="url(#terminal-3756623299-line-14)">,&#160;</text><text class="terminal-3756623299-r4" x="73.2" y="361.6" textLength="280.6" clip-path="url(#terminal-3756623299-line-14)">--regenerate-thumbnails</text><text class="terminal-3756623299-r2" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-3756623299-line-14)">
</text><text class="terminal-3756623299-r2" x="292.8" y="386" textLength="597.8" clip-path="url(#terminal-3756623299-line-15)">Regenerate&#160;thumbnails&#160;even&#160;if&#160;they&#160;already&#160;exist.</text><text class="terminal-3756623299-r2" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-3756623299-line-15)">
</text><text class="terminal-3756623299-r4" x="24.4" y="410.4" textLength="24.4" clip-path="url(#terminal-3756623299-line-16)">-n</text><text class="terminal-3756623299-r2" x="48.8" y="410.4" textLength="24.4" clip-path="url(#terminal-3756623299-line-16)">,&#160;</text><text class="terminal-3756623299-r4" x="73.2" y="410.4" textLength="268.4" clip-path="url(#terminal-3756623299-line-16)">--non-interactive-mode</text><text class="terminal-3756623299-r2" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-3756623299-line-16)">
</text><text class="terminal-3756623299-r2" x="292.8" y="434.8" textLength="646.6" clip-path="url(#terminal-3756623299-line-17)">Run&#160;in&#160;non-interactive&#160;mode,&#160;disabling&#160;progress&#160;bars.</text><text class="terminal-3756623299-r2" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-3756623299-line-17)">
</text><text class="terminal-3756623299-r4" x="24.4" y="459.2" textLength="24.4" clip-path="url(#terminal-3756623299-line-18)">-l</text><text class="terminal-3756623299-r2" x="48.8" y="459.2" textLength="24.4" clip-path="url(#terminal-3756623299-line-18)">,&#160;</text><text class="terminal-3756623299-r4" x="73.2" y="459.2" textLength="170.8" clip-path="url(#terminal-3756623299-line-18)">--license-type</text><text class="terminal-3756623299-r5" x="256.2" y="459.2" textLength="85.4" clip-path="url(#terminal-3756623299-line-18)">LICENSE</text><text class="terminal-3756623299-r2" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-3756623299-line-18)">
</text><text class="terminal-3756623299-r2" x="292.8" y="483.6" textLength="488" clip-path="url(#terminal-3756623299-line-19)">Specify&#160;the&#160;license&#160;type&#160;for&#160;the&#160;images.</text><text class="terminal-3756623299-r2" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-3756623299-line-19)">
</text><text class="terminal-3756623299-r4" x="24.4" y="508" textLength="24.4" clip-path="url(#terminal-3756623299-line-20)">-a</text><text class="terminal-3756623299-r2" x="48.8" y="508" textLength="24.4" clip-path="url(#terminal-3756623299-line-20)">,&#160;</text><text class="terminal-3756623299-r4" x="73.2" y="508" textLength="158.6" clip-path="url(#terminal-3756623299-line-20)">--author-name</text><text class="terminal-3756623299-r5" x="244" y="508" textLength="73.2" clip-path="url(#terminal-3756623299-line-20)">AUTHOR</text><text class="terminal-3756623299-r2" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-3756623299-line-20)">
</text><text class="terminal-3756623299-r2" x="292.8" y="532.4" textLength="402.6" clip-path="url(#terminal-3756623299-line-21)">Name&#160;of&#160;the&#160;author&#160;of&#160;the&#160;images.</text><text class="terminal-3756623299-r2" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-3756623299-line-21)">
</text><text class="terminal-3756623299-r4" x="24.4" y="556.8" textLength="24.4" clip-path="url(#terminal-3756623299-line-22)">-e</text><text class="terminal-3756623299-r2" x="48.8" y="556.8" textLength="24.4" clip-path="url(#terminal-3756623299-line-22)">,&#160;</text><text class="terminal-3756623299-r4" x="73.2" y="556.8" textLength="207.4" clip-path="url(#terminal-3756623299-line-22)">--file-extensions</text><text class="terminal-3756623299-r5" x="292.8" y="556.8" textLength="109.8" clip-path="url(#terminal-3756623299-line-22)">EXTENSION</text><text class="terminal-3756623299-r2" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-3756623299-line-22)">
</text><text class="terminal-3756623299-r2" x="292.8" y="581.2" textLength="744.2" clip-path="url(#terminal-3756623299-line-23)">File&#160;extensions&#160;to&#160;include&#160;(can&#160;be&#160;specified&#160;multiple&#160;times).</text><text class="terminal-3756623299-r2" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-3756623299-line-23)">
</text><text class="terminal-3756623299-r4" x="24.4" y="605.6" textLength="24.4" clip-path="url(#terminal-3756623299-line-24)">-m</text><text class="terminal-3756623299-r2" x="48.8" y="605.6" textLength="24.4" clip-path="url(#terminal-3756623299-line-24)">,&#160;</text><text class="terminal-3756623299-r4" x="73.2" y="605.6" textLength="170.8" clip-path="url(#terminal-3756623299-line-24)">--web-manifest</text><text class="terminal-3756623299-r2" x="292.8" y="605.6" textLength="353.8" clip-path="url(#terminal-3756623299-line-24)">Generate&#160;a&#160;web&#160;manifest&#160;file.</text><text class="terminal-3756623299-r2" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-3756623299-line-24)">
</text><text class="terminal-3756623299-r4" x="24.4" y="630" textLength="146.4" clip-path="url(#terminal-3756623299-line-25)">--theme-path</text><text class="terminal-3756623299-r5" x="183" y="630" textLength="48.8" clip-path="url(#terminal-3756623299-line-25)">PATH</text><text class="terminal-3756623299-r2" x="292.8" y="630" textLength="329.4" clip-path="url(#terminal-3756623299-line-25)">Path&#160;to&#160;the&#160;CSS&#160;theme&#160;file.</text><text class="terminal-3756623299-r2" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-3756623299-line-25)">
</text><text class="terminal-3756623299-r4" x="24.4" y="654.4" textLength="231.8" clip-path="url(#terminal-3756623299-line-26)">--use-fancy-folders</text><text class="terminal-3756623299-r2" x="292.8" y="654.4" textLength="890.6" clip-path="url(#terminal-3756623299-line-26)">Enable&#160;fancy&#160;folder&#160;view&#160;instead&#160;of&#160;the&#160;default&#160;Apache&#160;directory&#160;listing.</text><text class="terminal-3756623299-r2" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-3756623299-line-26)">
</text><text class="terminal-3756623299-r4" x="24.4" y="678.8" textLength="244" clip-path="url(#terminal-3756623299-line-27)">--ignore-other-files</text><text class="terminal-3756623299-r2" x="292.8" y="678.8" textLength="683.2" clip-path="url(#terminal-3756623299-line-27)">Ignore&#160;files&#160;that&#160;do&#160;not&#160;match&#160;the&#160;specified&#160;extensions.</text><text class="terminal-3756623299-r2" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-3756623299-line-27)">
</text><text class="terminal-3756623299-r4" x="24.4" y="703.2" textLength="195.2" clip-path="url(#terminal-3756623299-line-28)">--exclude-folder</text><text class="terminal-3756623299-r5" x="231.8" y="703.2" textLength="73.2" clip-path="url(#terminal-3756623299-line-28)">FOLDER</text><text class="terminal-3756623299-r2" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-3756623299-line-28)">
</text><text class="terminal-3756623299-r2" x="292.8" y="727.6" textLength="1049.2" clip-path="url(#terminal-3756623299-line-29)">Folders&#160;to&#160;exclude&#160;from&#160;processing,&#160;globs&#160;supported&#160;(can&#160;be&#160;specified&#160;multiple&#160;times).</text><text class="terminal-3756623299-r2" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-3756623299-line-29)">
</text><text class="terminal-3756623299-r4" x="24.4" y="752" textLength="109.8" clip-path="url(#terminal-3756623299-line-30)">--version</text><text class="terminal-3756623299-r2" x="292.8" y="752" textLength="463.6" clip-path="url(#terminal-3756623299-line-30)">show&#160;program&#x27;s&#160;version&#160;number&#160;and&#160;exit</text><text class="terminal-3756623299-r2" x="1464" y="752" textLength="12.2" clip-path="url(#terminal-3756623299-line-30)">
<g class="terminal-2271905219-matrix">
<text class="terminal-2271905219-r1" x="0" y="20" textLength="73.2" clip-path="url(#terminal-2271905219-line-0)">Usage:</text><text class="terminal-2271905219-r3" x="85.4" y="20" textLength="122" clip-path="url(#terminal-2271905219-line-0)">builder.py</text><text class="terminal-2271905219-r2" x="207.4" y="20" textLength="24.4" clip-path="url(#terminal-2271905219-line-0)">&#160;[</text><text class="terminal-2271905219-r4" x="231.8" y="20" textLength="24.4" clip-path="url(#terminal-2271905219-line-0)">-h</text><text class="terminal-2271905219-r2" x="256.2" y="20" textLength="36.6" clip-path="url(#terminal-2271905219-line-0)">]&#160;[</text><text class="terminal-2271905219-r4" x="292.8" y="20" textLength="24.4" clip-path="url(#terminal-2271905219-line-0)">-a</text><text class="terminal-2271905219-r5" x="329.4" y="20" textLength="73.2" clip-path="url(#terminal-2271905219-line-0)">AUTHOR</text><text class="terminal-2271905219-r2" x="402.6" y="20" textLength="36.6" clip-path="url(#terminal-2271905219-line-0)">]&#160;[</text><text class="terminal-2271905219-r4" x="439.2" y="20" textLength="24.4" clip-path="url(#terminal-2271905219-line-0)">-e</text><text class="terminal-2271905219-r5" x="475.8" y="20" textLength="109.8" clip-path="url(#terminal-2271905219-line-0)">EXTENSION</text><text class="terminal-2271905219-r2" x="585.6" y="20" textLength="36.6" clip-path="url(#terminal-2271905219-line-0)">]&#160;[</text><text class="terminal-2271905219-r4" x="622.2" y="20" textLength="24.4" clip-path="url(#terminal-2271905219-line-0)">-l</text><text class="terminal-2271905219-r5" x="658.8" y="20" textLength="85.4" clip-path="url(#terminal-2271905219-line-0)">LICENSE</text><text class="terminal-2271905219-r2" x="744.2" y="20" textLength="36.6" clip-path="url(#terminal-2271905219-line-0)">]&#160;[</text><text class="terminal-2271905219-r4" x="780.8" y="20" textLength="24.4" clip-path="url(#terminal-2271905219-line-0)">-m</text><text class="terminal-2271905219-r2" x="805.2" y="20" textLength="36.6" clip-path="url(#terminal-2271905219-line-0)">]&#160;[</text><text class="terminal-2271905219-r4" x="841.8" y="20" textLength="24.4" clip-path="url(#terminal-2271905219-line-0)">-n</text><text class="terminal-2271905219-r2" x="866.2" y="20" textLength="24.4" clip-path="url(#terminal-2271905219-line-0)">]&#160;</text><text class="terminal-2271905219-r4" x="890.6" y="20" textLength="24.4" clip-path="url(#terminal-2271905219-line-0)">-p</text><text class="terminal-2271905219-r5" x="927.2" y="20" textLength="48.8" clip-path="url(#terminal-2271905219-line-0)">ROOT</text><text class="terminal-2271905219-r2" x="976" y="20" textLength="24.4" clip-path="url(#terminal-2271905219-line-0)">&#160;[</text><text class="terminal-2271905219-r4" x="1000.4" y="20" textLength="24.4" clip-path="url(#terminal-2271905219-line-0)">-r</text><text class="terminal-2271905219-r2" x="1024.8" y="20" textLength="24.4" clip-path="url(#terminal-2271905219-line-0)">]&#160;</text><text class="terminal-2271905219-r4" x="1049.2" y="20" textLength="24.4" clip-path="url(#terminal-2271905219-line-0)">-t</text><text class="terminal-2271905219-r5" x="1085.8" y="20" textLength="61" clip-path="url(#terminal-2271905219-line-0)">TITLE</text><text class="terminal-2271905219-r4" x="1159" y="20" textLength="24.4" clip-path="url(#terminal-2271905219-line-0)">-w</text><text class="terminal-2271905219-r5" x="1195.6" y="20" textLength="36.6" clip-path="url(#terminal-2271905219-line-0)">URL</text><text class="terminal-2271905219-r2" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-2271905219-line-0)">
</text><text class="terminal-2271905219-r2" x="0" y="44.4" textLength="231.8" clip-path="url(#terminal-2271905219-line-1)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;[</text><text class="terminal-2271905219-r4" x="231.8" y="44.4" textLength="195.2" clip-path="url(#terminal-2271905219-line-1)">--exclude-folder</text><text class="terminal-2271905219-r5" x="439.2" y="44.4" textLength="73.2" clip-path="url(#terminal-2271905219-line-1)">FOLDER</text><text class="terminal-2271905219-r2" x="512.4" y="44.4" textLength="36.6" clip-path="url(#terminal-2271905219-line-1)">]&#160;[</text><text class="terminal-2271905219-r4" x="549" y="44.4" textLength="244" clip-path="url(#terminal-2271905219-line-1)">--ignore-other-files</text><text class="terminal-2271905219-r2" x="793" y="44.4" textLength="36.6" clip-path="url(#terminal-2271905219-line-1)">]&#160;[</text><text class="terminal-2271905219-r4" x="829.6" y="44.4" textLength="146.4" clip-path="url(#terminal-2271905219-line-1)">--theme-path</text><text class="terminal-2271905219-r5" x="988.2" y="44.4" textLength="48.8" clip-path="url(#terminal-2271905219-line-1)">PATH</text><text class="terminal-2271905219-r2" x="1037" y="44.4" textLength="36.6" clip-path="url(#terminal-2271905219-line-1)">]&#160;[</text><text class="terminal-2271905219-r4" x="1073.6" y="44.4" textLength="231.8" clip-path="url(#terminal-2271905219-line-1)">--use-fancy-folders</text><text class="terminal-2271905219-r2" x="1305.4" y="44.4" textLength="12.2" clip-path="url(#terminal-2271905219-line-1)">]</text><text class="terminal-2271905219-r2" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-2271905219-line-1)">
</text><text class="terminal-2271905219-r2" x="0" y="68.8" textLength="231.8" clip-path="url(#terminal-2271905219-line-2)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;[</text><text class="terminal-2271905219-r4" x="231.8" y="68.8" textLength="109.8" clip-path="url(#terminal-2271905219-line-2)">--version</text><text class="terminal-2271905219-r2" x="341.6" y="68.8" textLength="12.2" clip-path="url(#terminal-2271905219-line-2)">]</text><text class="terminal-2271905219-r2" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-2271905219-line-2)">
</text><text class="terminal-2271905219-r2" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-2271905219-line-3)">
</text><text class="terminal-2271905219-r2" x="0" y="117.6" textLength="671" clip-path="url(#terminal-2271905219-line-4)">Generate&#160;HTML&#160;files&#160;for&#160;a&#160;static&#160;image&#160;hosting&#160;website.</text><text class="terminal-2271905219-r2" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-2271905219-line-4)">
</text><text class="terminal-2271905219-r2" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-2271905219-line-5)">
</text><text class="terminal-2271905219-r1" x="0" y="166.4" textLength="97.6" clip-path="url(#terminal-2271905219-line-6)">Options:</text><text class="terminal-2271905219-r2" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-2271905219-line-6)">
</text><text class="terminal-2271905219-r4" x="24.4" y="190.8" textLength="24.4" clip-path="url(#terminal-2271905219-line-7)">-h</text><text class="terminal-2271905219-r2" x="48.8" y="190.8" textLength="24.4" clip-path="url(#terminal-2271905219-line-7)">,&#160;</text><text class="terminal-2271905219-r4" x="73.2" y="190.8" textLength="73.2" clip-path="url(#terminal-2271905219-line-7)">--help</text><text class="terminal-2271905219-r2" x="292.8" y="190.8" textLength="378.2" clip-path="url(#terminal-2271905219-line-7)">show&#160;this&#160;help&#160;message&#160;and&#160;exit</text><text class="terminal-2271905219-r2" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-2271905219-line-7)">
</text><text class="terminal-2271905219-r4" x="24.4" y="215.2" textLength="24.4" clip-path="url(#terminal-2271905219-line-8)">-a</text><text class="terminal-2271905219-r2" x="48.8" y="215.2" textLength="24.4" clip-path="url(#terminal-2271905219-line-8)">,&#160;</text><text class="terminal-2271905219-r4" x="73.2" y="215.2" textLength="158.6" clip-path="url(#terminal-2271905219-line-8)">--author-name</text><text class="terminal-2271905219-r5" x="244" y="215.2" textLength="73.2" clip-path="url(#terminal-2271905219-line-8)">AUTHOR</text><text class="terminal-2271905219-r2" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-2271905219-line-8)">
</text><text class="terminal-2271905219-r2" x="292.8" y="239.6" textLength="402.6" clip-path="url(#terminal-2271905219-line-9)">Name&#160;of&#160;the&#160;author&#160;of&#160;the&#160;images.</text><text class="terminal-2271905219-r2" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-2271905219-line-9)">
</text><text class="terminal-2271905219-r4" x="24.4" y="264" textLength="24.4" clip-path="url(#terminal-2271905219-line-10)">-e</text><text class="terminal-2271905219-r2" x="48.8" y="264" textLength="24.4" clip-path="url(#terminal-2271905219-line-10)">,&#160;</text><text class="terminal-2271905219-r4" x="73.2" y="264" textLength="207.4" clip-path="url(#terminal-2271905219-line-10)">--file-extensions</text><text class="terminal-2271905219-r5" x="292.8" y="264" textLength="109.8" clip-path="url(#terminal-2271905219-line-10)">EXTENSION</text><text class="terminal-2271905219-r2" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-2271905219-line-10)">
</text><text class="terminal-2271905219-r2" x="292.8" y="288.4" textLength="744.2" clip-path="url(#terminal-2271905219-line-11)">File&#160;extensions&#160;to&#160;include&#160;(can&#160;be&#160;specified&#160;multiple&#160;times).</text><text class="terminal-2271905219-r2" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-2271905219-line-11)">
</text><text class="terminal-2271905219-r4" x="24.4" y="312.8" textLength="24.4" clip-path="url(#terminal-2271905219-line-12)">-l</text><text class="terminal-2271905219-r2" x="48.8" y="312.8" textLength="24.4" clip-path="url(#terminal-2271905219-line-12)">,&#160;</text><text class="terminal-2271905219-r4" x="73.2" y="312.8" textLength="170.8" clip-path="url(#terminal-2271905219-line-12)">--license-type</text><text class="terminal-2271905219-r5" x="256.2" y="312.8" textLength="85.4" clip-path="url(#terminal-2271905219-line-12)">LICENSE</text><text class="terminal-2271905219-r2" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-2271905219-line-12)">
</text><text class="terminal-2271905219-r2" x="292.8" y="337.2" textLength="488" clip-path="url(#terminal-2271905219-line-13)">Specify&#160;the&#160;license&#160;type&#160;for&#160;the&#160;images.</text><text class="terminal-2271905219-r2" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-2271905219-line-13)">
</text><text class="terminal-2271905219-r4" x="24.4" y="361.6" textLength="24.4" clip-path="url(#terminal-2271905219-line-14)">-m</text><text class="terminal-2271905219-r2" x="48.8" y="361.6" textLength="24.4" clip-path="url(#terminal-2271905219-line-14)">,&#160;</text><text class="terminal-2271905219-r4" x="73.2" y="361.6" textLength="170.8" clip-path="url(#terminal-2271905219-line-14)">--web-manifest</text><text class="terminal-2271905219-r2" x="292.8" y="361.6" textLength="353.8" clip-path="url(#terminal-2271905219-line-14)">Generate&#160;a&#160;web&#160;manifest&#160;file.</text><text class="terminal-2271905219-r2" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-2271905219-line-14)">
</text><text class="terminal-2271905219-r4" x="24.4" y="386" textLength="24.4" clip-path="url(#terminal-2271905219-line-15)">-n</text><text class="terminal-2271905219-r2" x="48.8" y="386" textLength="24.4" clip-path="url(#terminal-2271905219-line-15)">,&#160;</text><text class="terminal-2271905219-r4" x="73.2" y="386" textLength="268.4" clip-path="url(#terminal-2271905219-line-15)">--non-interactive-mode</text><text class="terminal-2271905219-r2" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-2271905219-line-15)">
</text><text class="terminal-2271905219-r2" x="292.8" y="410.4" textLength="646.6" clip-path="url(#terminal-2271905219-line-16)">Run&#160;in&#160;non-interactive&#160;mode,&#160;disabling&#160;progress&#160;bars.</text><text class="terminal-2271905219-r2" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-2271905219-line-16)">
</text><text class="terminal-2271905219-r4" x="24.4" y="434.8" textLength="24.4" clip-path="url(#terminal-2271905219-line-17)">-p</text><text class="terminal-2271905219-r2" x="48.8" y="434.8" textLength="24.4" clip-path="url(#terminal-2271905219-line-17)">,&#160;</text><text class="terminal-2271905219-r4" x="73.2" y="434.8" textLength="195.2" clip-path="url(#terminal-2271905219-line-17)">--root-directory</text><text class="terminal-2271905219-r5" x="280.6" y="434.8" textLength="48.8" clip-path="url(#terminal-2271905219-line-17)">ROOT</text><text class="terminal-2271905219-r2" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-2271905219-line-17)">
</text><text class="terminal-2271905219-r2" x="292.8" y="459.2" textLength="451.4" clip-path="url(#terminal-2271905219-line-18)">Root&#160;directory&#160;containing&#160;the&#160;images.</text><text class="terminal-2271905219-r2" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-2271905219-line-18)">
</text><text class="terminal-2271905219-r4" x="24.4" y="483.6" textLength="24.4" clip-path="url(#terminal-2271905219-line-19)">-r</text><text class="terminal-2271905219-r2" x="48.8" y="483.6" textLength="24.4" clip-path="url(#terminal-2271905219-line-19)">,&#160;</text><text class="terminal-2271905219-r4" x="73.2" y="483.6" textLength="280.6" clip-path="url(#terminal-2271905219-line-19)">--regenerate-thumbnails</text><text class="terminal-2271905219-r2" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-2271905219-line-19)">
</text><text class="terminal-2271905219-r2" x="292.8" y="508" textLength="597.8" clip-path="url(#terminal-2271905219-line-20)">Regenerate&#160;thumbnails&#160;even&#160;if&#160;they&#160;already&#160;exist.</text><text class="terminal-2271905219-r2" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-2271905219-line-20)">
</text><text class="terminal-2271905219-r4" x="24.4" y="532.4" textLength="24.4" clip-path="url(#terminal-2271905219-line-21)">-t</text><text class="terminal-2271905219-r2" x="48.8" y="532.4" textLength="24.4" clip-path="url(#terminal-2271905219-line-21)">,&#160;</text><text class="terminal-2271905219-r4" x="73.2" y="532.4" textLength="146.4" clip-path="url(#terminal-2271905219-line-21)">--site-title</text><text class="terminal-2271905219-r5" x="231.8" y="532.4" textLength="61" clip-path="url(#terminal-2271905219-line-21)">TITLE</text><text class="terminal-2271905219-r2" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-2271905219-line-21)">
</text><text class="terminal-2271905219-r2" x="292.8" y="556.8" textLength="390.4" clip-path="url(#terminal-2271905219-line-22)">Title&#160;of&#160;the&#160;image&#160;hosting&#160;site.</text><text class="terminal-2271905219-r2" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-2271905219-line-22)">
</text><text class="terminal-2271905219-r4" x="24.4" y="581.2" textLength="24.4" clip-path="url(#terminal-2271905219-line-23)">-w</text><text class="terminal-2271905219-r2" x="48.8" y="581.2" textLength="24.4" clip-path="url(#terminal-2271905219-line-23)">,&#160;</text><text class="terminal-2271905219-r4" x="73.2" y="581.2" textLength="170.8" clip-path="url(#terminal-2271905219-line-23)">--web-root-url</text><text class="terminal-2271905219-r5" x="256.2" y="581.2" textLength="36.6" clip-path="url(#terminal-2271905219-line-23)">URL</text><text class="terminal-2271905219-r2" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-2271905219-line-23)">
</text><text class="terminal-2271905219-r2" x="292.8" y="605.6" textLength="634.4" clip-path="url(#terminal-2271905219-line-24)">Base&#160;URL&#160;of&#160;the&#160;web&#160;root&#160;for&#160;the&#160;image&#160;hosting&#160;site.</text><text class="terminal-2271905219-r2" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-2271905219-line-24)">
</text><text class="terminal-2271905219-r4" x="24.4" y="630" textLength="195.2" clip-path="url(#terminal-2271905219-line-25)">--exclude-folder</text><text class="terminal-2271905219-r5" x="231.8" y="630" textLength="73.2" clip-path="url(#terminal-2271905219-line-25)">FOLDER</text><text class="terminal-2271905219-r2" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-2271905219-line-25)">
</text><text class="terminal-2271905219-r2" x="292.8" y="654.4" textLength="1049.2" clip-path="url(#terminal-2271905219-line-26)">Folders&#160;to&#160;exclude&#160;from&#160;processing,&#160;globs&#160;supported&#160;(can&#160;be&#160;specified&#160;multiple&#160;times).</text><text class="terminal-2271905219-r2" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-2271905219-line-26)">
</text><text class="terminal-2271905219-r4" x="24.4" y="678.8" textLength="244" clip-path="url(#terminal-2271905219-line-27)">--ignore-other-files</text><text class="terminal-2271905219-r2" x="292.8" y="678.8" textLength="683.2" clip-path="url(#terminal-2271905219-line-27)">Ignore&#160;files&#160;that&#160;do&#160;not&#160;match&#160;the&#160;specified&#160;extensions.</text><text class="terminal-2271905219-r2" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-2271905219-line-27)">
</text><text class="terminal-2271905219-r4" x="24.4" y="703.2" textLength="146.4" clip-path="url(#terminal-2271905219-line-28)">--theme-path</text><text class="terminal-2271905219-r5" x="183" y="703.2" textLength="48.8" clip-path="url(#terminal-2271905219-line-28)">PATH</text><text class="terminal-2271905219-r2" x="292.8" y="703.2" textLength="329.4" clip-path="url(#terminal-2271905219-line-28)">Path&#160;to&#160;the&#160;CSS&#160;theme&#160;file.</text><text class="terminal-2271905219-r2" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-2271905219-line-28)">
</text><text class="terminal-2271905219-r4" x="24.4" y="727.6" textLength="231.8" clip-path="url(#terminal-2271905219-line-29)">--use-fancy-folders</text><text class="terminal-2271905219-r2" x="292.8" y="727.6" textLength="890.6" clip-path="url(#terminal-2271905219-line-29)">Enable&#160;fancy&#160;folder&#160;view&#160;instead&#160;of&#160;the&#160;default&#160;Apache&#160;directory&#160;listing.</text><text class="terminal-2271905219-r2" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-2271905219-line-29)">
</text><text class="terminal-2271905219-r4" x="24.4" y="752" textLength="109.8" clip-path="url(#terminal-2271905219-line-30)">--version</text><text class="terminal-2271905219-r2" x="292.8" y="752" textLength="463.6" clip-path="url(#terminal-2271905219-line-30)">show&#160;program&#x27;s&#160;version&#160;number&#160;and&#160;exit</text><text class="terminal-2271905219-r2" x="1464" y="752" textLength="12.2" clip-path="url(#terminal-2271905219-line-30)">
</text>
</g>
</g>

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

73
hl_config.yaml Normal file
View 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"

View File

@@ -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,22 +107,27 @@ def parse_arguments(version: str) -> Args:
An instance of the Args class containing the parsed arguments.
"""
# fmt: off
parser = argparse.ArgumentParser(description="Generate HTML files for a static image hosting website.", formatter_class=RichHelpFormatter)
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")
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("--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}")
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}")
parsed_args = parser.parse_args()
# fmt: on
_args = Args(
@@ -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,

View File

@@ -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,

View File

@@ -1,5 +1,5 @@
"""
loggerdabn.py
logger.py
This module provides functionality for setting up a centralized logging system using the
`logging` library and the `python-json-logger` to output logs in JSON format. It handles
@@ -12,9 +12,11 @@ Functions:
- setup_consolelogger(): Configures the logging system to output logs in console format.
"""
import logging
import os
import json
import gzip
import shutil
import logging
from datetime import datetime
from pythonjsonlogger import jsonlogger
@@ -44,14 +46,16 @@ def log_format(keys):
return [f"%({i})s" for i in keys]
def rotate_log_file():
def rotate_log_file(compress=False):
"""
Renames the existing 'latest.jsonl' file to a timestamped file based on the first log entry's asctime.
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.
If 'latest.jsonl' exists, it's renamed to the first timestamp found in the log entry.
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)
@@ -60,25 +64,36 @@ def rotate_log_file():
except (json.JSONDecodeError, KeyError):
first_timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
safe_timestamp = first_timestamp.replace(":", "-").replace(" ", "_")
old_log_filename = os.path.join(LOG_DIR, f"{safe_timestamp}.jsonl")
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:
with gzip.open(f"{old_log_filename}.gz", "wb") as f_out:
shutil.copyfileobj(f_in, f_out)
os.remove(old_log_filename)
# Truncate the original file
f.seek(0)
f.truncate()
def setup_logger():
def setup_logger(level=logging.INFO):
"""
Configures the logging system with a custom format and outputs logs in JSON format.
The logger will write to the 'logs/latest.jsonl' file, and it will include
multiple attributes such as the time of logging, the filename, function name, log level, etc.
If 'latest.jsonl' already exists, it will be renamed to a timestamped file before creating a new one.
Returns:
logging.Logger: A configured logger instance that can be used to log messages.
"""
rotate_log_file()
_logger = logging.getLogger()
_logger = logging.getLogger(name="defaultlogger")
supported_keys = ["asctime", "created", "filename", "funcName", "levelname", "levelno", "lineno", "module", "msecs", "message", "name", "pathname", "process", "processName", "relativeCreated", "thread", "threadName", "taskName"]
@@ -89,22 +104,37 @@ def setup_logger():
log_handler.setFormatter(formatter)
_logger.addHandler(log_handler)
_logger.setLevel(logging.INFO)
_logger.setLevel(level=level)
return _logger
def setup_consolelogger():
def setup_consolelogger(level=logging.INFO):
"""
Configures the logging system to output logs in console format.
Configures the logging system to output logs in console and JSON format.
The logger will write to the 'logs/latest.jsonl' file, and it will include
multiple attributes such as the time of logging, the filename, function name, log level, etc.
Returns:
logging.Logger: A configured logger instance that can be used to log messages.
"""
_logger = setup_logger()
_logger = logging.getLogger(name="consolelogger")
supported_keys = ["asctime", "created", "filename", "funcName", "levelname", "levelno", "lineno", "module", "msecs", "message", "name", "pathname", "process", "processName", "relativeCreated", "thread", "threadName", "taskName"]
custom_format = " ".join(log_format(supported_keys))
formatter = jsonlogger.JsonFormatter(custom_format)
log_handler = logging.FileHandler(LATEST_LOG_FILE)
log_handler.setFormatter(formatter)
_logger.addHandler(log_handler)
_logger.addHandler(logging.StreamHandler())
_logger.setLevel(level=level)
return _logger
rotate_log_file(compress=True)
logger = setup_logger()
consolelogger = setup_consolelogger()

View File

@@ -1,6 +1,7 @@
import os
import shutil
from typing import List, Dict
from subprocess import Popen, PIPE
from PIL import Image
from jinja2 import Environment, FileSystemLoader
@@ -92,10 +93,23 @@ def generate_favicon(iconspath: str, root_directory: str) -> None:
"""
favicon = os.path.join(root_directory, ".static", "favicon.ico")
logger.info("generating favicon with imagemagick", extra={"iconspath": iconspath, "favicon": favicon})
command = f'magick {os.path.join(iconspath, "icon.png")} -define icon:auto-resize=16,32,48,64,72,96,144,192 {favicon}'
_env = dict(os.environ)
lp_key = "LD_LIBRARY_PATH"
lp_orig = _env.get(lp_key + "_ORIG")
if lp_orig is not None:
_env[lp_key] = lp_orig
else:
_env.pop(lp_key, None)
if not shutil.which("magick"):
command = f'convert {os.path.join(iconspath, "icon.png")} -define icon:auto-resize=16,32,48,64,72,96,144,192 {favicon}'
os.system(command)
magick = shutil.which("convert")
else:
magick = shutil.which("magick")
command = [magick, os.path.join(iconspath, "icon.png"), "-define", "icon:auto-resize=16,32,48,64,72,96,144,192", favicon]
with Popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=_env, errors="ignore") as p:
out, err = p.communicate()
if p.returncode != 0:
logger.error("error generating favicon: %s", err, extra={"command": command, "out": out, "err": err})
logger.info("favicon generated successfully", extra={"command": command, "out": out, "err": err})
def icons(_args: Args) -> None:
@@ -164,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"},
@@ -174,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,
@@ -226,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
@@ -241,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)

View File

@@ -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

Binary file not shown.

4
view-latest-log.sh Executable file
View 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
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
LESS=-SR hl $(ls -tr logs/*.{jsonl,jsonl.gz}) --config hl_config.yaml