Radios de Vigo: Vigo FM dial and UK/US/Portugal news radio for Kodi

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
XoséandClaude Opus 5.5 committed 2026-11-12 22:19:10 +01:00
commit 927195e28d
60 files changed
+488

No files matched your search

+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""Writes resources/radio.m3u from tools/stations.txt and downloads the logos
that are missing from resources/logos (512 px PNG, needs macOS sips).
Run with: python3 -I tools/build.py [--logos] (--logos downloads every logo again)
"""
import json
import os
import re
import subprocess
import sys
import unicodedata
import urllib.parse
import urllib.request
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
LOGOS = os.path.join(ROOT, 'resources', 'logos')
UA = {'User-Agent': 'Mozilla/5.0'}
def get(url):
return urllib.request.urlopen(urllib.request.Request(url, headers=UA), timeout=20).read()
def channel_id(name):
name = unicodedata.normalize('NFKD', name.replace('+', ' mas')).encode('ascii', 'ignore').decode()
return re.sub(r'[^a-z0-9]+', '-', name.lower()).strip('-')
def stations():
with open(os.path.join(ROOT, 'tools', 'stations.txt'), encoding='utf-8') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#'):
yield line.split('|', 4)
def logo_url(source):
if source.startswith('rb:'):
query = urllib.parse.urlencode({'name': source[3:], 'limit': 5, 'order': 'clickcount', 'reverse': 'true'})
for s in json.loads(get('https://de1.api.radio-browser.info/json/stations/search?' + query)):
if s['favicon']:
return s['favicon']
return None
country, slug = source.split('.', 1)
page = get('https://onlineradiobox.com/{}/{}/'.format(country, slug)).decode('utf-8', 'replace')
m = re.search(r'og:image" content="https://cdn\.onlineradiobox\.com/img/fblogo/([^"]+)"', page)
return 'https://cdn.onlineradiobox.com/img/l/' + m.group(1) if m else None # the logo, not the share banner
def fetch_logo(source, dest):
url = logo_url(source)
if not url:
return False
tmp = dest + '.tmp'
with open(tmp, 'wb') as f:
f.write(get(url))
try:
subprocess.run(['sips', '-s', 'format', 'png', '-Z', '512', tmp, '--out', dest], check=True, capture_output=True)
finally:
os.remove(tmp)
return True
def main():
refresh = '--logos' in sys.argv
os.makedirs(LOGOS, exist_ok=True)
lines, used = ['#EXTM3U'], set()
for chno, name, groups, source, url in stations():
cid = channel_id(name)
logo = cid + '.png'
path = os.path.join(LOGOS, logo)
if source != '-' and (refresh or not os.path.exists(path)):
try:
if not fetch_logo(source, path):
print('no logo found:', name)
except Exception as e:
print('logo failed:', name, e)
attrs = 'tvg-id="{}" tvg-chno="{}" radio="true" group-title="{}"'.format(cid, chno, groups)
if os.path.exists(path):
attrs += ' tvg-logo="{}"'.format(logo)
used.add(logo)
else:
print('no logo:', name)
lines += ['#EXTINF:-1 {},{}'.format(attrs, name), url]
for f in sorted(set(os.listdir(LOGOS)) - used):
print('unused logo removed:', f)
os.remove(os.path.join(LOGOS, f))
with open(os.path.join(ROOT, 'resources', 'radio.m3u'), 'w', encoding='utf-8') as f:
f.write('\n'.join(lines) + '\n')
print(len(lines) // 2, 'stations')
if __name__ == '__main__':
main()
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env python3
"""Tests every stream in tools/stations.txt with ffprobe and lists the ones
that don't play. Exit status 1 if any failed.
Run with: python3 -I tools/check.py
"""
import os
import subprocess
import sys
from concurrent.futures import ThreadPoolExecutor
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from build import stations # noqa: E402
def probe(station):
chno, name, _, _, url = station
cmd = ['ffprobe', '-v', 'error', '-rw_timeout', '15000000', '-user_agent', 'Kodi/21.2',
'-show_entries', 'stream=codec_name,bit_rate', '-of', 'csv=p=0', url]
try:
r = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
out = (r.stdout + r.stderr).strip().splitlines()
except subprocess.TimeoutExpired:
out = ['timeout']
ok = bool(out) and out[0].split(',')[0] in ('aac', 'mp3', 'vorbis', 'opus', 'flac')
return ok, '{:>6} {:<28} {}'.format(chno, name, out[0] if out else 'no output')
def main():
with ThreadPoolExecutor(16) as pool:
results = list(pool.map(probe, stations()))
failed = [line for ok, line in results if not ok]
for ok, line in results:
print(('ok ' if ok else 'FAIL ') + line)
print('{} of {} streams play'.format(len(results) - len(failed), len(results)))
sys.exit(1 if failed else 0)
if __name__ == '__main__':
main()
+57
View File
@@ -0,0 +1,57 @@
# Channel list for service.radios.vigo. One station per line:
# channel number|name|groups (; between them)|logo source|stream URL
# Vigo stations use their FM frequency as channel number. Logo source is an
# onlineradiobox.com id (cc.slug), "rb:<name>" for radio-browser.info, or "-"
# to keep the logo already in resources/logos. After editing run
# tools/check.py (tests every stream) and tools/build.py (writes
# resources/radio.m3u and missing logos), then raise the version in addon.xml.
88.1|Radio 8|Vigo|es.turadio|https://relay.stream.enacast-cloud.com:30271/radio8HD.mp3
88.3|RNE Radio Clásica|Vigo|-|https://dispatcher.rndfnk.com/crtve/rnerc/main/mp3/high
88.7|Europa FM|Vigo|es.europa|https://atres-live.europafm.com/live/europafm/master.m3u8
89.6|Antena 2|Vigo|pt.rtpantena2|https://streaming-live.rtp.pt/liveradio/antena280a/playlist.m3u8
89.8|Ushuaïa Radio|Vigo|es.ushuaia|https://s4.radio.co/sc6f29e098/listen
90.1|RNE Radio Nacional Galicia|Vigo|es.rneradionacional|https://dispatcher.rndfnk.com/crtve/rne1/gal/mp3/high
90.5|Hit FM|Vigo|es.hitfm899|https://bbhitfm.kissfmradio.cires21.com/bbhitfm.mp3
90.8|Radio Galega Música|Vigo|es.galegamusica|https://crtvg-radiogalega-musica-hls.flumotion.cloud/playlist.m3u8
91.1|Radio Líder|Vigo|es.lider|http://server9.20comunicacion.com:8200/stream
91.4|Cadena Dial|Vigo|es.cadenadial|https://playerservices.streamtheworld.com/api/livestream-redirect/CADENADIAL.mp3
92.7|LOS40 Dance|Vigo|es.maximafm|https://playerservices.streamtheworld.com/api/livestream-redirect/LOS40_DANCE.mp3
93.7|SER+ Radio Vigo|Vigo|es.cadenaser|https://playerservices.streamtheworld.com/api/livestream-redirect/SER_ASO_MAS_VIGO.mp3
94.0|Kiss FM|Vigo|es.kissfm1027|https://kissfm.kissfmradio.cires21.com/kissfm.mp3
94.6|RNE Radio 3|Vigo|es.rneradio3|https://dispatcher.rndfnk.com/crtve/rner3/main/mp3/high
94.8|Flow Radio|Vigo|es.flow|https://flow.urbanrevolution.es:8445/flow
95.1|Radio Galega|Vigo|es.galega|https://crtvg-radiogalega-hls.flumotion.cloud/playlist.m3u8
95.4|RFM|Vigo|pt.rfmlisbon|https://playerservices.streamtheworld.com/api/livestream-redirect/RFMAAC.aac
96.0|RNE Radio 5 Galicia|Vigo|es.rner5|https://dispatcher.rndfnk.com/crtve/rne5/vig/mp3/high
96.5|Cadena 100|Vigo|es.cadena100|https://cadena100-streamers-mp3.flumotion.com/cope/cadena100.mp3
98.2|Antena 1|Vigo;Noticias · Portugal|pt.rtpantena1|https://streaming-live.rtp.pt/liveradio/antena180a/playlist.m3u8
98.3|Radio Marca Vigo|Vigo|es.marca|https://streaming.enacast.com/radiomarcavigoHD.mp3
98.7|SER Radio Pontevedra|Vigo|es.cadenaser|https://playerservices.streamtheworld.com/api/livestream-redirect/SER_ASO_PONTEVEDRA.mp3
99.0|Rádio Comercial|Vigo|pt.comercial|https://stream-icy.bauermedia.pt/comercial.mp3
99.1|motiva Vigo|Vigo|es.motivavigo|https://stream.guadigo.es/listen/motiva/motivavigo.mp3
99.4|LOS40 Vigo|Vigo|es.los40vigo|https://playerservices.streamtheworld.com/api/livestream-redirect/LOS40_ASO_VIGO.mp3
99.7|Informa Radio|Vigo|es.informa|https://24x7.red:42045/informa
100.6|SER Radio Vigo|Vigo|es.radiovigo|https://playerservices.streamtheworld.com/api/livestream-redirect/SER_ASO_VIGO.mp3
101.2|LOS40 Classic|Vigo|es.m80|https://playerservices.streamtheworld.com/api/livestream-redirect/LOS40_CLASSIC.mp3
101.9|esRadio Galicia|Vigo|-|https://libertaddigital-radio-live2.flumotion.com/libertaddigital/ld-live2-low.mp3
103.6|Onda Cero Pontevedra|Vigo|es.ondacero1036|https://radio-atres-live.ondacero.es/api/livestream-redirect/OC_PONTEVEDRAAAC.m3u8
103.8|Radio Voz Vigo|Vigo|es.radiovoz|https://live.radiovoz.es/mp3/stream_vigo.mp3
104.0|Antena 3|Vigo|pt.rtpantena3|https://streaming-live.rtp.pt/liveradio/antena380a/playlist.m3u8
104.9|LOS40 Urban|Vigo|es.los40|https://playerservices.streamtheworld.com/api/livestream-redirect/LOS40_URBAN.mp3
105.4|Onda Cero Vigo|Vigo|es.ondacero1054|https://radio-atres-live.ondacero.es/api/livestream-redirect/OC_VIGOAAC.m3u8
105.8|Inolvidable FM|Vigo|es.inolvidable|https://uk1.streamingpulse.com/ssl/inolvidablefm
106.1|Radio Piratona|Vigo|es.piratona|https://zeppelin.streampunk.cc/_stream/radiopiratona.ogg
106.7|Rock FM|Vigo|es.rockfm|https://rockfm-cope-rrcast.flumotion.com/cope/rockfm-low.mp3
107.7|Si Radio|Vigo|es.siradio|http://82.223.29.65:8080/vcbm_siradio
201|BBC Radio 4|Noticias · Reino Unido|uk.bbcradio4|http://as-hls-ww-live.akamaized.net/pool_55057080/live/ww/bbc_radio_fourfm/bbc_radio_fourfm.isml/bbc_radio_fourfm-audio%3d128000.norewind.m3u8
202|BBC World Service|Noticias · Reino Unido|uk.bbcworld|http://as-hls-ww-live.akamaized.net/pool_87948813/live/ww/bbc_world_service/bbc_world_service.isml/bbc_world_service-audio%3d128000.norewind.m3u8
203|BBC Radio 5 Live|Noticias · Reino Unido|uk.bbcradio5|http://as-hls-ww-live.akamaized.net/pool_89021708/live/ww/bbc_radio_five_live/bbc_radio_five_live.isml/bbc_radio_five_live-audio%3d128000.norewind.m3u8
204|Times Radio|Noticias · Reino Unido|uk.times|https://timesradio.wireless.radio/stream
205|LBC News|Noticias · Reino Unido|uk.lbcnews|https://media-ssl.musicradio.com/LBCNewsUK
301|NPR|Noticias · EUA|us.npr|https://npr-ice.streamguys1.com/live.mp3
302|WNYC New York|Noticias · EUA|us.wnycfm|https://fm939.wnyc.org/wnycfm-web
303|WBUR Boston|Noticias · EUA|us.wbur|https://icecast-stream.wbur.org/wbur_www
304|KQED San Francisco|Noticias · EUA|us.kqedfm|https://hls.kqed.org/hls/kqed_app/playlist.m3u8
305|Bloomberg Radio|Noticias · EUA|us.wbbr|https://playerservices.streamtheworld.com/api/livestream-redirect/WBBRAMAAC.aac
401|TSF Rádio Notícias|Noticias · Portugal|pt.tsfnoticias|https://directo.tsf.pt/tsfdirecto.mp3
402|Rádio Observador|Noticias · Portugal|pt.observador|https://playerservices.streamtheworld.com/api/livestream-redirect/OBSERVADORAAC.aac