taiage-spring/scripts/generar_podcast.py

149 lines
5.7 KiB
Python

#!/usr/bin/env python3
"""
Genera src/main/resources/static/podcast.xml escaneando los .mp3 presentes
en static/audios/.
Uso:
python3 scripts/generar_podcast.py
python3 scripts/generar_podcast.py --base-url https://taiage.tatvil.es
El BASE_URL también se puede fijar con la variable de entorno TAIAGE_BASE_URL.
Por defecto: https://taiage.tatvil.es
"""
import argparse
import os
import re
from datetime import datetime, timezone, timedelta
from pathlib import Path
BASE_DIR = Path(__file__).parent.parent
TEMAS_DIR = BASE_DIR / "src" / "main" / "resources" / "temas"
AUDIOS_DIR = BASE_DIR / "src" / "main" / "resources" / "static" / "audios"
OUTPUT = BASE_DIR / "src" / "main" / "resources" / "static" / "podcast.xml"
DEFAULT_BASE_URL = os.environ.get("TAIAGE_BASE_URL", "https://taiage.tatvil.es")
# Fecha base para asignar pubDate incrementales a cada episodio
FECHA_BASE = datetime(2026, 4, 1, 10, 0, 0, tzinfo=timezone.utc)
def titulo_desde_md(path_md: Path) -> str:
"""Lee el primer encabezado ## del .md fuente como título del episodio."""
if not path_md.exists():
return path_md.stem.replace('_', ' ').replace('-', ' ').capitalize()
for linea in path_md.read_text(encoding="utf-8").splitlines():
linea = linea.strip()
if linea.startswith('## '):
return linea[3:].strip()
if linea.startswith('# '):
return linea[2:].strip()
return path_md.stem.replace('_', ' ').replace('-', ' ').capitalize()
def pub_date(delta_days: int) -> str:
d = FECHA_BASE + timedelta(days=delta_days)
return d.strftime("%a, %d %b %Y %H:%M:%S +0000")
def item_xml(titulo: str, url: str, size: int, ep_num: int, day_offset: int) -> str:
titulo_esc = titulo.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
return (
f" <item>\n"
f" <title>{titulo_esc}</title>\n"
f" <description>{titulo_esc}</description>\n"
f" <enclosure url=\"{url}\" length=\"{size}\" type=\"audio/mpeg\"/>\n"
f" <guid>{url}</guid>\n"
f" <pubDate>{pub_date(day_offset)}</pubDate>\n"
f" <itunes:episode>{ep_num}</itunes:episode>\n"
f" <itunes:season>1</itunes:season>\n"
f" <itunes:episodeType>full</itunes:episodeType>\n"
f" </item>"
)
def recopilar_episodios(base_url: str) -> list[str]:
items = []
ep = 1
day = 0
# ── Temas (bloque1..4, orden natural) ──────────────────────────
for bloque_num in range(1, 5):
mp3_dir = AUDIOS_DIR / f"bloque{bloque_num}"
if not mp3_dir.exists():
continue
for mp3 in sorted(mp3_dir.glob("tema*_audio.mp3")):
# Buscar .md fuente para el título
match = re.match(r'tema(\d+)', mp3.stem)
if match:
md_path = TEMAS_DIR / f"bloque{bloque_num}" / f"tema{match.group(1)}_audio.md"
else:
md_path = Path("/dev/null")
titulo = titulo_desde_md(md_path)
url = f"{base_url}/audios/bloque{bloque_num}/{mp3.name}"
size = mp3.stat().st_size
items.append(item_xml(titulo, url, size, ep, day))
ep += 1
day += 1
# ── Leyes ───────────────────────────────────────────────────────
leyes_mp3_dir = AUDIOS_DIR / "leyes"
if leyes_mp3_dir.exists():
# Buscar .md fuente en temas/bloqueX/leyes/
leyes_md: dict[str, Path] = {}
for md in TEMAS_DIR.glob("bloque*/leyes/*.md"):
leyes_md[md.stem] = md
for mp3 in sorted(leyes_mp3_dir.glob("*.mp3")):
if mp3.name.endswith(".temp.mp3"):
continue
md_path = leyes_md.get(mp3.stem, Path("/dev/null"))
titulo = titulo_desde_md(md_path)
url = f"{base_url}/audios/leyes/{mp3.name}"
size = mp3.stat().st_size
items.append(item_xml(titulo, url, size, ep, day))
ep += 1
day += 1
return items
def generar(base_url: str) -> None:
items = recopilar_episodios(base_url)
cover_url = f"{base_url}/images/podcast_cover.jpg"
xml = (
'<?xml version="1.0" encoding="UTF-8"?>\n'
'<rss version="2.0"\n'
' xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"\n'
' xmlns:content="http://purl.org/rss/1.0/modules/content/">\n'
' <channel>\n'
' <title>TAI AGE \u2013 Temario oposici\u00f3n</title>\n'
f' <link>{base_url}</link>\n'
' <description>Audios del temario TAI \u2013 Administraci\u00f3n del Estado. Bloques I al IV.</description>\n'
' <language>es-es</language>\n'
' <itunes:author>TAI AGE</itunes:author>\n'
' <itunes:category text="Education"/>\n'
' <itunes:explicit>false</itunes:explicit>\n'
f' <itunes:image href="{cover_url}"/>\n'
' <image>\n'
f' <url>{cover_url}</url>\n'
' <title>TAI AGE</title>\n'
f' <link>{base_url}</link>\n'
' </image>\n'
)
xml += "\n".join(items) + "\n"
xml += " </channel>\n</rss>\n"
OUTPUT.write_text(xml, encoding="utf-8")
print(f"✅ podcast.xml generado: {len(items)} episodios → {OUTPUT.relative_to(BASE_DIR)}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Genera podcast.xml a partir de los .mp3 presentes")
parser.add_argument("--base-url", default=DEFAULT_BASE_URL,
help="URL pública base del servidor (ej: https://taiage.tatvil.es)")
args = parser.parse_args()
generar(args.base_url)