Added media.webtorrent plugin
This commit is contained in:
parent
4b7730d4cf
commit
a4f80d4622
4 changed files with 288 additions and 12 deletions
|
@ -124,9 +124,15 @@ class MediaPlugin(Plugin):
|
|||
or resource.startswith('https://www.youtube.com/watch?v='):
|
||||
resource = self._get_youtube_content(resource)
|
||||
elif resource.startswith('magnet:?'):
|
||||
try:
|
||||
get_plugin('media.webtorrent')
|
||||
return resource # media.webtorrent will handle this
|
||||
except:
|
||||
pass
|
||||
|
||||
torrents = get_plugin('torrent')
|
||||
self.logger.info('Downloading torrent {} to {}'.format(resource,
|
||||
download_dir))
|
||||
self.logger.info('Downloading torrent {} to {}'.format(
|
||||
resource, self.download_dir))
|
||||
|
||||
response = torrents.download(resource, download_dir=self.download_dir)
|
||||
resources = [f for f in response.output if self._is_video_file(f)]
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import os
|
||||
import select
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
|
||||
from platypush.context import get_bus
|
||||
from platypush.context import get_bus, get_plugin
|
||||
from platypush.message.response import Response
|
||||
from platypush.plugins.media import PlayerState, MediaPlugin
|
||||
from platypush.message.event.media import MediaPlayEvent, MediaPauseEvent, \
|
||||
MediaStopEvent, NewPlayingMediaEvent
|
||||
|
||||
from platypush.plugins import action
|
||||
from platypush.utils import find_bins_in_path
|
||||
|
||||
|
||||
class MediaMplayerPlugin(MediaPlugin):
|
||||
|
@ -77,11 +79,7 @@ class MediaMplayerPlugin(MediaPlugin):
|
|||
def _init_mplayer_bin(self, mplayer_bin=None):
|
||||
if not mplayer_bin:
|
||||
bin_name = 'mplayer.exe' if os.name == 'nt' else 'mplayer'
|
||||
bins = [os.path.join(p, bin_name)
|
||||
for p in os.environ.get('PATH', '').split(':')
|
||||
if os.path.isfile(os.path.join(p, bin_name))
|
||||
and (os.name == 'nt' or
|
||||
os.access(os.path.join(p, bin_name), os.X_OK))]
|
||||
bins = find_bins_in_path(bin_name)
|
||||
|
||||
if not bins:
|
||||
raise RuntimeError('MPlayer executable not specified and not ' +
|
||||
|
@ -121,13 +119,14 @@ class MediaMplayerPlugin(MediaPlugin):
|
|||
popen_args['env'] = self._env
|
||||
|
||||
self._mplayer = subprocess.Popen(args, **popen_args)
|
||||
threading.Thread(target=self._process_monitor()).start()
|
||||
|
||||
def _build_actions(self):
|
||||
""" Populates the actions list by introspecting the mplayer executable """
|
||||
|
||||
self._actions = {}
|
||||
mplayer = subprocess.Popen([self.mplayer_bin, '-input', 'cmdlist'],
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
|
||||
def args_pprint(txt):
|
||||
lc = txt.lower()
|
||||
|
@ -222,6 +221,17 @@ class MediaMplayerPlugin(MediaPlugin):
|
|||
return [ { 'action': action, 'args': self._actions[action] }
|
||||
for action in sorted(self._actions.keys()) ]
|
||||
|
||||
def _process_monitor(self):
|
||||
def _thread():
|
||||
if not self._mplayer:
|
||||
return
|
||||
|
||||
self._mplayer.wait()
|
||||
get_bus().post(MediaStopEvent())
|
||||
self._mplayer = None
|
||||
|
||||
return _thread
|
||||
|
||||
@action
|
||||
def play(self, resource, mplayer_args=None):
|
||||
"""
|
||||
|
@ -234,9 +244,14 @@ class MediaMplayerPlugin(MediaPlugin):
|
|||
MPlayer executable
|
||||
:type mplayer_args: list[str]
|
||||
"""
|
||||
|
||||
resource = self._get_resource(resource)
|
||||
if resource.startswith('file://'):
|
||||
resource = resource[7:]
|
||||
elif resource.startswith('magnet:?'):
|
||||
torrent_plugin = get_plugin('media.webtorrent')
|
||||
return torrent_plugin.play(resource)
|
||||
|
||||
return self._exec('loadfile', resource, mplayer_args=mplayer_args)
|
||||
|
||||
@action
|
||||
|
@ -288,11 +303,11 @@ class MediaMplayerPlugin(MediaPlugin):
|
|||
return self.get_property('pause').output.get('pause') == False
|
||||
|
||||
@action
|
||||
def load(self, resource):
|
||||
def load(self, resource, mplayer_args={}):
|
||||
"""
|
||||
Load a resource/video in the player.
|
||||
"""
|
||||
return self.play('loadfile', resource)
|
||||
return self.play(resource, mplayer_args=mplayer_args)
|
||||
|
||||
@action
|
||||
def mute(self):
|
||||
|
|
249
platypush/plugins/media/webtorrent.py
Normal file
249
platypush/plugins/media/webtorrent.py
Normal file
|
@ -0,0 +1,249 @@
|
|||
import os
|
||||
import select
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
|
||||
from platypush.context import get_bus, get_plugin
|
||||
from platypush.message.response import Response
|
||||
from platypush.plugins.media import PlayerState, MediaPlugin
|
||||
from platypush.message.event.media import MediaPlayEvent, MediaPauseEvent, \
|
||||
MediaStopEvent, NewPlayingMediaEvent
|
||||
|
||||
from platypush.plugins import action
|
||||
from platypush.utils import find_bins_in_path
|
||||
|
||||
|
||||
class MediaWebtorrentPlugin(MediaPlugin):
|
||||
"""
|
||||
Plugin to download and stream videos using webtorrent
|
||||
|
||||
Requires:
|
||||
|
||||
* **webtorrent** installed on your system (``npm install -g webtorrent``)
|
||||
* **webtorrent-cli** installed on your system
|
||||
(``npm install -g webtorrent-cli`` or better
|
||||
``npm install -g BlackLight/webtorrent-cli`` as my fork contains
|
||||
the ``--[player]-args`` options to pass custom arguments to your
|
||||
installed player)
|
||||
"""
|
||||
|
||||
_supported_media_plugins = { 'media.mplayer', 'media.omxplayer' }
|
||||
_supported_webtorrent_players = {'airplay', 'chromecast', 'mplayer',
|
||||
'mpv', 'omxplayer', 'vlc', 'xbmc'}
|
||||
|
||||
|
||||
def __init__(self, player_type=None, player_args=None, webtorrent_bin=None,
|
||||
*args, **kwargs):
|
||||
"""
|
||||
Create the webtorrent wrapper
|
||||
|
||||
:param player_type: Player type to be used for streaming. Check
|
||||
https://www.npmjs.com/package/webtorrent for a full list of the
|
||||
supported players. If no player is specified then it will search in
|
||||
your Platypush configuration for any enabled and supported media
|
||||
plugin (either ``media.mplayer`` or ``media.omxplayer``) to
|
||||
configure the streaming player. Currently supported options:
|
||||
- airplay
|
||||
- chromecast
|
||||
- mplayer
|
||||
- mpv
|
||||
- omxlayer
|
||||
- vlc
|
||||
- xbmc
|
||||
|
||||
:type player_type: str
|
||||
|
||||
:param player_args: Extra arguments to pass to the player as a list.
|
||||
For mplayer, vlc, omxplayer and mpv this will be a list of extra
|
||||
arguments to be passed to the executable (e.g. fullscreen, volume,
|
||||
audio sink etc.).
|
||||
:type player_args: list[str]
|
||||
|
||||
:param webtorrent_bin: Path to your webtorrent executable. If not set,
|
||||
then Platypush will search for the right executable in your PATH
|
||||
:type webtorrent_bin: str
|
||||
"""
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self._init_webtorrent_bin(webtorrent_bin=webtorrent_bin)
|
||||
self._init_media_player(player_type=player_type,
|
||||
player_args=player_args or [])
|
||||
|
||||
|
||||
def _init_webtorrent_bin(self, webtorrent_bin=None):
|
||||
if not webtorrent_bin:
|
||||
bin_name = 'webtorrent.exe' if os.name == 'nt' else 'webtorrent'
|
||||
bins = find_bins_in_path(bin_name)
|
||||
|
||||
if not bins:
|
||||
raise RuntimeError('Webtorrent executable not specified and ' +
|
||||
'not found in your PATH. Make sure that ' +
|
||||
'webtorrent is either installed or ' +
|
||||
'configured and that both webtorrent and ' +
|
||||
'webtorrent-cli are installed')
|
||||
|
||||
self.webtorrent_bin = bins[0]
|
||||
else:
|
||||
webtorrent_bin = os.path.expanduser(webtorrent_bin)
|
||||
if not (os.path.isfile(webtorrent_bin)
|
||||
and (os.name == 'nt' or os.access(webtorrent_bin, os.X_OK))):
|
||||
raise RuntimeError('{} is does not exist or is not a valid ' +
|
||||
'executable file'.format(webtorrent_bin))
|
||||
|
||||
self.webtorrent_bin = webtorrent_bin
|
||||
|
||||
def _init_media_player(self, player_type, player_args):
|
||||
if player_type is None:
|
||||
media_plugin = None
|
||||
plugin_name = None
|
||||
|
||||
for plugin_name in self._supported_media_plugins:
|
||||
try:
|
||||
media_plugin = get_plugin(plugin_name)
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
if not media_plugin:
|
||||
raise RuntimeError(('No media player specified and no ' +
|
||||
'compatible media plugin configured - ' +
|
||||
'supported media plugins: {}').format(
|
||||
self._supported_media_plugins))
|
||||
|
||||
self.player_type = plugin_name[len('media.'):]
|
||||
self.player_args = media_plugin.args
|
||||
else:
|
||||
if player_type not in self._supported_webtorrent_players:
|
||||
raise RuntimeError(('{} is not a supported player (supported ' +
|
||||
'players: {})').format(
|
||||
player_type,
|
||||
self._supported_webtorrent_players))
|
||||
|
||||
self.player_type = player_type
|
||||
self.player_args = player_args or []
|
||||
|
||||
if not self.player_args:
|
||||
try:
|
||||
plugin = get_plugin('media.' + player_type)
|
||||
self.player_args = plugin.args
|
||||
except:
|
||||
pass
|
||||
|
||||
self._player = None
|
||||
|
||||
|
||||
def _start_webtorrent(self, resource):
|
||||
if self._player:
|
||||
try:
|
||||
self.quit()
|
||||
except:
|
||||
self.logger.debug('Failed to quit the previous instance: {}'.
|
||||
format(str))
|
||||
|
||||
webtorrent_args = [self.webtorrent_bin, '--' + self.player_type,
|
||||
resource]
|
||||
|
||||
if self.player_args:
|
||||
webtorrent_args += ['--' + self.player_type + "-args='" + \
|
||||
repr(' '.join(self.player_args)) + "'"]
|
||||
|
||||
popen_args = {
|
||||
'stdin': subprocess.PIPE,
|
||||
'stdout': subprocess.PIPE,
|
||||
}
|
||||
|
||||
if self._env:
|
||||
popen_args['env'] = self._env
|
||||
else:
|
||||
try:
|
||||
plugin = get_plugin('media.' + player_type)
|
||||
if plugin._env:
|
||||
popen_args['env'] = plugin._env
|
||||
except:
|
||||
pass
|
||||
|
||||
self._player = subprocess.Popen(webtorrent_args, **popen_args)
|
||||
|
||||
def _process_monitor(self):
|
||||
def _thread():
|
||||
if not self._player:
|
||||
return
|
||||
|
||||
self._player.wait()
|
||||
get_bus().post(MediaStopEvent())
|
||||
self._player = None
|
||||
|
||||
return _thread
|
||||
|
||||
@action
|
||||
def play(self, resource):
|
||||
"""
|
||||
Download and stream a torrent
|
||||
|
||||
:param resource: Play a resource, as a magnet link, torrent URL or
|
||||
torrent file path
|
||||
:type resource: str
|
||||
"""
|
||||
|
||||
self._start_webtorrent(resource)
|
||||
bus = get_bus()
|
||||
bus.post(NewPlayingMediaEvent(resource=resource))
|
||||
threading.Thread(target=self._process_monitor()).start()
|
||||
|
||||
@action
|
||||
def stop(self):
|
||||
""" Stop the playback """
|
||||
return self.quit()
|
||||
|
||||
@action
|
||||
def quit(self):
|
||||
""" Quit the player """
|
||||
if self._player and self._is_process_alive(self._player.pid):
|
||||
self._player.terminate()
|
||||
self._player.wait()
|
||||
try: self._player.kill()
|
||||
except: pass
|
||||
bus.post(MediaStopEvent())
|
||||
|
||||
self._player = None
|
||||
|
||||
@action
|
||||
def load(self, resource):
|
||||
"""
|
||||
Load a torrent resource in the player.
|
||||
"""
|
||||
return self.play(resource)
|
||||
|
||||
def _is_process_alive(self):
|
||||
if not self._player:
|
||||
return False
|
||||
|
||||
try:
|
||||
os.kill(self._player.pid, 0)
|
||||
return True
|
||||
except OSError:
|
||||
self._player = None
|
||||
return False
|
||||
|
||||
@action
|
||||
def status(self):
|
||||
"""
|
||||
Get the current player state.
|
||||
|
||||
:returns: A dictionary containing the current state.
|
||||
|
||||
Example::
|
||||
|
||||
output = {
|
||||
"state": "play" # or "stop" or "pause"
|
||||
}
|
||||
"""
|
||||
|
||||
return {'state': PlayerState.PLAY.value
|
||||
if self._player and self._is_process_alive()
|
||||
else PlayerState.STOP.value}
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
|
@ -172,6 +172,12 @@ def set_thread_name(name):
|
|||
except ImportError:
|
||||
logger.debug('Unable to set thread name: prctl module is missing')
|
||||
|
||||
def find_bins_in_path(bin_name):
|
||||
return [os.path.join(p, bin_name)
|
||||
for p in os.environ.get('PATH', '').split(':')
|
||||
if os.path.isfile(os.path.join(p, bin_name))
|
||||
and (os.name == 'nt' or
|
||||
os.access(os.path.join(p, bin_name), os.X_OK))]
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
||||
|
|
Loading…
Reference in a new issue