Merge branch 'master' into vuejs

This commit is contained in:
Fabio Manganiello 2021-02-04 01:57:36 +01:00
commit 2abfb2964c
4 changed files with 69 additions and 200 deletions

View file

@ -162,7 +162,8 @@ class MediaPlugin(Plugin):
def _is_youtube_resource(resource): def _is_youtube_resource(resource):
return resource.startswith('youtube:') \ return resource.startswith('youtube:') \
or resource.startswith('https://youtu.be/') \ or resource.startswith('https://youtu.be/') \
or resource.startswith('https://www.youtube.com/watch?v=') or resource.startswith('https://www.youtube.com/watch?v=') \
or resource.startswith('https://youtube.com/watch?v=')
def _get_resource(self, resource): def _get_resource(self, resource):
""" """
@ -482,7 +483,7 @@ class MediaPlugin(Plugin):
@staticmethod @staticmethod
def get_youtube_video_url(url): def get_youtube_video_url(url):
youtube_dl = subprocess.Popen(['youtube-dl', '-f', 'best', '-g', url], stdout=subprocess.PIPE) youtube_dl = subprocess.Popen(['youtube-dl', '-f', 'best', '-g', url], stdout=subprocess.PIPE)
url = youtube_dl.communicate()[0].decode() url = youtube_dl.communicate()[0].decode().strip()
youtube_dl.wait() youtube_dl.wait()
return url return url

View file

@ -1,162 +0,0 @@
import re
import subprocess
from platypush.context import get_plugin
from platypush.plugins.media import PlayerState
from platypush.plugins import Plugin, action
class MediaCtrlPlugin(Plugin):
"""
Wrapper plugin to control audio and video media.
Examples of supported URL types:
- file:///media/movies/Movie.mp4 [requires media plugin enabled]
- youtube:video:poAk9XgK7Cs [requires media plugin+youtube-dl]
- magnet:?torrent_magnet [requires torrentcast]
- spotify:track:track_id [leverages plugins.music.mpd]
"""
_supported_plugins = {
'music.mpd', 'media', 'video.torrentcast'
}
def __init__(self, torrentcast_port=9090, *args, **kwargs):
super().__init__()
self.torrentcast_port = torrentcast_port
self.url = None
self.plugin = None
@classmethod
def _get_type_and_resource_by_url(cls, url):
# MPD/Mopidy media (TODO support more mopidy types)
m = re.search('^https://open.spotify.com/([^?]+)', url)
if m: url = 'spotify:{}'.format(m.group(1).replace('/', ':'))
if url.startswith('spotify:') \
or url.startswith('tunein:') \
or url.startswith('soundcloud:'):
return ('mpd', url)
# YouTube video
m = re.match('youtube:video:(.*)', url)
if m: url = 'https://www.youtube.com/watch?v={}'.format(m.group(1))
if url.startswith('https://www.youtube.com/watch?v='):
return ('youtube:video', url)
# Local media
if url.startswith('file://'):
m = re.match('^file://(.*)', url)
return ('file', m.group(1))
# URL to a .torrent media or Magnet link
if url.startswith('magnet:') or url.endswith('.torrent'):
return ('torrent', url)
raise RuntimeError('Unknown URL type: {}'.format(url))
def _get_playing_plugin(self):
if self.plugin:
status = self.plugin.status()
if status['state'] == PlayerState.PLAY.value or state['state'] == PlayerState.PAUSE.value:
return self.plugin
for plugin in self._supported_plugins:
try:
player = get_plugin(plugin)
except:
try:
player = get_plugin(plugin, reload=True)
except:
continue
status = player.status().output
if status['state'] == PlayerState.PLAY.value or status['state'] == PlayerState.PAUSE.value:
return player
return None
@action
def play(self, url):
(type, resource) = self._get_type_and_resource_by_url(url)
plugin_name = None
if type == 'mpd':
plugin_name = 'music.mpd'
elif type == 'youtube:video' or type == 'file':
plugin_name = 'media'
elif type == 'torrent':
plugin_name = 'video.torrentcast'
if not plugin_name:
raise RuntimeError("Unsupported type '{}'".format(type))
try:
self.plugin = get_plugin(plugin_name)
except:
self.plugin = get_plugin(plugin_name, reload=True)
self.url = resource
return self.plugin.play(resource)
@action
def pause(self):
plugin = self._get_playing_plugin()
if plugin: return plugin.pause()
@action
def stop(self):
plugin = self._get_playing_plugin()
if plugin:
ret = plugin.stop()
self.plugin = None
return ret
@action
def voldown(self):
plugin = self._get_playing_plugin()
if plugin: return plugin.voldown()
@action
def volup(self):
plugin = self._get_playing_plugin()
if plugin: return plugin.volup()
@action
def back(self):
plugin = self._get_playing_plugin()
if plugin: return plugin.back()
@action
def forward(self):
plugin = self._get_playing_plugin()
if plugin: return plugin.forward()
@action
def next(self):
plugin = self._get_playing_plugin()
if plugin: return plugin.next()
@action
def previous(self):
plugin = self._get_playing_plugin()
if plugin: return plugin.previous()
@action
def status(self):
plugin = self._get_playing_plugin()
if plugin: return plugin.status()
# vim:sw=4:ts=4:et:

View file

@ -1,9 +1,10 @@
import enum import enum
import math
import urllib.parse import urllib.parse
from platypush.context import get_bus from platypush.context import get_bus
from platypush.plugins.media import MediaPlugin, PlayerState from platypush.plugins.media import MediaPlugin, PlayerState
from platypush.message.event.media import MediaPlayEvent, MediaPauseEvent, MediaStopEvent, MediaSeekEvent from platypush.message.event.media import MediaPlayEvent, MediaPauseEvent, MediaStopEvent, MediaSeekEvent, MediaPlayRequestEvent
from platypush.plugins import action from platypush.plugins import action
@ -43,9 +44,9 @@ class MediaOmxplayerPlugin(MediaPlugin):
self._handlers = {e.value: [] for e in PlayerEvent} self._handlers = {e.value: [] for e in PlayerEvent}
@action @action
def play(self, resource, subtitles=None, *args, **kwargs): def play(self, resource=None, subtitles=None, *args, **kwargs):
""" """
Play a resource. Play or resume playing a resource.
:param resource: Resource to play. Supported types: :param resource: Resource to play. Supported types:
@ -56,6 +57,15 @@ class MediaOmxplayerPlugin(MediaPlugin):
:param subtitles: Subtitles file :param subtitles: Subtitles file
""" """
if not resource:
if not self._player:
self.logger.warning('No OMXPlayer instances running')
else:
self._player.play()
return self.status()
self._post_event(MediaPlayRequestEvent, resource=resource)
if subtitles: if subtitles:
args += ('--subtitles', subtitles) args += ('--subtitles', subtitles)
@ -75,17 +85,17 @@ class MediaOmxplayerPlugin(MediaPlugin):
try: try:
from omxplayer import OMXPlayer from omxplayer import OMXPlayer
self._player = OMXPlayer(resource, args=self.args) self._player = OMXPlayer(resource, args=self.args)
self._init_player_handlers()
except DBusException as e: except DBusException as e:
self.logger.warning('DBus connection failed: you will probably not ' + self.logger.warning('DBus connection failed: you will probably not ' +
'be able to control the media') 'be able to control the media')
self.logger.exception(e) self.logger.exception(e)
self._player.pause()
if self.volume: if self.volume:
self.set_volume(volume=self.volume) self.set_volume(self.volume)
self._post_event(MediaPlayEvent, resource=resource)
self._init_player_handlers()
self._player.play()
return self.status() return self.status()
@action @action
@ -111,35 +121,52 @@ class MediaOmxplayerPlugin(MediaPlugin):
self._player.quit() self._player.quit()
except OMXPlayerDeadError: except OMXPlayerDeadError:
pass pass
finally:
self._player = None self._player = None
return {'status': 'stop'} return {'status': 'stop'}
@action def get_volume(self) -> float:
def voldown(self): """
""" Volume down by 10% """ :return: The player volume in percentage [0, 100].
"""
if self._player: if self._player:
self._player.set_volume(max(0, self._player.volume()-0.1)) return self._player.volume()*100
@action
def voldown(self, step=10.0):
"""
Decrease the volume.
:param step: Volume decrease step between 0 and 100 (default: 10%).
:type step: float
"""
if self._player:
self.set_volume(max(0, self.get_volume()-step))
return self.status() return self.status()
@action @action
def volup(self): def volup(self, step=10.0):
""" Volume up by 10% """ """
Increase the volume.
:param step: Volume increase step between 0 and 100 (default: 10%).
:type step: float
"""
if self._player: if self._player:
self._player.set_volume(min(1, self._player.volume()+0.1)) self.set_volume(min(100, self.get_volume()+step))
return self.status() return self.status()
@action @action
def back(self, offset=60): def back(self, offset=30):
""" Back by (default: 60) seconds """ """ Back by (default: 30) seconds """
if self._player: if self._player:
self._player.seek(-offset) self._player.seek(-offset)
return self.status() return self.status()
@action @action
def forward(self, offset=60): def forward(self, offset=30):
""" Forward by (default: 60) seconds """ """ Forward by (default: 30) seconds """
if self._player: if self._player:
self._player.seek(offset) self._player.seek(offset)
return self.status() return self.status()
@ -216,30 +243,27 @@ class MediaOmxplayerPlugin(MediaPlugin):
return self.status() return self.status()
@action @action
def seek(self, relative_position): def seek(self, position):
""" """
Seek backward/forward by the specified number of seconds Seek to the specified number of seconds from the start.
:param relative_position: Number of seconds relative to the current cursor :param position: Number of seconds from the start
:type relative_position: int :type position: float
""" """
if self._player: if self._player:
self._player.seek(relative_position) self._player.set_position(position)
return self.status() return self.status()
@action @action
def set_position(self, position): def set_position(self, position):
""" """
Seek backward/forward to the specified absolute position Seek to the specified number of seconds from the start (same as :meth:`.seek`).
:param position: Number of seconds from the start :param position: Number of seconds from the start
:type position: int :type position: float
""" """
return self.seek(position)
if self._player:
self._player.seek(position)
return self.status()
@action @action
def set_volume(self, volume): def set_volume(self, volume):
@ -247,7 +271,7 @@ class MediaOmxplayerPlugin(MediaPlugin):
Set the volume Set the volume
:param volume: Volume value between 0 and 100 :param volume: Volume value between 0 and 100
:type volume: int :type volume: float
""" """
if self._player: if self._player:
@ -314,7 +338,7 @@ class MediaOmxplayerPlugin(MediaPlugin):
'state': state, 'state': state,
'title': urllib.parse.unquote(self._player.get_source()).split('/')[-1] if self._player.get_source().startswith('file://') else None, 'title': urllib.parse.unquote(self._player.get_source()).split('/')[-1] if self._player.get_source().startswith('file://') else None,
'url': self._player.get_source(), 'url': self._player.get_source(),
'volume': self._player.volume() * 100, 'volume': self.get_volume(),
'volume_max': 100, 'volume_max': 100,
} }
@ -349,14 +373,14 @@ class MediaOmxplayerPlugin(MediaPlugin):
return _f return _f
def on_stop(self): def on_stop(self):
def _f(player): def _f(player, *_, **__):
self._post_event(MediaStopEvent) self._post_event(MediaStopEvent)
for callback in self._handlers[PlayerEvent.STOP.value]: for callback in self._handlers[PlayerEvent.STOP.value]:
callback() callback()
return _f return _f
def on_seek(self): def on_seek(self):
def _f(player): def _f(player, *_, **__):
self._post_event(MediaSeekEvent, position=player.position()) self._post_event(MediaSeekEvent, position=player.position())
return _f return _f
@ -367,7 +391,9 @@ class MediaOmxplayerPlugin(MediaPlugin):
self._player.playEvent += self.on_play() self._player.playEvent += self.on_play()
self._player.pauseEvent += self.on_pause() self._player.pauseEvent += self.on_pause()
self._player.stopEvent += self.on_stop() self._player.stopEvent += self.on_stop()
self._player.exitEvent += self.on_stop()
self._player.positionEvent += self.on_seek() self._player.positionEvent += self.on_seek()
self._player.seekEvent += self.on_seek()
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et:

View file

@ -9,17 +9,21 @@ class ShellPlugin(Plugin):
""" """
@action @action
def exec(self, cmd, ignore_errors=False): def exec(self, cmd, background=False, ignore_errors=False):
""" """
Execute a command. Execute a command.
:param cmd: Command to execute :param cmd: Command to execute
:type cmd: str :type cmd: str
:param background: If set to True, execute the process in the background, otherwise wait for the process termination and return its output (deafult: False).
:param ignore_errors: If set, then any errors in the command execution will be ignored. Otherwise a RuntimeError will be thrown (default value: False) :param ignore_errors: If set, then any errors in the command execution will be ignored. Otherwise a RuntimeError will be thrown (default value: False)
:returns: A response object where the ``output`` field will contain the command output as a string, and the ``errors`` field will contain whatever was sent to stderr. :returns: A response object where the ``output`` field will contain the command output as a string, and the ``errors`` field will contain whatever was sent to stderr.
""" """
if background:
subprocess.Popen(cmd, shell=True)
try: try:
return subprocess.check_output( return subprocess.check_output(
cmd, stderr=subprocess.STDOUT, shell=True).decode('utf-8') cmd, stderr=subprocess.STDOUT, shell=True).decode('utf-8')