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:
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
Reference in New Issue
Block a user