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 (30)
Showing
with 2720 additions and 0 deletions
# AURA Engine
This piece of Software is part of 'AURA - AUtomated RAdio'.
AURA Engine does:
* requesting the programme from an external Source
* switches the soundserver at the correct time to a given source for a specific show
* records what is broadcasted
## Installation
### Software
#### Operating System
Any sound supporting linux system should work. It is tested and coded on a **debian stretch**
#### Packages
On a debian machine:
```bash
sudo apt install \
git \
python3 python3-pip \
redis-server redis-tools \
liquidsoap liquidsoap-plugin-alsa liquidsoap-plugin-flac liquidsoap-plugin-icecast liquidsoap-plugin-pulseaudio \
libev4 libev-dev \
mariadb-server libmariadbclient-dev
```
#### Python Packages
```
sudo pip3 install \
Flask Flask-Babel Flask-SQLAlchemy Flask-WTF \
mysqlclient redis simplejson
pyev python-dateutil
```
#### get the code
git clone https://gitlab.servus.at/autoradio/engine
#### Set Up a database
mysql -u root -p
CREATE DATABASE aura_engine CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'aura'@'localhost' IDENTIFIED BY 'secure-password';
GRANT ALL PRIVILEGES ON aura_engine.* TO 'aura'@'localhost';
#### Files
Create the audio folder defined in your aura.ini
mkdir /var/audio
mkdir /etc/aura
cp {where you cloned the repo}/configuration/aura.ini /etc/aura/aura.ini
edit installation dir and database settings in aura.ini
#### aura.py
It is the server which is connected to the external programme source, to liquidsoap and is listening for redis pubsub messages.
#### Guru
The commandline tool for interacting with the server.
#### Liquidsoap
The heart of AURA Engine. It uses the built in mixer, to switch between different sources. A source can be a stream, the filesystem or linein
### Hardware
#### Soundcard
AURA Engine ist tested with an ASUS Xonar DGX. It should work with every by ALSA supported soundcard. PulseAudio support is planned.
#### Hard/Soft
When you use ALSA, you will have to play around with ALSA settings. In the folder ./modules/liquidsoap is a scipt called alsa_settings_tester.liq. You can start it with 'liquidsoap -v --debug alsa_settings_tester.liq'. Changing and playing with settings can help you to find correct ALSA settings.
import signal
import sys
import threading
from libraries.base.config import ConfigReader
from modules.controller.controller import AuraController
from modules.communication.redis.adapter import ServerRedisAdapter
from modules.web.routes import Routes
class Aura():
server = None
config = None
messenger = None
controller = None
# ------------------------------------------------------------------------------------------ #
def __init__(self):
self.config = ConfigReader()
self.config.loadConfig()
server = object
self.controller = AuraController(self.config)
self.messenger = ServerRedisAdapter()
self.messenger.set_controller(self.controller)
self.messenger.set_config(self.config)
def receive_signal(signum, stack):
print("received signal")
server.reload()
signal.signal(signal.SIGUSR1, receive_signal)
def join_comm(self):
# start listener thread
self.messenger.start()
def start_web_service(self):
r = Routes()
#r.app.run()
# # ## ## ## ## ## # #
# # ENTRY FUNCTION # #
# # ## ## ## ## ## # #
def main():
aura = Aura()
aura.join_comm()
aura.start_web_service()
# # ## ## ## ## ## ## # #
# # End ENTRY FUNCTION # #
# # ## ## ## ## ## ## # #
if __name__ == "__main__":
main()
\ No newline at end of file
station_name="Radio FRO"
station_logo="/etc/aura/stationlogo.jpg"
station_fallback_pool="/var/audio/station_fallback_pool"
#Change this settings
daemongroup="gg"
daemonuser="gg"
scheduler_config_file="/etc/aura/scheduler.xml"
# SOUND CARD SETTINGS
line_in_count=1
line_out_count=1
input_device[0]="hw:0" # make it comma separated!!
output_device[0]="hw:0"
# DATABASE SETTINGS
db_user="aura"
db_name="aura"
db_pass="aura"
db_host="localhost"
# ALSA SETTINGS
# if you have no idea what to do here => set use_alsa to "n", then pulseaudio is used
use_alsa="y"
# alsa_buffer => int
alsa_buffer="16000"
# alsa_buffer_length => int
alsa_buffer_length="7"
# alsa_periods => int
alsa_periods="0"
# frame_duration => double
frame_duration="0.4"
# frame_size => int
frame_size=""
socketdir="/home/gg/PycharmProjects/engine/modules/liquidsoap"
logdir="/var/log/aura"
# channelnames for mixing
# leave this alone if you do not know what you are doing
http_channels="http,http2"
line_in_channels="live,live2"
filesystem_channels="filesystem"
adminmail="gogo@servus.at"
playlistdir="/var/audio/playlists/"
#calendarurl="http://localhost/index.php?option=com_jimtawl&view=calendar&format=json&from=#datefrom#&to=#dateto#"
#calendarurl="http://bermudafunk-kalender.critmass.de/index.php?option=com_jimtawl&view=calendar&format=json&from=#datefrom#&to=#dateto#"
calendarurl="http://localhost:8000/api/v1/playout"
# how many days in future should the calendar be stored in database - default=7
#calendar_precache_days=7
audiobase="/var/audio/rec"
altaudiobase="/var/audio/preprod"
# hardware settings
# SOUNDCARD FROM STWST:
# HOME CARD
# was player_input_device:
# recinput="soundcard"
# altrecinput="soundcard"
# altrecorder_device="soundcard"
# recorder_device="soundcard"
# track_sensitive => fallback_folder track sensitivity
# max_blank => maximum time of blank from source
# min_noise => minimum duration of noise on source to switch back over
# threshold => power in dB under which the stream is considered silent
fallback_audio_folder="/var/audio/fallback/NightmaresOnWax/Smokers Delight"
fallback_max_blank="5"
fallback_min_noise="30"
fallback_threshold="-40"
stream="y"
stream_type="harbor"
#stream_type="icecast"
stream_bitrate="128"
stream_port="8000"
stream_mountpoint="aura"
stream_user="source"
stream_password="eegah5Hi"
stream_host="localhost"
stream_url="http://www.fro.at"
stream_name="Comba Test Stream"
stream_genre="mixed"
stream_description="Test Stream"
stream_admin_user="admin"
stream_admin_password="ahZ4caeg"
# ZeroMessagingQueue SETTINGS
communication="zmq"
zmqhostip="127.0.0.1"
zmqport="9099"
loglevel="info"
webservice_mode="apache"
#servername=""
#serviceport=""
install_dir="/home/gg/PycharmProjects/engine"
debug="y"
File added
<Config>
<Jobs multiple="true">
<job>
<time>00:00</time>
<until>23:00</until>
<job>play_playlist</job>
<params>no_stop</params>
</job>
<job>
<job>start_recording</job>
<until>00:00</until>
<day>all</day>
<time>00:00</time>
<params>no_stop</params>
</job>
<job>
<daysolder>4</daysolder>
<job>clean_cached</job>
<day>1</day>
<time>00:03</time>
<params></params>
</job>
<job>
<time>01:00</time>
<day>all</day>
<job>precache</job>
<params></params>
</job>
</Jobs>
</Config>
{
"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"
},
"get_act_programme": {
"id": "39",
"00": "Successfully fetched the program",
"01": "Cannot fetch actual program"
}
}
{
"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 sys
import redis
from argparse import ArgumentParser
# own libs
from libraries.base.config import ConfigReader
from modules.tools.padavan import Padavan
from libraries.exceptions.auraexceptions import FallbackException
class Guru:
config = ConfigReader()
config.loadConfig()
# ------------------------------------------------------------------------------------------ #
def __init__(self):
try:
parser = ArgumentParser()
# options
parser.add_argument("-sep", "--stop-execution-time", action="store_true", dest="stoptime", default=False,
help="Prints the execution time at the end of the skript")
parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", default=False,
help="Just the result will outputed to stout")
# getter
parser.add_argument("-gam", "--get-active-mixer", action="store_true", dest="get_active_mixer", default=False,
help="Which mixer is activated?")
parser.add_argument("-pms", "--print-mixer-status", action="store_true", dest="get_mixer_status", default=False,
help="Prints all mixer sources and their states")
parser.add_argument("-pap", "--print-act-programme", action="store_true", dest="get_act_programme", default=False,
help="Prints the actual Programme, the controller holds")
# liquid manipulation
parser.add_argument("-am", "--select-mixer", action="store", dest="select_mixer", default=-1, metavar="MIXERNUM",
help="Which mixer should be activated?", type=int)
parser.add_argument("-dm", "--de-select-mixer", action="store", dest="deselect_mixer", default=-1, metavar="MIXERNUM",
help="Which mixer should be activated?", type=int)
parser.add_argument("-vm", "--volume", action="store", dest="set_volume", default=0, metavar=("MIXERNUM","VOLUME"), nargs=2,
help="Set volume of a mixer source", type=int)
#parser.add_argument("-as", "--add-source", action="store", dest="add_source", default=False,
# help="Add new source to LiquidSoap mixer [Experimental]")
# playlist manipulation
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")
parser.add_argument("-spe", "--swap-playlist-entries", action="store", dest="swap_playlist_entries", default=0, metavar=("FROM", "TO"), nargs=2,
help="Swaps two Playlistentries")
parser.add_argument("-dpe", "--delete-playlist-entry", action="store", dest="delete_playlist_entry", default=0, metavar="INDEX",
help="Delete Playlistentry at INDEX")
parser.add_argument("-ipe", "--insert-playlist-entry", action="store", dest="insert_playlist_entry", default=0, metavar=("FROMTIME", "SOURCE"), nargs=2,
help="Add a new Playlistentry at a given index. Set fromtime with this format: 2017-12-31T13:30:00") # , type=valid_playlist_entry)
parser.add_argument("-pmq", "--print-message-queue", action="store_true", dest="print_message_queue", default=False,
help="Prints message queue")
# send a redis message
parser.add_argument("-rm", "--redis-message", action="store", dest="redis_message", default=False, metavar=("CHANNEL", "MESSAGE"), nargs=2,
help="Send a redis message to the Listeners")
# calls from liquidsoap
parser.add_argument("-gnf", "--get-next-fallback-file-for", action="store", dest="get_fallback_for", default=False, metavar="FALLBACKTYPE",
help="For which type you wanna GET a next audio file?")
parser.add_argument("-snf", "--set-next-fallback-file-for", action="store", dest="set_fallback_for", default=False, metavar=("FALLBACKTYPE", "FILE"), nargs=2,
help="For which type you wanna SET a next audio file?")
parser.add_argument("-np", "--now-playing", action="store", dest="now_playing", default=False, metavar="NOWPLAYINGSOURCE",
help="Which source is now playing")
parser.add_argument("-ip", "--init-player", action="store_true", dest="init_player", default=False,
help="Reset liquidsoap volume and mixer activations?")
args = parser.parse_args()
if len(sys.argv) == 1:
raise ValueError("No Argument passed!")
except ValueError as e:
parser.print_help()
print()
print(e)
exit(1)
except TypeError as e:
parser.print_help()
print()
print(e)
exit(2)
if args.stoptime:
start = time.time()
if not args.quiet:
print("Guru thinking...")
try:
p = Padavan(args, self.config)
p.meditate()
except FallbackException as fe:
print(fe)
exit(4)
except redis.exceptions.TimeoutError as te:
print(te)
print("Timeout when waiting for redis message. Is AURA daemon running? Exiting...")
exit(3)
if not args.quiet:
print("...result: ")
if p.stringreply != "":
if p.stringreply[len(p.stringreply)-1] == "\n":
print(p.stringreply[0:len(p.stringreply) - 1])
else:
print(p.stringreply[0:len(p.stringreply)])
if args.stoptime:
end = time.time()
exectime = end-start
print("execution time: "+str(exectime)+"s")
def valid_playlist_entry(argument):
from datetime import datetime
import argparse
try:
index = int(argument[0])
fromtime = datetime.strptime(argument[1], "%Y-%m-%d")
source = argument[2]
return index, fromtime, source
except:
msg = "Not a valid date: '{0}'.".format(argument[0])
raise
# # ## ## ## ## ## # #
# # ENTRY FUNCTION # #
# # ## ## ## ## ## # #
def main():
Guru()
# # ## ## ## ## ## ## # #
# # End ENTRY FUNCTION # #
# # ## ## ## ## ## ## # #
if __name__ == "__main__":
main()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# combac.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
import codecs
import os
import sys
import threading
import simplejson
import queue
import traceback
import urllib
from datetime import datetime, timedelta
from libraries.database.broadcasts import Schedule, ScheduleEntry
from modules.communication.redis.messenger import RedisMessenger
class AuraCalendarService(threading.Thread):
messenger = RedisMessenger()
audiobase = ""
playlistdir = ""
until = ''
xmlplaylist = range(0)
queue = None
config = None
debug = False
_stop_event = None
"""
Fetching playlist data, write it into the database and notify service
"""
def __init__(self, config, datefrom="", dateto="", debug=True):
threading.Thread.__init__(self)
self.config = config
self.debug = debug
self.messenger.set_channel('aura')
self.messenger.set_section('calendar')
self.messenger.set_mail_addresses(self.config.get('frommail'), self.config.get('adminmail'))
self.dateto = dateto
self.datefrom = str(datefrom)
self.queue = queue.Queue()
self._stop_event = threading.Event()
# ------------------------------------------------------------------------------------------ #
def set_date_from(self, date):
self.datefrom = str(date).replace(" ", "T")
# ------------------------------------------------------------------------------------------ #
def set_date_to(self, date):
self.dateto = str(date).replace(" ", "T")
# ------------------------------------------------------------------------------------------ #
def set_until_time(self, timestring):
self.until = timestring
# ------------------------------------------------------------------------------------------ #
def set_audio_path(self, path):
self.audiobase = path
# ------------------------------------------------------------------------------------------ #
def set_playlist_store(self, path):
self.playlistdir = path
# ------------------------------------------------------------------------------------------ #
def get_duration(self, start, end):
return self.__calc_duration__(start, end)
# ------------------------------------------------------------------------------------------ #
def get_queue(self):
return self.queue
# ------------------------------------------------------------------------------------------ #
def get_uri(self):
if not self.playlistdir:
return False
if not self.datefrom:
return False
if not self.__calc_date_to__():
return
hostname = self.get('servername');
port = self.get('serviceport');
date_from = self.datefrom[0:16] + ':00';
date_to = self.dateto[0:16] + ':00';
uri = 'http://' + hostname + ':' + port + '/playlist/' + date_from + '/' + date_to
return uri
# ------------------------------------------------------------------------------------------ #
def run(self):
"""
Fetch calendar data and store it in the database
"""
self.__set_url__()
# fetch upcoming schedules from ENGINE
self.__fetch_schedule_data__()
# fetch playlist and fallbacks to the schedules from TANK
self.__fetch_schedule_entry_data__()
for schedule in self.fetched_schedule_data:
if 'start' not in schedule:
print("WARNING: no start of schedule given. skipping the schedule")
continue
if 'end' not in schedule:
print("WARNING: no end of schedule given. skipping the schedule")
continue
# store the schedule
self.store_schedule(schedule)
# store playlists to play
self.store_schedule_playlist(schedule, "playlist")
self.store_schedule_playlist(schedule, "schedule_fallback", True)
self.store_schedule_playlist(schedule, "show_fallback", True)
self.store_schedule_playlist(schedule, "station_fallback", True)
# release the mutex
self.queue.put("fetching_finished")
# terminate the thread
return
def store_schedule(self, schedule):
from libraries.database.database import db
#schedule_db = Schedule.query.filter(Schedule.schedule_id == schedule['schedule_id']).first()
schedule_db = db.session.query(Schedule).filter(Schedule.schedule_id == schedule['schedule_id']).first()
# schedule_db = Schedule.get_by_id(schedule['schedule_id'])
havetoadd = False
if not schedule_db:
print("no schedule with given schedule id in database => create new")
schedule_db = Schedule()
havetoadd = True
# calc duration
duration = self.__calc_duration__(schedule['start'], schedule['end'])
schedule['duration'] = timedelta(seconds=duration).__str__()
schedule_db.show_id = schedule["show_id"]
schedule_db.schedule_id = schedule["schedule_id"]
schedule_db.schedule_start = schedule["start"]
schedule_db.schedule_end = schedule["end"]
schedule_db.show_name = schedule["show_name"]
schedule_db.show_hosts = schedule["show_hosts"]
schedule_db.is_repetition = schedule["is_repetition"]
schedule_db.rtr_category = schedule["show_rtrcategory"]
#schedule_db.comment = schedule["comment"]
schedule_db.languages = schedule["show_languages"]
schedule_db.type = schedule["show_type"]
schedule_db.category = schedule["show_categories"]
schedule_db.topic = schedule["show_topics"]
schedule_db.musicfocus = schedule["show_musicfocus"]
schedule_db.playlist_id = schedule["playlist_id"]
schedule_db.schedule_fallback_id = schedule["schedule_fallback_id"]
schedule_db.show_fallback_id = schedule["show_fallback_id"]
schedule_db.station_fallback_id = schedule["station_fallback_id"]
schedule_db.store(add=havetoadd, commit=True)
return schedule_db
# ------------------------------------------------------------------------------------------ #
def store_schedule_playlist(self, schedule, playlist, isfallbackplaylist=False):
entrynum = 0
if "entries" in schedule[playlist]:
for entry in schedule[playlist]["entries"]:
self.store_playlist(schedule["schedule_id"], schedule[playlist+"_id"], entry, entrynum, isfallbackplaylist)
entrynum = entrynum + 1
else:
print("WARNING: No scheduleentries for " + playlist + " in schedule #" + str(schedule["schedule_id"]) + " found")
# ------------------------------------------------------------------------------------------ #
def store_playlist(self, schedule_id, playlist_id, entry, entrynum, isfallbackplaylist=False):
schedule_entry = ScheduleEntry.select_one(playlist_id, entrynum)
havetoadd = False
if not schedule_entry:
if self.debug:
print("no scheduleentry with id " + str(playlist_id) + " and pos " + str(entrynum) + " in database => creating a new one")
schedule_entry = ScheduleEntry()
havetoadd = True
schedule_entry.playlist_id = playlist_id
schedule_entry.entry_num = entrynum
schedule_entry.schedule_id = schedule_id
schedule_entry.entry_start = self.set(entry, "entry_start")
schedule_entry.entry_end = self.set(entry, "entry_end")
schedule_entry.source = self.set(entry, "source")
schedule_entry.artist = self.set(entry, "artist")
schedule_entry.track = self.set(entry, "track")
schedule_entry.albumname = self.set(entry, "albumname")
schedule_entry.genre = self.set(entry, "genre")
schedule_entry.tracknum = self.set(entry, "tracknum")
schedule_entry.cdnum = self.set(entry, "cdnum")
schedule_entry.year = self.set(entry, "year")
schedule_entry.is_fallback = isfallbackplaylist
if schedule_entry.year == '':
schedule_entry.year = 0
if self.debug:
print("Storing entries... playlist_id: " + str(playlist_id) + " schedule_id: " + str(schedule_id) + " num: " + str(entrynum))
schedule_entry.store(add=havetoadd, commit=True)
return schedule_entry
# ------------------------------------------------------------------------------------------ #
def set(self, dictionary, field):
try:
return dictionary[field]
except KeyError:
return None
# ------------------------------------------------------------------------------------------ #
def __calc_date_to__(self):
if self.dateto:
return True
if not self.until:
return False
if not self.datefrom:
return False
date_start = datetime.strptime(self.datefrom.replace('T',' '), "%Y-%m-%d %H:%M")
time_start = date_start.strftime('%H:%M')
day_offset = 1 if (time_start > self.until) else 0
end_date = date_start + timedelta(day_offset)
self.dateto = end_date.strftime('%F') + 'T' + self.until
return True
# ------------------------------------------------------------------------------------------ #
@staticmethod
def __calc_duration__(start, end):
"""
Berechnet Zeit in Sekunden aus Differenz zwischen Start und Enddatum
@type start: datetime
@param start: Startzeit
@type end: datetime
@param end: Endzeit
@rtype: int
@return: Zeit in Sekunden
"""
sec1 = int(datetime.strptime(start[0:16].replace(" ","T"),"%Y-%m-%dT%H:%M").strftime("%s"));
sec2 = int(datetime.strptime(end[0:16].replace(" ","T"),"%Y-%m-%dT%H:%M").strftime("%s"));
return (sec2 - sec1);
# ------------------------------------------------------------------------------------------ #
def __fetch_schedule_entry_data__(self):
# store fetched entries => do not have to fetch playlist_id more than once
fetched_entries=[]
try:
url = self.get_url()
data = self.get_data()
except IOError as e:
self.messenger.send("Could not connect to service " + self.dataURL, '1101', 'error', 'fetchCalenderData', self.__get_error_data__(), 'getcalendar')
try:
for schedule in self.fetched_schedule_data:
# retrieve playlist and the fallbacks for every schedule
# if a playlist is already fetched, it is not fetched again
schedule["playlist"] = self.__fetch_schedule_entries__(schedule["schedule_id"], schedule["playlist_id"], fetched_entries)
schedule["schedule_fallback"] = self.__fetch_schedule_entries__(schedule["schedule_id"], schedule["schedule_fallback_id"], fetched_entries)
schedule["show_fallback"] = self.__fetch_schedule_entries__(schedule["schedule_id"], schedule["show_fallback_id"], fetched_entries)
schedule["station_fallback"] = self.__fetch_schedule_entries__(schedule["schedule_id"], schedule["station_fallback_id"], fetched_entries)
if True:
print("fetched for schedule_id: "+str(schedule["schedule_id"]))
print("playlist: "+str(schedule["playlist"]))
print("schedule_fallback: "+str(schedule["schedule_fallback"]))
print("show_fallback: "+str(schedule["show_fallback"]))
print("station_fallback: "+str(schedule["station_fallback"]))
except Exception as e:
traceback.print_exc()
print("houston we have a problem")
nullcounter = 0
# ------------------------------------------------------------------------------------------ #
def __fetch_schedule_entries__(self, schedule_id, playlist_id, fetched_schedule_entries):
schedule_entries = list()
for entry in fetched_schedule_entries:
if entry["playlist_id"] == playlist_id:
if self.debug:
print(str(playlist_id) + " already fetched")
return entry
if self.debug:
print("Wanna fetch playlist/pool id " + str(playlist_id) + " of schedule #" + str(schedule_id))
# HARDCODED Testdata
if playlist_id == 0 or playlist_id is None: # not filled playlist id in pv
#schedule_entries = simplejson.loads('{"playlist_id":'+str(self.nullcounter)+',"entries":[{"source":"pool:///nothing"}]}')
schedule_entries = simplejson.loads('{}')
self.nullcounter = self.nullcounter + 1
elif playlist_id == 1: # station fallback
schedule_entries = simplejson.loads('{"playlist_id":1,"entries":[{"source":"pool://easylistening","artist":"station wide fallback pool","track":"","albumname":"","genre":"","tracknum":"","cdnum":"","year":""}]}')
elif playlist_id == 2: # playlist to play for schedule #1 => linein
schedule_entries = simplejson.loads('{"playlist_id":2,"entries":[{"entry_start":"'+(datetime.now()+timedelta(hours=0)).strftime('%Y-%m-%d %H:00:00')+'","entry_end":"'+(datetime.now()+timedelta(hours=0)).strftime('%Y-%m-%d %H:05:00')+'","source":"file:///var/audio/fallback/music.flac","artist":"jingle","track":"","albumname":"","genre":"","tracknum":"","cdnum":"","year":"1999"},{"entry_start":"'+(datetime.now()+timedelta(hours=0)).strftime('%Y-%m-%d %H:05:00')+'","entry_end":"'+(datetime.now()+timedelta(hours=1)).strftime('%Y-%m-%d %H:00:00')+'","source":"linein://0","artist":"live","track":"","albumname":"","genre":"","tracknum":"","cdnum":"","year":""}]}')
elif playlist_id == 4: # playlist to play for schedule #2 => from file system
schedule_entries = simplejson.loads('{"playlist_id":4,"entries":[{"entry_start":"'+(datetime.now()+timedelta(hours=1)).strftime('%Y-%m-%d %H:00:00')+'","entry_end":"'+(datetime.now()+timedelta(hours=1)).strftime('%Y-%m-%d %H:05:00')+'","source":"file:///var/audio/fallback/music.flac","artist":"jingle","track":"","albumname":"","genre":"","tracknum":"","cdnum":"","year":"1999"},{"entry_start":"'+(datetime.now()+timedelta(hours=1)).strftime('%Y-%m-%d %H:05:00')+'","entry_end":"'+(datetime.now()+timedelta(hours=2)).strftime('%Y-%m-%d %H:00:00')+'","source":"file:///var/audio/fallback/NightmaresOnWax/DJ-Kicks/01 - Type - Slow Process.flac","artist":"live","track":"","albumname":"","genre":"","tracknum":"","cdnum":"","year":""}]}')
elif playlist_id == 6: # playlist to play for schedule #3 => http stream
schedule_entries = simplejson.loads('{"playlist_id":6,"entries":[{"entry_start":"'+(datetime.now()+timedelta(hours=2)).strftime('%Y-%m-%d %H:00:00')+'","entry_end":"'+(datetime.now()+timedelta(hours=3)).strftime('%Y-%m-%d %H:00:00')+'","source":"http://stream.fro.at:80/fro-128.ogg","artist":"Stream Übernahme von Radio FRO","track":"","albumname":"","genre":"","tracknum":"","cdnum":"","year":""}]}')
elif playlist_id == 12: # schedule #0 fallback
schedule_entries = simplejson.loads('{"playlist_id":12,"entries":[{"source":"pool:///drumandbass"}]}')
elif playlist_id == 22: # schedule #1 fallback
schedule_entries = simplejson.loads('{"playlist_id":22,"entries":[{"source":"pool:///chillout"}]}')
elif playlist_id == 32: # schedule #2 fallback
schedule_entries = simplejson.loads('{"playlist_id":22,"entries":[{"source":"pool:///rock"}]}')
elif playlist_id == 92: # show fallback for schedule #0
schedule_entries = simplejson.loads('{"playlist_id":92,"entries":[{"source":"file:///var/audio/playlist/yarn-01-03.m3u8"}]}')
elif playlist_id == 102: # show fallback for schedule #1
schedule_entries = simplejson.loads('{"playlist_id":102,"entries":[{"source":"file:///var/audio/playlist/yarn-04-06.m3u8"}]}')
elif playlist_id == 112: # show fallback for schedule #2
schedule_entries = simplejson.loads('{"playlist_id":112,"entries":[{"source":"file:///var/audio/playlist/yarn-07-10.m3u8"}]}')
if "entries" in schedule_entries:
for entry in schedule_entries["entries"]:
if entry["source"].startswith("file"):
e = entry["source"][7:]
if not os.path.isfile(e):
print("WARNING: File", e, "does not exist!")
# elif entry["source"].startswith("pool"):
fetched_schedule_entries.append(schedule_entries)
return schedule_entries
# ------------------------------------------------------------------------------------------ #
def __fetch_schedule_data__(self):
error = False
# Now open Calendar Url
try:
url = self.get_url()
data = self.get_data()
# if no url is set => use testdata
if url == "":
error = True
else:
if data == "":
request = urllib.request.Request(url)
else:
request = urllib.request.Request(url, data)
with urllib.request.urlopen(request) as response:
html_response = response.read()
except IOError as e:
traceback.print_exc()
error = True
except ValueError as e:
traceback.print_exc()
error = True
# if an error occours => use testdata
if error:
html_response = '[{"schedule_id":1,"schedule_start":"' + (datetime.now() + timedelta(hours=0)).strftime('%Y-%m-%d %H:00:00') + '","schedule_end":"' + (datetime.now() + timedelta(hours=1)).strftime('%Y-%m-%d %H:00:00') + '","show_id":9,"show_name":"FROzine","show_hosts":"Sandra Hochholzer, Martina Schweiger","is_repetition":false,"playlist_id":2,"schedule_fallback_id":12,"show_fallback_id":92,"station_fallback_id":1,"rtr_category":"string","comment":"Kommentar","languages":"Sprachen","type":"Typ","category":"Kategorie","topic":"Topic","musicfocus":"Fokus"},{"schedule_id":2,"schedule_start":"' + (datetime.now()+timedelta(hours=1)).strftime('%Y-%m-%d %H:00:00') + '","schedule_end":"' + (datetime.now()+timedelta(hours=2)).strftime('%Y-%m-%d %H:00:00') + '","show_id":10,"show_name":"FROMat","show_hosts":"Sandra Hochholzer, Martina Schweiger","is_repetition":false,"playlist_id":4,"schedule_fallback_id":22,"show_fallback_id":102,"station_fallback_id":1,"rtr_category":"string","comment":"Kommentar","languages":"Sprachen","type":"Typ","category":"Kategorie","topic":"Topic","musicfocus":"Fokus"},{"schedule_id":3,"schedule_start":"' + (datetime.now()+timedelta(hours=2)).strftime('%Y-%m-%d %H:00:00') + '","schedule_end":"' + (datetime.now() + timedelta(hours=3)).strftime('%Y-%m-%d %H:00:00') + '","show_id":11,"show_name":"Radio für Senioren","show_hosts":"Sandra Hochholzer, Martina Schweiger","is_repetition":false,"playlist_id":6,"schedule_fallback_id":32,"show_fallback_id":112,"station_fallback_id":1,"rtr_category":"string","comment":"Kommentar","languages":"Sprachen","type":"Typ","category":"Kategorie","topic":"Topic","musicfocus":"Fokus"}]'
try:
self.fetched_schedule_data = simplejson.loads(html_response)
except Exception as e:
self.messenger.send("Could not decode calender data from service " + self.dataURL + "! Exception: " + e, '1102', 'error',
'fetchCalenderData', self.__get_error_data__(), 'getcalendar')
sys.exit()
else:
# check data
try:
print("WARNING: Hardcoded Response && no json data checks. i believe what i get here")
#print(self.data)
#print(len(self.data))
except KeyError as e:
self.messenger.send("Could not decode calender data from service " + self.dataURL + "! Exception: " + e, '1102', 'error',
'fetchCalenderData', self.__get_error_data__(), 'getcalendar')
sys.exit()
except Exception as e:
self.messenger.send("Could not decode calender data from service " + self.dataURL + "! Exception: " + e, '1102', 'error',
'fetchCalenderData', self.__get_error_data__(), 'getcalendar')
sys.exit()
else:
return self.fetched_schedule_data
# ------------------------------------------------------------------------------------------ #
def __get_error_data__(self):
"""
Basisdaten als dict liefern
"""
return {'from': str(self.datefrom), 'dateto': str(self.dateto), 'path': "self.playlistpath", 'url': self.config.get("calendarurl")}
# ------------------------------------------------------------------------------------------ #
def __set_url__(self):
"""
Load calendar url from config file. replace datefrom and dateto
"""
self.dataURL = self.config.get("calendarurl").replace('#start#', self.datefrom.replace(' ', 'T')).replace('#end#', self.dateto.replace(' ', 'T'))
# ------------------------------------------------------------------------------------------ #
def get_url(self):
pos = self.dataURL.find('&')
if pos == -1:
url = self.dataURL
else:
url = self.dataURL[0:self.dataURL.find('&')]
print("url: "+url)
return url
# ------------------------------------------------------------------------------------------ #
def get_data(self):
pos = self.dataURL.find('&')
if pos == -1:
params = ""
else:
params = self.dataURL[pos:]
print("params: "+params)
return params
# ------------------------------------------------------------------------------------------ #
def stop(self):
self._stop_event.set()
# ------------------------------------------------------------------------------------------ #
def get_calendar_data(self):
return self.fetched_schedule_data
#!/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
if key == "debug":
return self.__dict__[key].count("y")
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)
__author__ = 'gg'
# -*- coding: utf-8 -*-
import datetime, os, urllib, sys
import time
import decimal
from sqlalchemy import orm, func, Boolean, Column, Date, DateTime, Float, Integer, String, Text, Time, ForeignKey, ForeignKeyConstraint
from sqlalchemy.orm import relationship
from sqlalchemy.sql.expression import false
from libraries.database.database import db
from libraries.enum.scheduleentrytypes import ScheduleEntryTypes
class AuraDatabaseModel:
def store(self, add=False, commit=False):
#obj_session = db.session.object_session(self)
#if obj_session is not None:
# obj_session.add(self)
# if commit:
# obj_session.commit()
if add:
db.session.add(self)
if commit:
db.session.commit()
def delete(self, commit=False):
#obj_session = db.session.object_session(self)
#if obj_session is not None:
# obj_session.delete(self)
# if commit:
# obj_session.commit()
db.session.delete(self)
if commit:
db.session.commit()
def _asdict(self):
return self.__dict__
@staticmethod
def recreate_db(systemexit = False):
manualschedule = Schedule()
manualschedule.schedule_id = 0
manualschedule.show_name = "Manual Show"
# fallback_trackservice_schedule = TrackServiceSchedule()
# fallback_trackservice_schedule.ts_schedule_id = 0
print("Recreating Database...")
db.drop_all()
print("all dropped. creating...")
db.create_all()
print("inserting manual scheduling possibility and fallback trackservice schedule")
db.session.add(manualschedule)
# db.session.add(fallback_trackservice_schedule)
print("all created. commiting...")
db.session.commit()
print("Database recreated!")
if systemexit:
sys.exit(0)
# ------------------------------------------------------------------------------------------ #
class Schedule(db.Model, AuraDatabaseModel):
"""
One specific Schedule for a show on a timeslot
"""
__tablename__ = 'schedule'
# primary and foreign keys
schedule_id = Column(Integer, primary_key=True, autoincrement=False)
show_id = Column(Integer) # well, not needed..
schedule_start = Column(DateTime) # can be null due to manual entries
schedule_end = Column(DateTime) # can be null due to manual entries
show_name = Column(String(256))
show_hosts = Column(String(256))
rtr_category = Column(String(256))
comment = Column(String(512))
languages = Column(String(256))
type = Column(String(256))
category = Column(String(256))
topic = Column(String(256))
musicfocus = Column(String(256))
is_repetition = Column(Boolean())
playlist_id = Column(Integer)
timeslot_fallback_id = Column(Integer)
show_fallback_id = Column(Integer)
station_fallback_id = Column(Integer)
def get_length(self):
sec1 = int(datetime.datetime.strptime(self.start[0:16].replace(" ", "T"), "%Y-%m-%dT%H:%M").strftime("%s"))
sec2 = int(datetime.datetime.strptime(self.end[0:16].replace(" ", "T"), "%Y-%m-%dT%H:%M").strftime("%s"))
len = sec2 - sec1
return len
# ------------------------------------------------------------------------------------------ #
class ScheduleEntry(db.Model, AuraDatabaseModel):
"""
One schedule can have multiple entries
"""
__tablename__ = 'schedule_entry'
# primary and foreign keys
playlist_id = Column(Integer, primary_key=True, nullable=False, autoincrement=False)
entry_num = Column(Integer, primary_key=True, nullable=False, autoincrement=False)
schedule_id = Column(Integer, ForeignKey("schedule.schedule_id"))
entry_start = Column(DateTime) # can be null due to fallback playlists
entry_end = Column(DateTime) # can be null due to fallback playlists
source = Column(String(256))
artist = Column(String(256))
track = Column(String(256))
albumname = Column(String(256))
genre = Column(String(256))
tracknum = Column(String(256))
cdnum = Column(String(256))
year = Column(Integer())
volume = Column(Integer, default=100)
is_fallback = Column(Boolean, default=False)
entry_start_unix = 0
entry_end_unix = 0
programme_index = -1
type = None
schedule = relationship("Schedule", foreign_keys=[schedule_id], lazy="joined")
def __init__(self, **kwargs):
super(ScheduleEntry, self).__init__(**kwargs)
self.calc_unix_times()
@orm.reconstructor
def reconstructor(self):
self.calc_unix_times()
self.set_entry_type()
def calc_unix_times(self):
if self.entry_start is not None:
self.entry_start_unix = time.mktime(self.entry_start.timetuple())
if self.entry_end is not None:
self.entry_end_unix = time.mktime(self.entry_end.timetuple())
def set_entry_type(self):
if self.source.startswith("http"):
self.type = ScheduleEntryTypes.HTTP
if self.source.startswith("pool") or self.source.startswith("playlist") or self.source.startswith("file") :
self.type = ScheduleEntryTypes.FILESYSTEM
if self.source.startswith("live"):
self.type = ScheduleEntryTypes.LIVE
# ------------------------------------------------------------------------------------------ #
@staticmethod
def select_all():
# when deleting all entries, and fetching new programmes, the entries are stored and commited in the code.
# but sqlalchemy thinks somehow it is not commit and returns an empty set
#print("WARNING: broadcasts.py This commit before SELECT is a BAND-AID & UGLY-HACK. Why the hell is there a transaction pending and not commited?")
#db.session.commit()
# fetching all entries
all_entries = db.session.query(ScheduleEntry).filter(ScheduleEntry.is_fallback == false()).order_by(ScheduleEntry.entry_start).all()
# BAND-AID debug output. The model and db session are different. crap
#print("broadcasts.py SELECT ALL ScheduleEntry.q.session == db.session?")
#print(ScheduleEntry.query.session == db.session)
#print("broadcasts.py SELECT ALL ScheduleEntry.q.s.conn == db.s.conn?")
#print(ScheduleEntry.query.session.connection() == db.session.connection())
cnt = 0
for entry in all_entries:
entry.programme_index = cnt
cnt = cnt + 1
return all_entries
@staticmethod
def select_next_manual_entry_num():
max_manual_entry_num = db.session.query(func.max(ScheduleEntry.entry_num)).filter(ScheduleEntry.schedule_id == 0).first()
print(max_manual_entry_num)
if max_manual_entry_num[0] is None:
return 0
else:
return int(max_manual_entry_num[0])+1
# print("returning", res)
# return res
# ------------------------------------------------------------------------------------------ #
@staticmethod
def upcoming(datefrom=datetime.datetime.now()):
# damn BAND-AID
# db.session.commit()
upcomingtracks = db.session.query(ScheduleEntry).filter(ScheduleEntry.start > datefrom).all()
#upcomingtracks = ScheduleEntry.query.filter(ScheduleEntry.start > datefrom).all()
return upcomingtracks
# ------------------------------------------------------------------------------------------ #
@staticmethod
def select_one(playlist_id, entry_num):
return db.session.query(ScheduleEntry).filter(ScheduleEntry.playlist_id == playlist_id, ScheduleEntry.entry_num == entry_num).first()
# ------------------------------------------------------------------------------------------ #
def __str__(self):
return "ScheduleEntry starts @ " + ScheduleEntry.entry_start + " and ends @ " + ScheduleEntry.entry_end + " and plays " + ScheduleEntry.source
# ------------------------------------------------------------------------------------------ #
class TrackService(db.Model, AuraDatabaseModel):
__tablename__ = 'trackservice'
trackservice_id = Column(Integer, primary_key=True, autoincrement=True)
playlist_id = Column(Integer, nullable=False)
entry_num = Column(Integer, nullable=False)
source = Column(String(255), nullable=False)
start = Column(DateTime, nullable=False, default=func.now())
__table_args__ = (
ForeignKeyConstraint(['playlist_id', 'entry_num'], ['schedule_entry.playlist_id', 'schedule_entry.entry_num']),
)
#schedule = relationship("Schedule", foreign_keys=[schedule_id], lazy="joined")
# trackservice_entry = relationship("ScheduleEntry", foreign_keys=[playlist_id, entry_num], lazy="joined")
schedule_entry = relationship("ScheduleEntry", primaryjoin="and_(TrackService.playlist_id==ScheduleEntry.playlist_id, TrackService.entry_num==ScheduleEntry.entry_num)", lazy="joined")
@staticmethod
def select_one(trackservice_id):
return db.session.query(TrackService).filter(TrackService.trackservice_id == trackservice_id).first()
# ------------------------------------------------------------------------------------------ #
# class TrackServiceSchedule(db.Model, AuraDatabaseModel):
# """
# Trackservice is tracking every schedule.
# """
# __tablename__ = 'trackservice_schedule'
#
# # primary and foreign keys
# ts_schedule_id = Column(Integer, primary_key=True, autoincrement=True)
# schedule_id = Column(Integer, ForeignKey("schedule.schedule_id"))
#
# schedule = relationship("Schedule", foreign_keys=[schedule_id], lazy="joined")
#
# # ------------------------------------------------------------------------------------------ #
# @staticmethod
# def select_one(schedule_id):
# # damn BAND-AID
# # db.session.commit()
#
# return db.session.query(ScheduleEntry).filter(TrackServiceSchedule.schedule_id == schedule_id).first()
#
# # ------------------------------------------------------------------------------------------ #
# class TrackServiceScheduleEntry(db.Model, AuraDatabaseModel):
# """
# And a schedule can have multiple entries
# """
# __tablename__ = 'trackservice_entry'
#
# # primary and foreign keys. the foreign keys here can be null, because of fallback stuff
# ts_entry_id = Column(Integer, primary_key=True, autoincrement=True)
# ts_schedule_id = Column(Integer, ForeignKey("trackservice_schedule.ts_schedule_id"), nullable=True)
# playlist_id = Column(Integer, nullable=True)
# entry_num = Column(Integer, nullable=True)
#
# fallback = Column(Boolean, default=False)
# fallback_start = Column(DateTime, nullable=True, default=None)
# source = Column(String(256), nullable=True, default=None)
#
# # foreign key definitions
# __table_args__ = (
# ForeignKeyConstraint(['playlist_id', 'entry_num'], ['schedule_entry.playlist_id', 'schedule_entry.entry_num']),
# )
#
# trackservice_schedule = relationship("TrackServiceSchedule", foreign_keys=[ts_schedule_id], lazy="joined")
# #trackservice_entry = relationship("ScheduleEntry", foreign_keys=[playlist_id, entry_num], lazy="joined")
# trackservice_entry = relationship("ScheduleEntry", primaryjoin="and_(TrackServiceScheduleEntry.playlist_id==ScheduleEntry.playlist_id, TrackServiceScheduleEntry.entry_num==ScheduleEntry.entry_num)" , lazy="joined")
#
# @staticmethod
# def select_all():
# return db.session.query(TrackServiceScheduleEntry).filter().all()
#AuraDatabaseModel.recreate_db(True)
import datetime, os, urllib
from sqlalchemy import Boolean, Column, Date, DateTime, Float, Integer, String, Text, Time, ForeignKey
from libraries.database.database import db
# ------------------------------------------------------------------------------------------ #
class BroadcastModel:
def store(self, commit=False):
db.session.add(self)
if commit:
self.commit()
def commit(self):
db.session.commit()
# ------------------------------------------------------------------------------------------ #
class Format(db.Model, BroadcastModel):
"""
ID of show format # Sendungsformat
"""
__tablename__ = 'format'
id = Column(Integer, primary_key=True)
name = Column(String(255))
# ------------------------------------------------------------------------------------------ #
class BroadcastPeriod(db.Model, BroadcastModel):
"""
Daten werden noch in der scheduling.xml gehalten
"""
__tablename__ = 'broadcast_period'
identifier = Column(Integer, primary_key=True)
start = Column(DateTime)
end = Column(DateTime)
meta = {
'ordering': ['+start']
}
# ------------------------------------------------------------------------------------------ #
class BroadcastEvent(db.Model, BroadcastModel):
"""
Definiert eine Sendung mit Start- und End-Datum
"""
__tablename__ = 'broadcast_event'
identifier = Column(Integer, primary_key=True, autoincrement=True) # Unique (o)id
id_ext = Column(String(30)) #
location = Column(String(512)) # archived audio file, will be available when the broadcast event is over
reccurrence_id = Column(Integer) # Unique id of event to be repeated
duration = Column(Integer) # duration in seconds
start = Column(DateTime, nullable=False, default=datetime.datetime.now()) # start date
end = Column(DateTime, nullable=False, default=datetime.datetime.now()+datetime.timedelta(minutes=60)) # end date
rerun = Column(Boolean, default=False) # true, if the event is a rerun
replay_of_datetime = Column(DateTime)
replay_of = Column(Integer, ForeignKey('broadcast_event.identifier'))
programme_id = Column(String(100))
station_name = Column(String(100))
station_id = Column(String(100))
title = Column(String(100))
subject = Column(String(200))
description = Column(String(300))
overwrite_event = Column(Integer, ForeignKey('broadcast_event.identifier'))
state = Column(String(50), default='created')
created = Column(DateTime, default=datetime.datetime.now)
modified = Column(DateTime, default=datetime.datetime.now)
modified_by = Column(String(50))
text = Column(String(400))
#data = DictField(required=False)
meta = {
'ordering': ['+start'],
'indexes': [
{'fields': ['title', "subject"],
'default_language': 'german',
'weight': {'title': 10, 'subject': 2}
}
]
}
def fileExists(self):
return os.path.exists(str(self.location).replace('file://', ''))
# ------------------------------------------------------------------------------------------ #
class BroadcastEventTrack(db.Model, BroadcastModel):
"""
Track, der einem BroadcastEvent zugeordnet ist
"""
__tablename__ = 'broadcast_event_track'
identifier = Column(Integer, primary_key=True) # Unique id
location = Column(String(512)) # audio location (url or file)
length = Column(Float) # duration in seconds
start = Column(DateTime) # start date
end = Column(DateTime) # end date
record_at = Column(DateTime) # The date on which the recording has to start.
broadcast_event = Column(Integer, ForeignKey('broadcast_event.identifier')) # the BroadcastEvent the track refers to
meta = {
'ordering': ['+start']
}
def isLast(self):
return self == BroadcastEventTrack.objects(broadcast_event=self.broadcast_event).order_by('-start').first()
def fileExists(self):
return os.path.exists(str(self.location).replace('file://', ''))
def totalLength(self):
tracks = BroadcastEventTrack.objects(broadcast_event=self.broadcast_event)
length = 0
for track in tracks:
length = length + track.length if track.length else length
return length#
# ------------------------------------------------------------------------------------------ #
class BroadcastEventOverride(db.Model, BroadcastModel): # Base
"""
Ein Track, der in die Sendeautomatisierung eingeblendet wird
Muss manuell erstellt werden
"""
__tablename__ = 'broadcast_event_override'
identifier = Column(Integer, primary_key=True) # Unique id
location = Column(String(512)) # audio location (url or file)
mimetype = Column(String(100)) # audio mime
bitrate = Column(Integer) # bitrate
channels = Column(Integer) # number of channels
length = Column(Float) # duration in seconds
start = Column(DateTime) # start date
end = Column(DateTime) # end date
seek = Column(Integer) # TODO: seconds to seek into the audio
ordering = Column(Integer, default=0) # track sorting
#data = DictField(required=False) # additional data
broadcast_event = Column(Integer, ForeignKey('broadcast_event.identifier')) # the BroadcastEvent the track refers to
meta = {
'ordering': ['+ordering']
}
def nextOrdering(self):
"""
Return number of next track in tracklist
@return: int ordering
"""
lastOverride = BroadcastEventOverride.objects(broadcast_event=self.broadcast_event)\
.order_by('-ordering')\
.first()
if lastOverride:
return lastOverride.ordering + 1
else:
return 1
def filled(self):
"""
what percent of the broadcast event is allready filled with audio
@return: percent filled with audio
"""
object = self.broadcast_event
parent_total = (object.end - object.start).total_seconds()
proc = 0
try:
proc = (self.totalLength() / parent_total) * 100
except:
proc = 0
return int(proc)
def totalLength(self):
"""
Get the total length of audio in the track list
@return: total length of audio
"""
tracks = BroadcastEventOverride.objects(broadcast_event=self.broadcast_event)
length = 0
for track in tracks:
if track.length:
length = length + track.length
return length
def fileExists(self):
request = urllib.Request(self.location)
request.get_method = lambda: 'HEAD'
try:
response = urllib.urlopen(request)
return True
except:
return False
from sqlalchemy.ext.declarative import declarative_base
from libraries.base.config import ConfigReader
from flask_sqlalchemy import SQLAlchemy
from flask_babel import Babel
from flask import Flask
Base = declarative_base()
#### load config ####
config = ConfigReader()
config.loadConfig()
#### read config ####
install_dir = config.get(str("install_dir"))
db_name = config.get(str("db_name"))
db_user = config.get(str("db_user"))
db_pass = config.get(str("db_pass"))
db_host = config.get(str("db_host"))
#### create database conn ####
app = Flask(__name__, template_folder=install_dir+'/modules/web/templates')
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://"+db_user+":"+db_pass+"@"+db_host+"/"+db_name+"?charset=utf8"
app.config['BABEL_DEFAULT_LOCALE'] = 'de'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
babel = Babel(app)
# -*- coding: utf-8 -*-
import redis
import time
import datetime
import simplejson
import re
import uuid
class RedisStateStore(object):
"""Store and get Reports from redis"""
def __init__(self, **redis_kwargs):
"""The default connection parameters are: host='localhost', port=6379, db=0"""
self.db= redis.Redis()
self.channel = '*'
self.section = '*'
self.separator = '_'
self.daily = False
# ------------------------------------------------------------------------------------------ #
def set_channel(self, channel):
"""
Kanal setzen
@type channel: string
@param channel: Kanal
"""
self.channel = channel
# ------------------------------------------------------------------------------------------ #
def set_section(self, section):
"""
Sektion setzen
@type section: string
@param section: Sektion
"""
self.section = section
# ------------------------------------------------------------------------------------------ #
def set_alive_state(self):
"""
Alive Funktion - alle 20 Sekunden melden, dass man noch am Leben ist
"""
self.set_state('alive', 'Hi', 21)
# ------------------------------------------------------------------------------------------ #
def get_alive_state(self, channel):
"""
Alive Status eines Channels ermitteln
@type channel: string
@param channel: der Channel
@rtype: string/None
@return: Ein String, oder None, bei negativem Ergebnis
"""
return self.get_state('alive', channel)
# ------------------------------------------------------------------------------------------ #
def set_state(self, name, value, expires=None, channel=None):
"""
Setzt einen Status
@type name: string
@param name: Name des state
@type value: string
@param value: Wert
@type channel: string
@param channel: Kanal (optional)
"""
if not channel:
channel = self.channel
key = self.__create_key__(channel + 'State', name)
if value == "":
self.db.delete(key)
else:
# publish on channel
message = simplejson.dumps({'eventname':name, 'value': value})
self.db.publish(channel + 'Publish', message)
# store in database
self.db.set(key, value)
if(expires):
self.db.expire(key, 21)
# ------------------------------------------------------------------------------------------ #
def get_state(self, name, channel):
"""
Holt einen Status
@type name: string
@param name: Name des state
@type channel: string
@param channel: Kanal (optional)
"""
key = self.__create_key__(channel + 'State', name)
return self.db.get(key)
# ------------------------------------------------------------------------------------------ #
def queue_add_event(self, eventtime, name, value, channel=None):
"""
Kündigt einen Event an
@type eventtime: string
@param eventtime: Datum und Zeit des events
@type name: string
@param name: Name des Events
@type value: dict
@param value: Werte
@type channel: string
@param channel: Kanal (optional)
"""
timeevent = datetime.datetime.strptime(eventtime[0:16],"%Y-%m-%dT%H:%M")
expire = int(time.mktime(timeevent.timetuple()) - time.time()) + 60
self.__set_event__(name, eventtime, value, 'Evqueue', 'evqueue', expire, channel)
# ------------------------------------------------------------------------------------------ #
def queue_remove_events(self, name=None, channel=None):
"""
Löscht Events
@type name: string
@param name: Name des Events
@type channel: string
@param channel: Kanal (optional)
"""
query = channel + 'Evqueue_' if channel else '*Evqueue_'
query = query + '*_' + name if name else query + '*_*'
keys = self.db.keys(query)
for delkey in keys:
self.db.delete(delkey)
# ------------------------------------------------------------------------------------------ #
def fire_event(self, name, value, channel=None):
"""
Feuert einen Event
@type name: string
@param name: Name des Events
@type value: dict
@param value: Werte
@type channel: string
@param channel: Kanal (optional)
"""
eventtime = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M")
self.__set_event__(name, eventtime, value, 'Event', 'events', 60, channel)
# ------------------------------------------------------------------------------------------ #
def __set_event__(self, name, eventtime, value, type, namespace, expire, channel=None):
"""
Feuert einen Event
@type eventtime: string
@param eventtime: Datum und Zeit des events
@type value: dict
@param value: Werte
@type channel: string
@param channel: Kanal (optional)
"""
if not channel:
channel = self.channel
timeevent = datetime.datetime.strptime(eventtime[0:16],"%Y-%m-%dT%H:%M")
key = self.__create_key__(channel + type, eventtime, name)
value['starts'] = eventtime[0:16]
value['eventchannel'] = channel
value['eventname'] = name
self.db.hset(key, namespace, value)
self.db.expire(key, expire)
# ------------------------------------------------------------------------------------------ #
def get_event_queue(self, name=None, channel=None):
"""
Holt events eines Kanals
@type channel: string
@param channel: Kanal (optional)
@rtype: list
@return: Liste der Events
"""
query = channel + 'Evqueue_' if channel else '*Evqueue_'
query = query + '*_' + name if name else query + '*_*'
keys = self.db.keys(query)
keys.sort()
entries = self.__get_entries__(keys, 'evqueue')
return entries
# ------------------------------------------------------------------------------------------ #
def get_events(self, name=None, channel=None):
"""
Holt events eines Kanals
@type channel: string
@param channel: Kanal (optional)
@rtype: list
@return: Liste der Events
"""
query = channel + 'Event_' if channel else '*Event_'
query = query + '*_' + name if name else query + '*_*'
keys = self.db.keys(query)
keys.sort()
entries = self.__get_entries__(keys, 'events')
return entries
# ------------------------------------------------------------------------------------------ #
def get_next_event(self, name=None, channel=None):
"""
Holt den aktuellsten Event
@type channel: string
@param channel: Kanal (optional)
@rtype: dict/boolean
@return: ein Event oder False
"""
events = self.get_event_queue(name, channel)
if len(events) > 0:
result = events.pop(0)
else:
result = False
return result
# ------------------------------------------------------------------------------------------ #
def store(self, level, value):
"""
Hash speichern
@type level: string
@param level: der errorlevel
@type value: dict
@param value: Werte als dict
"""
microtime = str(time.time())
value['microtime'] = microtime
value['level'] = level
key = self.__create_key__(self.channel, self.section, level, microtime, str(uuid.uuid1()))
self.db.hset(key, self.channel, value)
self.db.expire(key, 864000)
# ------------------------------------------------------------------------------------------ #
def __get_keys__(self, level ='*'):
"""
Redis-Keys nach Suchkriterium ermitteln
@type level: string
@param level: einen Errorlevel filtern
@rtype: list
@return: Die Keys auf die das Suchkriterium zutrifft
"""
key = self.__create_key__(self.channel, self.section, level)
microtime = str(time.time())
search = microtime[0:4] + '*' if self.daily else '*'
return self.db.keys(key + self.separator + '*')
# ------------------------------------------------------------------------------------------ #
def __create_key__(self, *args):
"""
Key erschaffen - beliebig viele Argumente
@rtype: string
@return: Der key
"""
return self.separator.join(args)
def get_entries(self, level ='*'):
"""
Liste von Hashs nach Suchkriterium erhalten
@type level: string
@param level: einen Errorlevel filtern
@rtype: list
@return: Redis Hashs
"""
def tsort(x,y):
if float(x.split('_',4)[3]) > float(y.split('_',4)[3]):
return 1
elif float(x.split('_',4)[3]) < float(y.split('_',4)[3]):
return -1
else:
return 0
keys = self.__get_keys__(level)
keys.sort(tsort)
entries = self.__get_entries__(keys, self.channel)
entries = sorted(entries, key=lambda k: k['microtime'], reverse=True)
return entries
# ------------------------------------------------------------------------------------------ #
def __get_entries__(self, keys, channel):
entries = []
for key in keys:
entry = self.db.hget(key,channel)
entry = simplejson.dumps(entry.decode('utf-8'))
if not (entry is None):
try:
entry = entry.decode('utf-8').replace('None','"None"')
entry = re.sub("########[^]]*########", lambda x:x.group(0).replace('\"','').replace('\'',''),entry.replace("\\\"","########").replace("\\'","++++++++").replace("'",'"').replace('u"','"').replace('"{','{').replace('}"','}')).replace("########","\"")
entry = simplejson.loads(entry)
entry['key'] = key
entries.append(entry)
except:
pass
return entries
# ------------------------------------------------------------------------------------------ #
def publish(self, channel, message):
subscriber_count = self.db.execute_command('PUBSUB', 'NUMSUB', channel)
if channel.lower().find("reply") < 0 and subscriber_count[1] == 0:
raise Exception("No subscriber! Is Aura daemon running?")
self.db.publish(channel, message)
__author__ = 'gg'
# -*- coding: utf-8 -*-
from sqlalchemy import Boolean, Column, Date, DateTime, Float, Integer, String, Text, Time, ForeignKey
from libraries.database.database import db
from enum import Enum
class TerminalColors(Enum):
HEADER = '\033[95m'
OK_GREEN = "\033[32m"
OK_ORANGE = '\033[33m'
OK_BLUE = '\033[34m'
OK_PINK = '\033[35m'
WARNING = '\033[31m'
FAIL = '\033[41m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
ENDC = "\033[0m"
\ No newline at end of file
from enum import Enum
class Channels(Enum):
STANDARD = "aura"
# MAIN = "main"
IP_REPLY = "init_player_reply"
FNP_REPLY = "fetch_new_programme_reply"
GAP_REPLY = "get_act_programme_reply"
PMQ_REPLY = "print_message_queue_reply"
MPE_REPLY = "move_playlist_entry_reply"
DPE_REPLY = "delete_playlist_entry_reply"
IPE_REPLY = "insert_playlist_entry_reply"
\ No newline at end of file
from enum import Enum
class ScheduleEntryTypes(Enum):
HTTP = "http"
LIVE = "live"
FILESYSTEM = "fs"