Temas generados a partir de los hashtags de YouTube
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
"""Genera index.html a partir del feed RSS del canal (se ejecuta cada 15 min en el contenedor)."""
|
"""Genera index.html a partir del feed RSS del canal (se ejecuta cada 15 min en el contenedor)."""
|
||||||
import html
|
import html
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
import unicodedata
|
||||||
|
from collections import Counter
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import urllib.request
|
import urllib.request
|
||||||
@@ -11,7 +14,8 @@ from PIL import Image, ImageStat
|
|||||||
OUT = os.environ.get('SITE_DIR', 'public')
|
OUT = os.environ.get('SITE_DIR', 'public')
|
||||||
FEED = os.environ.get('FEED_URL', 'https://www.youtube.com/feeds/videos.xml?channel_id=UC8gVtzB3Lqm7nnfMhMvXGxQ')
|
FEED = os.environ.get('FEED_URL', 'https://www.youtube.com/feeds/videos.xml?channel_id=UC8gVtzB3Lqm7nnfMhMvXGxQ')
|
||||||
MAX_VIDEOS = 15
|
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']
|
MESES = ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']
|
||||||
YT = 'https://www.youtube.com/@andaquenosabesna'
|
YT = 'https://www.youtube.com/@andaquenosabesna'
|
||||||
TT = 'https://www.tiktok.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,
|
'id': e.find('y:videoId', NS).text,
|
||||||
'title': e.find('a:title', NS).text,
|
'title': e.find('a:title', NS).text,
|
||||||
'date': f'{int(d[2])} {MESES[int(d[1]) - 1]} {d[0]}',
|
'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):
|
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
|
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'<li>{html.escape(t)}</li>' for t in topics(videos))
|
||||||
|
|
||||||
esc = html.escape
|
esc = html.escape
|
||||||
featured, rest = videos[0], videos[1:] # el feed viene del más nuevo al más antiguo
|
featured, rest = videos[0], videos[1:] # el feed viene del más nuevo al más antiguo
|
||||||
|
|
||||||
@@ -157,9 +200,7 @@ page = f'''<!doctype html>
|
|||||||
<section id="programa" class="about wrap reveal">
|
<section id="programa" class="about wrap reveal">
|
||||||
<h2>De qué va</h2>
|
<h2>De qué va</h2>
|
||||||
<p>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.</p>
|
<p>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.</p>
|
||||||
<ul class="chips" aria-label="Temas">
|
<ul class="chips" aria-label="Temas">{chips}</ul>
|
||||||
<li>Educación</li><li>Egipto</li><li>Lengua árabe</li><li>Religiones</li><li>Magia</li><li>Cultura granaína</li><li>Entrevistas</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="seguir" class="follow wrap reveal">
|
<section id="seguir" class="follow wrap reveal">
|
||||||
|
|||||||
Reference in New Issue
Block a user