Use youtube-dl to extract the video URL instead of streaming its content to a local sock file
This commit is contained in:
parent
43f71ed47b
commit
711ea543bb
3 changed files with 31 additions and 28 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
# noinspection PyBroadException
|
||||||
|
try:
|
||||||
torrents = get_plugin(self.torrent_plugin)
|
torrents = get_plugin(self.torrent_plugin)
|
||||||
torrents.quit()
|
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]:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in a new issue