Skip to content

Commit d7e9c75

Browse files
committed
More assorted fixes
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)
1 parent a58decf commit d7e9c75

File tree

16 files changed

+93
-102
lines changed

16 files changed

+93
-102
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ build: clean
8080
@echo -e "$(white)=$(blue) Successfully wrote package as: $(white)../$(zip_name)$(reset)"
8181

8282
clean:
83+
@echo -e "$(white)=$(blue) Cleaning up$(reset)"
8384
find . -name '*.py[cod]' -type f -delete
8485
find . -name '__pycache__' -type d -delete
8586
rm -rf .pytest_cache/ .tox/

resources/language/resource.language.en_gb/strings.po

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -756,18 +756,6 @@ msgctxt "#30874"
756756
msgid "Open Up Next service add-on settings."
757757
msgstr ""
758758

759-
msgctxt "#30875"
760-
msgid "Install Twitter add-on"
761-
msgstr ""
762-
763-
msgctxt "#30877"
764-
msgid "Enable Twitter integration"
765-
msgstr ""
766-
767-
msgctxt "#30879"
768-
msgid "Twitter settings…"
769-
msgstr ""
770-
771759
msgctxt "#30881"
772760
msgid "Install PySocks library"
773761
msgstr ""

resources/language/resource.language.nl_nl/strings.po

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -756,18 +756,6 @@ msgctxt "#30874"
756756
msgid "Open Up Next service add-on settings."
757757
msgstr "Open de Up Next service add-on instellingen."
758758

759-
msgctxt "#30875"
760-
msgid "Install Twitter add-on"
761-
msgstr "Installeer de Twitter add-on"
762-
763-
msgctxt "#30877"
764-
msgid "Enable Twitter integration"
765-
msgstr "Activeer Twitter integratie"
766-
767-
msgctxt "#30879"
768-
msgid "Twitter settings…"
769-
msgstr "Twitter instellingen…"
770-
771759
msgctxt "#30881"
772760
msgid "Install PySocks library"
773761
msgstr "Installeer de PySocks library"

resources/lib/apihelper.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,14 @@ def __map_seasons(self, program, seasons, episodes):
160160
content = 'seasons'
161161

162162
episode = random.choice(episodes)
163-
info_labels = self._metadata.get_info_labels(episode, season=True)
164163
program_type = episode.get('programType')
164+
info_labels = self._metadata.get_info_labels(episode, season=True)
165+
info_labels.update(
166+
episode=len(episodes), # Total number of episodes in '* All seasons'
167+
season=len(seasons), # Total number of seasons in '* All seasons'
168+
tagline=localize(30133), # All seasons
169+
title=localize(30133), # All seasons
170+
)
165171

166172
# Reverse sort seasons if program_type is 'reeksaflopend' or 'daily'
167173
if program_type in ('daily', 'reeksaflopend'):
@@ -173,26 +179,34 @@ def __map_seasons(self, program, seasons, episodes):
173179
label=localize(30133), # All seasons
174180
path=url_for('programs', program=program, season='allseasons'),
175181
art_dict=self._metadata.get_art(episode, season='allseasons'),
176-
info_dict=info_labels,
182+
info_dict=info_labels.copy(),
177183
))
178184

179185
# NOTE: Sort the episodes ourselves, because Kodi does not allow to set to 'ascending'
180186
seasons = sorted(seasons, key=lambda k: k['key'], reverse=not ascending)
181187

182188
for season in seasons:
183189
season_key = season.get('key', '')
190+
episodelist = [e for e in episodes if e.get('seasonName') == season_key]
184191
# If more than 300 episodes exist, we may end up with an empty season (Winteruur)
185192
try:
186-
episode = random.choice([e for e in episodes if e.get('seasonName') == season_key])
193+
episode = random.choice(episodelist)
187194
except IndexError:
188195
episode = episodes[0]
189196

190197
label = '%s %s' % (localize(30131), season_key) # Season X
198+
info_labels.update(
199+
episode=len(episodelist), # Number of episodes in this folder
200+
season=1, # Number of seasons in this folder
201+
tagline=label,
202+
title=label,
203+
)
204+
191205
season_items.append(TitleItem(
192206
label=label,
193207
path=url_for('programs', program=program, season=season_key),
194208
art_dict=self._metadata.get_art(episode, season=True),
195-
info_dict=info_labels,
209+
info_dict=info_labels.copy(),
196210
prop_dict=self._metadata.get_properties(episode),
197211
))
198212
return season_items, sort, ascending, content
@@ -641,7 +655,7 @@ def list_channels(self, channels=None, live=True):
641655
label += ' [COLOR=yellow]| %s[/COLOR]' % playing_now
642656
# A single Live channel means it is the entry for channel's TV Show listing, so make it stand out
643657
if channels and len(channels) == 1:
644-
label = '[B]%s[/B]' % label
658+
label = '[COLOR yellow][B]%s[/B][/COLOR]' % label
645659
is_playable = True
646660
if channel.get('name') in ['een', 'canvas', 'ketnet']:
647661
if get_setting_bool('showfanart', default=True):
@@ -650,7 +664,16 @@ def list_channels(self, channels=None, live=True):
650664
else:
651665
plot = localize(30142, **channel) # Watch live
652666
# NOTE: Playcount and resumetime are required to not have live streams as "Watched" and resumed
653-
info_dict = dict(title=label, plot=plot, studio=channel.get('studio'), mediatype='video', playcount=0, duration=0)
667+
info_dict = dict(
668+
title=label,
669+
plot=plot,
670+
plotoutline=playing_now,
671+
tagline=playing_now,
672+
studio=channel.get('studio'),
673+
mediatype='video',
674+
playcount=0,
675+
duration=0,
676+
)
654677
prop_dict = dict(resumetime=0)
655678
stream_dict = dict(duration=0)
656679
context_menu.append((
@@ -700,10 +723,10 @@ def list_youtube(channels=None):
700723
label = localize(30143, **youtube) # Channel on YouTube
701724
# A single Live channel means it is the entry for channel's TV Show listing, so make it stand out
702725
if channels and len(channels) == 1:
703-
label = '[B]%s[/B]' % label
726+
label = '[COLOR yellow][B]%s[/B][/COLOR]' % label
704727
plot = localize(30144, **youtube) # Watch on YouTube
705728
# NOTE: Playcount is required to not have live streams as "Watched"
706-
info_dict = dict(title=label, plot=plot, studio=channel.get('studio'), mediatype='video', playcount=0)
729+
info_dict = dict(title=label, plot=plot, studio=channel.get('studio'), playcount=0)
707730

708731
context_menu = [(
709732
localize(30413), # Refresh menu

resources/lib/kodiutils.py

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@
1515

1616
SORT_METHODS = dict(
1717
# date=xbmcplugin.SORT_METHOD_DATE,
18-
dateadded=xbmcplugin.SORT_METHOD_DATEADDED,
19-
duration=xbmcplugin.SORT_METHOD_DURATION,
20-
episode=xbmcplugin.SORT_METHOD_EPISODE,
21-
# genre=xbmcplugin.SORT_METHOD_GENRE,
22-
# label=xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE,
23-
label=xbmcplugin.SORT_METHOD_LABEL,
24-
title=xbmcplugin.SORT_METHOD_TITLE,
25-
# none=xbmcplugin.SORT_METHOD_UNSORTED,
18+
dateadded=dict(method=xbmcplugin.SORT_METHOD_DATEADDED, label2='%a'),
19+
duration=dict(method=xbmcplugin.SORT_METHOD_DURATION, label2='%D'),
20+
episode=dict(method=xbmcplugin.SORT_METHOD_EPISODE, label2='%D'),
21+
# genre=dict(method=xbmcplugin.SORT_METHOD_GENRE, label2='%D'),
22+
# label=dict(method=xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE, label2='%D'),
23+
label=dict(method=xbmcplugin.SORT_METHOD_LABEL, label2='%D'),
24+
title=dict(method=xbmcplugin.SORT_METHOD_TITLE, label2='%D'),
25+
# none=dict(method=xbmcplugin.SORT_METHOD_UNSORTED, label2='%D'),
2626
# FIXME: We would like to be able to sort by unprefixed title (ignore date/episode prefix)
27-
# title=xbmcplugin.SORT_METHOD_TITLE_IGNORE_THE,
28-
unsorted=xbmcplugin.SORT_METHOD_UNSORTED,
27+
# title=dict(method=xbmcplugin.SORT_METHOD_TITLE_IGNORE_THE, label2='%D'),
28+
unsorted=dict(method=xbmcplugin.SORT_METHOD_UNSORTED, label2='%D'),
2929
)
3030

3131
WEEKDAY_LONG = {
@@ -86,6 +86,16 @@ def __missing__(self, key):
8686
return '{' + key + '}'
8787

8888

89+
def translate_path(path):
90+
"""Translate special xbmc paths"""
91+
return to_unicode(xbmc.translatePath(path))
92+
93+
94+
def get_addon_info(key):
95+
"""Return addon information"""
96+
return to_unicode(ADDON.getAddonInfo(key))
97+
98+
8999
def addon_icon():
90100
"""Cache and return add-on icon"""
91101
return get_addon_info('icon')
@@ -108,12 +118,17 @@ def addon_name():
108118

109119
def addon_path():
110120
"""Cache and return add-on path"""
111-
return get_addon_info('path')
121+
return translate_path(get_addon_info('path'))
112122

113123

114124
def addon_profile():
115125
"""Cache and return add-on profile"""
116-
return to_unicode(xbmc.translatePath(ADDON.getAddonInfo('profile')))
126+
return translate_path(ADDON.getAddonInfo('profile'))
127+
128+
129+
def addon_version():
130+
"""Cache and return add-on version"""
131+
return get_addon_info('version')
117132

118133

119134
def url_for(name, *args, **kwargs):
@@ -164,12 +179,12 @@ def show_listing(list_items, category=None, sort='unsorted', ascending=True, con
164179
sort = 'unsorted'
165180

166181
# Add all sort methods to GUI (start with preferred)
167-
xbmcplugin.addSortMethod(handle=plugin.handle, sortMethod=SORT_METHODS[sort])
182+
xbmcplugin.addSortMethod(handle=plugin.handle, sortMethod=SORT_METHODS[sort]['method'], label2Mask=SORT_METHODS[sort]['label2'])
168183
for key in sorted(SORT_METHODS):
169184
if key != sort:
170-
xbmcplugin.addSortMethod(handle=plugin.handle, sortMethod=SORT_METHODS[key])
185+
xbmcplugin.addSortMethod(handle=plugin.handle, sortMethod=SORT_METHODS[key]['method'], label2Mask=SORT_METHODS[key]['label2'])
171186

172-
# FIXME: This does not appear to be working, we have to order it ourselves
187+
# FIXME: This does not appear to be working, we have to order it ourselves and use 'unsorted' method
173188
# xbmcplugin.setProperty(handle=plugin.handle, key='sort.ascending', value='true' if ascending else 'false')
174189
# if ascending:
175190
# 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
185200
# - item is a playable file (playable, path)
186201
# - item is non-actionable item (not playable, no path)
187202
is_folder = bool(not title_item.is_playable and title_item.path)
188-
is_playable = bool(title_item.is_playable and title_item.path)
203+
is_playable = bool(title_item.is_playable and not title_item.path.endswith('/noop'))
189204

190205
list_item = ListItem(label=title_item.label)
191206

@@ -220,6 +235,10 @@ def show_listing(list_items, category=None, sort='unsorted', ascending=True, con
220235
# type is one of: video, music, pictures, game
221236
list_item.setInfo(type='video', infoLabels=title_item.info_dict)
222237

238+
# Add number of episodes to folders
239+
if is_folder and title_item.info_dict.get('episode'):
240+
list_item.setLabel2(str(title_item.info_dict.get('episode')))
241+
223242
if title_item.stream_dict:
224243
# type is one of: video, audio, subtitle
225244
list_item.addStreamInfo('video', title_item.stream_dict)
@@ -299,6 +318,14 @@ def get_search_string(search_string=None):
299318
return search_string
300319

301320

321+
def multiselect(heading='', options=None, autoclose=0, preselect=None, use_details=False):
322+
"""Show a Kodi multi-select dialog"""
323+
from xbmcgui import Dialog
324+
if not heading:
325+
heading = addon_name()
326+
return Dialog().multiselect(heading=heading, options=options, autoclose=autoclose, preselect=preselect, useDetails=use_details)
327+
328+
302329
def ok_dialog(heading='', message=''):
303330
"""Show Kodi's OK dialog"""
304331
from xbmcgui import Dialog
@@ -317,14 +344,6 @@ def notification(heading='', message='', icon='info', time=4000):
317344
Dialog().notification(heading=heading, message=message, icon=icon, time=time)
318345

319346

320-
def multiselect(heading='', options=None, autoclose=0, preselect=None, use_details=False):
321-
"""Show a Kodi multi-select dialog"""
322-
from xbmcgui import Dialog
323-
if not heading:
324-
heading = addon_name()
325-
return Dialog().multiselect(heading=heading, options=options, autoclose=autoclose, preselect=preselect, useDetails=use_details)
326-
327-
328347
def set_locale():
329348
"""Load the proper locale for date strings, only once"""
330349
if hasattr(set_locale, 'cached'):
@@ -355,7 +374,7 @@ def localize(string_id, **kwargs):
355374
def localize_time(time):
356375
"""Return localized time"""
357376
time_format = xbmc.getRegion('time').replace(':%S', '') # Strip off seconds
358-
return time.strftime(time_format).lstrip('0') # Remove leading zero on all platforms
377+
return time.strftime(time_format)
359378

360379

361380
def localize_date(date, strftime):
@@ -699,11 +718,6 @@ def get_cache_path():
699718
return getattr(get_cache_path, 'cached')
700719

701720

702-
def get_addon_info(key):
703-
"""Return addon information"""
704-
return to_unicode(ADDON.getAddonInfo(key))
705-
706-
707721
def listdir(path):
708722
"""Return all files in a directory (using xbmcvfs)"""
709723
from xbmcvfs import listdir as vfslistdir

resources/lib/metadata.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ def get_episode(self, api_data):
414414

415415
# VRT NU Suggest API
416416
if api_data.get('type') == 'program':
417-
return int()
417+
return api_data.get('episode_count', int()) # The number of episodes
418418

419419
# VRT NU Schedule API (some are missing vrt.whatson-id)
420420
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):
604604
if api_data.get('type') == 'episode':
605605
info_labels = dict(
606606
title=self.get_title(api_data),
607-
# sorttitle=self.get_title(api_data), # NOTE: Does not appear to work
608607
tvshowtitle=self.get_tvshowtitle(api_data),
609608
# date=self.get_date(api_data), # NOTE: Not sure when or how this is used
610609
aired=self.get_aired(api_data),
@@ -628,6 +627,7 @@ def get_info_labels(self, api_data, season=False, date=None, channel=None):
628627
info_labels = dict(
629628
tvshowtitle=self.get_tvshowtitle(api_data),
630629
plot=self.get_plot(api_data),
630+
episode=self.get_episode(api_data),
631631
mediatype=self.get_mediatype(api_data, season=season),
632632
studio=self.get_studio(api_data),
633633
tag=self.get_tag(api_data),
@@ -638,7 +638,6 @@ def get_info_labels(self, api_data, season=False, date=None, channel=None):
638638
if api_data.get('vrt.whatson-id') or api_data.get('startTime'):
639639
info_labels = dict(
640640
title=self.get_title(api_data),
641-
# sorttitle=self.get_title(api_data), # NOTE: Does not appear to work
642641
tvshowtitle=self.get_tvshowtitle(api_data),
643642
aired=self.get_aired(api_data),
644643
plot=self.get_plot(api_data, date=date),

resources/lib/resumepoints.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ def update(self, asset_id, title, url, watch_later=None, position=None, total=No
7575
# Update
7676
if (self.still_watching(position, total) or watch_later is True
7777
or (path and path.startswith('plugin://plugin.video.vrt.nu/play/upnext'))):
78-
# 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.
79-
# This mechanism doesn't work with "Up Next" episodes because these episodes are not initiated from a ListItem in Kodi GUI.
80-
# 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.
78+
# Normally, VRT NU resumepoints are deleted when an episode is (un)watched and Kodi GUI automatically sets the (un)watched status when Player exits
79+
# This mechanism doesn't work with "Up Next" episodes because these episodes are not initiated from a ListItem in Kodi GUI
80+
# 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
8181

8282
log(3, "[Resumepoints] Update resumepoint '{asset_id}' {position}/{total}", asset_id=asset_id, position=position, total=total)
8383

resources/lib/tvguide.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def get_channel_items(self, date=None, channel=None):
136136
path = url_for('tvguide', date=date, channel=chan.get('name'))
137137
plot = '[B]%s[/B]\n%s' % (datelong, localize(30302, **chan))
138138
else:
139-
label = '[B]%s[/B]' % localize(30303, **chan)
139+
label = '[COLOR yellow][B]%s[/B][/COLOR]' % localize(30303, **chan)
140140
path = url_for('tvguide_channel', channel=chan.get('name'))
141141
plot = '%s\n\n%s' % (localize(30302, **chan), self.live_description(chan.get('name')))
142142

resources/settings.xml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,6 @@
6060
<setting label="30869" help="30870" type="action" action="InstallAddon(service.upnext)" option="close" visible="!System.HasAddon(service.upnext)"/> <!-- Install Up Next add-on -->
6161
<setting label="30871" help="30872" type="bool" id="useupnext" default="true" visible="System.HasAddon(service.upnext)" />
6262
<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 -->
63-
<!-- Twitter -->
64-
<!-- setting label="30875" help="30876" type="action" action="InstallAddon(service.twitter)" option="close" visible="!System.HasAddon(service.twitter)"/ -->
65-
<setting label="30877" help="30878" type="bool" id="usetwitter" default="true" visible="System.HasAddon(service.twitter)"/>
66-
<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"/>
6763
<!-- PySocks -->
6864
<setting label="30881" help="30882" type="action" action="InstallAddon(script.module.pysocks)" option="close" visible="!System.HasAddon(script.module.pysocks)"/>
6965
</category>

tests/userdata/search_history.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
["winter", "dag", "test", "foobar"]
1+
["foobar"]

0 commit comments

Comments
 (0)