1.0.0+kodimov.7: synopsis in the programme guide; hourly IPTV Simple reload

Movistar's guide already carries the synopsis (Resena), genre, year,
age rating and pictures; write them to epg.xml.

IPTV Simple was set to reload the channel list and guide once a day at
4:00, so a new epg.xml didn't reach Kodi until then. Use its hourly
reload, which also makes Kodi fetch the guide again. The service switches
existing TV guide setups over once and restarts IPTV Simple when nothing
is playing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
XoséandClaude Opus 5.5 committed 2026-11-10 08:31:24 +01:00
1 parent 09d6a68f74
commit 9faaa482fc
6 files changed
+66 -10

No files matched your search

+3 -2
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.kodimovplus" <addon id="plugin.video.kodimovplus"
name="KodiMov+" name="KodiMov+"
version="1.0.0+kodimov.6" version="1.0.0+kodimov.7"
provider-name="Paco8, xocko"> provider-name="Paco8, xocko">
<requires> <requires>
<!--- <import addon="xbmc.python" version="2.25.0"/> --> <!--- <import addon="xbmc.python" version="2.25.0"/> -->
@@ -21,7 +21,8 @@
<summary lang="es_ES">Movistar+ para Kodi, con inicio de sesión automático desde la red de Movistar</summary> <summary lang="es_ES">Movistar+ para Kodi, con inicio de sesión automático desde la red de Movistar</summary>
<description lang="en_GB">Fork of Paco8's Movistar+ add-on (plugin.video.movistarplus). Logs in by itself from the Movistar home network, keeps the channel list and numbers up to date, hides the channels that need stronger DRM than Kodi has, adds free Galician channels (TVG2, TeleVigo) and writes a programme guide for every channel. Requires a Movistar+ subscription.</description> <description lang="en_GB">Fork of Paco8's Movistar+ add-on (plugin.video.movistarplus). Logs in by itself from the Movistar home network, keeps the channel list and numbers up to date, hides the channels that need stronger DRM than Kodi has, adds free Galician channels (TVG2, TeleVigo) and writes a programme guide for every channel. Requires a Movistar+ subscription.</description>
<description lang="es_ES">Fork del addon Movistar+ de Paco8 (plugin.video.movistarplus). Inicia sesión solo desde la red de Movistar, mantiene la lista y los números de los canales al día, oculta los canales que necesitan una protección que Kodi no tiene añade canales gallegos gratuitos (TVG2, TeleVigo) y genera la guía de programación de todos los canales. Se necesita estar abonado a Movistar+.</description> <description lang="es_ES">Fork del addon Movistar+ de Paco8 (plugin.video.movistarplus). Inicia sesión solo desde la red de Movistar, mantiene la lista y los números de los canales al día, oculta los canales que necesitan una protección que Kodi no tiene añade canales gallegos gratuitos (TVG2, TeleVigo) y genera la guía de programación de todos los canales. Se necesita estar abonado a Movistar+.</description>
<news>1.0.0+kodimov.6: the programme guide refreshes on its own again on boxes first set up with kodimov.1, where the automatic guide export had stayed off (the guide ran out after 2 days). <news>1.0.0+kodimov.7: the programme guide has the synopsis, picture, genre, year and age rating of each programme. IPTV Simple now reloads the guide every hour instead of once a day, so a new guide shows up in Kodi by itself (existing TV guide setups are switched over once).
1.0.0+kodimov.6: the programme guide refreshes on its own again on boxes first set up with kodimov.1, where the automatic guide export had stayed off (the guide ran out after 2 days).
1.0.0+kodimov.5: TV guide setup writes the programme guide before creating the IPTV Simple settings, so the guide isn't empty on a new box. 1.0.0+kodimov.5: TV guide setup writes the programme guide before creating the IPTV Simple settings, so the guide isn't empty on a new box.
1.0.0+kodimov.4: first login on a new box needs no password from the Movistar home network: the account is found and the box registered automatically. 1.0.0+kodimov.4: first login on a new box needs no password from the Movistar home network: the account is found and the box registered automatically.
1.0.0+kodimov.3: one-tap TV setup: "Create settings for IPTV Simple" also turns on Movistar channel numbers, daily list reload, and restarts IPTV Simple; fixes invalid XML in that file. 1.0.0+kodimov.3: one-tap TV setup: "Create settings for IPTV Simple" also turns on Movistar channel numbers, daily list reload, and restarts IPTV Simple; fixes invalid XML in that file.
+2 -2
View File
@@ -15,8 +15,8 @@ def save_iptv_settings(filename, name, addon_id, epg_url=None):
<setting id="m3uCache" default="true">true</setting> <setting id="m3uCache" default="true">true</setting>
<setting id="startNum" default="true">1</setting> <setting id="startNum" default="true">1</setting>
<setting id="numberByOrder" default="true">false</setting> <setting id="numberByOrder" default="true">false</setting>
<setting id="m3uRefreshMode">2</setting> <setting id="m3uRefreshMode">1</setting>
<setting id="m3uRefreshIntervalMins" default="true">60</setting> <setting id="m3uRefreshIntervalMins">60</setting>
<setting id="m3uRefreshHour" default="true">4</setting> <setting id="m3uRefreshHour" default="true">4</setting>
<setting id="connectioncheckinterval" default="true">10</setting> <setting id="connectioncheckinterval" default="true">10</setting>
<setting id="connectionchecktimeout" default="true">20</setting> <setting id="connectionchecktimeout" default="true">20</setting>
+31
View File
@@ -210,6 +210,37 @@ def write_if_changed(filename, content):
return True return True
def use_hourly_iptv_refresh(pvr_dir, addon_id):
"""TV guide setups before kodimov.7 made IPTV Simple reload the channel list
and programme guide only once a day, at 4:00, so a new epg.xml didn't reach
Kodi for up to a day. Switch them to a reload every hour, which also makes
Kodi fetch the guide again. Returns True if IPTV Simple must be restarted."""
import glob
import re
changed = False
for filename in glob.glob(os.path.join(pvr_dir, 'instance-settings-*.xml')):
with io.open(filename, 'r', encoding='utf-8') as f:
content = f.read()
if 'plugin.video.{}/channels.m3u8'.format(addon_id) not in content: continue
new = content
for setting, value in (('m3uRefreshMode', '1'), ('m3uRefreshIntervalMins', '60')):
new = re.sub(r'<setting id="{}"[^>]*?(/>|>[^<]*</setting>)'.format(setting),
'<setting id="{}">{}</setting>'.format(setting, value), new)
if write_if_changed(filename, new):
LOG('use_hourly_iptv_refresh: updated {}'.format(filename))
changed = True
return changed
def restart_iptv_simple():
"""Restarts IPTV Simple so it loads its instance settings again."""
import xbmc
for enabled in (False, True):
xbmc.executeJSONRPC(json.dumps({'jsonrpc': '2.0', 'id': 1, 'method': 'Addons.SetAddonEnabled',
'params': {'addonid': 'pvr.iptvsimple', 'enabled': enabled}}))
xbmc.sleep(3000)
def extra_channels_xmltv(session): def extra_channels_xmltv(session):
"""Returns the <channel> and <programme> elements for the extra channels, """Returns the <channel> and <programme> elements for the extra channels,
taken from a public XMLTV guide and renamed to their tvg-id.""" taken from a public XMLTV guide and renamed to their tvg-id."""
+18 -2
View File
@@ -564,6 +564,13 @@ class Movistar(object):
if 'TituloHorLinea2' in p: if 'TituloHorLinea2' in p:
pr['desc2'] = p['TituloHorLinea2'] pr['desc2'] = p['TituloHorLinea2']
pr['show_id'] = p['ShowId'] pr['show_id'] = p['ShowId']
# Synopsis, genre, year, age rating and picture come with the guide
pr['plot'] = p.get('Resena') or ''
pr['genre'] = p.get('GeneroComAntena') or ''
pr['year'] = p.get('Anno') or ''
pr['age_rating'] = (p.get('NivelMoral') or {}).get('Id') or ''
images = {i.get('id'): i.get('uri') for i in (p.get('Imagenes') or [])}
pr['image'] = images.get('horizontal') or images.get('default') or ''
if 'SerialId' in p: if 'SerialId' in p:
pr['serie_id'] = p['SerialId'] pr['serie_id'] = p['SerialId']
pr['id'] = p['Id'] pr['id'] = p['Id']
@@ -1454,6 +1461,9 @@ class Movistar(object):
t['subtitle'] = e['desc2'] t['subtitle'] = e['desc2']
t['start'] = datetime.utcfromtimestamp(e['start']/1000).strftime('%Y-%m-%dT%H:%M:%SZ') t['start'] = datetime.utcfromtimestamp(e['start']/1000).strftime('%Y-%m-%dT%H:%M:%SZ')
t['stop'] = datetime.utcfromtimestamp(e['end']/1000).strftime('%Y-%m-%dT%H:%M:%SZ') t['stop'] = datetime.utcfromtimestamp(e['end']/1000).strftime('%Y-%m-%dT%H:%M:%SZ')
for key, field in (('description', 'plot'), ('image', 'image'), ('genre', 'genre'),
('year', 'year'), ('age_rating', 'age_rating')):
if e.get(field): t[key] = e[field]
if True: if True:
program_id = str(e['show_id']) program_id = str(e['show_id'])
@@ -1533,8 +1543,6 @@ class Movistar(object):
'>\n' + '>\n' +
' <title>{}</title>\n'.format(html_escape(e['title'])) + ' <title>{}</title>\n'.format(html_escape(e['title'])) +
' <sub-title>{}</sub-title>\n'.format(html_escape(e['subtitle']))) ' <sub-title>{}</sub-title>\n'.format(html_escape(e['subtitle'])))
if 'image' in e:
res.append(' <icon src="{}"/>\n'.format(e['image']))
if 'description' in e: if 'description' in e:
res.append(' <desc>{}</desc>\n'.format(html_escape(e['description']))) res.append(' <desc>{}</desc>\n'.format(html_escape(e['description'])))
if 'credits' in e and len(e['credits']) > 0: if 'credits' in e and len(e['credits']) > 0:
@@ -1545,6 +1553,14 @@ class Movistar(object):
elif c['type'] == 'actor': elif c['type'] == 'actor':
res.append(' <actor>{}</actor>\n'.format(c['name'])) res.append(' <actor>{}</actor>\n'.format(c['name']))
res.append(' </credits>\n'); res.append(' </credits>\n');
if 'year' in e:
res.append(' <date>{}</date>\n'.format(html_escape(e['year'])))
if 'genre' in e:
res.append(' <category>{}</category>\n'.format(html_escape(e['genre'])))
if 'image' in e:
res.append(' <icon src="{}"/>\n'.format(html_escape(e['image'])))
if 'age_rating' in e:
res.append(' <rating system="ES"><value>{}</value></rating>\n'.format(html_escape(e['age_rating'])))
res.append('</programme>\n') res.append('</programme>\n')
if self.add_extra_channels: if self.add_extra_channels:
res.extend(kodimov.extra_channels_xmltv(self.net.session)) res.extend(kodimov.extra_channels_xmltv(self.net.session))
+2 -4
View File
@@ -749,6 +749,7 @@ def iptv(params):
def create_iptv_settings(): def create_iptv_settings():
from .iptv import save_iptv_settings from .iptv import save_iptv_settings
from . import kodimov
show_notification(addon.getLocalizedString(30319), xbmcgui.NOTIFICATION_INFO) show_notification(addon.getLocalizedString(30319), xbmcgui.NOTIFICATION_INFO)
output_file = 'instance-settings-91.xml' if kodi_version > 19 else 'settings.xml' output_file = 'instance-settings-91.xml' if kodi_version > 19 else 'settings.xml'
epg_url = None epg_url = None
@@ -776,10 +777,7 @@ def create_iptv_settings():
xbmc.executeJSONRPC(json.dumps({'jsonrpc': '2.0', 'id': 1, 'method': 'Settings.SetSettingValue', xbmc.executeJSONRPC(json.dumps({'jsonrpc': '2.0', 'id': 1, 'method': 'Settings.SetSettingValue',
'params': {'setting': 'pvrmanager.usebackendchannelnumbers', 'value': True}})) 'params': {'setting': 'pvrmanager.usebackendchannelnumbers', 'value': True}}))
# Restart IPTV Simple so it loads the new instance and channel list. # Restart IPTV Simple so it loads the new instance and channel list.
for enabled in (False, True): kodimov.restart_iptv_simple()
xbmc.executeJSONRPC(json.dumps({'jsonrpc': '2.0', 'id': 1, 'method': 'Addons.SetAddonEnabled',
'params': {'addonid': 'pvr.iptvsimple', 'enabled': enabled}}))
xbmc.sleep(3000)
except: except:
show_notification(addon.getLocalizedString(30324)) show_notification(addon.getLocalizedString(30324))
+10
View File
@@ -10,6 +10,7 @@ import xbmcgui
from resources.lib.proxy import Proxy from resources.lib.proxy import Proxy
from resources.lib.addon import * from resources.lib.addon import *
from resources.lib.log import LOG from resources.lib.log import LOG
from resources.lib import kodimov
def export_epg(): def export_epg():
from datetime import datetime, timedelta from datetime import datetime, timedelta
@@ -41,10 +42,19 @@ if __name__ == '__main__':
# Refreshes the TV guide channel list (new channels, new numbers) and the # Refreshes the TV guide channel list (new channels, new numbers) and the
# programme guide a minute after Kodi starts and then every 6 hours. Opening the add-on also renews the 10-day Movistar login. # programme guide a minute after Kodi starts and then every 6 hours. Opening the add-on also renews the 10-day Movistar login.
next_export = time.time() + 60 next_export = time.time() + 60
try:
pvr_dir = translatePath(xbmcaddon.Addon('pvr.iptvsimple').getAddonInfo('profile'))
restart_iptv = kodimov.use_hourly_iptv_refresh(pvr_dir, 'kodimovplus')
except RuntimeError: # IPTV Simple not installed or disabled
restart_iptv = False
player = xbmc.Player()
monitor = xbmc.Monitor() monitor = xbmc.Monitor()
while not monitor.abortRequested(): while not monitor.abortRequested():
if monitor.waitForAbort(1): if monitor.waitForAbort(1):
break break
if restart_iptv and not player.isPlaying(): # restarting stops live TV
restart_iptv = False
kodimov.restart_iptv_simple()
if time.time() >= next_export: if time.time() >= next_export:
next_export = time.time() + 6 * 3600 next_export = time.time() + 6 * 3600
if addon.getSettingBool('auto_export_epg'): # channels + programme guide if addon.getSettingBool('auto_export_epg'): # channels + programme guide