major rework of configration parameters.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
__version__ = "20220328.0"
|
||||
__version__ = "20220607.0"
|
||||
|
||||
|
||||
def get_version():
|
||||
|
||||
244
mirva/mirva.py
244
mirva/mirva.py
@@ -4,8 +4,11 @@ import sys
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from argparse import ArgumentParser
|
||||
import random
|
||||
from argparse import ArgumentParser, HelpFormatter
|
||||
|
||||
from mirva import get_version
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
class Mirva:
|
||||
@@ -22,23 +25,48 @@ class Mirva:
|
||||
".*\.jpg$|.*\.jpeg$|.*\.png$|.*\.gif$|.*\.tif$", 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()
|
||||
os.chdir(self.options.folder)
|
||||
self.write_resources()
|
||||
self.file_list = self.get_files()
|
||||
if self.options.config or not os.path.exists(self.config_file):
|
||||
self.create_config()
|
||||
print(
|
||||
"Config created: Exiting without gallery creation. Check config first."
|
||||
)
|
||||
return
|
||||
self.get_config()
|
||||
if self.options.exif:
|
||||
self.append_exif()
|
||||
self.create_posts()
|
||||
self.write_index()
|
||||
self.write_mediums()
|
||||
print("Gallery written.")
|
||||
|
||||
if self.run_commands["config"]:
|
||||
|
||||
self.write_resources()
|
||||
updated = self.create_config()
|
||||
if updated:
|
||||
print(
|
||||
"Config created or updated: Check config contents: {}".format(
|
||||
self.config_file
|
||||
)
|
||||
)
|
||||
|
||||
if self.run_commands["generator"]:
|
||||
self.get_config()
|
||||
self.create_posts()
|
||||
self.write_index()
|
||||
self.write_mediums()
|
||||
print("Gallery written.")
|
||||
|
||||
def create_config(self):
|
||||
|
||||
@@ -46,23 +74,48 @@ class Mirva:
|
||||
self.config.read(self.config_file)
|
||||
config_changed = False
|
||||
if not "SITE" in self.config:
|
||||
self.config["SITE"] = {
|
||||
"title": "",
|
||||
"sub_title": "",
|
||||
"intro": "",
|
||||
"image_size": 850,
|
||||
}
|
||||
self.config["SITE"] = {}
|
||||
config_changed = True
|
||||
|
||||
for key in self.site_defaults:
|
||||
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
|
||||
|
||||
for f in self.file_list:
|
||||
if not f in self.config:
|
||||
title, _ = os.path.splitext(f)
|
||||
title = title.replace("_", " ")
|
||||
self.config[f] = {"title": title, "description": ""}
|
||||
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:
|
||||
self.write_config()
|
||||
return config_changed
|
||||
|
||||
def create_posts(self):
|
||||
|
||||
@@ -93,7 +146,6 @@ class Mirva:
|
||||
self.config.write(fp)
|
||||
|
||||
def get_files(self):
|
||||
image_match = re.compile(".*\.jpg$|.*\.jpeg$|.*\.png$|.*\.gif$|.*\.tif$", re.I)
|
||||
files = []
|
||||
for f in sorted(os.listdir(".")):
|
||||
if f.startswith("."):
|
||||
@@ -104,24 +156,24 @@ class Mirva:
|
||||
return files
|
||||
|
||||
def get_options(self):
|
||||
parser = ArgumentParser(
|
||||
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 = ArgumentParser(prog="mirva", formatter_class=SmartFormatter)
|
||||
parser.add_argument(
|
||||
"--config",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Write config and exit. Required if more images are added.",
|
||||
"--version",
|
||||
action="version",
|
||||
version="%(prog)s {version}".format(version=get_version()),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--force",
|
||||
"--folder",
|
||||
type=str,
|
||||
default=".",
|
||||
help="Folder for gallery. Default current folder.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--purge",
|
||||
default=False,
|
||||
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(
|
||||
"--exif",
|
||||
@@ -130,20 +182,43 @@ class Mirva:
|
||||
help="Append EXIF information to image descriptions",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
action="version",
|
||||
version="%(prog)s {version}".format(version=get_version()),
|
||||
"--set",
|
||||
"-s",
|
||||
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(
|
||||
"folder",
|
||||
type=str,
|
||||
default=".",
|
||||
nargs="?",
|
||||
help="Folder for gallery",
|
||||
"--command",
|
||||
"-c",
|
||||
default="all",
|
||||
action="store",
|
||||
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">
|
||||
<!--
|
||||
@@ -164,10 +239,10 @@ Released : 20110306
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<link rel="shortcut icon" href="{resource}/mirva.ico"/>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<body data-scroll="{scroll}">
|
||||
<div id="wrapper">
|
||||
<div id="header">
|
||||
<div id="logo">
|
||||
@@ -183,7 +258,7 @@ Released : 20110306
|
||||
<div id="page-bgbtm">
|
||||
<div id="content">
|
||||
<div class="post">
|
||||
<div class="entry">
|
||||
<div class="entry intro">
|
||||
{intro}
|
||||
</div>
|
||||
</div>
|
||||
@@ -199,9 +274,10 @@ Released : 20110306
|
||||
</div>
|
||||
</body>
|
||||
</html>""".format(
|
||||
page_title=page_title,
|
||||
sub_title=sub_title,
|
||||
intro=intro,
|
||||
page_title=self.config["SITE"]["title"],
|
||||
sub_title=self.config["SITE"]["sub_title"],
|
||||
intro=self.config["SITE"]["intro"],
|
||||
scroll=self.config["SITE"]["scroll"],
|
||||
posts="\n".join(posts),
|
||||
resource=self.resource_dir,
|
||||
)
|
||||
@@ -212,7 +288,7 @@ Released : 20110306
|
||||
return """
|
||||
<div class="post">
|
||||
<div class="navigation"> </div>
|
||||
<div class=center><a href="{image}">
|
||||
<div class="image_wrapper center"><a href="{image}">
|
||||
<video class=post_image controls preload="metadata">
|
||||
<source src="{image}" type="video/mp4" >
|
||||
</video>
|
||||
@@ -227,7 +303,9 @@ Released : 20110306
|
||||
return """
|
||||
<div class="post">
|
||||
<div class="navigation"> </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 style="clear: both;"> </div>
|
||||
<div class="entry">{content}</div>
|
||||
@@ -253,14 +331,7 @@ Released : 20110306
|
||||
sys.exit(1)
|
||||
|
||||
with open("index.html", "wt") as fp:
|
||||
fp.write(
|
||||
self.get_index(
|
||||
self.config["SITE"]["title"],
|
||||
self.config["SITE"]["sub_title"],
|
||||
self.config["SITE"]["intro"],
|
||||
self.posts,
|
||||
)
|
||||
)
|
||||
fp.write(self.get_index(self.posts))
|
||||
|
||||
def write_resources(self):
|
||||
|
||||
@@ -270,19 +341,47 @@ Released : 20110306
|
||||
pass
|
||||
|
||||
for f in (
|
||||
"style.css",
|
||||
"mirva.css",
|
||||
"arrow_up.png",
|
||||
"arrow_down.png",
|
||||
"banner.jpg",
|
||||
"mirva.ico",
|
||||
"mirva.js",
|
||||
):
|
||||
if os.path.exists(os.path.join(self.resource_dir, f)):
|
||||
continue
|
||||
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):
|
||||
|
||||
try:
|
||||
@@ -290,14 +389,14 @@ Released : 20110306
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
r = self.config["SITE"].get("image_size", 850)
|
||||
r = self.config["SITE"].get("image_size", 1920)
|
||||
link = str(r).lower() == "link"
|
||||
if link:
|
||||
r = 0
|
||||
|
||||
res = "{:d}x{:d}>".format(int(r), int(r))
|
||||
force = self.options.force
|
||||
for f in self.file_list:
|
||||
for f in tqdm(self.file_list):
|
||||
if self.video_match.match(f):
|
||||
continue
|
||||
outfile = os.path.join(self.medium_dir, "{}.jpg".format(f))
|
||||
@@ -309,14 +408,11 @@ Released : 20110306
|
||||
|
||||
if not os.path.exists(outfile):
|
||||
if link:
|
||||
# TODO: Get absolute path to f?
|
||||
os.symlink("../../{}".format(f), outfile)
|
||||
continue
|
||||
|
||||
convargs = [
|
||||
"convert",
|
||||
"-define",
|
||||
"jpeg:size={}x{}".format(r, r),
|
||||
"{}[0]".format(f),
|
||||
"-background",
|
||||
"white",
|
||||
@@ -327,8 +423,6 @@ Released : 20110306
|
||||
"85",
|
||||
outfile,
|
||||
]
|
||||
sys.stdout.write(".")
|
||||
sys.stdout.flush()
|
||||
subprocess.run(convargs)
|
||||
sys.stdout.write("\n")
|
||||
|
||||
@@ -359,7 +453,6 @@ Released : 20110306
|
||||
|
||||
self.config[f]["description"] += p.stdout.decode("utf-8")
|
||||
sys.stdout.write("\n")
|
||||
self.write_config()
|
||||
|
||||
|
||||
def human_size(file_name, precision=1):
|
||||
@@ -379,3 +472,10 @@ def human_size(file_name, precision=1):
|
||||
size = size / 1024.0
|
||||
defPrecision = precision
|
||||
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 |
@@ -64,7 +64,7 @@ a:hover {
|
||||
height: 330px;
|
||||
margin: 0 auto;
|
||||
padding: 0px;
|
||||
background: url(banner.jpg) no-repeat left top;
|
||||
background: url(banner.jpg) no-repeat center center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
@@ -264,4 +264,4 @@ a:hover {
|
||||
|
||||
.float_down {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
@@ -1,82 +1,156 @@
|
||||
let current=-1;
|
||||
function r(f){/in/.test(document.readyState)?setTimeout('r('+f+')',9):f()}
|
||||
r(function(){
|
||||
create_nav();
|
||||
document.onkeydown = keyboard_entry;
|
||||
document.onwheel = scroll_event;
|
||||
let current = -1;
|
||||
let currentScrolled = -1;
|
||||
let scrollTimer = null;
|
||||
let scrollEventTimer = null;
|
||||
|
||||
function r(f) {
|
||||
/in/.test(document.readyState) ? setTimeout('r(' + f + ')', 9) : f()
|
||||
}
|
||||
r(function() {
|
||||
create_nav();
|
||||
document.onkeydown = keyboard_entry;
|
||||
});
|
||||
|
||||
function create_nav() {
|
||||
let navis = document.getElementsByClassName("navigation");
|
||||
for (let i = 0; i<navis.length; i++) {
|
||||
if (i==navis.length-1) {
|
||||
for (let i = 0; i < navis.length; i++) {
|
||||
if (i == navis.length - 1) {
|
||||
navis[i].appendChild(create_button("up", navis[0], 0));
|
||||
continue;
|
||||
}
|
||||
if (navis[i-1]) {
|
||||
navis[i].appendChild(create_button("up", navis[i-1], i-1));
|
||||
if (navis[i - 1]) {
|
||||
navis[i].appendChild(create_button("up", navis[i - 1], i - 1));
|
||||
} else {
|
||||
navis[i].appendChild(create_button("up", document.body, 0));
|
||||
}
|
||||
if (navis[i+1]) {
|
||||
navis[i].appendChild(create_button("down", navis[i+1], i+1));
|
||||
if (navis[i + 1]) {
|
||||
navis[i].appendChild(create_button("down", navis[i + 1], i + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function create_button(direction, to, next) {
|
||||
let container = document.createElement('div');
|
||||
container.classList.add("float_" + direction);
|
||||
let button = document.createElement('img');
|
||||
button.src = ".mirva/arrow_" + direction + ".png";
|
||||
button.src = ".mirva/arrow_" + direction + ".png";
|
||||
button.classList.add("navigation_button");
|
||||
button.onclick = function(){
|
||||
to.parentElement.scrollIntoView({behavior: "smooth"});
|
||||
current = next;
|
||||
button.onclick = function() {
|
||||
to.parentElement.scrollIntoView({
|
||||
behavior: "smooth"
|
||||
});
|
||||
current = next;
|
||||
};
|
||||
container.appendChild(button);
|
||||
return container
|
||||
}
|
||||
function is_visible(el) {
|
||||
let rect = el.getBoundingClientRect();
|
||||
|
||||
function is_visible(el, type) {
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
let kC = ev.keyCode;
|
||||
let ctrlDown = ev.ctrlKey || ev.metaKey;
|
||||
if (ctrlDown) {
|
||||
return;
|
||||
}
|
||||
if ([32,33,34,35,36,37,38,39,40].indexOf(kC) == -1) {
|
||||
return
|
||||
if ([32, 33, 34, 35, 36, 37, 38, 39, 40].indexOf(kC) == -1) {
|
||||
return
|
||||
}
|
||||
let navis = document.getElementsByClassName("navigation");
|
||||
let posts = document.getElementsByClassName("image_wrapper");
|
||||
let position = get_position();
|
||||
if (kC == '36') { // home
|
||||
current = -1;
|
||||
return
|
||||
}
|
||||
else if (kC == '35') { // end
|
||||
current = navis.length;
|
||||
return
|
||||
}
|
||||
else if (kC == '39') { // right
|
||||
current += 1;
|
||||
}
|
||||
else if (kC == '37') { // left
|
||||
current -= 1;
|
||||
return
|
||||
} else if (kC == '35') { // end
|
||||
return
|
||||
} else if (kC == '39') { // right
|
||||
current = position.next;
|
||||
} else if (kC == '37') { // left
|
||||
current = position.previous;
|
||||
} else {
|
||||
setTimeout(scroll_event, 100);
|
||||
return
|
||||
}
|
||||
if (current == -1) {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
return
|
||||
}
|
||||
current = Math.max(0, current);
|
||||
current = Math.min(navis.length-1, current);
|
||||
navis[current].parentElement.scrollIntoView({behavior: "smooth"});
|
||||
}
|
||||
current = Math.min(posts.length - 1, current);
|
||||
/* 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
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user