diff --git a/addon.xml b/addon.xml index 6dbc879fb..19205741b 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + diff --git a/changelog.txt b/changelog.txt index d8d59d139..4e2799ca1 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,14 @@ +## v7.0.9.1 +### Fixed +- Add playlist_type_hint property to workaround issues with CPlayListPlayer::Play #873 +- Fix comments not displaying for recommended videos #878 +- Fix not correctly loading bookmarks for My Subscriptions +- Fix invalid escape sequence in non-raw string #885 +- Attempt to fix various threading issues with My Subscriptions #882 + +### New +- Add refresh settings to Setup Wizard + ## v7.0.9 ### Fixed - Fix renaming playlists diff --git a/resources/lib/youtube_plugin/kodion/constants/const_content_types.py b/resources/lib/youtube_plugin/kodion/constants/const_content_types.py index 0003408a7..6235d08e7 100644 --- a/resources/lib/youtube_plugin/kodion/constants/const_content_types.py +++ b/resources/lib/youtube_plugin/kodion/constants/const_content_types.py @@ -12,6 +12,8 @@ VIDEO_CONTENT = 'videos' LIST_CONTENT = 'files' + +AUDIO_TYPE = 'music' VIDEO_TYPE = 'video' FILES = 'files' diff --git a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py index d53fd3708..33f7914bf 100644 --- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py +++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py @@ -200,6 +200,7 @@ class XbmcContext(AbstractContext): 'setup_wizard.prompt.settings.defaults': 30783, 'setup_wizard.prompt.settings.list_details': 30784, 'setup_wizard.prompt.settings.performance': 30785, + 'setup_wizard.prompt.settings.refresh': 30817, 'setup_wizard.prompt.subtitles': 30600, 'sign.enter_code': 30519, 'sign.go_to': 30502, diff --git a/resources/lib/youtube_plugin/kodion/items/__init__.py b/resources/lib/youtube_plugin/kodion/items/__init__.py index 423257807..7e11964e8 100644 --- a/resources/lib/youtube_plugin/kodion/items/__init__.py +++ b/resources/lib/youtube_plugin/kodion/items/__init__.py @@ -11,26 +11,24 @@ from __future__ import absolute_import, division, unicode_literals from . import menu_items -from .audio_item import AudioItem from .base_item import BaseItem from .command_item import CommandItem from .directory_item import DirectoryItem from .image_item import ImageItem +from .media_item import AudioItem, VideoItem from .new_search_item import NewSearchItem from .next_page_item import NextPageItem from .search_history_item import SearchHistoryItem from .search_item import SearchItem from .uri_item import UriItem from .utils import from_json -from .video_item import VideoItem from .watch_later_item import WatchLaterItem from .xbmc.xbmc_items import ( - audio_listitem, directory_listitem, image_listitem, + media_listitem, + playback_item, uri_listitem, - video_listitem, - video_playback_item, ) @@ -49,10 +47,9 @@ 'WatchLaterItem', 'from_json', 'menu_items', - 'audio_listitem', 'directory_listitem', 'image_listitem', + 'media_listitem', + 'playback_item', 'uri_listitem', - 'video_listitem', - 'video_playback_item', ) diff --git a/resources/lib/youtube_plugin/kodion/items/audio_item.py b/resources/lib/youtube_plugin/kodion/items/audio_item.py deleted file mode 100644 index 9e4f7a689..000000000 --- a/resources/lib/youtube_plugin/kodion/items/audio_item.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- -""" - - Copyright (C) 2014-2016 bromix (plugin.video.youtube) - Copyright (C) 2016-2018 plugin.video.youtube - - SPDX-License-Identifier: GPL-2.0-only - See LICENSES/GPL-2.0-only for more information. -""" - -from __future__ import absolute_import, division, unicode_literals - -from .base_item import BaseItem -from ..compatibility import to_str, unescape - - -class AudioItem(BaseItem): - _playable = True - - def __init__(self, name, uri, image='DefaultAudio.png', fanart=None): - super(AudioItem, self).__init__(name, uri, image, fanart) - self._start_time = None - self._duration = -1 - self._track_number = None - self._year = None - self._genres = None - self._album = None - self._artists = None - self._title = self.get_name() - self._rating = None - - def set_rating(self, rating): - rating = float(rating) - if rating > 10: - rating = 10.0 - elif rating < 0: - rating = 0.0 - self._rating = rating - - def get_rating(self): - return self._rating - - def set_title(self, title): - try: - title = unescape(title) - except: - pass - self._title = title - - def get_title(self): - return self._title - - def add_artist(self, artist): - if self._artists is None: - self._artists = [] - if artist: - self._artists.append(to_str(artist)) - - def get_artists(self): - return self._artists - - def set_artists(self, artists): - self._artists = list(artists) - - def set_album_name(self, album_name): - self._album = album_name or '' - - def get_album_name(self): - return self._album - - def add_genre(self, genre): - if self._genres is None: - self._genres = [] - if genre: - self._genres.append(to_str(genre)) - - def get_genres(self): - return self._genres - - def set_genres(self, genres): - self._genres = list(genres) - - def set_year(self, year): - self._year = int(year) - - def set_year_from_datetime(self, date_time): - self.set_year(date_time.year) - - def get_year(self): - return self._year - - def set_track_number(self, track_number): - self._track_number = int(track_number) - - def get_track_number(self): - return self._track_number - - def set_duration_from_milli_seconds(self, milli_seconds): - self.set_duration_from_seconds(int(milli_seconds) // 1000) - - def set_duration_from_seconds(self, seconds): - self._duration = int(seconds) - - def set_duration_from_minutes(self, minutes): - self.set_duration_from_seconds(int(minutes) * 60) - - def get_duration(self): - return self._duration - - def set_start_time(self, start_time): - self._start_time = start_time or 0.0 - - def get_start_time(self): - return self._start_time diff --git a/resources/lib/youtube_plugin/kodion/items/video_item.py b/resources/lib/youtube_plugin/kodion/items/media_item.py similarity index 81% rename from resources/lib/youtube_plugin/kodion/items/video_item.py rename to resources/lib/youtube_plugin/kodion/items/media_item.py index acf320bfd..ac0efeedd 100644 --- a/resources/lib/youtube_plugin/kodion/items/video_item.py +++ b/resources/lib/youtube_plugin/kodion/items/media_item.py @@ -10,121 +10,79 @@ from __future__ import absolute_import, division, unicode_literals -import datetime import re +from datetime import date -from .base_item import BaseItem +from . import BaseItem from ..compatibility import datetime_infolabel, to_str, unescape +from ..constants import CONTENT from ..utils import duration_to_seconds, seconds_to_duration -__RE_IMDB__ = re.compile(r'(http(s)?://)?www.imdb.(com|de)/title/(?P[t0-9]+)(/)?') +class MediaItem(BaseItem): + _ALLOWABLE_MEDIATYPES = frozenset() + _DEFAULT_MEDIATYPE = '' - -class VideoItem(BaseItem): _playable = True - def __init__(self, name, uri, image='DefaultVideo.png', fanart=None): - super(VideoItem, self).__init__(name, uri, image, fanart) - self._genres = None + def __init__(self, name, uri, image='DefaultFile.png', fanart=None): + super(MediaItem, self).__init__(name, uri, image, fanart) self._aired = None - self._scheduled_start_utc = None - self._duration = -1 - self._directors = None self._premiered = None - self._episode = None - self._season = None + self._scheduled_start_utc = None self._year = None - self._plot = None - self._title = self.get_name() - self._imdb_id = None + + self._artists = None self._cast = None - self._rating = None - self._track_number = None + self._genres = None self._studios = None - self._artists = None - self._production_code = None - self._mediatype = None + self._duration = -1 self._play_count = None self._last_played = None self._start_percent = None self._start_time = None + self._mediatype = None + self._plot = None + self._production_code = None + self._rating = None + self._title = self.get_name() + self._track_number = None + + self._headers = None + self._license_key = None + self._uses_isa = None + self.subtitles = None + self._completed = False self._live = False self._short = False self._upcoming = False self._vod = False - self._uses_isa = None - self.subtitles = None - self._headers = None - self.license_key = None - self._video_id = None self._channel_id = None self._subscription_id = None self._playlist_id = None self._playlist_item_id = None - def set_play_count(self, play_count): - self._play_count = int(play_count or 0) - - def get_play_count(self): - return self._play_count - - def add_artist(self, artist): - if self._artists is None: - self._artists = [] - if artist: - self._artists.append(to_str(artist)) - - def get_artists(self): - return self._artists - - def set_artists(self, artists): - self._artists = list(artists) - - def add_studio(self, studio): - if self._studios is None: - self._studios = [] - if studio: - self._studios.append(to_str(studio)) - - def get_studios(self): - return self._studios - - def set_studios(self, studios): - self._studios = list(studios) - - def set_title(self, title): - try: - title = unescape(title) - except: - pass - self._name = self._title = title - - def get_title(self): - return self._title - - def set_track_number(self, track_number): - self._track_number = int(track_number) - - def get_track_number(self): - return self._track_number - - def set_year(self, year): - self._year = int(year) + def set_aired(self, year, month, day): + self._aired = date(year, month, day) - def set_year_from_datetime(self, date_time): - self.set_year(date_time.year) + def set_aired_from_datetime(self, date_time): + self._aired = date_time.date() - def get_year(self): - return self._year + def get_aired(self, as_text=True, as_info_label=False): + if self._aired: + if as_info_label: + return self._aired.isoformat() + if as_text: + return self._aired.strftime('%x') + return self._aired def set_premiered(self, year, month, day): - self._premiered = datetime.date(year, month, day) + self._premiered = date(year, month, day) def set_premiered_from_datetime(self, date_time): self._premiered = date_time.date() @@ -137,43 +95,45 @@ def get_premiered(self, as_text=True, as_info_label=False): return self._premiered.strftime('%x') return self._premiered - def set_plot(self, plot): - try: - plot = unescape(plot) - except: - pass - self._plot = plot + def set_scheduled_start_utc(self, date_time): + self._scheduled_start_utc = date_time - def get_plot(self): - return self._plot + def get_scheduled_start_utc(self): + return self._scheduled_start_utc - def set_rating(self, rating): - rating = float(rating) - if rating > 10: - rating = 10.0 - elif rating < 0: - rating = 0.0 - self._rating = rating + def set_year(self, year): + self._year = int(year) - def get_rating(self): - return self._rating + def set_year_from_datetime(self, date_time): + self.set_year(date_time.year) - def add_directors(self, director): - if self._directors is None: - self._directors = [] - if director: - self._directors.append(to_str(director)) + def get_year(self): + return self._year - def get_directors(self): - return self._directors + def add_artist(self, artist): + if artist: + if self._artists is None: + self._artists = [] + self._artists.append(to_str(artist)) - def set_directors(self, directors): - self._directors = list(directors) + def get_artists(self): + return self._artists + + def get_artists_string(self): + if self._artists: + return ', '.join(self._artists) + return None + + def set_artists(self, artists): + self._artists = list(artists) + + def set_cast(self, members): + self._cast = list(members) def add_cast(self, name, role=None, order=None, thumbnail=None): - if self._cast is None: - self._cast = [] if name: + if self._cast is None: + self._cast = [] self._cast.append({ 'name': to_str(name), 'role': to_str(role) if role else '', @@ -184,30 +144,29 @@ def add_cast(self, name, role=None, order=None, thumbnail=None): def get_cast(self): return self._cast - def set_cast(self, members): - self._cast = list(members) - - def set_imdb_id(self, url_or_id): - re_match = __RE_IMDB__.match(url_or_id) - if re_match: - self._imdb_id = re_match.group('imdbid') - else: - self._imdb_id = url_or_id + def add_genre(self, genre): + if genre: + if self._genres is None: + self._genres = [] + self._genres.append(to_str(genre)) - def get_imdb_id(self): - return self._imdb_id + def get_genres(self): + return self._genres - def set_episode(self, episode): - self._episode = int(episode) + def set_genres(self, genres): + self._genres = list(genres) - def get_episode(self): - return self._episode + def add_studio(self, studio): + if studio: + if self._studios is None: + self._studios = [] + self._studios.append(to_str(studio)) - def set_season(self, season): - self._season = int(season) + def get_studios(self): + return self._studios - def get_season(self): - return self._season + def set_studios(self, studios): + self._studios = list(studios) def set_duration(self, hours=0, minutes=0, seconds=0, duration=''): if duration: @@ -227,144 +186,158 @@ def get_duration(self, as_text=False): return seconds_to_duration(self._duration) return self._duration - def set_aired(self, year, month, day): - self._aired = datetime.date(year, month, day) + def set_play_count(self, play_count): + self._play_count = int(play_count or 0) - def set_aired_from_datetime(self, date_time): - self._aired = date_time.date() + def get_play_count(self): + return self._play_count - def get_aired(self, as_text=True, as_info_label=False): - if self._aired: + def set_last_played(self, last_played): + self._last_played = last_played + + def get_last_played(self, as_info_label=False): + if self._last_played: if as_info_label: - return self._aired.isoformat() - if as_text: - return self._aired.strftime('%x') - return self._aired + return datetime_infolabel(self._last_played) + return self._last_played - def set_scheduled_start_utc(self, date_time): - self._scheduled_start_utc = date_time + def set_start_percent(self, start_percent): + self._start_percent = start_percent or 0 - def get_scheduled_start_utc(self): - return self._scheduled_start_utc + def get_start_percent(self): + return self._start_percent - @property - def completed(self): - return self._completed + def set_start_time(self, start_time): + self._start_time = start_time or 0.0 - @completed.setter - def completed(self, value): - self._completed = value + def get_start_time(self): + return self._start_time - @property - def live(self): - return self._live + def set_mediatype(self, mediatype): + if mediatype in self._ALLOWABLE_MEDIATYPES: + self._mediatype = mediatype + else: + self._mediatype = self._DEFAULT_MEDIATYPE - @live.setter - def live(self, value): - self._live = value + def get_mediatype(self): + return self._mediatype - @property - def short(self): - return self._short + def set_plot(self, plot): + try: + plot = unescape(plot) + except: + pass + self._plot = plot - @short.setter - def short(self, value): - self._short = value + def get_plot(self): + return self._plot - @property - def upcoming(self): - return self._upcoming + def set_production_code(self, value): + self._production_code = value or '' - @upcoming.setter - def upcoming(self, value): - self._upcoming = value + def get_production_code(self): + return self._production_code - @property - def vod(self): - return self._vod + def set_rating(self, rating): + rating = float(rating) + if rating > 10: + rating = 10.0 + elif rating < 0: + rating = 0.0 + self._rating = rating - @vod.setter - def vod(self, value): - self._vod = value + def get_rating(self): + return self._rating - def add_genre(self, genre): - if self._genres is None: - self._genres = [] - if genre: - self._genres.append(to_str(genre)) + def set_title(self, title): + try: + title = unescape(title) + except: + pass + self._name = self._title = title - def get_genres(self): - return self._genres + def get_title(self): + return self._title - def set_genres(self, genres): - self._genres = list(genres) + def set_track_number(self, track_number): + self._track_number = int(track_number) + + def get_track_number(self): + return self._track_number + + def set_headers(self, value): + self._headers = value - def set_isa_video(self, value=True): + def get_headers(self): + return self._headers + + def set_license_key(self, url): + self._license_key = url + + def get_license_key(self): + return self._license_key + + def set_isa(self, value=True): self._uses_isa = value - def use_isa_video(self): + def use_isa(self): return self._uses_isa - def use_hls_video(self): + def use_hls(self): uri = self.get_uri() if 'manifest/hls' in uri or uri.endswith('.m3u8'): return True return False - def use_mpd_video(self): + def use_mpd(self): uri = self.get_uri() if 'manifest/dash' in uri or uri.endswith('.mpd'): return True return False - def set_mediatype(self, mediatype): - if (mediatype in {'video', - 'movie', - 'tvshow', 'season', 'episode', - 'musicvideo'}): - self._mediatype = mediatype - else: - self._mediatype = 'video' - - def get_mediatype(self): - return self._mediatype - def set_subtitles(self, value): if value and isinstance(value, (list, tuple)): self.subtitles = value - def set_headers(self, value): - self._headers = value + @property + def completed(self): + return self._completed - def get_headers(self): - return self._headers + @completed.setter + def completed(self, value): + self._completed = value - def set_license_key(self, url): - self.license_key = url + @property + def live(self): + return self._live - def get_license_key(self): - return self.license_key + @live.setter + def live(self, value): + self._live = value - def set_last_played(self, last_played): - self._last_played = last_played + @property + def short(self): + return self._short - def get_last_played(self, as_info_label=False): - if self._last_played: - if as_info_label: - return datetime_infolabel(self._last_played) - return self._last_played + @short.setter + def short(self, value): + self._short = value - def set_start_percent(self, start_percent): - self._start_percent = start_percent or 0 + @property + def upcoming(self): + return self._upcoming - def get_start_percent(self): - return self._start_percent + @upcoming.setter + def upcoming(self, value): + self._upcoming = value - def set_start_time(self, start_time): - self._start_time = start_time or 0.0 + @property + def vod(self): + return self._vod - def get_start_time(self): - return self._start_time + @vod.setter + def vod(self, value): + self._vod = value @property def video_id(self): @@ -406,8 +379,69 @@ def playlist_item_id(self): def playlist_item_id(self, value): self._playlist_item_id = value - def get_code(self): - return self._production_code - def set_code(self, value): - self._production_code = value or '' +class AudioItem(MediaItem): + _ALLOWABLE_MEDIATYPES = {'song', 'album', 'artist'} + _DEFAULT_MEDIATYPE = CONTENT.AUDIO_TYPE + + def __init__(self, name, uri, image='DefaultAudio.png', fanart=None): + super(AudioItem, self).__init__(name, uri, image, fanart) + self._album = None + + def set_album_name(self, album_name): + self._album = album_name or '' + + def get_album_name(self): + return self._album + + +class VideoItem(MediaItem): + _ALLOWABLE_MEDIATYPES = {'video', + 'movie', + 'tvshow', 'season', 'episode', + 'musicvideo'} + _DEFAULT_MEDIATYPE = CONTENT.VIDEO_TYPE + _RE_IMDB = re.compile( + r'(http(s)?://)?www.imdb.(com|de)/title/(?P[t0-9]+)(/)?' + ) + + def __init__(self, name, uri, image='DefaultVideo.png', fanart=None): + super(VideoItem, self).__init__(name, uri, image, fanart) + self._directors = None + self._episode = None + self._imdb_id = None + self._season = None + + def add_directors(self, director): + if director: + if self._directors is None: + self._directors = [] + self._directors.append(to_str(director)) + + def get_directors(self): + return self._directors + + def set_directors(self, directors): + self._directors = list(directors) + + def set_episode(self, episode): + self._episode = int(episode) + + def get_episode(self): + return self._episode + + def set_imdb_id(self, url_or_id): + re_match = self._RE_IMDB.match(url_or_id) + if re_match: + self._imdb_id = re_match.group('imdbid') + else: + self._imdb_id = url_or_id + + def get_imdb_id(self): + return self._imdb_id + + def set_season(self, season): + self._season = int(season) + + def get_season(self): + return self._season diff --git a/resources/lib/youtube_plugin/kodion/items/utils.py b/resources/lib/youtube_plugin/kodion/items/utils.py index 0d54ba76b..f93ab11fa 100644 --- a/resources/lib/youtube_plugin/kodion/items/utils.py +++ b/resources/lib/youtube_plugin/kodion/items/utils.py @@ -13,10 +13,9 @@ import json from datetime import date, datetime -from .audio_item import AudioItem from .directory_item import DirectoryItem from .image_item import ImageItem -from .video_item import VideoItem +from .media_item import AudioItem, VideoItem from ..compatibility import string_type, to_str from ..utils.datetime_parser import strptime diff --git a/resources/lib/youtube_plugin/kodion/items/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/items/xbmc/xbmc_items.py index a043c725d..48f016716 100644 --- a/resources/lib/youtube_plugin/kodion/items/xbmc/xbmc_items.py +++ b/resources/lib/youtube_plugin/kodion/items/xbmc/xbmc_items.py @@ -28,63 +28,32 @@ def set_info(list_item, item, properties, set_play_count=True, resume=True): - is_video = False if not current_system_version.compatible(20, 0): if isinstance(item, VideoItem): - is_video = True info_labels = {} + info_type = 'video' value = item.get_aired(as_info_label=True) if value is not None: info_labels['aired'] = value - value = item.get_artists() - if value is not None: - info_labels['artist'] = value - value = item.get_cast() if value is not None: info_labels['castandrole'] = [(member['name'], member['role']) for member in value] - value = item.get_code() + value = item.get_production_code() if value is not None: info_labels['code'] = value - value = item.get_count() - if value is not None: - info_labels['count'] = value - - value = item.get_date(as_info_label=True) - if value is not None: - info_labels['date'] = value - value = item.get_dateadded(as_info_label=True) if value is not None: info_labels['dateadded'] = value - value = item.get_duration() - if value is not None: - info_labels['duration'] = value - value = item.get_episode() if value is not None: info_labels['episode'] = value - value = item.get_last_played(as_info_label=True) - if value is not None: - info_labels['lastplayed'] = value - - value = item.get_mediatype() - if value is not None: - info_labels['mediatype'] = value - - value = item.get_play_count() - if value is not None: - if set_play_count: - info_labels['playcount'] = value - properties[PLAY_COUNT] = value - value = item.get_plot() if value is not None: info_labels['plot'] = value @@ -93,35 +62,29 @@ def set_info(list_item, item, properties, set_play_count=True, resume=True): if value is not None: info_labels['premiered'] = value - value = item.get_rating() - if value is not None: - info_labels['rating'] = value - value = item.get_season() if value is not None: info_labels['season'] = value - value = item.get_title() + value = item.get_studios() if value is not None: - info_labels['title'] = value + info_labels['studio'] = value - value = item.get_track_number() - if value is not None: - info_labels['tracknumber'] = value + elif isinstance(item, AudioItem): + info_labels = {} + info_type = 'music' - value = item.get_year() + value = item.get_album_name() if value is not None: - info_labels['year'] = value + info_labels['album'] = value - value = item.get_studios() + value = item.get_plot() if value is not None: - info_labels['studio'] = value - - if info_labels: - list_item.setInfo('video', info_labels) + info_labels['plot'] = value elif isinstance(item, DirectoryItem): info_labels = {} + info_type = 'video' value = item.get_name() if value is not None: @@ -132,54 +95,69 @@ def set_info(list_item, item, properties, set_play_count=True, resume=True): info_labels['plot'] = value if info_labels: - list_item.setInfo('video', info_labels) + list_item.setInfo(info_type, info_labels) if properties: list_item.setProperties(properties) return - elif isinstance(item, AudioItem): - info_labels = {} - - value = item.get_album_name() + elif isinstance(item, ImageItem): + value = item.get_title() if value is not None: - info_labels['album'] = value + list_item.setInfo('picture', {'title': value}) - value = item.get_artists() - if value is not None: - info_labels['artist'] = value + if properties: + list_item.setProperties(properties) + return - value = item.get_duration() - if value is not None: - info_labels['duration'] = value + else: + return - value = item.get_rating() - if value is not None: - info_labels['rating'] = value + value = item.get_artists_string() + if value is not None: + info_labels['artist'] = value - value = item.get_title() - if value is not None: - info_labels['title'] = value + value = item.get_count() + if value is not None: + info_labels['count'] = value - value = item.get_track_number() - if value is not None: - info_labels['tracknumber'] = value + value = item.get_date(as_info_label=True) + if value is not None: + info_labels['date'] = value - value = item.get_year() - if value is not None: - info_labels['year'] = value + value = item.get_duration() + if value is not None: + info_labels['duration'] = value - if info_labels: - list_item.setInfo('music', info_labels) + value = item.get_last_played(as_info_label=True) + if value is not None: + info_labels['lastplayed'] = value - elif isinstance(item, ImageItem): - value = item.get_title() - if value is not None: - list_item.setInfo('picture', {'title': value}) + value = item.get_mediatype() + if value is not None: + info_labels['mediatype'] = value - if properties: - list_item.setProperties(properties) - return + value = item.get_play_count() + if value is not None: + if set_play_count: + info_labels['playcount'] = value + properties[PLAY_COUNT] = value + + value = item.get_rating() + if value is not None: + info_labels['rating'] = value + + value = item.get_title() + if value is not None: + info_labels['title'] = value + + value = item.get_track_number() + if value is not None: + info_labels['tracknumber'] = value + + value = item.get_year() + if value is not None: + info_labels['year'] = value resume_time = resume and item.get_start_time() if resume_time: @@ -187,22 +165,23 @@ def set_info(list_item, item, properties, set_play_count=True, resume=True): duration = item.get_duration() if duration: properties['TotalTime'] = str(duration) - if is_video: - list_item.addStreamInfo('video', {'duration': duration}) + if info_type == 'video': + list_item.addStreamInfo(info_type, {'duration': duration}) if properties: list_item.setProperties(properties) + + if info_labels: + list_item.setInfo(info_type, info_labels) return value = item.get_date(as_info_label=True) if value is not None: list_item.setDateTime(value) - info_tag = None - if isinstance(item, VideoItem): - is_video = True info_tag = list_item.getVideoInfoTag() + info_type = 'video' value = item.get_aired(as_info_label=True) if value is not None: @@ -212,14 +191,17 @@ def set_info(list_item, item, properties, set_play_count=True, resume=True): if value is not None: info_tag.setDateAdded(value) - value = item.get_last_played(as_info_label=True) - if value is not None: - info_tag.setLastPlayed(value) - value = item.get_premiered(as_info_label=True) if value is not None: info_tag.setPremiered(value) + # artist: list[str] + # eg. ["Angerfist"] + # Used as alias for channel name + value = item.get_artists() + if value is not None: + info_tag.setArtists(value) + # cast: list[xbmc.Actor] # From list[{member: str, role: str, order: int, thumbnail: str}] # Used as alias for channel name if enabled @@ -227,26 +209,12 @@ def set_info(list_item, item, properties, set_play_count=True, resume=True): if value is not None: info_tag.setCast([xbmc.Actor(**member) for member in value]) - # code: str - # eg. "466K | 3.9K | 312" - # Production code, currently used to store misc video data for label - # formatting - value = item.get_code() - if value is not None: - info_tag.setProductionCode(value) - - # count: int - # eg. 12 - # Can be used to store an id for later, or for sorting purposes - # Used for Youtube video view count - value = item.get_count() - if value is not None: - list_item.setInfo('video', {'count': value}) - # director: list[str] # eg. "Steven Spielberg" # Currently unused - # info_tag.setDirectors(item.get_directors()) + # value = item.get_directors() + # if value is not None: + # info_tag.setDirectors(value) # episode: int value = item.get_episode() @@ -256,25 +224,23 @@ def set_info(list_item, item, properties, set_play_count=True, resume=True): # imdbnumber: str # eg. "tt3458353" # Currently unused - # info_tag.setIMDBNumber(item.get_imdb_id()) - - # mediatype: str - value = item.get_mediatype() - if value is not None: - info_tag.setMediaType(value) - - # playcount: int - value = item.get_play_count() - if value is not None: - if set_play_count: - info_tag.setPlaycount(value) - properties[PLAY_COUNT] = value + # value = item.get_imdb_id() + # if value is not None: + # info_tag.setIMDBNumber(value) # plot: str value = item.get_plot() if value is not None: info_tag.setPlot(value) + # code: str + # eg. "466K | 3.9K | 312" + # Production code, currently used to store misc video data for label + # formatting + value = item.get_production_code() + if value is not None: + info_tag.setProductionCode(value) + # season: int value = item.get_season() if value is not None: @@ -286,6 +252,44 @@ def set_info(list_item, item, properties, set_play_count=True, resume=True): if value is not None: info_tag.setStudios(value) + # tracknumber: int + # eg. 12 + value = item.get_track_number() + if value is not None: + info_tag.setTrackNumber(value) + + elif isinstance(item, AudioItem): + info_tag = list_item.getMusicInfoTag() + info_type = 'music' + + value = item.get_premiered(as_info_label=True) + if value is not None: + info_tag.setReleaseDate(value) + + # album: str + # eg. "Buckle Up" + value = item.get_album_name() + if value is not None: + info_tag.setAlbum(value) + + # artist: str + # eg. "Artist 1, Artist 2" + # Used as alias for channel name + value = item.get_artists_string() + if value is not None: + info_tag.setArtist(value) + + # comment: str + value = item.get_plot() + if value is not None: + info_tag.setComment(value) + + # track: int + # eg. 12 + value = item.get_track_number() + if value is not None: + info_tag.setTrack(value) + elif isinstance(item, DirectoryItem): info_tag = list_item.getVideoInfoTag() @@ -312,14 +316,8 @@ def set_info(list_item, item, properties, set_play_count=True, resume=True): list_item.setProperties(properties) return - elif isinstance(item, AudioItem): - info_tag = list_item.getMusicInfoTag() - - # album: str - # eg. "Buckle Up" - value = item.get_album_name() - if value is not None: - info_tag.setAlbum(value) + else: + return resume_time = resume and item.get_start_time() duration = item.get_duration() @@ -327,25 +325,44 @@ def set_info(list_item, item, properties, set_play_count=True, resume=True): info_tag.setResumePoint(resume_time, float(duration)) elif resume_time: info_tag.setResumePoint(resume_time) - if is_video and duration: + if info_type == 'video' and duration: info_tag.addVideoStream(xbmc.VideoStreamDetail(duration=duration)) - # artist: list[str] - # eg. ["Angerfist"] - # Used as alias for channel name - value = item.get_artists() - if value is not None: - info_tag.setArtists(value) - # duration: int # As seconds if duration is not None: info_tag.setDuration(duration) + # mediatype: str + value = item.get_mediatype() + if value is not None: + info_tag.setMediaType(value) + + value = item.get_last_played(as_info_label=True) + if value is not None: + info_tag.setLastPlayed(value) + + # playcount: int + value = item.get_play_count() + if value is not None: + if set_play_count: + info_tag.setPlaycount(value) + properties[PLAY_COUNT] = value + + # count: int + # eg. 12 + # Can be used to store an id for later, or for sorting purposes + # Used for Youtube video view count + value = item.get_count() + if value is not None: + list_item.setInfo(info_type, {'count': value}) + # genre: list[str] # eg. ["Hardcore"] # Currently unused - # info_tag.setGenres(item.get_genres()) + # value = item.get_genres() + # if value is not None: + # info_tag.setGenres(value) # rating: float value = item.get_rating() @@ -358,12 +375,6 @@ def set_info(list_item, item, properties, set_play_count=True, resume=True): if value is not None: info_tag.setTitle(value) - # tracknumber: int - # eg. 12 - value = item.get_track_number() - if value is not None: - info_tag.setTrackNumber(value) - # year: int # eg. 1994 value = item.get_year() @@ -374,14 +385,12 @@ def set_info(list_item, item, properties, set_play_count=True, resume=True): list_item.setProperties(properties) -def video_playback_item(context, video_item, show_fanart=None, **_kwargs): - uri = video_item.get_uri() - context.log_debug('Converting VideoItem |%s|' % redact_ip(uri)) +def playback_item(context, media_item, show_fanart=None, **_kwargs): + uri = media_item.get_uri() + context.log_debug('Converting %s |%s|' % (media_item.__class__.__name__, + redact_ip(uri))) settings = context.get_settings() - headers = video_item.get_headers() - license_key = video_item.get_license_key() - ui = context.get_ui() is_external = ui.get_property(PLAY_WITH) is_strm = context.get_param('strm') @@ -395,19 +404,23 @@ def video_playback_item(context, video_item, show_fanart=None, **_kwargs): props = {} else: kwargs = { - 'label': video_item.get_title() or video_item.get_name(), - 'label2': video_item.get_short_details(), + 'label': media_item.get_title() or media_item.get_name(), + 'label2': media_item.get_short_details(), 'path': uri, 'offscreen': True, } props = { - 'isPlayable': str(video_item.playable).lower(), + 'isPlayable': str(media_item.playable).lower(), + 'playlist_type_hint': ( + xbmc.PLAYLIST_MUSIC if isinstance(media_item, AudioItem) else + xbmc.PLAYLIST_VIDEO + ), } - if video_item.use_isa_video() and context.use_inputstream_adaptive(): + if media_item.use_isa() and context.use_inputstream_adaptive(): capabilities = context.inputstream_adaptive_capabilities() - use_mpd = video_item.use_mpd_video() + use_mpd = media_item.use_mpd() if use_mpd: manifest_type = 'mpd' mime_type = 'application/dash+xml' @@ -430,7 +443,7 @@ def video_playback_item(context, video_item, show_fanart=None, **_kwargs): if not current_system_version.compatible(21, 0): props['inputstream.adaptive.manifest_type'] = manifest_type - if video_item.live: + if media_item.live: if 'manifest_config_prop' in capabilities: props['inputstream.adaptive.manifest_config'] = dumps({ 'timeshift_bufferlimit': 4 * 60 * 60, @@ -443,10 +456,12 @@ def video_playback_item(context, video_item, show_fanart=None, **_kwargs): 'ssl_verify_peer': False, }) + headers = media_item.get_headers() if headers: props['inputstream.adaptive.manifest_headers'] = headers props['inputstream.adaptive.stream_headers'] = headers + license_key = media_item.get_license_key() if license_key: props['inputstream.adaptive.license_type'] = 'com.widevine.alpha' props['inputstream.adaptive.license_key'] = license_key @@ -456,6 +471,7 @@ def video_playback_item(context, video_item, show_fanart=None, **_kwargs): mime_type = uri.split('mime=', 1)[1].split('&', 1)[0] mime_type = mime_type.replace('%2F', '/') + headers = media_item.get_headers() if (headers and uri.startswith('http') and not (is_external or settings.default_player_web_urls())): @@ -473,66 +489,23 @@ def video_playback_item(context, video_item, show_fanart=None, **_kwargs): if show_fanart is None: show_fanart = settings.fanart_selection() - image = video_item.get_image() + image = media_item.get_image() art = {'icon': image} if image: art['thumb'] = image if show_fanart: - art['fanart'] = video_item.get_fanart() + art['fanart'] = media_item.get_fanart() list_item.setArt(art) - if video_item.subtitles: - list_item.setSubtitles(video_item.subtitles) + if media_item.subtitles: + list_item.setSubtitles(media_item.subtitles) resume = context.get_param('resume') - set_info(list_item, video_item, props, resume=resume) + set_info(list_item, media_item, props, resume=resume) return list_item -def audio_listitem(context, - audio_item, - show_fanart=None, - for_playback=False, - **_kwargs): - uri = audio_item.get_uri() - context.log_debug('Converting AudioItem |%s|' % uri) - - kwargs = { - 'label': audio_item.get_title() or audio_item.get_name(), - 'label2': audio_item.get_short_details(), - 'path': uri, - 'offscreen': True, - } - props = { - 'isPlayable': str(audio_item.playable).lower(), - 'ForceResolvePlugin': 'true', - } - - list_item = xbmcgui.ListItem(**kwargs) - - if show_fanart is None: - show_fanart = context.get_settings().fanart_selection() - image = audio_item.get_image() - art = {'icon': image} - if image: - art['thumb'] = image - if show_fanart: - art['fanart'] = audio_item.get_fanart() - list_item.setArt(art) - - resume = context.get_param('resume') or not for_playback - set_info(list_item, audio_item, props, resume=resume) - - context_menu = audio_item.get_context_menu() - if context_menu: - list_item.addContextMenuItems(context_menu) - - if for_playback: - return list_item - return uri, list_item, False - - def directory_listitem(context, directory_item, show_fanart=None, **_kwargs): uri = directory_item.get_uri() context.log_debug('Converting DirectoryItem |%s|' % uri) @@ -655,33 +628,38 @@ def uri_listitem(context, uri_item, **_kwargs): return list_item -def video_listitem(context, - video_item, +def media_listitem(context, + media_item, show_fanart=None, focused=None, **_kwargs): - uri = video_item.get_uri() - context.log_debug('Converting VideoItem |%s|' % uri) + uri = media_item.get_uri() + context.log_debug('Converting %s |%s|' % (media_item.__class__.__name__, + uri)) kwargs = { - 'label': video_item.get_title() or video_item.get_name(), - 'label2': video_item.get_short_details(), + 'label': media_item.get_title() or media_item.get_name(), + 'label2': media_item.get_short_details(), 'path': uri, 'offscreen': True, } props = { - 'isPlayable': str(video_item.playable).lower(), + 'isPlayable': str(media_item.playable).lower(), 'ForceResolvePlugin': 'true', + 'playlist_type_hint': ( + xbmc.PLAYLIST_MUSIC if isinstance(media_item, AudioItem) else + xbmc.PLAYLIST_VIDEO + ), } - published_at = video_item.get_added_utc() - scheduled_start = video_item.get_scheduled_start_utc() + published_at = media_item.get_added_utc() + scheduled_start = media_item.get_scheduled_start_utc() datetime = scheduled_start or published_at local_datetime = None if datetime: local_datetime = datetime_parser.utc_to_local(datetime) props['PublishedLocal'] = to_str(local_datetime) - if video_item.live: + if media_item.live: props['PublishedSince'] = context.localize('live') elif local_datetime: props['PublishedSince'] = to_str(datetime_parser.datetime_to_since( @@ -690,7 +668,7 @@ def video_listitem(context, set_play_count = True resume = True - prop_value = video_item.video_id + prop_value = media_item.video_id if prop_value: if focused and focused == prop_value: set_play_count = False @@ -698,22 +676,22 @@ def video_listitem(context, props[VIDEO_ID] = prop_value # make channel_id property available for keymapping - prop_value = video_item.channel_id + prop_value = media_item.channel_id if prop_value: props[CHANNEL_ID] = prop_value # make subscription_id property available for keymapping - prop_value = video_item.subscription_id + prop_value = media_item.subscription_id if prop_value: props[SUBSCRIPTION_ID] = prop_value # make playlist_id property available for keymapping - prop_value = video_item.playlist_id + prop_value = media_item.playlist_id if prop_value: props[PLAYLIST_ID] = prop_value # make playlist_item_id property available for keymapping - prop_value = video_item.playlist_item_id + prop_value = media_item.playlist_item_id if prop_value: props[PLAYLISTITEM_ID] = prop_value @@ -721,34 +699,34 @@ def video_listitem(context, if show_fanart is None: show_fanart = context.get_settings().fanart_selection() - image = video_item.get_image() + image = media_item.get_image() art = {'icon': image} if image: art['thumb'] = image if show_fanart: - art['fanart'] = video_item.get_fanart() + art['fanart'] = media_item.get_fanart() list_item.setArt(art) - if video_item.subtitles: - list_item.setSubtitles(video_item.subtitles) + if media_item.subtitles: + list_item.setSubtitles(media_item.subtitles) set_info(list_item, - video_item, + media_item, props, set_play_count=set_play_count, resume=resume) if not set_play_count: - video_id = video_item.video_id + video_id = media_item.video_id playback_history = context.get_playback_history() playback_history.set_item(video_id, dict( playback_history.get_item(video_id) or {}, - play_count=int(not video_item.get_play_count()), + play_count=int(not media_item.get_play_count()), played_time=0.0, played_percent=0, )) - context_menu = video_item.get_context_menu() + context_menu = media_item.get_context_menu() if context_menu: list_item.addContextMenuItems(context_menu) diff --git a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py index 5b8ee0ea6..a914c2051 100644 --- a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py +++ b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py @@ -14,7 +14,7 @@ from ..abstract_playlist import AbstractPlaylist from ...compatibility import xbmc -from ...items import VideoItem, video_listitem +from ...items import VideoItem, media_listitem from ...utils.methods import jsonrpc, wait @@ -44,7 +44,7 @@ def clear(self): self._playlist.clear() def add(self, base_item): - uri, item, _ = video_listitem(self._context, base_item) + uri, item, _ = media_listitem(self._context, base_item) if item: self._playlist.add(uri, listitem=item) diff --git a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py index a4e9912a7..1df1e133d 100644 --- a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py +++ b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py @@ -28,19 +28,18 @@ ) from ...exceptions import KodionException from ...items import ( - audio_listitem, directory_listitem, image_listitem, + media_listitem, + playback_item, uri_listitem, - video_listitem, - video_playback_item, ) from ...player import XbmcPlaylist class XbmcPlugin(AbstractPlugin): _LIST_ITEM_MAP = { - 'AudioItem': audio_listitem, + 'AudioItem': media_listitem, 'CommandItem': directory_listitem, 'DirectoryItem': directory_listitem, 'ImageItem': image_listitem, @@ -48,14 +47,14 @@ class XbmcPlugin(AbstractPlugin): 'SearchHistoryItem': directory_listitem, 'NewSearchItem': directory_listitem, 'NextPageItem': directory_listitem, - 'VideoItem': video_listitem, + 'VideoItem': media_listitem, 'WatchLaterItem': directory_listitem, } _PLAY_ITEM_MAP = { - 'AudioItem': audio_listitem, + 'AudioItem': playback_item, 'UriItem': uri_listitem, - 'VideoItem': video_playback_item, + 'VideoItem': playback_item, } def __init__(self): @@ -204,7 +203,6 @@ def run(self, provider, context, focused=None): context, result, show_fanart=context.get_settings().fanart_selection(), - for_playback=True, ) result = True xbmcplugin.setResolvedUrl(self.handle, diff --git a/resources/lib/youtube_plugin/kodion/plugin_runner.py b/resources/lib/youtube_plugin/kodion/plugin_runner.py index 66e361603..e5fa69fcf 100644 --- a/resources/lib/youtube_plugin/kodion/plugin_runner.py +++ b/resources/lib/youtube_plugin/kodion/plugin_runner.py @@ -10,7 +10,6 @@ from __future__ import absolute_import, division, unicode_literals -from copy import deepcopy from platform import python_version from .context import XbmcContext @@ -44,7 +43,7 @@ def run(context=_context, context.init() new_uri = context.get_uri() - params = deepcopy(context.get_params()) + params = context.get_params().copy() for key in ('api_key', 'client_id', 'client_secret'): if key in params: params[key] = '' diff --git a/resources/lib/youtube_plugin/kodion/sql_store/bookmarks_list.py b/resources/lib/youtube_plugin/kodion/sql_store/bookmarks_list.py index 1fe376796..c7ca563aa 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/bookmarks_list.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/bookmarks_list.py @@ -16,7 +16,6 @@ class BookmarksList(Storage): _table_name = 'storage_v2' - _table_created = False _table_updated = False _sql = {} diff --git a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py index 595813be8..3c6f6a08f 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/data_cache.py @@ -15,7 +15,6 @@ class DataCache(Storage): _table_name = 'storage_v2' - _table_created = False _table_updated = False _sql = {} diff --git a/resources/lib/youtube_plugin/kodion/sql_store/feed_history.py b/resources/lib/youtube_plugin/kodion/sql_store/feed_history.py index c759c09e2..0efbdd9ed 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/feed_history.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/feed_history.py @@ -14,7 +14,6 @@ class FeedHistory(Storage): _table_name = 'storage_v2' - _table_created = False _table_updated = False _sql = {} diff --git a/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py index 9491c3dbc..0679b3708 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py @@ -19,7 +19,6 @@ class FunctionCache(Storage): _table_name = 'storage_v2' - _table_created = False _table_updated = False _sql = {} diff --git a/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py index 8f679075f..48ed96542 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/playback_history.py @@ -14,7 +14,6 @@ class PlaybackHistory(Storage): _table_name = 'storage_v2' - _table_created = False _table_updated = False _sql = {} diff --git a/resources/lib/youtube_plugin/kodion/sql_store/search_history.py b/resources/lib/youtube_plugin/kodion/sql_store/search_history.py index 66e5ad723..3ebe58121 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/search_history.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/search_history.py @@ -17,7 +17,6 @@ class SearchHistory(Storage): _table_name = 'storage_v2' - _table_created = False _table_updated = False _sql = {} diff --git a/resources/lib/youtube_plugin/kodion/sql_store/storage.py b/resources/lib/youtube_plugin/kodion/sql_store/storage.py index 6edb6ce2f..0425dbc56 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/storage.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/storage.py @@ -31,7 +31,6 @@ class Storage(object): _base = None _table_name = 'storage_v2' - _table_created = False _table_updated = False _sql = { @@ -175,7 +174,6 @@ def __init__(self, self._base = self self._sql = {} self._table_name = migrate - self._table_created = True self._table_updated = True else: self._base = self.__class__ @@ -219,16 +217,18 @@ def __exit__(self, exc_type=None, exc_val=None, exc_tb=None): self._lock.release() def _open(self): + statements = [] if not os.path.exists(self._filepath): make_dirs(os.path.dirname(self._filepath)) - self._base._table_created = False + statements.append( + self._sql['create_table'] + ) self._base._table_updated = True for _ in range(3): try: db = sqlite3.connect(self._filepath, check_same_thread=False, - timeout=1, isolation_level=None) break except (sqlite3.Error, sqlite3.OperationalError) as exc: @@ -248,20 +248,14 @@ def _open(self): 'PRAGMA busy_timeout = 1000;', 'PRAGMA read_uncommitted = TRUE;', 'PRAGMA secure_delete = FALSE;', - 'PRAGMA synchronous = NORMAL;', - 'PRAGMA locking_mode = NORMAL;' + 'PRAGMA synchronous = OFF;', + 'PRAGMA locking_mode = EXCLUSIVE;' 'PRAGMA temp_store = MEMORY;', 'PRAGMA mmap_size = 4096000;', 'PRAGMA page_size = 4096;', 'PRAGMA cache_size = 1000;', 'PRAGMA journal_mode = WAL;', ] - statements = [] - - if not self._table_created: - statements.append( - self._sql['create_table'] - ) if not self._table_updated: for result in self._execute(cursor, self._sql['has_old_table']): @@ -279,7 +273,6 @@ def _open(self): sql_script[transaction_begin:transaction_begin] = statements self._execute(cursor, '\n'.join(sql_script), script=True) - self._base._table_created = True self._base._table_updated = True self._db = db self._cursor = cursor diff --git a/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py index 54150c577..b876ce2f1 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/watch_later_list.py @@ -16,7 +16,6 @@ class WatchLaterList(Storage): _table_name = 'storage_v2' - _table_created = False _table_updated = False _sql = {} diff --git a/resources/lib/youtube_plugin/kodion/utils/methods.py b/resources/lib/youtube_plugin/kodion/utils/methods.py index e0337b16a..bdb55f6fa 100644 --- a/resources/lib/youtube_plugin/kodion/utils/methods.py +++ b/resources/lib/youtube_plugin/kodion/utils/methods.py @@ -10,7 +10,6 @@ from __future__ import absolute_import, division, unicode_literals -import copy import json import os import re @@ -97,13 +96,15 @@ def _stream_sort(_stream): context.log_debug('Available streams: {0}'.format(num_streams)) for idx, stream in enumerate(stream_list): - log_data = copy.deepcopy(stream) + log_data = stream.copy() if 'license_info' in log_data: + license_info = log_data['license_info'].copy() for detail in ('url', 'token'): - original_value = log_data['license_info'].get(detail) + original_value = license_info.get(detail) if original_value: - log_data['license_info'][detail] = '' + license_info[detail] = '' + log_data['license_info'] = license_info original_value = log_data.get('url') if original_value: @@ -313,4 +314,4 @@ def wait(timeout=None): def redact_ip(url): - return re.sub(r'([?&/])ip([=/])[^?&/]+', '\g<1>ip\g<2>', url) + return re.sub(r'([?&/])ip([=/])[^?&/]+', r'\g<1>ip\g<2>', url) diff --git a/resources/lib/youtube_plugin/youtube/client/request_client.py b/resources/lib/youtube_plugin/youtube/client/request_client.py index bf1fb5159..506241b8b 100644 --- a/resources/lib/youtube_plugin/youtube/client/request_client.py +++ b/resources/lib/youtube_plugin/youtube/client/request_client.py @@ -356,12 +356,20 @@ def build_client(cls, client_name=None, data=None): params = client['params'] if client.get('_access_token'): if 'key' in params: + params = params.copy() del params['key'] + client['params'] = params else: - if 'Authorization' in client['headers']: - del client['headers']['Authorization'] + headers = client['headers'] + if 'Authorization' in headers: + headers = headers.copy() + del headers['Authorization'] + client['headers'] = headers + if 'key' in params and params['key'] is ValueError: + params = params.copy() del params['key'] + client['params'] = params except KeyError: pass diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py index 8295d2a16..542f4f12e 100644 --- a/resources/lib/youtube_plugin/youtube/client/youtube.py +++ b/resources/lib/youtube_plugin/youtube/client/youtube.py @@ -12,15 +12,16 @@ import threading import xml.etree.ElementTree as ET -from copy import deepcopy from functools import partial from itertools import chain, islice from random import randint +from traceback import format_exc from .login_client import LoginClient from ..helper.stream_info import StreamInfo from ..youtube_exceptions import InvalidJSON, YouTubeException from ...kodion.compatibility import cpu_count, string_type, to_str +from ...kodion.items import DirectoryItem from ...kodion.utils import ( current_system_version, datetime_parser, @@ -341,7 +342,6 @@ def get_subscription(self, page_token='', **kwargs): """ - :param channel_id: [channel-id|'mine'] :param order: ['alphabetical'|'relevance'|'unread'] :param page_token: @@ -1444,7 +1444,12 @@ def _sort_by_date_time(item, limits): limits['video_ids'].add(video_id) return item['_timestamp'] - channel_ids = [] + threaded_output = { + 'channel_ids': [], + 'feeds': {}, + 'do_refresh': [], + } + params = { 'part': 'snippet', 'maxResults': '50', @@ -1452,36 +1457,35 @@ def _sort_by_date_time(item, limits): 'mine': 'true' } - def _get_channels(_params=params): - if not _params or 'complete' in _params: - return None, None + def _get_channels(output, _params=params): json_data = self.api_request(method='GET', path='subscriptions', params=_params, **kwargs) if not json_data: - return None, None + return False, True + + output['channel_ids'].extend([{ + 'channel_id': item['snippet']['resourceId']['channelId'] + } for item in json_data.get('items', [])]) subs_page_token = json_data.get('nextPageToken') if subs_page_token: _params['pageToken'] = subs_page_token - else: - _params['complete'] = True - - return 'list_list', [{ - 'channel_id': item['snippet']['resourceId']['channelId'] - } for item in json_data.get('items', [])] + return True, False + return True, True bookmarks = self._context.get_bookmarks_list().get_items() if bookmarks: - channel_ids.extend([ - {'channel_id': item_id} - for item_id, item in bookmarks.items() - if (isinstance(item, float) - or getattr(item, 'get_channel_id', bool)()) - ]) - - feeds = {} + channel_ids = threaded_output['channel_ids'] + for item_id, item in bookmarks.items(): + if isinstance(item, DirectoryItem): + item_id = getattr(item, 'channel_id', None) + elif not isinstance(item, float): + continue + if item_id: + channel_ids.append({'channel_id': item_id}) + headers = { 'Host': 'www.youtube.com', 'Connection': 'keep-alive', @@ -1497,7 +1501,7 @@ def _get_channels(_params=params): 'Accept-Language': 'en-US,en;q=0.7,de;q=0.3' } - def _get_feed_cache(channel_id, _cache=cache, _refresh=refresh): + def _get_feed_cache(output, channel_id, _cache=cache, _refresh=refresh): cached = _cache.get_item(channel_id) if cached: feed_details = cached['value'] @@ -1510,19 +1514,33 @@ def _get_feed_cache(channel_id, _cache=cache, _refresh=refresh): _refresh = True if _refresh: + output['do_refresh'].append({'channel_id': channel_id}) feed_details['refresh'] = True - return 'dict_dict_dict', (channel_id, feed_details) + feeds = output['feeds'] + if channel_id in feeds: + feeds[channel_id].update(feed_details) + else: + feeds[channel_id] = feed_details + return True, False - def _get_feed(channel_id, _headers=headers): - return 'dict_dict_dict', (channel_id, { + def _get_feed(output, channel_id, _headers=headers): + _output = { 'content': self.request( 'https://www.youtube.com/feeds/videos.xml?channel_id=' + channel_id, headers=_headers, ), 'refresh': True, - }) + } + + feeds = output['feeds'] + if channel_id in feeds: + feeds[channel_id].update(_output) + else: + feeds[channel_id] = _output + + return True, False namespaces = { 'atom': 'http://www.w3.org/2005/Atom', @@ -1604,10 +1622,11 @@ def _threaded_fetch(kwargs, worker, threads, pool_id, - dynamic, input_wait, **_kwargs): + complete = False while not threads['balance'].is_set(): + threads['loop'].set() if kwargs is True: _kwargs = {} elif kwargs: @@ -1617,48 +1636,33 @@ def _threaded_fetch(kwargs, input_wait.release() if kwargs: continue + complete = True break else: + complete = True break try: - output_type, _output = worker(**_kwargs) + success, complete = worker(output, **_kwargs) except Exception as exc: - self._context.log_error('threaded_fetch error: |{exc}|' - .format(exc=exc)) + msg = 'get_my_subscriptions._threaded_fetch - {exc}' + self._context.log_error(msg.format(exc=format_exc())) continue - if not output_type: + if complete or not success: break - if output_type == 'value_dict': - output[_output[0]] = _output[1] - elif output_type == 'dict_dict': - output.update(_output) - elif output_type == 'value_list': - output.append(_output) - elif output_type == 'list_list': - output.extend(_output) - elif output_type == 'value_list_dict': - if _output[0] not in output: - output[_output[0]] = [] - output[_output[0]].append(_output[1]) - elif output_type == 'list_list_dict': - if _output[0] not in output: - output[_output[0]] = [] - output[_output[0]].extend(_output[1]) - elif output_type == 'dict_dict_dict': - if _output[0] not in output: - output[_output[0]] = {} - output[_output[0]].update(_output[1]) else: threads['balance'].clear() - thread = threading.current_thread() + current_thread = threading.current_thread() threads['available'].release() - if dynamic: + if complete: + threads['pool_counts'][pool_id] = None + else: threads['pool_counts'][pool_id] -= 1 threads['pool_counts']['all'] -= 1 - threads['current'].discard(thread) + threads['current'].discard(current_thread) + threads['loop'].set() try: num_cores = cpu_count() or 1 @@ -1673,85 +1677,89 @@ def _threaded_fetch(kwargs, 'all': 0, }, 'balance': threading.Event(), + 'loop': threading.Event(), } - payloads = [ - { - 'pool_id': 1, - 'kwargs': True, - 'output': channel_ids, + payloads = {} + if logged_in: + payloads[1] = { 'worker': _get_channels, + 'kwargs': True, + 'output': threaded_output, 'threads': threads, 'limit': 1, - 'dynamic': False, 'input_wait': None, - }, - ] if logged_in else [] - payloads.extend(( - { - 'pool_id': 2, - 'kwargs': channel_ids, - 'output': feeds, + } + payloads.update({ + 2: { 'worker': _get_feed_cache, + 'kwargs': threaded_output['channel_ids'], + 'output': threaded_output, 'threads': threads, 'limit': 1, - 'dynamic': False, 'input_wait': threading.Lock(), }, - { - 'pool_id': 3, - 'kwargs': channel_ids, - 'output': feeds, + 3: { 'worker': _get_feed, + 'kwargs': threaded_output['do_refresh'], + 'output': threaded_output, 'threads': threads, 'limit': None, - 'dynamic': True, 'input_wait': threading.Lock(), }, - )) - while 1: - for payload in payloads: - pool_id = payload['pool_id'] - if pool_id in threads['pool_counts']: - current_num = threads['pool_counts'][pool_id] - else: - current_num = threads['pool_counts'][pool_id] = 0 + }) + + completed = [] + iterator = iter(payloads) + threads['loop'].set() + while threads['loop'].wait(): + try: + pool_id = next(iterator) + except StopIteration: + threads['loop'].clear() + if not threads['current']: + break + for pool_id in completed: + del payloads[pool_id] + completed = [] + iterator = iter(payloads) + continue + payload = payloads[pool_id] + payload['pool_id'] = pool_id + current_num = threads['pool_counts'].setdefault(pool_id, 0) + if current_num is None: + completed.append(pool_id) + continue + + if payload['kwargs']: input_wait = payload['input_wait'] - if payload['kwargs']: - if input_wait and input_wait.locked(): - input_wait.release() - else: - continue + if input_wait and input_wait.locked(): + input_wait.release() + else: + continue - available = threads['max'] - threads['pool_counts']['all'] - limit = payload['limit'] - if limit: - if current_num >= limit: - continue - if not available: - threads['balance'].set() - elif not available: + available = threads['max'] - threads['pool_counts']['all'] + limit = payload['limit'] + if limit: + if current_num >= limit: continue + if available <= 0: + threads['balance'].set() + elif available <= 0: + continue - thread = threading.Thread( - target=_threaded_fetch, - kwargs=payload, - ) - thread.daemon = True - threads['current'].add(thread) - threads['pool_counts'][pool_id] += 1 - threads['pool_counts']['all'] += 1 - threads['available'].acquire(True) - thread.start() - - if not threads['current']: - break - - for thread in threads['current']: - if thread and thread.is_alive(): - thread.join(30) + new_thread = threading.Thread( + target=_threaded_fetch, + kwargs=payload, + ) + new_thread.daemon = True + threads['current'].add(new_thread) + threads['pool_counts'][pool_id] += 1 + threads['pool_counts']['all'] += 1 + threads['available'].acquire(True) + new_thread.start() - items = _parse_feeds(feeds) + items = _parse_feeds(threaded_output['feeds']) # filter, sorting by publish date and trim if items: @@ -2022,19 +2030,21 @@ def api_request(self, client = self.build_client(version, client_data) - if 'key' in client['params'] and not client['params']['key']: + params = client.get('params') + if 'key' in params and not params['key']: + params = params.copy() key = self._config.get('key') or self._config_tv.get('key') if key: - client['params']['key'] = key + params['key'] = key else: - del client['params']['key'] + del params['key'] + client['params'] = params if clear_data and 'json' in client: del client['json'] - params = client.get('params') if params: - log_params = deepcopy(params) + log_params = params.copy() if 'location' in log_params: log_params['location'] = '|xx.xxxx,xx.xxxx|' if 'key' in log_params: @@ -2045,7 +2055,7 @@ def api_request(self, headers = client.get('headers') if headers: - log_headers = deepcopy(headers) + log_headers = headers.copy() if 'Authorization' in log_headers: log_headers['Authorization'] = '|logged in|' else: diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py index da6d00da1..178dcee16 100644 --- a/resources/lib/youtube_plugin/youtube/helper/utils.py +++ b/resources/lib/youtube_plugin/youtube/helper/utils.py @@ -429,8 +429,10 @@ def update_video_infos(provider, context, video_id_dict, continue snippet = yt_item['snippet'] - video_item = video_id_dict[video_id] - video_item.set_mediatype(CONTENT.VIDEO_TYPE) + media_item = video_id_dict[video_id] + media_item.set_mediatype( + CONTENT.AUDIO_TYPE if audio_only else CONTENT.VIDEO_TYPE + ) play_data = use_play_data and yt_item.get('play_data') if play_data and 'total_time' in play_data: @@ -443,84 +445,84 @@ def update_video_infos(provider, context, video_id_dict, # subtract 1s because YouTube duration is +1s too long duration = duration.seconds - 1 if duration: - video_item.set_duration_from_seconds(duration) + media_item.set_duration_from_seconds(duration) if duration <= 60: - video_item.short = True + media_item.short = True broadcast_type = snippet.get('liveBroadcastContent') - video_item.live = broadcast_type == 'live' - video_item.upcoming = broadcast_type == 'upcoming' + media_item.live = broadcast_type == 'live' + media_item.upcoming = broadcast_type == 'upcoming' upload_status = yt_item.get('status', {}).get('uploadStatus') if upload_status == 'processed' and duration: - video_item.live = False + media_item.live = False elif upload_status == 'uploaded' and not duration: - video_item.live = True + media_item.live = True if 'liveStreamingDetails' in yt_item: streaming_details = yt_item['liveStreamingDetails'] if 'actualStartTime' in streaming_details: start_at = streaming_details['actualStartTime'] - video_item.upcoming = False + media_item.upcoming = False if 'actualEndTime' in streaming_details: - video_item.completed = True + media_item.completed = True else: start_at = streaming_details.get('scheduledStartTime') - video_item.upcoming = True + media_item.upcoming = True else: - video_item.completed = False - video_item.live = False - video_item.upcoming = False - video_item.vod = True + media_item.completed = False + media_item.live = False + media_item.upcoming = False + media_item.vod = True start_at = None if item_filter and ( (not item_filter['shorts'] - and video_item.short) + and media_item.short) or (not item_filter['completed'] - and video_item.completed) + and media_item.completed) or (not item_filter['live'] - and video_item.live and not video_item.upcoming) + and media_item.live and not media_item.upcoming) or (not item_filter['upcoming'] - and video_item.upcoming) + and media_item.upcoming) or (not item_filter['premieres'] - and video_item.upcoming and not video_item.live) + and media_item.upcoming and not media_item.live) or (not item_filter['upcoming_live'] - and video_item.upcoming and video_item.live) + and media_item.upcoming and media_item.live) or (not item_filter['vod'] - and video_item.vod) + and media_item.vod) ): continue - if not video_item.live and play_data: + if not media_item.live and play_data: if 'play_count' in play_data: - video_item.set_play_count(play_data['play_count']) + media_item.set_play_count(play_data['play_count']) if 'played_percent' in play_data: - video_item.set_start_percent(play_data['played_percent']) + media_item.set_start_percent(play_data['played_percent']) if 'played_time' in play_data: - video_item.set_start_time(play_data['played_time']) + media_item.set_start_time(play_data['played_time']) if 'last_played' in play_data: - video_item.set_last_played(play_data['last_played']) - elif video_item.live: - video_item.set_play_count(0) + media_item.set_last_played(play_data['last_played']) + elif media_item.live: + media_item.set_play_count(0) if start_at: datetime = datetime_parser.parse(start_at) - video_item.set_scheduled_start_utc(datetime) + media_item.set_scheduled_start_utc(datetime) local_datetime = datetime_parser.utc_to_local(datetime) - video_item.set_year_from_datetime(local_datetime) - video_item.set_aired_from_datetime(local_datetime) - video_item.set_premiered_from_datetime(local_datetime) - video_item.set_date_from_datetime(local_datetime) - if video_item.upcoming: - if video_item.live: + media_item.set_year_from_datetime(local_datetime) + media_item.set_aired_from_datetime(local_datetime) + media_item.set_premiered_from_datetime(local_datetime) + media_item.set_date_from_datetime(local_datetime) + if media_item.upcoming: + if media_item.live: type_label = localize('live.upcoming') else: type_label = localize('upcoming') - elif video_item.live: + elif media_item.live: type_label = localize('live') else: type_label = localize(335) # "Start" @@ -556,7 +558,7 @@ def update_video_infos(provider, context, video_id_dict, rating[0] = value elif stat == 'viewCount': rating[1] = value - video_item.set_count(value) + media_item.set_count(value) label_stats = ' | '.join(label_stats) stats = ' | '.join(stats) @@ -568,20 +570,20 @@ def update_video_infos(provider, context, video_id_dict, # This is a completely made up, arbitrary ranking score rating = (10 * (log10(rating[1]) * log10(rating[0])) / (log10(rating[0] + rating[1]) ** 2)) - video_item.set_rating(rating) + media_item.set_rating(rating) # Used for label2, but is poorly supported in skins - video_item.set_short_details(label_stats) + media_item.set_short_details(label_stats) # Hack to force a custom label mask containing production code, # activated on sort order selection, to display details # Refer XbmcContext.set_content for usage - video_item.set_code(label_stats) + media_item.set_production_code(label_stats) # update and set the title - title = video_item.get_title() + title = media_item.get_title() if not title or title == untitled: title = snippet.get('title') or untitled - video_item.set_title(ui.italic(title) if video_item.upcoming else title) + media_item.set_title(ui.italic(title) if media_item.upcoming else title) """ This is experimental. We try to get the most information out of the title of a video. @@ -596,7 +598,7 @@ def update_video_infos(provider, context, video_id_dict, value = int(value) if value < 2 ** 31: season = value - video_item.set_season(season) + media_item.set_season(season) if not episode: value = season_episode[1] @@ -604,18 +606,18 @@ def update_video_infos(provider, context, video_id_dict, value = int(value) if value < 2 ** 31: episode = value - video_item.set_episode(episode) + media_item.set_episode(episode) if season and episode: break # channel name channel_name = snippet.get('channelTitle', '') - video_item.add_artist(channel_name) + media_item.add_artist(channel_name) if 'cast' in channel_name_aliases: - video_item.add_cast(channel_name, role=channel_role) + media_item.add_cast(channel_name, role=channel_role) if 'studio' in channel_name_aliases: - video_item.add_studio(channel_name) + media_item.add_studio(channel_name) # plot description = strip_html_from_text(snippet['description']) @@ -623,41 +625,41 @@ def update_video_infos(provider, context, video_id_dict, description = ''.join(( ui.bold(channel_name, cr_after=1) if channel_name else '', ui.new_line(stats, cr_after=1) if stats else '', - (ui.italic(start_at, cr_after=1) if video_item.upcoming + (ui.italic(start_at, cr_after=1) if media_item.upcoming else ui.new_line(start_at, cr_after=1)) if start_at else '', description, ui.new_line('https://youtu.be/' + video_id, cr_before=1) )) - video_item.set_plot(description) + media_item.set_plot(description) # date time published_at = snippet.get('publishedAt') if published_at: datetime = datetime_parser.parse(published_at) - video_item.set_added_utc(datetime) + media_item.set_added_utc(datetime) local_datetime = datetime_parser.utc_to_local(datetime) - video_item.set_dateadded_from_datetime(local_datetime) + media_item.set_dateadded_from_datetime(local_datetime) if not start_at: - video_item.set_year_from_datetime(local_datetime) - video_item.set_aired_from_datetime(local_datetime) - video_item.set_premiered_from_datetime(local_datetime) - video_item.set_date_from_datetime(local_datetime) + media_item.set_year_from_datetime(local_datetime) + media_item.set_aired_from_datetime(local_datetime) + media_item.set_premiered_from_datetime(local_datetime) + media_item.set_date_from_datetime(local_datetime) # try to find a better resolution for the image - image = video_item.get_image() + image = media_item.get_image() if not image: image = get_thumbnail(thumb_size, snippet.get('thumbnails', {})) if image.endswith('_live.jpg'): image = ''.join((image, '?ct=', thumb_stamp)) - video_item.set_image(image) + media_item.set_image(image) # update channel mapping channel_id = snippet.get('channelId', '') - video_item.channel_id = channel_id + media_item.channel_id = channel_id if channel_id and channel_items_dict is not None: if channel_id not in channel_items_dict: channel_items_dict[channel_id] = [] - channel_items_dict[channel_id].append(video_item) + channel_items_dict[channel_id].append(media_item) context_menu = [ # Refresh @@ -697,14 +699,14 @@ def update_video_infos(provider, context, video_id_dict, elif not in_watched_later_list: context_menu.append( menu_items.watch_later_local_add( - context, video_item + context, media_item ) ) if not in_bookmarks_list: context_menu.append( menu_items.bookmark_add( - context, video_item + context, media_item ) ) @@ -714,21 +716,21 @@ def update_video_infos(provider, context, video_id_dict, and playlist_channel_id == 'mine' and playlist_id.strip().lower() not in {'hl', 'wl'}): playlist_item_id = playlist_item_id_dict[video_id] - video_item.playlist_id = playlist_id - video_item.playlist_item_id = playlist_item_id + media_item.playlist_id = playlist_id + media_item.playlist_item_id = playlist_item_id context_menu.append( menu_items.remove_video_from_playlist( context, playlist_id=playlist_id, video_id=playlist_item_id, - video_name=video_item.get_name(), + video_name=media_item.get_name(), ) ) # got to [CHANNEL] only if we are not directly in the channel if (channel_id and channel_name and context.create_path('channel', channel_id) != path): - video_item.channel_id = channel_id + media_item.channel_id = channel_id context_menu.append( menu_items.go_to_channel( context, channel_id, channel_name @@ -759,7 +761,7 @@ def update_video_infos(provider, context, video_id_dict, ) ) - if not video_item.live and play_data: + if not media_item.live and play_data: context_menu.append( menu_items.history_mark_unwatched( context, video_id @@ -813,7 +815,7 @@ def update_video_infos(provider, context, video_id_dict, ) ) - if video_item.live: + if media_item.live: context_menu.append( menu_items.play_timeshift( context, video_id @@ -822,15 +824,15 @@ def update_video_infos(provider, context, video_id_dict, if context_menu: context_menu.append(menu_items.separator()) - video_item.add_context_menu(context_menu) + media_item.add_context_menu(context_menu) -def update_play_info(provider, context, video_id, video_item, video_stream, +def update_play_info(provider, context, video_id, media_item, video_stream, use_play_data=True): - video_item.video_id = video_id + media_item.video_id = video_id update_video_infos(provider, context, - {video_id: video_item}, + {video_id: media_item}, use_play_data=use_play_data) settings = context.get_settings() @@ -838,35 +840,35 @@ def update_play_info(provider, context, video_id, video_item, video_stream, meta_data = video_stream.get('meta') if meta_data: - video_item.live = meta_data.get('status', {}).get('live', False) - video_item.set_subtitles(meta_data.get('subtitles', None)) + media_item.live = meta_data.get('status', {}).get('live', False) + media_item.set_subtitles(meta_data.get('subtitles', None)) image = get_thumbnail(settings.get_thumbnail_size(), meta_data.get('thumbnails', {})) if image: - if video_item.live: + if media_item.live: image = ''.join((image, '?ct=', get_thumb_timestamp())) - video_item.set_image(image) + media_item.set_image(image) if 'headers' in video_stream: - video_item.set_headers(video_stream['headers']) + media_item.set_headers(video_stream['headers']) # set _uses_isa - if video_item.live: - video_item.set_isa_video(settings.use_isa_live_streams()) - elif video_item.use_hls_video() or video_item.use_mpd_video(): - video_item.set_isa_video(settings.use_isa()) + if media_item.live: + media_item.set_isa(settings.use_isa_live_streams()) + elif media_item.use_hls() or media_item.use_mpd(): + media_item.set_isa(settings.use_isa()) - if video_item.use_isa_video(): + if media_item.use_isa(): license_info = video_stream.get('license_info', {}) license_proxy = license_info.get('proxy', '') license_url = license_info.get('url', '') license_token = license_info.get('token', '') if ISHelper and license_proxy and license_url and license_token: - ISHelper('mpd' if video_item.use_mpd_video() else 'hls', + ISHelper('mpd' if media_item.use_mpd() else 'hls', drm='com.widevine.alpha').check_inputstream() - video_item.set_license_key(license_proxy) + media_item.set_license_key(license_proxy) ui.set_property(LICENSE_URL, license_url) ui.set_property(LICENSE_TOKEN, license_token) diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py index a3f402741..9313de34f 100644 --- a/resources/lib/youtube_plugin/youtube/helper/v3.py +++ b/resources/lib/youtube_plugin/youtube/helper/v3.py @@ -10,7 +10,7 @@ from __future__ import absolute_import, division, unicode_literals -from threading import Thread +import threading from .utils import ( THUMB_TYPES, @@ -363,22 +363,41 @@ def _fetch(resource): data = resource['fetcher']( *resource['args'], **resource['kwargs'] ) - if not data or not resource['updater']: - return - resource['upd_kwargs']['data'] = data - resource['updater'](*resource['upd_args'], **resource['upd_kwargs']) + if data and resource['updater']: + resource['upd_kwargs']['data'] = data + resource['updater'](*resource['upd_args'], **resource['upd_kwargs']) + resource['complete'] = True + threads['current'].discard(resource['thread']) + threads['loop'].set() + + threads = { + 'current': set(), + 'loop': threading.Event(), + } remaining = len(resources) deferred = sum(1 for resource in resources.values() if resource['defer']) - iterator = iter(resources.values()) - while remaining: + completed = [] + iterator = iter(resources) + threads['loop'].set() + while threads['loop'].wait(): try: - resource = next(iterator) + resource_id = next(iterator) except StopIteration: - iterator = iter(resources.values()) - resource = next(iterator) + if not remaining and not threads['current']: + break + if threads['current']: + threads['loop'].clear() + for resource_id in completed: + del resources[resource_id] + completed = [] + iterator = iter(resources) + continue + resource = resources[resource_id] if resource['complete']: + remaining -= 1 + completed.append(resource_id) continue defer = resource['defer'] @@ -392,21 +411,14 @@ def _fetch(resource): args = resource['args'] if args and not args[0]: resource['complete'] = True - remaining -= 1 continue - thread = resource['thread'] - if thread: - thread.join(5) - if not thread.is_alive(): - resource['thread'] = None - resource['complete'] = True - remaining -= 1 - else: - thread = Thread(target=_fetch, args=(resource,)) - thread.daemon = True - thread.start() - resource['thread'] = thread + if not resource['thread']: + new_thread = threading.Thread(target=_fetch, args=(resource,)) + new_thread.daemon = True + threads['current'].add(new_thread) + resource['thread'] = new_thread + new_thread.start() return result diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_login.py b/resources/lib/youtube_plugin/youtube/helper/yt_login.py index 15dffb72a..d951d869d 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_login.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_login.py @@ -10,7 +10,6 @@ from __future__ import absolute_import, division, unicode_literals -import copy import time from ..youtube_exceptions import LoginException @@ -81,7 +80,7 @@ def _do_login(login_type): _do_logout() raise - log_data = copy.deepcopy(json_data) + log_data = json_data.copy() if 'access_token' in log_data: log_data['access_token'] = '' if 'refresh_token' in log_data: diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py index 9bcf42f03..1da0a13ae 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py @@ -29,7 +29,7 @@ SERVER_POST_START, SERVER_WAKEUP, ) -from ...kodion.items import VideoItem +from ...kodion.items import AudioItem, VideoItem from ...kodion.network import get_connect_address from ...kodion.utils import find_video_id, select_stream @@ -119,13 +119,16 @@ def _play_stream(provider, context): )) stream['url'] = url - video_item = VideoItem(metadata.get('title', ''), stream['url']) + if audio_only or not video_type: + media_item = AudioItem(metadata.get('title', ''), stream['url']) + else: + media_item = VideoItem(metadata.get('title', ''), stream['url']) use_history = not (screensaver or incognito or stream.get('live')) use_remote_history = use_history and settings.use_remote_history() use_play_data = use_history and settings.use_local_history() - utils.update_play_info(provider, context, video_id, video_item, + utils.update_play_info(provider, context, video_id, media_item, stream, use_play_data=use_play_data) seek_time = 0.0 if params.get('resume') else params.get('seek', 0.0) @@ -133,20 +136,20 @@ def _play_stream(provider, context): end_time = params.get('end', 0.0) if start_time: - video_item.set_start_time(start_time) + media_item.set_start_time(start_time) # Setting the duration based on end_time can cause issues with # listing/sorting and other addons that monitor playback # if end_time: # video_item.set_duration_from_seconds(end_time) - play_count = use_play_data and video_item.get_play_count() or 0 + play_count = use_play_data and media_item.get_play_count() or 0 playback_stats = stream.get('playback_stats') playback_data = { 'video_id': video_id, 'channel_id': metadata.get('channel', {}).get('id', ''), 'video_status': metadata.get('status', {}), - 'playing_file': video_item.get_uri(), + 'playing_file': media_item.get_uri(), 'play_count': play_count, 'use_remote_history': use_remote_history, 'use_local_history': use_play_data, @@ -160,7 +163,7 @@ def _play_stream(provider, context): ui.set_property(PLAYER_DATA, json.dumps(playback_data, ensure_ascii=False)) context.send_notification(PLAYBACK_INIT, playback_data) - return video_item + return media_item def _play_playlist(provider, context): @@ -342,9 +345,9 @@ def process(provider, context, **_kwargs): return False ui.clear_property(SERVER_POST_START) context.wakeup(SERVER_WAKEUP, timeout=5) - video_item = _play_stream(provider, context) + media_item = _play_stream(provider, context) ui.set_property(SERVER_POST_START) - return video_item + return media_item if playlist_id or 'playlist_ids' in params: return _play_playlist(provider, context) diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py b/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py index d0917caa7..d50d80ff4 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py @@ -534,3 +534,23 @@ def _convert_old_history_item(value, item): wait_for=WAIT_FLAG, ) return step + + +def process_refresh_settings(context, step, steps, **_kwargs): + localize = context.localize + + step += 1 + if context.get_ui().on_yes_no_input( + localize('setup_wizard') + ' ({0}/{1})'.format(step, steps), + (localize('setup_wizard.prompt') + % localize('setup_wizard.prompt.settings.refresh')) + ): + context.execute( + 'RunScript({addon},maintenance/{action}?{query})' + .format(addon=ADDON_ID, + action='refresh', + query='target=settings_xml'), + wait_for=WAIT_FLAG, + ) + context.get_settings(refresh=True) + return step diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py index f1fc9ff02..d4846bf9e 100644 --- a/resources/lib/youtube_plugin/youtube/provider.py +++ b/resources/lib/youtube_plugin/youtube/provider.py @@ -96,6 +96,7 @@ def get_wizard_steps(): yt_setup_wizard.process_old_search_db, yt_setup_wizard.process_old_history_db, yt_setup_wizard.process_list_detail_settings, + yt_setup_wizard.process_refresh_settings, ] return steps