From 711ea543bb7dd5b142e3ce215ff4bca7c751581b Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Wed, 11 Nov 2020 03:07:23 +0100 Subject: [PATCH] Use youtube-dl to extract the video URL instead of streaming its content to a local sock file --- platypush/plugins/ffmpeg.py | 2 +- platypush/plugins/media/__init__.py | 46 +++++++++++++---------------- platypush/plugins/media/mpv.py | 11 +++++-- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/platypush/plugins/ffmpeg.py b/platypush/plugins/ffmpeg.py index 50b44532f..2942eadc4 100644 --- a/platypush/plugins/ffmpeg.py +++ b/platypush/plugins/ffmpeg.py @@ -1,7 +1,7 @@ import os import subprocess import threading -from typing import Callable, Optional, List, Tuple +from typing import Callable, Optional, List from platypush.plugins import Plugin, action diff --git a/platypush/plugins/media/__init__.py b/platypush/plugins/media/__init__.py index e49934841..bc32cd7a5 100644 --- a/platypush/plugins/media/__init__.py +++ b/platypush/plugins/media/__init__.py @@ -42,7 +42,6 @@ class MediaPlugin(Plugin): # A media plugin can either be local or remote (e.g. control media on # another device) _is_local = True - _youtube_fifo = os.path.join(tempfile.gettempdir(), 'youtube_video.sock') _NOT_IMPLEMENTED_ERR = NotImplementedError('This method must be implemented in a derived class') # Supported audio extensions @@ -159,6 +158,12 @@ class MediaPlugin(Plugin): evt_queue.put(event.args['files']) return handler + @staticmethod + def _is_youtube_resource(resource): + return resource.startswith('youtube:') \ + or resource.startswith('https://youtu.be/') \ + or resource.startswith('https://www.youtube.com/watch?v=') + def _get_resource(self, resource): """ :param resource: Resource to play/parse. Supported types: @@ -169,9 +174,7 @@ class MediaPlugin(Plugin): * Torrents (format: Magnet links, Torrent URLs or local Torrent files) """ - if resource.startswith('youtube:') \ - or resource.startswith('https://youtu.be/') \ - or resource.startswith('https://www.youtube.com/watch?v='): + if self._is_youtube_resource(resource): m = re.match('youtube:video:(.*)', resource) if not m: m = re.match('https://youtu.be/(.*)', resource) @@ -182,8 +185,7 @@ class MediaPlugin(Plugin): # The Chromecast has already its native way to handle YouTube return resource - self.stream_youtube_to_fifo(resource) - resource = 'file://' + self._youtube_fifo + resource = self.get_youtube_video_url(resource) elif resource.startswith('magnet:?'): self.logger.info('Downloading torrent {} to {}'.format( resource, self.download_dir)) @@ -201,11 +203,16 @@ class MediaPlugin(Plugin): else: raise RuntimeError('No media file found in torrent {}'.format(resource)) + assert resource, 'Unable to find any compatible media resource' return resource def _stop_torrent(self): - torrents = get_plugin(self.torrent_plugin) - torrents.quit() + # noinspection PyBroadException + try: + torrents = get_plugin(self.torrent_plugin) + torrents.quit() + except: + pass @action def play(self, resource, *args, **kwargs): @@ -467,23 +474,12 @@ class MediaPlugin(Plugin): # noinspection PyProtectedMember return YoutubeMediaSearcher()._youtube_search_html_parse(query) - def stream_youtube_to_fifo(self, url): - if self._youtube_proc: - self.logger.info('Terminating existing YouTube process') - self._youtube_proc.terminate() - self._youtube_proc = None - - if os.path.exists(self._youtube_fifo): - os.unlink(self._youtube_fifo) - - os.mkfifo(self._youtube_fifo, 0o644) - - def _youtube_dl_thread(): - self._youtube_proc = subprocess.Popen(['youtube-dl', '-f', 'best', '-o', self._youtube_fifo, url]) - self._youtube_proc.wait() - self._youtube_proc = None - - threading.Thread(target=_youtube_dl_thread).start() + @staticmethod + def get_youtube_video_url(url): + youtube_dl = subprocess.Popen(['youtube-dl', '-f', 'best', '-g', url], stdout=subprocess.PIPE) + url = youtube_dl.communicate()[0].decode() + youtube_dl.wait() + return url @staticmethod def get_youtube_id(url: str) -> Optional[str]: diff --git a/platypush/plugins/media/mpv.py b/platypush/plugins/media/mpv.py index d3750c271..f73392932 100644 --- a/platypush/plugins/media/mpv.py +++ b/platypush/plugins/media/mpv.py @@ -85,7 +85,7 @@ class MediaMpvPlugin(MediaPlugin): self._post_event(MediaPauseEvent, resource=self._get_current_resource(), title=self._player.filename) elif evt == Event.UNPAUSE: self._post_event(MediaPlayEvent, resource=self._get_current_resource(), title=self._player.filename) - elif evt == Event.SHUTDOWN or evt == Event.IDLE or ( + elif evt == Event.SHUTDOWN or ( evt == Event.END_FILE and event.get('event', {}).get('reason') in [EndFile.EOF_OR_INIT_FAILURE, EndFile.ABORTED, EndFile.QUIT]): playback_rebounced = self._playback_rebounce_event.wait(timeout=0.5) @@ -135,10 +135,10 @@ class MediaMpvPlugin(MediaPlugin): args['sub_file'] = self.get_subtitles_file(subtitles) resource = self._get_resource(resource) - if resource.startswith('file://'): resource = resource[7:] + assert self._player, 'The player is not ready' self._player.play(resource) if self.volume: self.set_volume(volume=self.volume) @@ -417,4 +417,11 @@ class MediaMpvPlugin(MediaPlugin): return ('file://' if os.path.isfile(self._player.stream_path) else '') + self._player.stream_path + def _get_resource(self, resource): + if self._is_youtube_resource(resource): + return resource # mpv can handle YouTube streaming natively + + return super()._get_resource(resource) + + # vim:sw=4:ts=4:et: