Skip to content
Open
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
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/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
_PLUGIN_NAME = Plugin().name
_PLUGIN_VERSION = Plugin().addon.getAddonInfo('version')
# Arte hbbtv - deprecated API since 2022 prefer Arte TV API
_HBBTV_URL = 'http://www.arte.tv/hbbtvv2/services/web/index.php'
_HBBTV_URL = 'https://www.arte.tv/hbbtvv2/services/web/index.php'
_HBBTV_HEADERS = {
'user-agent': f"{_PLUGIN_NAME}/{_PLUGIN_VERSION}"
}
Expand Down
6 changes: 5 additions & 1 deletion resources/lib/mapper/artecollection.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ def _build_menu(self, json_dict, collection_type, **nav_arg):
# Abstract class should NOT be instantiated
# pylint: disable=assignment-from-none
meta = self._get_page_meta(json_dict)
items = [ArteTvVideoItem(self.plugin, item).map_artetv_item() for item in pages]
items = []
for page_item in pages:
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):
total_pages = meta.get('pages')
current_page = meta.get('page')
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