update @ Sun Dec 15 02:53:32 EET 2024

Signed-off-by: Ari Archer <ari@ari.lt>
This commit is contained in:
Arija A. 2024-12-15 02:53:32 +02:00
parent b880ee9a5b
commit bcf213414f
WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS.
GPG key ID: A50D5B4B599AF8A2
3 changed files with 265 additions and 3 deletions

View file

@ -3,6 +3,7 @@
--qb: #ede9d3;
--qf: #e6e0c8;
--lf: #d3ceba;
--ef: #bb6868;
}
pre {
@ -42,3 +43,24 @@ blockquote:before {
#legal {
color: var(--lf);
}
.error {
color: var(--ef);
}
.media {
text-align: center;
background-color: var(--cb);
padding: 1em;
}
.media > audio {
max-width: 600px;
width: 100%;
}
.media > img {
max-width: 400px;
width: 100%;
height: auto;
}

View file

@ -1,2 +1,4 @@
pyfzf
requests
pillow
python-magic

View file

@ -28,6 +28,7 @@ import mistune.inline_parser
import mistune.plugins
import unidecode
import web_mini
from mistune.renderers.html import HTMLRenderer
from readtime import of_markdown as read_time_of_markdown # type: ignore
from readtime.result import Result as MarkdownResult # type: ignore
@ -37,6 +38,19 @@ from readtime.result import Result as MarkdownResult # type: ignore
__version__: typing.Final[int] = 2
GEN: typing.Final[str] = f"ari-web blog generator version {__version__}"
MEDIA_MIME: dict[str, str] = {
"image/jpeg": "jpeg",
"image/png": "png",
"image/gif": "gif",
"image/webp": "webp",
"image/avif": "avif",
"image/svg+xml": "svg",
"audio/mpeg": "mp3",
"audio/wav": "wav",
"audio/ogg": "ogg",
"audio/flac": "flac",
}
OK: typing.Final[int] = 0
ER: typing.Final[int] = 1
@ -415,10 +429,14 @@ STATS_TEMPLATE: typing.Final[str] = (
if NCI:
import http.server
import magic
import pyfzf # type: ignore
from PIL import Image
else:
pyfzf: typing.Any = None
http: typing.Any = None
magic: typing.Any = None
Image: typing.Any = None
class Commands:
@ -656,6 +674,9 @@ def s_to_str(seconds: float) -> str:
# markdown
TITLE_LINKS_RE: typing.Final[str] = r"<#:[^>]+?>"
MEDIA_EMBED_RE: typing.Final[str] = r"<@:[^>]+?>"
MEDIA_INDEX: dict[str, dict[str, typing.Any]] = {}
def parse_inline_titlelink(
@ -676,20 +697,114 @@ def parse_inline_titlelink(
return m.end()
def parse_inline_media_embed(
_: mistune.inline_parser.InlineParser,
m: re.Match[str],
state: mistune.core.InlineState,
) -> int:
text: str = m.group(0)[3:-1]
if text not in MEDIA_INDEX:
state.append_token(
{
"type": "block_div",
"children": [
{
"type": "strong_error",
"raw": f"ERROR: Media '{html_escape(text)}' does not exist.",
}
],
"attrs": {"classes": "media"},
}
)
return m.end()
mdx: dict[str, typing.Any] = MEDIA_INDEX[text]
source: str = f"/media/{text}.{mdx['ext']}"
if mdx["type"] == "image":
child: dict[str, typing.Any] = {
"type": "image",
"raw": source,
"attrs": {
"alt": mdx["alt"],
"type": mdx["mime"],
"width": mdx["width"],
"height": mdx["height"],
},
}
elif mdx["type"] == "audio":
child = {
"type": "audio",
"raw": source,
"attrs": {
"alt": mdx["alt"],
"type": mdx["mime"],
},
}
else:
state.append_token(
{
"type": "block_div",
"children": [
{
"type": "strong_error",
"raw": f"ERROR: Media '{html_escape(text)}' has an unsupported type.",
}
],
"attrs": {"classes": "media"},
}
)
return m.end()
state.append_token(
{
"type": "block_div",
"children": [
child,
{"type": "linebreak"},
{
"type": "emphasis",
"raw": f"{mdx['alt']} | \"{mdx['title']}\" by {mdx['credit']} ({mdx['license']}). Purpose: {html_escape(mdx['purpose'])}.",
},
],
"attrs": {"classes": "media"},
}
)
return m.end()
def titlelink(md: mistune.Markdown) -> None:
md.inline.register("titlelink", TITLE_LINKS_RE, parse_inline_titlelink, before="link") # type: ignore
class BlogRenderer(mistune.HTMLRenderer):
def heading(self, text: str, level: int, **_: typing.Any) -> str:
def media_embed(md: mistune.Markdown) -> None:
md.inline.register("media_embed", MEDIA_EMBED_RE, parse_inline_media_embed, before="link") # type: ignore
class BlogRenderer(HTMLRenderer):
def heading(self, text: str, level: int) -> str:
slug: str = slugify(text, [], 768, 768)
level = max(2, level)
return f'<h{level} id="{slug}" h><a href="#{slug}">#</a> {text}</h{level}>'
def strong_error(self, text: str) -> str:
return f"<strong class=error>{text}</strong>"
def block_div(self, text: str, classes: str):
return f'<div class="{html_escape(classes)}">{text}</div>'
def image(self, text: str, alt: str, type: str, width: int, height: int) -> str:
return f'<img title="{html_escape(alt)}" src="{text}" alt="{html_escape(alt)}" type="{type}" data-width="{width}" data-height="{height}" loading=lazy />'
def audio(self, text: str, alt: str, type: str) -> str:
return f'<audio controls title="{html_escape(alt)}"> <source src="{text}" type="{type}" {alt} /> </audio>'
def markdown(md: str, plugins: list[typing.Any]) -> str:
return mistune.create_markdown(plugins=plugins + [titlelink], renderer=BlogRenderer())(md) # type: ignore
return mistune.create_markdown(plugins=plugins + [titlelink, media_embed], renderer=BlogRenderer())(md) # type: ignore
# edit commands
@ -1499,6 +1614,123 @@ def blog(config: dict[str, typing.Any]) -> int:
return OK
@cmds.new
def media(config: dict[str, typing.Any]) -> int:
"""add media"""
path: str = iinput("media path")
path = os.path.expanduser(path)
if not os.path.isfile(path):
return err(f"file {path!r} is not a file or does not exist")
purpose: str = iinput("media purpose")
title: str = iinput("media title")
license: str = iinput("media license (SPDX)")
credit: str = iinput("media credit")
# MIME stuff
mime: str = magic.from_file(path, mime=True)
if mime not in MEDIA_MIME:
return err(f"mime {mime!r} is not a supported media type")
ext: str = MEDIA_MIME[mime]
# Filename
sha256_hash: typing.Any = hashlib.sha256() # type: ignore
with open(path, "rb") as fp:
for byte_block in iter(lambda: fp.read(4096), b""):
sha256_hash.update(byte_block)
hash_hex: str = sha256_hash.hexdigest()
# Create media dir + media index
os.makedirs("media", exist_ok=True)
if os.path.exists("media/media.json"):
with open("media/media.json", "r") as fp:
index: dict[str, dict[str, typing.Any]] = json.load(fp)
else:
index = {}
# Check if it exists
if hash_hex in index:
return err(f"media pointing to {path!r} already exists")
# Process stuff
filename: str = f"{hash_hex}.{ext}"
fpath: str = f"media/{filename}"
if mime.startswith("image/"):
with Image.open(path) as img:
width, height = img.size
shutil.copy(path, fpath)
if ext in {"jpeg", "png"}:
quality_s: str = iinput("image quality % (1-100)")
try:
quality: int = int(quality_s)
quality = 100 if quality > 100 else quality
except Exception:
quality = 100
img.save(
fpath,
format=ext,
quality=quality,
optimize=True,
)
index[hash_hex] = {
"type": "image",
"width": width,
"height": height,
"alt": iinput("alt text"),
}
elif mime.startswith("audio/"):
shutil.copy(path, fpath)
index[hash_hex] = {
"type": "audio",
"alt": iinput("alt text"),
}
else:
return err(f"unsupported MIME: {mime!r}")
index[hash_hex].update(
{
"purpose": purpose,
"title": title,
"license": license,
"credit": credit,
"ext": ext,
"mime": mime,
}
)
lnew(f"media {hash_hex} created")
# Update media.json
with open("media/media.json", "w") as fp:
json.dump(index, fp, indent=config["indent"])
with open("media/media_json_hash.txt", "w") as fp:
with open("media/media.json", "rb") as fk:
fp.write(hashlib.sha256(fk.read()).hexdigest())
lnew("Updated media.json and media_json_hash.txt")
return OK
def main() -> int:
"""entry / main function"""
@ -1518,6 +1750,12 @@ def main() -> int:
else:
lnew("using the default config")
if os.path.exists("media/media.json"):
with open("media/media.json", "r") as fp:
MEDIA_INDEX.update(json.load(fp))
log("Loaded the media index (media/media.json)")
sort(cfg)
log(f"looking command {sys.argv[1]!r} up")