diff --git a/addon.xml b/addon.xml index 9734c8c..62057a4 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index fa87ac6..65e244c 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -91,6 +91,10 @@ msgctxt "#30024" msgid "Try to fix error 4027" msgstr "" +msgctxt "#30025" +msgid "Send video progress to provider's server" +msgstr "" + # For SkyOTT only msgctxt "#30031" msgid "Streaming service" @@ -204,6 +208,10 @@ msgctxt "#30121" msgid "Kids" msgstr "" +msgctxt "#30122" +msgid "Continue watching" +msgstr "" + msgctxt "#30150" msgid "Close session" msgstr "" @@ -281,6 +289,22 @@ msgctxt "#30174" msgid "Do you want to delete {}?" msgstr "" +msgctxt "#30175" +msgid "Add to my list" +msgstr "" + +msgctxt "#30176" +msgid "Remove from my list" +msgstr "" + +msgctxt "#30177" +msgid "Title added to my list" +msgstr "" + +msgctxt "#30178" +msgid "Title removed from my list" +msgstr "" + msgctxt "#30180" msgid "Profiles" msgstr "" @@ -333,6 +357,14 @@ msgctxt "#30204" msgid "Video not available" msgstr "" +msgctxt "#30205" +msgid "No playback URL" +msgstr "" + +msgctxt "#30206" +msgid "Proxy is not running" +msgstr "" + # For Orange and Movistar only msgctxt "#30300" msgid "IPTV Manager" diff --git a/resources/language/resource.language.es_es/strings.po b/resources/language/resource.language.es_es/strings.po index 9914e34..ddb69d8 100644 --- a/resources/language/resource.language.es_es/strings.po +++ b/resources/language/resource.language.es_es/strings.po @@ -81,6 +81,10 @@ msgctxt "#30024" msgid "Try to fix error 4027" msgstr "Intentar solucionar el error 4027" +msgctxt "#30025" +msgid "Send video progress to provider's server" +msgstr "Enviar progreso al servidor de la plataforma" + msgctxt "#30031" msgid "Streaming service" msgstr "Plataforma" @@ -185,6 +189,10 @@ msgctxt "#30121" msgid "Kids" msgstr "Infantiles" +msgctxt "#30122" +msgid "Continue watching" +msgstr "Seguir viendo" + msgctxt "#30150" msgid "Close session" msgstr "Cerrar sesión" @@ -257,6 +265,22 @@ msgctxt "#30174" msgid "Do you want to delete {}?" msgstr "¿Quieres borrar {}?" +msgctxt "#30175" +msgid "Add to my list" +msgstr "Añadir a mi lista" + +msgctxt "#30176" +msgid "Remove from my list" +msgstr "Quitar de mi lista" + +msgctxt "#30177" +msgid "Title added to my list" +msgstr "Título añadido a mi lista" + +msgctxt "#30178" +msgid "Title removed from my list" +msgstr "Título quitado de mi lista" + msgctxt "#30180" msgid "Profiles" msgstr "Perfiles" @@ -309,6 +333,14 @@ msgctxt "#30204" msgid "Video not available" msgstr "Vídeo no disponible" +msgctxt "#30205" +msgid "No playback URL" +msgstr "No hay URL del vídeo" + +msgctxt "#30206" +msgid "Proxy is not running" +msgstr "El proxy no está funcionando" + msgctxt "#30300" msgid "IPTV Manager" msgstr "IPTV Manager" diff --git a/resources/lib/movistar.py b/resources/lib/movistar.py index 41fafeb..6a870ce 100644 --- a/resources/lib/movistar.py +++ b/resources/lib/movistar.py @@ -333,7 +333,6 @@ def unregister_device(self): return self.delete_device(self.account['device_id']) def new_device_id(self): - """ headers = self.net.headers.copy() headers['Content-Type'] = 'application/json' headers['x-movistarplus-ui'] = '2.36.30' @@ -343,11 +342,11 @@ def new_device_id(self): response = self.net.session.post(url, headers=headers) content = response.content.decode('utf-8') return content.strip('"') - """ - import random - s = '' - for _ in range(0, 32): s += random.choice('abcdef0123456789') - return s + + #import random + #s = '' + #for _ in range(0, 32): s += random.choice('abcdef0123456789') + #return s def delete_device(self, device_id): headers = self.net.headers.copy() @@ -595,9 +594,38 @@ def download_list(self, url, use_hz = False): if use_hz: headers['Authorization'] = 'Bearer ' + self.account['access_token'] headers['X-Hzid'] = self.account['session_token'] - return self.net.load_data(url, headers) + def add_to_wishlist(self, id, stype='vod'): + LOG('add_to_wishlist: {} {}'.format(id, stype)) + url = self.endpoints['marcadofavoritos2'].format(family=stype) + headers = self.net.headers.copy() + headers['Accept'] = 'application/vnd.miviewtv.v1+json' + headers['Content-Type'] = 'application/json' + headers['X-Hzid'] = self.account['session_token'] + post_data = {'objectID': id} + response = self.net.session.post(url, data=json.dumps(post_data), headers=headers) + content = response.content.decode('utf-8') + if response.status_code != 201: + data = json.loads(content) + if 'resultCode' in data: + return data['resultCode'], data['resultText'] + return response.status_code, '' + + def delete_from_wishlist(self, id, stype='vod'): + url = self.endpoints['borradofavoritos'].format(family=stype, contentId=id) + headers = self.net.headers.copy() + headers['Accept'] = 'application/vnd.miviewtv.v1+json' + headers['Content-Type'] = 'application/json' + headers['X-Hzid'] = self.account['session_token'] + response = self.net.session.delete(url, headers=headers) + content = response.content.decode('utf-8') + if response.status_code != 204: + data = json.loads(content) + if 'resultCode' in data: + return data['resultCode'], data['resultText'] + return response.status_code, '' + def get_wishlist_url(self): url = self.endpoints['favoritos'].format( deviceType='webplayer', DIGITALPLUSUSERIDC=self.account['encoded_user'], PROFILE=self.account['platform'], @@ -642,6 +670,8 @@ def get_title(self, data): t['art']['thumb'] = t['art']['poster'] t['info']['genre'] = ed['GeneroComAntena'] if ed.get('TipoComercial') == 'Impulsivo': return None # Alquiler + if 'Seguible' in ed: t['seguible'] = ed['Seguible'] + if 'links' in data: t['links'] = data['links'] if ed['TipoContenido'] in ['Individual', 'Episodio']: t['type'] = 'movie' t['stream_type'] = 'vod' @@ -681,7 +711,13 @@ def get_title(self, data): t['type'] = 'season' t['info']['mediatype'] = 'season' t['subscribed'] = self.is_subscribed_vod(data.get('tvProducts', [])) - + if 'DatosAccesoAnonimo' in data: + da = data['DatosAccesoAnonimo'] + if 'HoraInicio' in da and da['HoraInicio'] != None: + t['start'] = int(da['HoraInicio']) + t['start_str'] = timestamp2str(t['start']) + t['date_str'] = timestamp2str(t['start'], '%a %d %H:%M') + if t['url'] == '': t['info']['title'] += ' (' + t['date_str'] +')' return t def get_list(self, data): @@ -766,6 +802,7 @@ def add_video_extra_info(self, t): def get_seasons(self, id): url = self.endpoints['ficha'].format(deviceType='webplayer', id=id, profile=self.account['platform'], mediatype='FOTOV', version='7.1', mode='GLOBAL', catalog='', channels='', state='', mdrm='true', demarcation=self.account['demarcation'], legacyBoxOffice='') + #print(url) data = self.net.load_data(url) #print_json(data) res = [] @@ -783,6 +820,7 @@ def get_seasons(self, id): t['info']['season'] = c t['art']['poster'] = t['art']['thumb'] = data['Imagen'] t['subscribed'] = self.is_subscribed_vod(data.get('tvProducts', [])) + if 'Seguible' in data: t['seguible'] = data['Seguible'] c += 1 res.append(t) @@ -826,6 +864,14 @@ def get_episodes(self, id): elif video['AssetType'] == 'U7D': t['stream_type'] = 'u7d' t['session_request'] = '{"contentID":' + str(t['id']) + ', "streamType":"CUTV"}' + if 'ShowId' in video: t['show_id'] = video['ShowId'] + if 'DatosAccesoAnonimo' in d: + da = d['DatosAccesoAnonimo'] + if 'HoraInicio' in da and da['HoraInicio'] != None: + t['start'] = int(da['HoraInicio']) + t['start_str'] = timestamp2str(t['start']) + t['date_str'] = timestamp2str(t['start'], '%a %d %H:%M') + if t['url'] == '': t['info']['title'] += ' (' + t['date_str'] +')' if self.add_extra_info: self.add_video_extra_info(t) res.append(t) @@ -977,6 +1023,10 @@ def save_key_file(self, d): data = {'timestamp': int(time.time()*1000), 'response': d} self.cache.save_file('auth.key', json.dumps(data, ensure_ascii=False)) + def delete_session_files(self): + for f in ['access_token.conf', 'account.json', 'device_id.conf', 'devices.json', 'profile_id.conf', 'tokens.json']: + self.cache.remove_file(f) + def get_profile_image_url(self, img_id): content = self.cache.load_file('avatars.json') if content: diff --git a/resources/lib/player.py b/resources/lib/player.py new file mode 100644 index 0000000..e605bff --- /dev/null +++ b/resources/lib/player.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# encoding: utf-8 +# +# SPDX-License-Identifier: LGPL-2.1-or-later + +from __future__ import unicode_literals, absolute_import, division + +import xbmc +from .log import LOG + +class MyPlayer(xbmc.Player): + + def __init__(self): + super(MyPlayer, self).__init__() + self.running = True + + def onPlayBackEnded(self): + LOG('onPlayBackEnded') + self.finish() + + def onPlayBackError(self): + LOG('onPlayBackError') + self.finish() + + def onPlayBackStopped(self): + LOG('onPlayBackStopped') + self.finish() + + def finish(self): + LOG('finish') + self.running = False diff --git a/resources/lib/plugin.py b/resources/lib/plugin.py index ab9d253..4cdc214 100644 --- a/resources/lib/plugin.py +++ b/resources/lib/plugin.py @@ -65,8 +65,10 @@ def play(params): m.register_device() """ + session_opened = False if addon.getSettingBool('open_session') or stype == 'vod': d = m.open_session(params['session_request'], session_token) + session_opened = True LOG('Open session: d: {}'.format(d)) if d['resultCode'] != 0: show_notification(d['resultText']) @@ -76,6 +78,7 @@ def play(params): if not addon.getSettingBool('open_session'): d = m.delete_session() + session_opened = False LOG('Delete session: d: {}'.format(d)) LOG("token: {} session_token: {}".format(token, session_token)) @@ -193,15 +196,20 @@ def play(params): xbmcplugin.setResolvedUrl(_handle, True, listitem=play_item) - player.running = True - monitor = xbmc.Monitor() - while not monitor.abortRequested() and player.running: - if monitor.waitForAbort(1): - break - LOG('Exiting play') + LOG('**** session_opened: {}'.format(session_opened)) + if session_opened: + from .player import MyPlayer + player = MyPlayer() + monitor = xbmc.Monitor() + while not monitor.abortRequested() and player.running: + monitor.waitForAbort(10) + #LOG('**** waiting') + d = m.delete_session() + LOG('Delete session: d: {}'.format(d)) + LOG('Playback finished') -def add_videos(category, ctype, videos, ref=None, url_next=None, url_prev=None): +def add_videos(category, ctype, videos, ref=None, url_next=None, url_prev=None, from_wishlist=False): #LOG("category: {} ctype: {}".format(category, ctype)) xbmcplugin.setPluginCategory(_handle, category) xbmcplugin.setContent(_handle, ctype) @@ -229,6 +237,33 @@ def add_videos(category, ctype, videos, ref=None, url_next=None, url_prev=None): t['info']['title'] = m.colorize_title(t) title_name = t['info']['title'] if not 'type' in t: continue + + wishlist_action = None + if t['type'] in ['movie', 'series', 'season']: + id = t['id'] + stype = 'vod' + possible_to_add = False + + if t['type'] in ['series', 'season']: + possible_to_add = t.get('seguible', False) + + if t['type'] == 'movie': + if t.get('stream_type') == 'vod': + possible_to_add = t['url'] != '' + if 'show_id' in t: + possible_to_add = t['url'] != '' + stype = 'tv' + id = t['show_id'] + + if possible_to_add: + if not from_wishlist: + op = 'add' + message = 30175 + else: + op = 'delete' + message = 30176 + wishlist_action = (addon.getLocalizedString(message), "RunPlugin(" + get_url(action='to_wishlist', id=id, op=op, stype=stype) + ")") + if t['type'] == 'movie': list_item = xbmcgui.ListItem(label = title_name) #if t['url'] == '': @@ -245,6 +280,9 @@ def add_videos(category, ctype, videos, ref=None, url_next=None, url_prev=None): action = get_url(action='delete_recording', id=t['rec']['id'], name=t['rec']['name']) list_item.addContextMenuItems([(addon.getLocalizedString(30173), "RunPlugin(" + action + ")")]) + if wishlist_action: + list_item.addContextMenuItems([wishlist_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) @@ -252,11 +290,15 @@ def add_videos(category, ctype, videos, ref=None, url_next=None, url_prev=None): list_item = xbmcgui.ListItem(label = title_name) list_item.setInfo('video', t['info']) list_item.setArt(t['art']) + if wishlist_action: + list_item.addContextMenuItems([wishlist_action]) xbmcplugin.addDirectoryItem(_handle, get_url(action='series', id=t['id'], name=title_name), list_item, True) elif t['type'] == 'season': list_item = xbmcgui.ListItem(label = title_name) list_item.setInfo('video', t['info']) list_item.setArt(t['art']) + if wishlist_action: + list_item.addContextMenuItems([wishlist_action]) xbmcplugin.addDirectoryItem(_handle, get_url(action='season', id=t['id'], name=title_name), list_item, True) elif t['type'] == 'category': list_item = xbmcgui.ListItem(label = title_name) @@ -358,13 +400,13 @@ def listing(name, url): url_prev = data['prev']['href'] if isinstance(data['next'], dict) and 'prev' in data['next'] else None add_videos(name, 'movies', l, url_next=url_next, url_prev=url_prev, ref='listing') -def listing_hz(name, url): +def listing_hz(name, url, from_wishlist=False): data = m.download_list(url, use_hz=True) if 'Contenidos' in data: l = m.get_list(data['Contenidos']) url_next = data['next']['href'] if isinstance(data['next'], dict) and 'href' in data['next'] else None url_prev = data['prev']['href'] if isinstance(data['next'], dict) and 'prev' in data['next'] else None - add_videos(name, 'movies', l, url_next=url_next, url_prev=url_prev, ref='listing_hz') + add_videos(name, 'movies', l, url_next=url_next, url_prev=url_prev, ref='listing_hz', from_wishlist=from_wishlist) def list_vod(): open_folder(addon.getLocalizedString(30111)) # VOD @@ -429,12 +471,7 @@ def delete_recording(id, name): xbmc.executebuiltin("Container.Refresh") def clear_session(): - m.cache.remove_file('access_token.conf') - m.cache.remove_file('account.json') - m.cache.remove_file('device_id.conf') - m.cache.remove_file('devices.json') - m.cache.remove_file('profile_id.conf') - m.cache.remove_file('tokens.json') + m.delete_session_files() def logout(): clear_session() @@ -504,6 +541,21 @@ def export_epg_now(): show_notification(addon.getLocalizedString(30311), xbmcgui.NOTIFICATION_INFO) m.export_epg_to_xml(epg_filename) +def to_wishlist(params): + stype = params['stype'] + if params['op'] == 'add': + retcode, message = m.add_to_wishlist(params['id'], stype) + else: + retcode, message = m.delete_from_wishlist(params['id'], stype) + if retcode in [201, 204]: + message = 30177 if params['op'] == 'add' else 30178 + show_notification(addon.getLocalizedString(message), xbmcgui.NOTIFICATION_INFO) + if params['op'] == 'delete': + xbmc.executebuiltin("Container.Refresh") + else: + show_notification(str(retcode) +': '+ message) + + def router(paramstring): """ Router function that calls other functions @@ -545,7 +597,7 @@ def router(paramstring): delete_recording(params['id'], params['name']) elif params['action'] == 'wishlist': # Wishlist - listing_hz(addon.getLocalizedString(30102), m.get_wishlist_url()) + listing_hz(addon.getLocalizedString(30102), m.get_wishlist_url(), from_wishlist=True) elif params['action'] == 'recordings': # Recordings listing_hz(addon.getLocalizedString(30103), m.get_recordings_url()) @@ -563,6 +615,8 @@ def router(paramstring): search(params) elif params['action'] == 'export_epg_now': export_epg_now() + elif params['action'] == 'to_wishlist': + to_wishlist(params) elif 'iptv' in params['action']: iptv(params) else: @@ -584,33 +638,6 @@ def router(paramstring): close_folder(cacheToDisc=False) -class Player(xbmc.Player): - running = False - - def onAVStarted(self): - LOG('Playback started') - - def onPlayBackPaused(self): - LOG('Playback paused') - - def onPlayBackResumed(self): - LOG('Playback resumed') - - def onPlayBackEnded(self): - LOG('Playback ended') - self.close_session() - - def onPlayBackStopped(self): - LOG('Playback stopped') - self.close_session() - - def close_session(self): - if self.running: - LOG('Closing session') - d = m.delete_session() - LOG('delete_session: {}'.format(d)) - self.running = False - def run(): global m reuse_devices = addon.getSettingBool('reuse_devices') @@ -627,9 +654,6 @@ def run(): # 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 params = sys.argv[2][1:] diff --git a/resources/settings.xml b/resources/settings.xml index 46f15b2..bf3f282 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -1,8 +1,8 @@ - - + +