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

+2
View File
@@ -0,0 +1,2 @@
tools export-ignore
README.md export-ignore
+2
View File
@@ -0,0 +1,2 @@
__pycache__/
*.pyc
+38
View File
@@ -0,0 +1,38 @@
# Radios de Vigo (service.radios.vigo)
Kodi add-on that adds the radio stations on Vigo's FM dial and news radio from
the UK, US and Portugal to Kodi's **Radio** section, with logos and groups:
| Group | Channel numbers |
|---|---|
| Vigo (Spanish and the Portuguese ones from Valença) | the FM frequency, e.g. 100.6 = SER Radio Vigo |
| Noticias · Reino Unido | 201–205 (BBC Radio 4, World Service, 5 Live, Times Radio, LBC News) |
| Noticias · EUA | 301–305 (NPR, WNYC, WBUR, KQED, Bloomberg Radio) |
| Noticias · Portugal | 401–402 (TSF, Observador) + Antena 1 (98.2) |
Religious stations (Radio María, Radio Vaticano, La Voz de Fátima, COPE, Rádio
Renascença, …) are left out on purpose.
## How it works
`service.py` runs when Kodi starts and after each update. It writes an
IPTV Simple instance (`instance-settings-95.xml`, or the next free number)
that reads `resources/radio.m3u` and `resources/logos/` from the add-on
folder, and restarts IPTV Simple (only when nothing is playing) if the
station list changed. On the first run it turns on *Use channel numbers from
backend* + *always*, so the frequencies show as channel numbers and KodiMov+
keeps Movistar's TV numbers.
Uninstalling the add-on leaves that instance behind: remove it in
IPTV Simple's settings (or delete the instance file).
## Changing stations
1. Edit `tools/stations.txt`.
2. `python3 -I tools/check.py` tests every stream with ffprobe.
3. `python3 -I tools/build.py` writes `resources/radio.m3u` and fetches missing logos.
4. Raise `version` in `addon.xml` (and add a `<news>` line), commit, push to `main`.
5. Publish: dispatch the skin repo's `kodi-repo.yml` workflow; boxes update
from repository.xocko.
`tools/` and this README aren't in the add-on zip (`.gitattributes` export-ignore).
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="service.radios.vigo"
name="Radios de Vigo"
version="1.0.0"
provider-name="xocko">
<requires>
<import addon="xbmc.python" version="3.0.0"/>
<import addon="pvr.iptvsimple" version="21.0.0"/>
</requires>
<extension point="xbmc.service" library="service.py" start="startup"/>
<extension point="xbmc.addon.metadata">
<summary lang="gl_ES">As radios que se sintonizan en Vigo e informativos de Reino Unido, EUA e Portugal</summary>
<summary lang="es_ES">Las radios que se sintonizan en Vigo e informativos de Reino Unido, EE. UU. y Portugal</summary>
<summary lang="en_GB">The radio stations you can tune in Vigo, plus news radio from the UK, US and Portugal</summary>
<description lang="gl_ES">Engade á sección Radio de Kodi as emisoras do dial FM de Vigo (co número de canal na súa frecuencia, incluídas as portuguesas de Valença) e emisoras de noticias de Reino Unido, Estados Unidos e Portugal. Crea a súa propia instancia de IPTV Simple e mantena ao día cando se actualiza o complemento.</description>
<description lang="es_ES">Añade a la sección Radio de Kodi las emisoras del dial FM de Vigo (con el número de canal en su frecuencia, incluidas las portuguesas de Valença) y emisoras de noticias de Reino Unido, Estados Unidos y Portugal. Crea su propia instancia de IPTV Simple y la mantiene al día cuando se actualiza el complemento.</description>
<description lang="en_GB">Adds the stations on Vigo's FM dial (numbered by frequency, including the Portuguese ones from Valença) and news stations from the UK, US and Portugal to Kodi's Radio section. It creates its own IPTV Simple instance and keeps it up to date when the add-on updates.</description>
<news>1.0.0: first version, 38 Vigo stations and 12 news stations.</news>
<platform>all</platform>
<license>MIT</license>
<source>https://git.anosanube.ovh/xocko/radios-vigo</source>
<assets>
<icon>resources/icon.png</icon>
</assets>
</extension>
</addon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

+101
View File
@@ -0,0 +1,101 @@
#EXTM3U
#EXTINF:-1 tvg-id="radio-8" tvg-chno="88.1" radio="true" group-title="Vigo" tvg-logo="radio-8.png",Radio 8
https://relay.stream.enacast-cloud.com:30271/radio8HD.mp3
#EXTINF:-1 tvg-id="rne-radio-clasica" tvg-chno="88.3" radio="true" group-title="Vigo" tvg-logo="rne-radio-clasica.png",RNE Radio Clásica
https://dispatcher.rndfnk.com/crtve/rnerc/main/mp3/high
#EXTINF:-1 tvg-id="europa-fm" tvg-chno="88.7" radio="true" group-title="Vigo" tvg-logo="europa-fm.png",Europa FM
https://atres-live.europafm.com/live/europafm/master.m3u8
#EXTINF:-1 tvg-id="antena-2" tvg-chno="89.6" radio="true" group-title="Vigo" tvg-logo="antena-2.png",Antena 2
https://streaming-live.rtp.pt/liveradio/antena280a/playlist.m3u8
#EXTINF:-1 tvg-id="ushuaia-radio" tvg-chno="89.8" radio="true" group-title="Vigo" tvg-logo="ushuaia-radio.png",Ushuaïa Radio
https://s4.radio.co/sc6f29e098/listen
#EXTINF:-1 tvg-id="rne-radio-nacional-galicia" tvg-chno="90.1" radio="true" group-title="Vigo" tvg-logo="rne-radio-nacional-galicia.png",RNE Radio Nacional Galicia
https://dispatcher.rndfnk.com/crtve/rne1/gal/mp3/high
#EXTINF:-1 tvg-id="hit-fm" tvg-chno="90.5" radio="true" group-title="Vigo" tvg-logo="hit-fm.png",Hit FM
https://bbhitfm.kissfmradio.cires21.com/bbhitfm.mp3
#EXTINF:-1 tvg-id="radio-galega-musica" tvg-chno="90.8" radio="true" group-title="Vigo" tvg-logo="radio-galega-musica.png",Radio Galega Música
https://crtvg-radiogalega-musica-hls.flumotion.cloud/playlist.m3u8
#EXTINF:-1 tvg-id="radio-lider" tvg-chno="91.1" radio="true" group-title="Vigo" tvg-logo="radio-lider.png",Radio Líder
http://server9.20comunicacion.com:8200/stream
#EXTINF:-1 tvg-id="cadena-dial" tvg-chno="91.4" radio="true" group-title="Vigo" tvg-logo="cadena-dial.png",Cadena Dial
https://playerservices.streamtheworld.com/api/livestream-redirect/CADENADIAL.mp3
#EXTINF:-1 tvg-id="los40-dance" tvg-chno="92.7" radio="true" group-title="Vigo" tvg-logo="los40-dance.png",LOS40 Dance
https://playerservices.streamtheworld.com/api/livestream-redirect/LOS40_DANCE.mp3
#EXTINF:-1 tvg-id="ser-mas-radio-vigo" tvg-chno="93.7" radio="true" group-title="Vigo" tvg-logo="ser-mas-radio-vigo.png",SER+ Radio Vigo
https://playerservices.streamtheworld.com/api/livestream-redirect/SER_ASO_MAS_VIGO.mp3
#EXTINF:-1 tvg-id="kiss-fm" tvg-chno="94.0" radio="true" group-title="Vigo" tvg-logo="kiss-fm.png",Kiss FM
https://kissfm.kissfmradio.cires21.com/kissfm.mp3
#EXTINF:-1 tvg-id="rne-radio-3" tvg-chno="94.6" radio="true" group-title="Vigo" tvg-logo="rne-radio-3.png",RNE Radio 3
https://dispatcher.rndfnk.com/crtve/rner3/main/mp3/high
#EXTINF:-1 tvg-id="flow-radio" tvg-chno="94.8" radio="true" group-title="Vigo" tvg-logo="flow-radio.png",Flow Radio
https://flow.urbanrevolution.es:8445/flow
#EXTINF:-1 tvg-id="radio-galega" tvg-chno="95.1" radio="true" group-title="Vigo" tvg-logo="radio-galega.png",Radio Galega
https://crtvg-radiogalega-hls.flumotion.cloud/playlist.m3u8
#EXTINF:-1 tvg-id="rfm" tvg-chno="95.4" radio="true" group-title="Vigo" tvg-logo="rfm.png",RFM
https://playerservices.streamtheworld.com/api/livestream-redirect/RFMAAC.aac
#EXTINF:-1 tvg-id="rne-radio-5-galicia" tvg-chno="96.0" radio="true" group-title="Vigo" tvg-logo="rne-radio-5-galicia.png",RNE Radio 5 Galicia
https://dispatcher.rndfnk.com/crtve/rne5/vig/mp3/high
#EXTINF:-1 tvg-id="cadena-100" tvg-chno="96.5" radio="true" group-title="Vigo" tvg-logo="cadena-100.png",Cadena 100
https://cadena100-streamers-mp3.flumotion.com/cope/cadena100.mp3
#EXTINF:-1 tvg-id="antena-1" tvg-chno="98.2" radio="true" group-title="Vigo;Noticias · Portugal" tvg-logo="antena-1.png",Antena 1
https://streaming-live.rtp.pt/liveradio/antena180a/playlist.m3u8
#EXTINF:-1 tvg-id="radio-marca-vigo" tvg-chno="98.3" radio="true" group-title="Vigo" tvg-logo="radio-marca-vigo.png",Radio Marca Vigo
https://streaming.enacast.com/radiomarcavigoHD.mp3
#EXTINF:-1 tvg-id="ser-radio-pontevedra" tvg-chno="98.7" radio="true" group-title="Vigo" tvg-logo="ser-radio-pontevedra.png",SER Radio Pontevedra
https://playerservices.streamtheworld.com/api/livestream-redirect/SER_ASO_PONTEVEDRA.mp3
#EXTINF:-1 tvg-id="radio-comercial" tvg-chno="99.0" radio="true" group-title="Vigo" tvg-logo="radio-comercial.png",Rádio Comercial
https://stream-icy.bauermedia.pt/comercial.mp3
#EXTINF:-1 tvg-id="motiva-vigo" tvg-chno="99.1" radio="true" group-title="Vigo" tvg-logo="motiva-vigo.png",motiva Vigo
https://stream.guadigo.es/listen/motiva/motivavigo.mp3
#EXTINF:-1 tvg-id="los40-vigo" tvg-chno="99.4" radio="true" group-title="Vigo" tvg-logo="los40-vigo.png",LOS40 Vigo
https://playerservices.streamtheworld.com/api/livestream-redirect/LOS40_ASO_VIGO.mp3
#EXTINF:-1 tvg-id="informa-radio" tvg-chno="99.7" radio="true" group-title="Vigo" tvg-logo="informa-radio.png",Informa Radio
https://24x7.red:42045/informa
#EXTINF:-1 tvg-id="ser-radio-vigo" tvg-chno="100.6" radio="true" group-title="Vigo" tvg-logo="ser-radio-vigo.png",SER Radio Vigo
https://playerservices.streamtheworld.com/api/livestream-redirect/SER_ASO_VIGO.mp3
#EXTINF:-1 tvg-id="los40-classic" tvg-chno="101.2" radio="true" group-title="Vigo" tvg-logo="los40-classic.png",LOS40 Classic
https://playerservices.streamtheworld.com/api/livestream-redirect/LOS40_CLASSIC.mp3
#EXTINF:-1 tvg-id="esradio-galicia" tvg-chno="101.9" radio="true" group-title="Vigo" tvg-logo="esradio-galicia.png",esRadio Galicia
https://libertaddigital-radio-live2.flumotion.com/libertaddigital/ld-live2-low.mp3
#EXTINF:-1 tvg-id="onda-cero-pontevedra" tvg-chno="103.6" radio="true" group-title="Vigo" tvg-logo="onda-cero-pontevedra.png",Onda Cero Pontevedra
https://radio-atres-live.ondacero.es/api/livestream-redirect/OC_PONTEVEDRAAAC.m3u8
#EXTINF:-1 tvg-id="radio-voz-vigo" tvg-chno="103.8" radio="true" group-title="Vigo" tvg-logo="radio-voz-vigo.png",Radio Voz Vigo
https://live.radiovoz.es/mp3/stream_vigo.mp3
#EXTINF:-1 tvg-id="antena-3" tvg-chno="104.0" radio="true" group-title="Vigo" tvg-logo="antena-3.png",Antena 3
https://streaming-live.rtp.pt/liveradio/antena380a/playlist.m3u8
#EXTINF:-1 tvg-id="los40-urban" tvg-chno="104.9" radio="true" group-title="Vigo" tvg-logo="los40-urban.png",LOS40 Urban
https://playerservices.streamtheworld.com/api/livestream-redirect/LOS40_URBAN.mp3
#EXTINF:-1 tvg-id="onda-cero-vigo" tvg-chno="105.4" radio="true" group-title="Vigo" tvg-logo="onda-cero-vigo.png",Onda Cero Vigo
https://radio-atres-live.ondacero.es/api/livestream-redirect/OC_VIGOAAC.m3u8
#EXTINF:-1 tvg-id="inolvidable-fm" tvg-chno="105.8" radio="true" group-title="Vigo" tvg-logo="inolvidable-fm.png",Inolvidable FM
https://uk1.streamingpulse.com/ssl/inolvidablefm
#EXTINF:-1 tvg-id="radio-piratona" tvg-chno="106.1" radio="true" group-title="Vigo" tvg-logo="radio-piratona.png",Radio Piratona
https://zeppelin.streampunk.cc/_stream/radiopiratona.ogg
#EXTINF:-1 tvg-id="rock-fm" tvg-chno="106.7" radio="true" group-title="Vigo" tvg-logo="rock-fm.png",Rock FM
https://rockfm-cope-rrcast.flumotion.com/cope/rockfm-low.mp3
#EXTINF:-1 tvg-id="si-radio" tvg-chno="107.7" radio="true" group-title="Vigo" tvg-logo="si-radio.png",Si Radio
http://82.223.29.65:8080/vcbm_siradio
#EXTINF:-1 tvg-id="bbc-radio-4" tvg-chno="201" radio="true" group-title="Noticias · Reino Unido" tvg-logo="bbc-radio-4.png",BBC Radio 4
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
#EXTINF:-1 tvg-id="bbc-world-service" tvg-chno="202" radio="true" group-title="Noticias · Reino Unido" tvg-logo="bbc-world-service.png",BBC World Service
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
#EXTINF:-1 tvg-id="bbc-radio-5-live" tvg-chno="203" radio="true" group-title="Noticias · Reino Unido" tvg-logo="bbc-radio-5-live.png",BBC Radio 5 Live
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
#EXTINF:-1 tvg-id="times-radio" tvg-chno="204" radio="true" group-title="Noticias · Reino Unido" tvg-logo="times-radio.png",Times Radio
https://timesradio.wireless.radio/stream
#EXTINF:-1 tvg-id="lbc-news" tvg-chno="205" radio="true" group-title="Noticias · Reino Unido" tvg-logo="lbc-news.png",LBC News
https://media-ssl.musicradio.com/LBCNewsUK
#EXTINF:-1 tvg-id="npr" tvg-chno="301" radio="true" group-title="Noticias · EUA" tvg-logo="npr.png",NPR
https://npr-ice.streamguys1.com/live.mp3
#EXTINF:-1 tvg-id="wnyc-new-york" tvg-chno="302" radio="true" group-title="Noticias · EUA" tvg-logo="wnyc-new-york.png",WNYC New York
https://fm939.wnyc.org/wnycfm-web
#EXTINF:-1 tvg-id="wbur-boston" tvg-chno="303" radio="true" group-title="Noticias · EUA" tvg-logo="wbur-boston.png",WBUR Boston
https://icecast-stream.wbur.org/wbur_www
#EXTINF:-1 tvg-id="kqed-san-francisco" tvg-chno="304" radio="true" group-title="Noticias · EUA" tvg-logo="kqed-san-francisco.png",KQED San Francisco
https://hls.kqed.org/hls/kqed_app/playlist.m3u8
#EXTINF:-1 tvg-id="bloomberg-radio" tvg-chno="305" radio="true" group-title="Noticias · EUA" tvg-logo="bloomberg-radio.png",Bloomberg Radio
https://playerservices.streamtheworld.com/api/livestream-redirect/WBBRAMAAC.aac
#EXTINF:-1 tvg-id="tsf-radio-noticias" tvg-chno="401" radio="true" group-title="Noticias · Portugal" tvg-logo="tsf-radio-noticias.png",TSF Rádio Notícias
https://directo.tsf.pt/tsfdirecto.mp3
#EXTINF:-1 tvg-id="radio-observador" tvg-chno="402" radio="true" group-title="Noticias · Portugal" tvg-logo="radio-observador.png",Rádio Observador
https://playerservices.streamtheworld.com/api/livestream-redirect/OBSERVADORAAC.aac
+127
View File
@@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
"""Keeps an IPTV Simple instance that plays this add-on's radio.m3u.
Runs when Kodi starts and again after every add-on update. Writes the
instance settings, then restarts IPTV Simple (once nothing is playing) if the
settings or the station list changed since the last run.
"""
import glob
import hashlib
import io
import json
import os
import xbmc
import xbmcaddon
import xbmcvfs
ADDON = xbmcaddon.Addon()
ADDON_ID = ADDON.getAddonInfo('id')
HOME = 'special://home/addons/{}/resources/'.format(ADDON_ID)
FIRST_INSTANCE = 95 # KodiMov+ uses 91 (VAIO) and 93 (.49)
SETTINGS = '''<settings version="2">
<setting id="kodi_addon_instance_name">Radios de Vigo</setting>
<setting id="kodi_addon_instance_enabled">true</setting>
<setting id="m3uPathType">0</setting>
<setting id="m3uPath">{home}radio.m3u</setting>
<setting id="m3uUrl" default="true" />
<setting id="m3uCache" default="true">true</setting>
<setting id="startNum" default="true">1</setting>
<setting id="numberByOrder" default="true">false</setting>
<setting id="m3uRefreshMode" default="true">0</setting>
<setting id="epgPathType">0</setting>
<setting id="epgPath" default="true" />
<setting id="epgUrl" default="true" />
<setting id="logoPathType">0</setting>
<setting id="logoPath">{home}logos/</setting>
<setting id="useLogosLocalPathOnly" default="true">false</setting>
<setting id="logoFromEpg">0</setting>
<setting id="useFFmpegReconnect" default="true">true</setting>
<setting id="useInputstreamAdaptiveforHls" default="true">false</setting>
</settings>
'''.format(home=HOME)
def log(msg):
xbmc.log('[{}] {}'.format(ADDON_ID, msg), xbmc.LOGINFO)
def rpc(method, **params):
return json.loads(xbmc.executeJSONRPC(json.dumps(
{'jsonrpc': '2.0', 'id': 1, 'method': method, 'params': params})))
def read(path):
try:
with io.open(path, 'r', encoding='utf-8') as f:
return f.read()
except (IOError, OSError):
return None
def write(path, content):
tmp = path + '.tmp'
with io.open(tmp, 'w', encoding='utf-8', newline='') as f:
f.write(content)
os.replace(tmp, path)
def instance_file(pvr_dir):
"""Our instance file: the one that already reads radio.m3u, else the
first free number from FIRST_INSTANCE on."""
for path in glob.glob(os.path.join(pvr_dir, 'instance-settings-*.xml')):
if HOME + 'radio.m3u' in (read(path) or ''):
return path
n = FIRST_INSTANCE
while os.path.exists(os.path.join(pvr_dir, 'instance-settings-{}.xml'.format(n))):
n += 1
return os.path.join(pvr_dir, 'instance-settings-{}.xml'.format(n))
def restart_iptv_simple():
for enabled in (False, True):
rpc('Addons.SetAddonEnabled', addonid='pvr.iptvsimple', enabled=enabled)
xbmc.sleep(3000)
def main():
monitor, player = xbmc.Monitor(), xbmc.Player()
profile = xbmcvfs.translatePath(ADDON.getAddonInfo('profile'))
pvr_dir = xbmcvfs.translatePath('special://profile/addon_data/pvr.iptvsimple/')
for d in (profile, pvr_dir):
if not os.path.isdir(d):
os.makedirs(d)
path = instance_file(pvr_dir)
if read(path) != SETTINGS:
write(path, SETTINGS)
log('wrote ' + path)
numbers_set = os.path.join(profile, 'numbers-set')
if not os.path.exists(numbers_set):
# Show the stations' own numbers (Vigo ones are their FM frequency).
# With more than one IPTV Simple instance Kodi ignores them unless
# "always" is on too; that also keeps KodiMov+'s TV numbers.
for setting in ('pvrmanager.usebackendchannelnumbers', 'pvrmanager.usebackendchannelnumbersalways'):
rpc('Settings.SetSettingValue', setting=setting, value=True)
write(numbers_set, '')
log('turned on backend channel numbers')
m3u = read(xbmcvfs.translatePath(HOME + 'radio.m3u')) or ''
state = hashlib.sha1((SETTINGS + m3u).encode('utf-8')).hexdigest()
if read(os.path.join(profile, 'state')) == state:
return
while player.isPlaying(): # restarting IPTV Simple stops the radio or TV playing
if monitor.waitForAbort(10):
return
if monitor.waitForAbort(5):
return
log('station list or settings changed, restarting IPTV Simple')
restart_iptv_simple()
write(os.path.join(profile, 'state'), state)
if __name__ == '__main__':
main()
+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