Refactored torrent module
This commit is contained in:
parent
a4f80d4622
commit
286eb431f0
3 changed files with 91 additions and 130 deletions
|
@ -46,15 +46,6 @@ class TorrentStateChangeEvent(TorrentEvent):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class TorrentStateChangeEvent(TorrentEvent):
|
|
||||||
"""
|
|
||||||
Event triggered upon torrent state change
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TorrentDownloadCompletedEvent(TorrentEvent):
|
class TorrentDownloadCompletedEvent(TorrentEvent):
|
||||||
"""
|
"""
|
||||||
Event triggered upon torrent state change
|
Event triggered upon torrent state change
|
||||||
|
@ -74,4 +65,3 @@ class TorrentDownloadStopEvent(TorrentEvent):
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
||||||
|
|
|
@ -155,7 +155,7 @@ class MediaMplayerPlugin(MediaPlugin):
|
||||||
self._init_mplayer(mplayer_args)
|
self._init_mplayer(mplayer_args)
|
||||||
else:
|
else:
|
||||||
if not self._mplayer:
|
if not self._mplayer:
|
||||||
raise RuntimeError('MPlayer is not running')
|
self.logger.warning('MPlayer is not running')
|
||||||
|
|
||||||
cmd = '{}{}{}{}\n'.format(
|
cmd = '{}{}{}{}\n'.format(
|
||||||
prefix + ' ' if prefix else '',
|
prefix + ' ' if prefix else '',
|
||||||
|
@ -249,8 +249,7 @@ class MediaMplayerPlugin(MediaPlugin):
|
||||||
if resource.startswith('file://'):
|
if resource.startswith('file://'):
|
||||||
resource = resource[7:]
|
resource = resource[7:]
|
||||||
elif resource.startswith('magnet:?'):
|
elif resource.startswith('magnet:?'):
|
||||||
torrent_plugin = get_plugin('media.webtorrent')
|
return get_plugin('media.webtorrent').play(resource)
|
||||||
return torrent_plugin.play(resource)
|
|
||||||
|
|
||||||
return self._exec('loadfile', resource, mplayer_args=mplayer_args)
|
return self._exec('loadfile', resource, mplayer_args=mplayer_args)
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
|
import datetime
|
||||||
import os
|
import os
|
||||||
import select
|
import select
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from platypush.config import Config
|
||||||
from platypush.context import get_bus, get_plugin
|
from platypush.context import get_bus, get_plugin
|
||||||
from platypush.message.response import Response
|
from platypush.message.response import Response
|
||||||
from platypush.plugins.media import PlayerState, MediaPlugin
|
from platypush.plugins.media import PlayerState, MediaPlugin
|
||||||
from platypush.message.event.media import MediaPlayEvent, MediaPauseEvent, \
|
from platypush.message.event.torrent import TorrentDownloadStartEvent, \
|
||||||
MediaStopEvent, NewPlayingMediaEvent
|
TorrentDownloadCompletedEvent
|
||||||
|
|
||||||
from platypush.plugins import action
|
from platypush.plugins import action
|
||||||
from platypush.utils import find_bins_in_path
|
from platypush.utils import find_bins_in_path
|
||||||
|
@ -26,39 +29,19 @@ class MediaWebtorrentPlugin(MediaPlugin):
|
||||||
``npm install -g BlackLight/webtorrent-cli`` as my fork contains
|
``npm install -g BlackLight/webtorrent-cli`` as my fork contains
|
||||||
the ``--[player]-args`` options to pass custom arguments to your
|
the ``--[player]-args`` options to pass custom arguments to your
|
||||||
installed player)
|
installed player)
|
||||||
|
* A media plugin configured for streaming (e.g. media.mplayer
|
||||||
|
or media.omxplayer)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_supported_media_plugins = { 'media.mplayer', 'media.omxplayer' }
|
_supported_media_plugins = { 'media.mplayer', 'media.omxplayer' }
|
||||||
_supported_webtorrent_players = {'airplay', 'chromecast', 'mplayer',
|
|
||||||
'mpv', 'omxplayer', 'vlc', 'xbmc'}
|
|
||||||
|
|
||||||
|
# Download at least 10 MBs before starting streaming
|
||||||
|
_download_size_before_streaming = 10 * 2**20
|
||||||
|
|
||||||
def __init__(self, player_type=None, player_args=None, webtorrent_bin=None,
|
def __init__(self, webtorrent_bin=None, *args, **kwargs):
|
||||||
*args, **kwargs):
|
|
||||||
"""
|
"""
|
||||||
Create the webtorrent wrapper
|
media.webtorrent will use the default media player plugin you have
|
||||||
|
configured (e.g. mplayer, omxplayer) to stream the torrent.
|
||||||
: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,
|
:param webtorrent_bin: Path to your webtorrent executable. If not set,
|
||||||
then Platypush will search for the right executable in your PATH
|
then Platypush will search for the right executable in your PATH
|
||||||
|
@ -68,11 +51,12 @@ class MediaWebtorrentPlugin(MediaPlugin):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self._init_webtorrent_bin(webtorrent_bin=webtorrent_bin)
|
self._init_webtorrent_bin(webtorrent_bin=webtorrent_bin)
|
||||||
self._init_media_player(player_type=player_type,
|
self._init_media_player()
|
||||||
player_args=player_args or [])
|
|
||||||
|
|
||||||
|
|
||||||
def _init_webtorrent_bin(self, webtorrent_bin=None):
|
def _init_webtorrent_bin(self, webtorrent_bin=None):
|
||||||
|
self._webtorrent_process = None
|
||||||
|
|
||||||
if not webtorrent_bin:
|
if not webtorrent_bin:
|
||||||
bin_name = 'webtorrent.exe' if os.name == 'nt' else 'webtorrent'
|
bin_name = 'webtorrent.exe' if os.name == 'nt' else 'webtorrent'
|
||||||
bins = find_bins_in_path(bin_name)
|
bins = find_bins_in_path(bin_name)
|
||||||
|
@ -94,89 +78,59 @@ class MediaWebtorrentPlugin(MediaPlugin):
|
||||||
|
|
||||||
self.webtorrent_bin = webtorrent_bin
|
self.webtorrent_bin = webtorrent_bin
|
||||||
|
|
||||||
def _init_media_player(self, player_type, player_args):
|
def _init_media_player(self):
|
||||||
if player_type is None:
|
self._media_plugin = None
|
||||||
media_plugin = None
|
plugin_name = None
|
||||||
plugin_name = None
|
|
||||||
|
|
||||||
for plugin_name in self._supported_media_plugins:
|
for plugin_name in self._supported_media_plugins:
|
||||||
try:
|
try:
|
||||||
media_plugin = get_plugin(plugin_name)
|
if Config.get(plugin_name):
|
||||||
|
self._media_plugin = get_plugin(plugin_name)
|
||||||
break
|
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:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self._player = subprocess.Popen(webtorrent_args, **popen_args)
|
if not self._media_plugin:
|
||||||
|
raise RuntimeError(('No media player specified and no ' +
|
||||||
|
'compatible media plugin configured - ' +
|
||||||
|
'supported media plugins: {}').format(
|
||||||
|
self._supported_media_plugins))
|
||||||
|
|
||||||
def _process_monitor(self):
|
|
||||||
|
def _process_monitor(self, resource, output_file):
|
||||||
def _thread():
|
def _thread():
|
||||||
if not self._player:
|
if not self._webtorrent_process:
|
||||||
return
|
return
|
||||||
|
|
||||||
self._player.wait()
|
bus = get_bus()
|
||||||
get_bus().post(MediaStopEvent())
|
bus.post(TorrentDownloadStartEvent(resource=resource))
|
||||||
self._player = None
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
if os.path.getsize(output_file) > \
|
||||||
|
self._download_size_before_streaming:
|
||||||
|
break
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self._media_plugin.play(output_file)
|
||||||
|
self._webtorrent_process.wait()
|
||||||
|
bus.post(TorrentDownloadCompletedEvent(resource=resource))
|
||||||
|
self._webtorrent_process = None
|
||||||
|
|
||||||
return _thread
|
return _thread
|
||||||
|
|
||||||
|
|
||||||
|
def _get_torrent_download_path(self):
|
||||||
|
if self._media_plugin.download_dir:
|
||||||
|
# TODO set proper file name based on the torrent metadata
|
||||||
|
return os.path.join(self._media_plugin.download_dir,
|
||||||
|
'torrent_media_' + datetime.datetime.
|
||||||
|
today().strftime('%Y-%m-%d_%H-%M-%S-%f'))
|
||||||
|
else:
|
||||||
|
return tempfile.NamedTemporaryFile(delete=False).name
|
||||||
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def play(self, resource):
|
def play(self, resource):
|
||||||
"""
|
"""
|
||||||
|
@ -187,10 +141,25 @@ class MediaWebtorrentPlugin(MediaPlugin):
|
||||||
:type resource: str
|
:type resource: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._start_webtorrent(resource)
|
if self._webtorrent_process:
|
||||||
bus = get_bus()
|
try:
|
||||||
bus.post(NewPlayingMediaEvent(resource=resource))
|
self.quit()
|
||||||
threading.Thread(target=self._process_monitor()).start()
|
except:
|
||||||
|
self.logger.debug('Failed to quit the previous instance: {}'.
|
||||||
|
format(str))
|
||||||
|
|
||||||
|
output_file = self._get_torrent_download_path()
|
||||||
|
webtorrent_args = [self.webtorrent_bin, '--stdout', resource]
|
||||||
|
|
||||||
|
with open(output_file, 'w') as f:
|
||||||
|
self._webtorrent_process = subprocess.Popen(webtorrent_args,
|
||||||
|
stdout=f)
|
||||||
|
|
||||||
|
threading.Thread(target=self._process_monitor(
|
||||||
|
resource=resource, output_file=output_file)).start()
|
||||||
|
|
||||||
|
return { 'resource': resource }
|
||||||
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
@ -200,14 +169,18 @@ class MediaWebtorrentPlugin(MediaPlugin):
|
||||||
@action
|
@action
|
||||||
def quit(self):
|
def quit(self):
|
||||||
""" Quit the player """
|
""" Quit the player """
|
||||||
if self._player and self._is_process_alive(self._player.pid):
|
if self._media_plugin:
|
||||||
self._player.terminate()
|
self._media_plugin.quit()
|
||||||
self._player.wait()
|
|
||||||
try: self._player.kill()
|
if self._webtorrent_process and self._is_process_alive(
|
||||||
|
self._webtorrent_process.pid):
|
||||||
|
self._webtorrent_process.terminate()
|
||||||
|
self._webtorrent_process.wait()
|
||||||
|
try: self._webtorrent_process.kill()
|
||||||
except: pass
|
except: pass
|
||||||
bus.post(MediaStopEvent())
|
bus.post(MediaStopEvent())
|
||||||
|
|
||||||
self._player = None
|
self._webtorrent_process = None
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def load(self, resource):
|
def load(self, resource):
|
||||||
|
@ -217,14 +190,14 @@ class MediaWebtorrentPlugin(MediaPlugin):
|
||||||
return self.play(resource)
|
return self.play(resource)
|
||||||
|
|
||||||
def _is_process_alive(self):
|
def _is_process_alive(self):
|
||||||
if not self._player:
|
if not self._webtorrent_process:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.kill(self._player.pid, 0)
|
os.kill(self._webtorrent_process.pid, 0)
|
||||||
return True
|
return True
|
||||||
except OSError:
|
except OSError:
|
||||||
self._player = None
|
self._webtorrent_process = None
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -241,9 +214,8 @@ class MediaWebtorrentPlugin(MediaPlugin):
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return {'state': PlayerState.PLAY.value
|
return {'state': self._media_plugin.status()
|
||||||
if self._player and self._is_process_alive()
|
.get('state', PlayerState.STOP.value)}
|
||||||
else PlayerState.STOP.value}
|
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
Loading…
Reference in a new issue