def enqueue_audio_search(self, arg): """Search YouTube and add the audio streams to the playback queue. :param arg: a search string """ logging.info("arg : %s", arg) try: print_msg("[YouTube] [Audio search] : '{0}'. ".format(arg)) query = generate_search_query(arg) wdata = pafy.call_gdata("search", query) wdata2 = wdata count = 0 while True: for track_info in get_tracks_from_json(wdata2): self._add_to_playback_queue(info=track_info) count += 1 if count > 100: break if not wdata2.get("nextPageToken"): break query["pageToken"] = wdata2["nextPageToken"] wdata2 = pafy.call_gdata("search", query) self._update_play_queue_order() except ValueError: raise ValueError(str("Could not find any mixes : %s" % arg))
def enqueue_audio_search(self, arg): """Search YouTube and add the audio streams to the playback queue. :param arg: a search string """ logging.info('arg : %s', arg) try: query = generate_search_query(arg) wdata = pafy.call_gdata('search', query) wdata2 = wdata count = 0 while True: for track_info in get_tracks_from_json(wdata2): self.add_to_playback_queue(info=track_info) count += 1 if count > 100: break if not wdata2.get('nextPageToken'): break query['pageToken'] = wdata2['nextPageToken'] wdata2 = pafy.call_gdata('search', query) self.__update_play_queue_order() except ValueError: raise ValueError(str("Could not find any mixes : %s" % arg))
def ytSearch(query, max=10): q = {'q': query, 'part': 'id', 'maxResults': max, 'type': 'video,playlist'} results = pafy.call_gdata('search', q)['items'] videosIds = [ i['id']['videoId'] for i in results if i['id']['kind'] == 'youtube#video' ] playlistsIds = [ i['id']['playlistId'] for i in results if i['id']['kind'] == 'youtube#playlist' ] # print(results[0]) ids = ','.join(videosIds) videos = pafy.call_gdata('videos', { 'id': ids, 'part': 'id,snippet,contentDetails' })['items'] ids = ','.join(playlistsIds) playlists = pafy.call_gdata('playlists', { 'id': ids, 'part': 'id,snippet,contentDetails' })['items'] #print(playlists) browsed = [ videos.pop(0) if i['id']['kind'] == 'youtube#video' else playlists.pop(0) for i in results ] return [ProxyItem(i) for i in browsed]
def api(self, bestmatch=True): """ Use YouTube API to search and return a list of matching videos. """ try: query = {'part': 'snippet', 'maxResults': 50, 'type': 'video'} if const.config.music_videos_only: query['videoCategoryId'] = '10' if not self.meta_tags: song = self.raw_song query['q'] = song else: query['q'] = self.search_query log.debug('query: {0}'.format(query)) data = pafy.call_gdata('search', query) data['items'] = list( filter(lambda x: x['id'].get('videoId') is not None, data['items'])) query_results = { 'part': 'contentDetails,snippet,statistics', 'maxResults': 50, 'id': ','.join(i['id']['videoId'] for i in data['items']) } log.debug('query_results: {0}'.format(query_results)) vdata = pafy.call_gdata('videos', query_results) videos = [] for x in vdata['items']: duration_s = pafy.playlist.parseISO8591( x['contentDetails']['duration']) youtubedetails = { 'link': x['id'], 'title': x['snippet']['title'], 'videotime': internals.videotime_from_seconds(duration_s), 'seconds': duration_s, 'views': x['statistics'].get('viewCount', 0) } videos.append(youtubedetails) if bestmatch: return self._best_match(videos) return videos except Exception as e: if ERROR_QUOTA_STR in str(e): if set_new_key(): return self.api(bestmatch) else: log.warn("#############################################") log.warn("No more YT keys. Wait until quota is restored") log.warn("#############################################") raise e
def enqueue_audio_mix_search(self, arg): """Obtain a YouTube mix associated to a given textual search and add all the audio streams in the mix playlist to the playback queue. :param arg: a search string """ logging.info('arg : %s', arg) try: query = generate_search_query(arg) wdata = pafy.call_gdata('search', query) wdata2 = wdata count = len(self.queue) for track_info in get_tracks_from_json(wdata2): if track_info and track_info.ytid: try: self.enqueue_audio_mix(track_info.ytid, feelinglucky=False) break except ValueError: logging.info('Could not find a mix. Trying another video') if count == len(self.queue): raise ValueError except ValueError: raise ValueError(str("Could not find any mixes : %s" % arg))
def enqueue_audio_mix_search(self, arg): """Obtain a YouTube mix associated to a given textual search and add all the audio streams in the mix playlist to the playback queue. :param arg: a search string """ logging.info('arg : %s', arg) try: query = generate_search_query(arg) wdata = pafy.call_gdata('search', query) wdata2 = wdata count = len(self.queue) for track_info in get_tracks_from_json(wdata2): if track_info and track_info.ytid: try: self.enqueue_audio_mix(track_info.ytid, feelinglucky=False) break except ValueError: logging.info( 'Could not find a mix. Trying another video') if count == len(self.queue): raise ValueError except ValueError: raise ValueError(str("Could not find any mixes : %s" % arg))
async def time(websocket, path): async for message in websocket: message = json.loads(message) qs = {'q': message['q'], 'maxResults': 10, 'part': 'id,snippet'} gdata = pafy.call_gdata('search', qs) with youtube_dl.YoutubeDL({'format': 'bestaudio/best'}) as ydl: for item in gdata['items']: try: result = { 'extracted': False, 'id': item['id']['videoId'], 'title': item['snippet']['title'] } await websocket.send(json.dumps(result)) except KeyError: pass for item in gdata['items']: try: url = "https://www.youtube.com/watch?v=" + item['id'][ 'videoId'] info_dict = ydl.extract_info(url, download=False) result = { 'extracted': True, 'id': item['id']['videoId'], 'url': info_dict.get('url', '#'), 'title': item['snippet']['title'] } await websocket.send(json.dumps(result)) except KeyError: pass
def _search(progtext, qs=None, msg=None, failmsg=None): """ Perform memoized url fetch, display progtext. """ loadmsg = "Searching for '%s%s%s'" % (c.y, progtext, c.w) wdata = pafy.call_gdata('search', qs) def iter_songs(): wdata2 = wdata while True: for song in get_tracks_from_json(wdata2): yield song if not wdata2.get('nextPageToken'): break qs['pageToken'] = wdata2['nextPageToken'] wdata2 = pafy.call_gdata('search', qs) # The youtube search api returns a maximum of 500 results length = min(wdata['pageInfo']['totalResults'], 500) slicer = util.IterSlicer(iter_songs(), length) paginatesongs(slicer, length=length, msg=msg, failmsg=failmsg, loadmsg=loadmsg)
def channelfromname(user): """ Query channel id from username. """ cached = userdata_cached(user) if cached: user, channel_id = cached else: # if the user is looked for by their display name, # we have to sent an additional request to find their # channel id qs = {'part': 'id,snippet', 'forUsername': user, 'key': config.API_KEY.get} try: userinfo = pafy.call_gdata('channels', qs)['items'] if len(userinfo) > 0: snippet = userinfo[0].get('snippet', {}) channel_id = userinfo[0].get('id', user) username = snippet.get('title', user) user = cache_userdata(user, username, channel_id)[0] else: g.message = "User {} not found.".format(c.y + user + c.w) return except pafy.GdataError as e: g.message = "Could not retrieve information for user {}\n{}".format( c.y + user + c.w, e) util.dbg('Error during channel request for user {}:\n{}'.format( user, e)) return # at this point, we know the channel id associated to a user name return (user, channel_id)
def api(self, bestmatch=True): """ Use YouTube API to search and return a list of matching videos. """ query = {"part": "snippet", "maxResults": 50, "type": "video"} if const.args.music_videos_only: query["videoCategoryId"] = "10" if not self.meta_tags: song = self.raw_song query["q"] = song else: query["q"] = self.search_query log.debug("query: {0}".format(query)) data = pafy.call_gdata("search", query) data["items"] = list( filter(lambda x: x["id"].get("videoId") is not None, data["items"])) query_results = { "part": "contentDetails,snippet,statistics", "maxResults": 50, "id": ",".join(i["id"]["videoId"] for i in data["items"]), } log.debug("query_results: {0}".format(query_results)) vdata = pafy.call_gdata("videos", query_results) videos = [] for x in vdata["items"]: duration_s = pafy.playlist.parseISO8591( x["contentDetails"]["duration"]) youtubedetails = { "link": x["id"], "title": x["snippet"]["title"], "videotime": internals.videotime_from_seconds(duration_s), "seconds": duration_s, } if self.meta_tags["name"] in x["snippet"]["title"]: videos.append(youtubedetails) log.error("I'm hot") if bestmatch: return self._best_match(videos) return videos
def _fetch_commentreplies(parentid): return pafy.call_gdata( 'comments', { 'parentId': parentid, 'part': 'snippet', 'textFormat': 'plainText', 'maxResults': 50 }).get('items', [])
def api(self): """ Use YouTube API to search and return a list of matching videos. """ query = {'part': 'snippet', 'maxResults': 50, 'type': 'video'} if const.args.music_videos_only: query['videoCategoryId'] = '10' if not self.meta_tags: song = self.raw_song query['q'] = song else: song = '{0} - {1}'.format(self.meta_tags['artists'][0]['name'], self.meta_tags['name']) query['q'] = song log.debug('query: {0}'.format(query)) data = pafy.call_gdata('search', query) data['items'] = list( filter(lambda x: x['id'].get('videoId') is not None, data['items'])) query_results = { 'part': 'contentDetails,snippet,statistics', 'maxResults': 50, 'id': ','.join(i['id']['videoId'] for i in data['items']) } log.debug('query_results: {0}'.format(query_results)) vdata = pafy.call_gdata('videos', query_results) videos = [] for x in vdata['items']: duration_s = pafy.playlist.parseISO8591( x['contentDetails']['duration']) youtubedetails = { 'link': x['id'], 'title': x['snippet']['title'], 'videotime': internals.videotime_from_seconds(duration_s), 'seconds': duration_s } videos.append(youtubedetails) if not self.meta_tags: break return self._best_match(videos)
def perform_search(self, query, *args, **kwargs): """ Search songs on youtube. """ qs = generate_search_query(query, api_key=self.config.get('api_key')) yresult = pafy.call_gdata('search', qs) return get_songs_from_result(yresult)
def video_results(video_ids): query = { 'id': ','.join(video_ids), 'part': 'snippet,statistics,contentDetails', 'key': conf.api_key, } videos = pafy.call_gdata('videos', query) return videos
def find_best_match(query): """Find the best(first)""" # This assumes that the first match is the best one qs = search.generate_search_qs(query) wdata = pafy.call_gdata("search", qs) results = search.get_tracks_from_json(wdata) if results: res, score = album_search._best_song_match(results, query, 0.1, 1.0, 0.0) return res
def find_best_match(query): """Find the best(first)""" # This assumes that the first match is the best one qs = search.generate_search_qs(query) wdata = pafy.call_gdata('search', qs) results = search.get_tracks_from_json(wdata) if results: res, score = album_search._best_song_match( results, query, 0.1, 1.0, 0.0) return res
def iter_songs(): wdata2 = wdata while True: for song in get_tracks_from_json(wdata2): yield song if not wdata2.get('nextPageToken'): break qs['pageToken'] = wdata2['nextPageToken'] wdata2 = pafy.call_gdata('search', qs)
def _fetch_commentreplies(parentid): return pafy.call_gdata( "comments", { "parentId": parentid, "part": "snippet", "textFormat": "plainText", "maxResults": 50, }, ).get("items", [])
def get_item_info(ytid): """ Fetchs data from single item from youtube """ qs = {'part': 'contentDetails,statistics,snippet', 'id': ','.join([ytid])} wdata = pafy.call_gdata('videos', qs) items_vidinfo = wdata.get('items', []) return items_vidinfo[0] if items_vidinfo else {}
def description_generator(text): """ Fetches a videos description and parses it for <artist> - <track> combinations """ if not isinstance(g.model, Playlist): g.message = util.F("mkp desc unknown") return # Use only the first result, for now num = text.replace("--description", "") num = num.replace("-d", "") num = util.number_string_to_list(num)[0] query = {} query['id'] = g.model[num].ytid query['part'] = 'snippet' query['maxResults'] = '1' data = pafy.call_gdata('videos', query)['items'][0]['snippet'] title = "mkp %s" % data['title'] data = util.fetch_songs(data['description'], data['title']) columns = [ { "name": "idx", "size": 3, "heading": "Num" }, { "name": "artist", "size": 30, "heading": "Artist" }, { "name": "title", "size": "remaining", "heading": "Title" }, ] def run_m(idx): """ Create playlist based on the results selected """ create_playlist(idx, title) if data: data = [listview.ListSongtitle(x) for x in data] g.content = listview.ListView(columns, data, run_m) g.message = util.F("mkp desc which data") else: g.message = util.F("mkp no valid") return
def _get_video_gdata(self, video_id): """ Return json string containing video metadata from gdata api. """ if self.callback: self.callback("Fetching video gdata") query = {'part': 'id,snippet,statistics', 'maxResults': 1, 'id': video_id} gdata = call_gdata('videos', query) dbg("Fetched video gdata") if self.callback: self.callback("Fetched video gdata") return gdata
def get_results(q): results = [] qs = {'q': q, 'maxResults': 12, 'part': 'id,snippet'} pafy.set_api_key(os.environ['PAFY_API_KEY']) gdata = pafy.call_gdata('search', qs) with youtube_dl.YoutubeDL({'format': 'bestaudio/best'}) as ydl: for item in gdata['items']: try: result = {'extracted': False, 'id': item['id']['videoId'], 'title': item['snippet']['title']} results.append(result) except KeyError: pass return results
def fetch_comments(item): """ Fetch comments for item using gdata. """ # pylint: disable=R0912 # pylint: disable=R0914 ytid, title = item.ytid, item.title util.dbg("Fetching comments for %s", c.c("y", ytid)) screen.writestatus("Fetching comments for %s" % c.c("y", title[:55])) qs = { "textFormat": "plainText", "videoId": ytid, "maxResults": 50, "part": "snippet", } jsdata = None try: jsdata = pafy.call_gdata("commentThreads", qs) except pafy.util.GdataError as e: raise pafy.util.GdataError( str(e).replace( ' identified by the <code><a href="/youtube/v3/docs/commentThreads/list#videoId">videoId</a></code> parameter', "", )) coms = [x.get("snippet", {}) for x in jsdata.get("items", [])] # skip blanks coms = [ x for x in coms if len( x.get("topLevelComment", {}).get("snippet", {}).get( "textDisplay", "").strip()) ] if not len(coms): g.message = "No comments for %s" % item.title[:50] g.content = generate_songlist_display() return commentstext = "" for n, com in enumerate(coms, 1): snippet = com.get("topLevelComment", {}).get("snippet", {}) commentstext += _format_comment(snippet, n, len(coms)) if com.get("totalReplyCount") > 0: replies = _fetch_commentreplies( com.get("topLevelComment").get("id")) for n, com in enumerate(reversed(replies), 1): commentstext += _format_comment(com.get("snippet", {}), n, len(replies), True) g.current_page = 0 g.content = content.StringContent(commentstext)
def get(self): results = [] if 'qs' in request.args: term = request.args['qs'] wdata = pafy.call_gdata('search', generate_search_qs(term)) for item in wdata['items']: song = { 'title': item['snippet']['title'], 'video_id': get_track_id_from_json(item) } results.append(song) return jsonify({'results': results}), 200
def _perform_api_call(self): # Include nextPageToken if it is set qry = dict(pageToken=self.nextpagetoken, **(self.queries)) if self.nextpagetoken else self.queries # Run query util.dbg("CQ.query", qry) data = pafy.call_gdata(self.api, qry) self.maxresults = int(data.get("pageInfo").get("totalResults")) self.nextpagetoken = data.get("nextPageToken") for obj in data.get("items"): self.pdata.append(self.datatype(obj))
def search_results(term, content, results, category, duration, order): query = { 'part': 'id', 'safeSearch': 'none', 'maxResults': results, 'type': content, 'key': conf.api_key, 'videoCategoryId': category, 'videoDuration': duration, 'q': term, 'order': order } search = pafy.call_gdata('search', query) return search
def get_tracks_from_json(jsons, howmany=0): """ Get search results from pafy's call_gdata response :param jsons: The result of calling pafy.call_gdata('search', query) :param howmany: The maximum number of tracks to retrieve (0 means, no limit) """ items = jsons.get("items") if not items: logging.info("got unexpected data or no search results") return () # fetch detailed information about items from videos API query_string = { 'part': 'contentDetails,statistics,snippet', 'id': ','.join([get_track_id_from_json(i) for i in items]) } wdata = pafy.call_gdata('videos', query_string) items_vidinfo = wdata.get('items', []) # enhance search results by adding information from videos API response for searchresult, vidinfoitem in zip(items, items_vidinfo): searchresult.update(vidinfoitem) # populate list of video objects songs = [] for item in items: try: ytid = get_track_id_from_json(item) snippet = item.get('snippet', {}) title = snippet.get('title', '').strip() info = VideoInfo(ytid=ytid, title=title) except Exception as exception: logging.info('Error during metadata extraction/instantiation of ' + 'search result {}\n{}'.format(ytid, exception)) songs.append(info) if howmany != 0 and len(songs) == howmany: break # return video objects return songs
def _perform_api_call(self): # Include nextPageToken if it is set qry = dict( pageToken=self.nextpagetoken, **(self.queries) ) if self.nextpagetoken else self.queries # Run query util.dbg("CQ.query", qry) data = pafy.call_gdata(self.api, qry) self.maxresults = int(data.get("pageInfo").get("totalResults")) self.nextpagetoken = data.get("nextPageToken") for obj in data.get("items"): self.pdata.append(self.datatype(obj))
def get_tracks_from_json(jsons, howmany=0): """ Get search results from pafy's call_gdata response :param jsons: The result of calling pafy.call_gdata('search', query) :param howmany: The maximum number of tracks to retrieve (0 means, no limit) """ items = jsons.get("items") if not items: logging.info("got unexpected data or no search results") return () # fetch detailed information about items from videos API query_string = {'part':'contentDetails,statistics,snippet', 'id': ','.join([get_track_id_from_json(i) for i in items])} wdata = pafy.call_gdata('videos', query_string) items_vidinfo = wdata.get('items', []) # enhance search results by adding information from videos API response for searchresult, vidinfoitem in zip(items, items_vidinfo): searchresult.update(vidinfoitem) # populate list of video objects songs = [] for item in items: try: ytid = get_track_id_from_json(item) snippet = item.get('snippet', {}) title = snippet.get('title', '').strip() info = VideoInfo(ytid=ytid, title=title) except Exception as exception: logging.info('Error during metadata extraction/instantiation of ' + 'search result {}\n{}'.format(ytid, exception)) songs.append(info) if howmany != 0 and len(songs) == howmany: break # return video objects return songs
def search(q, channel): search_query = { 'q': q, 'maxResults': min(50, int(app.configuration["query"]["limit_results"])), 'part': 'id,snippet', 'type': 'video' } if channel: search_query['channelId'] = channel print(search_query) result = pafy.call_gdata('search', search_query) return [{ 'id': 'https://www.youtube.com/watch?v=%s' % item['id']['videoId'], 'album': 'YouTube', 'artist': item['snippet']['channelTitle'], 'title': item['snippet']['title'] } for item in result['items'] if item['id']['kind'] == "youtube#video"]
def fetch_comments(item): """ Fetch comments for item using gdata. """ # pylint: disable=R0912 # pylint: disable=R0914 ytid, title = item.ytid, item.title util.dbg("Fetching comments for %s", c.c("y", ytid)) screen.writestatus("Fetching comments for %s" % c.c("y", title[:55])) qs = { 'textFormat': 'plainText', 'videoId': ytid, 'maxResults': 50, 'part': 'snippet' } jsdata = pafy.call_gdata('commentThreads', qs) coms = [x.get('snippet', {}) for x in jsdata.get('items', [])] # skip blanks coms = [ x for x in coms if len( x.get('topLevelComment', {}).get('snippet', {}).get( 'textDisplay', '').strip()) ] if not len(coms): g.message = "No comments for %s" % item.title[:50] g.content = generate_songlist_display() return commentstext = '' for n, com in enumerate(coms, 1): snippet = com.get('topLevelComment', {}).get('snippet', {}) commentstext += _format_comment(snippet, n, len(coms)) if com.get('totalReplyCount') > 0: replies = _fetch_commentreplies( com.get('topLevelComment').get('id')) for n, com in enumerate(reversed(replies), 1): commentstext += _format_comment(com.get('snippet', {}), n, len(replies), True) g.current_page = 0 g.content = content.StringContent(commentstext)
def get_songs_from_result(yresult): """ Return items from youtube search result """ items = yresult.get("items") if not items: # TODO: logger print('Result withou items') return () # fetch detailed information about items from videos API id_list = [get_track_id_from_json(i) for i in items if i['id']['kind'] == 'youtube#video'] qs = {'part': 'contentDetails,statistics,snippet', 'id': ','.join(id_list)} wdata = pafy.call_gdata('videos', qs) items_vidinfo = wdata.get('items', []) # enhance search results by adding information from videos API response for searchresult, vidinfoitem in zip(items, items_vidinfo): searchresult.update(vidinfoitem) # populate list of video objects songs = [] for item in items: ytid = get_track_id_from_json(item) duration = item.get('contentDetails', {}).get('duration') duration = get_duration_from_duration(duration) snippet = item.get('snippet', {}) title = snippet.get('title', '').strip() duration = format_int_duration(duration) current = Song(title=title, duration=duration, source=SOURCE_NAME, available=False, search_id=ytid) songs.append(current) return songs
def description_generator(text): """ Fetches a videos description and parses it for <artist> - <track> combinations """ if not isinstance(g.model, Playlist): g.message = util.F("mkp desc unknown") return # Use only the first result, for now num = text.replace("--description", "") num = num.replace("-d", "") num = util.number_string_to_list(num)[0] query = {} query['id'] = g.model[num].ytid query['part'] = 'snippet' query['maxResults'] = '1' data = pafy.call_gdata('videos', query)['items'][0]['snippet'] title = "mkp %s" % data['title'] data = util.fetch_songs(data['description'], data['title']) columns = [ {"name": "idx", "size": 3, "heading": "Num"}, {"name": "artist", "size": 30, "heading": "Artist"}, {"name": "title", "size": "remaining", "heading": "Title"}, ] def run_m(idx): """ Create playlist based on the results selected """ create_playlist(idx, title) if data: data = [listview.ListSongtitle(x) for x in data] g.content = listview.ListView(columns, data, run_m) g.message = util.F("mkp desc which data") else: g.message = util.F("mkp no valid") return
def __init__(self, playlist_url, basic, gdata, size, callback): playlist_id = extract_playlist_id(playlist_url) if not playlist_id: err = "Unrecognized playlist url: %s" raise ValueError(err % playlist_url) query = {'part': 'snippet, contentDetails', 'id': playlist_id} allinfo = call_gdata('playlists', query) pl = allinfo['items'][0] self.plid = playlist_id self.title = pl['snippet']['title'] self.author = pl['snippet']['channelTitle'] self.description = pl['snippet']['description'] self._len = pl['contentDetails']['itemCount'] self._basic = basic self._gdata = gdata self._size = size self._callback = callback
def _match_tracks(artist, title, mb_tracks): """ Match list of tracks in mb_tracks by performing multiple searches. """ # pylint: disable=R0914 util.dbg("artists is %s", artist) util.dbg("title is %s", title) title_artist_str = c.g + title + c.w, c.g + artist + c.w util.xprint("\nSearching for %s by %s\n\n" % title_artist_str) def dtime(x): """ Format time to M:S. """ return time.strftime("%M:%S", time.gmtime(int(x))) # do matching for track in mb_tracks: ttitle = track["title"] length = track["length"] util.xprint("Search : %s%s - %s%s - %s" % (c.y, artist, ttitle, c.w, dtime(length))) q = "%s %s" % (artist, ttitle) w = q = ttitle if artist == "Various Artists" else q query = generate_search_qs(w, 0) util.dbg(query) # perform fetch wdata = pafy.call_gdata("search", query) results = get_tracks_from_json(wdata) if not results: util.xprint(c.r + "Nothing matched :(\n" + c.w) continue s, score = _best_song_match(results, artist + " " + ttitle, length, 0.5, 0.5) cc = c.g if score > 85 else c.y cc = c.r if score < 75 else cc util.xprint( "Matched: %s%s%s - %s \n[%sMatch confidence: " "%s%s]\n" % (c.y, s.title, c.w, util.fmt_time(s.length), cc, score, c.w)) yield s
def _get_tracks_from_json(self, jsons): """ Get search results from API response """ items = jsons.get("items") if not items: logging.info("got unexpected data or no search results") return () # fetch detailed information about items from videos API qs = {'part':'contentDetails,statistics,snippet', 'id': ','.join([self._get_track_id_from_json(i) for i in items])} wdata = pafy.call_gdata('videos', qs) items_vidinfo = wdata.get('items', []) # enhance search results by adding information from videos API response for searchresult, vidinfoitem in zip(items, items_vidinfo): searchresult.update(vidinfoitem) # populate list of video objects songs = [] for item in items: try: ytid = self._get_track_id_from_json(item) snippet = item.get('snippet', {}) title = snippet.get('title', '').strip() info = VideoInfo(ytid=ytid, title=title) except Exception as e: logging.info('Error during metadata extraction/instantiation of ' + 'search result {}\n{}'.format(ytid, e)) songs.append(info) # return video objects return songs
def _match_tracks(artist, title, mb_tracks): """ Match list of tracks in mb_tracks by performing multiple searches. """ # pylint: disable=R0914 util.dbg("artists is %s", artist) util.dbg("title is %s", title) title_artist_str = c.g + title + c.w, c.g + artist + c.w util.xprint("\nSearching for %s by %s\n\n" % title_artist_str) def dtime(x): """ Format time to M:S. """ return time.strftime('%M:%S', time.gmtime(int(x))) # do matching for track in mb_tracks: ttitle = track['title'] length = track['length'] util.xprint("Search : %s%s - %s%s - %s" % (c.y, artist, ttitle, c.w, dtime(length))) q = "%s %s" % (artist, ttitle) w = q = ttitle if artist == "Various Artists" else q query = generate_search_qs(w, 0) util.dbg(query) # perform fetch wdata = pafy.call_gdata('search', query) results = get_tracks_from_json(wdata) if not results: util.xprint(c.r + "Nothing matched :(\n" + c.w) continue s, score = _best_song_match( results, artist + " " + ttitle, length, .5, .5) cc = c.g if score > 85 else c.y cc = c.r if score < 75 else cc util.xprint("Matched: %s%s%s - %s \n[%sMatch confidence: " "%s%s]\n" % (c.y, s.title, c.w, util.fmt_time(s.length), cc, score, c.w)) yield s
def artist_from_title(title): """ Try to determine an artist by doing a search on the video and try to find the most common element by n number of times looking for the most common substring in a subset of the results from youtube """ query = {} query['q'] = title query['type'] = 'video' query['fields'] = "items(snippet(title))" query['maxResults'] = 50 query['part'] = "snippet" results = pafy.call_gdata('search', query)['items'] titles = [x['snippet']['title'].upper() for x in results] alts = {} for _ in range(100): random.shuffle(titles) subset = titles[:10] string = long_substr(subset).strip() if len(string) > 3: alts[string] = alts.get(string, 0) + 1 best_string = None if len(alts) == 1: best_string = list(alts.keys())[0].capitalize() else: best_guess = 99999 best_string = None for key in list(alts.keys()): current_guess = title.upper().find(key) if current_guess < best_guess: best_guess = current_guess best_string = key.capitalize() best_string = re.sub(r"([^\w]+)$", "", best_string) best_string = re.sub(r"^([^\w]+)", "", best_string) return best_string
def fetch_comments(item): """ Fetch comments for item using gdata. """ # pylint: disable=R0912 # pylint: disable=R0914 ytid, title = item.ytid, item.title util.dbg("Fetching comments for %s", c.c("y", ytid)) screen.writestatus("Fetching comments for %s" % c.c("y", title[:55])) qs = {'textFormat': 'plainText', 'videoId': ytid, 'maxResults': 50, 'part': 'snippet'} # XXX should comment threads be expanded? this would require # additional requests for comments responding on top level comments jsdata = pafy.call_gdata('commentThreads', qs) coms = jsdata.get('items', []) coms = [x.get('snippet', {}) for x in coms] coms = [x.get('topLevelComment', {}) for x in coms] # skip blanks coms = [x for x in coms if len(x.get('snippet', {}).get('textDisplay', '').strip())] if not len(coms): g.message = "No comments for %s" % item.title[:50] g.content = generate_songlist_display() return commentstext = '' for n, com in enumerate(coms, 1): snippet = com.get('snippet', {}) poster = snippet.get('authorDisplayName') _, shortdate = util.yt_datetime(snippet.get('publishedAt', '')) text = snippet.get('textDisplay', '') cid = ("%s/%s" % (n, len(coms))) commentstext += ("%s %-35s %s\n" % (cid, c.c("g", poster), shortdate)) commentstext += c.c("y", text.strip()) + '\n\n' g.content = content.StringContent(commentstext)
def fetch_comments(item): """ Fetch comments for item using gdata. """ # pylint: disable=R0912 # pylint: disable=R0914 ytid, title = item.ytid, item.title util.dbg("Fetching comments for %s", c.c("y", ytid)) screen.writestatus("Fetching comments for %s" % c.c("y", title[:55])) qs = {'textFormat': 'plainText', 'videoId': ytid, 'maxResults': 50, 'part': 'snippet'} jsdata = pafy.call_gdata('commentThreads', qs) coms = [x.get('snippet', {}) for x in jsdata.get('items', [])] # skip blanks coms = [x for x in coms if len(x.get('topLevelComment', {}).get('snippet', {}).get('textDisplay', '').strip())] if not len(coms): g.message = "No comments for %s" % item.title[:50] g.content = generate_songlist_display() return commentstext = '' for n, com in enumerate(coms, 1): snippet = com.get('topLevelComment', {}).get('snippet', {}) commentstext += _format_comment(snippet, n, len(coms)) if com.get('totalReplyCount') > 0: replies = _fetch_commentreplies(com.get('topLevelComment').get('id')) for n, com in enumerate(reversed(replies), 1): commentstext += _format_comment(com.get('snippet', {}), n, len(replies), True) g.current_page = 0 g.content = content.StringContent(commentstext)
def pl_search(term, page=0, splash=True, is_user=False): """ Search for YouTube playlists. term can be query str or dict indicating user playlist search. """ if not term or len(term) < 2: g.message = c.r + "Not enough input" + c.w g.content = content.generate_songlist_display() return if splash: g.content = content.logo(c.g) prog = "user: "******"Searching playlists for %s" % c.y + prog + c.w screen.update() if is_user: ret = channelfromname(term) if not ret: # Error return user, channel_id = ret else: # playlist search is done with the above url and param type=playlist logging.info("playlist search for %s", prog) qs = generate_search_qs(term) qs['pageToken'] = token(page) qs['type'] = 'playlist' if 'videoCategoryId' in qs: del qs['videoCategoryId'] # Incompatable with type=playlist pldata = pafy.call_gdata('search', qs) id_list = [i.get('id', {}).get('playlistId') for i in pldata.get('items', ()) if i['id']['kind'] == 'youtube#playlist'] result_count = min(pldata['pageInfo']['totalResults'], 500) qs = {'part': 'contentDetails,snippet', 'maxResults': 50} if is_user: if page: qs['pageToken'] = token(page) qs['channelId'] = channel_id else: qs['id'] = ','.join(id_list) pldata = pafy.call_gdata('playlists', qs) playlists = get_pl_from_json(pldata)[:util.getxy().max_results] if is_user: result_count = pldata['pageInfo']['totalResults'] if playlists: g.last_search_query = (pl_search, {"term": term, "is_user": is_user}) g.browse_mode = "ytpl" g.current_page = page g.result_count = result_count g.ytpls = playlists g.message = "Playlist results for %s" % c.y + prog + c.w g.content = content.generate_playlist_display() else: g.message = "No playlists found for: %s" % c.y + prog + c.w g.current_page = 0 g.content = content.generate_songlist_display(zeromsg=g.message)
def get_tracks_from_json(jsons): """ Get search results from API response """ items = jsons.get("items") if not items: util.dbg("got unexpected data or no search results") return () # fetch detailed information about items from videos API id_list = [get_track_id_from_json(i) for i in items if i['id']['kind'] == 'youtube#video'] qs = {'part':'contentDetails,statistics,snippet', 'id': ','.join(id_list)} wdata = pafy.call_gdata('videos', qs) items_vidinfo = wdata.get('items', []) # enhance search results by adding information from videos API response for searchresult, vidinfoitem in zip(items, items_vidinfo): searchresult.update(vidinfoitem) # populate list of video objects songs = [] for item in items: try: ytid = get_track_id_from_json(item) duration = item.get('contentDetails', {}).get('duration') if duration: duration = ISO8601_TIMEDUR_EX.findall(duration) if len(duration) > 0: _, hours, _, minutes, _, seconds = duration[0] duration = [seconds, minutes, hours] duration = [int(v) if len(v) > 0 else 0 for v in duration] duration = sum([60**p*v for p, v in enumerate(duration)]) else: duration = 30 else: duration = 30 stats = item.get('statistics', {}) snippet = item.get('snippet', {}) title = snippet.get('title', '').strip() # instantiate video representation in local model cursong = Video(ytid=ytid, title=title, length=duration) likes = int(stats.get('likeCount', 0)) dislikes = int(stats.get('dislikeCount', 0)) #XXX this is a very poor attempt to calculate a rating value rating = 5.*likes/(likes+dislikes) if (likes+dislikes) > 0 else 0 category = snippet.get('categoryId') publishedlocaldatetime = util.yt_datetime_local(snippet.get('publishedAt', '')) # cache video information in custom global variable store g.meta[ytid] = dict( # tries to get localized title first, fallback to normal title title=snippet.get('localized', {'title':snippet.get('title', '[!!!]')}).get('title', '[!]'), length=str(util.fmt_time(cursong.length)), rating=str('{}'.format(rating))[:4].ljust(4, "0"), uploader=snippet.get('channelId'), uploaderName=snippet.get('channelTitle'), category=category, aspect="custom", #XXX uploaded=publishedlocaldatetime[1], uploadedTime=publishedlocaldatetime[2], likes=str(num_repr(likes)), dislikes=str(num_repr(dislikes)), commentCount=str(num_repr(int(stats.get('commentCount', 0)))), viewCount=str(num_repr(int(stats.get('viewCount', 0))))) except Exception as e: util.dbg(json.dumps(item, indent=2)) util.dbg('Error during metadata extraction/instantiation of ' + 'search result {}\n{}'.format(ytid, e)) songs.append(cursong) # return video objects return songs
def _fetch_commentreplies(parentid): return pafy.call_gdata('comments', { 'parentId': parentid, 'part': 'snippet', 'textFormat': 'plainText', 'maxResults': 50}).get('items', [])