diff --git a/Makefile b/Makefile
index bc6f2b34..2aeca569 100644
--- a/Makefile
+++ b/Makefile
@@ -80,6 +80,7 @@ build: clean
@echo -e "$(white)=$(blue) Successfully wrote package as: $(white)../$(zip_name)$(reset)"
clean:
+ @echo -e "$(white)=$(blue) Cleaning up$(reset)"
find . -name '*.py[cod]' -type f -delete
find . -name '__pycache__' -type d -delete
rm -rf .pytest_cache/ .tox/
diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po
index 9d39d073..0ffd9249 100644
--- a/resources/language/resource.language.en_gb/strings.po
+++ b/resources/language/resource.language.en_gb/strings.po
@@ -756,18 +756,6 @@ msgctxt "#30874"
msgid "Open Up Next service add-on settings."
msgstr ""
-msgctxt "#30875"
-msgid "Install Twitter add-on"
-msgstr ""
-
-msgctxt "#30877"
-msgid "Enable Twitter integration"
-msgstr ""
-
-msgctxt "#30879"
-msgid "Twitter settings…"
-msgstr ""
-
msgctxt "#30881"
msgid "Install PySocks library"
msgstr ""
diff --git a/resources/language/resource.language.nl_nl/strings.po b/resources/language/resource.language.nl_nl/strings.po
index 57c80018..1be283ff 100644
--- a/resources/language/resource.language.nl_nl/strings.po
+++ b/resources/language/resource.language.nl_nl/strings.po
@@ -756,18 +756,6 @@ msgctxt "#30874"
msgid "Open Up Next service add-on settings."
msgstr "Open de Up Next service add-on instellingen."
-msgctxt "#30875"
-msgid "Install Twitter add-on"
-msgstr "Installeer de Twitter add-on"
-
-msgctxt "#30877"
-msgid "Enable Twitter integration"
-msgstr "Activeer Twitter integratie"
-
-msgctxt "#30879"
-msgid "Twitter settings…"
-msgstr "Twitter instellingen…"
-
msgctxt "#30881"
msgid "Install PySocks library"
msgstr "Installeer de PySocks library"
diff --git a/resources/lib/apihelper.py b/resources/lib/apihelper.py
index f21c1895..31ac8b34 100644
--- a/resources/lib/apihelper.py
+++ b/resources/lib/apihelper.py
@@ -160,8 +160,14 @@ def __map_seasons(self, program, seasons, episodes):
content = 'seasons'
episode = random.choice(episodes)
- info_labels = self._metadata.get_info_labels(episode, season=True)
program_type = episode.get('programType')
+ info_labels = self._metadata.get_info_labels(episode, season=True)
+ info_labels.update(
+ episode=len(episodes), # Total number of episodes in '* All seasons'
+ season=len(seasons), # Total number of seasons in '* All seasons'
+ tagline=localize(30133), # All seasons
+ title=localize(30133), # All seasons
+ )
# Reverse sort seasons if program_type is 'reeksaflopend' or 'daily'
if program_type in ('daily', 'reeksaflopend'):
@@ -173,7 +179,7 @@ def __map_seasons(self, program, seasons, episodes):
label=localize(30133), # All seasons
path=url_for('programs', program=program, season='allseasons'),
art_dict=self._metadata.get_art(episode, season='allseasons'),
- info_dict=info_labels,
+ info_dict=info_labels.copy(),
))
# NOTE: Sort the episodes ourselves, because Kodi does not allow to set to 'ascending'
@@ -181,18 +187,26 @@ def __map_seasons(self, program, seasons, episodes):
for season in seasons:
season_key = season.get('key', '')
+ episodelist = [e for e in episodes if e.get('seasonName') == season_key]
# If more than 300 episodes exist, we may end up with an empty season (Winteruur)
try:
- episode = random.choice([e for e in episodes if e.get('seasonName') == season_key])
+ episode = random.choice(episodelist)
except IndexError:
episode = episodes[0]
label = '%s %s' % (localize(30131), season_key) # Season X
+ info_labels.update(
+ episode=len(episodelist), # Number of episodes in this folder
+ season=1, # Number of seasons in this folder
+ tagline=label,
+ title=label,
+ )
+
season_items.append(TitleItem(
label=label,
path=url_for('programs', program=program, season=season_key),
art_dict=self._metadata.get_art(episode, season=True),
- info_dict=info_labels,
+ info_dict=info_labels.copy(),
prop_dict=self._metadata.get_properties(episode),
))
return season_items, sort, ascending, content
@@ -641,7 +655,7 @@ def list_channels(self, channels=None, live=True):
label += ' [COLOR=yellow]| %s[/COLOR]' % playing_now
# A single Live channel means it is the entry for channel's TV Show listing, so make it stand out
if channels and len(channels) == 1:
- label = '[B]%s[/B]' % label
+ label = '[COLOR yellow][B]%s[/B][/COLOR]' % label
is_playable = True
if channel.get('name') in ['een', 'canvas', 'ketnet']:
if get_setting_bool('showfanart', default=True):
@@ -650,7 +664,16 @@ def list_channels(self, channels=None, live=True):
else:
plot = localize(30142, **channel) # Watch live
# NOTE: Playcount and resumetime are required to not have live streams as "Watched" and resumed
- info_dict = dict(title=label, plot=plot, studio=channel.get('studio'), mediatype='video', playcount=0, duration=0)
+ info_dict = dict(
+ title=label,
+ plot=plot,
+ plotoutline=playing_now,
+ tagline=playing_now,
+ studio=channel.get('studio'),
+ mediatype='video',
+ playcount=0,
+ duration=0,
+ )
prop_dict = dict(resumetime=0)
stream_dict = dict(duration=0)
context_menu.append((
@@ -700,10 +723,10 @@ def list_youtube(channels=None):
label = localize(30143, **youtube) # Channel on YouTube
# A single Live channel means it is the entry for channel's TV Show listing, so make it stand out
if channels and len(channels) == 1:
- label = '[B]%s[/B]' % label
+ label = '[COLOR yellow][B]%s[/B][/COLOR]' % label
plot = localize(30144, **youtube) # Watch on YouTube
# NOTE: Playcount is required to not have live streams as "Watched"
- info_dict = dict(title=label, plot=plot, studio=channel.get('studio'), mediatype='video', playcount=0)
+ info_dict = dict(title=label, plot=plot, studio=channel.get('studio'), playcount=0)
context_menu = [(
localize(30413), # Refresh menu
diff --git a/resources/lib/kodiutils.py b/resources/lib/kodiutils.py
index dafa3b8e..1ea104f9 100644
--- a/resources/lib/kodiutils.py
+++ b/resources/lib/kodiutils.py
@@ -15,17 +15,17 @@
SORT_METHODS = dict(
# date=xbmcplugin.SORT_METHOD_DATE,
- dateadded=xbmcplugin.SORT_METHOD_DATEADDED,
- duration=xbmcplugin.SORT_METHOD_DURATION,
- episode=xbmcplugin.SORT_METHOD_EPISODE,
- # genre=xbmcplugin.SORT_METHOD_GENRE,
- # label=xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE,
- label=xbmcplugin.SORT_METHOD_LABEL,
- title=xbmcplugin.SORT_METHOD_TITLE,
- # none=xbmcplugin.SORT_METHOD_UNSORTED,
+ dateadded=dict(method=xbmcplugin.SORT_METHOD_DATEADDED, label2='%a'),
+ duration=dict(method=xbmcplugin.SORT_METHOD_DURATION, label2='%D'),
+ episode=dict(method=xbmcplugin.SORT_METHOD_EPISODE, label2='%D'),
+ # genre=dict(method=xbmcplugin.SORT_METHOD_GENRE, label2='%D'),
+ # label=dict(method=xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE, label2='%D'),
+ label=dict(method=xbmcplugin.SORT_METHOD_LABEL, label2='%D'),
+ title=dict(method=xbmcplugin.SORT_METHOD_TITLE, label2='%D'),
+ # none=dict(method=xbmcplugin.SORT_METHOD_UNSORTED, label2='%D'),
# FIXME: We would like to be able to sort by unprefixed title (ignore date/episode prefix)
- # title=xbmcplugin.SORT_METHOD_TITLE_IGNORE_THE,
- unsorted=xbmcplugin.SORT_METHOD_UNSORTED,
+ # title=dict(method=xbmcplugin.SORT_METHOD_TITLE_IGNORE_THE, label2='%D'),
+ unsorted=dict(method=xbmcplugin.SORT_METHOD_UNSORTED, label2='%D'),
)
WEEKDAY_LONG = {
@@ -86,6 +86,16 @@ def __missing__(self, key):
return '{' + key + '}'
+def translate_path(path):
+ """Translate special xbmc paths"""
+ return to_unicode(xbmc.translatePath(path))
+
+
+def get_addon_info(key):
+ """Return addon information"""
+ return to_unicode(ADDON.getAddonInfo(key))
+
+
def addon_icon():
"""Cache and return add-on icon"""
return get_addon_info('icon')
@@ -108,12 +118,17 @@ def addon_name():
def addon_path():
"""Cache and return add-on path"""
- return get_addon_info('path')
+ return translate_path(get_addon_info('path'))
def addon_profile():
"""Cache and return add-on profile"""
- return to_unicode(xbmc.translatePath(ADDON.getAddonInfo('profile')))
+ return translate_path(ADDON.getAddonInfo('profile'))
+
+
+def addon_version():
+ """Cache and return add-on version"""
+ return get_addon_info('version')
def url_for(name, *args, **kwargs):
@@ -164,12 +179,12 @@ def show_listing(list_items, category=None, sort='unsorted', ascending=True, con
sort = 'unsorted'
# Add all sort methods to GUI (start with preferred)
- xbmcplugin.addSortMethod(handle=plugin.handle, sortMethod=SORT_METHODS[sort])
+ xbmcplugin.addSortMethod(handle=plugin.handle, sortMethod=SORT_METHODS[sort]['method'], label2Mask=SORT_METHODS[sort]['label2'])
for key in sorted(SORT_METHODS):
if key != sort:
- xbmcplugin.addSortMethod(handle=plugin.handle, sortMethod=SORT_METHODS[key])
+ xbmcplugin.addSortMethod(handle=plugin.handle, sortMethod=SORT_METHODS[key]['method'], label2Mask=SORT_METHODS[key]['label2'])
- # FIXME: This does not appear to be working, we have to order it ourselves
+ # FIXME: This does not appear to be working, we have to order it ourselves and use 'unsorted' method
# xbmcplugin.setProperty(handle=plugin.handle, key='sort.ascending', value='true' if ascending else 'false')
# if ascending:
# xbmcplugin.setProperty(handle=plugin.handle, key='sort.order', value=str(SORT_METHODS[sort]))
@@ -185,7 +200,7 @@ def show_listing(list_items, category=None, sort='unsorted', ascending=True, con
# - item is a playable file (playable, path)
# - item is non-actionable item (not playable, no path)
is_folder = bool(not title_item.is_playable and title_item.path)
- is_playable = bool(title_item.is_playable and title_item.path)
+ is_playable = bool(title_item.is_playable and not title_item.path.endswith('/noop'))
list_item = ListItem(label=title_item.label)
@@ -220,6 +235,10 @@ def show_listing(list_items, category=None, sort='unsorted', ascending=True, con
# type is one of: video, music, pictures, game
list_item.setInfo(type='video', infoLabels=title_item.info_dict)
+ # Add number of episodes to folders
+ if is_folder and title_item.info_dict.get('episode'):
+ list_item.setLabel2(str(title_item.info_dict.get('episode')))
+
if title_item.stream_dict:
# type is one of: video, audio, subtitle
list_item.addStreamInfo('video', title_item.stream_dict)
@@ -299,6 +318,14 @@ def get_search_string(search_string=None):
return search_string
+def multiselect(heading='', options=None, autoclose=0, preselect=None, use_details=False):
+ """Show a Kodi multi-select dialog"""
+ from xbmcgui import Dialog
+ if not heading:
+ heading = addon_name()
+ return Dialog().multiselect(heading=heading, options=options, autoclose=autoclose, preselect=preselect, useDetails=use_details)
+
+
def ok_dialog(heading='', message=''):
"""Show Kodi's OK dialog"""
from xbmcgui import Dialog
@@ -317,14 +344,6 @@ def notification(heading='', message='', icon='info', time=4000):
Dialog().notification(heading=heading, message=message, icon=icon, time=time)
-def multiselect(heading='', options=None, autoclose=0, preselect=None, use_details=False):
- """Show a Kodi multi-select dialog"""
- from xbmcgui import Dialog
- if not heading:
- heading = addon_name()
- return Dialog().multiselect(heading=heading, options=options, autoclose=autoclose, preselect=preselect, useDetails=use_details)
-
-
def set_locale():
"""Load the proper locale for date strings, only once"""
if hasattr(set_locale, 'cached'):
@@ -355,7 +374,7 @@ def localize(string_id, **kwargs):
def localize_time(time):
"""Return localized time"""
time_format = xbmc.getRegion('time').replace(':%S', '') # Strip off seconds
- return time.strftime(time_format).lstrip('0') # Remove leading zero on all platforms
+ return time.strftime(time_format)
def localize_date(date, strftime):
@@ -699,11 +718,6 @@ def get_cache_path():
return getattr(get_cache_path, 'cached')
-def get_addon_info(key):
- """Return addon information"""
- return to_unicode(ADDON.getAddonInfo(key))
-
-
def listdir(path):
"""Return all files in a directory (using xbmcvfs)"""
from xbmcvfs import listdir as vfslistdir
diff --git a/resources/lib/metadata.py b/resources/lib/metadata.py
index d2b576ad..d02a24e5 100644
--- a/resources/lib/metadata.py
+++ b/resources/lib/metadata.py
@@ -414,7 +414,7 @@ def get_episode(self, api_data):
# VRT NU Suggest API
if api_data.get('type') == 'program':
- return int()
+ return api_data.get('episode_count', int()) # The number of episodes
# VRT NU Schedule API (some are missing vrt.whatson-id)
if api_data.get('vrt.whatson-id') or api_data.get('startTime'):
@@ -604,7 +604,6 @@ def get_info_labels(self, api_data, season=False, date=None, channel=None):
if api_data.get('type') == 'episode':
info_labels = dict(
title=self.get_title(api_data),
- # sorttitle=self.get_title(api_data), # NOTE: Does not appear to work
tvshowtitle=self.get_tvshowtitle(api_data),
# date=self.get_date(api_data), # NOTE: Not sure when or how this is used
aired=self.get_aired(api_data),
@@ -628,6 +627,7 @@ def get_info_labels(self, api_data, season=False, date=None, channel=None):
info_labels = dict(
tvshowtitle=self.get_tvshowtitle(api_data),
plot=self.get_plot(api_data),
+ episode=self.get_episode(api_data),
mediatype=self.get_mediatype(api_data, season=season),
studio=self.get_studio(api_data),
tag=self.get_tag(api_data),
@@ -638,7 +638,6 @@ def get_info_labels(self, api_data, season=False, date=None, channel=None):
if api_data.get('vrt.whatson-id') or api_data.get('startTime'):
info_labels = dict(
title=self.get_title(api_data),
- # sorttitle=self.get_title(api_data), # NOTE: Does not appear to work
tvshowtitle=self.get_tvshowtitle(api_data),
aired=self.get_aired(api_data),
plot=self.get_plot(api_data, date=date),
diff --git a/resources/lib/resumepoints.py b/resources/lib/resumepoints.py
index e5a6b7aa..2f7d57f9 100644
--- a/resources/lib/resumepoints.py
+++ b/resources/lib/resumepoints.py
@@ -75,9 +75,9 @@ def update(self, asset_id, title, url, watch_later=None, position=None, total=No
# Update
if (self.still_watching(position, total) or watch_later is True
or (path and path.startswith('plugin://plugin.video.vrt.nu/play/upnext'))):
- # Normally, VRT NU resumepoints are deleted when an episode is (un)watched and Kodi GUI automatically sets the (un)watched status when Kodi Player exits.
- # This mechanism doesn't work with "Up Next" episodes because these episodes are not initiated from a ListItem in Kodi GUI.
- # For "Up Next" episodes, we should never delete the VRT NU resumepoints to make sure the watched status can be forced in Kodi GUI using the playcount infolabel.
+ # Normally, VRT NU resumepoints are deleted when an episode is (un)watched and Kodi GUI automatically sets the (un)watched status when Player exits
+ # This mechanism doesn't work with "Up Next" episodes because these episodes are not initiated from a ListItem in Kodi GUI
+ # For "Up Next" episodes, we should never delete the VRT NU resumepoints to make sure the watched status can be forced using the playcount infolabel
log(3, "[Resumepoints] Update resumepoint '{asset_id}' {position}/{total}", asset_id=asset_id, position=position, total=total)
diff --git a/resources/lib/tvguide.py b/resources/lib/tvguide.py
index 450e7b23..4b0739cf 100644
--- a/resources/lib/tvguide.py
+++ b/resources/lib/tvguide.py
@@ -136,7 +136,7 @@ def get_channel_items(self, date=None, channel=None):
path = url_for('tvguide', date=date, channel=chan.get('name'))
plot = '[B]%s[/B]\n%s' % (datelong, localize(30302, **chan))
else:
- label = '[B]%s[/B]' % localize(30303, **chan)
+ label = '[COLOR yellow][B]%s[/B][/COLOR]' % localize(30303, **chan)
path = url_for('tvguide_channel', channel=chan.get('name'))
plot = '%s\n\n%s' % (localize(30302, **chan), self.live_description(chan.get('name')))
diff --git a/resources/settings.xml b/resources/settings.xml
index 7a1d2355..a390214d 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -60,10 +60,6 @@
-
-
-
-
diff --git a/tests/userdata/search_history.json b/tests/userdata/search_history.json
index 9d4cc0b2..9c6f78c6 100644
--- a/tests/userdata/search_history.json
+++ b/tests/userdata/search_history.json
@@ -1 +1 @@
-["winter", "dag", "test", "foobar"]
\ No newline at end of file
+["foobar"]
\ No newline at end of file
diff --git a/tests/xbmc.py b/tests/xbmc.py
index 4ecf54d8..ead9a0ce 100644
--- a/tests/xbmc.py
+++ b/tests/xbmc.py
@@ -11,8 +11,8 @@
import json
import time
import weakref
-from xbmcextra import ADDON_ID, global_settings, import_language
from utils import to_unicode
+from xbmcextra import ADDON_ID, GLOBAL_SETTINGS as settings, LANGUAGE
LOGLEVELS = ['Debug', 'Info', 'Notice', 'Warning', 'Error', 'Severe', 'Fatal', 'None']
LOGDEBUG = 0
@@ -34,9 +34,6 @@
'dateshort': '%Y-%m-%d',
}
-settings = global_settings()
-LANGUAGE = import_language(language=settings.get('locale.language'))
-
class Keyboard(object): # pylint: disable=useless-object-inheritance
"""A stub implementation of the xbmc Keyboard class"""
diff --git a/tests/xbmcaddon.py b/tests/xbmcaddon.py
index 7890c6a6..c50e53ea 100644
--- a/tests/xbmcaddon.py
+++ b/tests/xbmcaddon.py
@@ -9,9 +9,6 @@
from xbmc import getLocalizedString
from xbmcextra import ADDON_ID, ADDON_INFO, addon_settings
-# Ensure the addon settings are retained (as we don't write to disk)
-ADDON_SETTINGS = addon_settings(ADDON_ID)
-
class Addon:
"""A reimplementation of the xbmcaddon Addon class"""
@@ -19,10 +16,7 @@ class Addon:
def __init__(self, id=ADDON_ID): # pylint: disable=redefined-builtin
"""A stub constructor for the xbmcaddon Addon class"""
self.id = id
- if id == ADDON_ID:
- self.settings = ADDON_SETTINGS
- else:
- self.settings = addon_settings(id)
+ self.settings = addon_settings(id)
def getAddonInfo(self, key):
"""A working implementation for the xbmcaddon Addon class getAddonInfo() method"""
diff --git a/tests/xbmcextra.py b/tests/xbmcextra.py
index 93d85cee..7ea9caaa 100644
--- a/tests/xbmcextra.py
+++ b/tests/xbmcextra.py
@@ -186,3 +186,5 @@ def import_language(language):
ADDON_INFO = read_addon_xml('addon.xml')
ADDON_ID = next(iter(list(ADDON_INFO.values()))).get('id')
+GLOBAL_SETTINGS = global_settings()
+LANGUAGE = import_language(language=GLOBAL_SETTINGS.get('locale.language'))
diff --git a/tests/xbmcgui.py b/tests/xbmcgui.py
index d1103be4..58546d00 100644
--- a/tests/xbmcgui.py
+++ b/tests/xbmcgui.py
@@ -19,7 +19,6 @@ def __init__(self):
@staticmethod
def selectItem(index):
"""A stub implementation for the xbmcgui Control class selectItem() method"""
- return
class ControlLabel(Control):
@@ -210,37 +209,34 @@ def __init__(self, label='', label2='', iconImage='', thumbnailImage='', path=''
@staticmethod
def addContextMenuItems(items, replaceItems=False):
"""A stub implementation for the xbmcgui ListItem class addContextMenuItems() method"""
- return
@staticmethod
def addStreamInfo(stream_type, stream_values):
"""A stub implementation for the xbmcgui LitItem class addStreamInfo() method"""
- return
@staticmethod
def setArt(key):
"""A stub implementation for the xbmcgui ListItem class setArt() method"""
- return
@staticmethod
def setContentLookup(enable):
"""A stub implementation for the xbmcgui ListItem class setContentLookup() method"""
- return
@staticmethod
def setInfo(type, infoLabels): # pylint: disable=redefined-builtin
"""A stub implementation for the xbmcgui ListItem class setInfo() method"""
- return
@staticmethod
def setIsFolder(isFolder):
"""A stub implementation for the xbmcgui ListItem class setIsFolder() method"""
- return
+
+ @staticmethod
+ def setLabel2(label):
+ """A stub implementation for the xbmcgui ListItem class setLabel2() method"""
@staticmethod
def setMimeType(mimetype):
"""A stub implementation for the xbmcgui ListItem class setMimeType() method"""
- return
def setPath(self, path):
"""A stub implementation for the xbmcgui ListItem class setPath() method"""
@@ -249,22 +245,18 @@ def setPath(self, path):
@staticmethod
def setProperty(key, value):
"""A stub implementation for the xbmcgui ListItem class setProperty() method"""
- return
@staticmethod
def setProperties(dictionary):
"""A stub implementation for the xbmcgui ListItem class setProperties() method"""
- return
@staticmethod
def setSubtitles(subtitleFiles):
"""A stub implementation for the xbmcgui ListItem class setSubtitles() method"""
- return
@staticmethod
def setUniqueIDs(values, defaultrating=None):
"""A stub implementation for the xbmcgui ListItem class setUniqueIDs() method"""
- return
class Window:
@@ -272,7 +264,6 @@ class Window:
def __init__(self, windowId):
"""A stub constructor for the xbmcgui Window class"""
- return None
def close(self):
"""A stub implementation for the xbmcgui Window class close() method"""
@@ -295,12 +286,10 @@ def getProperty(key):
@staticmethod
def setProperty(key, value):
"""A stub implementation for the xbmcgui Window class setProperty() method"""
- return
@staticmethod
def clearProperty(key):
"""A stub implementation for the xbmcgui Window class clearProperty() method"""
- return
def show(self):
"""A stub implementation for the xbmcgui Window class show() method"""
diff --git a/tests/xbmcplugin.py b/tests/xbmcplugin.py
index de02e475..4cac29e0 100644
--- a/tests/xbmcplugin.py
+++ b/tests/xbmcplugin.py
@@ -79,7 +79,7 @@ def addDirectoryItems(handle, listing, length):
return True
-def addSortMethod(handle, sortMethod):
+def addSortMethod(handle, sortMethod, label2Mask='%D'):
"""A stub implementation of the xbmcplugin addSortMethod() function"""
diff --git a/tests/xbmcvfs.py b/tests/xbmcvfs.py
index f2ebb1c2..e454e7cd 100644
--- a/tests/xbmcvfs.py
+++ b/tests/xbmcvfs.py
@@ -3,7 +3,7 @@
# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""This file implements the Kodi xbmcvfs module, either using stubs or alternative functionality"""
-# pylint: disable=invalid-name
+# pylint: disable=invalid-name,too-few-public-methods
from __future__ import absolute_import, division, print_function, unicode_literals
import os