diff --git a/build.py b/build.py index 5275caf..d019ecc 100644 --- a/build.py +++ b/build.py @@ -1,6 +1,9 @@ """Genera index.html a partir del feed RSS del canal (se ejecuta cada 15 min en el contenedor).""" import html import os +import re +import unicodedata +from collections import Counter import shutil import sys import urllib.request @@ -11,7 +14,8 @@ from PIL import Image, ImageStat OUT = os.environ.get('SITE_DIR', 'public') FEED = os.environ.get('FEED_URL', 'https://www.youtube.com/feeds/videos.xml?channel_id=UC8gVtzB3Lqm7nnfMhMvXGxQ') MAX_VIDEOS = 15 -NS = {'a': 'http://www.w3.org/2005/Atom', 'y': 'http://www.youtube.com/xml/schemas/2015'} +NS = {'a': 'http://www.w3.org/2005/Atom', 'y': 'http://www.youtube.com/xml/schemas/2015', + 'm': 'http://search.yahoo.com/mrss/'} MESES = ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'] YT = 'https://www.youtube.com/@andaquenosabesna' TT = 'https://www.tiktok.com/@andaquenosabesna' @@ -37,6 +41,7 @@ for e in root.findall('a:entry', NS)[:MAX_VIDEOS]: 'id': e.find('y:videoId', NS).text, 'title': e.find('a:title', NS).text, 'date': f'{int(d[2])} {MESES[int(d[1]) - 1]} {d[0]}', + 'desc': e.find('m:group/m:description', NS).text or '', }) def sync_thumbs(vids): @@ -72,6 +77,44 @@ def is_short(vid): return ImageStat.Stat(im.crop((0, 0, int(w * .18), h))).stddev[0] < 25 +# --- Temas: hashtags de las descripciones de YouTube ------------------------------------------- +STOP = {'podcast', 'podcastclips', 'andaquenosabesna', 'andaquenosabesna', 'aqnsn', 'shorts', 'short', 'fyp', + 'viral', 'parati', 'galaxoide', 'youtube', 'video', 'clips'} +LABELS = { # clave normalizada -> texto a mostrar (sinónimos incluidos) + 'egipto': 'Egipto', 'egiptomania': 'Egipto', 'educacion': 'Educación', 'arabe': 'Lengua árabe', + 'lenguas': 'Lenguas', 'religion': 'Religiones', 'religiones': 'Religiones', 'magia': 'Magia', + 'burocracia': 'Burocracia', 'poligamia': 'Poligamia', 'economia': 'Economía', + 'gastronomia': 'Gastronomía', 'comidaegipcia': 'Comida egipcia', 'robotica': 'Robótica', + 'tecnologia': 'Tecnología', 'agricultura': 'Agricultura', 'curiosidades': 'Curiosidades', +} +PINNED = ['Cultura granaína', 'Entrevistas'] # siempre presentes +MAX_TOPICS = 12 + + +def norm(tag): + t = unicodedata.normalize('NFKD', tag.lower()) + return ''.join(c for c in t if not unicodedata.combining(c)) + + +def topics(vids): + count, raw = Counter(), {} + for v in vids: + seen = set() + for tag in re.findall(r'#(\w+)', v['desc']): + k = norm(tag) + if k in STOP or k in seen: + continue + seen.add(k) + label = LABELS.get(k) or (tag[0].upper() + tag[1:]) + raw[label] = label + count[label] += 1 + ranked = [t for t, _ in sorted(count.items(), key=lambda kv: (-kv[1], kv[0]))] + out = [t for t in ranked if t not in PINNED][:max(MAX_TOPICS - len(PINNED), 0)] + return out + PINNED + + +chips = ''.join(f'
  • {html.escape(t)}
  • ' for t in topics(videos)) + esc = html.escape featured, rest = videos[0], videos[1:] # el feed viene del más nuevo al más antiguo @@ -157,9 +200,7 @@ page = f'''

    De qué va

    Un programa de noche, con tertulia y sin prisa. Cada semana entra gente distinta, con su oficio y su manía, y se habla de lo que sale: de cómo se educa a un adolescente, de la vida en las calles de El Cairo, de si el árabe es tan antiguo como dicen, de qué tienen en común las religiones o de cómo se hace un truco de cartas.

    - +