Merge branch 'master' of bitbucket.org:MoonQ/mirva
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
# Mirva
|
# Mirva
|
||||||
|
|
||||||
A tool to create a web gallery from a single folder of images.
|
A tool to create a web gallery from a single folder of images.
|
||||||
|
Also .mp4 files are included in the gallery
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = "20210821.0"
|
__version__ = "20210831.0"
|
||||||
|
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
|
|||||||
118
mirva/mirva.py
118
mirva/mirva.py
@@ -16,6 +16,9 @@ class Mirva:
|
|||||||
)
|
)
|
||||||
self.resource_dir = ".mirva"
|
self.resource_dir = ".mirva"
|
||||||
self.config_file = os.path.join(self.resource_dir, "config.cfg")
|
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()
|
self.get_options()
|
||||||
os.chdir(self.options.folder)
|
os.chdir(self.options.folder)
|
||||||
self.write_resources()
|
self.write_resources()
|
||||||
@@ -24,8 +27,9 @@ class Mirva:
|
|||||||
self.create_config()
|
self.create_config()
|
||||||
print("Config created: Exiting without gallery creation. Check config first.")
|
print("Config created: Exiting without gallery creation. Check config first.")
|
||||||
return
|
return
|
||||||
self.create_config()
|
|
||||||
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()
|
||||||
@@ -33,11 +37,11 @@ class Mirva:
|
|||||||
|
|
||||||
def create_config(self):
|
def create_config(self):
|
||||||
|
|
||||||
config = configparser.ConfigParser()
|
self.config = configparser.ConfigParser()
|
||||||
config.read(self.config_file)
|
self.config.read(self.config_file)
|
||||||
config_changed = False
|
config_changed = False
|
||||||
if not "SITE" in config:
|
if not "SITE" in self.config:
|
||||||
config["SITE"] = {
|
self.config["SITE"] = {
|
||||||
"title": "",
|
"title": "",
|
||||||
"sub_title": "",
|
"sub_title": "",
|
||||||
"intro": "",
|
"intro": "",
|
||||||
@@ -46,20 +50,14 @@ class Mirva:
|
|||||||
config_changed = True
|
config_changed = True
|
||||||
|
|
||||||
for f in self.file_list:
|
for f in self.file_list:
|
||||||
if not f in config:
|
if not f in self.config:
|
||||||
title, _ = os.path.splitext(f)
|
title, _ = os.path.splitext(f)
|
||||||
title = title.replace("_", " ")
|
title = title.replace("_", " ")
|
||||||
config[f] = {"title": title, "description": ""}
|
self.config[f] = {"title": title, "description": ""}
|
||||||
config_changed = True
|
config_changed = True
|
||||||
|
|
||||||
if config_changed:
|
if config_changed:
|
||||||
print(
|
self.write_config()
|
||||||
"Modified config: {}".format(
|
|
||||||
os.path.join(self.options.folder, self.config_file)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
with open(self.config_file, "wt") as fp:
|
|
||||||
config.write(fp)
|
|
||||||
|
|
||||||
def create_posts(self):
|
def create_posts(self):
|
||||||
|
|
||||||
@@ -75,13 +73,28 @@ class Mirva:
|
|||||||
self.config = configparser.ConfigParser()
|
self.config = configparser.ConfigParser()
|
||||||
self.config.read(self.config_file)
|
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):
|
def get_files(self):
|
||||||
image_match = re.compile(".*\.jpg$|.*\.jpeg$|.*\.png$|.*\.gif$|.*\.tif$", re.I)
|
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("."):
|
||||||
continue
|
continue
|
||||||
if not image_match.match(f):
|
if not (self.image_match.match(f) or self.video_match.match(f)):
|
||||||
continue
|
continue
|
||||||
files.append(f)
|
files.append(f)
|
||||||
return files
|
return files
|
||||||
@@ -95,7 +108,7 @@ class Mirva:
|
|||||||
)
|
)
|
||||||
# parser.add_argument("-v", default=False, action="store_true")
|
# parser.add_argument("-v", default=False, action="store_true")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--config", default=False, action="store_true", help="Write config and exit"
|
"--config", default=False, action="store_true", help="Write config and exit. Required if more images are added."
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--force",
|
"--force",
|
||||||
@@ -103,6 +116,12 @@ class Mirva:
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Force regeneration of middle sized images",
|
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(
|
parser.add_argument(
|
||||||
"--version",
|
"--version",
|
||||||
action="version",
|
action="version",
|
||||||
@@ -218,6 +237,22 @@ function create_button(direction, to) {{
|
|||||||
|
|
||||||
def get_post(self, image, title, content):
|
def get_post(self, image, title, content):
|
||||||
|
|
||||||
|
if self.video_match.match(image):
|
||||||
|
return """
|
||||||
|
<div class="post">
|
||||||
|
<div class="navigation"> </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;"> </div>
|
||||||
|
<div class="entry">{content}</div>
|
||||||
|
</div>""".format(
|
||||||
|
image=image, title=title, content=content
|
||||||
|
)
|
||||||
|
|
||||||
return """
|
return """
|
||||||
<div class="post">
|
<div class="post">
|
||||||
<div class="navigation"> </div>
|
<div class="navigation"> </div>
|
||||||
@@ -287,9 +322,11 @@ function create_button(direction, to) {{
|
|||||||
if link:
|
if link:
|
||||||
r = 0
|
r = 0
|
||||||
|
|
||||||
|
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 self.file_list:
|
||||||
res = "{:d}x{:d}>".format(int(r), int(r))
|
if self.video_match.match(f):
|
||||||
|
continue
|
||||||
outfile = os.path.join(".med", "{}.jpg".format(f))
|
outfile = os.path.join(".med", "{}.jpg".format(f))
|
||||||
if force:
|
if force:
|
||||||
try:
|
try:
|
||||||
@@ -319,4 +356,49 @@ function create_button(direction, to) {{
|
|||||||
]
|
]
|
||||||
sys.stdout.write(".")
|
sys.stdout.write(".")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
subprocess.call(convargs)
|
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])
|
||||||
|
|||||||
@@ -203,11 +203,13 @@ a:hover {
|
|||||||
.post_image {
|
.post_image {
|
||||||
max-height: calc(100vh - 60px);
|
max-height: calc(100vh - 60px);
|
||||||
max-width: calc(100vw - 128px);
|
max-width: calc(100vw - 128px);
|
||||||
|
display: inline;
|
||||||
}
|
}
|
||||||
@media only screen and (max-width: 800px) {
|
@media only screen and (max-width: 800px) {
|
||||||
.post_image {
|
.post_image {
|
||||||
max-height: calc(100vh - 2px);
|
max-height: calc(100vh - 2px);
|
||||||
max-width: calc(100vw - 2px);
|
max-width: calc(100vw - 2px);
|
||||||
|
display: inline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Footer */
|
/* Footer */
|
||||||
|
|||||||
Reference in New Issue
Block a user