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):
return 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=') \
or resource.startswith('https://youtube.com/watch?v=')
def _get_resource(self, resource):
"""
@ -482,7 +483,7 @@ class MediaPlugin(Plugin):
@staticmethod
def get_youtube_video_url(url):
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()
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 math
import urllib.parse
from platypush.context import get_bus
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
@ -43,9 +44,9 @@ class MediaOmxplayerPlugin(MediaPlugin):
self._handlers = {e.value: [] for e in PlayerEvent}
@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:
@ -56,6 +57,15 @@ class MediaOmxplayerPlugin(MediaPlugin):
: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:
args += ('--subtitles', subtitles)
@ -75,17 +85,17 @@ class MediaOmxplayerPlugin(MediaPlugin):
try:
from omxplayer import OMXPlayer
self._player = OMXPlayer(resource, args=self.args)
self._init_player_handlers()
except DBusException as e:
self.logger.warning('DBus connection failed: you will probably not ' +
'be able to control the media')
self.logger.exception(e)
self._player.pause()
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()
@action
@ -111,35 +121,52 @@ class MediaOmxplayerPlugin(MediaPlugin):
self._player.quit()
except OMXPlayerDeadError:
pass
self._player = None
finally:
self._player = None
return {'status': 'stop'}
@action
def voldown(self):
""" Volume down by 10% """
def get_volume(self) -> float:
"""
:return: The player volume in percentage [0, 100].
"""
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()
@action
def volup(self):
""" Volume up by 10% """
def volup(self, step=10.0):
"""
Increase the volume.
:param step: Volume increase step between 0 and 100 (default: 10%).
:type step: float
"""
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()
@action
def back(self, offset=60):
""" Back by (default: 60) seconds """
def back(self, offset=30):
""" Back by (default: 30) seconds """
if self._player:
self._player.seek(-offset)
return self.status()
@action
def forward(self, offset=60):
""" Forward by (default: 60) seconds """
def forward(self, offset=30):
""" Forward by (default: 30) seconds """
if self._player:
self._player.seek(offset)
return self.status()
@ -216,30 +243,27 @@ class MediaOmxplayerPlugin(MediaPlugin):
return self.status()
@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
:type relative_position: int
:param position: Number of seconds from the start
:type position: float
"""
if self._player:
self._player.seek(relative_position)
self._player.set_position(position)
return self.status()
@action
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
:type position: int
:type position: float
"""
return self.seek(position)
if self._player:
self._player.seek(position)
return self.status()
@action
def set_volume(self, volume):
@ -247,7 +271,7 @@ class MediaOmxplayerPlugin(MediaPlugin):
Set the volume
:param volume: Volume value between 0 and 100
:type volume: int
:type volume: float
"""
if self._player:
@ -314,7 +338,7 @@ class MediaOmxplayerPlugin(MediaPlugin):
'state': state,
'title': urllib.parse.unquote(self._player.get_source()).split('/')[-1] if self._player.get_source().startswith('file://') else None,
'url': self._player.get_source(),
'volume': self._player.volume() * 100,
'volume': self.get_volume(),
'volume_max': 100,
}
@ -349,14 +373,14 @@ class MediaOmxplayerPlugin(MediaPlugin):
return _f
def on_stop(self):
def _f(player):
def _f(player, *_, **__):
self._post_event(MediaStopEvent)
for callback in self._handlers[PlayerEvent.STOP.value]:
callback()
return _f
def on_seek(self):
def _f(player):
def _f(player, *_, **__):
self._post_event(MediaSeekEvent, position=player.position())
return _f
@ -367,7 +391,9 @@ class MediaOmxplayerPlugin(MediaPlugin):
self._player.playEvent += self.on_play()
self._player.pauseEvent += self.on_pause()
self._player.stopEvent += self.on_stop()
self._player.exitEvent += self.on_stop()
self._player.positionEvent += self.on_seek()
self._player.seekEvent += self.on_seek()
# vim:sw=4:ts=4:et:

View File

@ -9,17 +9,21 @@ class ShellPlugin(Plugin):
"""
@action
def exec(self, cmd, ignore_errors=False):
def exec(self, cmd, background=False, ignore_errors=False):
"""
Execute a command.
:param cmd: Command to execute
: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)
: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:
return subprocess.check_output(
cmd, stderr=subprocess.STDOUT, shell=True).decode('utf-8')