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

Target

Select target project
  • aura/engine
  • hermannschwaerzler/engine
  • sumpfralle/aura-engine
3 results
Show changes
#!/bin/bash
pack_int(){ printf "%08X\n" $1 | sed 's/\([0-9A-F]\{2\}\)\([0-9A-F]\{2\}\)\([0-9A-F]\{2\}\)\([0-9A-F]\{2\}\)/\\\\\\x\4\\\\\\x\3\\\\\\x\2\\\\\\x\1/I' | xargs printf; }
pack_short(){ printf "%04X\n" $1 | sed 's/\([0-9A-F]\{2\}\)\([0-9A-F]\{2\}\)/\\\\\\x\2\\\\\\x\1/I' | xargs printf; }
duration=1800
if [[ $# -eq 1 ]]; then
duration=$1
fi
channels=2
bps=16
sample=44100
Subchunk1Size=18
Subchunk2Size=$(echo "$duration*$sample*$channels*$bps/8" | bc)
ChunkSize=$((20 + $Subchunk1Size + $Subchunk2Size))
echo -n RIFF
pack_int $ChunkSize
echo -n "WAVEfmt "
pack_int $Subchunk1Size
pack_short 1
pack_short $channels
pack_int $sample
pack_int $((bps/8 * channels * sample))
pack_short $((bps/8 * channels))
pack_short $bps
pack_short 0
echo -n data
pack_int $Subchunk2Size
dd if=/dev/zero bs=1 count=$Subchunk2Size 2>/dev/null
def read_ini(file)
ret = get_process_lines("cat "^file )
ret = list.map(string.split(separator="="), ret)
# l' => the filling list
def f(l',l)=
if list.length(l) >= 2 then
line = string.extract(pattern='"(.*)"', list.nth(l,1))
# print(line)
# print((list.hd(l),line['1']))
list.append([(list.hd(l),line['1'])],l')
else
if list.length(l) >= 1 then
list.append([(list.hd(l),"")],l')
else
directory = dirname()
ignore(directory)
list.add(("install_dir", "/home/gg/PycharmProjects/engine"), l')
# l'
end
end
end
list.fold(f,[],ret)
end
# shutdown server function
#server.register(namespace='server',
# description="shutdown server",
# usage="stop",
# "stop",
# fun(x,y) -> stop_server )
\ No newline at end of file
# LOG FILE SETTINGS
set("log.file.path", "./<script>.log")
# SERVER SETTINGS
set("server.telnet", true)
set("server.telnet.bind_addr", "0.0.0.0")
set("server.telnet.port", 1234)
set("server.socket", true)
set("server.socket.path", "./<script>.sock")
# SOUND CARD SETTINGS
input_device_0 = list.assoc("input_device[0]", ini)
#input_device_1 = list.assoc("input_device[1]", ini)
#input_device_2 = list.assoc("input_device[2]", ini)
output_device_0 = list.assoc("output_device[0]", ini)
ignore(input_device_0)
ignore(output_device_0)
# ALSA / pulse settings
use_alsa = list.assoc("use_alsa", ini) == "y"
frame_duration = float_of_string(list.assoc("frame_duration", ini))
frame_size = int_of_string(list.assoc("frame_size", ini))
alsa_buffer = int_of_string(list.assoc("alsa_buffer", ini))
alsa_buffer_length = int_of_string(list.assoc("alsa_buffer_length", ini))
alsa_periods = int_of_string(list.assoc("alsa_periods", ini))
if use_alsa then
if frame_duration > 0.0 then
print("setting frame.duration to #{frame_duration}")
set("frame.duration", frame_duration)
end
if frame_size > 0 then
print("setting frame.size to #{frame_size}")
set("frame.size", frame_size)
end
if alsa_buffer > 0 then
print("setting alsa.buffer to #{alsa_buffer}")
set("alsa.alsa_buffer", alsa_buffer)
end
if alsa_buffer > 0 then
print("setting alsa.buffer_length to #{alsa_buffer_length}")
set("alsa.buffer_length", alsa_buffer_length)
end
if alsa_periods > 0 then
print("setting alsa.periods to #{alsa_periods}")
set("alsa.periods", alsa_periods) # assertion error when setting periods other than 0 => alsa default
end
end
\ No newline at end of file
import os
import sys
import threading
import simplejson
import queue
import traceback
import urllib
import logging
from mutagen.flac import FLAC
from datetime import datetime, timedelta
from libraries.database.broadcasts import Schedule, ScheduleEntry
from libraries.enum.scheduleentrytype import ScheduleEntryType
from modules.communication.redis.messenger import RedisMessenger
class AuraCalendarService(threading.Thread):
messenger = RedisMessenger()
until = ""
audiobase = ""
playlistdir = ""
xmlplaylist = range(0)
has_already_fetched = False
queue = None
config = None
debug = False
_stop_event = None
logger = None
url = dict()
data = dict()
"""
Fetching playlist data, write it into the database and notify service
"""
def __init__(self, config, datefrom="", dateto=""):
threading.Thread.__init__(self)
self.config = config
self.logger = logging.getLogger("AuraEngine")
self.messenger.set_channel("aura")
self.messenger.set_section("calendar")
self.datefrom = str(datefrom)
self.dateto = dateto
self.queue = queue.Queue()
self._stop_event = threading.Event()
self.__set_url__("calendar")
self.__set_url__("importer")
# ------------------------------------------------------------------------------------------ #
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
"""
try:
# 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:
self.logger.warning("No start of schedule given. skipping the schedule "+str(schedule))
continue
if "end" not in schedule:
self.logger.warning("No end of schedule given. skipping the schedule "+str(schedule))
continue
# store the schedule
schedule_db = self.store_schedule(schedule)
# store playlists to play
self.store_schedule_playlist(schedule_db, schedule, "playlist")
self.store_schedule_playlist(schedule_db, schedule, "schedule_fallback", True)
self.store_schedule_playlist(schedule_db, schedule, "show_fallback", True)
self.store_schedule_playlist(schedule_db, schedule, "station_fallback", True)
# release the mutex
self.queue.put(schedule) #"fetching_finished")
except:
self.queue.put("fetching_aborted")
# terminate the thread
return
def store_schedule(self, schedule):
schedule_db = Schedule.query.filter(Schedule.schedule_id == schedule["schedule_id"]).first()
havetoadd = False
if not schedule_db:
self.logger.debug("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.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_db, schedule, playlistname, isfallbackplaylist=False):
playlist = schedule[playlistname]
info = "Schedule playlist (" + playlistname + ") for " + schedule_db.show_name + " stored"
warning = "No scheduleentries for playlist #" + str(playlist['playlist_id']) + " in schedule #" + str(schedule_db.schedule_id) + " found"
entrynum = 0
if "entries" in playlist:
lastentry = None
for entry in playlist["entries"]:
lastentry = self.store_playlist_entry(schedule_db, playlist, entry, lastentry, entrynum, isfallbackplaylist)
entrynum = entrynum + 1
if lastentry is None:
self.logger.warning(warning)
else:
self.logger.info(info)
else:
self.logger.warning(warning)
# ------------------------------------------------------------------------------------------ #
def store_playlist_entry(self, schedule_db, playlist, entry, lastentry, entrynum, isfallbackplaylist=False):
schedule_entry_db = ScheduleEntry.select_one(playlist["playlist_id"], entrynum)
havetoadd = False
if not schedule_entry_db:
self.logger.debug("no scheduleentry with id " + str(playlist["playlist_id"]) + " and pos " + str(entrynum) + " in database => creating a new one")
schedule_entry_db = ScheduleEntry()
havetoadd = True
schedule_entry_db.playlist_id = playlist["playlist_id"]
schedule_entry_db.entry_num = entrynum
schedule_entry_db.schedule_id = schedule_db.schedule_id
schedule_entry_db.source = entry["source"]
schedule_entry_db.is_fallback = isfallbackplaylist
schedule_entry_db.entry_start = schedule_db.schedule_start + timedelta(seconds=self.get_length(lastentry))
schedule_entry_db.calc_unix_times()
schedule_entry_db.define_clean_source()
self.logger.debug("Storing entries... playlist_id: " + str(playlist["playlist_id"]) + " schedule_id: " + str(schedule_db.schedule_id) + " num: " + str(entrynum))
schedule_entry_db.store(add=havetoadd, commit=True)
return schedule_entry_db
# ------------------------------------------------------------------------------------------ #
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:
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, "playlist_id", fetched_entries)
schedule["schedule_fallback"] = self.__fetch_schedule_entries__(schedule, "schedule_fallback_id", fetched_entries)
schedule["show_fallback"] = self.__fetch_schedule_entries__(schedule, "show_fallback_id", fetched_entries)
schedule["station_fallback"] = self.__fetch_schedule_entries__(schedule, "station_fallback_id", fetched_entries)
self.logger.info(str(schedule))
except Exception as e:
self.logger.error(str(e))
# ------------------------------------------------------------------------------------------ #
def __fetch_schedule_entries__(self, schedule, id_name, fetched_schedule_entries):
servicetype = "importer"
use_testdata = False
json_response = self.__fetch_data__(servicetype)
if not json_response:
use_testdata = True
for entry in fetched_schedule_entries:
if entry["playlist_id"] == schedule[id_name]:
self.logger.debug("playlist #" + str(schedule[id_name]) + " already fetched")
return entry
if use_testdata:
# HARDCODED Testdata
if schedule[id_name] == 0 or schedule[id_name] is None:
# this happens when playlist id is not filled out in pv
json_response = '{"playlist_id": 0}'
elif schedule[id_name] % 4 == 0: # playlist with two files
json_response = '{"playlist_id":' + str(schedule[id_name]) + ',"entries":[{"source":"file:///var/audio/fallback/music.flac"},{"source":"file:///var/audio/fallback/NightmaresOnWax/DJ-Kicks/01 - Type - Slow Process.flac"}]}'
elif schedule[id_name] % 3 == 0: # playlist with jingle and then http stream
json_response = '{"playlist_id":' + str(schedule[id_name]) + ',"entries":[{"source":"file:///var/audio/fallback/music.flac"},{"source":"linein://0"}]}'
elif schedule[id_name] % 2 == 0: # playlist with jingle and then linein
json_response = '{"playlist_id":' + str(schedule[id_name]) + ',"entries":[{"source":"file:///var/audio/fallback/music.flac"},{"source":"http://stream.fro.at:80/fro-128.ogg"}]}'
else: # pool playlist
json_response = '{"playlist_id":' + str(schedule[id_name]) + ',"entries":[{"source":"pool:///chillout"}]}'
self.logger.info("Using hardcoded playlists: "+json_response)
try:
schedule_entries = simplejson.loads(json_response)
except Exception as e:
self.logger.critical("Cannot fetch schedule entries from importer")
sys.exit()
if "entries" in schedule_entries:
for entry in schedule_entries["entries"]:
if entry["source"].startswith("file"):
e = entry["source"][7:] # filter file:// out
if not os.path.isfile(e):
self.logger.warning("File", e, "does not exist!")
fetched_schedule_entries.append(schedule_entries)
return schedule_entries
# ------------------------------------------------------------------------------------------ #
def __fetch_schedule_data__(self):
servicetype = "calendar"
use_testdata = False
html_response = self.__fetch_data__(servicetype)
if not html_response:
use_testdata = True
# if an error occours => use testdata
if use_testdata:
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.logger.critical("Cannot fetch schedule entries from PV")
sys.exit()
# check data
self.logger.critical("Hardcoded Response && no JSON data checks. I believe what i get here")
return self.fetched_schedule_data
# ------------------------------------------------------------------------------------------ #
def __fetch_data__(self, type):
# init html_response
html_response = ""
# open an url and read the data
try:
if type not in self.data:
if self.url[type] == "":
return False
request = urllib.request.Request(self.url[type])
else:
request = urllib.request.Request(self.url[type], self.data[type])
response = urllib.request.urlopen(request)
html_response = response.read()
except (urllib.error.URLError, IOError, ValueError) as e:
self.logger.error("Cannot connect to " + self.url[type] + "! reason: " + str(e.reason))
if not self.has_already_fetched: # first fetch
sys.exit()
self.has_already_fetched = True
return html_response
# ------------------------------------------------------------------------------------------ #
def get_length(self, entry):
if entry is None or entry.source == ScheduleEntryType.STREAM or entry.type == ScheduleEntryType.LIVE:
return 0
audio_file = FLAC(entry.cleansource)
return audio_file.info.length
# ------------------------------------------------------------------------------------------ #
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, type):
url = self.config.get(type+"url")
pos = url.find("?")
if pos > 0:
self.url[type] = url[0:pos]
self.data[type] = url[pos:]
else:
self.url[type] = url
# ------------------------------------------------------------------------------------------ #
def stop(self):
self._stop_event.set()
# ------------------------------------------------------------------------------------------ #
def get_calendar_data(self):
return self.fetched_schedule_data
# -*- coding: utf-8 -*-
#
# scheduler.py
#
# Copyright 2018 Radio FRO <https://fro.at>, Radio Helsinki <https://helsinki.at>, Radio Orange <https://o94.at>
#
# 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.0.1'
__license__ = "GNU General Public License (GPL) Version 3"
__version_info__ = (0, 0, 1)
__author__ = 'Gottfried Gaisbauer <gottfried.gaisbauer@servus.at>'
"""
Aura Scheduler
Is holding the eventqueue
"""
import time
import simplejson
import datetime
import decimal
import traceback
import sqlalchemy
import logging
import threading
# Die eigenen Bibliotheken
from modules.communication.redis.messenger import RedisMessenger
from modules.scheduling.calendar import AuraCalendarService
from libraries.database.broadcasts import Schedule, ScheduleEntry, AuraDatabaseModel
from libraries.exceptions.exception_logger import ExceptionLogger
from libraries.enum.scheduleentrytype import ScheduleEntryType
def alchemyencoder(obj):
"""JSON encoder function for SQLAlchemy special classes."""
if isinstance(obj, datetime.date):
return obj.isoformat()
elif isinstance(obj, decimal.Decimal):
return float(obj)
elif isinstance(obj, sqlalchemy.orm.state.InstanceState):
return ""
elif isinstance(obj, Schedule):
return simplejson.dumps([obj._asdict()], default=alchemyencoder)
else:
return str(obj)
class AuraScheduler(ExceptionLogger, threading.Thread):
"""
Aura Scheduler Class
Gets data from pv and importer, stores and fires events
"""
redismessenger = RedisMessenger()
message_timer = []
job_result = {}
liquidsoapcommunicator = None
schedule_entries = None
active_entry = None
programme = None
client = None
logger = None
config = None
tried_fetching = 0
fetch_max = 2
def __init__(self, config):
"""
Constructor
@type config: ConfigReader
@param config: read engine.ini
"""
self.config = config
self.logger = logging.getLogger("AuraEngine")
# init threading
threading.Thread.__init__(self)
# init messenger.. probably not needed anymore
self.redismessenger.set_channel('scheduler')
self.redismessenger.set_section('execjob')
# load schedulerconfig...
self.schedulerconfig = self.config.get("scheduler_config_file")
# load error messages
error_file = self.config.get("install_dir") + "/errormessages/scheduler_error.js"
f = open(error_file)
self.error_data = simplejson.load(f)
f.close()
# init database ?
self.init_database()
self.redismessenger.send('Scheduler started', '0000', 'success', 'initApp', None, 'appinternal')
# start loading new programm every hour
self.start()
# ------------------------------------------------------------------------------------------ #
def init_database(self):
# check if tables do exist. if not create them
try:
ScheduleEntry.select_all()
except sqlalchemy.exc.ProgrammingError as e:
errcode = e.orig.args[0]
if errcode == 1146: # error for no such table
x = AuraDatabaseModel()
x.recreate_db()
else:
raise
def run(self):
# set seconds to wait
seconds_to_wait = 3600
while True:
# calc next time
next_time = datetime.datetime.now() + datetime.timedelta(seconds=seconds_to_wait)
# write to logger
self.logger.info("Fetch new programmes every started. Going to start next time " + str(next_time))
# fetch new programme
self.fetch_new_programme()
# and wait
time.sleep(seconds_to_wait)
# ------------------------------------------------------------------------------------------ #
def get_active_entry(self):
now_unix = time.mktime(datetime.datetime.now().timetuple())
actsource = ""
lastentry = None
# load programme if necessary
if self.programme is None:
self.logger.debug("want to get active channel, but have to load programme first")
self.load_programme_from_db()
# get active source
for entry in self.programme:
# check if lastentry is set and if act entry is in the future
if lastentry is not None and entry.entry_start_unix > now_unix:
# return lastentry if so
return entry # actsource = entry.source
# break
lastentry = entry
return None
# ------------------------------------------------------------------------------------------ #
def load_programme_from_db(self, silent=False):
self.programme = ScheduleEntry.select_all()
# now in unixtime
now_unix = time.mktime(datetime.datetime.now().timetuple())
# switch to check if its the first stream in loaded programme
first_stream_in_programme = False
for entry in self.programme:
# since we get also programmes from act hour, filter these out
if entry.entry_start_unix > now_unix:
# when do we have to start?
diff = entry.entry_start_unix - now_unix
diff = diff/10000 # testing purpose
# create the activation threads and run them after <diff> seconds
if entry.source.startswith("linein"):
self.add_or_update_timer(entry, diff, self.liquidsoapcommunicator.activate)
elif entry.type == ScheduleEntryType.STREAM:
if first_stream_in_programme:
self.liquidsoapcommunicator.next_stream_source(entry.source)
first_stream_in_programme = False
self.add_or_update_timer(entry, diff, self.liquidsoapcommunicator.activate)
elif entry.type == ScheduleEntryType.FILESYSTEM:
self.add_or_update_timer(entry, diff, self.liquidsoapcommunicator.activate)
else:
self.logger.warning("Scheduler cannot understand source '" + entry.source + "' from " + str(entry))
self.logger.warning(" Not setting any activation Thread!")
self.logger.info(str(entry))
# ------------------------------------------------------------------------------------------ #
def add_or_update_timer(self, entry, diff, func):
# check if something is planned at given time
planned_timer = self.is_something_planned_at_time(entry.entry_start)
# if something is planned on entry.entry_start
if planned_timer:
planned_entry = planned_timer.entry
# check if the playlist_id's are different
if planned_entry.playlist_id != entry.playlist_id:
# if not stop the old timer and remove it from the list
self.stop_timer(planned_timer)
# and create a new one
self.create_timer(entry, diff, func)
# if the playlist id's do not differ => do nothing, they are the same
# if nothing is planned at given time, create a new timer
else:
self.create_timer(entry, diff, func)
# ------------------------------------------------------------------------------------------ #
def stop_timer(self, timer):
# stop timer
timer.cancel()
# and remove it from message queue
self.message_timer.remove(timer)
# ------------------------------------------------------------------------------------------ #
def create_timer(self, entry, diff, func):
t = CallFunctionTimer(diff, func, [entry])
self.message_timer.append(t)
t.start()
# ------------------------------------------------------------------------------------------ #
def is_something_planned_at_time(self, given_time):
for t in self.message_timer:
if t.entry.entry_start == given_time:
return t
return False
# ------------------------------------------------------------------------------------------ #
def find_entry_in_timers(self, entry):
# check if a playlist id is already planned
for t in self.message_timer:
if t.entry.playlist_id == entry.playlist_id and t.entry.entry_start == entry.entry_start:
return t
return False
# ------------------------------------------------------------------------------------------ #
def get_act_programme_as_string(self):
programme_as_string = ""
if self.programme is None or len(self.programme) == 0:
self.fetch_new_programme()
try:
programme_as_string = simplejson.dumps([p._asdict() for p in self.programme], default=alchemyencoder)
except Exception as e:
self.logger.error("Cannot transform programme into JSON String. Reason: " + str(e))
traceback.print_exc()
return programme_as_string
# ------------------------------------------------------------------------------------------ #
def print_message_queue(self):
message_queue = ""
for t in self.message_timer:
message_queue += t.get_info()+"\n"
return message_queue
# ------------------------------------------------------------------------------------------ #
def swap_playlist_entries(self, indexes):
from_entry = None
to_entry = None
from_idx = indexes["from_index"]
to_idx = indexes["to_index"]
# find the entries
for p in self.programme:
if p.programme_index == int(from_idx):
from_entry = p
if p.programme_index == int(to_idx):
to_entry = p
# break out of loop, if both entries found
if from_entry is not None and to_entry is not None:
break
# check if entries are found
if from_entry is None or to_entry is None:
return "From or To Entry not found!"
# swap sources
swap = from_entry.source
from_entry.source = to_entry.source
to_entry.source = swap
# store to database
from_entry.store(add=False, commit=False)
to_entry.store(add=False, commit=True)
# and return the programme with swapped entries
return self.get_act_programme_as_string()
# ------------------------------------------------------------------------------------------ #
def delete_playlist_entry(self, index):
found = False
for p in self.programme:
if p.programme_index == int(index):
p.delete(True)
self.load_programme_from_db()
found = True
break
if not found:
self.logger.warning("Nothing to delete")
return self.get_act_programme_as_string()
# ------------------------------------------------------------------------------------------ #
def insert_playlist_entry(self, fromtime_source):
fromtime = fromtime_source["fromtime"]
source = fromtime_source["source"]
entry = ScheduleEntry()
entry.entry_start = fromtime
entry.source = source
entry.playlist_id = 0
entry.schedule_id = 0
entry.entry_num = ScheduleEntry.select_next_manual_entry_num()
entry.store(add=True, commit=True)
self.load_programme_from_db()
return self.get_act_programme_as_string()
# ------------------------------------------------------------------------------------------ #
def fetch_new_programme(self):
if self.tried_fetching == self.fetch_max:
self.logger.error("Cannot connect to PV or Tank! No Programme loaded!")
self.tried_fetching = 0
return ""
self.tried_fetching += 1
acs = AuraCalendarService(self.config)
queue = acs.get_queue()
# start fetching thread
acs.start()
# wait for the end
response = queue.get()
if type(response) is dict:
self.load_programme_from_db()
if self.programme is not None and len(self.programme) > 0:
self.tried_fetching = 0
if len(self.programme) == 0 and self.tried_fetching == self.fetch_max:
self.logger.critical("Programme loaded from database has no entries!")
return self.get_act_programme_as_string()
elif response == "fetching_aborted":
self.logger.warning("Fetching was being aborted from AuraCalendarService! Are you connected?")
else:
self.logger.warning("Got an unknown response from AuraCalendarService: " + response)
# ------------------------------------------------------------------------------------------ #
def set_next_file_for(self, playlistname):
print(playlistname)
return ""
# ------------------------------------------------------------------------------------------ #
def start_recording(self, data):
"""
Aufnahme starten
"""
result = self.client.recorder_start()
if self.__check_result__(result):
self.success("start_recording", result, "00")
else:
self.error("start_recording", result, "01")
# ------------------------------------------------------------------------------------------ #
def stop_recording(self, data):
"""
Aufnahme anhalten
"""
result = self.client.recorder_stop()
if self.__check_result__(result):
self.success("stop_recording", result, "00")
else:
self.error("stop_recording", result, "01")
class CallFunctionTimer(threading.Timer):
logger = None
entry = None
debug = False
diff = None
def __init__(self, diff, func, param):
threading.Timer.__init__(self, diff, func, param)
self.diff = diff
self.func = func
self.entry = param[0]
self.logger = logging.getLogger("AuraEngine")
msg = "MessageTimer starting @ " + str(self.entry.entry_start) + " source '" + str(self.entry.source) + "' In seconds: " + str(self.diff)
self.logger.debug(msg)
from flask import request
from libraries.database.database import APP
from libraries.database.broadcasts import TrackService
class Routes:
error = None
def __init__(self):
APP.run()
@staticmethod
@APP.route('/')
@APP.route('/index')
def index():
return "Welcome to Aura. Please use the CLI Tool guru.py to manipulate the server. Web service is in planned..."
# request: http://localhost:5000/trackservice?from=2018-01-17T13:30:00&to=2018-01-17T16:00:00
@staticmethod
@APP.route("/trackservice", methods=["GET"])
def trackservice():
from_time = request.args.get("from")
to_time = request.args.get("to")
now = request.args.get("now")
if now == "":
entry = TrackService.now_playing()
return "from: " + str(from_time) + " to: " + str(to_time) + " now: " + str(now)
\ No newline at end of file
Flask==0.12.2
Flask-Babel==0.11.2
Flask-SQLAlchemy==2.2
Flask-WTF==0.14.2
mysqlclient==1.3.7
redis==2.10.5
simplejson==3.11.1
mutagen==1.38
python-dateutil==2.6.0
validators==0.12.1
[Unit]
Description=Aura Engine Playout Server
After=network.target
[Service]
Type=simple
User=gg
WorkingDirectory=/home/gg/PycharmProjects/engine
ExecStart=/home/gg/PycharmProjects/engine/aura.py
Restart=always
[Install]
WantedBy=multi-user.target
#!/usr/bin/python3
from libraries.database.broadcasts import *
import simplejson
import sqlalchemy
def alchemyencoder(obj):
"""JSON encoder function for SQLAlchemy special classes."""
if isinstance(obj, datetime.date):
return obj.isoformat()
elif isinstance(obj, decimal.Decimal):
return float(obj)
elif isinstance(obj, sqlalchemy.orm.state.InstanceState):
return ""
#elif isinstance(obj, Schedule):
# return simplejson.dumps([obj._asdict()], default=alchemyencoder)
else:
return str(obj)
def select_with_relationship():
se = TrackServiceScheduleEntry.select_all()
for e in se:
print(e._asdict())
# programme_as_string = simplejson.dumps([se[0]._asdict()], default=alchemyencoder)
# print(programme_as_string)
# # ## ## ## ## ## # #
# # ENTRY FUNCTION # #
# # ## ## ## ## ## # #
def main():
select_with_relationship()
# # ## ## ## ## ## ## # #
# # End ENTRY FUNCTION # #
# # ## ## ## ## ## ## # #
if __name__ == "__main__":
main()
import os
import unittest
import validators
from datetime import datetime
# libraries.base
from libraries.base.logger import AuraLogger
from libraries.base.config import AuraConfig
# libraries.database
from libraries.database.broadcasts import Schedule, ScheduleEntry, TrackService
# libraries.security
from libraries.security.user import AuraUser
# modules
from modules.communication.liquidsoap.communicator import LiquidSoapCommunicator
from modules.scheduling.scheduler import AuraScheduler
class TestLogger(unittest.TestCase):
aura_logger = None
def setUp(self):
self.aura_logger = AuraLogger()
def test_logger(self):
self.assertTrue(self.aura_logger.logger.hasHandlers())
class TestConfig(unittest.TestCase):
aura_config = None
def setUp(self):
self.aura_config = AuraConfig()
def test_config(self):
# is ini path correct set?
self.assertEqual(self.aura_config.config.ini_path, "/etc/aura/engine.ini")
# install_dir is set by runtime. is it a directory?
self.assertTrue(os.path.isdir(self.aura_config.config.get("install_dir")))
# calendarurl and importerurl set and valid urls?
self.assertTrue(validators.url(self.aura_config.config.get("calendarurl")))
self.assertTrue(validators.url(self.aura_config.config.get("importerurl")))
# is liquidsoap socketdir set and a directory?
self.assertTrue(os.path.isdir(self.aura_config.config.get("socketdir")))
# database settings set?
self.assertIsNotNone(self.aura_config.config.get("db_user"))
self.assertIsNotNone(self.aura_config.config.get("db_pass"))
self.assertIsNotNone(self.aura_config.config.get("db_name"))
self.assertIsNotNone(self.aura_config.config.get("db_host"))
class TestSchedule(unittest.TestCase):
schedule = None
def setUp(self):
self.schedule = Schedule()
def test_schedule(self):
# select one and check if its not None and a Schedule
entry = self.schedule.select_by_id(1)
self.assertIsNotNone(entry)
self.assertIsInstance(entry, Schedule)
class TestScheduleEntry(unittest.TestCase):
schedule_entry = None
def setUp(self):
self.schedule_entry = ScheduleEntry()
def test_schedule_entry(self):
# select one playlist and check if its not None, a ScheduleEntry
entry = self.schedule_entry.select_playlist(2)
self.assertIsNotNone(entry)
self.assertIsInstance(entry, list)
self.assertGreaterEqual(len(entry), 1)
class TestTrackService(unittest.TestCase):
track_service = None
def setUp(self):
self.track_service = TrackService()
def test_track_service(self):
day = datetime.strptime("19.03.2018", "%d.%m.%Y")
entry = self.track_service.select_by_day(day)
self.assertIsNotNone(entry)
self.assertIsInstance(entry, list)
class TestAuraUser(unittest.TestCase):
aura_user = None
def setUp(self):
self.aura_user = AuraUser()
def test_add_user(self):
username = "user"
password = "password"
role = "admin"
login_cnt = len(self.aura_user.getLogins())
# insert user
key = self.aura_user.insertUser(username, password, role)
self.assertGreaterEqual(len(self.aura_user.getLogins()), login_cnt)
# selecting user and check data
user = self.aura_user.getUserByKey(key)
self.assertEqual(user["username"], username)
# TODO: no encrypted storage.., but usermgm not really in use
self.assertEqual(user["password"], password)
self.assertEqual(user["role"], role)
class TestLQSComm(unittest.TestCase):
comm = None
def setUp(self):
# wosn do passiert?
p = AuraConfig().config
self.comm = LiquidSoapCommunicator(p)
self.comm.scheduler = AuraScheduler(p)
self.comm.init_player()
def test_get_active_channel(self):
active_channel = self.comm.get_active_channel()
print(active_channel)
if __name__ == '__main__':
unittest.main()
\ No newline at end of file