Skip to content

Commit

Permalink
More assorted fixes
Browse files Browse the repository at this point in the history
This PR includes:
- Fix requirements.txt for xmlschema on Python 2.7
- Add tagline and plotoutline to TV channels
- Add label2Mask to sort methods
- Add label2 episodes to folders (does not work in Kodi yet)
- Add episode and season to folders (see video pane)
  • Loading branch information
dagwieers committed Mar 27, 2020
1 parent d5c9692 commit a313c4e
Show file tree
Hide file tree
Showing 16 changed files with 93 additions and 102 deletions.
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

0 comments on commit a313c4e

Please sign in to comment.