Files
Clément Saillant 7e69182d7a tools: add gen_covers.py for v2 manifest + covers
Fetches Radio France RSS feeds to build show cover images (160x160
RGB565 LVGL .bin) and writes a v2 manifest.json grouped by show,
preserving episode titles from the existing flat v1 manifest.
Runs with the IDF Python venv (pillow + pypng already installed).
2026-06-14 17:37:28 +02:00

69 lines
2.6 KiB
Python

#!/usr/bin/env python3
# Build v2 manifest (grouped by show) + LVGL .bin covers for the Lisael Box.
import json, os, re, subprocess, sys, urllib.request, xml.etree.ElementTree as ET
from PIL import Image
import io
OUT = "/tmp/lisael_episodes"
LVGLIMG = "/tmp/lisael-box/managed_components/lvgl__lvgl/scripts/LVGLImage.py"
UA = {"User-Agent": "Mozilla/5.0"}
NS = {"itunes": "http://www.itunes.com/dtds/podcast-1.0.dtd"}
# key -> (show title, RSS url)
FEEDS = {
"oli": ("Une histoire et Oli", "https://radiofrance-podcast.net/podcast09/rss_19721.xml"),
"odyssees": ("Les Odyssees", "https://radiofrance-podcast.net/podcast09/rss_20108.xml"),
"salutinfo": ("Salut l'info", "https://radiofrance-podcast.net/podcast09/rss_20689.xml"),
"bestioles": ("Bestioles",
"https://radiofrance-podcast.net/podcast09/35099478-7c72-4f9e-a6de-1b928400e9e5/"
"podcast_a80ecbd5-df3d-4c9d-bee7-4e3d9efc1974.xml"),
}
def fetch(url, t=60):
return urllib.request.urlopen(urllib.request.Request(url, headers=UA), timeout=t).read()
def cover_url(root):
el = root.find(".//channel/itunes:image", NS)
if el is not None and el.get("href"):
return el.get("href")
el = root.find(".//channel/image/url")
return el.text if el is not None else None
titles = {}
v1 = os.path.join(OUT, "manifest.json.v1")
if os.path.exists(v1):
for e in json.load(open(v1, encoding="utf-8")):
titles[e["file"]] = e["title"]
manifest = []
for key, (show, rss) in FEEDS.items():
eps = []
for f in sorted(os.listdir(OUT)):
if re.match(rf"^{key}_\d+\.(m4a|mp3)$", f):
eps.append({"file": f, "title": titles.get(f, f)})
if not eps:
print(f"[{key}] no episodes on disk, skip"); continue
cover_name = ""
try:
root = ET.fromstring(fetch(rss))
cu = cover_url(root)
if cu:
img = Image.open(io.BytesIO(fetch(cu))).convert("RGB").resize((160, 160))
png = os.path.join(OUT, f"{key}.png")
img.save(png)
subprocess.run([sys.executable, LVGLIMG, "--ofmt", "BIN", "--cf", "RGB565",
"-o", OUT, png], check=True)
cover_name = f"{key}.bin"
print(f"[{key}] cover -> {cover_name}")
else:
print(f"[{key}] no itunes:image in feed")
except Exception as ex:
print(f"[{key}] cover failed: {ex}")
manifest.append({"show": show, "cover": cover_name, "episodes": eps})
json.dump(manifest, open(os.path.join(OUT, "manifest.json"), "w", encoding="utf-8"),
ensure_ascii=False, indent=2)
print(f"manifest.json: {len(manifest)} shows")