# -*- 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 ...youtube.helper import yt_context_menu from ... import kodion from ...kodion import items from . import utils def _process_list_response(provider, context, json_data): video_id_dict = {} channel_id_dict = {} playlist_id_dict = {} playlist_item_id_dict = {} subscription_id_dict = {} result = [] is_upcoming = False thumb_size = context.get_settings().use_thumbnail_size() yt_items = json_data.get('items', []) if len(yt_items) == 0: context.log_warning('List of search result is empty') return result incognito = str(context.get_param('incognito', False)).lower() == 'true' addon_id = context.get_param('addon_id', '') for yt_item in yt_items: is_youtube, kind = _parse_kind(yt_item) if not is_youtube or not kind: context.log_debug('v3 response: Item discarded, is_youtube=False') continue if kind == 'video': video_id = yt_item['id'] snippet = yt_item['snippet'] title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled'])) image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {})) item_params = {'video_id': video_id} if incognito: item_params.update({'incognito': incognito}) if addon_id: item_params.update({'addon_id': addon_id}) item_uri = context.create_uri(['play'], item_params) video_item = items.VideoItem(title, item_uri, image=image) video_item.video_id = video_id if incognito: video_item.set_play_count(0) video_item.set_fanart(provider.get_fanart(context)) result.append(video_item) video_id_dict[video_id] = video_item elif kind == 'channel': channel_id = yt_item['id'] snippet = yt_item['snippet'] title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled'])) image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {})) item_params = {} if incognito: item_params.update({'incognito': incognito}) if addon_id: item_params.update({'addon_id': addon_id}) item_uri = context.create_uri(['channel', channel_id], item_params) channel_item = items.DirectoryItem(title, item_uri, image=image) channel_item.set_fanart(provider.get_fanart(context)) # if logged in => provide subscribing to the channel if provider.is_logged_in(): context_menu = [] yt_context_menu.append_subscribe_to_channel(context_menu, provider, context, channel_id) channel_item.set_context_menu(context_menu) result.append(channel_item) channel_id_dict[channel_id] = channel_item elif kind == 'guidecategory': guide_id = yt_item['id'] snippet = yt_item['snippet'] title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled'])) item_params = {'guide_id': guide_id} if incognito: item_params.update({'incognito': incognito}) if addon_id: item_params.update({'addon_id': addon_id}) item_uri = context.create_uri(['special', 'browse_channels'], item_params) guide_item = items.DirectoryItem(title, item_uri) guide_item.set_fanart(provider.get_fanart(context)) result.append(guide_item) elif kind == 'subscription': snippet = yt_item['snippet'] title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled'])) image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {})) channel_id = snippet['resourceId']['channelId'] item_params = {} if incognito: item_params.update({'incognito': incognito}) if addon_id: item_params.update({'addon_id': addon_id}) item_uri = context.create_uri(['channel', channel_id], item_params) channel_item = items.DirectoryItem(title, item_uri, image=image) channel_item.set_fanart(provider.get_fanart(context)) # map channel id with subscription id - we need it for the unsubscription subscription_id_dict[channel_id] = yt_item['id'] result.append(channel_item) channel_id_dict[channel_id] = channel_item elif kind == 'playlist': playlist_id = yt_item['id'] snippet = yt_item['snippet'] title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled'])) image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {})) channel_id = snippet['channelId'] # if the path directs to a playlist of our own, we correct the channel id to 'mine' if context.get_path() == '/channel/mine/playlists/': channel_id = 'mine' item_params = {} if incognito: item_params.update({'incognito': incognito}) if addon_id: item_params.update({'addon_id': addon_id}) item_uri = context.create_uri(['channel', channel_id, 'playlist', playlist_id], item_params) playlist_item = items.DirectoryItem(title, item_uri, image=image) playlist_item.set_fanart(provider.get_fanart(context)) result.append(playlist_item) playlist_id_dict[playlist_id] = playlist_item elif kind == 'playlistitem': snippet = yt_item['snippet'] video_id = snippet['resourceId']['videoId'] # store the id of the playlistItem - for deleting this item we need this item playlist_item_id_dict[video_id] = yt_item['id'] title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled'])) image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {})) item_params = {'video_id': video_id} if incognito: item_params.update({'incognito': incognito}) if addon_id: item_params.update({'addon_id': addon_id}) item_uri = context.create_uri(['play'], item_params) video_item = items.VideoItem(title, item_uri, image=image) video_item.video_id = video_id if incognito: video_item.set_play_count(0) video_item.set_fanart(provider.get_fanart(context)) # Get Track-ID from Playlist video_item.set_track_number(snippet['position'] + 1) result.append(video_item) video_id_dict[video_id] = video_item elif kind == 'activity': snippet = yt_item['snippet'] details = yt_item['contentDetails'] activity_type = snippet['type'] # recommendations if activity_type == 'recommendation': video_id = details['recommendation']['resourceId']['videoId'] elif activity_type == 'upload': video_id = details['upload']['videoId'] else: continue title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled'])) image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {})) item_params = {'video_id': video_id} if incognito: item_params.update({'incognito': incognito}) if addon_id: item_params.update({'addon_id': addon_id}) item_uri = context.create_uri(['play'], item_params) video_item = items.VideoItem(title, item_uri, image=image) video_item.video_id = video_id if incognito: video_item.set_play_count(0) video_item.set_fanart(provider.get_fanart(context)) result.append(video_item) video_id_dict[video_id] = video_item elif kind == 'commentthread': thread_snippet = yt_item['snippet'] total_replies = thread_snippet['totalReplyCount'] snippet = thread_snippet['topLevelComment']['snippet'] item_params = {'parent_id': yt_item['id']} if total_replies: item_uri = context.create_uri(['special', 'child_comments'], item_params) else: item_uri = '' result.append(utils.make_comment_item(context, provider, snippet, item_uri, total_replies)) elif kind == 'comment': result.append(utils.make_comment_item(context, provider, yt_item['snippet'], uri='')) elif kind == 'searchresult': _, kind = _parse_kind(yt_item.get('id', {})) # video if kind == 'video': video_id = yt_item['id']['videoId'] snippet = yt_item['snippet'] is_upcoming = snippet.get('liveBroadcastContent', '').lower() == 'upcoming' title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled'])) image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {})) item_params = {'video_id': video_id} if incognito: item_params.update({'incognito': incognito}) if addon_id: item_params.update({'addon_id': addon_id}) item_uri = context.create_uri(['play'], item_params) video_item = items.VideoItem(title, item_uri, image=image) video_item.video_id = video_id if incognito: video_item.set_play_count(0) video_item.set_fanart(provider.get_fanart(context)) result.append(video_item) video_id_dict[video_id] = video_item # playlist elif kind == 'playlist': playlist_id = yt_item['id']['playlistId'] snippet = yt_item['snippet'] title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled'])) image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {})) channel_id = snippet['channelId'] # if the path directs to a playlist of our own, we correct the channel id to 'mine' if context.get_path() == '/channel/mine/playlists/': channel_id = 'mine' # channel_name = snippet.get('channelTitle', '') item_params = {} if incognito: item_params.update({'incognito': incognito}) if addon_id: item_params.update({'addon_id': addon_id}) item_uri = context.create_uri(['channel', channel_id, 'playlist', playlist_id], item_params) playlist_item = items.DirectoryItem(title, item_uri, image=image) playlist_item.set_fanart(provider.get_fanart(context)) result.append(playlist_item) playlist_id_dict[playlist_id] = playlist_item elif kind == 'channel': channel_id = yt_item['id']['channelId'] snippet = yt_item['snippet'] title = snippet.get('title', context.localize(provider.LOCAL_MAP['youtube.untitled'])) image = utils.get_thumbnail(thumb_size, snippet.get('thumbnails', {})) item_params = {} if incognito: item_params.update({'incognito': incognito}) if addon_id: item_params.update({'addon_id': addon_id}) item_uri = context.create_uri(['channel', channel_id], item_params) channel_item = items.DirectoryItem(title, item_uri, image=image) channel_item.set_fanart(provider.get_fanart(context)) result.append(channel_item) channel_id_dict[channel_id] = channel_item else: raise kodion.KodionException("Unknown kind '%s'" % kind) else: raise kodion.KodionException("Unknown kind '%s'" % kind) use_play_data = not incognito and context.get_settings().use_playback_history() # this will also update the channel_id_dict with the correct channel id for each video. channel_items_dict = {} utils.update_video_infos(provider, context, video_id_dict, playlist_item_id_dict, channel_items_dict, live_details=is_upcoming, use_play_data=use_play_data) utils.update_playlist_infos(provider, context, playlist_id_dict, channel_items_dict) utils.update_channel_infos(provider, context, channel_id_dict, subscription_id_dict, channel_items_dict) if video_id_dict or playlist_id_dict: utils.update_fanarts(provider, context, channel_items_dict) return result def response_to_items(provider, context, json_data, sort=None, reverse_sort=False, process_next_page=True): result = [] is_youtube, kind = _parse_kind(json_data) if not is_youtube: context.log_debug('v3 response: Response discarded, is_youtube=False') return result if kind in ['searchlistresponse', 'playlistitemlistresponse', 'playlistlistresponse', 'subscriptionlistresponse', 'guidecategorylistresponse', 'channellistresponse', 'videolistresponse', 'activitylistresponse', 'commentthreadlistresponse', 'commentlistresponse']: result.extend(_process_list_response(provider, context, json_data)) else: raise kodion.KodionException("Unknown kind '%s'" % kind) if sort is not None: result = sorted(result, key=sort, reverse=reverse_sort) # no processing of next page item if not process_next_page: return result # next page """ This will try to prevent the issue 7163 (https://code.google.com/p/gdata-issues/issues/detail?id=7163). Somehow the APIv3 is missing the token for the next page. We implemented our own calculation for the token into the YouTube client...this should work for up to ~2000 entries. """ yt_total_results = int(json_data.get('pageInfo', {}).get('totalResults', 0)) yt_results_per_page = int(json_data.get('pageInfo', {}).get('resultsPerPage', 0)) page = int(context.get_param('page', 1)) yt_next_page_token = json_data.get('nextPageToken', '') if yt_next_page_token or (page * yt_results_per_page < yt_total_results): if not yt_next_page_token: client = provider.get_client(context) yt_next_page_token = client.calculate_next_page_token(page + 1, yt_results_per_page) new_params = {} new_params.update(context.get_params()) new_params['page_token'] = yt_next_page_token new_context = context.clone(new_params=new_params) current_page = int(new_context.get_param('page', 1)) next_page_item = items.NextPageItem(new_context, current_page, fanart=provider.get_fanart(new_context)) result.append(next_page_item) return result def handle_error(provider, context, json_data): if json_data and 'error' in json_data: ok_dialog = False message_timeout = 5000 message = kodion.utils.strip_html_from_text(json_data['error'].get('message', '')) log_message = kodion.utils.strip_html_from_text(json_data['error'].get('message', '')) reason = json_data['error']['errors'][0].get('reason', '') title = '%s: %s' % (context.get_name(), reason) context.log_error('Error reason: |%s| with message: |%s|' % (reason, log_message)) if reason == 'accessNotConfigured': message = context.localize(provider.LOCAL_MAP['youtube.key.requirement.notification']) ok_dialog = True if reason == 'keyInvalid' and message == 'Bad Request': message = context.localize(provider.LOCAL_MAP['youtube.api.key.incorrect']) message_timeout = 7000 if reason == 'quotaExceeded' or reason == 'dailyLimitExceeded': message_timeout = 7000 if ok_dialog: context.get_ui().on_ok(title, message) else: context.get_ui().show_notification(message, title, time_milliseconds=message_timeout) return False return True def _parse_kind(item): kind = item.get('kind', '').split('#') if len(kind) < 1: return False, '' if len(kind) < 2: try: _ = kind.index('youtube') return True, '' except ValueError: return False, str(kind[0]).lower() try: idx = kind.index('youtube') if idx == 0: return True, str(kind[1]).lower() except ValueError: pass return False, str(kind[1]).lower()