Radios de Vigo: Vigo FM dial and UK/US/Portugal news radio for Kodi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No files matched your search
@@ -0,0 +1,2 @@
|
||||
tools export-ignore
|
||||
README.md export-ignore
|
||||
@@ -0,0 +1,2 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
@@ -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).
|
||||
@@ -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>
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 20 KiB |
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||