Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
12 changes: 0 additions & 12 deletions resources/language/resource.language.en_gb/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
Expand Down
12 changes: 0 additions & 12 deletions resources/language/resource.language.nl_nl/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
39 changes: 31 additions & 8 deletions resources/lib/apihelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'):
Expand All @@ -173,26 +179,34 @@ 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'
seasons = sorted(seasons, key=lambda k: k['key'], reverse=not ascending)

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
Expand Down Expand Up @@ -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):
Expand All @@ -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((
Expand Down Expand Up @@ -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
Expand Down
74 changes: 44 additions & 30 deletions resources/lib/kodiutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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')
Expand All @@ -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):
Expand Down Expand Up @@ -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]))
Expand All @@ -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)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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'):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
5 changes: 2 additions & 3 deletions resources/lib/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'):
Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand All @@ -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),
Expand Down
6 changes: 3 additions & 3 deletions resources/lib/resumepoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion resources/lib/tvguide.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')))

Expand Down
4 changes: 0 additions & 4 deletions resources/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@
<setting label="30869" help="30870" type="action" action="InstallAddon(service.upnext)" option="close" visible="!System.HasAddon(service.upnext)"/> <!-- Install Up Next add-on -->
<setting label="30871" help="30872" type="bool" id="useupnext" default="true" visible="System.HasAddon(service.upnext)" />
<setting label="30873" help="30874" type="action" action="Addon.OpenSettings(service.upnext)" enable="eq(-1,true)" option="close" visible="System.HasAddon(service.upnext)" subsetting="true"/> <!-- Up Next settings -->
<!-- Twitter -->
<!-- setting label="30875" help="30876" type="action" action="InstallAddon(service.twitter)" option="close" visible="!System.HasAddon(service.twitter)"/ -->
<setting label="30877" help="30878" type="bool" id="usetwitter" default="true" visible="System.HasAddon(service.twitter)"/>
<setting label="30879" help="30880" type="action" option="close" action="Addon.OpenSettings(service.twitter)" enable="eq(-1,true)" visible="System.HasAddon(service.twitter)" subsetting="true"/>
<!-- PySocks -->
<setting label="30881" help="30882" type="action" action="InstallAddon(script.module.pysocks)" option="close" visible="!System.HasAddon(script.module.pysocks)"/>
</category>
Expand Down
2 changes: 1 addition & 1 deletion tests/userdata/search_history.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
["winter", "dag", "test", "foobar"]
["foobar"]
5 changes: 1 addition & 4 deletions tests/xbmc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"""
Expand Down
Loading