Use youtube-dl to extract the video URL instead of streaming its content to a local sock file

This commit is contained in:
Fabio Manganiello 2020-11-11 03:07:23 +01:00
parent 43f71ed47b
commit 711ea543bb
3 changed files with 31 additions and 28 deletions

View file

@ -1,7 +1,7 @@
import os import os
import subprocess import subprocess
import threading import threading
from typing import Callable, Optional, List, Tuple from typing import Callable, Optional, List
from platypush.plugins import Plugin, action from platypush.plugins import Plugin, action

View file

@ -42,7 +42,6 @@ class MediaPlugin(Plugin):
# A media plugin can either be local or remote (e.g. control media on # A media plugin can either be local or remote (e.g. control media on
# another device) # another device)
_is_local = True _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') _NOT_IMPLEMENTED_ERR = NotImplementedError('This method must be implemented in a derived class')
# Supported audio extensions # Supported audio extensions
@ -159,6 +158,12 @@ class MediaPlugin(Plugin):
evt_queue.put(event.args['files']) evt_queue.put(event.args['files'])
return handler 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): def _get_resource(self, resource):
""" """
:param resource: Resource to play/parse. Supported types: :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) * Torrents (format: Magnet links, Torrent URLs or local Torrent files)
""" """
if resource.startswith('youtube:') \ if self._is_youtube_resource(resource):
or resource.startswith('https://youtu.be/') \
or resource.startswith('https://www.youtube.com/watch?v='):
m = re.match('youtube:video:(.*)', resource) m = re.match('youtube:video:(.*)', resource)
if not m: if not m:
m = re.match('https://youtu.be/(.*)', resource) m = re.match('https://youtu.be/(.*)', resource)
@ -182,8 +185,7 @@ class MediaPlugin(Plugin):
# The Chromecast has already its native way to handle YouTube # The Chromecast has already its native way to handle YouTube
return resource return resource
self.stream_youtube_to_fifo(resource) resource = self.get_youtube_video_url(resource)
resource = 'file://' + self._youtube_fifo
elif resource.startswith('magnet:?'): elif resource.startswith('magnet:?'):
self.logger.info('Downloading torrent {} to {}'.format( self.logger.info('Downloading torrent {} to {}'.format(
resource, self.download_dir)) resource, self.download_dir))
@ -201,11 +203,16 @@ class MediaPlugin(Plugin):
else: else:
raise RuntimeError('No media file found in torrent {}'.format(resource)) raise RuntimeError('No media file found in torrent {}'.format(resource))
assert resource, 'Unable to find any compatible media resource'
return resource return resource
def _stop_torrent(self): def _stop_torrent(self):
torrents = get_plugin(self.torrent_plugin) # noinspection PyBroadException
torrents.quit() try:
torrents = get_plugin(self.torrent_plugin)
torrents.quit()
except:
pass
@action @action
def play(self, resource, *args, **kwargs): def play(self, resource, *args, **kwargs):
@ -467,23 +474,12 @@ class MediaPlugin(Plugin):
# noinspection PyProtectedMember # noinspection PyProtectedMember
return YoutubeMediaSearcher()._youtube_search_html_parse(query) return YoutubeMediaSearcher()._youtube_search_html_parse(query)
def stream_youtube_to_fifo(self, url): @staticmethod
if self._youtube_proc: def get_youtube_video_url(url):
self.logger.info('Terminating existing YouTube process') youtube_dl = subprocess.Popen(['youtube-dl', '-f', 'best', '-g', url], stdout=subprocess.PIPE)
self._youtube_proc.terminate() url = youtube_dl.communicate()[0].decode()
self._youtube_proc = None youtube_dl.wait()
return url
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 @staticmethod
def get_youtube_id(url: str) -> Optional[str]: def get_youtube_id(url: str) -> Optional[str]:

View file

@ -85,7 +85,7 @@ class MediaMpvPlugin(MediaPlugin):
self._post_event(MediaPauseEvent, resource=self._get_current_resource(), title=self._player.filename) self._post_event(MediaPauseEvent, resource=self._get_current_resource(), title=self._player.filename)
elif evt == Event.UNPAUSE: elif evt == Event.UNPAUSE:
self._post_event(MediaPlayEvent, resource=self._get_current_resource(), title=self._player.filename) 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 evt == Event.END_FILE and event.get('event', {}).get('reason') in
[EndFile.EOF_OR_INIT_FAILURE, EndFile.ABORTED, EndFile.QUIT]): [EndFile.EOF_OR_INIT_FAILURE, EndFile.ABORTED, EndFile.QUIT]):
playback_rebounced = self._playback_rebounce_event.wait(timeout=0.5) 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) args['sub_file'] = self.get_subtitles_file(subtitles)
resource = self._get_resource(resource) resource = self._get_resource(resource)
if resource.startswith('file://'): if resource.startswith('file://'):
resource = resource[7:] resource = resource[7:]
assert self._player, 'The player is not ready'
self._player.play(resource) self._player.play(resource)
if self.volume: if self.volume:
self.set_volume(volume=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) return ('file://' if os.path.isfile(self._player.stream_path)
else '') + 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: # vim:sw=4:ts=4:et: