commit 3c6c72fe551fc79917436587434fbfa6142252e9 Author: Mediacore Developer Date: Mon Sep 24 10:42:16 2018 +0000 Initial commit of current code base diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1dc31af --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +deb_dist/ +*.pyc diff --git a/BUILDING.txt b/BUILDING.txt new file mode 100644 index 0000000..896bfb5 --- /dev/null +++ b/BUILDING.txt @@ -0,0 +1,36 @@ +Package: khmedia + +PACKAGE BUILDING: + +For building the debian package, the following prerequisities are needed: + python-stdeb + +To start the package building process, execute: + ./setup.py bdist_deb + +after succesful excecution, the package files can be found in the directory + ./deb_dist/ + +INSTALLING LOCALLY: + +For testing purposes the package can be installed by executing: + ./setup.py install + +However, because packages installed like that can not be easily removed, +it is recommended for non-dedicated development stations to build the debian package, +and install that package. It can subsequently be easily removed by doing + apt-get remove + +TESTING AND DEVELOPING LOCALLY: +If the required packages are installed, this package can be tested for development without installing. +The scripts in the root dir of the package are intended to start the applications locally without installing. +The package in the subfolder will be used in that case. + +*NOTE 1: +Dependent packages to need to be installed either though a debian package (recommended), or by issuing + ./setup.py install +in their source folder. + +*NOTE 2: +If you install manually, you will also need to update the gtk icon cache with the following command + sudo gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor \ No newline at end of file diff --git a/VERSION.py b/VERSION.py new file mode 100644 index 0000000..5ecbbdc --- /dev/null +++ b/VERSION.py @@ -0,0 +1,7 @@ + +major=1 # Major version +minor=0 # Minor version +micro=2 # Bugfix version + +# code to generate version string from above data +version_string = "{0}.{1}.{2}".format(major,minor,micro) diff --git a/debian/postinst b/debian/postinst new file mode 100755 index 0000000..1f861a5 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,40 @@ +#!/bin/sh +# postinst script for piio-server +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + configure) + update-rc.d mediaserver defaults + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/debian/prerm b/debian/prerm new file mode 100644 index 0000000..73f3269 --- /dev/null +++ b/debian/prerm @@ -0,0 +1,39 @@ +#!/bin/sh +# prerm script for piio-server +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `remove' +# * `upgrade' +# * `failed-upgrade' +# * `remove' `in-favour' +# * `deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + remove|upgrade|deconfigure) + update-rc.d -f mediaserver remove + ;; + + failed-upgrade) + ;; + + *) + echo "prerm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/etc/dbus-1/system.d/nl.miqra.MediaServer.conf b/etc/dbus-1/system.d/nl.miqra.MediaServer.conf new file mode 100644 index 0000000..3518add --- /dev/null +++ b/etc/dbus-1/system.d/nl.miqra.MediaServer.conf @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/etc/init.d/mediaserver b/etc/init.d/mediaserver new file mode 100755 index 0000000..ea6968e --- /dev/null +++ b/etc/init.d/mediaserver @@ -0,0 +1,175 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: mediacore-mediaserver +# Required-Start: $syslog $dbus +# Required-Stop: $syslog $dbus +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Example initscript +# Description: This file should be used to construct scripts to be +# placed in /etc/init.d. +### END INIT INFO + +# Author: Miqra Engineering <@miqra.nl> +# +# Please remove the "Author" lines above and replace them +# with your own name if you copy and modify this script. + +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/sbin:/bin:/usr/sbin:/usr/bin +DESC="Mediacore media player server" +NAME=mediaserver +DAEMON=/usr/sbin/$NAME +DAEMON_ARGS="" +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/usr/local/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh + +VERBOSE=yes + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.2-14) to ensure that this file is present +# and status_of_proc is working. +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ + # start background image + fbi -a -noverbose -t 1 -T 1 -d /dev/fb0 /usr/share/mediaserver/background-image.png >/dev/null 2> /dev/null & + + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + + start-stop-daemon --start --quiet --background -m --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --background -m --pidfile $PIDFILE --exec $DAEMON -- \ + $DAEMON_ARGS \ + || return 2 + # Add code here, if necessary, that waits for the process to be ready + # to handle requests from services started subsequently which depend + # on this one. As a last resort, sleep for some time. +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # kill background image (fbi) + killall fbi + + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --signal TERM --pidfile $PIDFILE + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE + return "$RETVAL" +} + +# +# Function that sends a SIGHUP to the daemon/service +# +do_reload() { + # + # If the daemon can reload its configuration without + # restarting (for example, when it is sent a SIGHUP), + # then implement that here. + # + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --exec $DAEMON + return 0 +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + + status_of_proc "$DAEMON" "$NAME" >/dev/null + if [ $? != 0 ]; then + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + else + [ "$VERBOSE" != no ] && log_end_msg 1 + [ "$VERBOSE" != no ] && echo "$NAME already running" + fi + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + #reload|force-reload) + # + # If do_reload() is not implemented then leave this commented out + # and leave 'force-reload' as an alias for 'restart'. + # + #log_daemon_msg "Reloading $DESC" "$NAME" + #do_reload + #log_end_msg $? + #;; + restart|force-reload) + # + # If the "reload" option is implemented then remove the + # 'force-reload' alias + # + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +: diff --git a/mediacore-mediaserver b/mediacore-mediaserver new file mode 100755 index 0000000..188a82e --- /dev/null +++ b/mediacore-mediaserver @@ -0,0 +1,4 @@ +#!/usr/bin/python +import mediaserver.mediaserver + +mediaserver.mediaserver.Run() \ No newline at end of file diff --git a/mediaserver/PKG_CONFIG.py b/mediaserver/PKG_CONFIG.py new file mode 100644 index 0000000..188b90e --- /dev/null +++ b/mediaserver/PKG_CONFIG.py @@ -0,0 +1,51 @@ +# shared package configuration settings for applications in KHMedia +##### Common ##### + +# folder to store user specific configuration +user_cfg_folder = "~/.khmedia" + +# khmedia config file +config_file = "khmedia_config.xml" + +# khmedia example config resource +example_config_resource = "khmedia_config.example.xml" + +# location of the khsystem_need_config file +khsystem_need_config = "~/.khsystem_config_needed" + +##### Player ##### + +#default folders to look for song files (last in line is the fallback folder that will be created if none are existing) +default_song_folders = ["/usr/share/khmedia/songs","/usr/local/share/khmedia/songs","~/Songs"] + +#default folders to look for background music files (last in line is the fallback folder that will be created if none are existing) +default_music_folders = ["/usr/share/khmedia/music","/usr/local/share/khmedia/music","~/Music"] + +# files that are considered playable music files +music_extensions = [".mp3",".wav",".ogg",".m4b",".m4a"] + +# default volume for song playback (Linear 0.0 <= value <= 1.0 ) +default_song_volume = 0.5 + +# default volume for music playback (Linear 0.0 <= value <= 1.0 ) +default_music_volume = 0.12 + +##### Recorder ##### + +#default folder to store recordings +# You can use the following replacements +# {XDG_DESKTOP_DIR} +# {XDG_DOWNLOAD_DIR} +# {XDG_TEMPLATES_DIR} +# {XDG_PUBLICSHARE_DIR} +# {XDG_DOCUMENTS_DIR} +# {XDG_MUSIC_DIR} +# {XDG_PICTURES_DIR} +# {XDG_VIDEOS_DIR} + +# lowercase text between brackets is replaced by the corresponding text in the +# currently loaded translation table +# e.g. {something} can be replaced by "Something" or "Iets" if that is defined in the +# translation tabled +default_recordings_folder = "{XDG_DESKTOP_DIR}/{dir_recordings}" + diff --git a/mediaserver/__init__.py b/mediaserver/__init__.py new file mode 100644 index 0000000..aee403e --- /dev/null +++ b/mediaserver/__init__.py @@ -0,0 +1,15 @@ +# perform gstreamer imports (python3 style) +import gi +gi.require_version('Gst','1.0') + +from gi.repository import GObject +from gi.repository import Gst + +# now perform once-per-module initializations +GObject.threads_init() +Gst.init(None) + +""" +from mediaserver.basicplayer import BasicPlayer +player = BasicPlayer() +""" \ No newline at end of file diff --git a/mediaserver/audioplayer.py b/mediaserver/audioplayer.py new file mode 100644 index 0000000..ab165a9 --- /dev/null +++ b/mediaserver/audioplayer.py @@ -0,0 +1,68 @@ +# perform gstreamer imports (python3 style) +import gi +gi.require_version('Gst','1.0') + +from gi.repository import GObject + +from threading import Thread +import time + +# import from this module +from basicplayer import BasicPlayer + +class MonitorThread(Thread): + def __init__(self,player): + self.player = player + Thread.__init__(self) + self.daemon = True # as in: don't wait on this thread to quit + self._running = False + + def stop(self): + if self._running: + self._running = False + #self.join(0.5) + + def run(self): + self._running = True + while self._running: + if self.player.player_state == "PLAYING" and self.player.playtime > 0: + position = self.player.position() + if (position - self.player.startpos) >= self.player.playtime: + self.player._finished() + if self._running and time is not None: + time.sleep(0.2) + pass + +class AudioPlayer(BasicPlayer): + + def __init__(self): + BasicPlayer.__init__(self) + # setup monitor thread + self.monitor = MonitorThread(self) + self.monitor.start() + self.playtime = -1 + + def __del__(self): + self.monitor.stop() + + def playfor(self,duration): + if duration > 0: # not much point in wasting system resources otherwise + self.startpos = self.position() + self.playtime = duration +# self.monitor.start() + BasicPlayer.play(self) + elif duration < 0: # same as normal play + self.play() + + def play(self): + if self.player_state != "PAUSED": + self.playtime = -1; + BasicPlayer.play(self) + + def stop(self): + self.playtime = -1; + BasicPlayer.stop(self) + #self.monitor.stop() + +GObject.type_register(AudioPlayer) + \ No newline at end of file diff --git a/mediaserver/basicplayer.py b/mediaserver/basicplayer.py new file mode 100644 index 0000000..8b53eaf --- /dev/null +++ b/mediaserver/basicplayer.py @@ -0,0 +1,227 @@ +# License for this source file +# +# Copyright (c) 2014 Miqra Engineering +# + +import sys, os +# perform gstreamer imports (python3 style) +import gi +gi.require_version('Gst','1.0') + +from gi.repository import GObject +from gi.repository import Gst + + +class BasicPlayer(GObject.GObject): + __gsignals__ = { + 'playback-ready' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, # parameters: source_file tag_dict + (GObject.TYPE_STRING, GObject.TYPE_PYOBJECT)), + 'playback-playing' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, # parameters: None + ()), + 'playback-stopped' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, # parameters: None + ()), + 'playback-paused' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, # parameters: None + ()), + 'playback-finished' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, # parameters: None + ()), + 'playback-error' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, # parameters: player_state error debug + (GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING,)), + 'volume-changed' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, # parameters: volume + (GObject.TYPE_FLOAT,)), + 'playback-buffering' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, # parameters: volume + (GObject.TYPE_INT,)), + } + + __gproperties__ = { + + 'volume' : (GObject.TYPE_FLOAT, + 'Volume', + 'Volume', + 0, + 1, + 1, + GObject.PARAM_READWRITE), + } + + _running = False + tags = {} + + source = None + + def __init__(self): + GObject.GObject.__init__(self) + # setup gstreamer + self.pipeline_state = Gst.State.NULL; + self.prepare_gstreamer() + + self.buffering = False + + def __del__(self): + self.stop_gstreamer() + + def do_get_property(self, property): + if property.name == 'volume': + return self.pipeline.get_property('volume') + else: + raise AttributeError, 'unknown property %s' % property.name + + def do_set_property(self, property, value): + if property.name == 'volume': + volume = clamp(value,0.0,1.0) + self.pipeline.set_property('volume', volume) + #print "Set volume to {0}, got {1}".format(volume,self.pipeline.get_property('volume')) + self.emit('volume-changed',volume) + else: + raise AttributeError, 'unknown property %s' % property.name + + def play(self): + # start playback ;) + if self.player_state in ["READY","PAUSED",]: + self.player_state = "PLAYING" + self.pipeline.set_state(Gst.State.PLAYING) + self.emit('playback-playing') + + def load(self,file): + self.source = "file://" + file + #print "Attempting to load: '{0}'".format(file) + self.pipeline.set_state(Gst.State.NULL); + self.pipeline.set_property("uri", self.source) + self.pipeline.set_state(Gst.State.PAUSED); + + self.player_state = "LOADING" + self.tags.clear() + + def load_uri(self,uri): + self.source = uri + #print "Attempting to load: '{0}'".format(file) + self.pipeline.set_state(Gst.State.NULL); + self.pipeline.set_property("uri", self.source) + self.pipeline.set_state(Gst.State.PAUSED); + + self.player_state = "LOADING" + self.tags.clear() + + + def stop(self): + if self.player_state in ["PLAYING","PAUSED",]: + self._running = False + self.pipeline.set_state(Gst.State.READY) + self.player_state = "READY" + self.emit('playback-stopped') + + def pause(self): + # cannot pause/unpause if we're waiting for buffer + if self.player_state in ["PLAYING",] and not self.buffering: + self.player_state = "PAUSED" + self.pipeline.set_state(Gst.State.PAUSED) + self.emit('playback-paused') + + def _finished(self): + self._running = False + self.pipeline.set_state(Gst.State.READY) + self.seek(0) + self.player_state = "READY" + self.emit('playback-finished') + + def prepare_gstreamer(self): + # bin containing the recorder stuff + + self.pipeline = Gst.ElementFactory.make("playbin", None) + videosink = Gst.ElementFactory.make("eglglessink", None) + alsasink = Gst.ElementFactory.make("alsasink", None) + + # set output device + #devicename = self.config["Devices.Output"].getStr('name') + #if common.check_alsadev(devicename): + # alsasink.set_property('device', devicename) + + self.pipeline.set_property("video-sink", videosink) + self.pipeline.set_property("audio-sink", alsasink) + + # connect the bus listener to the message function + self.bus = self.pipeline.get_bus() + self.bus.add_signal_watch() + self.bus.connect("message", self.on_message) + + self.player_state = "NONE" + + def stop_gstreamer(self): + try: + self.pipeline.get_bus().disconnect(self.busconnection) + self.pipeline.get_bus().remove_signal_watch() + self.pipeline.set_state(Gst.State.NULL) + except GObject.GError, e: + self.set_sensitive(True) + + def seek(self,seconds): + self.pipeline.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, seconds* Gst.SECOND) + + def duration(self): + result = self.pipeline.query_duration(Gst.Format.TIME) + if result is not None and result[0]: + return float(result[1]) / Gst.SECOND + else: + return -1; + + def position(self): + result = self.pipeline.query_position(Gst.Format.TIME) + if result is not None and result[0]: + return float(result[1]) / Gst.SECOND + else: + return -1; + + def on_message(self, bus, message): + t = message.type + + # detect end of stream, and + if t == Gst.MessageType.EOS: + self._finished() + elif t == Gst.MessageType.ERROR: + self.pipeline.set_state(Gst.State.NULL) + err, debug = message.parse_error() + #sys.stderr.write("Error: {0}\nDebug: {1}".format(err,debug)) + self.pipeline.set_state(Gst.State.NULL) + self._running = False + self.emit('playback-finished') + self.emit('playback-error', self.player_state, err, debug) + self.player_state = "NONE" + elif t == Gst.MessageType.TAG: + tags = message.parse_tag(); + for i in range(0,tags.n_tags()): + key = tags.nth_tag_name(i) + val = tags.get_value_index(key,0) + self.tags[key] = val; + elif t == Gst.MessageType.ASYNC_DONE: + if message.src == self.pipeline: + pass + elif t == Gst.MessageType.STREAM_STATUS: + (status,owner) = message.parse_stream_status() +# print "Stream status: {0} (by {1})\n".format(status,owner) + pass + elif t == Gst.MessageType.BUFFERING: + pct = message.parse_buffering() + print "Buffering: {0}%".format(pct) + self.emit('playback-buffering',pct) + if pct != 100: + if self.pipeline_state == Gst.State.PLAYING: + self.pipeline.set_state(Gst.State.PAUSED) + self.buffering = True + elif pct == 100: + if self.player_state == "PLAYING": + self.pipeline.set_state(Gst.State.PLAYING) + self.buffering = False + elif t == Gst.MessageType.DURATION_CHANGED: +# print "Stream duration changed: {0}\n".format(float(self.pipeline.query_duration(Gst.Format.TIME)[1])/Gst.SECOND) + pass + elif t == Gst.MessageType.STATE_CHANGED: + if message.src == self.pipeline: + (old,new,pending) = message.parse_state_changed() + self.pipeline_state = new +# print "State changed from '{0}' to '{1}' pending '{2}'\n".format(old,new,pending) + if old == Gst.State.READY and new == Gst.State.PAUSED and self.player_state == "LOADING": + self.pipeline.set_state(Gst.State.PAUSED) + self.player_state = "READY" + self.emit('playback-ready',self.source,dict(self.tags)) + + +GObject.type_register(BasicPlayer) diff --git a/mediaserver/event.py b/mediaserver/event.py new file mode 100644 index 0000000..3a4391d --- /dev/null +++ b/mediaserver/event.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +# event.py +# +# (C) 2011 Bram Kuijvenhoven + +''' +Provides generic event object that allows callback registration to events. + +''' + +class Event(object): + ''' + Generic event object providing callback registration. + + Event listeners are callables. + The semantics of the argument list to the callbacks is not hardcoded in this class; + instead is it implied by the context where this Event object lives. + Registration and unregistration are done by the += and -= operators. + A callable can only be registered once and only be unregistered once. + The event is fired by calling the event object itself with the desired parameters. + + Example: + + >>> def listener(message): + ... print message; + >>> event = Event(); + >>> event("x"); + >>> event += listener; + >>> event("x"); + x + >>> event -= listener; + >>> event("x"); + + ''' + def __init__(self): + self.listeners = []; + def __iadd__(self, listener): + """ + Add new event listener. + @param listener: callable; will be called whenever the event fires. + @return: self. + @raise ValueError: if the listener has already been registered for this event. + """ + if listener in self.listeners: + raise ValueError("Listener already registered to event"); + self.listeners.append(listener); + return self; + def __isub__(self, listener): + """ + Remove previously registered event listener. + @param listener: previously registered event listener. + @return: self. + @raise ValueError: if the listener is not registered for this event. + """ + if listener not in self.listeners: + raise ValueError("Listener not registered to event"); + self.listeners.remove(listener); + return self; + def __call__(self, *args, **kwargs): + """ + Fire event, passing the specified arguments to all listeners. + Each listener will be called with listener(*args, **kwargs). + """ + for listener in list(self.listeners): + listener(*args, **kwargs); diff --git a/mediaserver/mediaserver.py b/mediaserver/mediaserver.py new file mode 100644 index 0000000..2930752 --- /dev/null +++ b/mediaserver/mediaserver.py @@ -0,0 +1,248 @@ +# License for this source file +# +# Copyright (c) 2014 Miqra Engineering +# + +import sys, os +import signal + +from optparse import OptionParser +from collections import OrderedDict + +# perform gstreamer imports (python3 style) +import gi +gi.require_version('Gst','1.0') + +from gi.repository import GObject +from gi.repository import Gst +Gst.init(None) + +# perform dbus imports +import dbus +import dbus.service +from dbus.mainloop.glib import DBusGMainLoop + +# set dbus to use GObject Mainloop +DBusGMainLoop(set_as_default=True) + +# imports from module +from audioplayer import AudioPlayer +from quickplayer import QuickPlayer + + +class MediaService(dbus.service.Object): + def __init__(self): + + bus_name = dbus.service.BusName('nl.miqra.MediaCore.Media', bus=dbus.SystemBus()) + dbus.service.Object.__init__(self, bus_name, '/nl/miqra/MediaCore/Media') + + self.player = AudioPlayer() + self.quickplayer = QuickPlayer() + + self.player.connect('playback-ready',self.onPlayerReady) # parameters: source_file tag_dict + self.player.connect('playback-playing',self.onPlayerPlaying) # parameters: None + self.player.connect('playback-stopped',self.onPlayerStopped) # parameters: None + self.player.connect('playback-paused',self.onPlayerPaused) # parameters: None + self.player.connect('playback-finished',self.onPlayerFinished) # parameters: None + self.player.connect('playback-error',self.onPlayerError) # parameters: error debug + self.player.connect('volume-changed',self.onPlayerVolumeChanged) # parameters: volume + self.player.connect('playback-buffering',self.onPlayerBuffering) # parameters: volume + + @dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='s', out_signature='') + def QuickPlay(self, file,): + """ Directly play back a local file + """ + self.quickplayer.play(file) + + @dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='sd', out_signature='') + def QuickPlayFor(self, file, duration): + """ Directly play back a local file + """ + self.quickplayer.playfor(file,duration) + + @dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='s', out_signature='') + def QuickPlayUrl(self, url,): + """ Directly play back a url + """ + self.quickplayer.playurl(url) + + @dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='sd', out_signature='') + def QuickPlayUrlFor(self, url, duration): + """ Directly play back a url + """ + self.quickplayer.playurlfor(url,duration) + + + @dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='s', out_signature='') + def LoadFile(self, file): + """ Load a local file for playback + """ + print "Loading file {0}".format(file) + self.player.load(file) + self.OnLoading("file://{0}".format(file)) + + @dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='s', out_signature='') + def LoadUrl(self, uri): + """ Load an url for playback + """ + print "Loading url {0}".format(file) + self.player.load_uri(uri) + self.OnLoading(uri) + + @dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='d', out_signature='') + def PlayFor(self, duration): + """ Starts playback for [duration] seconds + """ + print "Starting playback for {0} seconds".format(duration) + self.player.playfor(duration) + + @dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='', out_signature='') + def Play(self): + """ Starts/resumes playback + """ + pos = self.player.position() + print "Starting playback at timestamp {0}".format(pos) + self.player.play() + + @dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='', out_signature='') + def Pause(self): + """ Pauses playback + """ + print "Pausing" + self.player.pause() + + @dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='', out_signature='') + def Stop(self): + """ Stops playback + """ + print "Stopping" + self.player.stop() + + @dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='', out_signature='d') + def Length(self): + """ returns length of currently loaded source + """ + return self.player.duration() + + @dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='', out_signature='d') + def Position(self): + """ returns current position in the source + """ + return self.player.position() + + @dbus.service.method(dbus_interface='nl.miqra.MediaCore.Media', in_signature='d', out_signature='b') + def Seek(self,position): + """ returns current position in the source + """ + self.player.seek(position) + + ### Callback functions + def onPlayerReady(self,player,filename,tags): + print "Loaded {0}".format(filename) + print "Tags:" + taglist = {} + for tag in tags: + try: + taglist[tag] = str(tags[tag]) + print " {0}: {1}".format(tag,taglist[tag]) + except Exception as x: + print "Error converting value for '{0}':\n {1}".format(tag,x) + self.OnReady(filename,taglist) + + def onPlayerPlaying(self,player): + self.OnPlaying() + + def onPlayerStopped(self,player): + self.OnStopped() + + def onPlayerPaused(self,player): + self.OnPaused() + + def onPlayerFinished(self,player): + self.OnFinished() + + def onPlayerError(self,player,state,error,debug): + print "Player state: {0}".format(state) + if state == "LOADING": + print "Failure during LOAD: {0}".format(error) + self.OnLoadFail(error) + else: + print "Failure during RUN: {0}".format(error) + self.OnRunFail(error) + + def onPlayerVolumeChanged(self,player,volume): + pass + + def onPlayerBuffering(self,player,pct): + self.OnBuffering(pct) + pass + + ## Signalling functions (DBus) + + @dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='s') + def OnLoading(self,url): + """ gets called when a source has been requested for playback + """ + pass + + @dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='sa{ss}') + def OnReady(self,filename,tags): + """ gets called when a source is ready for playback + """ + pass + + @dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='') + def OnPlaying(self): + """ gets called when a playback has started + """ + pass + + @dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='') + def OnPaused(self): + """ gets called when a playback is paused + """ + pass + + @dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='q') + def OnBuffering(self,percent): + """ gets called when a playback is paused + """ + pass + + @dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='') + def OnStopped(self): + """ gets called when playback has stopped + """ + pass + + @dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='') + def OnFinished(self): + """ gets called when playback has completed + """ + pass + + @dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='s') + def OnLoadFail(self,reason): + """ gets called when loading a source for playback fails + """ + pass + + @dbus.service.signal(dbus_interface='nl.miqra.MediaCore.Media', signature='s') + def OnRunFail(self,reason): + """ gets called when playback fails + """ + pass + +def Run(): + mediaservice = MediaService() + loop = GObject.MainLoop() + loopcontext = loop.get_context() + + def onsigint(signal,frame): + print "Quitting" + loop.quit() + + signal.signal(signal.SIGINT, onsigint) + + print "Starting..." + loop.run() diff --git a/mediaserver/quickplayer.py b/mediaserver/quickplayer.py new file mode 100644 index 0000000..b91df3a --- /dev/null +++ b/mediaserver/quickplayer.py @@ -0,0 +1,66 @@ +# perform gstreamer imports (python3 style) +import gi +gi.require_version('Gst','1.0') + +from gi.repository import GObject + +# import from this module +from audioplayer import AudioPlayer + +class QuickPlayer(GObject.GObject): + + def __init__(self): + GObject.GObject.__init__(self) + self.player = AudioPlayer() + self._duration = -1; + self.player.connect("playback-ready",self.onReady) + self.player.connect("playback-error",self.onError) + + + # Calling functions for file + def playfor(self,file,duration): + if duration > 0: # not much point in wasting system resources otherwise + self._duration = duration + self.player.load(file) + elif duration < 0: # same as normal play + self.play(file) + + def play(self,file): + self._duration = -1; + self.player.load(file) + + # Calling functions for urls + def playurlfor(self,url,duration): + if duration > 0: # not much point in wasting system resources otherwise + self._duration = duration + self.player.load_uri(url) + elif duration < 0: # same as normal play + self.playurl(url) + + def playurl(self,url): + self._duration = -1; + self.player.load_uri(url) + + # stop function + def stop(self): + self._duration = -1 + self.player.stop() + + def onReady(self,player,file,tags): + print "Quickplay loaded: {0}".format(file) + for tag in tags: + print " {0}: {1}".format(tag,tags[tag]) + if self._duration > 0: + self.player.playfor(self._duration) + elif self._duration < 0: + self.player.play() + + def onError(self,player,player_state,error,debug): + print "Quickplay error during " + player_state + ":" + print " " + error + print " " + print " " + debug + + +GObject.type_register(QuickPlayer) + \ No newline at end of file diff --git a/sbin/mediaserver b/sbin/mediaserver new file mode 100644 index 0000000..188a82e --- /dev/null +++ b/sbin/mediaserver @@ -0,0 +1,4 @@ +#!/usr/bin/python +import mediaserver.mediaserver + +mediaserver.mediaserver.Run() \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..467c90c --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[global] +command-packages=stdeb.command \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..ef304da --- /dev/null +++ b/setup.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +'''KHMedia applications. +Includes scheduled recorder, music player, sound level meter and a webcam viewer +''' + +from distutils.core import setup +from distutils.extension import Extension +from glob import glob +import VERSION + +# patch distutils if it's too old to cope with the "classifiers" or +# "download_url" keywords +from sys import version +if version < '2.2.3': + from distutils.dist import DistributionMetadata + DistributionMetadata.classifiers = None + DistributionMetadata.download_url = None + +if __name__ == '__main__': + setup( + name = 'mediaserver', + version = VERSION.version_string, + description = 'Mediacore mediaserver', + long_description = __doc__, + author = 'Miqra Engineering', + author_email='packaging@miqra.nl', + maintainer = 'Miqra Engineering Packaging', + maintainer_email = 'packaging@miqra.nl', + license='', + platforms=['posix'], + url='', + classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: End Users/Desktop', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python :: 2', + 'Topic :: Multimedia :: Sound/Audio', + 'Topic :: Multimedia :: Sound/Audio :: Players', + ], + packages=['mediaserver'], + package_data={'mediaserver': ['image/*', ]}, + data_files = [ + # note that some files are forced to /usr/share/... instead of just share/.. + # this is because the system does not look in /usr/local/share/... for those files, but only in /usr/share/... + ('/usr/share/mediaserver', glob('usr-share-mediaserver/*')), + ('/etc/dbus-1/system.d', glob('etc/dbus-1/system.d/*')), + ('/etc/init.d', glob('etc/init.d/*')), + ('bin', glob('bin/*')), + ('sbin', glob('sbin/*')), + + ], + ) diff --git a/stdeb.cfg b/stdeb.cfg new file mode 100755 index 0000000..efa8e71 --- /dev/null +++ b/stdeb.cfg @@ -0,0 +1,9 @@ + +[DEFAULT] +Depends: python-dbus, python-gi, fbi, gir1.2-gstreamer-1.0, gir1.2-gst-plugins-base-1.0, gstreamer1.0-plugins-good, gstreamer1.0-plugins-ugly, gstreamer1.0-plugins-bad, gstreamer1.0-alsa, gstreamer1.0-omx, gstreamer1.0-libav +XS-Python-Version: >= 2.6 +Section: sound +Package: mediaserver +Suite: stable +# Do reset the debian version below to 1 for each public version update +Debian-Version: 2 diff --git a/test_basicplayer.py b/test_basicplayer.py new file mode 100644 index 0000000..085cbf5 --- /dev/null +++ b/test_basicplayer.py @@ -0,0 +1,43 @@ +#!/usr/bin/python + +from mediaserver.audioplayer import AudioPlayer +from mediaserver.quickplayer import QuickPlayer +from gi.repository import GObject + + +def onReady(player, file, tags): + print "Starting {0} ...\n".format(file) + print " Tags:\n" + for tag in tags: + print " {0} : '{1}'\n".format(tag,tags[tag]) + + duration = player.duration() + pos = player.position() + print "Song duration is {0} seconds".format(duration) + print "Current position is {0} seconds".format(pos) + + player.playfor(20) + +def onPlaying(player): + + print "Playing ..." + + #print "Jumping to 40 seconds" + #player.seek(40) + +def onStop(player): + print "Quitting...." + loop.quit() + +player = AudioPlayer() +player.connect("playback-ready",onReady) +player.connect("playback-playing",onPlaying) +player.connect("playback-finished",onStop) +player.connect("playback-stopped",onStop) + +player.load("/opt/mediacore/mediaserver2/snnw_E_138.mp3") + + + +loop = GObject.MainLoop() +loop.run() diff --git a/usr-share-mediaserver/3d-loudspeaker.svg b/usr-share-mediaserver/3d-loudspeaker.svg new file mode 100644 index 0000000..039f1db --- /dev/null +++ b/usr-share-mediaserver/3d-loudspeaker.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + \ No newline at end of file diff --git a/usr-share-mediaserver/background-image.png b/usr-share-mediaserver/background-image.png new file mode 100755 index 0000000..96b4ca4 Binary files /dev/null and b/usr-share-mediaserver/background-image.png differ diff --git a/usr-share-mediaserver/background-image.svg b/usr-share-mediaserver/background-image.svg new file mode 100755 index 0000000..c723152 --- /dev/null +++ b/usr-share-mediaserver/background-image.svg @@ -0,0 +1,266 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + Mediacore + + +