From 679cad53b58db4aeae29a9ca0d6761fbf09373f7 Mon Sep 17 00:00:00 2001 From: BlackLight Date: Wed, 12 Dec 2018 22:31:36 +0100 Subject: [PATCH] Added YouTube plugin that leverages the YouTube API; Replaced OMXPlayer search references to the old HTML parser withe new YouTube plugin --- .../platypush/plugins/google.youtube.rst | 6 ++ docs/source/plugins.rst | 1 + platypush/plugins/google/youtube.py | 83 +++++++++++++++++++ platypush/plugins/video/omxplayer.py | 23 ++++- 4 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 docs/source/platypush/plugins/google.youtube.rst create mode 100644 platypush/plugins/google/youtube.py diff --git a/docs/source/platypush/plugins/google.youtube.rst b/docs/source/platypush/plugins/google.youtube.rst new file mode 100644 index 0000000000..f7d9646d0f --- /dev/null +++ b/docs/source/platypush/plugins/google.youtube.rst @@ -0,0 +1,6 @@ +``platypush.plugins.google.youtube`` +==================================== + +.. automodule:: platypush.plugins.google.youtube + :members: + diff --git a/docs/source/plugins.rst b/docs/source/plugins.rst index bfa87806f0..1250776413 100644 --- a/docs/source/plugins.rst +++ b/docs/source/plugins.rst @@ -19,6 +19,7 @@ Plugins platypush/plugins/google.mail.rst platypush/plugins/google.maps.rst platypush/plugins/google.rst + platypush/plugins/google.youtube.rst platypush/plugins/gpio.rst platypush/plugins/gpio.sensor.accelerometer.rst platypush/plugins/gpio.sensor.distance.rst diff --git a/platypush/plugins/google/youtube.py b/platypush/plugins/google/youtube.py new file mode 100644 index 0000000000..1f9b4514d9 --- /dev/null +++ b/platypush/plugins/google/youtube.py @@ -0,0 +1,83 @@ +""" +.. moduleauthor:: Fabio Manganiello +""" + +import base64 +import datetime +import httplib2 +import os + +from apiclient import discovery + +from platypush.plugins import action +from platypush.plugins.google import GooglePlugin +from platypush.plugins.calendar import CalendarInterface + + +class GoogleYoutubePlugin(GooglePlugin, CalendarInterface): + """ + YouTube plugin + """ + + scopes = ['https://www.googleapis.com/auth/youtube.readonly'] + + # See https://developers.google.com/youtube/v3/getting-started#part + _default_parts = ['snippet'] + + # See https://developers.google.com/youtube/v3/getting-started#resources + _default_types = ['video'] + + + def __init__(self, *args, **kwargs): + super().__init__(scopes=self.scopes, *args, **kwargs) + + + @action + def search(self, parts=None, query='', types=None, max_results=25, **kwargs): + """ + Search for YouTube content. + + :param parts: List of parts to get (default: snippet). See the `YouTube API documentation `_. + :type parts: list[str] or str + + :param query: Query string (default: empty string) + :type query: str + + :param types: List of types to retrieve (default: video). See the `YouTube API documentation `_. + :type types: list[str] or str + + :param max_results: Maximum number of items that will be returned (default: 25). + :type max_results: int + + :param kwargs: Any extra arguments that will be transparently passed to the YouTube API, see the `YouTube API documentation `_. + + :return: A list of YouTube resources, see the `YouTube API documentation `_. + """ + + parts = parts or self._default_parts[:] + if isinstance(parts, list): + parts = ','.join(parts) + + types = types or self._default_types[:] + if isinstance(types, list): + types = ','.join(types) + + service = self._get_service() + result = service.search().list(part=parts, q=query, type=types, + **kwargs).execute() + + events = result.get('items', []) + return events + + + def _get_service(self, scope=None): + if scope is None: + scope = self.scopes[0] + + credentials = self.credentials[scope] + http = credentials.authorize(httplib2.Http()) + return discovery.build('youtube', 'v3', http=http, cache_discovery=False) + + +# vim:sw=4:ts=4:et: + diff --git a/platypush/plugins/video/omxplayer.py b/platypush/plugins/video/omxplayer.py index 79a8881638..325c3e2c14 100644 --- a/platypush/plugins/video/omxplayer.py +++ b/platypush/plugins/video/omxplayer.py @@ -7,9 +7,6 @@ import time import urllib.request import urllib.parse -from dbus.exceptions import DBusException -from omxplayer import OMXPlayer - from platypush.context import get_backend, get_plugin from platypush.plugins.media import PlayerState from platypush.message.event.video import VideoPlayEvent, VideoPauseEvent, \ @@ -89,6 +86,8 @@ class VideoOmxplayerPlugin(Plugin): * Torrents (format: Magnet links, Torrent URLs or local Torrent files) """ + from dbus.exceptions import DBusException + if resource.startswith('youtube:') \ or resource.startswith('https://www.youtube.com/watch?v='): resource = self._get_youtube_content(resource) @@ -114,6 +113,7 @@ class VideoOmxplayerPlugin(Plugin): 'of OMXPlayer, trying to play anyway') try: + from omxplayer import OMXPlayer self.player = OMXPlayer(resource, args=self.args) self._init_player_handlers() except DBusException as e: @@ -430,8 +430,25 @@ class VideoOmxplayerPlugin(Plugin): @action def youtube_search(self, query): + """ + Performs a YouTube search either using the YouTube API (faster and + recommended, it requires the :mod:`platypush.plugins.google.youtube` + plugin to be configured) or parsing the HTML search results (fallback + slower method) + """ + self.logger.info('Searching YouTube for "{}"'.format(query)) + try: + return get_plugin('google.youtube').search(query=query) + except Exception as e: + self.logger.warning('Unable to load the YouTube plugin, falling ' + + 'back to HTML parse method: {}'.format(str(e))) + + return self._youtube_search_html_parse(query=query) + + + def _youtube_search_html_parse(self, query): query = urllib.parse.quote(query) url = "https://www.youtube.com/results?search_query=" + query response = urllib.request.urlopen(url)