Files
mirva/mirva/mirva.py
2021-08-31 15:54:48 +03:00

403 lines
12 KiB
Python
Executable File

import configparser
import os
import sys
import re
import shutil
import subprocess
from argparse import ArgumentParser
from mirva import get_version
class Mirva:
def __init__(self):
self.resource_src = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "resources"
)
self.resource_dir = ".mirva"
self.config_file = os.path.join(self.resource_dir, "config.cfg")
self.config_backup = os.path.join(self.resource_dir, "config.cfg.bkp")
self.image_match = re.compile(".*\.jpg$|.*\.jpeg$|.*\.png$|.*\.gif$|.*\.tif$", re.I)
self.video_match = re.compile(".*\.mp4$", re.I)
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.")
def create_config(self):
self.config = configparser.ConfigParser()
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,
}
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
if config_changed:
self.write_config()
def create_posts(self):
self.posts = []
for c in self.config:
if c in self.file_list:
post = self.get_post(
c, self.config[c]["title"], self.config[c]["description"]
)
self.posts.append(post)
def get_config(self):
self.config = configparser.ConfigParser()
self.config.read(self.config_file)
def write_config(self):
print(
"Modified config: {}".format(
os.path.join(self.options.folder, self.config_file)
)
)
if os.path.exists(self.config_file):
with open(self.config_file, "rt") as reader:
with open(self.config_backup, "wt") as writer:
writer.write(reader.read())
with open(self.config_file, "wt") as fp:
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("."):
continue
if not (self.image_match.match(f) or self.video_match.match(f)):
continue
files.append(f)
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.add_argument(
"--config", default=False, action="store_true", help="Write config and exit. Required if more images are added."
)
parser.add_argument(
"--force",
default=False,
action="store_true",
help="Force regeneration of middle sized images",
)
parser.add_argument(
"--exif",
default=False,
action="store_true",
help="Append EXIF information to image descriptions",
)
parser.add_argument(
"--version",
action="version",
version="%(prog)s {version}".format(version=get_version()),
)
parser.add_argument(
"folder",
type=str,
default=".",
nargs="?",
help="Folder for gallery",
)
self.options = parser.parse_args()
def get_index(self, page_title, sub_title, intro, posts):
return """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!--
Design by TEMPLATED
http://templated.co
Released for free under the Creative Commons Attribution License
Name : Green Forest
Description: A two-column, fixed-width design with dark color scheme.
Version : 1.0
Released : 20110306
-->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="generator" content="Mirva" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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" />
<script>
function r(f){{/in/.test(document.readyState)?setTimeout('r('+f+')',9):f()}}
r(function(){{
create_nav();
}});
function create_nav() {{
let navis = document.getElementsByClassName("navigation");
for (let i = 0; i<navis.length; i++) {{
if (i==navis.length-1) {{
navis[i].appendChild(create_button("up", navis[0]));
continue;
}}
if (navis[i-1]) {{
navis[i].appendChild(create_button("up", navis[i-1]));
}} else {{
navis[i].appendChild(create_button("up", document.body));
}}
if (navis[i+1]) {{
navis[i].appendChild(create_button("down", navis[i+1]));
}}
}}
}}
function create_button(direction, to) {{
let container = document.createElement('div');
container.classList.add("float_" + direction);
let button = document.createElement('img');
button.src = "{resource}/arrow_" + direction + ".png";
button.classList.add("navigation_button");
button.onclick = function(){{
to.parentElement.scrollIntoView({{behavior: "smooth"}});
}};
container.appendChild(button);
return container
}}
</script>
</head>
<body>
<div id="wrapper">
<div id="header">
<div id="logo">
<h1 id="page_title">{page_title}</h1>
<p>{sub_title}</a></p>
</div>
</div>
<!-- end #header -->
<div id="page">
<div id="page-bgtop">
<div id="page-bgbtm">
<div id="content">
<div class="post">
<div class="entry">
{intro}
</div>
</div>
{posts}
<div style="clear: both;">&nbsp;</div>
</div>
<!-- end #content -->
<div style="clear: both;">&nbsp;</div>
</div>
</div>
<!-- end #page -->
</div>
</body>
</html>""".format(
page_title=page_title,
sub_title=sub_title,
intro=intro,
posts="\n".join(posts),
resource=self.resource_dir,
)
def get_post(self, image, title, content):
if self.video_match.match(image):
return """
<div class="post">
<div class="navigation">&nbsp;</div>
<div class=center><a href="{image}">
<video class=post_image controls >
<source src="{image}" type="video/mp4" >
</video>
</a></div>
<div class="meta"><div class="name">{title}</div></div>
<div style="clear: both;">&nbsp;</div>
<div class="entry">{content}</div>
</div>""".format(
image=image, title=title, content=content
)
return """
<div class="post">
<div class="navigation">&nbsp;</div>
<div class=center><a href="{image}"><img class=post_image src=".med/{image}.jpg"></a></div>
<div class="meta"><div class="name">{title}</div></div>
<div style="clear: both;">&nbsp;</div>
<div class="entry">{content}</div>
</div>""".format(
image=image, title=title, content=content
)
def is_created_with_mirva(self):
with open("index.html", "rt") as fp:
for line in fp.readlines():
if line.strip() == '<meta name="generator" content="Mirva" />':
return True
return False
def write_index(self):
if os.path.exists("index.html"):
if not self.is_created_with_mirva():
print("index.html exists, and it's not written with Mirva. Not overwriting.")
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,
)
)
def write_resources(self):
try:
os.makedirs(self.resource_dir)
except Exception:
pass
for f in (
"style.css",
"arrow_up.png",
"arrow_down.png",
"banner.jpg",
"mirva.ico",
):
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)
)
def write_mediums(self):
try:
os.makedirs(".med")
except Exception:
pass
r = self.config["SITE"].get("image_size", 850)
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:
if self.video_match.match(f):
continue
outfile = os.path.join(".med", "{}.jpg".format(f))
if force:
try:
os.remove(outfile)
except FileNotFoundError:
pass
if not os.path.exists(outfile):
if link:
os.symlink("../{}".format(f), outfile)
continue
convargs = [
"convert",
"-define",
"jpeg:size={}x{}".format(r, r),
"{}[0]".format(f),
"-background",
"white",
"-flatten",
"-resize",
res,
"-quality",
"85",
outfile,
]
sys.stdout.write(".")
sys.stdout.flush()
subprocess.run(convargs)
sys.stdout.write("\n")
def append_exif(self):
exif_format = '''
<ul>
<li>Date: %[EXIF:DateTimeOriginal]
<li>Camera: %[EXIF:Make] %[EXIF:Model]
<li>Parameters: %[EXIF:ExposureTime]s / F%[EXIF:FNumber] / Focal %[EXIF:FocalLength]
<li>Size: %w x %h / {size}
</ul>
'''
for f in self.config:
if f in self.file_list:
sys.stdout.write(".")
sys.stdout.flush()
file_size = human_size(f)
p = subprocess.run(
['identify','-format',exif_format.format(size=file_size),"{}[0]".format(f)],
capture_output = True
)
self.config[f]['description'] += p.stdout.decode('utf-8')
sys.stdout.write("\n")
self.write_config()
def human_size(file_name, precision=1):
size = os.path.getsize(file_name)
if size == None:
return "nan"
sign = ""
if size < 0:
sign = "-"
size = -size
suffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB"]
suffixIndex = 0
defPrecision = 0
while size > 1024:
suffixIndex += 1
size = size / 1024.0
defPrecision = precision
return "%s%.*f%s" % (sign, defPrecision, size, suffixes[suffixIndex])