182 lines
6.2 KiB
Python
182 lines
6.2 KiB
Python
# Melpomene webcomic reader JSON/JS/HTML generator
|
|
# Version 1.0.0 - UNSTABLE
|
|
# CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/
|
|
|
|
import sys
|
|
import re
|
|
import xml.etree.ElementTree as ET
|
|
import argparse
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
HTML_START_CONSTANT = """\
|
|
<!-- Melpomene comic reader -->
|
|
<!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ -->
|
|
<div id="reader-frame">
|
|
<div id="reader-content-frame">
|
|
"""
|
|
|
|
HTML_END_CONSTANT = """\
|
|
<div id="focus-overlay" class="flex-col fill">
|
|
<div class="grow obscured animated"></div>
|
|
<div id="focus-overlay-height" class="flex animated" style="height:100%">
|
|
<div class="grow obscured animated"></div>
|
|
<div id="focus-overlay-width" class="focus animated" style="width:100%"></div>
|
|
<div class="grow obscured animated"></div>
|
|
</div>
|
|
<div class="grow obscured animated"></div>
|
|
</div>
|
|
<div id="nav-controls" class="fill">
|
|
<div class="left" id="nav-left" onclick="moveReader(false,false)"></div>
|
|
<div class="right" id="nav-right" onclick="moveReader(true,false)"></div>
|
|
</div>
|
|
<div id="help-menu">
|
|
<div id="help-controls" style="opacity:1; transform: translate(0,0);">
|
|
<div><div class="key">←</div>/ scroll up / clic : previous</div>
|
|
<div><div class="key">→</div>/ scroll down / clic : next</div>
|
|
<div>-----------------------</div>
|
|
<div><div class="key">F</div>: Toggle fullscreen</div>
|
|
<div><div class="key">P</div>: Toggle progress bar</div>
|
|
<div><div class="key">V</div>: Toggle panel / page viewing mode</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="reader-progress-container">
|
|
<div id="reader-progress-bar"></div>
|
|
<div id="reader-progress-pages"></div>
|
|
</div>
|
|
</div>
|
|
<!-- End of Melpomene comic reader -->
|
|
"""
|
|
|
|
|
|
def extract_zooms(src_folder):
|
|
folder = Path(src_folder)
|
|
|
|
zooms = {}
|
|
|
|
max_width = 0
|
|
max_height = 0
|
|
|
|
idx = 0
|
|
|
|
for svg_path in folder.glob("*.svg"):
|
|
|
|
idx += 1
|
|
|
|
print(f"page {idx} : {svg_path.name}")
|
|
|
|
zooms[idx] = {
|
|
"name": svg_path.stem,
|
|
"zooms": [],
|
|
}
|
|
|
|
tree = ET.parse(svg_path)
|
|
root = tree.getroot()
|
|
|
|
for svg in root.findall('.//{*}svg'):
|
|
if area.get("width") > max_width:
|
|
max_width = area.get("width")
|
|
if area.get("height") > max_width:
|
|
max_width = area.get("height")
|
|
|
|
for area in root.findall('.//{*}rect'):
|
|
zooms[idx]["zooms"].append([
|
|
float(area.get("width")),
|
|
float(area.get("height")),
|
|
float(area.get("x")),
|
|
float(area.get("y")),
|
|
])
|
|
|
|
return zooms, max_width, max_height
|
|
|
|
|
|
def write_json_or_js(zooms, dest_file, is_js):
|
|
|
|
with open(dest_file, "w") as data_file:
|
|
|
|
if is_js:
|
|
data_file.write("PAGES_ZOOMS = ")
|
|
data_file.write("[\n")
|
|
first_coma_skiped = False
|
|
for page_idx in sorted(zooms.keys()):
|
|
for zoom in zooms[page_idx]["zooms"]:
|
|
|
|
if zoom[2] < 0 or zoom[3] < 0 :
|
|
print(f"WARNING: negative pos x / pos y in page {page_idx} for zoom {zoom} (is the rectangle flipped?)")
|
|
|
|
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, pages_width, pages_height, prefix, extention):
|
|
|
|
with open(dest_file, "w") as data_file:
|
|
|
|
data_file.write(HTML_START_CONSTANT)
|
|
|
|
data_file.write(f' <div id="reader-pages" class="animated" data-pages-width="{pages_width}" data-pages-height="{pages_height}" hidden>\n')
|
|
|
|
for page_idx in sorted(zooms.keys()):
|
|
img_url = f"{prefix}{zooms[page_idx]['name']}.{extention}"
|
|
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)
|
|
data_file.write(f' <img loading="lazy" src="{img_url}" data-zooms="{zoom_html_str}"/>\n')
|
|
|
|
data_file.write(f' </div>\n')
|
|
|
|
data_file.write(HTML_END_CONSTANT)
|
|
|
|
def generate_argparse():
|
|
""" Generate Melpomene's generator input parser"""
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description="Helper that can generate JSON / JS / HTML files for Melpomene webcomic reader"
|
|
)
|
|
|
|
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")
|
|
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
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
args = generate_argparse().parse_args()
|
|
|
|
|
|
# Get the final outout name
|
|
output = None
|
|
|
|
if not args.o:
|
|
output = "melpomene_data"
|
|
else:
|
|
output = args.o
|
|
|
|
if args.output_format == "html" and not output.endswith(".html"):
|
|
output += ".html"
|
|
|
|
elif args.output_format == "json" and not output.endswith(".json"):
|
|
output += ".json"
|
|
|
|
elif args.output_format == "js" and not output.endswith(".js"):
|
|
output += ".js"
|
|
|
|
zooms, max_width, max_height = extract_zooms(args.svg_folders)
|
|
|
|
if args.output_format == "html":
|
|
write_html(zooms, output, max_width, max_height, args.p, args.e)
|
|
|
|
elif args.output_format == "json":
|
|
write_json_or_js(zooms, output, False)
|
|
|
|
elif args.output_format == "js":
|
|
write_json_or_js(zooms, output, True) |