import os, time, email.utils, urllib.parse, html, re, sys, datetime TITLE = os.environ.get("FEED_TITLE", "Cum Town Archive (local)") DESC = os.environ.get("FEED_DESC", "Self-hosted archive feed served from my NAS") BASE = os.environ.get("BASE_URL") LIMIT = int(os.environ.get("MAX_ITEMS","0")) # 0 = onbeperkt ORDER_ASC = False # output-volgorde (maakt in PA meestal niet uit) ONLY_SUBDIR = os.environ.get("ONLY_SUBDIR","").strip() # bv. '1) Cum Town Archive' NUMBERED_PUBDATE = os.environ.get("NUMBERED_PUBDATE","0") in ("1","true","True") if not BASE: raise SystemExit("BASE_URL env var ontbreekt") AUDIO_EXTS = {".mp3",".m4a",".aac",".ogg",".opus",".flac",".wav"} def is_audio(fn): return os.path.splitext(fn)[1].lower() in AUDIO_EXTS def esc(s:str) -> str: return html.escape(s, quote=True) def parse_episode_number(name: str): base = os.path.splitext(os.path.basename(name))[0] m = re.match(r'^\s*(\d+)', base) if m: return int(m.group(1)) m = re.search(r'\b[Ee]p[.\s-]*(\d+)', base) if m: return int(m.group(1)) m = re.search(r'\b[Bb]onus[.\s-]*(\d+)', base) if m: return int(m.group(1)) return None walk_root = "." if ONLY_SUBDIR: walk_root = os.path.join(".", ONLY_SUBDIR) if not os.path.isdir(walk_root): raise SystemExit(f"ONLY_SUBDIR bestaat niet: {ONLY_SUBDIR}") items=[] for root,_,files in os.walk(walk_root): for f in files: if not is_audio(f): continue p=os.path.join(root,f) st=os.stat(p) rel=os.path.relpath(p,".").replace("\\","/") url=BASE.rstrip("/")+"/"+urllib.parse.quote(rel) size=str(st.st_size) original_title=os.path.splitext(os.path.basename(f))[0] epnum=parse_episode_number(f) items.append({ "mtime": st.st_mtime, "original_title": original_title, "url": url, "size": size, "epnum": epnum, "rel": rel, }) # Paddingbreedte bepalen epnums=[it["epnum"] for it in items if it["epnum"] is not None] pad = len(str(max(epnums))) if epnums else 3 # Definitieve titels + pubDate bepalen base_dt = datetime.datetime(2010,1,1, tzinfo=datetime.timezone.utc) # vast begin; puur voor volgorde for it in items: if it["epnum"] is not None: it["title"] = f"{it['epnum']:0{pad}d} — {it['original_title']}" else: it["title"] = it["original_title"] if NUMBERED_PUBDATE and it["epnum"] is not None: # volgorde volgens nummer: lagere nummers = oudere datum dt = base_dt + datetime.timedelta(seconds=it["epnum"]) it["pub"] = email.utils.format_datetime(dt) else: it["pub"] = email.utils.formatdate(it["mtime"], usegmt=True) # sorteren voor output (niet per se nodig, maar netjes) def sort_key(it): # sorteer primair op epnum, secundair op mtime key_primary = (it["epnum"] is None, it["epnum"] if it["epnum"] is not None else it["mtime"]) return key_primary items.sort(key=sort_key, reverse=not ORDER_ASC) if LIMIT>0: items = items[:LIMIT] now=email.utils.formatdate(time.time(), usegmt=True) parts=[f''' {esc(TITLE)} {esc(BASE)} {esc(DESC)} nl-NL {esc(now)} Self-hosted false '''] for it in items: ext=it["url"].split("?")[0].split(".")[-1].lower() mime=("audio/mpeg" if ext=="mp3" else "audio/mp4" if ext in ("m4a","aac") else "audio/ogg" if ext in ("ogg","opus") else "application/octet-stream") guid=urllib.parse.quote(it["url"], safe="") parts.append(" \n") parts.append(f" {esc(it['title'])}\n") if it["epnum"] is not None: parts.append(f" {it['epnum']}\n") parts.append(" full\n") parts.append(f" {esc(it['pub'])}\n") parts.append(f" {esc(guid)}\n") parts.append(f" \n") parts.append(" \n") parts.append(" \n\n") with open("feed.xml","w",encoding="utf-8") as f: f.write("".join(parts)) print(f"Written feed.xml with {len(items)} items (pad width {pad}, numbered_pubdate={NUMBERED_PUBDATE})")