149 lines
5.7 KiB
Python
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('&', '&').replace('<', '<').replace('>', '>')
|
|
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)
|