Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • dev-old
  • dev-old-david
  • develop
  • lars-tests
  • master
  • master-old
  • topic/filesystem-fallbacks
  • topic/tank_connection
  • topic/tank_connection_david
  • user/equinox/docker
10 results

Target

Select target project
  • aura/engine
  • hermannschwaerzler/engine
  • sumpfralle/aura-engine
3 results
Select Git revision
  • 122-synchronized-ci
  • feat-use-docker-main-tag
  • fix-aura-sysuser
  • fix-broken-pipe-153
  • fix-docker-release
  • fix-push-latest-with-tag
  • fix-streamchannel-retries
  • gitlab-templates
  • improve-test-coverage-137
  • improve-test-coverage-143
  • main
  • orm-less-scheduling
  • remove-mailer
  • update-changelog-alpha3
  • virtual-timeslots-131
  • 1.0.0-alpha1
  • 1.0.0-alpha2
  • 1.0.0-alpha3
  • 1.0.0-alpha4
  • 1.0.0-alpha5
20 results
Show changes
Commits on Source (3)
Showing
with 1593 additions and 0 deletions
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.5.3 (/usr/bin/python3.5)" project-jdk-type="Python SDK" />
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/aura.iml" filepath="$PROJECT_DIR$/.idea/aura.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PreferredVcsStorage">
<preferredVcsName>ApexVCS</preferredVcsName>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
This diff is collapsed.
import signal
import sys
import threading
from datetime import datetime
from libraries.base.config import ConfigReader
from libraries.reporting.messenger import AuraMessenger
from modules.controller.controller import AuraController
from modules.communication.zmq.zmqadapter import ServerZMQAdapter
class Aura(threading.Thread):
config = None
messenger = None
controller = None
def __init__(self):
self.config = ConfigReader()
self.config.loadConfig()
messenger = AuraMessenger()
messenger.setChannel("aura")
server = object
self.controller = AuraController(self.config)
def receive_signal(signum, stack):
print("received signal")
server.reload()
signal.signal(signal.SIGUSR1, receive_signal)
def startListening(self):
# start listener thread
server = ServerZMQAdapter(self.controller, self.config)
try:
while server.is_alive():
print(str(datetime.now())+" joining")
server.join()
print("join out")
# if cnt % 30 == 0:
# print(datetime.datetime.now().isoformat())
# server.printLastMessages()
# cnt = 0
# cnt = cnt + 1
except (KeyboardInterrupt, SystemExit):
# Dem Server den Shutdown event setzen
# server.shutdown_event.set()
# Der Server wartet auf Eingabe
# Daher einen Client initiieren, der eine Nachricht schickt
server.halt()
sys.exit('Terminated')
# # ## ## ## ## ## # #
# # ENTRY FUNCTION # #
# # ## ## ## ## ## # #
def main():
aura = Aura()
aura.startListening()
# # ## ## ## ## ## ## # #
# # End ENTRY FUNCTION # #
# # ## ## ## ## ## ## # #
if __name__ == "__main__":
main()
\ No newline at end of file
{
"allData": {
"id": "01",
"00": "Global Metadata delivered",
"01": "Could not get Data from Sound Engine"
},
"channel_insert": {
"id": "02",
"00": "On Channel ::channel:: insert ::uri:: at position ::pos::",
"02": "On Channel ::channel:: could not insert ::uri:: at position ::pos::"
},
"channel_move": {
"id": "03",
"00": "On Channel ::channel:: moved Item from ::fromPos:: to position ::toPos::",
"01": "Warning: Position ::fromPos:: out of range",
"02": "Warning: Cannot move to same position",
"03": "On Channel ::channel:: could not move from position ::fromPos:: to position ::toPos::"
},
"channel_off": {
"id": "04",
"00": "Channel ::channel:: off",
"01": "Could not activate Channel ::channel::"
},
"channel_on": {
"id": "05",
"00": "Channel ::channel:: on",
"01": "Could not deactivate Channel ::channel::"
},
"channel_queue": {
"id": "06",
"00": "Channel Queue for ::channel:: delivered",
"01": "Could not get channel queue from channel ::channel::",
"02": "Could not get channel queue from channel ::channel::",
"03": "Could not get channel queue from channel ::channel::"
},
"channel_remove": {
"id": "07",
"00": "Removed item on position ::pos:: from channel ::channel::",
"01": "Could not remove item on position ::pos:: from channel ::channel::",
"02": "Warning: position ::pos:: out of range'"
},
"channel_seek": {
"id": "08",
"00": "Seeked channel ::channel:: ::duration:: seconds",
"01": "Could not seek channel ::channel:: ::duration:: seconds"
},
"channel_skip": {
"id": "09",
"00": "Skipped channel ::channel::",
"01": "0 Channels listed",
"02": "Could not get channels from sound engine",
"03": "Could not skip ::channel::"
},
"channel_volume": {
"id": "10",
"00": "Volume ::volume::% set on channel ::channel::",
"01": "Could not set volume to ::volume::% on channel ::channel::",
"02": "0 Channels listed",
"03": "Could not get channels from sound engine"
},
"currentData": {
"id": "11",
"00": "Current track metadata delivered",
"01": "Nothing seems to be on air",
"02": "Could not detect metadata"
},
"help": {
"id": "12",
"00": "none",
"01": "Could not open help file"
},
"listChannels": {
"id": "13",
"00": "Listed Channels",
"01": "0 Channels listed",
"02": "Could not get channels from sound engine"
},
"message": {
"id": "14",
"00": "none"
},
"playlist_data": {
"id": "15",
"00": "Playlist data delivered"
},
"playlist_flush": {
"id": "16",
"00": "Flushed playlist",
"01": "Could not flush playlist"
},
"playlist_insert":{
"id": "17",
"00": "Insert track ::uri:: on position ::pos::"
},
"playlist_load": {
"id": "18",
"00": "Load Playlist ::uri::",
"01": "Could not load Playlist ::uri::",
"02": "Playlist is not well formed XML"
},
"playlist_move": {
"id": "19",
"00": "Moved playlist track from position ::fromPos:: to ::toPos::"
},
"playlist_pause": {
"id": "20",
"00": "Playlist paused",
"01": "Playlist already paused"
},
"playlist_stop": {
"id": "21",
"00": "Playlist stopped",
"01": "Playlist already stopped"
},
"playlist_play": {
"id": "22",
"00": "Playlist started",
"01": "Playlist already playing",
"02": "0 Channels listed",
"03": "Could not get channels from sound engine"
},
"playlist_push": {
"id": "23",
"00": "Playlist: pushed ::uri::",
"01": "Could not push ::uri::"
},
"playlist_remove":{
"id": "24",
"00": "Removed track on position ::pos:: from playlist",
"01": "Could not remove track on position ::pos:: from playlist"
},
"playlist_seek": {
"id": "25",
"00": "Seeked playlist ::duration:: seconds",
"01": "Could not seek playlist ::duration:: seconds"
},
"playlist_skip": {
"id": "26",
"00": "Skipped playlist",
"00": "Could not skip playlist"
},
"recorder_data": {
"id": "27",
"00": "Delivered recorder data",
"01": "Could not deliver recorder data"
},
"recorder_start": {
"id": "28",
"00": "Recorder started",
"01": "Could not start recorder"
},
"recorder_stop": {
"id": "29",
"00": "Recorder stopped",
"01": "Could not stop recorder"
},
"scheduler_reload": {
"id": "30",
"00": "Reload signal was sent to scheduler",
"01": "Could not find the scheduler process"
},
"sendLqcCommand": {
"id": "31",
"01": "Soundengine not running",
"02": "Recorder not running"
},
"get_channel_state" : {
"id": "32",
"00": "Channels ::channel:: state",
"01": "Could not get channel state from channel ::channel::"
},
"setPassword": {
"id": "33",
"00": "Successfull set password",
"01": "Not enough access rights for this operation"
},
"addUser": {
"id": "34",
"00": "Successfull add user ::username::",
"01": "Not enough access rights for this operation"
},
"delUser": {
"id": "35",
"00": "Successfull removed user ::username::",
"01": "Not enough access rights for this operation"
},
"scheduler_data": {
"id": "36",
"00": "Successfull delivered scheduler config",
"01": "Scheduler config seems to be broken"
},
"scheduler_store": {
"id": "37",
"00": "Successfull stored scheduler config",
"01": "Not enough access rights for this operation",
"02": "Could not store a valid scheduler XML"
},
"getUserlist": {
"id": "38",
"00": "Userlist was successfully delivered",
"01": "Not enough access rights for this operation"
}
}
{
"exec_job": {
"id": "01",
"00": "Execute job ::job::",
"01": "Fatal: Could not execute job ::job::. Command ::exec:: results in Exception ::Exception::. Stopped watcher"
},
"schedule_job": {
"id": "02",
"00": "Scheduled job ::job:: for ::scheduled_for:: at ::scheduled_at::",
"01": "Could not execute job"
},
"load_playlist": {
"id": "03",
"00": "Load playlist ::uri::",
"01": "Could not load playlist ::uri::. File does not exist!",
"02": "Controller failed to load playlist ::uri::. Message was '::message::'"
},
"play_playlist": {
"id": "04",
"00": "Started playlist",
"01": "Controller failed to start playlist. Message was '::message::'"
},
"stop_playlist": {
"id": "05",
"00": "Started playlist",
"01": "Controller failed to start playlist. Message was '::message::'"
},
"start_recording": {
"id": "06",
"00": "Started recording",
"01": "Controller failed to start recording. Message was '::message::'"
},
"stop_recording": {
"id": "07",
"00": "Stopped recording",
"01": "Controller failed to stop recording. Message was '::message::'" },
"precache": {
"id": "08",
"00": "Precached playlists",
"01": "Could not precache playlist."
},
"clean_cached": {
"id": "09",
"00": "Cleaned cache",
"01": "Could not clean cache"
},
"on_start": {
"id": "10",
"00": "Do initial jobs",
"01": "Could not do initial jobs"
},
"lookup_prearranged": {
"id": "11",
"00": "Lookup for prearranged tracks",
"01": "No system channel available"
},
"start_prearranged": {
"id": "12",
"00": "Started preaarranged tracks"
},
"end_prearranged": {
"id": "13",
"00": "Stopped preaarranged tracks"
}
}
#!/usr/bin/python3
import time
import simplejson
from argparse import ArgumentParser
from modules.communication.liquidsoap.LiquidSoapCommunicator import LiquidSoapCommunicator
from libraries.base.config import ConfigReader
from modules.communication.zmq.zmqadapter import ClientZMQAdapter
class Guru:
config = ConfigReader()
config.loadConfig()
lsc = LiquidSoapCommunicator(False)
# # AuraScheduler(lsc.getClient(), "/etc/comba/scheduler.xml")
#fcd = FetchCalendarData()
zmqclient = ClientZMQAdapter(config.get('zmqhostip'), config.get('zmqport'))
def __init__(self):
nothing_done = True
try:
parser = ArgumentParser()
# commands
parser.add_argument("-fnp", "--fetch-new-programmes", action="store_true", dest="fetch_new_programme",
default=False, help="Fetch new programmes from calendarurl in comba.ini")
# getter
parser.add_argument("-gam", "--get-active-mixer", action="store_true", dest="getactivemixer", default=False,
help="Which mixer is activated?")
parser.add_argument("-pms", "--print-mixer-status", action="store_true", dest="printmixerstatus", default=False,
help="Prints all mixer sources and their states")
parser.add_argument("-pap", "--print-act-programme", action="store_true", dest="printactprog", default=False,
help="Prints the actual Programme, the controller holds")
# manipulation
parser.add_argument("-am", "--select-mixer", action="store", dest="selectmixer", default=-1,
help="Which mixer should be activated?", type=int)
parser.add_argument("-dm", "--de-select-mixer", action="store", dest="deselectmixer", default=-1,
help="Which mixer should be activated?", type=int)
parser.add_argument("-as", "--add-source", action="store", dest="addsource", default="",
help="Add new source to LiquidSoap mixer [Experimental]")
args = parser.parse_args()
except ValueError:
parser.print_help()
exit(1)
if args.fetch_new_programme:
print("Guru is learning how to fetch new programmes")
self.fetch_new_programme()
nothing_done = False
if args.getactivemixer:
self.getactivemixer()
nothing_done = False
if args.printmixerstatus:
self.printmixerstatus()
nothing_done = False
if args.printactprog:
self.printactprog()
nothing_done = False
if args.addsource != "":
print("Guru still has to learn to add a source")
nothing_done = False
if args.selectmixer != -1:
self.selectmixer(args.selectmixer)
nothing_done = False
if args.deselectmixer != -1:
self.selectmixer(args.deselectmixer, False)
nothing_done = False
if nothing_done:
parser.print_help()
def fetch_new_programme(self):
reply = self.zmqclient.send("fetch_new_programme")
print(type(reply))
print(reply)
print(simplejson.loads(reply))
def selectmixer(self, mixernumber, activate=True):
return self.lsc.switchmixernumber(mixernumber, activate)
def getactivemixer(self):
print("Guru thinking...")
am = self.lsc.getactivemixer()
if len(am) == 0:
print("Guru recognized a problem: No active source!!!")
elif len(am) > 1:
print("Guru recognized a problem: Multiple active sources!!! " + str(am))
else:
print("Guru thinking result: ActiveMixer: " + str(am[0]))
def printmixerstatus(self):
status = self.lsc.getmixerstatus()
for k,v in status.items():
print("source: "+k+"\t status: "+v)
def printactprog(self):
reply = self.zmqclient.send("get_act_programme")
print(reply)
# # ## ## ## ## ## # #
# # ENTRY FUNCTION # #
# # ## ## ## ## ## # #
def main():
guru = Guru()
# # ## ## ## ## ## ## # #
# # End ENTRY FUNCTION # #
# # ## ## ## ## ## ## # #
if __name__ == "__main__":
main()
File added
File added
File added
File added
This diff is collapsed.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# combabase.py
#
# Copyright 2014 BFR <info@freie-radios.de>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; Version 3 of the License
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, the license can be downloaded here:
#
# http://www.gnu.org/licenses/gpl.html
# Meta
__version__ = '0.1.1'
__license__ = "GNU General Public License (GPL) Version 3"
__version_info__ = (0, 1, 1)
__author__ = 'Michael Liebler <michael-liebler@radio-z.net>'
# massively enhanced by Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>
"""
Comba Base Class - lade Config
"""
import os
import sys
#import StringIO
#import ConfigParser
import socket
from io import StringIO
from configparser import RawConfigParser
from configparser import ConfigParser
class ConfigReader(object):
def set(self, key, value):
"""
Eine property setzen
@type key: string
@param key: Der Key
@type value: mixed
@param value: Beliebiger Wert
"""
if(key == "securitylevel"):
self.__dict__[key] = int(value)
else:
self.__dict__[key] = value
# ------------------------------------------------------------------------------------------ #
def get(self, key, default=None):
"""
Eine property holen
@type key: string
@param key: Der Key
@type default: mixed
@param default: Beliebiger Wert
"""
if key not in self.__dict__:
if default:
self.set(key, default)
else:
print("WARNING: Key "+key+" not found!")
return None
return self.__dict__[key]
# ------------------------------------------------------------------------------------------ #
def loadConfig(self):
"""
Set config defaults and load settings from file
:return:
"""
ini_path = self.get('configpath', '/etc/aura/aura.ini')
if not os.path.isfile(ini_path):
print(ini_path + " not found :(")
sys.exit(1)
# INI einlesen
f = open(ini_path, 'r')
ini_str = '[root]\n' + f.read()
f.close()
self.configDefaults = {
'secondspertrack' : '1800',
'audiobase' : '/var/audio/rec',
'altaudiobase' : '/var/audio/preprod',
'calendarurl' : 'http://localhost/index.php?option=com_jimtawl&view=calendar&format=json&from=#datefrom#&to=#dateto#',
'communication': 'zmq',
'playlistdir': '/var/audio/playlists',
'archivebase': '/var/audio/archive/',
'controllerport': '9099',
'logdir' : '/var/log/comba',
'loglevel': 'info',
'securitylevel': '0',
'adminmail': '',
'frommail': '',
'calendar_precache_days': '7',
'servername': socket.getfqdn(),
'serviceport': '8080',
'stream' : "",
'stream_type' : "",
'stream_host' : "",
'stream_port' : "",
'stream_mountpoint' : "",
'stream_admin_user' : "",
'stream_admin_password' : "",
}
# readfp is deprecated since 3.2
if sys.version_info <= (3, 2):
ini_str = StringIO(ini_str)
config = RawConfigParser(self.configDefaults)
config.readfp(ini_str)
else:
config = ConfigParser(self.configDefaults)
config.read_string(ini_str)
for key, value in config.items('root'):
self.set(key, config.get('root', key).replace('"', '').strip())
#!/usr/bin/env python
"Makes working with XML feel like you are working with JSON"
from xml.parsers import expat
from xml.sax.saxutils import XMLGenerator
from xml.sax.xmlreader import AttributesImpl
try: # pragma no cover
from cStringIO import StringIO
except ImportError: # pragma no cover
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
try: # pragma no cover
from collections import OrderedDict
except ImportError: # pragma no cover
try:
from ordereddict import OrderedDict
except ImportError:
OrderedDict = dict
try: # pragma no cover
_basestring = str #basestring
except NameError: # pragma no cover
_basestring = bytes #str
try: # pragma no cover
_unicode = str #unicode
except NameError: # pragma no cover
_unicode = bytes #str
__author__ = 'Martin Blech'
__version__ = '0.9.0'
__license__ = 'MIT'
class ParsingInterrupted(Exception):
pass
class _DictSAXHandler(object):
def __init__(self,
item_depth=0,
item_callback=lambda *args: True,
xml_attribs=True,
attr_prefix='@',
cdata_key='#text',
force_cdata=False,
cdata_separator='',
postprocessor=None,
dict_constructor=OrderedDict,
strip_whitespace=True,
namespace_separator=':',
namespaces=None):
self.path = []
self.stack = []
self.data = None
self.item = None
self.item_depth = item_depth
self.xml_attribs = xml_attribs
self.item_callback = item_callback
self.attr_prefix = attr_prefix
self.cdata_key = cdata_key
self.force_cdata = force_cdata
self.cdata_separator = cdata_separator
self.postprocessor = postprocessor
self.dict_constructor = dict_constructor
self.strip_whitespace = strip_whitespace
self.namespace_separator = namespace_separator
self.namespaces = namespaces
def _build_name(self, full_name):
if not self.namespaces:
return full_name
i = full_name.rfind(self.namespace_separator)
if i == -1:
return full_name
namespace, name = full_name[:i], full_name[i+1:]
short_namespace = self.namespaces.get(namespace, namespace)
if not short_namespace:
return name
else:
return self.namespace_separator.join((short_namespace, name))
def _attrs_to_dict(self, attrs):
if isinstance(attrs, dict):
return attrs
return self.dict_constructor(zip(attrs[0::2], attrs[1::2]))
def startElement(self, full_name, attrs):
name = self._build_name(full_name)
attrs = self._attrs_to_dict(attrs)
self.path.append((name, attrs or None))
if len(self.path) > self.item_depth:
self.stack.append((self.item, self.data))
if self.xml_attribs:
attrs = self.dict_constructor(
(self.attr_prefix+key, value)
for (key, value) in attrs.items())
else:
attrs = None
self.item = attrs or None
self.data = None
def endElement(self, full_name):
name = self._build_name(full_name)
if len(self.path) == self.item_depth:
item = self.item
if item is None:
item = self.data
should_continue = self.item_callback(self.path, item)
if not should_continue:
raise ParsingInterrupted()
if len(self.stack):
item, data = self.item, self.data
self.item, self.data = self.stack.pop()
if self.strip_whitespace and data is not None:
data = data.strip() or None
if data and self.force_cdata and item is None:
item = self.dict_constructor()
if item is not None:
if data:
self.push_data(item, self.cdata_key, data)
self.item = self.push_data(self.item, name, item)
else:
self.item = self.push_data(self.item, name, data)
else:
self.item = self.data = None
self.path.pop()
def characters(self, data):
if not self.data:
self.data = data
else:
self.data += self.cdata_separator + data
def push_data(self, item, key, data):
if self.postprocessor is not None:
result = self.postprocessor(self.path, key, data)
if result is None:
return item
key, data = result
if item is None:
item = self.dict_constructor()
try:
value = item[key]
if isinstance(value, list):
value.append(data)
else:
item[key] = [value, data]
except KeyError:
item[key] = data
return item
def parsexml(xml_input, encoding=None, expat=expat, process_namespaces=False,
namespace_separator=':', **kwargs):
"""Parse the given XML input and convert it into a dictionary.
`xml_input` can either be a `string` or a file-like object.
If `xml_attribs` is `True`, element attributes are put in the dictionary
among regular child elements, using `@` as a prefix to avoid collisions. If
set to `False`, they are just ignored.
Simple example::
>>> import xmltodict
>>> doc = xmltodict.parse(\"\"\"
... <a prop="x">
... <b>1</b>
... <b>2</b>
... </a>
... \"\"\")
>>> doc['a']['@prop']
u'x'
>>> doc['a']['b']
[u'1', u'2']
If `item_depth` is `0`, the function returns a dictionary for the root
element (default behavior). Otherwise, it calls `item_callback` every time
an item at the specified depth is found and returns `None` in the end
(streaming mode).
The callback function receives two parameters: the `path` from the document
root to the item (name-attribs pairs), and the `item` (dict). If the
callback's return value is false-ish, parsing will be stopped with the
:class:`ParsingInterrupted` exception.
Streaming example::
>>> def handle(path, item):
... print 'path:%s item:%s' % (path, item)
... return True
...
>>> xmltodict.parse(\"\"\"
... <a prop="x">
... <b>1</b>
... <b>2</b>
... </a>\"\"\", item_depth=2, item_callback=handle)
path:[(u'a', {u'prop': u'x'}), (u'b', None)] item:1
path:[(u'a', {u'prop': u'x'}), (u'b', None)] item:2
The optional argument `postprocessor` is a function that takes `path`,
`key` and `value` as positional arguments and returns a new `(key, value)`
pair where both `key` and `value` may have changed. Usage example::
>>> def postprocessor(path, key, value):
... try:
... return key + ':int', int(value)
... except (ValueError, TypeError):
... return key, value
>>> xmltodict.parse('<a><b>1</b><b>2</b><b>x</b></a>',
... postprocessor=postprocessor)
OrderedDict([(u'a', OrderedDict([(u'b:int', [1, 2]), (u'b', u'x')]))])
You can pass an alternate version of `expat` (such as `defusedexpat`) by
using the `expat` parameter. E.g:
>>> import defusedexpat
>>> xmltodict.parse('<a>hello</a>', expat=defusedexpat.pyexpat)
OrderedDict([(u'a', u'hello')])
"""
handler = _DictSAXHandler(namespace_separator=namespace_separator,
**kwargs)
if isinstance(xml_input, _unicode):
if not encoding:
encoding = 'utf-8'
xml_input = xml_input.encode(encoding)
if not process_namespaces:
namespace_separator = None
parser = expat.ParserCreate(
encoding,
namespace_separator
)
try:
parser.ordered_attributes = True
except AttributeError:
# Jython's expat does not support ordered_attributes
pass
parser.StartElementHandler = handler.startElement
parser.EndElementHandler = handler.endElement
parser.CharacterDataHandler = handler.characters
parser.buffer_text = True
try:
parser.ParseFile(xml_input)
except (TypeError, AttributeError):
parser.Parse(xml_input, True)
return handler.item
def _emit(key, value, content_handler,
attr_prefix='@',
cdata_key='#text',
depth=0,
preprocessor=None,
pretty=False,
newl='\n',
indent='\t'):
if preprocessor is not None:
result = preprocessor(key, value)
if result is None:
return
key, value = result
if not isinstance(value, (list, tuple)):
value = [value]
if depth == 0 and len(value) > 1:
raise ValueError('document with multiple roots')
for v in value:
if v is None:
v = OrderedDict()
elif not isinstance(v, dict):
v = _unicode(v)
if isinstance(v, _basestring):
v = OrderedDict(((cdata_key, v),))
cdata = None
attrs = OrderedDict()
children = []
for ik, iv in v.items():
if ik == cdata_key:
cdata = iv
continue
if ik.startswith(attr_prefix):
attrs[ik[len(attr_prefix):]] = iv
continue
children.append((ik, iv))
if pretty:
content_handler.ignorableWhitespace(depth * indent)
content_handler.startElement(key, AttributesImpl(attrs))
if pretty and children:
content_handler.ignorableWhitespace(newl)
for child_key, child_value in children:
_emit(child_key, child_value, content_handler,
attr_prefix, cdata_key, depth+1, preprocessor,
pretty, newl, indent)
if cdata is not None:
content_handler.characters(cdata)
if pretty and children:
content_handler.ignorableWhitespace(depth * indent)
content_handler.endElement(key)
if pretty and depth:
content_handler.ignorableWhitespace(newl)
def unparse(input_dict, output=None, encoding='utf-8', full_document=True, **kwargs):
"""Emit an XML document for the given `input_dict` (reverse of `parse`).
The resulting XML document is returned as a string, but if `output`
(afile-like object) is specified, it is written there instead.
Dictionary keys prefixed with `attr_prefix` (default=`'@'`) are interpreted
as XML node attributes, whereas keys equal to `cdata_key`
(default=`'#text'`) are treated as character data.
The `pretty` parameter (default=`False`) enables pretty-printing. In this
mode, lines are terminated with `'\n'` and indented with `'\t'`, but this
can be customized with the `newl` and `indent` parameters.
"""
((key, value),) = input_dict.items()
must_return = False
if output is None:
output = StringIO()
must_return = True
content_handler = XMLGenerator(output, encoding)
if full_document:
content_handler.startDocument()
_emit(key, value, content_handler, **kwargs)
if full_document:
content_handler.endDocument()
if must_return:
value = output.getvalue()
try: # pragma no cover
value = value.decode(encoding)
except AttributeError: # pragma no cover
pass
return value
if __name__ == '__main__': # pragma: no cover
import sys
import marshal
(item_depth,) = sys.argv[1:]
item_depth = int(item_depth)
def handle_item(path, item):
marshal.dump((path, item), sys.stdout)
return True
try:
root = parsexml(sys.stdin,
item_depth=item_depth,
item_callback=handle_item,
dict_constructor=dict)
if item_depth == 0:
handle_item([], root)
except KeyboardInterrupt:
pass
from xml.dom.minidom import parse
import datetime
from datetime import timedelta
import simplejson
from xml.etree import ElementTree
class NotTextNodeError(BaseException):
pass
class AuraSchedulerConfig():
def __init__(self, xmlpath):
self.jobs = {}
self.filename = xmlpath
self.playperiods = []
self.recordperiods = []
self.hasinstance = False
self.until = None
# -----------------------------------------------------------------------#
def getPlayPeriods(self):
if not self.hasinstance:
self.getJobs()
return self.playperiods
# -----------------------------------------------------------------------#
def getRecordPeriods(self):
if not self.hasinstance:
self.getJobs()
return self.recordperiods
# -----------------------------------------------------------------------#
def getJobs(self):
self.hasinstance = True
self.loadXml()
for job in self.jobs:
if 'job' not in job:
continue;
if 'until' not in job:
job['until'] = ''
if 'day' not in job:
job['day'] = 'all'
# self.jobs.sort(cmp=lambda x,y: cmp(x['time'], y['time']))
# self.jobs.sort(cmp=lambda x,y: cmp(x['day'], y['day']))
self.jobs.sort(key=lambda job: job['time'])
self.jobs.sort(key=lambda job: job['day'])
for index, job in enumerate(self.jobs):
if job['job'] == 'play_playlist':
job['duration'] = self._calcDuration(job['time'], job['until'])
self.playperiods.append({'from': job['time'],'until': job['until'], 'duration': job['duration']})
day = None
if 'day' in job:
day = job['day']
self.addPlaylistLoadJob(job['time'], job['until'], day)
if job['job'] == 'start_recording':
job['duration'] = self._calcDuration(job['time'], job['until'])
self.recordperiods.append({'from': job['time'],'until': job['until'], 'duration': job['duration']})
return self.jobs
# -----------------------------------------------------------------------#
def addPlaylistLoadJob(self, playTime, untilTime, day=None):
job = {}
playStart = datetime.datetime.strptime('1901-01-01T' + playTime,'%Y-%m-%dT%H:%M');
loadTime = playStart - timedelta(minutes=3)
loadTime = loadTime.strftime('%H:%M')
job['time'] = loadTime
job['from'] = playTime
job['until'] = untilTime
job['job'] = 'load_playlist'
if day and not day == 'all' and loadTime > playTime:
day = int(day)
day = 6 if day == 0 else day - 1
job['day'] = str(day)
self.jobs.append(job)
# -----------------------------------------------------------------------#
def storeJsonToXml(self, json):
try:
jobs = simplejson.loads(json)
except:
return False
xml = '<?xml version="1.0" encoding="UTF-8"?>'+"\n"
xml += '<Config>'+"\n";
xml += ' <Jobs multiple="true">'+"\n";
xmlend = ' </Jobs>'+"\n";
xmlend += '</Config>';
for job in jobs:
xml+= ' <job>'+"\n";
for key in job.keys():
xml+= ' <'+key+'>'+str(job[key])+'</'+key+'>'+"\n"
if not job.has_key('params'):
xml+= ' <params></params>'+"\n"
if not job.has_key('day'):
xml+= ' <day>all</day>'+"\n"
xml+= ' </job>'+"\n"
# validate xml
try:
x = ElementTree.fromstring(xml+xmlend)
except:
return False
else:
try:
file = open(self.filename, "w")
file.write(xml+xmlend)
file.close()
except:
return False
else:
return True
# -----------------------------------------------------------------------#
def loadXml(self):
dom = parse(self.filename)
config = self.nodeToDic(dom)
self.jobs = config['Config']['Jobs']
# -----------------------------------------------------------------------#
def getTextFromNode(self, node):
t = ""
for n in node.childNodes:
if n.nodeType == n.TEXT_NODE:
t += n.nodeValue
else:
raise NotTextNodeError
return t
# -----------------------------------------------------------------------#
def nodeToDic(self, node):
dic = {}
for n in node.childNodes:
if n.nodeType != n.ELEMENT_NODE:
continue
if n.getAttribute("multiple") == "true":
# node with multiple children:
# put them in a list
l = []
for c in n.childNodes:
if c.nodeType != n.ELEMENT_NODE:
continue
l.append(self.nodeToDic(c))
dic.update({n.nodeName: l})
continue
try:
text = self.getTextFromNode(n)
except NotTextNodeError:
# 'normal' node
dic.update({str(n.nodeName): self.nodeToDic(n)})
continue
# text node
dic.update({str(n.nodeName): str(text)})
continue
return dic
# -----------------------------------------------------------------------#
def in_timeperiod(self, now, job):
if 'until' not in job or not job['until']:
print("not in timeperiod")
return False
(hour1, minute1) = job['time'].split(':')
(hour2, minute2) = job['until'].split(':')
if job['time'] > job['until']:
print("in time period. time greater than until")
return datetime.time(hour=int(hour1), minute=int(minute1)) \
<= now.time()
else:
print("in time period. until greater than time")
return datetime.time(hour=int(hour1), minute=int(minute1)) \
<= now.time() \
<= datetime.time(hour=int(hour2), minute=int(minute2))
# -----------------------------------------------------------------------#
def _calcDuration(self, timestring1, timestring2):
"""Berechnet Zeit in Sekunden aus zwei Time-Strings
"""
ftr = [3600, 60, 1]
sec1 = sum([a * b for a, b in zip(ftr, map(int, timestring1.split(':')))])
sec2 = sum([a * b for a, b in zip(ftr, map(int, timestring2.split(':')))])
offset = 0 if sec2 > sec1 else 86400
return (sec2 + offset) - sec1
# -----------------------------------------------------------------------#
def find_next(self, items, index, key, value):
for idx, item in enumerate(items):
if idx <= index:
continue
if item[key] == value:
return idx
return self.find_next(items,0,key,value)
# -*- coding: utf-8 -*-
import sys
import simplejson
import urllib
"""
Die AuraClient Klasse stellt die Tasks zur Verfügung,
die dem Playlout Controller übertragen werden können
Dies ist im Wesentlichen ein Wrapper
"""
class AuraClient():
def __init__(self, sender):
"""
Constructor
@type sender: object
@param sender: Der Communicator Adapter - z-B. zmq
"""
self.sender = sender
# ------------------------------------------------------------------------------------------ #
def command(self, command):
"""
Kommando an den Controller absetzen
und Antwort entgegennehmen
@type command: string
@param command: Kommando
@rtype: string
@return: Antwort des Controllers
"""
self.sender.send(command)
message = self.sender.receive()
return message
# ------------------------------------------------------------------------------------------ #
def channel_skip(self, channel):
"""
Skipt einen Kanal oder die Playlist
@type channel: string
@param channel: Kanal
@rtype: string
@return: Antwort des Controllers
"""
return self.command('channel_skip ' + channel)
# ------------------------------------------------------------------------------------------ #
def channel_is_active(self, channel='playlist'):
"""
Ist der Kanal aktiv?
@type channel: string
@param channel: Kanal
@rtype: boolean
@return: True/False
"""
state = self._get_channel_state(channel)
is_active = True if state['selected'] == 'true' else False
return is_active
# ------------------------------------------------------------------------------------------ #
def channel_on(self, channel):
"""
Kanal einschalten
@type channel: string
@param channel: Kanal
@rtype: string
@return: Antwort des Controllers
"""
return self.command('channel_on ' + channel)
# ------------------------------------------------------------------------------------------ #
def channel_off(self, channel):
"""
Kanal ausschalten
@type channel: string
@param channel: Kanal
@rtype: string
@return: Antwort des Controllers
"""
return self.command('channel_off ' + channel)
# ------------------------------------------------------------------------------------------ #
def get_channellist(self):
"""
Channels als Liste ausgeben
@rtype: list
@return: Antwort des Controllers
"""
return simplejson.loads(self.command('listChannels'))
# ------------------------------------------------------------------------------------------ #
def get_channelqueue(self, channel):
"""
Channel Queue ausgeben
@type channel: string
@param channel: Kanal
@rtype: dict
@return: Antwort des Controllers
"""
return simplejson.loads(self.command('channel_queue ' + channel))
# ------------------------------------------------------------------------------------------ #
def get_channel_volume(self, channel='playlist'):
"""
Lautstärke des Kanals ausgeben
@type channel: string
@param channel: Kanal
@rtype: string/boolean
@return: Volumen von 1-100/False
"""
state = self._get_channel_state(channel)
channels = simplejson.loads(self.command('allData'))
try:
volume = state['volume']
except KeyError:
return False
else:
return volume
return False
# ------------------------------------------------------------------------------------------ #
def channel_remove_track(self, channel, track_pos):
"""
Löscht einen Track aus dem secondary_queue
@type channel: string
@param channel: Kanal
@type track_pos: string/int
@param track_pos: Position des zu entfernenden Eintrags
@rtype: string
@return: Antwort des Controllers
"""
return self.command('channel_remove ' + channel + ' ' + str(track_pos))
# ------------------------------------------------------------------------------------------ #
def channel_seek(self, channel, duration):
"""
Spult den laufenen Track des Kanals <duration> Sekunden weiter (falls möglich)
Beispiel: channel_seek('ch1',60) - 60 Sekunden nach vorne
@type channel: string
@param channel: Kanal
@type duration: string/int
@param duration: Dauer in Sekunden
@rtype: string
@return: Antwort des Controllers
"""
return self.command('channel_seek ' + channel + ' ' + str(duration))
# ------------------------------------------------------------------------------------------ #
def channel_track_up(self, channel, track_pos):
"""
Einen Track um eine Position nach oben schieben
@type channel: string
@param channel: Kanal
@type track_pos: string
@param track_pos: Position des zu verschiebenden Eintrags
@rtype: string
@return: Antwort des Controllers
"""
return self.command('channel_move ' + channel + ' ' + str(track_pos) + ' ' + str(track_pos - 1))
# ------------------------------------------------------------------------------------------ #
def channel_track_down(self, channel, track_pos):
"""
Einen Track um eine Position nach unten schieben
@type channel: string
@param channel: Kanal
@type track_pos: string
@param track_pos: Position des zu verschiebenden Eintrags
@rtype: string
@return: Antwort des Controllers
"""
return self.command('channel_move ' + channel + ' ' + str(track_pos) + ' ' + str(track_pos + 1))
# ------------------------------------------------------------------------------------------ #
def channel_track_insert(self, channel, uri, pos=0):
"""
Uri eines Audios in den (secondary) queue einfügen
@type channel: string
@param channel: Kanal
@type uri: string
@param uri: uri, z.b. file:///my/audio/song.mp3
@rtype: string
@return: Antwort des Controllers
"""
return self.command('channel_insert ' + channel + ' ' + urllib.quote(uri) + ' ' + str(pos))
# ------------------------------------------------------------------------------------------ #
def channel_set_volume(self, channel, volume):
"""
Lautstärke setzen (Prozentual von 1 - 100
@type channel: string
@param channel: Kanal
@type volume: string/int
@param volume: Zahl von 1 bis 100
@rtype: string
@return: Antwort des Controllers
"""
return self.command('channel_volume ' + channel + ' ' + str(volume))
# ------------------------------------------------------------------------------------------ #
def _get_channel_state(self, channel):
"""
Private: Status eines Kanals abfragen
Ausgabe Beispiel: {'ready':'true', 'selected:'false', 'single':'false','volume':'100%','remaining:'0.00'}
@type channel: string
@param channel: Kanal
@rtype: dict/boolen
@return: Antwort des Controllers/ False
"""
data = simplejson.loads(self.command('allData'))
if not isinstance(data, dict):
# es wurde kein assoz. Array/dict zurückgegeben
return False
if data['success'] != 'success':
# die Abfrage war nicht erfolgreich
return False
if not data.has_key('value'):
# es wurden keine Werte geliefert
return False
channels = data['value']
if not isinstance(channels, dict):
# es wurde kein assoz. Array/dict zurückgegeben
return False
if channels.has_key(channel):
chan = channels[channel]
try:
state = chan['state']
except KeyError:
return False
else:
return state
else:
return False
# ------------------------------------------------------------------------------------------ #
def playlist_load(self, uri):
"""
Playlist im XSPF-Format laden
@type uri: string
@param uri: Uri einer Playlist - z.B. /my/playlists/2014-12-22-12-00.xspf
@rtype: string
@return: Antwort des Controllers
"""
return self.command('playlist_load ' + str(uri))
# ------------------------------------------------------------------------------------------ #
def playlist_data(self):
"""
Gibt die aktuelle Playlist als dict zurück
@rtype: dict
@return: Antwort des Controllers
"""
return simplejson.loads(self.command('playlist_data'))
# ------------------------------------------------------------------------------------------ #
def playlist_flush(self):
"""
Leert die Playlist
@rtype: dict
@return: Antwort des Controllers
"""
return self.command('playlist_flush')
# ------------------------------------------------------------------------------------------ #
def playlist_insert(self, uri, pos):
"""
Audio in Playlist einfügen
@type uri: string
@param uri: Uri einer Audiodatei - z.B. /my/audio/song.mp3
@rtype: string
@return: Antwort des Controllers
"""
return self.command('playlist_insert ' + uri + ' ' + pos)
# ------------------------------------------------------------------------------------------ #
def playlist_track_up(self, track_pos):
"""
Einen Track der Playlist um eine Position nach oben schieben
@type track_pos: string
@param track_pos: Position des zu verschiebenden Eintrags
@rtype: string
@return: Antwort des Controllers
"""
return self.command('playlist_move ' + str(track_pos) + ' ' + str(track_pos - 1))
# ------------------------------------------------------------------------------------------ #
def playlist_track_down(self, track_pos):
"""
Einen Track der Playlist um eine Position nach unten schieben
@type track_pos: string
@param track_pos: Position des zu verschiebenden Eintrags
@rtype: string
@return: Antwort des Controllers
"""
self.command('playlist_move ' + str(track_pos) + ' ' + str(track_pos + 1))
# ------------------------------------------------------------------------------------------ #
def playlist_pause(self):
"""
Playlist anhalten
@rtype: string
@return: Antwort des Controllers
"""
return self.command('playlist_pause')
# ------------------------------------------------------------------------------------------ #
def playlist_stop(self):
"""
Playlist stoppen
@rtype: string
@return: Antwort des Controllers
"""
return self.command('playlist_stop')
# ------------------------------------------------------------------------------------------ #
def playlist_play(self):
"""
Playlist starten/abspielen
@rtype: string
@return: Antwort des Controllers
"""
return self.command('playlist_play')
# ------------------------------------------------------------------------------------------ #
def playlist_remove_track(self, track_pos):
"""
Löscht einen Track der Playlist
Hinweis: Der laufende Track wird nicht berücksichtigt
@type track_pos: string/int
@param track_pos: Position des zu entfernenden Eintrags
@rtype: string
@return: Antwort des Controllers
"""
return self.command('playlist_remove ' + str(track_pos))
# ------------------------------------------------------------------------------------------ #
def playlist_seek(self, duration):
"""
Spult den laufenden Track der Playlist <duration> Sekunden weiter (falls möglich)
Beispiel: playlist_seek('ch1',60) - 60 Sekunden nach vorne
@type duration: string/int
@param duration: Dauer in Sekunden
@rtype: string
@return: Antwort des Controllers
"""
return self.command('playlist_seek ' + str(duration))
# ------------------------------------------------------------------------------------------ #
def playlist_skip(self):
"""
Skipt den laufenden Track der Playlist
@rtype: string
@return: Antwort des Controllers
"""
return self.command('playlist_skip')
# ------------------------------------------------------------------------------------------ #
def recorder_start(self):
"""
Recorder starten
@rtype: string
@return: Antwort des Controllers
"""
return self.command('recorder_start')
# ------------------------------------------------------------------------------------------ #
def recorder_stop(self):
"""
Recorder stoppen
@rtype: string
@return: Antwort des Controllers
"""
return self.command('recorder_stop')
def recorder_data(self):
"""
Daten der aktuellen Aufnahme abrufen
Beispiel: {'file':'/my/audio/2014-12-22-12-00.wav', 'recorded': '25'} - Die Aufnahme der Datei /my/audio/2014-12-22-12-00.wav ist bei 25%
@rtype: dict
@return: Antwort des Controllers
"""
return simplejson.loads(self.command('recorder_data'))
\ No newline at end of file