import subprocess
import time
import os
import signal
import logging
import threading
import math
from decorator import decorator
from glob import glob
from dbus import DBusException, Int64, ObjectPath
import omxplayer.bus_finder
from omxplayer.dbus_connection import DBusConnection, \
DBusConnectionError
#### CONSTANTS ####
RETRY_DELAY = 0.05
#### FILE GLOBAL OBJECTS ####
logger = logging.getLogger(__name__)
#### CLASSES ####
[docs]class FileNotFoundError(Exception):
pass
[docs]class FileCleaner(object):
def __init__(self, path):
self.path = path
[docs] def clean(self):
for file in glob(self.path):
os.remove(file)
[docs]class OMXPlayer(object):
"""
OMXPlayer controller
This works by speaking to OMXPlayer over DBus sending messages.
Args:
filename (str): Path to the file you wish to play
args (list): used to pass option parameters to omxplayer.
multiple argument example:
# OMXPlayer('path.mp4', args=['--no-osd', '--no-keys', '-b'])
info: https://github.com/popcornmix/omxplayer#synopsis
"""
def __init__(self, filename,
args=[],
bus_address_finder=None,
Connection=None,
cleaner=FileCleaner('/tmp/*omxplayer*')):
logger.debug('Instantiating OMXPlayer')
self.cleaner = cleaner
self._clean_old_files()
self.args = args
if not bus_address_finder:
bus_address_finder = omxplayer.bus_finder.BusFinder()
if not Connection:
Connection = DBusConnection
self.tries = 0
self._is_playing = True
self._filename = filename
self._process = self._setup_omxplayer_process(filename)
self.connection = self._setup_dbus_connection(Connection,
bus_address_finder)
time.sleep(0.5) # Wait for the DBus interface to be initialised
self.pause()
def _clean_old_files(self):
logger.debug("Removing old OMXPlayer pid files etc")
self.cleaner.clean()
def _run_omxplayer(self, filename, devnull):
def on_exit():
logger.info("OMXPlayer process is dead, all DBus calls from here "
"will fail")
def monitor(process, on_exit):
process.wait()
on_exit()
command = ['omxplayer'] + self.args + [filename]
logger.debug("Opening omxplayer with the command: %s" % command)
process = subprocess.Popen(command,
stdout=devnull,
preexec_fn=os.setsid)
self._process_monitor = threading.Thread(target=monitor, args=(process,
on_exit))
self._process_monitor.start()
return process
def _setup_omxplayer_process(self, filename):
logger.debug('Setting up OMXPlayer process')
if not os.path.isfile(filename):
raise FileNotFoundError("Could not find: {}".format(filename))
with open(os.devnull, 'w') as devnull:
process = self._run_omxplayer(filename, devnull)
logger.debug('Process opened with PID %s' % process.pid)
return process
def _setup_dbus_connection(self, Connection, bus_address_finder):
logger.debug('Trying to connect to OMXPlayer via DBus')
while self.tries < 50:
logger.debug('DBus connect attempt: {}'.format(self.tries))
try:
connection = Connection(bus_address_finder.get_address())
logger.debug(
'Connected to OMXPlayer at DBus address: %s' % connection)
return connection
except (DBusConnectionError, IOError):
logger.debug('Failed to connect to OMXPlayer DBus address')
self.tries += 1
time.sleep(RETRY_DELAY)
raise SystemError('DBus cannot connect to the OMXPlayer process')
""" Utilities """
def _check_player_is_active(fn):
# wraps is a decorator that improves debugging wrapped methods
def wrapped(fun, self, *args, **kwargs):
logger.debug('Checking if process is still alive')
# poll determines whether the process has terminated,
# if it hasn't it returns None.
if self._process.poll() is None:
logger.debug('OMXPlayer is running, so execute %s' %
fn.__name__)
return fn(self, *args, **kwargs)
else:
logger.info('Process is no longer alive, can\'t run command')
return decorator(wrapped, fn)
""" ROOT INTERFACE METHODS """
@_check_player_is_active
[docs] def can_quit(self):
"""
Returns:
bool: """
return bool(self._get_root_interface().CanQuit())
@_check_player_is_active
[docs] def can_set_fullscreen(self):
"""
Returns:
bool: """
return bool(self._get_root_interface().CanSetFullscreen())
@_check_player_is_active
[docs] def identity(self):
"""
Get the ID of the media player
Returns:
bool:
"""
return str(self._get_root_interface().Identity())
""" PLAYER INTERFACE PROPERTIES """
@_check_player_is_active
[docs] def can_go_next(self):
"""
Returns:
bool: Whether the player can move to the next item in the playlist
"""
return bool(self._get_properties_interface().CanGoNext())
@_check_player_is_active
[docs] def can_go_previous(self):
"""
Returns:
bool: Whether the player can move to the previous item in the
playlist
"""
return bool(self._get_properties_interface().CanGoPrevious())
@_check_player_is_active
[docs] def can_seek(self):
"""
Returns:
bool: Whether the player can seek """
return bool(self._get_properties_interface().CanSeek())
@_check_player_is_active
[docs] def can_control(self):
"""
Returns:
bool: """
return bool(self._get_properties_interface().CanControl())
@_check_player_is_active
[docs] def can_play(self):
"""
Returns:
bool: """
return bool(self._get_properties_interface().CanPlay())
@_check_player_is_active
[docs] def can_pause(self):
"""
Returns:
bool: """
return bool(self._get_properties_interface().CanPause())
@_check_player_is_active
[docs] def playback_status(self):
"""
Returns:
str: One of ("Playing" | "Paused" | "Stopped")
"""
return str(self._get_properties_interface().PlaybackStatus())
@_check_player_is_active
[docs] def volume(self):
"""
Returns:
volume (float): Volume in millibels
"""
vol = float(self._get_properties_interface().Volume())
return 2000 * math.log(vol, 10)
@_check_player_is_active
[docs] def set_volume(self, volume):
"""
Args:
volume (float): Volume in millibels
"""
return float(self._get_properties_interface().Volume(
10**(volume/2000.0)
))
@_check_player_is_active
[docs] def mute(self):
"""
Turns mute on, if the audio is already muted, then this does not do
anything
Returns:
None:
"""
self._get_properties_interface().Mute()
@_check_player_is_active
[docs] def unmute(self):
"""
Unmutes the video, if the audio is already unmuted, then this does
not do anything
Returns:
None:
"""
self._get_properties_interface().Unmute()
@_check_player_is_active
[docs] def position(self):
"""
Returns:
float: The position in seconds
"""
return self._get_properties_interface().Position() / (1000 * 1000.0)
@_check_player_is_active
def _duration_us(self):
"""
Returns:
long: The duration in microseconds
"""
return long(self._get_properties_interface().Duration())
@_check_player_is_active
[docs] def duration(self):
"""
Returns:
float: The duration in seconds
"""
return self._duration_us() / (1000 * 1000.0)
@_check_player_is_active
[docs] def minimum_rate(self):
"""
Returns:
str: The minimum playback rate
"""
return float(self._get_properties_interface().MinimumRate())
@_check_player_is_active
[docs] def maximum_rate(self):
"""
Returns:
str: The maximum playback rate
"""
return float(self._get_properties_interface().MaximumRate())
""" PLAYER INTERFACE METHODS """
@_check_player_is_active
[docs] def pause(self):
"""
Return:
None:
"""
self._get_player_interface().Pause()
@_check_player_is_active
[docs] def play_pause(self):
"""
Return:
None:
"""
self._get_player_interface().PlayPause()
self._is_playing = not self._is_playing
@_check_player_is_active
[docs] def stop(self):
self._get_player_interface().Stop()
@_check_player_is_active
[docs] def seek(self, relative_position):
"""
Args:
relative_position (float): The position in seconds to seek to.
"""
self._get_player_interface().Seek(Int64(relative_position))
@_check_player_is_active
[docs] def set_position(self, position):
"""
Args:
position (float): The position in seconds.
"""
self._get_player_interface().SetPosition(ObjectPath("/not/used"), Int64(position*1000*1000))
@_check_player_is_active
[docs] def list_video(self):
"""
Returns:
[str]: A list of all known video streams, each item is in the
format: ``<index>:<language>:<name>:<codec>:<active>``
"""
return map(str, self._get_player_interface().ListVideo())
@_check_player_is_active
[docs] def list_audio(self):
"""
Returns:
[str]: A list of all known audio streams, each item is in the
format: ``<index>:<language>:<name>:<codec>:<active>``
"""
return map(str, self._get_player_interface().ListAudio())
@_check_player_is_active
[docs] def list_subtitles(self):
"""
Returns:
[str]: A list of all known subtitles, each item is in the
format: ``<index>:<language>:<name>:<codec>:<active>``
"""
return map(str, self._get_player_interface().ListSubtitles())
@_check_player_is_active
[docs] def action(self, code):
"""
Executes a keyboard command via a code
Args:
code (int): The key code you wish to emulate
refer to ``keys.py`` for the possible keys
Returns:
None:
"""
self._get_player_interface().Action(code)
@_check_player_is_active
[docs] def is_playing(self):
"""
Returns:
None:
"""
self._is_playing = (self.playback_status() == "Playing")
logger.info("Playing?: %s" % self._is_playing)
return self._is_playing
@_check_player_is_active
[docs] def play_sync(self):
"""
Returns:
None:
"""
self.play()
logger.info("Playing synchronously")
try:
time.sleep(0.05)
logger.debug("Wait for playing to start")
while self.is_playing():
time.sleep(0.05)
except DBusException:
logger.error(
"Cannot play synchronously any longer as DBus calls timed out."
)
@_check_player_is_active
[docs] def play(self):
"""
Returns:
None:
"""
if not self.is_playing():
self.play_pause()
def _get_root_interface(self):
return self.connection.root_interface
def _get_player_interface(self):
return self.connection.player_interface
def _get_properties_interface(self):
return self.connection.properties_interface
[docs] def quit(self):
logger.debug('Quitting OMXPlayer')
try:
os.killpg(self._process.pid, signal.SIGINT)
self._process.wait()
self._process_monitor.join()
logger.debug('SIGINT Sent to pid: %s' % self._process.pid)
except OSError:
logger.error('Could not find the process to kill')
@_check_player_is_active
[docs] def get_filename(self):
"""
Returns:
str: filename currently playing
"""
return self._filename
# MediaPlayer2.Player types:
# Track_Id: DBus ID of track
# Plaback_Rate: Multiplier for playback speed (1 = normal speed)
# Volume: 0--1, 0 is muted and 1 is full volume
# Time_In_Us: Time in microseconds
# Playback_Status: Playing|Paused|Stopped
# Loop_Status: None|Track|Playlist