Skip to content

Commit

Permalink
Issue #31 Restore multilanguage support
Browse files Browse the repository at this point in the history
Temporary commit / PR.
Done :
  - multilanguage support from context menu
  - driven by renamed parameter (old nqme: show more streams, new multilanguage)
  - play lang stream from menu, context menu and playlist (series)
  - improve cache usage to build home page
  • Loading branch information
thomas-ernest committed Jan 3, 2024
1 parent 9b23a8e commit 16a5817
Show file tree
Hide file tree
Showing 15 changed files with 161 additions and 122 deletions.
27 changes: 14 additions & 13 deletions addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,17 @@
from resources.lib.settings import Settings

# global declarations
# plugin stuff
plugin = Plugin()
CACHE_TTL = 2880

plugin = Plugin()
settings = Settings(plugin)


@plugin.route('/', name='index')
def index():
"""Display home menu"""
return view.build_home_page(plugin, settings, plugin.get_storage('cached_categories', TTL=60))
return view.build_home_page(
plugin, settings, plugin.get_storage('cached_categories', TTL=CACHE_TTL))


@plugin.route('/api_category/<category_code>', name='api_category')
Expand All @@ -58,13 +59,13 @@ def api_category(category_code):
def cached_category(zone_id):
"""Display the menu for a category that is stored
in cache from previous api call like home page"""
return view.get_cached_category(zone_id, plugin.get_storage('cached_categories', TTL=60))
return view.get_cached_category(zone_id, plugin.get_storage('cached_categories', TTL=CACHE_TTL))


@plugin.route('/category_page/<zone_id>/<page>/<page_id>', name='category_page')
def category_page(zone_id, page, page_id):
"""Display the menu for a category that needs an api call"""
return ArteZone(plugin, settings, plugin.get_storage('cached_categories', TTL=60)) \
return ArteZone(plugin, settings, plugin.get_storage('cached_categories', TTL=CACHE_TTL)) \
.build_menu(zone_id, page, page_id)


Expand Down Expand Up @@ -145,13 +146,7 @@ def purge_last_viewed():
def display_collection(kind, program_id):
"""Display menu for collection of content"""
plugin.set_content('tvshows')
return plugin.finish(view.build_mixed_collection(plugin, kind, program_id, settings))


@plugin.route('/streams/<program_id>', name='streams')
def streams(program_id):
"""Play a multi language content."""
return plugin.finish(view.build_video_streams(plugin, settings, program_id))
return plugin.finish(view.build_collection_menu_tree(plugin, settings, kind, program_id))


@plugin.route('/play_live/<stream_url>', name='play_live')
Expand Down Expand Up @@ -179,16 +174,22 @@ def play(kind, program_id, audio_slot='1', from_playlist='0'):
synched_player = Player(user.get_cached_token(plugin, settings.username, True), program_id)
# try to seek parent collection, when out of the context of playlist creation
sibling_playlist = None

if from_playlist == '0':
sibling_playlist = view.build_sibling_playlist(plugin, settings, program_id)
item = None
if sibling_playlist is not None and len(sibling_playlist['collection']) > 1:
# Empty playlist, otherwise requested video is present twice in the playlist
xbmc.PlayList(xbmc.PLAYLIST_VIDEO).clear()
# Start playing with the first playlist item
result = plugin.set_resolved_url(plugin.add_to_playlist(sibling_playlist['collection'])[0])
item = plugin.add_to_playlist(sibling_playlist['collection'])[0]
result = plugin.set_resolved_url(item)
else:
item = view.build_stream_url(plugin, kind, program_id, int(audio_slot), settings)
result = plugin.set_resolved_url(item)
# Needed to play from context menu for multilanguage support
plugin.play_video(item)
# Needed to synch with Arte tv account
synch_during_playback(synched_player)
del synched_player
return result
Expand Down
4 changes: 2 additions & 2 deletions resources/language/resource.language.de_de/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ msgid "Stream video quality"
msgstr "Qualität des Videostreams"

msgctxt "#30053"
msgid "Show video stream options"
msgstr "Show video stream options"
msgid "Show multi-language streams in context menu"
msgstr "Mehrsprachige Streams im Kontextmenü anzeigen"

msgctxt "#30054"
msgid "Email"
Expand Down
2 changes: 1 addition & 1 deletion resources/language/resource.language.en_gb/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ msgid "Stream video quality"
msgstr ""

msgctxt "#30053"
msgid "Show video stream options"
msgid "Show multi-language streams in context menu"
msgstr ""

msgctxt "#30054"
Expand Down
4 changes: 2 additions & 2 deletions resources/language/resource.language.fr_fr/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ msgid "Stream video quality"
msgstr "Qualité vidéo préferée"

msgctxt "#30053"
msgid "Show video stream options"
msgstr "Afficher les options de flux vidéo"
msgid "Show multi-language streams in context menu"
msgstr "Afficher les flux multi-langue dans le menu contextuel"

msgctxt "#30054"
msgid "Email"
Expand Down
4 changes: 2 additions & 2 deletions resources/language/resource.language.it_it/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ msgid "Stream video quality"
msgstr "Qualità video"

msgctxt "#30053"
msgid "Show video stream options"
msgstr "Show video stream options"
msgid "Show multi-language streams in context menu"
msgstr "Mostra flussi multilingue nel menu contestuale"

msgctxt "#30054"
msgid "Email"
Expand Down
4 changes: 2 additions & 2 deletions resources/language/resource.language.pl_pl/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ msgid "Stream video quality"
msgstr "Jakość wideo"

msgctxt "#30053"
msgid "Show video stream options"
msgstr "Pokaz opcje wideo"
msgid "Show multi-language streams in context menu"
msgstr "Pokaż strumienie wielojęzyczne w menu kontekstowym"

msgctxt "#30054"
msgid "Email"
Expand Down
2 changes: 1 addition & 1 deletion resources/lib/mapper/artecollection.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def _build_menu(self, json_dict, collection_type, **nav_arg):
meta = self._get_page_meta(json_dict)
items = []
for page_item in pages:
menu_item = ArteTvVideoItem(self.plugin, page_item).map_artetv_item()
menu_item = ArteTvVideoItem(self.plugin, self.settings, page_item).map_item()
if menu_item is not None:
items.append(menu_item)
if meta and meta.get('pages', False):
Expand Down
108 changes: 101 additions & 7 deletions resources/lib/mapper/arteitem.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from xbmcswift2 import xbmc
# pylint: disable=import-error
from xbmcswift2 import actions
from resources.lib import api
from resources.lib import user


# pylint: disable=too-few-public-methods
Expand Down Expand Up @@ -43,19 +45,25 @@ class ArteVideoItem(ArteItem):
It aims at being mapped into XBMC ListItem.
"""

def __init__(self, plugin, settings, json_dict):
ArteItem.__init__(self, plugin, json_dict)
self.settings = settings

def build_item(self, path, is_playable):
"""Identify what is the type of current item and build the most detailled item possible"""
if self.is_hbbtv():
return ArteHbbTvVideoItem(self.plugin, self.json_dict).build_item(path, is_playable)
return ArteTvVideoItem(self.plugin, self.json_dict).build_item(path, is_playable)
return ArteHbbTvVideoItem(self.plugin, self.settings, self.json_dict).build_item(
path, is_playable)
return ArteTvVideoItem(self.plugin, self.settings, self.json_dict).build_item(
path, is_playable)

def _build_item(self, path, is_playable):
"""
Build ListItem common to HBB TV and Arte TV API.
"""
item = self.json_dict
program_id = item.get('programId')
label = self.format_title_and_subtitle()

return {
'label': label,
'path': path,
Expand All @@ -73,7 +81,35 @@ def _build_item(self, path, is_playable):
'fanart_image': self._get_image_url(),
'TotalTime': str(self._get_duration()),
},
'context_menu': [
'context_menu': self._build_context_menu(item),
}

def _build_context_menu(self, item):
"""
Return an ordered list of tuple label-action to be used as context menu.
List contains tuples to manage favorites and mark as watched, is a user is logged in.
List contains tuples in multiple-languages, if setting is enabled.
List might be empty, but never None
"""
program_id = item.get('programId')
label = self.format_title_and_subtitle()
context_menu = []

# multi-language streams are available in context menu if enabled
if self.settings.multilanguage:
kind = item.get('kind')
if isinstance(kind, dict) and kind.get('code', False):
kind = kind.get('code')
# support multi-language for videos items and not collections e.g. TV_SERIES
if kind == 'SHOW':
streams = api.streams(kind, program_id, self.settings.language)
context_menu.extend(
self._map_streams(program_id, kind, streams, self.settings.quality))

# favorites management and mark as watched in Arte TV
# are available in context menu, if user is logged-in
if user.is_logged_in(self.plugin, self.settings):
context_menu.extend([
(self.plugin.addon.getLocalizedString(30023),
actions.background(self.plugin.url_for(
'add_favorite', program_id=program_id, label=label))),
Expand All @@ -83,8 +119,50 @@ def _build_item(self, path, is_playable):
(self.plugin.addon.getLocalizedString(30035),
actions.background(self.plugin.url_for(
'mark_as_watched', program_id=program_id, label=label))),
],
}
])

return context_menu

def _map_streams(self, program_id, kind, streams, quality):
"""Map JSON item and list of audio streams into a menu."""
sorted_filtered_streams = self._sort_and_filter_streams(streams, quality)

return [self._map_to_ctxt_menu(program_id, kind, stream)
for stream in sorted_filtered_streams]

def _sort_and_filter_streams(self, streams, quality):
"""
Return a list of streams matching quality provided as parameter
and order by their numerical audio slot from Arte API
"""
if len(streams) <= 0:
return []

filtered_streams = None
for qlt in [quality] + [i for i in ['SQ', 'EQ', 'HQ', 'MQ'] if i is not quality]:
filtered_streams = [s for s in streams if s.get('quality') == qlt]
if len(filtered_streams) > 0:
break

if filtered_streams is None or len(filtered_streams) == 0:
raise RuntimeError('Could not resolve stream...')

return sorted(
filtered_streams, key=lambda s: s.get('audioSlot'))

def _map_to_ctxt_menu(self, program_id, kind, stream):
"""
Map an Arte HBBTV API stream to a context menu item,
which enables to play the specific stream
"""
audio_slot = stream.get('audioSlot')
audio_label = stream.get('audioLabel')
return (
audio_label,
actions.background(self.plugin.url_for(
'play_siblings', kind=kind, program_id=program_id,
audio_slot=str(audio_slot), from_playlist='1'))
)

def _get_duration(self):
"""
Expand Down Expand Up @@ -154,7 +232,7 @@ class ArteTvVideoItem(ArteVideoItem):
from Arte TV API data
"""

def map_artetv_item(self):
def map_item(self):
"""
Return video menu item to show content from Arte TV API.
Manage specificities of various types : playlist, menu or video items
Expand Down Expand Up @@ -287,6 +365,13 @@ class ArteHbbTvVideoItem(ArteVideoItem):
from Arte HBB TV API data
"""

def map_item(self):
"""Create a playable video menu item from a json returned by Arte HBBTV API"""
program_id = self.json_dict.get('programId')
kind = self.json_dict.get('kind')
path = self.plugin.url_for('play', kind=kind, program_id=program_id)
return self.build_item(path, True)

def build_item(self, path, is_playable):
basic_item = super()._build_item(path, is_playable)
if basic_item is None:
Expand Down Expand Up @@ -355,3 +440,12 @@ def map_collection_as_menu_item(self):
'plotoutline': item.get('teaserText')
}
}

def build_collection_or_hbbtv_item(self, settings):
"""Return entry menu for video or playlist"""
item = self.json_dict
if ArteVideoItem(self.plugin, settings, item).is_playlist():
item = ArteCollectionItem(self.plugin, item).map_collection_as_menu_item()
else:
item = ArteHbbTvVideoItem(self.plugin, settings, item).map_item()
return item
15 changes: 14 additions & 1 deletion resources/lib/mapper/artezone.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,27 @@ def build_item(self, zone):
a zone in the HOME page or SEARH page result.
"""
zone_id = zone.get('id')

# try to get category from cache
if isinstance(self.cached_categories, dict):
cached_category = self.cached_categories.get(zone_id, None)
if self._is_valid_menu(cached_category):
return {
'label': zone.get('title'),
'path': self.plugin.url_for('cached_category', zone_id=zone_id)
}

# otherwise try to build the category and save it in cqche
cached_category = self._build_menu(
zone.get('content'), 'category_page', zone_id=zone_id, page_id='HOME')
if self._is_valid_menu(cached_category):
self.cached_categories[zone_id] = cached_category
if isinstance(self.cached_categories, dict):
self.cached_categories[zone_id] = cached_category
return {
'label': zone.get('title'),
'path': self.plugin.url_for('cached_category', zone_id=zone_id)
}

return None

def _is_valid_menu(self, cached_category):
Expand Down
4 changes: 2 additions & 2 deletions resources/lib/mapper/live.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
Module for ArteLiveItem depends on ArteTvVideoItem and mapper module
for map_playable and match_hbbtv
for map_playable and match_artetv
"""

import html
Expand Down Expand Up @@ -58,7 +58,7 @@ def build_item_live(self, quality, audio_slot):
# while it starts the video like the live tv, with the above
# 'path': plugin.url_for('play', kind='SHOW', program_id=programId.replace('_fr', '')),
'thumbnail': thumbnail_url,
'is_playable': True, # not show_video_streams
'is_playable': True,
'info_type': 'video',
'info': {
'title': meta.get('title'),
Expand Down
Loading

0 comments on commit 16a5817

Please sign in to comment.