feat(tower): twemoji to RGB565A8 bin helper

Add emoji_cp() (pure: emoji → Twemoji codepoint, strips VS16) and
emoji_bin() (download Twemoji, resize 160×160 RGBA, LVGLImage.py →
RGB565A8 .bin in STAGING_ROUTINES). TDD test passes (test_routines.py).
This commit is contained in:
L'électron rare
2026-06-21 09:08:36 +02:00
parent f39bbdebf9
commit f8532ed3f3
2 changed files with 47 additions and 0 deletions
+28
View File
@@ -8,12 +8,14 @@ from PIL import Image
BASE = os.environ.get("LISAEL_BASE", "/home/clems/lisael-content")
STAGING = os.environ.get("LISAEL_STAGING", os.path.join(BASE, "podcasts"))
STAGING_ROUTINES = os.environ.get("LISAEL_STAGING_ROUTINES", os.path.join(BASE, "routines"))
LVGLIMG = os.environ.get("LISAEL_LVGLIMG", os.path.join(BASE, "LVGLImage.py"))
FEEDS_JSON = os.path.join(BASE, "feeds.json")
BOX_JSON = os.path.join(BASE, "box-state.json")
BOX_PORT = int(os.environ.get("LISAEL_BOX_PORT", "8080"))
TTS_URL = os.environ.get("LISAEL_TTS_URL", "http://100.78.6.122:9300/v1/audio/speech")
TTS_VOICE = os.environ.get("LISAEL_TTS_VOICE", "fr")
TWEMOJI = os.environ.get("LISAEL_TWEMOJI", "https://raw.githubusercontent.com/jdecked/twemoji/main/assets/72x72")
UA = {"User-Agent": "Mozilla/5.0"}
NS = {"itunes": "http://www.itunes.com/dtds/podcast-1.0.dtd"}
SUBS = {"": "'", "": '"', "": '"', "": "-",
@@ -116,6 +118,32 @@ def tts_speak(text):
with urllib.request.urlopen(req, timeout=30) as r:
return r.read()
def emoji_cp(emoji):
"""Twemoji filename codepoints, VS16 (U+FE0F) stripped or twemoji 404s."""
return "-".join(f"{ord(c):x}" for c in emoji if c != "")
def emoji_bin(emoji, key):
"""Download the Twemoji for `emoji`, resize 160x160 RGBA, write `<key>.bin`
(RGB565A8, keeps alpha) into STAGING_ROUTINES. Returns '<key>.bin' or ''."""
try:
url = f"{TWEMOJI}/{emoji_cp(emoji)}.png"
raw = urllib.request.urlopen(
urllib.request.Request(url, headers={"User-Agent": "curl/8"}),
timeout=15, context=ssl.create_default_context()).read()
im = Image.open(io.BytesIO(raw)).convert("RGBA").resize((160, 160), Image.LANCZOS)
os.makedirs(STAGING_ROUTINES, exist_ok=True)
with tempfile.TemporaryDirectory() as td:
png = os.path.join(td, f"{key}.png"); im.save(png)
subprocess.run([sys.executable, LVGLIMG, "--ofmt", "BIN", "--cf", "RGB565A8",
"-o", td, png], check=True, capture_output=True)
binp = os.path.join(td, f"{key}.bin")
if os.path.exists(binp):
os.replace(binp, os.path.join(STAGING_ROUTINES, f"{key}.bin"))
return f"{key}.bin"
except Exception as e:
print("emoji_bin fail", key, emoji, e, flush=True)
return ""
def make_cover(img_bytes, key):
"""160x160 RGB565 .bin into STAGING via LVGLImage. Returns '<key>.bin' or ''."""
try:
+19
View File
@@ -0,0 +1,19 @@
import os, tempfile
os.environ["LISAEL_BASE"] = tempfile.mkdtemp(prefix="routines_test_")
os.environ["LISAEL_STAGING"] = os.path.join(os.environ["LISAEL_BASE"], "podcasts")
os.environ["LISAEL_STAGING_ROUTINES"] = os.path.join(os.environ["LISAEL_BASE"], "routines")
import lisael_content as C
def test_emoji_cp_strips_vs16():
# plain emoji: single codepoint
assert C.emoji_cp("\U0001F600") == "1f600" # 😀
# emoji with VS16 (U+FE0F) must be stripped (twemoji 404s otherwise)
assert C.emoji_cp("☀️") == "2600" # ☀️ -> 2600
# multi-codepoint (no VS16): joined by '-'
assert C.emoji_cp("\U0001F468\U0001F4BB").split("-")[0] == "1f468"
if __name__ == "__main__":
for name, fn in sorted(globals().items()):
if name.startswith("test_"):
fn(); print("ok", name)
print("ALL OK")