major rework of configration parameters.

This commit is contained in:
2022-06-07 18:03:45 +03:00
parent afca67300e
commit f311b97d37
7 changed files with 295 additions and 120 deletions

View File

@@ -1,4 +1,4 @@
__version__ = "20220328.0" __version__ = "20220607.0"
def get_version(): def get_version():

View File

@@ -4,8 +4,11 @@ import sys
import re import re
import shutil import shutil
import subprocess import subprocess
from argparse import ArgumentParser import random
from argparse import ArgumentParser, HelpFormatter
from mirva import get_version from mirva import get_version
from tqdm import tqdm
class Mirva: class Mirva:
@@ -22,19 +25,44 @@ class Mirva:
".*\.jpg$|.*\.jpeg$|.*\.png$|.*\.gif$|.*\.tif$", re.I ".*\.jpg$|.*\.jpeg$|.*\.png$|.*\.gif$|.*\.tif$", re.I
) )
self.video_match = re.compile(".*\.mp4$", re.I) self.video_match = re.compile(".*\.mp4$", re.I)
self.site_defaults = {
"title": {"default": "", "help": "Title of the site"},
"sub_title": {
"default": "",
"help": "Subtitle of the site, shown under the title",
},
"intro": {
"default": "",
"help": "Intro text shown before first image",
},
"image_size": {
"default": "1920",
"help": "Resize images for faster loading. Use 'link' for symbolic links without resizing.",
},
"scroll": {
"default": "smooth",
"help": "Transition to next image with keyboard: smooth or auto",
},
}
## Init ##
self.get_options() self.get_options()
os.chdir(self.options.folder) os.chdir(self.options.folder)
self.write_resources()
self.file_list = self.get_files() self.file_list = self.get_files()
if self.options.config or not os.path.exists(self.config_file):
self.create_config() if self.run_commands["config"]:
self.write_resources()
updated = self.create_config()
if updated:
print( print(
"Config created: Exiting without gallery creation. Check config first." "Config created or updated: Check config contents: {}".format(
self.config_file
) )
return )
if self.run_commands["generator"]:
self.get_config() self.get_config()
if self.options.exif:
self.append_exif()
self.create_posts() self.create_posts()
self.write_index() self.write_index()
self.write_mediums() self.write_mediums()
@@ -46,12 +74,19 @@ class Mirva:
self.config.read(self.config_file) self.config.read(self.config_file)
config_changed = False config_changed = False
if not "SITE" in self.config: if not "SITE" in self.config:
self.config["SITE"] = { self.config["SITE"] = {}
"title": "", config_changed = True
"sub_title": "",
"intro": "", for key in self.site_defaults:
"image_size": 850, if not key in self.config["SITE"]:
} config_changed = True
self.config["SITE"][key] = self.site_defaults[key]["default"]
if self.options.set:
for key, value in self.options.set:
if key not in self.site_defaults:
raise KeyError("Key '{}' is not a config keyword".format(key))
self.config["SITE"][key] = value
config_changed = True config_changed = True
for f in self.file_list: for f in self.file_list:
@@ -60,9 +95,27 @@ class Mirva:
title = title.replace("_", " ") title = title.replace("_", " ")
self.config[f] = {"title": title, "description": ""} self.config[f] = {"title": title, "description": ""}
config_changed = True config_changed = True
print("Added {}".format(f))
if self.options.purge:
unnecessary = []
for f in self.config:
if f in ("SITE", "DEFAULT"):
continue
if f not in self.file_list:
print("{} not found in files".format(f))
unnecessary.append(f)
for f in unnecessary:
del self.config[f]
config_changed = True
if self.options.exif:
self.append_exif()
config_changed = True
if config_changed: if config_changed:
self.write_config() self.write_config()
return config_changed
def create_posts(self): def create_posts(self):
@@ -93,7 +146,6 @@ class Mirva:
self.config.write(fp) self.config.write(fp)
def get_files(self): def get_files(self):
image_match = re.compile(".*\.jpg$|.*\.jpeg$|.*\.png$|.*\.gif$|.*\.tif$", re.I)
files = [] files = []
for f in sorted(os.listdir(".")): for f in sorted(os.listdir(".")):
if f.startswith("."): if f.startswith("."):
@@ -104,24 +156,24 @@ class Mirva:
return files return files
def get_options(self): def get_options(self):
parser = ArgumentParser( parser = ArgumentParser(prog="mirva", formatter_class=SmartFormatter)
prog="mirva",
epilog='Configuration note: item "image_size = [integer]" is the '
+ "middle sized image max width/height in pixels. It also accepts a special "
+ 'value "link" to make symbolic links.',
)
# parser.add_argument("-v", default=False, action="store_true")
parser.add_argument( parser.add_argument(
"--config", "--version",
default=False, action="version",
action="store_true", version="%(prog)s {version}".format(version=get_version()),
help="Write config and exit. Required if more images are added.",
) )
parser.add_argument( parser.add_argument(
"--force", "--folder",
type=str,
default=".",
help="Folder for gallery. Default current folder.",
)
parser.add_argument(
"--purge",
default=False, default=False,
action="store_true", action="store_true",
help="Force regeneration of middle sized images", help="Remove non-existent files in image list. Required if images are removed.",
) )
parser.add_argument( parser.add_argument(
"--exif", "--exif",
@@ -130,20 +182,43 @@ class Mirva:
help="Append EXIF information to image descriptions", help="Append EXIF information to image descriptions",
) )
parser.add_argument( parser.add_argument(
"--version", "--set",
action="version", "-s",
version="%(prog)s {version}".format(version=get_version()), default=None,
nargs=2,
action="append",
metavar=("key", "value"),
help="smart|Configurable options: \n"
+ "\n".join(
["{}: {}".format(k, v["help"]) for k, v in self.site_defaults.items()]
),
)
parser.add_argument(
"--force",
default=False,
action="store_true",
help="Force regeneration of middle sized images",
) )
parser.add_argument( parser.add_argument(
"folder", "--command",
type=str, "-c",
default=".", default="all",
nargs="?", action="store",
help="Folder for gallery", choices=["all", "config", "generator"],
help="Run only part of the process. Defaults to all. Config is run always if it doesn't exist.",
) )
self.options = parser.parse_args()
def get_index(self, page_title, sub_title, intro, posts): self.options = parser.parse_args()
self.run_commands = {
"config": self.options.command == "config"
or self.options.command == "all"
or not os.path.exists(self.config_file),
"generator": self.options.command == "generator"
or self.options.command == "all",
}
def get_index(self, posts):
return """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> return """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- <!--
@@ -164,10 +239,10 @@ Released : 20110306
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta http-equiv="content-type" content="text/html; charset=utf-8" />
<link rel="shortcut icon" href="{resource}/mirva.ico"/> <link rel="shortcut icon" href="{resource}/mirva.ico"/>
<title>{page_title}</title> <title>{page_title}</title>
<link href="{resource}/style.css" rel="stylesheet" type="text/css" media="screen" /> <link href="{resource}/mirva.css" rel="stylesheet" type="text/css" media="screen" />
<script src="{resource}/mirva.js"></script> <script src="{resource}/mirva.js"></script>
</head> </head>
<body> <body data-scroll="{scroll}">
<div id="wrapper"> <div id="wrapper">
<div id="header"> <div id="header">
<div id="logo"> <div id="logo">
@@ -183,7 +258,7 @@ Released : 20110306
<div id="page-bgbtm"> <div id="page-bgbtm">
<div id="content"> <div id="content">
<div class="post"> <div class="post">
<div class="entry"> <div class="entry intro">
{intro} {intro}
</div> </div>
</div> </div>
@@ -199,9 +274,10 @@ Released : 20110306
</div> </div>
</body> </body>
</html>""".format( </html>""".format(
page_title=page_title, page_title=self.config["SITE"]["title"],
sub_title=sub_title, sub_title=self.config["SITE"]["sub_title"],
intro=intro, intro=self.config["SITE"]["intro"],
scroll=self.config["SITE"]["scroll"],
posts="\n".join(posts), posts="\n".join(posts),
resource=self.resource_dir, resource=self.resource_dir,
) )
@@ -212,7 +288,7 @@ Released : 20110306
return """ return """
<div class="post"> <div class="post">
<div class="navigation">&nbsp;</div> <div class="navigation">&nbsp;</div>
<div class=center><a href="{image}"> <div class="image_wrapper center"><a href="{image}">
<video class=post_image controls preload="metadata"> <video class=post_image controls preload="metadata">
<source src="{image}" type="video/mp4" > <source src="{image}" type="video/mp4" >
</video> </video>
@@ -227,7 +303,9 @@ Released : 20110306
return """ return """
<div class="post"> <div class="post">
<div class="navigation">&nbsp;</div> <div class="navigation">&nbsp;</div>
<div class=center><a href="{image}"><img loading=lazy class=post_image src="{med_dir}/{image}.jpg"></a></div> <div class="image_wrapper center"><a href="{image}">
<img loading=lazy class=post_image src="{med_dir}/{image}.jpg">
</a></div>
<div class="meta"><div class="name">{title}</div></div> <div class="meta"><div class="name">{title}</div></div>
<div style="clear: both;">&nbsp;</div> <div style="clear: both;">&nbsp;</div>
<div class="entry">{content}</div> <div class="entry">{content}</div>
@@ -253,14 +331,7 @@ Released : 20110306
sys.exit(1) sys.exit(1)
with open("index.html", "wt") as fp: with open("index.html", "wt") as fp:
fp.write( fp.write(self.get_index(self.posts))
self.get_index(
self.config["SITE"]["title"],
self.config["SITE"]["sub_title"],
self.config["SITE"]["intro"],
self.posts,
)
)
def write_resources(self): def write_resources(self):
@@ -270,17 +341,45 @@ Released : 20110306
pass pass
for f in ( for f in (
"style.css", "mirva.css",
"arrow_up.png", "arrow_up.png",
"arrow_down.png", "arrow_down.png",
"banner.jpg",
"mirva.ico", "mirva.ico",
"mirva.js", "mirva.js",
): ):
if os.path.exists(os.path.join(self.resource_dir, f)): if os.path.exists(os.path.join(self.resource_dir, f)):
continue continue
shutil.copy( shutil.copy(
os.path.join(self.resource_src, f), os.path.join(self.resource_dir, f) os.path.join(self.resource_src, f),
os.path.join(self.resource_dir, f),
)
if not os.path.exists(os.path.join(self.resource_dir, "banner.jpg")):
try:
f = random.choice(self.file_list)
outfile = os.path.join(self.resource_dir, "banner.jpg")
res = "1920x"
convargs = [
"convert",
"{}[0]".format(f),
"-background",
"white",
"-flatten",
"-resize",
res,
"+contrast",
"+contrast",
"+contrast",
"-quality",
"85",
outfile,
]
subprocess.run(convargs)
print("Random banner written to {}".format(outfile))
except Exception:
shutil.copy(
os.path.join(self.resource_src, "banner.jpg"),
os.path.join(self.resource_dir, "banner.jpg"),
) )
def write_mediums(self): def write_mediums(self):
@@ -290,14 +389,14 @@ Released : 20110306
except Exception: except Exception:
pass pass
r = self.config["SITE"].get("image_size", 850) r = self.config["SITE"].get("image_size", 1920)
link = str(r).lower() == "link" link = str(r).lower() == "link"
if link: if link:
r = 0 r = 0
res = "{:d}x{:d}>".format(int(r), int(r)) res = "{:d}x{:d}>".format(int(r), int(r))
force = self.options.force force = self.options.force
for f in self.file_list: for f in tqdm(self.file_list):
if self.video_match.match(f): if self.video_match.match(f):
continue continue
outfile = os.path.join(self.medium_dir, "{}.jpg".format(f)) outfile = os.path.join(self.medium_dir, "{}.jpg".format(f))
@@ -309,14 +408,11 @@ Released : 20110306
if not os.path.exists(outfile): if not os.path.exists(outfile):
if link: if link:
# TODO: Get absolute path to f?
os.symlink("../../{}".format(f), outfile) os.symlink("../../{}".format(f), outfile)
continue continue
convargs = [ convargs = [
"convert", "convert",
"-define",
"jpeg:size={}x{}".format(r, r),
"{}[0]".format(f), "{}[0]".format(f),
"-background", "-background",
"white", "white",
@@ -327,8 +423,6 @@ Released : 20110306
"85", "85",
outfile, outfile,
] ]
sys.stdout.write(".")
sys.stdout.flush()
subprocess.run(convargs) subprocess.run(convargs)
sys.stdout.write("\n") sys.stdout.write("\n")
@@ -359,7 +453,6 @@ Released : 20110306
self.config[f]["description"] += p.stdout.decode("utf-8") self.config[f]["description"] += p.stdout.decode("utf-8")
sys.stdout.write("\n") sys.stdout.write("\n")
self.write_config()
def human_size(file_name, precision=1): def human_size(file_name, precision=1):
@@ -379,3 +472,10 @@ def human_size(file_name, precision=1):
size = size / 1024.0 size = size / 1024.0
defPrecision = precision defPrecision = precision
return "%s%.*f%s" % (sign, defPrecision, size, suffixes[suffixIndex]) return "%s%.*f%s" % (sign, defPrecision, size, suffixes[suffixIndex])
class SmartFormatter(HelpFormatter):
def _split_lines(self, help, width):
if help.startswith("smart|"):
return help[6:].splitlines()
return HelpFormatter._split_lines(self, help, width)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -64,7 +64,7 @@ a:hover {
height: 330px; height: 330px;
margin: 0 auto; margin: 0 auto;
padding: 0px; padding: 0px;
background: url(banner.jpg) no-repeat left top; background: url(banner.jpg) no-repeat center center;
background-size: cover; background-size: cover;
} }

View File

@@ -1,82 +1,156 @@
let current=-1; let current = -1;
function r(f){/in/.test(document.readyState)?setTimeout('r('+f+')',9):f()} let currentScrolled = -1;
r(function(){ let scrollTimer = null;
let scrollEventTimer = null;
function r(f) {
/in/.test(document.readyState) ? setTimeout('r(' + f + ')', 9) : f()
}
r(function() {
create_nav(); create_nav();
document.onkeydown = keyboard_entry; document.onkeydown = keyboard_entry;
document.onwheel = scroll_event;
}); });
function create_nav() { function create_nav() {
let navis = document.getElementsByClassName("navigation"); let navis = document.getElementsByClassName("navigation");
for (let i = 0; i<navis.length; i++) { for (let i = 0; i < navis.length; i++) {
if (i==navis.length-1) { if (i == navis.length - 1) {
navis[i].appendChild(create_button("up", navis[0], 0)); navis[i].appendChild(create_button("up", navis[0], 0));
continue; continue;
} }
if (navis[i-1]) { if (navis[i - 1]) {
navis[i].appendChild(create_button("up", navis[i-1], i-1)); navis[i].appendChild(create_button("up", navis[i - 1], i - 1));
} else { } else {
navis[i].appendChild(create_button("up", document.body, 0)); navis[i].appendChild(create_button("up", document.body, 0));
} }
if (navis[i+1]) { if (navis[i + 1]) {
navis[i].appendChild(create_button("down", navis[i+1], i+1)); navis[i].appendChild(create_button("down", navis[i + 1], i + 1));
} }
} }
} }
function create_button(direction, to, next) { function create_button(direction, to, next) {
let container = document.createElement('div'); let container = document.createElement('div');
container.classList.add("float_" + direction); container.classList.add("float_" + direction);
let button = document.createElement('img'); let button = document.createElement('img');
button.src = ".mirva/arrow_" + direction + ".png"; button.src = ".mirva/arrow_" + direction + ".png";
button.classList.add("navigation_button"); button.classList.add("navigation_button");
button.onclick = function(){ button.onclick = function() {
to.parentElement.scrollIntoView({behavior: "smooth"}); to.parentElement.scrollIntoView({
behavior: "smooth"
});
current = next; current = next;
}; };
container.appendChild(button); container.appendChild(button);
return container return container
} }
function is_visible(el) {
function is_visible(el, type) {
let rect = el.getBoundingClientRect(); let rect = el.getBoundingClientRect();
if (type == "fully") {
return rect.top >= 0 && rect.top < window.innerHeight && rect.bottom >= 0 && rect.bottom <= window.innerHeight
} else {
return rect.top < window.innerHeight && rect.bottom >= 0; return rect.top < window.innerHeight && rect.bottom >= 0;
}
function scroll_event(ev) {
let navis = document.getElementsByClassName("navigation");
for (let i = 0; i<navis.length; i++) {
if (is_visible(navis[i])) {
current = i -1;
return
}
} }
} }
function get_position() {
let intro = document.getElementsByClassName("intro");
for (let i = 0; i < intro.length; i++) {
if (is_visible(intro[i], "fully")) {
return {
"current": -1,
"next": 0,
"previous": -1,
"type": "top"
}
}
}
let posts = document.getElementsByClassName("image_wrapper");
for (let i = 0; i < posts.length; i++) {
if (is_visible(posts[i], "fully")) {
return {
"current": i,
"next": i + 1,
"previous": i - 1,
"type": "full"
}
}
}
let partials = [];
for (let i = 0; i < posts.length; i++) {
if (is_visible(posts[i], "partial")) {
partials.push(i)
}
}
if (partials.length == 1) {
return {
"current": partials[0],
"next": partials[0] + 1,
"previous": partials[0] - 1,
"type": "partial single"
}
}
if (partials.length > 1) {
return {
"current": partials[0],
"next": partials[0] + 1,
"previous": partials[0],
"type": "partial multi"
}
}
return {
"current": current,
"next": current + 1,
"previous": current - 1,
"type": "unknown"
}
}
function keyboard_entry(ev) { function keyboard_entry(ev) {
let kC = ev.keyCode; let kC = ev.keyCode;
let ctrlDown = ev.ctrlKey || ev.metaKey; let ctrlDown = ev.ctrlKey || ev.metaKey;
if (ctrlDown) { if (ctrlDown) {
return; return;
} }
if ([32,33,34,35,36,37,38,39,40].indexOf(kC) == -1) { if ([32, 33, 34, 35, 36, 37, 38, 39, 40].indexOf(kC) == -1) {
return return
} }
let navis = document.getElementsByClassName("navigation"); let posts = document.getElementsByClassName("image_wrapper");
let position = get_position();
if (kC == '36') { // home if (kC == '36') { // home
current = -1;
return return
} } else if (kC == '35') { // end
else if (kC == '35') { // end
current = navis.length;
return return
} } else if (kC == '39') { // right
else if (kC == '39') { // right current = position.next;
current += 1; } else if (kC == '37') { // left
} current = position.previous;
else if (kC == '37') { // left
current -= 1;
} else { } else {
setTimeout(scroll_event, 100); return
}
if (current == -1) {
window.scrollTo({ top: 0, behavior: 'smooth' });
return return
} }
current = Math.max(0, current); current = Math.max(0, current);
current = Math.min(navis.length-1, current); current = Math.min(posts.length - 1, current);
navis[current].parentElement.scrollIntoView({behavior: "smooth"}); /* Scroll smooth, if no frequent keypress */
if (scrollTimer) {
posts[current].parentElement.scrollIntoView({
behavior: "auto"
});
clearTimeout(scrollTimer);
} else {
posts[current].parentElement.scrollIntoView({
behavior: "smooth"
});
}
scrollTimer = setTimeout(function() {
scrollTimer = null;
},
200
);
} }

View File

@@ -11,4 +11,5 @@ setup(
"mirva=mirva:main", "mirva=mirva:main",
], ],
}, },
install_requires=["tqdm"],
) )