2011-03-03 06:22:28 +01:00
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import time
|
2011-08-15 22:11:50 +02:00
|
|
|
import calendar
|
2011-03-03 06:22:28 +01:00
|
|
|
import logging
|
|
|
|
import logging.config
|
|
|
|
import shutil
|
|
|
|
import random
|
|
|
|
import string
|
|
|
|
import json
|
2011-03-04 02:13:55 +01:00
|
|
|
import telnetlib
|
2011-03-21 00:34:43 +01:00
|
|
|
import math
|
2012-02-12 05:53:43 +01:00
|
|
|
from threading import Thread
|
2011-03-24 03:14:57 +01:00
|
|
|
from subprocess import Popen, PIPE
|
2011-06-14 23:17:15 +02:00
|
|
|
from datetime import datetime
|
2011-06-16 00:23:12 +02:00
|
|
|
from datetime import timedelta
|
2011-08-15 22:10:46 +02:00
|
|
|
import filecmp
|
2011-03-23 06:09:27 +01:00
|
|
|
|
2011-03-03 06:22:28 +01:00
|
|
|
from api_clients import api_client
|
|
|
|
|
|
|
|
from configobj import ConfigObj
|
|
|
|
|
2011-03-21 00:34:43 +01:00
|
|
|
# configure logging
|
|
|
|
logging.config.fileConfig("logging.cfg")
|
|
|
|
|
2011-03-03 06:22:28 +01:00
|
|
|
# loading config file
|
|
|
|
try:
|
2011-03-30 00:32:53 +02:00
|
|
|
config = ConfigObj('/etc/airtime/pypo.cfg')
|
2011-03-03 06:22:28 +01:00
|
|
|
LS_HOST = config['ls_host']
|
|
|
|
LS_PORT = config['ls_port']
|
2011-03-23 06:09:27 +01:00
|
|
|
POLL_INTERVAL = int(config['poll_interval'])
|
2011-03-21 00:34:43 +01:00
|
|
|
|
2011-03-03 06:22:28 +01:00
|
|
|
except Exception, e:
|
2011-06-01 18:32:42 +02:00
|
|
|
logger = logging.getLogger()
|
|
|
|
logger.error('Error loading config file: %s', e)
|
2011-03-03 06:22:28 +01:00
|
|
|
sys.exit()
|
|
|
|
|
2011-03-21 00:34:43 +01:00
|
|
|
class PypoFetch(Thread):
|
2012-02-28 20:44:39 +01:00
|
|
|
def __init__(self, pypoFetch_q, pypoPush_q):
|
2011-03-21 00:34:43 +01:00
|
|
|
Thread.__init__(self)
|
2011-03-03 06:22:28 +01:00
|
|
|
self.api_client = api_client.api_client_factory(config)
|
2012-02-23 02:41:24 +01:00
|
|
|
|
2012-02-28 20:44:39 +01:00
|
|
|
self.fetch_queue = pypoFetch_q
|
|
|
|
self.push_queue = pypoPush_q
|
2012-02-23 02:41:24 +01:00
|
|
|
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger = logging.getLogger();
|
|
|
|
|
2012-02-23 02:41:24 +01:00
|
|
|
self.cache_dir = os.path.join(config["cache_dir"], "scheduler")
|
|
|
|
logger.debug("Cache dir %s", self.cache_dir)
|
|
|
|
try:
|
|
|
|
if not os.path.exists(dir):
|
|
|
|
logger.debug("Cache dir does not exist. Creating...")
|
|
|
|
os.makedirs(dir)
|
|
|
|
except Exception, e:
|
|
|
|
logger.error(e)
|
|
|
|
|
2011-08-15 22:10:46 +02:00
|
|
|
self.schedule_data = []
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("PypoFetch: init complete")
|
2011-08-15 22:10:46 +02:00
|
|
|
|
|
|
|
"""
|
|
|
|
Handle a message from RabbitMQ, put it into our yucky global var.
|
|
|
|
Hopefully there is a better way to do this.
|
|
|
|
"""
|
2012-02-12 05:53:43 +01:00
|
|
|
def handle_message(self, message):
|
|
|
|
try:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Received event from Pypo Message Handler: %s" % message)
|
2011-09-13 20:56:24 +02:00
|
|
|
|
2012-02-12 05:53:43 +01:00
|
|
|
m = json.loads(message)
|
2011-09-13 20:56:24 +02:00
|
|
|
command = m['event_type']
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Handling command: " + command)
|
2011-08-15 22:10:46 +02:00
|
|
|
|
2011-09-13 20:56:24 +02:00
|
|
|
if command == 'update_schedule':
|
|
|
|
self.schedule_data = m['schedule']
|
2012-02-27 19:52:35 +01:00
|
|
|
self.process_schedule(self.schedule_data, False)
|
2011-09-13 20:56:24 +02:00
|
|
|
elif command == 'update_stream_setting':
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Updating stream setting...")
|
2011-09-13 20:56:24 +02:00
|
|
|
self.regenerateLiquidsoapConf(m['setting'])
|
2012-02-11 00:43:40 +01:00
|
|
|
elif command == 'update_stream_format':
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Updating stream format...")
|
2012-02-11 00:43:40 +01:00
|
|
|
self.update_liquidsoap_stream_format(m['stream_format'])
|
|
|
|
elif command == 'update_station_name':
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Updating station name...")
|
2012-02-11 00:43:40 +01:00
|
|
|
self.update_liquidsoap_station_name(m['station_name'])
|
2011-09-13 20:56:24 +02:00
|
|
|
elif command == 'cancel_current_show':
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Cancel current show command received...")
|
2011-09-13 20:56:24 +02:00
|
|
|
self.stop_current_show()
|
|
|
|
except Exception, e:
|
2012-02-28 20:44:39 +01:00
|
|
|
import traceback
|
|
|
|
top = traceback.format_exc()
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.error('Exception: %s', e)
|
|
|
|
self.logger.error("traceback: %s", top)
|
|
|
|
self.logger.error("Exception in handling Message Handler message: %s", e)
|
|
|
|
|
2011-08-15 22:10:46 +02:00
|
|
|
|
2011-08-29 17:54:44 +02:00
|
|
|
def stop_current_show(self):
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.debug('Notifying Liquidsoap to stop playback.')
|
2011-08-29 17:54:44 +02:00
|
|
|
try:
|
|
|
|
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
|
|
|
|
tn.write('source.skip\n')
|
|
|
|
tn.write('exit\n')
|
|
|
|
tn.read_all()
|
|
|
|
except Exception, e:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.debug(e)
|
|
|
|
self.logger.debug('Could not connect to liquidsoap')
|
2011-08-29 17:54:44 +02:00
|
|
|
|
2011-08-15 22:10:46 +02:00
|
|
|
def regenerateLiquidsoapConf(self, setting):
|
|
|
|
existing = {}
|
|
|
|
# create a temp file
|
|
|
|
fh = open('/etc/airtime/liquidsoap.cfg', 'r')
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Reading existing config...")
|
2011-08-15 22:10:46 +02:00
|
|
|
# read existing conf file and build dict
|
|
|
|
while 1:
|
|
|
|
line = fh.readline()
|
|
|
|
if not line:
|
2011-08-23 16:12:18 +02:00
|
|
|
break
|
2011-08-29 23:44:28 +02:00
|
|
|
|
|
|
|
line = line.strip()
|
2011-08-23 16:12:18 +02:00
|
|
|
if line.find('#') == 0:
|
|
|
|
continue
|
2011-08-29 23:44:28 +02:00
|
|
|
# if empty line
|
|
|
|
if not line:
|
|
|
|
continue
|
2012-02-24 20:10:27 +01:00
|
|
|
key, value = line.split(' = ')
|
2011-08-15 22:10:46 +02:00
|
|
|
key = key.strip()
|
|
|
|
value = value.strip()
|
|
|
|
value = value.replace('"', '')
|
2011-08-18 19:53:12 +02:00
|
|
|
if value == "" or value == "0":
|
2011-08-15 22:10:46 +02:00
|
|
|
value = ''
|
|
|
|
existing[key] = value
|
|
|
|
fh.close()
|
2011-08-18 19:53:12 +02:00
|
|
|
|
|
|
|
# dict flag for any change in cofig
|
|
|
|
change = {}
|
|
|
|
# this flag is to detect diable -> disable change
|
|
|
|
# in that case, we don't want to restart even if there are chnges.
|
|
|
|
state_change_restart = {}
|
|
|
|
#restart flag
|
|
|
|
restart = False
|
2011-08-15 22:10:46 +02:00
|
|
|
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Looking for changes...")
|
2011-08-15 22:10:46 +02:00
|
|
|
# look for changes
|
|
|
|
for s in setting:
|
2011-09-01 22:02:06 +02:00
|
|
|
if "output_sound_device" in s[u'keyname'] or "icecast_vorbis_metadata" in s[u'keyname']:
|
2011-08-18 19:53:12 +02:00
|
|
|
dump, stream = s[u'keyname'].split('_', 1)
|
|
|
|
state_change_restart[stream] = False
|
|
|
|
# This is the case where restart is required no matter what
|
|
|
|
if (existing[s[u'keyname']] != s[u'value']):
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("'Need-to-restart' state detected for %s...", s[u'keyname'])
|
2011-08-18 19:53:12 +02:00
|
|
|
restart = True;
|
|
|
|
else:
|
2011-08-24 23:13:56 +02:00
|
|
|
stream, dump = s[u'keyname'].split('_',1)
|
|
|
|
if "_output" in s[u'keyname']:
|
|
|
|
if (existing[s[u'keyname']] != s[u'value']):
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("'Need-to-restart' state detected for %s...", s[u'keyname'])
|
2011-08-24 23:13:56 +02:00
|
|
|
restart = True;
|
2011-08-26 19:13:02 +02:00
|
|
|
state_change_restart[stream] = True
|
2011-08-24 23:13:56 +02:00
|
|
|
elif ( s[u'value'] != 'disabled'):
|
2011-08-18 19:53:12 +02:00
|
|
|
state_change_restart[stream] = True
|
2011-08-26 19:13:02 +02:00
|
|
|
else:
|
|
|
|
state_change_restart[stream] = False
|
2011-08-24 23:13:56 +02:00
|
|
|
else:
|
2011-08-18 19:53:12 +02:00
|
|
|
# setting inital value
|
|
|
|
if stream not in change:
|
|
|
|
change[stream] = False
|
|
|
|
if not (s[u'value'] == existing[s[u'keyname']]):
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Keyname: %s, Curent value: %s, New Value: %s", s[u'keyname'], existing[s[u'keyname']], s[u'value'])
|
2011-08-18 19:53:12 +02:00
|
|
|
change[stream] = True
|
|
|
|
|
|
|
|
# set flag change for sound_device alway True
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Change:%s, State_Change:%s...", change, state_change_restart)
|
2011-08-18 19:53:12 +02:00
|
|
|
|
|
|
|
for k, v in state_change_restart.items():
|
|
|
|
if k == "sound_device" and v:
|
|
|
|
restart = True
|
|
|
|
elif v and change[k]:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("'Need-to-restart' state detected for %s...", k)
|
2011-08-18 19:53:12 +02:00
|
|
|
restart = True
|
2011-08-15 22:10:46 +02:00
|
|
|
# rewrite
|
2011-08-18 19:53:12 +02:00
|
|
|
if restart:
|
2011-08-15 22:10:46 +02:00
|
|
|
fh = open('/etc/airtime/liquidsoap.cfg', 'w')
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Rewriting liquidsoap.cfg...")
|
2011-08-23 16:12:18 +02:00
|
|
|
fh.write("################################################\n")
|
|
|
|
fh.write("# THIS FILE IS AUTO GENERATED. DO NOT CHANGE!! #\n")
|
|
|
|
fh.write("################################################\n")
|
2011-08-15 22:10:46 +02:00
|
|
|
for d in setting:
|
|
|
|
buffer = d[u'keyname'] + " = "
|
|
|
|
if(d[u'type'] == 'string'):
|
|
|
|
temp = d[u'value']
|
|
|
|
if(temp == ""):
|
2011-08-18 19:53:12 +02:00
|
|
|
temp = ""
|
2011-08-15 22:10:46 +02:00
|
|
|
buffer += "\"" + temp + "\""
|
|
|
|
else:
|
|
|
|
temp = d[u'value']
|
|
|
|
if(temp == ""):
|
|
|
|
temp = "0"
|
|
|
|
buffer += temp
|
|
|
|
buffer += "\n"
|
2011-12-15 22:09:58 +01:00
|
|
|
fh.write(api_client.encode_to(buffer))
|
2011-08-24 23:13:56 +02:00
|
|
|
fh.write("log_file = \"/var/log/airtime/pypo-liquidsoap/<script>.log\"\n");
|
2011-08-15 22:10:46 +02:00
|
|
|
fh.close()
|
2011-08-18 19:53:12 +02:00
|
|
|
# restarting pypo.
|
|
|
|
# we could just restart liquidsoap but it take more time somehow.
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Restarting pypo...")
|
2012-02-10 20:53:22 +01:00
|
|
|
sys.exit(0)
|
2011-08-15 22:10:46 +02:00
|
|
|
else:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("No change detected in setting...")
|
2011-12-24 16:59:09 +01:00
|
|
|
self.update_liquidsoap_connection_status()
|
|
|
|
"""
|
|
|
|
updates the status of liquidsoap connection to the streaming server
|
|
|
|
This fucntion updates the bootup time variable in liquidsoap script
|
|
|
|
"""
|
|
|
|
def update_liquidsoap_connection_status(self):
|
|
|
|
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
|
|
|
|
# update the boot up time of liquidsoap. Since liquidsoap is not restarting,
|
|
|
|
# we are manually adjusting the bootup time variable so the status msg will get
|
|
|
|
# updated.
|
|
|
|
current_time = time.time()
|
|
|
|
boot_up_time_command = "vars.bootup_time "+str(current_time)+"\n"
|
|
|
|
tn.write(boot_up_time_command)
|
|
|
|
tn.write("streams.connection_status\n")
|
|
|
|
tn.write('exit\n')
|
|
|
|
|
|
|
|
output = tn.read_all()
|
|
|
|
output_list = output.split("\r\n")
|
|
|
|
stream_info = output_list[2]
|
|
|
|
|
|
|
|
# streamin info is in the form of:
|
|
|
|
# eg. s1:true,2:true,3:false
|
|
|
|
streams = stream_info.split(",")
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info(streams)
|
2011-12-24 16:59:09 +01:00
|
|
|
|
|
|
|
fake_time = current_time + 1
|
|
|
|
for s in streams:
|
|
|
|
info = s.split(':')
|
|
|
|
stream_id = info[0]
|
|
|
|
status = info[1]
|
|
|
|
if(status == "true"):
|
|
|
|
self.api_client.notify_liquidsoap_status("OK", stream_id, str(fake_time))
|
|
|
|
|
2012-02-11 00:43:40 +01:00
|
|
|
def update_liquidsoap_stream_format(self, stream_format):
|
|
|
|
# Push stream metadata to liquidsoap
|
|
|
|
# TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!!
|
|
|
|
try:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info(LS_HOST)
|
|
|
|
self.logger.info(LS_PORT)
|
2012-02-11 00:43:40 +01:00
|
|
|
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
|
|
|
|
command = ('vars.stream_metadata_type %s\n' % stream_format).encode('utf-8')
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info(command)
|
2012-02-11 00:43:40 +01:00
|
|
|
tn.write(command)
|
|
|
|
tn.write('exit\n')
|
|
|
|
tn.read_all()
|
|
|
|
except Exception, e:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.error("Exception %s", e)
|
2012-02-11 00:43:40 +01:00
|
|
|
|
|
|
|
def update_liquidsoap_station_name(self, station_name):
|
2011-03-23 06:09:27 +01:00
|
|
|
# Push stream metadata to liquidsoap
|
|
|
|
# TODO: THIS LIQUIDSOAP STUFF NEEDS TO BE MOVED TO PYPO-PUSH!!!
|
2011-03-03 06:22:28 +01:00
|
|
|
try:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info(LS_HOST)
|
|
|
|
self.logger.info(LS_PORT)
|
2011-03-23 06:09:27 +01:00
|
|
|
tn = telnetlib.Telnet(LS_HOST, LS_PORT)
|
2012-02-11 00:43:40 +01:00
|
|
|
command = ('vars.station_name %s\n' % station_name).encode('utf-8')
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info(command)
|
2012-02-11 00:43:40 +01:00
|
|
|
tn.write(command)
|
2011-03-23 06:09:27 +01:00
|
|
|
tn.write('exit\n')
|
|
|
|
tn.read_all()
|
|
|
|
except Exception, e:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.error("Exception %s", e)
|
2012-02-11 00:43:40 +01:00
|
|
|
|
|
|
|
"""
|
|
|
|
Process the schedule
|
|
|
|
- Reads the scheduled entries of a given range (actual time +/- "prepare_ahead" / "cache_for")
|
|
|
|
- Saves a serialized file of the schedule
|
|
|
|
- playlists are prepared. (brought to liquidsoap format) and, if not mounted via nsf, files are copied
|
|
|
|
to the cache dir (Folder-structure: cache/YYYY-MM-DD-hh-mm-ss)
|
|
|
|
- runs the cleanup routine, to get rid of unused cached files
|
|
|
|
"""
|
2012-02-23 02:41:24 +01:00
|
|
|
def process_schedule(self, schedule_data, bootstrapping):
|
2012-02-27 19:52:35 +01:00
|
|
|
media = schedule_data["media"]
|
2011-03-03 06:22:28 +01:00
|
|
|
|
2011-06-15 21:49:42 +02:00
|
|
|
# Download all the media and put playlists in liquidsoap "annotate" format
|
2011-03-21 00:34:43 +01:00
|
|
|
try:
|
2012-02-27 19:52:35 +01:00
|
|
|
media = self.prepare_media(media, bootstrapping)
|
|
|
|
except Exception, e: self.logger.error("%s", e)
|
2011-03-21 00:34:43 +01:00
|
|
|
|
2011-03-23 06:09:27 +01:00
|
|
|
# Send the data to pypo-push
|
2011-03-21 00:34:43 +01:00
|
|
|
scheduled_data = dict()
|
2011-03-03 06:22:28 +01:00
|
|
|
|
2012-02-27 19:52:35 +01:00
|
|
|
scheduled_data['liquidsoap_annotation_queue'] = liquidsoap_annotation_queue
|
|
|
|
self.push_queue.put(media)
|
|
|
|
|
|
|
|
"""
|
2011-03-03 06:22:28 +01:00
|
|
|
# cleanup
|
2012-02-23 02:41:24 +01:00
|
|
|
try: self.cleanup()
|
2012-02-27 19:52:35 +01:00
|
|
|
except Exception, e: self.logger.error("%s", e)
|
|
|
|
"""
|
2011-03-03 06:22:28 +01:00
|
|
|
|
2012-02-27 19:52:35 +01:00
|
|
|
|
2011-03-03 06:22:28 +01:00
|
|
|
|
2012-02-27 19:52:35 +01:00
|
|
|
def prepare_media(self, media, bootstrapping):
|
|
|
|
"""
|
|
|
|
Iterate through the list of media items in "media" and
|
|
|
|
download them.
|
|
|
|
"""
|
2011-03-03 06:22:28 +01:00
|
|
|
try:
|
2012-02-27 19:52:35 +01:00
|
|
|
mediaKeys = sorted(media.iterkeys())
|
|
|
|
for mkey in mediaKeys:
|
|
|
|
self.logger.debug("Media item starting at %s", mkey)
|
|
|
|
media_item = media[mkey]
|
|
|
|
|
|
|
|
if bootstrapping:
|
|
|
|
check_for_crash(media_item)
|
2011-03-03 06:22:28 +01:00
|
|
|
|
|
|
|
# create playlist directory
|
|
|
|
try:
|
2012-02-27 19:52:35 +01:00
|
|
|
"""
|
|
|
|
Extract year, month, date from mkey
|
|
|
|
"""
|
|
|
|
y_m_d = mkey[0:10]
|
|
|
|
download_dir = os.mkdir(os.path.join(self.cache_dir, y_m_d))
|
|
|
|
fileExt = os.path.splitext(media_item['uri'])[1]
|
|
|
|
dst = os.path.join(download_dir, media_item['id']+fileExt)
|
2011-03-03 06:22:28 +01:00
|
|
|
except Exception, e:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.warning(e)
|
|
|
|
|
|
|
|
if self.handle_media_file(media_item, dst):
|
|
|
|
entry = create_liquidsoap_annotation(media_item, dst)
|
|
|
|
#entry['show_name'] = playlist['show_name']
|
|
|
|
entry['show_name'] = "TODO"
|
|
|
|
media_item["annotation"] = entry
|
2011-06-15 21:49:42 +02:00
|
|
|
|
2011-03-03 06:22:28 +01:00
|
|
|
except Exception, e:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.error("%s", e)
|
|
|
|
|
|
|
|
return media
|
|
|
|
|
2011-03-23 06:09:27 +01:00
|
|
|
|
2012-02-27 19:52:35 +01:00
|
|
|
def create_liquidsoap_annotation(media, dst):
|
|
|
|
pl_entry = \
|
|
|
|
'annotate:media_id="%s",liq_start_next="%s",liq_fade_in="%s",liq_fade_out="%s",liq_cue_in="%s",liq_cue_out="%s",schedule_table_id="%s":%s' \
|
|
|
|
% (media['id'], 0, \
|
|
|
|
float(media['fade_in']) / 1000, \
|
|
|
|
float(media['fade_out']) / 1000, \
|
|
|
|
float(media['cue_in']), \
|
|
|
|
float(media['cue_out']), \
|
|
|
|
media['row_id'], dst)
|
|
|
|
|
|
|
|
"""
|
|
|
|
Tracks are only added to the playlist if they are accessible
|
|
|
|
on the file system and larger than 0 bytes.
|
|
|
|
So this can lead to playlists shorter than expectet.
|
|
|
|
(there is a hardware silence detector for this cases...)
|
|
|
|
"""
|
|
|
|
entry = dict()
|
|
|
|
entry['type'] = 'file'
|
|
|
|
entry['annotate'] = pl_entry
|
|
|
|
return entry
|
2011-06-16 00:23:12 +02:00
|
|
|
|
2012-02-27 19:52:35 +01:00
|
|
|
def check_for_crash(media_item):
|
|
|
|
start = media_item['start']
|
|
|
|
end = media_item['end']
|
2011-06-16 00:23:12 +02:00
|
|
|
|
2011-12-02 23:28:57 +01:00
|
|
|
dtnow = datetime.utcnow()
|
2011-06-16 00:23:12 +02:00
|
|
|
str_tnow_s = dtnow.strftime('%Y-%m-%d-%H-%M-%S')
|
2012-02-27 19:52:35 +01:00
|
|
|
|
|
|
|
if start <= str_tnow_s and str_tnow_s < end:
|
|
|
|
#song is currently playing and we just started pypo. Maybe there
|
|
|
|
#was a power outage? Let's restart playback of this song.
|
|
|
|
start_split = map(int, start.split('-'))
|
|
|
|
media_start = datetime(start_split[0], start_split[1], start_split[2], start_split[3], start_split[4], start_split[5], 0, None)
|
|
|
|
self.logger.debug("Found media item that started at %s.", media_start)
|
2011-06-16 00:23:12 +02:00
|
|
|
|
2012-02-27 19:52:35 +01:00
|
|
|
delta = dtnow - media_start #we get a TimeDelta object from this operation
|
|
|
|
self.logger.info("Starting media item at %d second point", delta.seconds)
|
2011-06-16 00:23:12 +02:00
|
|
|
|
2012-02-27 19:52:35 +01:00
|
|
|
"""
|
|
|
|
Set the cue_in. This is used by Liquidsoap to determine at what point in the media
|
|
|
|
item it should start playing. If the cue_in happens to be > cue_out, then make cue_in = cue_out
|
|
|
|
"""
|
|
|
|
media_item['cue_in'] = delta.seconds + 10 if delta.seconds + 10 < media_item['cue_out'] else media_item['cue_out']
|
|
|
|
|
|
|
|
"""
|
|
|
|
Set the start time, which is used by pypo-push to determine when a media item is scheduled.
|
|
|
|
Pushing the start time into the future will ensure pypo-push will push this to Liquidsoap.
|
|
|
|
"""
|
|
|
|
td = timedelta(seconds=10)
|
|
|
|
media_item['start'] = (dtnow + td).strftime('%Y-%m-%d-%H-%M-%S')
|
|
|
|
self.logger.info("Crash detected, setting playlist to restart at %s", (dtnow + td).strftime('%Y-%m-%d-%H-%M-%S'))
|
|
|
|
|
|
|
|
def handle_media_file(self, media_item, dst):
|
|
|
|
"""
|
|
|
|
Download and cache the media item.
|
|
|
|
"""
|
|
|
|
|
|
|
|
self.logger.debug("Processing track %s", media_item['uri'])
|
2011-03-03 06:22:28 +01:00
|
|
|
|
2012-02-27 19:52:35 +01:00
|
|
|
try:
|
|
|
|
#blocking function to download the media item
|
|
|
|
self.download_file(media_item, dst)
|
|
|
|
|
|
|
|
if os.access(dst, os.R_OK):
|
|
|
|
# check filesize (avoid zero-byte files)
|
|
|
|
try:
|
|
|
|
fsize = os.path.getsize(dst)
|
2011-03-03 06:22:28 +01:00
|
|
|
if fsize > 0:
|
2012-02-27 19:52:35 +01:00
|
|
|
return True
|
|
|
|
except Exception, e:
|
|
|
|
self.logger.error("%s", e)
|
|
|
|
fsize = 0
|
|
|
|
else:
|
|
|
|
self.logger.warning("Cannot read file %s.", dst)
|
2011-03-03 06:22:28 +01:00
|
|
|
|
2012-02-27 19:52:35 +01:00
|
|
|
except Exception, e:
|
|
|
|
self.logger.info("%s", e)
|
|
|
|
|
|
|
|
return False
|
2011-03-03 06:22:28 +01:00
|
|
|
|
|
|
|
|
2011-03-23 06:09:27 +01:00
|
|
|
"""
|
|
|
|
Download a file from a remote server and store it in the cache.
|
|
|
|
"""
|
2012-02-27 19:52:35 +01:00
|
|
|
def download_file(self, media_item, dst):
|
2011-06-15 21:49:42 +02:00
|
|
|
if os.path.isfile(dst):
|
|
|
|
pass
|
2012-02-27 19:52:35 +01:00
|
|
|
#self.logger.debug("file already in cache: %s", dst)
|
2011-03-03 06:22:28 +01:00
|
|
|
else:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.debug("try to download %s", media_item['uri'])
|
|
|
|
self.api_client.get_media(media_item['uri'], dst)
|
2011-03-03 06:22:28 +01:00
|
|
|
|
2011-03-23 06:09:27 +01:00
|
|
|
"""
|
|
|
|
Cleans up folders in cache_dir. Look for modification date older than "now - CACHE_FOR"
|
|
|
|
and deletes them.
|
|
|
|
"""
|
2012-02-23 02:41:24 +01:00
|
|
|
def cleanup(self):
|
2011-03-03 06:22:28 +01:00
|
|
|
offset = 3600 * int(config["cache_for"])
|
|
|
|
now = time.time()
|
|
|
|
|
|
|
|
for r, d, f in os.walk(self.cache_dir):
|
|
|
|
for dir in d:
|
|
|
|
try:
|
2011-08-15 22:11:50 +02:00
|
|
|
timestamp = calendar.timegm(time.strptime(dir, "%Y-%m-%d-%H-%M-%S"))
|
2011-03-03 06:22:28 +01:00
|
|
|
if (now - timestamp) > offset:
|
|
|
|
try:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.debug('trying to remove %s - timestamp: %s', os.path.join(r, dir), timestamp)
|
2011-03-03 06:22:28 +01:00
|
|
|
shutil.rmtree(os.path.join(r, dir))
|
|
|
|
except Exception, e:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.error("%s", e)
|
2011-03-03 06:22:28 +01:00
|
|
|
pass
|
|
|
|
else:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info('sucessfully removed %s', os.path.join(r, dir))
|
2011-03-03 06:22:28 +01:00
|
|
|
except Exception, e:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.error(e)
|
2011-03-03 06:22:28 +01:00
|
|
|
|
2011-03-23 06:09:27 +01:00
|
|
|
|
2011-09-08 18:17:42 +02:00
|
|
|
def main(self):
|
2011-03-23 06:09:27 +01:00
|
|
|
try: os.mkdir(self.cache_dir)
|
|
|
|
except Exception, e: pass
|
|
|
|
|
|
|
|
# Bootstrap: since we are just starting up, we need to grab the
|
|
|
|
# most recent schedule. After that we can just wait for updates.
|
2012-02-27 19:52:35 +01:00
|
|
|
success, self.schedule_data = self.api_client.get_schedule()
|
|
|
|
if success:
|
|
|
|
self.logger.info("Bootstrap schedule received: %s", self.schedule_data)
|
|
|
|
self.process_schedule(self.schedule_data, True)
|
2011-09-20 19:25:29 +02:00
|
|
|
|
2011-03-23 06:09:27 +01:00
|
|
|
loops = 1
|
2011-03-21 00:34:43 +01:00
|
|
|
while True:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.info("Loop #%s", loops)
|
2012-02-12 05:53:43 +01:00
|
|
|
try:
|
2012-02-27 19:52:35 +01:00
|
|
|
message = self.fetch_queue.get(block=True, timeout=3600)
|
|
|
|
self.handle_message(message)
|
2011-09-08 18:17:42 +02:00
|
|
|
except Exception, e:
|
|
|
|
"""
|
2012-02-12 05:53:43 +01:00
|
|
|
There is a problem with the RabbitMq messenger service. Let's
|
|
|
|
log the error and get the schedule via HTTP polling
|
2011-09-08 18:17:42 +02:00
|
|
|
"""
|
2012-02-27 19:52:35 +01:00
|
|
|
self.logger.error("Exception, %s", e)
|
2012-02-12 05:53:43 +01:00
|
|
|
|
|
|
|
status, self.schedule_data = self.api_client.get_schedule()
|
|
|
|
if status == 1:
|
2012-02-27 19:52:35 +01:00
|
|
|
self.process_schedule(self.schedule_data, False)
|
2011-09-08 18:17:42 +02:00
|
|
|
|
2012-02-24 19:12:50 +01:00
|
|
|
loops += 1
|
2011-09-08 18:17:42 +02:00
|
|
|
|
|
|
|
"""
|
|
|
|
Main loop of the thread:
|
|
|
|
Wait for schedule updates from RabbitMQ, but in case there arent any,
|
|
|
|
poll the server to get the upcoming schedule.
|
|
|
|
"""
|
|
|
|
def run(self):
|
|
|
|
while True:
|
|
|
|
self.main()
|