Version 0.5.20
No files matched your search
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.movistarplus"
|
||||
name="Movistar+"
|
||||
version="0.5.14"
|
||||
version="0.5.20"
|
||||
provider-name="Paco8">
|
||||
<requires>
|
||||
<!--- <import addon="xbmc.python" version="2.25.0"/> -->
|
||||
@@ -33,6 +33,8 @@
|
||||
<screenshot>resources/screen1.jpg</screenshot>
|
||||
<screenshot>resources/screen2.jpg</screenshot>
|
||||
<screenshot>resources/screen3.jpg</screenshot>
|
||||
<screenshot>resources/screen4.jpg</screenshot>
|
||||
<screenshot>resources/screen5.jpg</screenshot>
|
||||
</assets>
|
||||
</extension>
|
||||
</addon>
|
||||
@@ -272,3 +272,19 @@ msgstr ""
|
||||
msgctxt "#30204"
|
||||
msgid "Video not available"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30300"
|
||||
msgid "IPTV Manager"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30301"
|
||||
msgid "Install IPTV Manager add-on"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30302"
|
||||
msgid "Enable IPTV Manager integration"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#30303"
|
||||
msgid "IPTV Manager settings"
|
||||
msgstr ""
|
||||
@@ -272,3 +272,19 @@ msgstr "Formato no soportado"
|
||||
msgctxt "#30204"
|
||||
msgid "Video not available"
|
||||
msgstr "Vídeo no disponible"
|
||||
|
||||
msgctxt "#30300"
|
||||
msgid "IPTV Manager"
|
||||
msgstr "IPTV Manager"
|
||||
|
||||
msgctxt "#30301"
|
||||
msgid "Install IPTV Manager add-on"
|
||||
msgstr "Instalar IPTV Manager"
|
||||
|
||||
msgctxt "#30302"
|
||||
msgid "Enable IPTV Manager integration"
|
||||
msgstr "Habilitar integración con IPTV Manager"
|
||||
|
||||
msgctxt "#30303"
|
||||
msgid "IPTV Manager settings"
|
||||
msgstr "Opciones de IPTV Manager"
|
||||
@@ -48,3 +48,16 @@ class Cache(object):
|
||||
files.extend(glob.glob(self.config_directory + ext))
|
||||
for filename in files:
|
||||
os.remove(filename)
|
||||
|
||||
def clear_cache(self, days=10):
|
||||
from datetime import datetime
|
||||
deleted = 0
|
||||
files = glob.glob(os.path.join(self.config_directory + 'cache', '*.json'))
|
||||
now = datetime.now()
|
||||
for filename in files:
|
||||
modified_time = datetime.fromtimestamp(os.path.getmtime(filename))
|
||||
time_diff = now - modified_time
|
||||
if time_diff.days > days:
|
||||
os.remove(filename)
|
||||
deleted += 1
|
||||
return deleted
|
||||
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""IPTV Manager Integration module"""
|
||||
|
||||
import json
|
||||
import socket
|
||||
from .movistar import Movistar
|
||||
|
||||
class IPTVManager:
|
||||
"""Interface to IPTV Manager"""
|
||||
|
||||
def __init__(self, port):
|
||||
"""Initialize IPTV Manager object"""
|
||||
self.port = port
|
||||
|
||||
def via_socket(func):
|
||||
"""Send the output of the wrapped function to socket"""
|
||||
|
||||
def send(self, m):
|
||||
"""Decorator to send over a socket"""
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(('127.0.0.1', self.port))
|
||||
try:
|
||||
sock.sendall(json.dumps(func(self, m)).encode())
|
||||
finally:
|
||||
sock.close()
|
||||
|
||||
return send
|
||||
|
||||
@via_socket
|
||||
def send_channels(self, m):
|
||||
"""Return JSON-STREAMS formatted python datastructure to IPTV Manager"""
|
||||
return dict(version=1, streams=m.export_channels())
|
||||
|
||||
@via_socket
|
||||
def send_epg(self, m):
|
||||
"""Return JSON-EPG formatted python data structure to IPTV Manager"""
|
||||
return dict(version=1, epg=m.export_epg())
|
||||
@@ -11,6 +11,7 @@ import io
|
||||
import os
|
||||
import time
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
from .log import LOG, print_json
|
||||
from .network import Network
|
||||
@@ -48,6 +49,8 @@ class Movistar(object):
|
||||
|
||||
# Cache
|
||||
self.cache = Cache(config_directory)
|
||||
if not os.path.exists(config_directory + 'cache'):
|
||||
os.makedirs(config_directory + 'cache')
|
||||
|
||||
# Endpoints
|
||||
self.endpoints = self.get_endpoints()
|
||||
@@ -297,9 +300,10 @@ class Movistar(object):
|
||||
data = self.net.load_data(url, headers)
|
||||
self.cache.save_file('devices.json', json.dumps(data, ensure_ascii=False))
|
||||
#print_json(data)
|
||||
if not isinstance(data, list): return []
|
||||
res = []
|
||||
for d in data:
|
||||
if d['Id'] != '-':
|
||||
if d.get('Id') != '-':
|
||||
dev = {}
|
||||
dev['id'] = d['Id']
|
||||
dev['name'] = d['Name']
|
||||
@@ -436,7 +440,8 @@ class Movistar(object):
|
||||
if 'TituloHorLinea2' in p:
|
||||
pr['desc2'] = p['TituloHorLinea2']
|
||||
pr['show_id'] = p['ShowId']
|
||||
if 'links' in p: pr['links'] = p['links']
|
||||
pr['id'] = p['Id']
|
||||
#if 'links' in p: pr['links'] = p['links']
|
||||
epg[id].append(pr)
|
||||
|
||||
return epg
|
||||
@@ -497,8 +502,9 @@ class Movistar(object):
|
||||
ch['desc2'] = desc2
|
||||
plot += "[B]{}[/B] {} {}\n".format(programs[i]['start_str'], desc1, desc2)
|
||||
if self.add_extra_info:
|
||||
ch['art'] = {'poster': None, 'fanart': None}
|
||||
self.add_video_extra_info(programs[i]['show_id'], ch, catalog='events')
|
||||
ch['art']['poster'] = ch['art']['fanart'] = None
|
||||
ch['show_id'] = programs[i]['show_id']
|
||||
self.add_video_extra_info(ch)
|
||||
if 'plot' in ch['info']:
|
||||
plot = plot + ch['info']['plot']
|
||||
break
|
||||
@@ -541,6 +547,7 @@ class Movistar(object):
|
||||
t['stream_type'] = 'tv'
|
||||
t['info']['mediatype'] = 'movie'
|
||||
t['info']['title'] = str(c['Dial']) +'. ' + c['Nombre']
|
||||
t['channel_name'] = c['Nombre']
|
||||
t['id'] = c['CodCadenaTv']
|
||||
if add_epg_info:
|
||||
t['desc1'] = c['Nombre']
|
||||
@@ -650,8 +657,9 @@ class Movistar(object):
|
||||
elif video['AssetType'] == 'NPVR':
|
||||
t['stream_type'] = 'rec'
|
||||
t['session_request'] = '{"contentID":' + str(t['id']) + ', "streamType":"NPVR"}'
|
||||
if 'ShowId' in video: t['show_id'] = video['ShowId']
|
||||
if self.add_extra_info:
|
||||
self.add_video_extra_info(t['id'], t)
|
||||
self.add_video_extra_info(t)
|
||||
if 'Recording' in data:
|
||||
t['stream_type'] = 'rec'
|
||||
t['rec'] = {'id': data['Recording']['id'],
|
||||
@@ -680,11 +688,49 @@ class Movistar(object):
|
||||
res.append(t)
|
||||
return res
|
||||
|
||||
def add_video_extra_info(self, id, t, catalog=''):
|
||||
def add_video_extra_info(self, t):
|
||||
#LOG('add_video_extra_info: t: {}'.format(t))
|
||||
try:
|
||||
if 'show_id' in t:
|
||||
catalog = 'events'
|
||||
id = t['show_id']
|
||||
elif 'id' in t:
|
||||
catalog = ''
|
||||
id = t['id']
|
||||
else:
|
||||
return
|
||||
#LOG('id: {} catalog: {}'.format(id, catalog))
|
||||
|
||||
prefix = 'event' if catalog=='events' else 'info'
|
||||
cache_filename = 'cache/{}_{}.json'.format(prefix, id)
|
||||
content = self.cache.load(cache_filename, 30*24*60)
|
||||
if content:
|
||||
data = json.loads(content)
|
||||
else:
|
||||
url = self.endpoints['ficha'].format(deviceType='webplayer', id=id, profile=self.account['platform'], mediatype='FOTOV', version='7.1', mode='GLOBAL', catalog=catalog, channels='', state='', mdrm='true', demarcation=self.account['demarcation'], legacyBoxOffice='')
|
||||
#print(url)
|
||||
data = self.net.load_data(url)
|
||||
self.cache.save_file(cache_filename, json.dumps(data, ensure_ascii=False))
|
||||
|
||||
if not 'info' in t: t['info'] = {}
|
||||
if not 'art' in t: t['art'] = {}
|
||||
|
||||
if not t['info'].get('title'):
|
||||
if 'TituloEpisodio' in data:
|
||||
t['info']['title'] = data['TituloEpisodio']
|
||||
else:
|
||||
t['info']['title'] = data['Titulo']
|
||||
|
||||
if 'Serie' in data:
|
||||
if not t['info'].get('episode'): t['info']['episode'] = data['NumeroEpisodio']
|
||||
if not t['info'].get('tvshowtitle'): t['info']['tvshowtitle'] = data['Serie']['TituloSerie']
|
||||
if not t['info'].get('season') and data['Serie'].get('Temporada'):
|
||||
m = re.search(r'T(\d+)', data['Serie']['Temporada'])
|
||||
if m: t['info']['season'] = m.group(1)
|
||||
|
||||
if not 'duration' in t['info']:
|
||||
t['info']['duration'] = data.get('Duracion', 0) * 60
|
||||
|
||||
if data.get('Sinopsis'):
|
||||
t['info']['plot'] = data['Sinopsis']
|
||||
if data.get('Actores'):
|
||||
@@ -774,7 +820,7 @@ class Movistar(object):
|
||||
t['stream_type'] = 'u7d'
|
||||
t['session_request'] = '{"contentID":' + str(t['id']) + ', "streamType":"CUTV"}'
|
||||
if self.add_extra_info:
|
||||
self.add_video_extra_info(t['id'], t)
|
||||
self.add_video_extra_info(t)
|
||||
res.append(t)
|
||||
|
||||
return res
|
||||
@@ -829,19 +875,23 @@ class Movistar(object):
|
||||
t['stream_type'] = 'u7d'
|
||||
t['info']['mediatype'] = 'movie'
|
||||
t['url'] = ''
|
||||
t['id'] = p['show_id']
|
||||
t['id'] = p['id']
|
||||
t['show_id'] = p['show_id']
|
||||
t['session_request'] = '{"contentID":' + str(t['id']) +',"streamType":"CUTV"}'
|
||||
t['info']['playcount'] = 1 # Set as watched
|
||||
t['start'] = p['start']
|
||||
t['end'] = p['end']
|
||||
t['aired'] = (p['end'] <= (time.time() * 1000))
|
||||
t['subscribed'] = True # Fix me
|
||||
"""
|
||||
if 'links' in p:
|
||||
for link in p['links']:
|
||||
if link['rel'] == 'start-over':
|
||||
t['url'] = link['href']
|
||||
"""
|
||||
t['url'] = 'https://grmovistar.imagenio.telefonica.net/asfe/rest/tvMediaURLs?tvProgram.id='+ str(t['show_id']) +'&svc=startover'
|
||||
if self.add_extra_info:
|
||||
self.add_video_extra_info(t['id'], t, catalog='events')
|
||||
self.add_video_extra_info(t)
|
||||
res.append(t)
|
||||
return res
|
||||
|
||||
@@ -935,3 +985,111 @@ class Movistar(object):
|
||||
if link['sizes'] == '512x512':
|
||||
return link['href']
|
||||
return None
|
||||
|
||||
def export_channels(self):
|
||||
if sys.version_info[0] >= 3:
|
||||
from urllib.parse import urlencode
|
||||
else:
|
||||
from urllib import urlencode
|
||||
channels = self.get_channels(False)
|
||||
res = []
|
||||
for c in channels:
|
||||
if not c['subscribed']: continue
|
||||
t = {}
|
||||
t['name'] = c['channel_name']
|
||||
t['id'] = c['id']
|
||||
t['logo'] = c['art']['icon']
|
||||
t['preset'] = c['dial']
|
||||
args = urlencode({'action': 'play', 'stype': 'tv', 'id': c['id'], 'url': c['url'], 'session_request': c['session_request']})
|
||||
t['stream'] = 'plugin://plugin.video.movistarplus/?' + args
|
||||
res.append(t)
|
||||
return res
|
||||
|
||||
def export_channels_to_m3u8(self, filename):
|
||||
channels = self.export_channels()
|
||||
items = []
|
||||
for t in channels:
|
||||
item = '#EXTINF:-1 tvg-name="{name}" tvg-id="{id}" tvg-logo="{logo}" tvg-chno="{preset}" group-title="Movistar+" catchup="vod",{name}\n{stream}\n\n'.format(
|
||||
name=t['name'], id=t['id'], logo=t['logo'], preset=t['preset'], stream=t['stream'])
|
||||
items.append(item)
|
||||
res = '#EXTM3U\n## Movistar+\n{}'.format(''.join(items))
|
||||
with io.open(filename, 'w', encoding='utf-8', newline='') as handle:
|
||||
handle.write(res)
|
||||
|
||||
def export_epg(self):
|
||||
if sys.version_info[0] >= 3:
|
||||
from urllib.parse import urlencode
|
||||
else:
|
||||
from urllib import urlencode
|
||||
now = time.time()*1000
|
||||
res = {}
|
||||
epg = self.get_epg()
|
||||
channels = self.get_channels(False)
|
||||
for channel in channels:
|
||||
if not channel['subscribed']: continue
|
||||
id = channel['id']
|
||||
res[id] = []
|
||||
for e in epg[id]:
|
||||
t = {}
|
||||
t['title'] = e['desc1']
|
||||
t['subtitle'] = e['desc2']
|
||||
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')
|
||||
|
||||
aired = (e['end'] <= now)
|
||||
if aired:
|
||||
program_id = str(e['show_id'])
|
||||
url = 'https://grmovistar.imagenio.telefonica.net/asfe/rest/tvMediaURLs?tvProgram.id='+ program_id +'&svc=startover'
|
||||
session_request = '{"contentID":' + program_id +',"streamType":"CUTV"}'
|
||||
args = urlencode({'action': 'play', 'stype': 'u7d', 'id': program_id, 'url': url, 'session_request': session_request})
|
||||
t['stream'] = 'plugin://plugin.video.movistarplus/?' + args
|
||||
|
||||
"""
|
||||
if self.add_extra_info and id in ['HOLLYW', 'TCM', 'AMC', 'MV3', 'MV2', 'CPSER', 'FOXGE', 'TNT']:
|
||||
i = {'id': e['id'], 'show_id': e['show_id']}
|
||||
self.add_video_extra_info(i)
|
||||
#print(i)
|
||||
t['description'] = i['info'].get('plot')
|
||||
t['image'] = i['art'].get('poster')
|
||||
t['credits'] = []
|
||||
for text in i['info'].get('director', []):
|
||||
t['credits'].append({'type': 'director', 'name': text})
|
||||
for text in i['info'].get('cast', []):
|
||||
t['credits'].append({'type': 'actor', 'name': text})
|
||||
"""
|
||||
|
||||
res[id].append(t)
|
||||
return res
|
||||
|
||||
def export_epg_to_xml(self, filename):
|
||||
channels = self.export_channels()
|
||||
res = []
|
||||
res.append('<?xml version="1.0" encoding="UTF-8"?>\n' +
|
||||
'<!DOCTYPE tv SYSTEM "xmltv.dtd">\n' +
|
||||
'<tv>\n')
|
||||
|
||||
for t in channels:
|
||||
res.append('<channel id="{}">\n'.format(t['id']) +
|
||||
' <display-name>{}</display-name>\n'.format(t['name']) +
|
||||
' <icon src="{}"/>\n'.format(t['logo']) +
|
||||
'</channel>\n')
|
||||
|
||||
epg = self.export_epg()
|
||||
for ch in channels:
|
||||
for e in epg[ch['id']]:
|
||||
start = datetime.strptime(e['start'], "%Y-%m-%dT%H:%M:%SZ").strftime("%Y%m%d%H%M%S +0000")
|
||||
stop = datetime.strptime(e['stop'], "%Y-%m-%dT%H:%M:%SZ").strftime("%Y%m%d%H%M%S +0000")
|
||||
url = e.get('stream', None)
|
||||
if url:
|
||||
url = url.replace('&', '&')
|
||||
res.append('<programme start="{}" stop="{}" channel="{}"'.format(start, stop, ch['id']) +
|
||||
(' catchup-id="{}"'.format(url) if url else "") +
|
||||
'>\n' +
|
||||
' <title>{}</title>\n'.format(e['title']) +
|
||||
' <sub-title>{}</sub-title>'.format(e['subtitle']) +
|
||||
'</programme>\n')
|
||||
|
||||
res.append('</tv>\n')
|
||||
with io.open(filename, 'w', encoding='utf-8', newline='') as handle:
|
||||
handle.write(''.join(res))
|
||||
|
||||
@@ -148,6 +148,14 @@ def play(params):
|
||||
play_item.setMimeType('application/dash+xml')
|
||||
play_item.setContentLookup(False)
|
||||
|
||||
# Add info
|
||||
if addon.getSettingBool('add_extra_info') and stype in ['vod', 'u7d', 'rec']:
|
||||
t = {'id': params['id']}
|
||||
if 'show_id' in params: t['show_id'] = params['show_id']
|
||||
m.add_video_extra_info(t)
|
||||
play_item.setInfo('video', t['info'])
|
||||
play_item.setArt(t['art'])
|
||||
|
||||
# Subtitles
|
||||
if stype == 'vod' and addon.getSettingBool('use_ttml2ssa'):
|
||||
# Convert subtitles
|
||||
@@ -238,6 +246,7 @@ def add_videos(category, ctype, videos, ref=None, url_next=None, url_prev=None):
|
||||
list_item.addContextMenuItems([(addon.getLocalizedString(30173), "RunPlugin(" + action + ")")])
|
||||
|
||||
url = get_url(action='play', id=t['id'], url=t['url'], session_request=t['session_request'], stype=t['stream_type'])
|
||||
if 'show_id' in t: url += '&show_id={}'.format(t['show_id'])
|
||||
xbmcplugin.addDirectoryItem(_handle, url, list_item, False)
|
||||
elif t['type'] == 'series':
|
||||
list_item = xbmcgui.ListItem(label = title_name)
|
||||
@@ -466,6 +475,19 @@ def list_users():
|
||||
add_menu_option(addon.getLocalizedString(30150), get_url(action='logout')) # Close session
|
||||
close_folder()
|
||||
|
||||
def iptv(params):
|
||||
LOG('iptv: params: {}'.format(params))
|
||||
if m.logged:
|
||||
try:
|
||||
from .iptvmanager import IPTVManager
|
||||
port = int(params['port'])
|
||||
if params['action'] == 'iptv-channels':
|
||||
IPTVManager(port).send_channels(m)
|
||||
elif params['action'] == 'iptv-epg':
|
||||
IPTVManager(port).send_epg(m)
|
||||
except:
|
||||
pass
|
||||
|
||||
def router(paramstring):
|
||||
"""
|
||||
Router function that calls other functions
|
||||
@@ -523,6 +545,8 @@ def router(paramstring):
|
||||
list_vod()
|
||||
elif params['action'] == 'search':
|
||||
search(params)
|
||||
elif 'iptv' in params['action']:
|
||||
iptv(params)
|
||||
else:
|
||||
# Main
|
||||
open_folder(addon.getLocalizedString(30101)) # Menu
|
||||
@@ -582,9 +606,15 @@ def run():
|
||||
if profile_id in ['OTT', 'NODTH']:
|
||||
m.account['platform'] = addon.getSetting('profile_id')
|
||||
|
||||
# Clear cache
|
||||
LOG('Cleaning cache. {} files removed.'.format(m.cache.clear_cache()))
|
||||
|
||||
global player
|
||||
player = Player()
|
||||
|
||||
# Call the router function and pass the plugin call parameters to it.
|
||||
# We use string slicing to trim the leading '?' from the plugin call paramstring
|
||||
router(sys.argv[2][1:])
|
||||
params = sys.argv[2][1:]
|
||||
if '/iptv/channels' in sys.argv[0]: params += '&action=iptv-channels'
|
||||
elif '/iptv/epg' in sys.argv[0]: params += '&action=iptv-epg'
|
||||
router(params)
|
||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 216 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 148 KiB |
@@ -4,7 +4,7 @@
|
||||
<setting label="30020" type="bool" id="channels_with_epg" default="true"/>
|
||||
<setting label="30021" type="bool" id="add_extra_info" default="true"/>
|
||||
<setting id="use_ttml2ssa" type="bool" label="$ADDON[script.module.ttml2ssa 32202]" default="false"/>
|
||||
<setting id="ssa_settings" type="action" label="$ADDON[script.module.ttml2ssa 32201]" option="close" action="Addon.OpenSettings(script.module.ttml2ssa)" enable="eq(-1,true)"/>
|
||||
<setting id="ssa_settings" type="action" label="$ADDON[script.module.ttml2ssa 32201]" option="close" action="Addon.OpenSettings(script.module.ttml2ssa)" enable="eq(-1,true)" subsetting="true"/>
|
||||
<setting label="30019" type="action" id="is_settings" action="Addon.OpenSettings(inputstream.adaptive)" enable="System.HasAddon(inputstream.adaptive)" option="close"/>
|
||||
<setting id="reuse_devices" type="bool" label="30023" default="false" visible="true"/>
|
||||
<setting id="open_session" type="bool" label="30022" default="true" visible="true"/>
|
||||
@@ -14,12 +14,19 @@
|
||||
<category label="30002">
|
||||
<setting label="30011" type="bool" id="manifest_modification" default="true"/>
|
||||
<setting label="30012" type="bool" id="use_proxy_for_license" default="true"/>
|
||||
<setting id="reregister" type="bool" label="30024" default="true" enable="eq(-1,true)"/>
|
||||
<setting id="reregister" type="bool" label="30024" default="true" enable="eq(-1,true)" subsetting="true"/>
|
||||
</category>
|
||||
<!--
|
||||
<category label="30003">
|
||||
<setting label="30010" type="labelenum" id="drm_type" values="Widevine|Playready"/>
|
||||
</category>
|
||||
-->
|
||||
<category label="30300">
|
||||
<setting label="30301" type="action" action="InstallAddon(service.iptv.manager)" option="close" visible="!System.HasAddon(service.iptv.manager)"/>
|
||||
<setting label="30302" type="bool" id="iptv.enabled" default="true" visible="System.HasAddon(service.iptv.manager)" />
|
||||
<setting label="30303" type="action" action="Addon.OpenSettings(service.iptv.manager)" enable="eq(-1,true)" option="close" visible="System.HasAddon(service.iptv.manager)" subsetting="true"/>
|
||||
<setting id="iptv.channels_uri" default="plugin://plugin.video.movistarplus/iptv/channels" visible="false"/>
|
||||
<setting id="iptv.epg_uri" default="plugin://plugin.video.movistarplus/iptv/epg" visible="false"/>
|
||||
</category>
|
||||
</settings>
|
||||
|
||||