melpomene/zooms_generator.py

200 lines
5.7 KiB
Python
Raw Permalink Normal View History

2023-05-21 21:07:40 +02:00
# Melpomene webcomic reader JSON/JS/HTML generator
2023-05-26 19:32:31 +02:00
# Version 1.0.0_RC1
2023-05-19 18:15:34 +02:00
# CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/
2023-04-15 13:12:49 +02:00
import sys
from argparse import ArgumentParser
from xml.etree import ElementTree
from xml.etree.ElementTree import Element
from typing import Any
2023-04-15 13:12:49 +02:00
from pathlib import Path
2023-05-26 17:23:54 +02:00
HTML_TEMPLATE = Path(__file__).parent / "melpomene.html"
HTML_TO_REPLACE = "<!-- your img tags here, see documentation -->"
def get_val_has_str(elem: Element, attrib: str, filepath: str | Path) -> str:
value = elem.get(attrib)
2023-04-15 13:12:49 +02:00
if value is None:
sys.exit(f"Attribute '{attrib}' is not valid in file {filepath}")
2023-06-25 09:20:38 +02:00
return str(value)
def extract_zooms(src_folder) -> dict[int, Any]:
folder = Path(src_folder)
pages_zooms: dict[int, Any] = {}
2023-06-25 09:20:38 +02:00
idx = 0
2023-04-15 13:12:49 +02:00
for svg_path in folder.glob("*.svg"):
idx += 1
2023-06-25 09:20:38 +02:00
print(f"page {idx} : {svg_path.name}")
2023-06-25 09:20:38 +02:00
# Setting up default values
pages_zooms[idx] = {
"name": svg_path.stem,
2023-05-25 21:31:22 +02:00
"width": 0,
"height": 0,
"zooms": [],
}
2023-06-25 09:20:38 +02:00
tree = ElementTree.parse(svg_path)
root = tree.getroot()
2023-05-25 21:31:22 +02:00
width = get_val_has_str(root, "width", svg_path)
height = get_val_has_str(root, "height", svg_path)
if "." in width:
2023-06-25 09:20:38 +02:00
print(
f"WARNING: file {svg_path} has a floating width, it will be rounded",
file=sys.stderr,
)
pages_zooms[idx]["width"] = round(float(width))
if "." in height:
2023-06-25 09:20:38 +02:00
print(
f"WARNING: file {svg_path} has a floating height, it will be rounded",
file=sys.stderr,
)
pages_zooms[idx]["height"] = round(float(height))
2023-06-25 09:20:38 +02:00
zooms = []
2023-06-25 09:20:38 +02:00
for area in root.findall(".//{*}rect"):
zooms.append(
2023-06-25 09:20:38 +02:00
[
float(get_val_has_str(area, "width", svg_path)),
float(get_val_has_str(area, "height", svg_path)),
float(get_val_has_str(area, "x", svg_path)),
float(get_val_has_str(area, "y", svg_path)),
2023-06-25 09:20:38 +02:00
]
)
pages_zooms[idx]["zooms"] = zooms
return pages_zooms
def write_json_or_js(zooms, dest_file, is_js) -> None:
with open(dest_file, "w", encoding="UTF-8") as data_file:
if is_js:
data_file.write("PAGES_ZOOMS = ")
data_file.write("[\n")
first_coma_skiped = False
2023-04-15 13:12:49 +02:00
for page_idx in sorted(zooms.keys()):
for zoom in zooms[page_idx]["zooms"]:
2023-06-25 09:20:38 +02:00
if zoom[2] < 0 or zoom[3] < 0:
print(
f"WARNING: negative pos x / pos y in page {page_idx} for "
f"zoom {zoom} (is the rectangle flipped?)"
2023-06-25 09:20:38 +02:00
)
if first_coma_skiped:
data_file.write(",\n")
else:
first_coma_skiped = True
data_file.write(f" {[page_idx] + zoom}")
data_file.write("\n]\n")
def write_html(zooms, dest_file, prefix, extention) -> None:
2023-05-26 17:23:54 +02:00
img_tags = ""
for page_idx in sorted(zooms.keys()):
img_url = f"{prefix}{zooms[page_idx]['name']}.{extention}"
2023-06-25 09:20:38 +02:00
zoom_html_data = [
",".join([str(zoom) for zoom in page_zooms])
for page_zooms in zooms[page_idx]["zooms"]
]
zoom_html_str = ";".join(zoom_html_data)
img_tags = (
img_tags
+ " "
+ f'<img loading="lazy" height="{zooms[page_idx]["height"]}" '
+ f'width="{zooms[page_idx]["width"]}" src="{img_url}" '
+ f'data-zooms="{zoom_html_str}"/>\n'
2023-06-25 09:20:38 +02:00
)
2023-05-26 17:23:54 +02:00
img_tags = img_tags.strip()
2023-06-25 09:20:38 +02:00
with open(HTML_TEMPLATE, "r", encoding="UTF-8") as template_file, open(
dest_file, "w", encoding="UTF-8"
) as data_file:
2023-05-26 17:23:54 +02:00
data = template_file.read().replace(HTML_TO_REPLACE, img_tags)
2023-06-25 09:20:38 +02:00
2023-05-26 17:23:54 +02:00
data_file.write(data)
def generate_argparse() -> ArgumentParser:
2023-06-25 09:20:38 +02:00
"""Generate Melpomene's generator input parser"""
parser = ArgumentParser(
description=(
"Helper that can generate JSON / JS / "
"HTML files for Melpomene webcomic reader"
)
)
2023-06-25 09:20:38 +02:00
parser.add_argument(
"output_format",
choices=["html", "json", "js"],
help="The type of output to generate",
)
parser.add_argument("svg_folders", help="Path of the folder containing the SVGs")
2023-06-25 09:20:38 +02:00
parser.add_argument(
"-o", metavar="dest_file", help="Where to write the generator output to"
)
parser.add_argument(
"-p",
default="",
metavar="img_url_prefix",
help="What to prefix the URL of the images when using HTML format.",
)
parser.add_argument(
"-e",
default="png",
metavar="img_ext",
help="What extention to use in the URL of the images when using HTML format.",
)
return parser
2023-04-15 13:12:49 +02:00
def run():
args = generate_argparse().parse_args()
2023-06-25 09:20:38 +02:00
# Get the final outout name
output = None
2023-06-25 09:20:38 +02:00
if not args.o:
output = "melpomene_data"
else:
output = args.o
2023-06-25 09:20:38 +02:00
if args.output_format == "html" and not output.endswith(".html"):
output += ".html"
2023-06-25 09:20:38 +02:00
elif args.output_format == "json" and not output.endswith(".json"):
output += ".json"
2023-06-25 09:20:38 +02:00
elif args.output_format == "js" and not output.endswith(".js"):
output += ".js"
2023-06-25 09:20:38 +02:00
zooms = extract_zooms(args.svg_folders)
if args.output_format == "html":
write_html(zooms, output, args.p, args.e)
2023-06-25 09:20:38 +02:00
elif args.output_format == "json":
write_json_or_js(zooms, output, False)
2023-06-25 09:20:38 +02:00
elif args.output_format == "js":
2023-05-25 21:31:22 +02:00
write_json_or_js(zooms, output, True)
if __name__ == "__main__":
run()