feat(tower): weekly podcast refresh to staging

This commit is contained in:
Clément Saillant
2026-06-15 00:48:11 +02:00
parent e35058f53d
commit 48e2b6c830
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
"""Weekly refresh: latest Radio France youth episodes -> staging dir that
lisael_server.py pushes to the box. Atomic swap so a failed run keeps the old set."""
import io, json, os, shutil, ssl, subprocess, sys, tempfile, urllib.request
import xml.etree.ElementTree as ET
from PIL import Image
STAGING = os.environ.get("LISAEL_STAGING", "/home/clems/lisael-content/podcasts")
LVGLIMG = os.environ.get("LISAEL_LVGLIMG", "/home/clems/lisael-content/LVGLImage.py")
UA = {"User-Agent": "Mozilla/5.0"}
NS = {"itunes": "http://www.itunes.com/dtds/podcast-1.0.dtd"}
FEEDS = {
"oli": ("Une histoire et Oli", "https://radiofrance-podcast.net/podcast09/rss_19721.xml", 3),
"odyssees": ("Les Odyssees", "https://radiofrance-podcast.net/podcast09/rss_20108.xml", 3),
"salutinfo": ("Salut l'info", "https://radiofrance-podcast.net/podcast09/rss_20689.xml", 2),
"bestioles": ("Bestioles",
"https://radiofrance-podcast.net/podcast09/35099478-7c72-4f9e-a6de-1b928400e9e5/"
"podcast_a80ecbd5-df3d-4c9d-bee7-4e3d9efc1974.xml", 2),
}
SUBS = {"": "'", "": '"', "": '"', "": "-",
"": "-", "": "...", "«": '"', "»": '"'}
def fetch(u, t=180):
return urllib.request.urlopen(urllib.request.Request(u, headers=UA),
timeout=t, context=ssl.create_default_context()).read()
def ext_for(url, mime):
u = url.split("?")[0].lower()
return "m4a" if (u.endswith(".m4a") or "mp4" in (mime or "") or "x-m4a" in (mime or "")) else "mp3"
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
def clean(s):
for a, b in SUBS.items():
s = s.replace(a, b)
return " ".join(s.split()).strip()
def main():
tmp = tempfile.mkdtemp(prefix="lisael_stg_")
manifest = []
for key, (show, rss, n) in FEEDS.items():
try:
root = ET.fromstring(fetch(rss))
except Exception as e:
print("RSS fail", key, e); continue
eps = []
for i, it in enumerate(root.findall(".//item")[:n], 1):
enc = it.find("enclosure")
if enc is None or not enc.get("url"):
continue
url = enc.get("url"); fname = f"{key}_{i:02d}.{ext_for(url, enc.get('type',''))}"
try:
open(os.path.join(tmp, fname), "wb").write(fetch(url))
except Exception as e:
print("dl fail", fname, e); continue
eps.append({"file": fname, "title": clean(it.findtext("title") or f"{show} {i}")})
if not eps:
continue
cover = ""
try:
cu = cover_url(root)
if cu:
im = Image.open(io.BytesIO(fetch(cu))).convert("RGB").resize((160, 160))
png = os.path.join(tmp, f"{key}.png"); im.save(png)
subprocess.run([sys.executable, LVGLIMG, "--ofmt", "BIN", "--cf", "RGB565",
"-o", tmp, png], check=True)
cover = f"{key}.bin"; os.remove(png)
except Exception as e:
print("cover fail", key, e)
manifest.append({"show": show, "cover": cover, "episodes": eps})
json.dump(manifest, open(os.path.join(tmp, "manifest.json"), "w", encoding="utf-8"),
ensure_ascii=False, indent=2)
os.makedirs(os.path.dirname(STAGING), exist_ok=True)
bak = STAGING + ".old"
if os.path.exists(STAGING):
shutil.rmtree(bak, ignore_errors=True); os.rename(STAGING, bak)
os.rename(tmp, STAGING)
shutil.rmtree(bak, ignore_errors=True)
print(f"staging OK: {len(manifest)} shows, {len(os.listdir(STAGING))} files in {STAGING}")
if __name__ == "__main__":
main()