mirror of
https://github.com/greflm13/StaticGalleryBuilder.git
synced 2026-02-05 11:09:26 +00:00
hierarchical tagging selection
This commit is contained in:
@@ -176,6 +176,7 @@ figure {
|
|||||||
|
|
||||||
.tooltip .tagdropdown {
|
.tooltip .tagdropdown {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip:hover .tooltiptext {
|
.tooltip:hover .tooltiptext {
|
||||||
@@ -196,6 +197,11 @@ figure {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip .tooltiptext ol {
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.tooltip .tooltiptext .tagentry label {
|
.tooltip .tooltiptext .tagentry label {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import fnmatch
|
|||||||
import json
|
import json
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from tqdm.auto import tqdm
|
from tqdm.auto import tqdm
|
||||||
from PIL import Image, ExifTags, TiffImagePlugin, UnidentifiedImageError
|
from PIL import Image, ExifTags, TiffImagePlugin, UnidentifiedImageError
|
||||||
@@ -189,27 +190,66 @@ def get_image_info(item: str, folder: str) -> dict[str, Any]:
|
|||||||
tags = xmpdata["xmpmeta"]["RDF"]["Description"]["subject"]["Bag"]["li"]
|
tags = xmpdata["xmpmeta"]["RDF"]["Description"]["subject"]["Bag"]["li"]
|
||||||
if isinstance(tags, str):
|
if isinstance(tags, str):
|
||||||
tags = [tags]
|
tags = [tags]
|
||||||
xmp = xmpdata
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
...
|
pass
|
||||||
except KeyError:
|
except KeyError:
|
||||||
...
|
pass
|
||||||
try:
|
try:
|
||||||
tags = xmpdata["xapmeta"]["RDF"]["Description"]["subject"]["Bag"]["li"]
|
tags = xmpdata["xapmeta"]["RDF"]["Description"]["subject"]["Bag"]["li"]
|
||||||
if isinstance(tags, str):
|
if isinstance(tags, str):
|
||||||
tags = [tags]
|
tags = [tags]
|
||||||
xmp = xmpdata
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
...
|
pass
|
||||||
except KeyError:
|
except KeyError:
|
||||||
...
|
pass
|
||||||
|
try:
|
||||||
|
tags = xmpdata["xmpmeta"]["RDF"]["Description"]["hierarchicalSubject"]["Bag"]["li"]
|
||||||
|
if isinstance(tags, str):
|
||||||
|
tags = [tags]
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
tags = xmpdata["xapmeta"]["RDF"]["Description"]["hierarchicalSubject"]["Bag"]["li"]
|
||||||
|
if isinstance(tags, str):
|
||||||
|
tags = [tags]
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
if None in tags:
|
if None in tags:
|
||||||
tags.remove(None)
|
tags.remove(None)
|
||||||
if "st" in tags:
|
|
||||||
tags.remove("st")
|
|
||||||
return {"width": width, "height": height, "tags": tags, "exifdata": exifdata, "xmp": xmp}
|
return {"width": width, "height": height, "tags": tags, "exifdata": exifdata, "xmp": xmp}
|
||||||
|
|
||||||
|
|
||||||
|
def nested_dict():
|
||||||
|
return defaultdict(nested_dict)
|
||||||
|
|
||||||
|
|
||||||
|
def insert_path(d, path):
|
||||||
|
for part in path[:-1]:
|
||||||
|
d = d[part]
|
||||||
|
last = path[-1]
|
||||||
|
if not isinstance(d[last], dict):
|
||||||
|
d[last] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def finalize(d):
|
||||||
|
if isinstance(d, defaultdict):
|
||||||
|
# Sort keys before recursion
|
||||||
|
return {k: finalize(d[k]) for k in sorted(d)}
|
||||||
|
return d or []
|
||||||
|
|
||||||
|
|
||||||
|
def parse_hierarchical_tags(tags, delimiter="|"):
|
||||||
|
tree = nested_dict()
|
||||||
|
for tag in tags:
|
||||||
|
parts = tag.split(delimiter)
|
||||||
|
insert_path(tree, parts)
|
||||||
|
return finalize(tree)
|
||||||
|
|
||||||
|
|
||||||
def get_tags(sidecarfile: str) -> list[str]:
|
def get_tags(sidecarfile: str) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Extracts Tags from XMP sidecar file
|
Extracts Tags from XMP sidecar file
|
||||||
@@ -230,21 +270,35 @@ def get_tags(sidecarfile: str) -> list[str]:
|
|||||||
if isinstance(tags, str):
|
if isinstance(tags, str):
|
||||||
tags = [tags]
|
tags = [tags]
|
||||||
except TypeError:
|
except TypeError:
|
||||||
...
|
pass
|
||||||
except KeyError:
|
except KeyError:
|
||||||
...
|
pass
|
||||||
try:
|
try:
|
||||||
tags = xmpdata["xapmeta"]["RDF"]["Description"]["subject"]["Bag"]["li"]
|
tags = xmpdata["xapmeta"]["RDF"]["Description"]["subject"]["Bag"]["li"]
|
||||||
if isinstance(tags, str):
|
if isinstance(tags, str):
|
||||||
tags = [tags]
|
tags = [tags]
|
||||||
except TypeError:
|
except TypeError:
|
||||||
...
|
pass
|
||||||
except KeyError:
|
except KeyError:
|
||||||
...
|
pass
|
||||||
|
try:
|
||||||
|
tags = xmpdata["xmpmeta"]["RDF"]["Description"]["hierarchicalSubject"]["Bag"]["li"]
|
||||||
|
if isinstance(tags, str):
|
||||||
|
tags = [tags]
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
tags = xmpdata["xapmeta"]["RDF"]["Description"]["hierarchicalSubject"]["Bag"]["li"]
|
||||||
|
if isinstance(tags, str):
|
||||||
|
tags = [tags]
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
if None in tags:
|
if None in tags:
|
||||||
tags.remove(None)
|
tags.remove(None)
|
||||||
if "st" in tags:
|
|
||||||
tags.remove("st")
|
|
||||||
return tags
|
return tags
|
||||||
|
|
||||||
|
|
||||||
@@ -489,9 +543,9 @@ def create_html_file(folder: str, title: str, foldername: str, images: list[dict
|
|||||||
|
|
||||||
alltags = set()
|
alltags = set()
|
||||||
for img in images:
|
for img in images:
|
||||||
for tag in img["tags"]:
|
alltags.update(img["tags"])
|
||||||
alltags.add(tag)
|
|
||||||
alltags = sorted(alltags)
|
nested_tags = parse_hierarchical_tags(alltags)
|
||||||
|
|
||||||
folder_info = info.get(urllib.parse.quote(folder), "").split("\n")
|
folder_info = info.get(urllib.parse.quote(folder), "").split("\n")
|
||||||
_info = [i for i in folder_info if len(i) > 1] if folder_info else None
|
_info = [i for i in folder_info if len(i) > 1] if folder_info else None
|
||||||
@@ -541,7 +595,7 @@ def create_html_file(folder: str, title: str, foldername: str, images: list[dict
|
|||||||
version=version,
|
version=version,
|
||||||
logo=logo,
|
logo=logo,
|
||||||
licensefile=license_url,
|
licensefile=license_url,
|
||||||
tags=alltags,
|
tags=nested_tags,
|
||||||
)
|
)
|
||||||
|
|
||||||
with open(html_file, "w", encoding="utf-8") as f:
|
with open(html_file, "w", encoding="utf-8") as f:
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
background-color: var(--color2);
|
background-color: var(--color2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--color4);
|
background-color: var(--color4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,17 @@
|
|||||||
|
{%- macro render_tags(tag_tree, parent) -%}
|
||||||
|
<ol>
|
||||||
|
{%- for key, value in tag_tree.items() %}
|
||||||
|
<li class="tagentry">
|
||||||
|
<label onclick="filter()" title="{{ key }}" id="{{ parent }}|{{ key }}">
|
||||||
|
<input type="checkbox" />{{ key }}
|
||||||
|
</label>
|
||||||
|
{%- if value %}
|
||||||
|
{{ render_tags(value, parent + '|' + key) }}
|
||||||
|
{%- endif %}
|
||||||
|
</li>
|
||||||
|
{%- endfor %}
|
||||||
|
</ol>
|
||||||
|
{%- endmacro -%}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
@@ -49,15 +63,14 @@
|
|||||||
<li class="title"><span class="header">{{ header }}</span></li>
|
<li class="title"><span class="header">{{ header }}</span></li>
|
||||||
</div>
|
</div>
|
||||||
<div class="navright">
|
<div class="navright">
|
||||||
{%- if tags|length > 0 %}
|
{% if tags %}
|
||||||
<li class="tooltip"><a>Filter by Tags</a>
|
<li class="tooltip">
|
||||||
|
<a>Filter by Tags</a>
|
||||||
<ol class="tooltiptext tagdropdown" id="tagdropdown">
|
<ol class="tooltiptext tagdropdown" id="tagdropdown">
|
||||||
{%- for tag in tags -%}
|
{{ render_tags(tags, '') }}
|
||||||
<li class="tagentry"><label onclick="filter()"><input type="checkbox" />{{ tag }}</label></li><br />
|
|
||||||
{%- endfor -%}
|
|
||||||
</ol>
|
</ol>
|
||||||
</li>
|
</li>
|
||||||
{%- endif %}
|
{% endif %}
|
||||||
{%- if licensefile %}
|
{%- if licensefile %}
|
||||||
<li class="license"><a href="{{ licensefile }}">License</a></li>
|
<li class="license"><a href="{{ licensefile }}">License</a></li>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
@@ -165,7 +178,6 @@
|
|||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
];
|
];
|
||||||
var shown = [];
|
|
||||||
var re = /pid=(\d+)/;
|
var re = /pid=(\d+)/;
|
||||||
var filterre = /#(.*)/;
|
var filterre = /#(.*)/;
|
||||||
var controllers = {}
|
var controllers = {}
|
||||||
@@ -194,10 +206,10 @@
|
|||||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateImageList() {
|
function updateImageList(images) {
|
||||||
var str = ""
|
var str = ""
|
||||||
var imagelist = document.getElementById("imagelist");
|
var imagelist = document.getElementById("imagelist");
|
||||||
shown.forEach((item, index) => {
|
images.forEach((item, index) => {
|
||||||
str += '<div class="column"><figure><img src="' + item.msrc + '" onclick="openSwipe(' + index + ')" onmouseover="prefetch(' + index + ')" onmouseleave="cancel(' + index + ')" /><figcaption class="caption">' + item.name;
|
str += '<div class="column"><figure><img src="' + item.msrc + '" onclick="openSwipe(' + index + ')" onmouseover="prefetch(' + index + ')" onmouseleave="cancel(' + index + ')" /><figcaption class="caption">' + item.name;
|
||||||
if (item.tiff != "") {
|
if (item.tiff != "") {
|
||||||
str += ' <a href="' + item.tiff + '">TIFF</a>';
|
str += ' <a href="' + item.tiff + '">TIFF</a>';
|
||||||
@@ -229,31 +241,31 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function filter() {
|
function filter() {
|
||||||
window.location.href = window.location.href.split("#")[0] + "#"
|
window.location.href = window.location.href.split("#")[0] + "#";
|
||||||
var selected_tags = [];
|
|
||||||
var tagdropdown, tags, incl;
|
const selected_tags = [];
|
||||||
shown = [];
|
const shown = [];
|
||||||
tagdropdown = document.getElementById("tagdropdown").getElementsByTagName("li");
|
|
||||||
for (var i = 0; i < tagdropdown.length; i++) {
|
const tagcheckboxes = document.querySelectorAll("#tagdropdown input[type='checkbox']:checked");
|
||||||
if (tagdropdown[i].firstChild.firstChild.checked) {
|
|
||||||
selected_tags.push([tagdropdown[i].innerText])
|
tagcheckboxes.forEach((checkbox) => {
|
||||||
}
|
const tag = checkbox.parentElement.id.trim().substring(1);
|
||||||
}
|
selected_tags.push(tag);
|
||||||
var urltags = selected_tags.join(",");
|
});
|
||||||
items.forEach((item, index) => {
|
console.log(selected_tags);
|
||||||
tags = item.tags;
|
|
||||||
incl = true;
|
const urltags = selected_tags.join(",");
|
||||||
selected_tags.forEach((tag) => {
|
|
||||||
if (tags.indexOf(tag) == -1) {
|
items.forEach((item) => {
|
||||||
incl = false;
|
const tags = item.tags || [];
|
||||||
|
const include = selected_tags.every(tag => tags.includes(tag));
|
||||||
|
if (include || selected_tags.length === 0) {
|
||||||
|
shown.push(item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (incl | selected_tags == []) {
|
|
||||||
shown.push(item)
|
updateImageList(shown);
|
||||||
}
|
window.location.href += urltags;
|
||||||
});
|
|
||||||
updateImageList();
|
|
||||||
window.location.href += urltags
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setFilter(selected) {
|
function setFilter(selected) {
|
||||||
@@ -275,8 +287,7 @@
|
|||||||
}
|
}
|
||||||
filter();
|
filter();
|
||||||
{%- else %}
|
{%- else %}
|
||||||
shown = items;
|
updateImageList(items);
|
||||||
updateImageList();
|
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
if (re.test(window.location.href)) {
|
if (re.test(window.location.href)) {
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ body {
|
|||||||
background-color: var(--color6);
|
background-color: var(--color6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--color3);
|
background-color: var(--color3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ body {
|
|||||||
background-color: var(--color3);
|
background-color: var(--color3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--color7);
|
background-color: var(--color7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
background-color: var(--bcolor1);
|
background-color: var(--bcolor1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--bcolor3);
|
background-color: var(--bcolor3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
background-color: var(--bcolor1);
|
background-color: var(--bcolor1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--bcolor3);
|
background-color: var(--bcolor3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
font-family: "Playfair Display", serif;
|
font-family: "Playfair Display", serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--color3);
|
background-color: var(--color3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
background-color: var(--bcolor2);
|
background-color: var(--bcolor2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--bcolor4);
|
background-color: var(--bcolor4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
font-family: "Nunito", sans-serif;
|
font-family: "Nunito", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--color4);
|
background-color: var(--color4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
background-color: var(--bcolor2);
|
background-color: var(--bcolor2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--bcolor4);
|
background-color: var(--bcolor4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
background-color: var(--color3);
|
background-color: var(--color3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--color4);
|
background-color: var(--color4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
background-color: var(--color2);
|
background-color: var(--color2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--color4);
|
background-color: var(--color4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
background-color: var(--bcolor2);
|
background-color: var(--bcolor2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--bcolor4);
|
background-color: var(--bcolor4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
background-color: var(--color3);
|
background-color: var(--color3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--color2);
|
background-color: var(--color2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
background-color: var(--bcolor2);
|
background-color: var(--bcolor2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--bcolor3);
|
background-color: var(--bcolor3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
background-color: var(--bcolor2);
|
background-color: var(--bcolor2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--bcolor4);
|
background-color: var(--bcolor4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
font-family: "Lora", serif;
|
font-family: "Lora", serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--bcolor3);
|
background-color: var(--bcolor3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
background-color: var(--color4);
|
background-color: var(--color4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--color3);
|
background-color: var(--color3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
font-family: "Roboto", sans-serif;
|
font-family: "Roboto", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--bcolor3);
|
background-color: var(--bcolor3);
|
||||||
color: var(--bcolor2);
|
color: var(--bcolor2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
background-color: var(--bcolor2);
|
background-color: var(--bcolor2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--bcolor4);
|
background-color: var(--bcolor4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
font-family: "Montserrat", sans-serif;
|
font-family: "Montserrat", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagentry:hover {
|
.tagentry > label:hover {
|
||||||
background-color: var(--color2);
|
background-color: var(--color2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user