238 lines
8.6 KiB
Python
238 lines
8.6 KiB
Python
from sys import argv
|
|
from subprocess import Popen, PIPE, run, call
|
|
from PIL import Image
|
|
from jinja2 import Environment, FileSystemLoader
|
|
from markdown import markdown
|
|
from glob import glob
|
|
from json import dump, dumps, load
|
|
from os import path
|
|
from feedgen.feed import FeedGenerator
|
|
import re
|
|
import datetime
|
|
|
|
numbers_re = re.compile("[0-9]+")
|
|
name_from_url_re = re.compile(r"https:\/\/helixnebula.space/(.*)/")
|
|
md_links_re = re.compile(r"\[([^\]]*)\]\([^\)]*\)")
|
|
out_dir = "/var/www/html/"
|
|
gemini_out_dir = "/var/gemini/"
|
|
base_url = "https://helixnebula.space/"
|
|
md_dir = "/var/stories/"
|
|
template_environment = Environment(
|
|
loader=FileSystemLoader("templates/"))
|
|
|
|
with open("metadata.json", "r") as f:
|
|
metadata = load(f)
|
|
|
|
story_names = [
|
|
path.basename(post).split('/')[-1][:-3] for post in glob(md_dir + "*")
|
|
]
|
|
story_names.sort()
|
|
|
|
for placename in metadata.keys():
|
|
read_path = f"{out_dir}{placename}/"
|
|
photos = [
|
|
path.basename(photo).split('/')[-1] for photo in
|
|
glob(read_path + "*")
|
|
]
|
|
try:
|
|
photos.remove("index.html")
|
|
except ValueError:
|
|
pass
|
|
metadata[placename]["count"] = len(photos)
|
|
metadata[placename]["photos"] = photos
|
|
metadata[placename]["photos"].sort(key=lambda path: int(numbers_re.search(path).group(0)))
|
|
|
|
with open(f"{out_dir}photos.json", "w") as f:
|
|
dump(metadata, f)
|
|
|
|
def header_from_code(code):
|
|
path = f"{md_dir}{code}.md"
|
|
with open(path, "r") as f:
|
|
md = f.read()
|
|
header = ""
|
|
for char in md[2:]:
|
|
if char == "\n":
|
|
break
|
|
header += char
|
|
return header
|
|
|
|
def covers(overwrite=False):
|
|
print("running covers")
|
|
for placename, info in metadata.items():
|
|
read_path = f"{out_dir}{placename}/{info['cover']}"
|
|
write_path = f"{out_dir}cover/cover_{info['cover']}"
|
|
if not overwrite and path.exists(write_path):
|
|
continue
|
|
command = ["convert", read_path, "-strip", "-interlace", "Plane", "-gaussian-blur", "0.05",
|
|
"-auto-orient", "-resize", "700x525>", "-quality", "80%", write_path]
|
|
run(command)
|
|
|
|
def thumbnails(overwrite=False):
|
|
print("running thumbnails")
|
|
for placename in metadata.keys():
|
|
for photo in metadata[placename]["photos"]:
|
|
read_path = f"{out_dir}{placename}/{photo}"
|
|
write_path = f"{out_dir}thumbnail/thumbnail_{photo}"
|
|
if not overwrite and path.exists(write_path):
|
|
continue
|
|
command = ["convert", read_path, "-strip", "-interlace", "Plane", "-gaussian-blur", "0.05",
|
|
"-auto-orient", "-resize", "300x200>", "-quality", "65%", write_path]
|
|
run(command)
|
|
|
|
def compressed(overwrite=False):
|
|
print("running compressed")
|
|
for placename in metadata.keys():
|
|
for photo in metadata[placename]["photos"]:
|
|
read_path = f"{out_dir}{placename}/{photo}"
|
|
write_path = f"{out_dir}compressed/compressed_{photo}"
|
|
if not overwrite and path.exists(write_path):
|
|
continue
|
|
command = ["convert", read_path, "-auto-orient", "-strip", "-resize", "1200>", "-quality", "90%", write_path]
|
|
run(command)
|
|
|
|
def render_index():
|
|
template = template_environment.get_template("main")
|
|
data = {key: value for key, value in sorted(metadata.items(), key=lambda item: item[1]['title'])}
|
|
photo_counts = {placename: metadata[placename]["count"] for placename in metadata.keys()}
|
|
total_count = 0
|
|
posts = [
|
|
path.basename(post).split('/')[-1][:-3] for post in
|
|
glob(md_dir + "*")
|
|
]
|
|
posts.sort()
|
|
for _, info in metadata.items():
|
|
total_count = info["count"] + total_count
|
|
with open(out_dir + "index.html", "w") as f:
|
|
f.write(template.render({
|
|
"metadata": data,
|
|
"album_count": len(metadata.keys()),
|
|
"photo_counts": photo_counts,
|
|
"total_count": total_count,
|
|
"posts": posts
|
|
}))
|
|
|
|
def render_places():
|
|
template = template_environment.get_template("place")
|
|
for placename, info in metadata.items():
|
|
working_path = out_dir + placename
|
|
photos = metadata[placename]["photos"]
|
|
photos_json = dumps(photos)
|
|
count = len(photos)
|
|
widths = []
|
|
heights = []
|
|
for photo in photos:
|
|
photo_path = out_dir + "thumbnail/thumbnail_" + photo
|
|
img = Image.open(photo_path)
|
|
width, height = img.size
|
|
widths.append(width)
|
|
heights.append(height)
|
|
photo_specs = zip(widths, heights, photos)
|
|
try:
|
|
with open(md_dir + placename + ".md", "r") as f:
|
|
md = markdown(f.read())
|
|
except FileNotFoundError:
|
|
md = "<h1>No story (yet)</h1>"
|
|
with open(working_path + "/index.html", "w") as f:
|
|
f.write(template.render({
|
|
"count": count,
|
|
"placename": placename,
|
|
"cover": info["cover"],
|
|
"info": info,
|
|
"markdown": md,
|
|
"photos": photo_specs,
|
|
"photos_json": photos_json
|
|
}))
|
|
|
|
def sub_http_local_urls(match):
|
|
name = match.group(1)
|
|
if name in story_names:
|
|
return name + ".gmi"
|
|
else:
|
|
return match.group(0)
|
|
|
|
def render_gemini_index():
|
|
template = template_environment.get_template("gemini_main")
|
|
with open(gemini_out_dir + "index.gmi", "w") as f:
|
|
f.write(template.render({
|
|
"post_info": {post_name: metadata[post_name]["title"] for post_name in story_names},
|
|
"post_states": {post_name: metadata[post_name]["state"] for post_name in story_names}
|
|
}))
|
|
|
|
def render_gemini_places():
|
|
post_paths = glob(md_dir + "*")
|
|
template = template_environment.get_template("gemini_place")
|
|
for post_path in post_paths:
|
|
post_name = path.basename(post_path).split('/')[-1][:-3]
|
|
with open(post_path, "r") as f:
|
|
content = f.read()
|
|
process = Popen(["gemgen"], stdout=PIPE, stdin=PIPE, stderr=PIPE, text=True)
|
|
gemtext = process.communicate(input=content)[0]
|
|
gemtext_with_local_urls = name_from_url_re.sub(sub_http_local_urls, gemtext)
|
|
with open(gemini_out_dir + post_name + ".gmi", "w") as f:
|
|
f.write(template.render({
|
|
"post_code": post_name,
|
|
"post_body": gemtext_with_local_urls,
|
|
"post_title": metadata[post_name]["title"],
|
|
"state": metadata[post_name]["state"]
|
|
}))
|
|
|
|
def md_from_code(code):
|
|
with open(f"{md_dir}{code}.md", "r") as f:
|
|
return markdown(f.read())
|
|
|
|
def path_to_story_name(story_path):
|
|
return path.basename(story_path).split('/')[-1][:-3]
|
|
|
|
def render_atom():
|
|
posts_sorted = story_names.copy()
|
|
posts_sorted.sort(key=lambda i: metadata[i]["story_time"])
|
|
feed = FeedGenerator()
|
|
feed.id("helixnebula.space")
|
|
feed.title("helixnebula.space")
|
|
feed.updated(datetime.datetime.fromtimestamp(metadata[posts_sorted[-1]]["story_time"], datetime.timezone.utc))
|
|
feed.author({"name":"~nebula","email":"nebula@tilde.town"})
|
|
feed.link(href="https://helixnebula.space/", rel="alternate")
|
|
feed.link(href="https://helixnebula.space/feed.atom", rel="self")
|
|
feed.subtitle("Exploring America the Beautiful")
|
|
feed.icon("https://helixnebula.space/favicon.ico")
|
|
feed.language("en")
|
|
for post_code in posts_sorted:
|
|
post_metadata = metadata[post_code]
|
|
header = header_from_code(post_code)
|
|
title = f"{post_metadata['title']}, {post_metadata['state']}: {header}"
|
|
entry = feed.add_entry()
|
|
entry.id(post_code)
|
|
entry.title(title)
|
|
entry.updated(datetime.datetime.fromtimestamp(metadata[post_code]["story_time"], datetime.timezone.utc))
|
|
entry.link(href=f"https://helixnebula.space/{post_code}/")
|
|
entry.summary(header)
|
|
entry.description(header)
|
|
entry.content(md_from_code(post_code), type="html")
|
|
feed.atom_file(out_dir + "feed_html.atom")
|
|
template = template_environment.get_template("atom_page")
|
|
with open(f"{out_dir}feeds.html", "w") as f:
|
|
f.write(template.render({
|
|
"last_post_code": posts_sorted[-1],
|
|
"metadata": metadata
|
|
}))
|
|
|
|
|
|
def copy_files():
|
|
call(f"cp js/* {out_dir}js/", shell=True)
|
|
call(f"cp style.css {out_dir}style.css", shell=True)
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
overwrite = "overwrite" in argv
|
|
covers(overwrite=overwrite)
|
|
thumbnails(overwrite=overwrite)
|
|
compressed(overwrite=overwrite)
|
|
except KeyboardInterrupt:
|
|
exit()
|
|
render_index()
|
|
render_places()
|
|
copy_files()
|
|
render_gemini_index()
|
|
render_gemini_places()
|
|
render_atom()
|