Use a filesystem FIFO for YouTube media content instead of playing the *.googlevideo.com URL directly

Google Video URLs now return 403 if played directly. Let youtube-dl
handle the heavylifting and use a FIFO to stream the media
This commit is contained in:
Fabio Manganiello 2019-09-30 18:06:30 +02:00
parent f69a7e422b
commit 09991b2e8a
2 changed files with 29 additions and 19 deletions

View file

@ -42,6 +42,7 @@ 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( _NOT_IMPLEMENTED_ERR = NotImplementedError(
'This method must be implemented in a derived class') 'This method must be implemented in a derived class')
@ -137,6 +138,7 @@ class MediaPlugin(Plugin):
self.media_dirs.add(self.download_dir) self.media_dirs.add(self.download_dir)
self._videos_queue = [] self._videos_queue = []
self._youtube_proc = None
@staticmethod @staticmethod
def _torrent_event_handler(evt_queue): def _torrent_event_handler(evt_queue):
@ -157,12 +159,20 @@ class MediaPlugin(Plugin):
""" """
if resource.startswith('youtube:') \ if resource.startswith('youtube:') \
or resource.startswith('https://youtu.be/') \
or resource.startswith('https://www.youtube.com/watch?v='): or resource.startswith('https://www.youtube.com/watch?v='):
m = re.match('youtube:video:(.*)', resource)
if not m:
m = re.match('https://youtu.be/(.*)', resource)
if m:
resource = 'https://www.youtube.com/watch?v={}'.format(m.group(1))
if self.__class__.__name__ == 'MediaChromecastPlugin': if self.__class__.__name__ == 'MediaChromecastPlugin':
# The Chromecast has already its native way to handle YouTube # The Chromecast has already its native way to handle YouTube
return resource return resource
resource = self.get_youtube_url(resource).output self.stream_youtube_to_fifo(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))
@ -458,6 +468,24 @@ class MediaPlugin(Plugin):
return results return results
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()
@action @action
def get_youtube_url(self, url): def get_youtube_url(self, url):
m = re.match('youtube:video:(.*)', url) m = re.match('youtube:video:(.*)', url)

View file

@ -1,5 +1,4 @@
import os import os
import re
import threading import threading
from platypush.context import get_bus from platypush.context import get_bus
@ -102,19 +101,6 @@ class MediaMpvPlugin(MediaPlugin):
return callback return callback
@staticmethod
def _get_youtube_link(resource):
base_url = 'https://youtu.be/'
regexes = ['^https://(www\.)?youtube.com/watch\?v=([^?&#]+)',
'^https://(www\.)?youtu.be.com/([^?&#]+)',
'^(youtube:video):([^?&#]+)']
for regex in regexes:
m = re.search(regex, resource)
if m:
return base_url + m.group(2)
return None
@action @action
def execute(self, cmd, **args): def execute(self, cmd, **args):
""" """
@ -150,10 +136,6 @@ class MediaMpvPlugin(MediaPlugin):
if resource.startswith('file://'): if resource.startswith('file://'):
resource = resource[7:] resource = resource[7:]
else:
yt_resource = self._get_youtube_link(resource)
if yt_resource:
resource = yt_resource
self._player.play(resource) self._player.play(resource)
return self.status() return self.status()