Pypo fixes and improvements

General:
 * Moved pypo author info into one file
 * Added two database columns in schedule table: "schedule_group_played"
   and "media_item_played".

API clients:
 * Created get_liquidsoap_data() function which allows you
   to give arbitrary data to liquidsoap.
 * Added documentation
 * Renamed functions to make it more obvious what is happening

pypo_cli:
 * Got rid of more constants that were not needed
 * Created function set_export_source() to reduce code repetition
 * Separated the downloading of the schedule from tracking what has
   been played.  The tracking info is now kept in a separate file.
   This fixes the major bug that the playlist keeps restarting for
   the first minute of playback.
 * converted more print statements to debug statements

pypoTester:
 * Now uses samples from the audio_samples directory, and schedules two
   audio clips back-to-back.
This commit is contained in:
paul.baranowski 2010-11-29 18:34:22 -05:00
parent a138424451
commit 3613812012
20 changed files with 699 additions and 543 deletions

View File

@ -1,6 +1,15 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
###############################################################################
# This file holds the implementations for all the API clients.
#
# If you want to develop a new client, here are some suggestions:
# Get the fetch methods working first, then the push, then the liquidsoap notifier.
# You will probably want to create a script on your server side to automatically
# schedule a playlist one minute from the current time.
###############################################################################
import sys
import time
import urllib
@ -22,49 +31,82 @@ def api_client_factory(config):
class ApiClientInterface:
# This is optional.
# Implementation: optional
#
# Called from: beginning of all scripts
#
# Should exit the program if this version of pypo is not compatible with
# 3rd party software.
def check_version(self):
nil
pass
# Required.
# Implementation: Required
#
# Called from: fetch loop
#
# This is the main method you need to implement when creating a new API client.
# start and end are for testing purposes.
# start and end are strings in the format YYYY-DD-MM-hh-mm-ss
def get_schedule(self, start=None, end=None):
return 0, []
# Required.
# Implementation: Required
#
# Called from: fetch loop
#
# This downloads the media from the server.
def get_media(self, src, dst):
nil
pass
# This is optional.
# You dont actually have to implement this function for the liquidsoap playout to work.
def update_scheduled_item(self, item_id, value):
nil
# Implementation: optional
#
# Called from: push loop
#
# Tell server that the scheduled *playlist* has started.
def notify_scheduled_item_start_playing(self, pkey, schedule):
pass
# This is optional.
# Implementation: optional
# You dont actually have to implement this function for the liquidsoap playout to work.
def update_start_playing(self, playlist_type, export_source, media_id, playlist_id, transmission_id):
nil
#
# Called from: pypo_notify.py
#
# This is a callback from liquidsoap, we use this to notify about the
# currently playing *song*. We get passed a JSON string which we handed to
# liquidsoap in get_liquidsoap_data().
def notify_media_item_start_playing(self, data, media_id):
pass
# Implementation: optional
# You dont actually have to implement this function for the liquidsoap playout to work.
def generate_range_dp(self):
nil
pass
# Implementation: optional
#
# Called from: push loop
#
# Return a dict of extra info you want to pass to liquidsoap
# You will be able to use this data in update_start_playing
def get_liquidsoap_data(self, pkey, schedule):
pass
# Put here whatever tests you want to run to make sure your API is working
def test(self):
nil
pass
#def get_media_type(self, playlist):
# nil
################################################################################
# Campcaster API Client
################################################################################
class CampcasterApiClient(ApiClientInterface):
def __init__(self, config):
self.config = config
#self.api_auth = api_auth
def __get_campcaster_version(self):
logger = logging.getLogger()
@ -109,7 +151,6 @@ class CampcasterApiClient(ApiClientInterface):
def test(self):
logger = logging.getLogger()
status, items = self.get_schedule('2010-01-01-00-00-00', '2011-01-01-00-00-00')
#print items
schedule = items["playlists"]
logger.debug("Number of playlists found: %s", str(len(schedule)))
count = 1
@ -117,12 +158,10 @@ class CampcasterApiClient(ApiClientInterface):
logger.debug("Playlist #%s",str(count))
count+=1
#logger.info("found playlist at %s", pkey)
#print pkey
playlist = schedule[pkey]
for item in playlist["medias"]:
filename = urlparse(item["uri"])
filename = filename.query[5:]
#print filename
self.get_media(item["uri"], filename)
@ -142,6 +181,7 @@ class CampcasterApiClient(ApiClientInterface):
print 'pypo is compatible with this version of Campcaster.'
print
def get_schedule(self, start=None, end=None):
logger = logging.getLogger()
@ -183,26 +223,26 @@ class CampcasterApiClient(ApiClientInterface):
except Exception, e:
print e
schedule = response["playlists"]
scheduleKeys = sorted(schedule.iterkeys())
# Remove all playlists that have passed current time
try:
tnow = time.localtime(time.time())
str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5])
toRemove = []
for pkey in scheduleKeys:
if (str_tnow_s > schedule[pkey]['end']):
toRemove.append(pkey)
else:
break
#logger.debug("Remove keys: %s", toRemove)
for index in toRemove:
del schedule[index]
#logger.debug("Schedule dict: %s", schedule)
except Exception, e:
logger.debug("'Ignore Past Playlists' feature not supported by API: %s", e)
response["playlists"] = schedule
#schedule = response["playlists"]
#scheduleKeys = sorted(schedule.iterkeys())
#
## Remove all playlists that have passed current time
#try:
# tnow = time.localtime(time.time())
# str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5])
# toRemove = []
# for pkey in scheduleKeys:
# if (str_tnow_s > schedule[pkey]['end']):
# toRemove.append(pkey)
# else:
# break
# #logger.debug("Remove keys: %s", toRemove)
# for index in toRemove:
# del schedule[index]
# #logger.debug("Schedule dict: %s", schedule)
#except Exception, e:
# logger.debug("'Ignore Past Playlists' feature not supported by API: %s", e)
#response["playlists"] = schedule
return status, response
@ -220,51 +260,58 @@ class CampcasterApiClient(ApiClientInterface):
logger.error("%s", e)
def update_scheduled_item(self, item_id, value):
pass
#logger = logging.getLogger()
#
#url = self.config["base_url"] + self.config["api_base"] + self.config["update_item_url"]
#
#try:
# response = urllib.urlopen(url, self.api_auth)
# response = json.read(response.read())
# logger.info("API-Status %s", response['status'])
# logger.info("API-Message %s", response['message'])
#
#except Exception, e:
# print e
# api_status = False
# logger.critical("Unable to connect - %s", e)
#
#return response
"""
Tell server that the scheduled *playlist* has started.
"""
def notify_scheduled_item_start_playing(self, pkey, schedule):
logger = logging.getLogger()
playlist = schedule[pkey]
schedule_id = playlist["schedule_id"]
url = self.config["base_url"] + self.config["api_base"] + self.config["update_item_url"]
url = url.replace("%%schedule_id%%", str(schedule_id))
url += "&api_key=" + self.config["api_key"]
logger.debug(url)
try:
response = urllib.urlopen(url)
response = json.read(response.read())
logger.info("API-Status %s", response['status'])
logger.info("API-Message %s", response['message'])
except Exception, e:
logger.critical("Unable to connect - %s", e)
return response
def update_start_playing(self, playlist_type, export_source, media_id, playlist_id, transmission_id):
pass
#logger = logging.getLogger()
#
#url = self.config["base_url"] + self.config["api_base"] + self.config["update_start_playing_url"]
#url = url.replace("%%playlist_type%%", str(playlist_type))
#url = url.replace("%%export_source%%", str(export_source))
#url = url.replace("%%media_id%%", str(media_id))
#url = url.replace("%%playlist_id%%", str(playlist_id))
#url = url.replace("%%transmission_id%%", str(transmission_id))
#print url
#
#try:
# response = urllib.urlopen(url)
# response = json.read(response.read())
# logger.info("API-Status %s", response['status'])
# logger.info("API-Message %s", response['message'])
# logger.info("TXT %s", response['str_dls'])
#
#except Exception, e:
# print e
# api_status = False
# logger.critical("Unable to connect - %s", e)
#
#return response
"""
This is a callback from liquidsoap, we use this to notify about the
currently playing *song*. We get passed a JSON string which we handed to
liquidsoap in get_liquidsoap_data().
"""
def notify_media_item_start_playing(self, data, media_id):
logger = logging.getLogger()
response = ''
if (data[0] != '{'):
return response
try:
data = json.read(data)
logger.debug(str(data))
schedule_id = data["schedule_id"]
url = self.config["base_url"] + self.config["api_base"] + self.config["update_start_playing_url"]
url = url.replace("%%media_id%%", str(media_id))
url = url.replace("%%schedule_id%%", str(schedule_id))
url += "&api_key=" + self.config["api_key"]
logger.debug(url)
response = urllib.urlopen(url)
response = json.read(response.read())
logger.info("API-Status %s", response['status'])
logger.info("API-Message %s", response['message'])
except Exception, e:
logger.critical("Exception: %s", e)
return response
def generate_range_dp(self):
@ -287,6 +334,17 @@ class CampcasterApiClient(ApiClientInterface):
#
#return response
def get_liquidsoap_data(self, pkey, schedule):
logger = logging.getLogger()
playlist = schedule[pkey]
data = dict()
try:
data["schedule_id"] = playlist['id']
except Exception, e:
data["schedule_id"] = 0
data = json.write(data)
return data
################################################################################
@ -327,7 +385,7 @@ class ObpApiClient():
def get_obp_version(self):
logger = logging.getLogger("ObpApiClient.get_obp_version")
logger = logging.getLogger()
# lookup OBP version
url = self.config["base_url"] + self.config["api_base"]+ self.config["version_url"]
@ -369,7 +427,7 @@ class ObpApiClient():
def get_schedule(self, start=None, end=None):
logger = logging.getLogger("CampcasterApiClient.get_schedule")
logger = logging.getLogger()
"""
calculate start/end time range (format: YYYY-DD-MM-hh-mm-ss,YYYY-DD-MM-hh-mm-ss)
@ -421,13 +479,15 @@ class ObpApiClient():
logger.error("%s", e)
def update_scheduled_item(self, item_id, value):
logger = logging.getLogger("ObpApiClient.update_scheduled_item")
# lookup OBP version
"""
Tell server that the scheduled *playlist* has started.
"""
def notify_scheduled_item_start_playing(self, pkey, schedule):
#def update_scheduled_item(self, item_id, value):
logger = logging.getLogger()
url = self.config["base_url"] + self.config["api_base"] + self.config["update_item_url"]
url = url.replace("%%item_id%%", str(item_id))
url = url.replace("%%played%%", str(value))
url = url.replace("%%item_id%%", str(schedule[pkey]["id"]))
url = url.replace("%%played%%", "1")
try:
response = urllib.urlopen(url, self.api_auth)
@ -442,9 +502,18 @@ class ObpApiClient():
return response
def update_start_playing(self, playlist_type, export_source, media_id, playlist_id, transmission_id):
logger = logging.getLogger("ApiClient.update_scheduled_item")
"""
This is a callback from liquidsoap, we use this to notify about the
currently playing *song*. We get passed a JSON string which we handed to
liquidsoap in get_liquidsoap_data().
"""
def notify_media_item_start_playing(self, data, media_id):
# def update_start_playing(self, playlist_type, export_source, media_id, playlist_id, transmission_id):
logger = logging.getLogger()
playlist_type = data["playlist_type"]
export_source = data["export_source"]
playlist_id = data["playlist_id"]
transmission_id = data["transmission_id"]
url = self.config["base_url"] + self.config["api_base"] + self.config["update_start_playing_url"]
url = url.replace("%%playlist_type%%", str(playlist_type))
@ -470,7 +539,7 @@ class ObpApiClient():
def generate_range_dp(self):
logger = logging.getLogger("ObpApiClient.generate_range_dp")
logger = logging.getLogger()
url = self.config["base_url"] + self.config["api_base"] + self.config["generate_range_url"]
@ -486,5 +555,20 @@ class ObpApiClient():
api_status = False
logger.critical("Unable to handle the OBP API request - %s", e)
return response
def get_liquidsoap_data(self, pkey, schedule):
playlist = schedule[pkey]
data = dict()
data["ptype"] = playlist['subtype']
try:
data["user_id"] = playlist['user_id']
data["playlist_id"] = playlist['id']
data["transmission_id"] = playlist['schedule_id']
except Exception, e:
data["playlist_id"] = 0
data["user_id"] = 0
data["transmission_id"] = 0
data = json.write(data)
return data

View File

@ -14,67 +14,12 @@ api_client = "campcaster"
# _include_ trailing slash !! #
############################################
cache_dir = '/opt/pypo/cache/'
schedule_dir = '/opt/pypo/cache/schedule'
file_dir = '/opt/pypo/files/'
tmp_dir = '/opt/pypo/tmp/'
############################################
# API path & co #
############################################
# Value needed to access the API
api_key = 'AAA'
# Hostname
base_url = 'http://localhost/'
################################################################################
# Generic Config - if you are creating a new API client, define these values #
################################################################################
# Path to the base of the API
#api_base = ''
# URL to get the version number of the API
#version_url = ''
# Schedule export path.
# %%from%% - starting date/time in the form YYYY-MM-DD-hh-mm
# %%to%% - starting date/time in the form YYYY-MM-DD-hh-mm
#export_url = ''
# Update whether an item has been played.
# %%item_id%%
# %%played%%
#update_item_url = ''
# Update whether an item is currently playing.
#update_start_playing_url = ''
# ???
#generate_range_url = ''
#####################
# Campcaster Config #
#####################
api_base = 'campcaster/'
version_url = 'api/api_version.php?api_key=%%api_key%%'
export_url = 'api/schedule.php?from=%%from%%&to=%%to%%&api_key=%%api_key%%'
update_item_url = 'api/schedule.php?item_id=%%item_id%%&played=%%played%%'
update_start_playing_url = 'api/update_start_playing.php?playlist_type=%%playlist_type%%&export_source=%%export_source%%&media_id=%%media_id%%&playlist_id=%%playlist_id%%&transmission_id=%%transmission_id%%'
generate_range_url = 'api/generate_range_dp.php'
##############
# OBP config #
##############
#base_url = 'http://localhost/'
#api_base = ''
#version_url = 'api/pypo/status/json'
#update_item_url = 'api/pypo/update_shedueled_item/$$item_id%%?played=%%played%%'
#update_start_playing_url = 'api/pypo/update_start_playing/?playlist_type=%%playlist_type%%&export_source=%%export_source%%&media_id=%%media_id%%&playlist_id=%%playlist_id%%&transmission_id=%%transmission_id%%'
#generate_range_url = 'api/pypo/generate_range_dp/'
############################################
# Liquidsoap settings #
############################################
@ -87,10 +32,90 @@ ls_port = '1234'
prepare_ahead = 24 #in hours
cache_for = 24 #how long to hold the cache, in hours
poll_interval = 10 # in seconds
# Poll interval in seconds.
#
# This is how often the poll script downloads new schedules and files from the
# server.
#
# For production use, this number depends on whether you plan on making any
# last-minute changes to your schedule. This number should be set to half of
# the time you expect to "lock-in" your schedule. So if your schedule is set
# 24 hours in advance, this can be set to poll every 12 hours.
#
poll_interval = 10 # in seconds.
# Push interval in seconds.
#
# This is how often the push script checks whether it has something new to
# push to liquidsoap.
#
# It's hard to imagine a situation where this should be more than 1 second.
#
push_interval = 1 # in seconds
# 'pre' or 'otf'. 'pre' cues while playlist preparation
# while 'otf' (on the fly) cues while loading into ls
# (needs the post_processor patch)
cue_style = 'pre'
################################################################################
# Uncomment *one of the sets* of values from the API clients below, and comment
# out all the others.
################################################################################
#####################
# Campcaster Config #
#####################
# Value needed to access the API
api_key = 'AAA'
# Path to the base of the API
api_base = 'campcaster/'
# URL to get the version number of the server API
version_url = 'api/api_version.php?api_key=%%api_key%%'
# Schedule export path.
# %%from%% - starting date/time in the form YYYY-MM-DD-hh-mm
# %%to%% - starting date/time in the form YYYY-MM-DD-hh-mm
export_url = 'api/schedule.php?from=%%from%%&to=%%to%%&api_key=%%api_key%%'
# Update whether a schedule group has begun playing.
update_item_url = 'api/notify_schedule_group_play.php?schedule_id=%%schedule_id%%'
# Update whether an audio clip is currently playing.
update_start_playing_url = 'api/notify_media_item_start_play.php?media_id=%%media_id%%&schedule_id=%%schedule_id%%'
# ???
generate_range_url = 'api/generate_range_dp.php'
##############
# OBP config #
##############
# Value needed to access the API
#api_key = 'AAA'
#base_url = 'http://localhost/'
# Path to the base of the API
#api_base = ''
# URL to get the version number of the server API
#version_url = 'api/pypo/status/json'
# Schedule export path.
# %%from%% - starting date/time in the form YYYY-MM-DD-hh-mm
# %%to%% - starting date/time in the form YYYY-MM-DD-hh-mm
# Update whether an item has been played.
#update_item_url = 'api/pypo/update_shedueled_item/$$item_id%%?played=%%played%%'
# Update whether an item is currently playing.
#update_start_playing_url = 'api/pypo/mod/medialibrary/?playlist_type=%%playlist_type%%&export_source=%%export_source%%&media_id=%%media_id%%&playlist_id=%%playlist_id%%&transmission_id=%%transmission_id%%'
# ???
#generate_range_url = 'api/pypo/generate_range_dp/'

View File

@ -1,8 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author Jonas Ohrstrom <jonas@digris.ch>
"""
Python part of radio playout (pypo)

View File

@ -1,8 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author Jonas Ohrstrom <jonas@digris.ch>
"""
Python part of radio playout (pypo)
@ -19,7 +17,7 @@ tracks over and over again.
Attention & ToDos
- liquidsoap does not like mono files! So we have to make sure that only files with
2 channels are fed to LS
2 channels are fed to LiquidSoap
(solved: current = audio_to_stereo(current) - maybe not with ultimate performance)
@ -74,14 +72,14 @@ parser = OptionParser(usage=usage)
parser.add_option("-v", "--compat", help="Check compatibility with server API version", default=False, action="store_true", dest="check_compat")
parser.add_option("-t", "--test", help="Do a test to make sure everything is working properly.", default=False, action="store_true", dest="test")
parser.add_option("-f", "--fetch-scheduler", help="Fetch from scheduler - scheduler (loop, interval in config file)", default=False, action="store_true", dest="fetch_scheduler")
parser.add_option("-p", "--push-scheduler", help="Push scheduler to Liquidsoap (loop, interval in config file)", default=False, action="store_true", dest="push_scheduler")
parser.add_option("-f", "--fetch-scheduler", help="Fetch the schedule from server. This is a polling process that runs forever.", default=False, action="store_true", dest="fetch_scheduler")
parser.add_option("-p", "--push-scheduler", help="Push the schedule to Liquidsoap. This is a polling process that runs forever.", default=False, action="store_true", dest="push_scheduler")
parser.add_option("-F", "--fetch-daypart", help="Fetch from daypart - scheduler (loop, interval in config file)", default=False, action="store_true", dest="fetch_daypart")
parser.add_option("-P", "--push-daypart", help="Push daypart to Liquidsoap (loop, interval in config file)", default=False, action="store_true", dest="push_daypart")
parser.add_option("-b", "--cleanup", help="Cleanup", default=False, action="store_true", dest="cleanup")
parser.add_option("-j", "--jingles", help="Get new jingles from obp, comma separated list if jingle-id's as argument", metavar="LIST")
#parser.add_option("-j", "--jingles", help="Get new jingles from server, comma separated list if jingle-id's as argument", metavar="LIST")
parser.add_option("-c", "--check", help="Check the cached schedule and exit", default=False, action="store_true", dest="check")
# parse options
@ -93,32 +91,19 @@ logging.config.fileConfig("logging.cfg")
# loading config file
try:
config = ConfigObj('config.cfg')
CACHE_DIR = config['cache_dir']
FILE_DIR = config['file_dir']
TMP_DIR = config['tmp_dir']
API_BASE = config['api_base']
API_KEY = config['api_key']
POLL_INTERVAL = float(config['poll_interval'])
PUSH_INTERVAL = float(config['push_interval'])
LS_HOST = config['ls_host']
LS_PORT = config['ls_port']
#PREPARE_AHEAD = config['prepare_ahead']
CACHE_FOR = config['cache_for']
CUE_STYLE = config['cue_style']
#print config
except Exception, e:
print 'Error loading config file: ', e
sys.exit()
#TIME = time.localtime(time.time())
TIME = (2010, 6, 26, 15, 33, 23, 2, 322, 0)
class Global:
def __init__(self):
print
def selfcheck(self):
self.api_auth = urllib.urlencode({'api_key': API_KEY})
self.api_client = api_client.api_client_factory(config)
self.api_client.check_version()
@ -128,18 +113,23 @@ class Global:
"""
class Playout:
def __init__(self):
self.file_dir = FILE_DIR
self.tmp_dir = TMP_DIR
self.api_auth = urllib.urlencode({'api_key': API_KEY})
self.api_client = api_client.api_client_factory(config)
self.cue_file = CueFile()
self.silence_file = self.file_dir + 'basic/silence.mp3'
# set initial state
self.silence_file = config["file_dir"] + 'basic/silence.mp3'
self.push_ahead = 15
self.range_updated = False
def test_api(self):
self.api_client.test()
def set_export_source(self, export_source):
self.export_source = export_source
self.cache_dir = config["cache_dir"] + self.export_source + '/'
self.schedule_file = self.cache_dir + 'schedule.pickle'
self.schedule_tracker_file = self.cache_dir + "schedule_tracker.pickle"
"""
Fetching part of pypo
@ -155,9 +145,7 @@ class Playout:
"""
logger = logging.getLogger()
self.export_source = export_source
self.cache_dir = CACHE_DIR + self.export_source + '/'
self.schedule_file = self.cache_dir + 'schedule'
self.set_export_source(export_source)
try: os.mkdir(self.cache_dir)
except Exception, e: pass
@ -185,10 +173,10 @@ class Playout:
except Exception, e: logger.error("%s", e)
# prepare the playlists
if CUE_STYLE == 'pre':
try: self.prepare_playlists_cue(self.export_source)
if config["cue_style"] == 'pre':
try: self.prepare_playlists_cue()
except Exception, e: logger.error("%s", e)
elif CUE_STYLE == 'otf':
elif config["cue_style"] == 'otf':
try: self.prepare_playlists(self.export_source)
except Exception, e: logger.error("%s", e)
@ -210,10 +198,10 @@ class Playout:
tnow = time.localtime(time.time())
if(tnow[3] == 16):
if (tnow[3] == 16):
self.range_updated = False
if(tnow[3] == 17 and self.range_updated == False):
if (tnow[3] == 17 and self.range_updated == False):
try:
print self.api_client.generate_range_dp()
logger.info("daypart updated")
@ -248,18 +236,11 @@ class Playout:
playlist folder.
file is eg 2010-06-23-15-00-00/17_cue_10.132-123.321.mp3
"""
def prepare_playlists_cue(self, export_source):
def prepare_playlists_cue(self):
logger = logging.getLogger()
self.export_source = export_source
try:
schedule_file = open(self.schedule_file, "r")
schedule = pickle.load(schedule_file)
schedule_file.close()
except Exception, e:
logger.error("%s", e)
schedule = None
# Load schedule from disk
schedule = self.load_schedule()
# Dont do anything if schedule is empty
if (not schedule):
@ -271,7 +252,6 @@ class Playout:
try:
for pkey in scheduleKeys:
logger.info("found playlist at %s", pkey)
#print pkey
playlist = schedule[pkey]
# create playlist directory
@ -282,18 +262,20 @@ class Playout:
ls_playlist = '';
print '*****************************************'
print 'pkey: ' + str(pkey)
print 'cached at : ' + self.cache_dir + str(pkey)
print 'subtype: ' + str(playlist['subtype'])
print 'played: ' + str(playlist['played'])
print 'schedule id: ' + str(playlist['schedule_id'])
print 'duration: ' + str(playlist['duration'])
print 'source id: ' + str(playlist['x_ident'])
print '*****************************************'
logger.debug('*****************************************')
logger.debug('pkey: ' + str(pkey))
logger.debug('cached at : ' + self.cache_dir + str(pkey))
logger.debug('subtype: ' + str(playlist['subtype']))
logger.debug('played: ' + str(playlist['played']))
logger.debug('schedule id: ' + str(playlist['schedule_id']))
logger.debug('duration: ' + str(playlist['duration']))
logger.debug('source id: ' + str(playlist['x_ident']))
logger.debug('*****************************************')
# Creating an API call like the next two lines would make this more flexible
# mediaType = api_client.get_media_type(playlist)
# if (mediaType == PYPO_MEDIA_SKIP):
#mediaType = api_client.get_media_type(playlist)
#if (mediaType == PYPO_MEDIA_SKIP):
if int(playlist['played']) == 1:
logger.info("playlist %s already played / sent to liquidsoap, so will ignore it", pkey)
@ -401,7 +383,6 @@ class Playout:
if True == os.access(dst, os.R_OK):
# check filesize (avoid zero-byte files)
#print 'waiting: ' + dst
try: fsize = os.path.getsize(dst)
except Exception, e:
logger.error("%s", e)
@ -445,15 +426,11 @@ class Playout:
else:
if os.path.isfile(dst):
logger.debug("file already in cache: %s", dst)
#print 'cached'
else:
logger.debug("try to download and cue %s", media['uri'])
#print '***'
dst_tmp = self.tmp_dir + "".join([random.choice(string.letters) for i in xrange(10)]) + '.mp3'
#print dst_tmp
#print '***'
dst_tmp = config["tmp_dir"] + "".join([random.choice(string.letters) for i in xrange(10)]) + '.mp3'
self.api_client.get_media(media['uri'], dst_tmp)
# cue
@ -509,7 +486,7 @@ class Playout:
logger.debug("try to copy and cue %s", media['uri'])
print '***'
dst_tmp = self.tmp_dir + "".join([random.choice(string.letters) for i in xrange(10)])
dst_tmp = config["tmp_dir"] + "".join([random.choice(string.letters) for i in xrange(10)])
print dst_tmp
print '***'
@ -550,10 +527,8 @@ class Playout:
"""
logger = logging.getLogger()
self.export_source = export_source
self.cache_dir = CACHE_DIR + self.export_source + '/'
self.schedule_file = self.cache_dir + 'schedule'
offset = 3600 * int(CACHE_FOR)
self.set_export_source(export_source)
offset = 3600 * int(config["cache_for"])
now = time.time()
for r, d, f in os.walk(self.cache_dir):
@ -588,128 +563,109 @@ class Playout:
def push(self, export_source):
logger = logging.getLogger()
self.export_source = export_source
self.cache_dir = CACHE_DIR + self.export_source + '/'
self.schedule_file = self.cache_dir + 'schedule'
self.set_export_source(export_source)
self.push_ahead = 15
#try:
# dummy = self.schedule
# logger.debug('schedule already loaded')
#except Exception, e:
# self.schedule = self.push_init(self.export_source)
self.schedule = self.load_schedule()
playedItems = self.load_schedule_tracker()
try:
dummy = self.schedule
logger.debug('schedule already loaded')
except Exception, e:
self.schedule = self.push_init(self.export_source)
self.schedule = self.push_init(self.export_source)
"""
I'm quite sure that this could be achieved in a much more elegant way in python...
"""
tcomming = time.localtime(time.time() + self.push_ahead)
tnow = time.localtime(time.time())
str_tnow = "%04d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4])
str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5])
str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5])
str_tcomming = "%04d-%02d-%02d-%02d-%02d" % (tcomming[0], tcomming[1], tcomming[2], tcomming[3], tcomming[4])
str_tcomming_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tcomming[0], tcomming[1], tcomming[2], tcomming[3], tcomming[4], tcomming[5])
#print '--'
#print str_tnow_s + ' now'
#print str_tcomming_s + ' comming'
playnow = None
if self.schedule == None:
print 'unable to loop schedule - maybe write in progress'
print 'will try in next loop'
logger.warn('Unable to loop schedule - maybe write in progress?')
logger.warn('Will try again in next loop.')
else:
for pkey in self.schedule:
for pkey in self.schedule:
if pkey[0:16] == str_tcomming:
logger.debug('Preparing to push playlist scheduled at: %s', pkey)
playlist = self.schedule[pkey]
if int(playlist['played']) != 1:
#print '!!!!!!!!!!!!!!!!!!!'
#print 'MATCH'
"""
ok we have a match, replace the current playlist and
force liquidsoap to refresh
Add a 'played' state to the list in schedule, so it is not called again
in the next push loop
"""
playedFlag = (pkey in playedItems) and playedItems[pkey].get("played", 0)
logger.debug("PLAYED FLAG: " + str(playedFlag))
if not playedFlag:
# We have a match, replace the current playlist and
# force liquidsoap to refresh.
ptype = playlist['subtype']
try:
user_id = playlist['user_id']
playlist_id = playlist['id']
transmission_id = playlist['schedule_id']
except Exception, e:
playlist_id = 0
user_id = 0
transmission_id = 0
print e
#print 'Playlist id:',
if (self.push_liquidsoap(pkey, ptype, user_id, playlist_id, transmission_id, self.push_ahead) == 1):
if (self.push_liquidsoap(pkey, self.schedule, ptype) == 1):
logger.debug("Pushed to liquidsoap, updating 'played' status.")
self.schedule[pkey]['played'] = 1
"""
Call api to update schedule states and
write changes back to cache file
"""
self.api_client.update_scheduled_item(int(playlist['schedule_id']), 1)
schedule_file = open(self.schedule_file, "w")
pickle.dump(self.schedule, schedule_file)
schedule_file.close()
logger.debug("Wrote schedule to disk")
#else:
# print 'Nothing to do...'
# Marked the current playlist as 'played' in the schedule tracker
# so it is not called again in the next push loop.
# Write changes back to tracker file.
playedItems[pkey] = playlist
playedItems[pkey]['played'] = 1
schedule_tracker = open(self.schedule_tracker_file, "w")
pickle.dump(playedItems, schedule_tracker)
schedule_tracker.close()
logger.debug("Wrote schedule to disk: "+str(playedItems))
# Call API to update schedule states
logger.debug("Doing callback to server to update 'played' status.")
self.api_client.notify_scheduled_item_start_playing(pkey, self.schedule)
def push_init(self, export_source):
def load_schedule(self):
logger = logging.getLogger()
schedule = None
self.export_source = export_source
self.cache_dir = CACHE_DIR + self.export_source + '/'
self.schedule_file = self.cache_dir + 'schedule'
# load the schedule from cache
logger.debug('loading schedule from cache...')
try:
schedule_file = open(self.schedule_file, "r")
schedule = pickle.load(schedule_file)
schedule_file.close()
except Exception, e:
logger.error('%s', e)
schedule = None
# create the file if it doesnt exist
if (not os.path.exists(self.schedule_file)):
logger.debug('creating file ' + self.schedule_file)
open(self.schedule_file, 'w').close()
else:
# load the schedule from cache
logger.debug('loading schedule file '+self.schedule_file)
try:
schedule_file = open(self.schedule_file, "r")
schedule = pickle.load(schedule_file)
schedule_file.close()
except Exception, e:
logger.error('%s', e)
return schedule
def push_liquidsoap(self, pkey, ptype, user_id, playlist_id, transmission_id, push_ahead):
def load_schedule_tracker(self):
logger = logging.getLogger()
playedItems = dict()
# create the file if it doesnt exist
if (not os.path.exists(self.schedule_tracker_file)):
logger.debug('creating file ' + self.schedule_tracker_file)
schedule_tracker = open(self.schedule_tracker_file, 'w')
pickle.dump(playedItems, schedule_tracker)
schedule_tracker.close()
else:
try:
logger.debug('loading schedule tracker file '+ self.schedule_tracker_file)
schedule_tracker = open(self.schedule_tracker_file, "r")
playedItems = pickle.load(schedule_tracker)
schedule_tracker.close()
except Exception, e:
logger.error('Unable to load schedule tracker file: %s', e)
return playedItems
#self.export_source = export_source
self.push_ahead = push_ahead
self.cache_dir = CACHE_DIR + self.export_source + '/'
self.schedule_file = self.cache_dir + 'schedule'
def push_liquidsoap(self, pkey, schedule, ptype):
logger = logging.getLogger()
src = self.cache_dir + str(pkey) + '/list.lsp'
logger.debug(src)
try:
if True == os.access(src, os.R_OK):
logger.debug('OK - Can read playlist file')
@ -724,8 +680,7 @@ class Playout:
if (int(ptype) == 6):
tn.write("live_in.start")
tn.write("\n")
if (int(ptype) < 5):
for line in pl_file.readlines():
logger.debug(line.strip())
@ -741,19 +696,11 @@ class Playout:
logger.debug('sending "flip"')
tn = telnetlib.Telnet(LS_HOST, 1234)
"""
Pass some extra information to liquidsoap
"""
logger.debug('user_id: %s', user_id)
logger.debug('playlist_id: %s', playlist_id)
logger.debug('transmission_id: %s', transmission_id)
logger.debug('ptype: %s', ptype)
tn.write("vars.user_id %s\n" % user_id)
tn.write("vars.playlist_id %s\n" % playlist_id)
tn.write("vars.transmission_id %s\n" % transmission_id)
tn.write("vars.playlist_type %s\n" % ptype)
# Get any extra information for liquidsoap (which will be sent back to us)
liquidsoap_data = self.api_client.get_liquidsoap_data(pkey, schedule)
logger.debug("Sending additional data to liquidsoap: "+liquidsoap_data)
tn.write("vars.pypo_data "+liquidsoap_data+"\n")
# if(int(ptype) < 5):
# tn.write(self.export_source + '.flip')
# tn.write("\n")
@ -761,7 +708,7 @@ class Playout:
tn.write(self.export_source + '.flip')
tn.write("\n")
if(int(ptype) == 6):
if (int(ptype) == 6):
tn.write("live.active 1")
tn.write("\n")
else:
@ -772,7 +719,7 @@ class Playout:
tn.write("exit\n")
logger.debug(tn.read_all())
tn.read_all()
status = 1
except Exception, e:
logger.error('%s', e)
@ -857,38 +804,38 @@ class Playout:
"""
Updates the jingles. Give comma separated list of jingle tracks.
NOTE: commented out because it needs to be converted to use the API client. - Paul
"""
def update_jingles(self, options):
print 'jingles'
jingle_list = string.split(options, ',')
print jingle_list
for media_id in jingle_list:
# api path maybe should not be hard-coded
src = API_BASE + 'api/pypo/get_media/' + str(media_id)
print src
# include the hourly jungles for the moment
dst = "%s%s/%s.mp3" % (self.file_dir, 'jingles/hourly', str(media_id))
print dst
try:
print '** urllib auth with: ',
print self.api_auth
opener = urllib.URLopener()
opener.retrieve (src, dst, False, self.api_auth)
logger.info("downloaded %s to %s", src, dst)
except Exception, e:
print e
logger.error("%s", e)
#def update_jingles(self, options):
# print 'jingles'
#
# jingle_list = string.split(options, ',')
# print jingle_list
# for media_id in jingle_list:
# # api path maybe should not be hard-coded
# src = API_BASE + 'api/pypo/get_media/' + str(media_id)
# print src
# # include the hourly jungles for the moment
# dst = "%s%s/%s.mp3" % (config["file_dir"], 'jingles/hourly', str(media_id))
# print dst
#
# try:
# print '** urllib auth with: ',
# print self.api_auth
# opener = urllib.URLopener()
# opener.retrieve (src, dst, False, self.api_auth)
# logger.info("downloaded %s to %s", src, dst)
# except Exception, e:
# print e
# logger.error("%s", e)
def check_schedule(self, export_source):
logger = logging.getLogger()
self.export_source = export_source
self.cache_dir = CACHE_DIR + self.export_source + '/'
self.schedule_file = self.cache_dir + 'schedule'
self.set_export_source(export_source)
try:
schedule_file = open(self.schedule_file, "r")
schedule = pickle.load(schedule_file)
@ -898,11 +845,8 @@ class Playout:
logger.error("%s", e)
schedule = None
#for pkey in schedule:
for pkey in sorted(schedule.iterkeys()):
playlist = schedule[pkey]
print '*****************************************'
print '\033[0;32m%s %s\033[m' % ('scheduled at:', str(pkey))
print 'cached at : ' + self.cache_dir + str(pkey)
@ -911,7 +855,6 @@ class Playout:
print 'schedule id: ' + str(playlist['schedule_id'])
print 'duration: ' + str(playlist['duration'])
print 'source id: ' + str(playlist['x_ident'])
print '-----------------------------------------'
for media in playlist['medias']:
@ -921,7 +864,6 @@ class Playout:
if __name__ == '__main__':
print
print '###########################################'
print '# *** pypo *** #'
@ -997,11 +939,11 @@ while run == True:
time.sleep(PUSH_INTERVAL)
while options.jingles:
try: po.update_jingles(options.jingles)
except Exception, e:
print e
sys.exit()
#while options.jingles:
# try: po.update_jingles(options.jingles)
# except Exception, e:
# print e
# sys.exit()
while options.check:
@ -1011,7 +953,7 @@ while run == True:
sys.exit()
while options.cleanup:
try: po.cleanup()
try: po.cleanup('scheduler')
except Exception, e:
print e
sys.exit()

View File

@ -1,21 +1,18 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author Jonas Ohrstrom <jonas@digris.ch>
"""
Python part of radio playout (pypo)
This function acts as a gateway between liquidsoap and the obp-api.
Mainliy used to tell the plattform what pypo/LS does.
Mainliy used to tell the platform what pypo/LS does.
Main case:
- whenever Liquidsoap starts playing a new track, its on_metadata callback calls
a function in liquidsoap (notify(m)) which then calls the python script here
with the currently starting filename as parameter
- this python script takes this parameter, tries to extract the actual
media id from it, and then calls back to obp via api to tell about
media id from it, and then calls back to API to tell it about it.
"""
@ -171,7 +168,6 @@ class Notify:
if __name__ == '__main__':
print
print '#########################################'
print '# *** pypo *** #'

View File

@ -1,21 +1,18 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author Jonas Ohrstrom <jonas@digris.ch>
"""
Python part of radio playout (pypo)
This function acts as a gateway between liquidsoap and the obp-api.
Mainliy used to tell the plattform what pypo/LS does.
This function acts as a gateway between liquidsoap and the server API.
Mainly used to tell the platform what pypo/liquidsoap does.
Main case:
- whenever LS starts playing a new track, its on_metadata callback calls
a function in ls (notify(m)) which then calls the pythin script here
a function in ls (notify(m)) which then calls the python script here
with the currently starting filename as parameter
- this python script takes this parameter, tries to extract the actual
media id from it, and then calls back to obp via api to tell about
media id from it, and then calls back to the API to tell about it about it.
"""
@ -52,14 +49,14 @@ usage = "%prog [options]" + " - notification gateway"
parser = OptionParser(usage=usage)
# Options
#parser.add_option("-p", "--playing", help="Tell daddy what is playing right now", dest="playing", default=False, metavar=False)
parser.add_option("-p", "--playing", help="Tell daddy what is playing right now", default=False, action="store_true", dest="playing")
parser.add_option("-t", "--playlist-type", help="Tell daddy what is playing right now", metavar="playlist_type")
parser.add_option("-M", "--media-id", help="Tell daddy what is playing right now", metavar="media_id")
parser.add_option("-U", "--user-id", help="Tell daddy what is playing right now", metavar="user_id")
parser.add_option("-P", "--playlist-id", help="Tell daddy what is playing right now", metavar="playlist_id")
parser.add_option("-T", "--transmission-id", help="Tell daddy what is playing right now", metavar="transmission_id")
parser.add_option("-E", "--export-source", help="Tell daddy what is playing right now", metavar="export_source")
parser.add_option("-d", "--data", help="Pass JSON data from liquidsoap into this script.", metavar="data")
#parser.add_option("-p", "--playing", help="Tell server what is playing right now.", default=False, action="store_true", dest="playing")
#parser.add_option("-t", "--playlist-type", help="", metavar="playlist_type")
parser.add_option("-m", "--media-id", help="ID of the file that is currently playing.", metavar="media_id")
#parser.add_option("-U", "--user-id", help="", metavar="user_id")
#parser.add_option("-P", "--playlist-id", help="", metavar="playlist_id")
#parser.add_option("-T", "--transmission-id", help="", metavar="transmission_id")
#parser.add_option("-E", "--export-source", help="", metavar="export_source")
# parse options
(options, args) = parser.parse_args()
@ -70,10 +67,6 @@ logging.config.fileConfig("logging.cfg")
# loading config file
try:
config = ConfigObj('config.cfg')
TMP_DIR = config['tmp_dir']
BASE_URL = config['base_url']
API_BASE = BASE_URL + 'mod/medialibrary/'
API_KEY = config['api_key']
except Exception, e:
print 'error: ', e
@ -85,144 +78,156 @@ class Global:
print
def selfcheck(self):
self.api_auth = urllib.urlencode({'api_key': API_KEY})
self.api_client = api_client.api_client_factory(config)
self.api_client.check_version()
pass
#self.api_client = api_client.api_client_factory(config)
#self.api_client.check_version()
class Notify:
def __init__(self):
self.tmp_dir = TMP_DIR
self.api_auth = urllib.urlencode({'api_key': API_KEY})
self.api_client = api_client.api_client_factory(config)
self.dls_client = DlsClient('127.0.0.128', 50008, 'myusername', 'mypass')
#self.dls_client = DlsClient('127.0.0.128', 50008, 'myusername', 'mypass')
def start_playing(self, options):
logger = logging.getLogger("start_playing")
tnow = time.localtime(time.time())
def notify_media_start_playing(self, data, media_id):
logger = logging.getLogger()
#tnow = time.localtime(time.time())
#print options
print '#################################################'
print '# calling obp to tell about what\'s playing #'
print '#################################################'
if int(options.playlist_type) < 5:
print 'seems to be a playlist'
try:
media_id = int(options.media_id)
except Exception, e:
media_id = 0
response = self.api_client.update_start_playing(options.playlist_type, options.export_source, media_id, options.playlist_id, options.transmission_id)
print response
if int(options.playlist_type) == 6:
print 'seems to be a couchcast'
try:
media_id = int(options.media_id)
except Exception, e:
media_id = 0
response = self.api_client.update_start_playing(options.playlist_type, options.export_source, media_id, options.playlist_id, options.transmission_id)
print response
sys.exit()
logger.debug('#################################################')
logger.debug('# Calling server to update about what\'s playing #')
logger.debug('#################################################')
logger.debug('data = '+ str(data))
#print 'options.data = '+ options.data
#data = json.read(options.data)
response = self.api_client.notify_media_item_start_playing(data, media_id)
logger.debug("Response: "+str(response))
#def start_playing(self, options):
# logger = logging.getLogger("start_playing")
# tnow = time.localtime(time.time())
#
# #print options
#
# logger.debug('#################################################')
# logger.debug('# Calling server to update about what\'s playing #')
# logger.debug('#################################################')
#
# if int(options.playlist_type) < 5:
# logger.debug('seems to be a playlist')
#
# try:
# media_id = int(options.media_id)
# except Exception, e:
# media_id = 0
#
# response = self.api_client.update_start_playing(options.playlist_type, options.export_source, media_id, options.playlist_id, options.transmission_id)
#
# logger.debug(response)
#
# if int(options.playlist_type) == 6:
# logger.debug('seems to be a couchcast')
#
# try:
# media_id = int(options.media_id)
# except Exception, e:
# media_id = 0
#
# response = self.api_client.update_start_playing(options.playlist_type, options.export_source, media_id, options.playlist_id, options.transmission_id)
#
# logger.debug(response)
#
# sys.exit()
def start_playing_legacy(self, options):
logger = logging.getLogger("start_playing")
tnow = time.localtime(time.time())
print '#################################################'
print '# calling obp to tell about what\'s playing #'
print '#################################################'
path = options
print
print path
print
if 'pl_id' in path:
print 'seems to be a playlist'
type = 'playlist'
id = path[5:]
elif 'text' in path:
print 'seems to be a playlist'
type = 'text'
id = path[4:]
print id
else:
print 'seems to be a single track (media)'
type = 'media'
try:
file = path.split("/")[-1:][0]
if file.find('_cue_') > 0:
id = file.split("_cue_")[0]
else:
id = file.split(".")[-2:][0]
except Exception, e:
#print e
id = False
try:
id = id
except Exception, e:
#print e
id = False
print
print type + " id: ",
print id
response = self.api_client.update_start_playing(type, id, self.export_source, path)
print 'DONE'
try:
txt = response['txt']
print '#######################################'
print txt
print '#######################################'
#self.dls_client.set_txt(txt)
except Exception, e:
print e
#def start_playing_legacy(self, options):
# logger = logging.getLogger("start_playing")
# tnow = time.localtime(time.time())
#
# print '#################################################'
# print '# Calling server to update about what\'s playing #'
# print '#################################################'
#
# path = options
#
# print
# print path
# print
#
# if 'pl_id' in path:
# print 'seems to be a playlist'
# type = 'playlist'
# id = path[5:]
#
# elif 'text' in path:
# print 'seems to be a playlist'
# type = 'text'
# id = path[4:]
# print id
#
# else:
# print 'seems to be a single track (media)'
# type = 'media'
# try:
# file = path.split("/")[-1:][0]
# if file.find('_cue_') > 0:
# id = file.split("_cue_")[0]
# else:
# id = file.split(".")[-2:][0]
#
# except Exception, e:
# #print e
# id = False
#
# try:
# id = id
# except Exception, e:
# #print e
# id = False
#
# print
# print type + " id: ",
# print id
#
#
# response = self.api_client.update_start_playing(type, id, self.export_source, path)
#
# print 'DONE'
#
# try:
# txt = response['txt']
# print '#######################################'
# print txt
# print '#######################################'
# #self.dls_client.set_txt(txt)
#
# except Exception, e:
# print e
if __name__ == '__main__':
print
print '#########################################'
print '# *** pypo *** #'
print '# pypo notification gateway #'
print '#########################################'
print
# initialize
g = Global()
g.selfcheck()
n = Notify()
run = True
while run == True:
logger = logging.getLogger("pypo notify")
if options.playing:
try: n.start_playing(options)
except Exception, e:
print e
sys.exit()
sys.exit()
logger = logging.getLogger()
#if options.playing:
# try: n.start_playing(options)
# except Exception, e:
# print e
# sys.exit()
if not options.data:
print "NOTICE: 'data' command-line argument not given."
sys.exit()
if not options.media_id:
print "NOTICE: 'media_id' command-line argument not given."
sys.exit()
try:
g.selfcheck()
n = Notify()
n.notify_media_start_playing(options.data, options.media_id)
except Exception, e:
print e

View File

@ -1,8 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author Jonas Ohrstrom <jonas@digris.ch>
"""
cue script that gets called by liquidsoap if a file in the playlist

View File

@ -6,7 +6,11 @@ playlist_id = ref '0'
user_id = ref '0'
transmission_id = ref '0'
playlist_type = ref '0'
pypo_data = ref '0'
def set_pypo_data(s)
pypo_data := s
end
def set_user_id(s)
user_id := s
@ -29,4 +33,5 @@ end
server.register(namespace="vars", "user_id", fun (s) -> begin set_user_id(s) "Done!" end)
server.register(namespace="vars", "playlist_id", fun (s) -> begin set_playlist_id(s) "Done!" end)
server.register(namespace="vars", "transmission_id", fun (s) -> begin set_transmission_id(s) "Done!" end)
server.register(namespace="vars", "playlist_type", fun (s) -> begin set_playlist_type(s) "Done!" end)
server.register(namespace="vars", "playlist_type", fun (s) -> begin set_playlist_type(s) "Done!" end)
server.register(namespace="vars", "pypo_data", fun (s) -> begin set_pypo_data(s) "Done!" end)

View File

@ -4,10 +4,10 @@
def notify(m)
print('user_id: #{!user_id}')
print('playlist_id: #{!playlist_id}')
print('transmission_id: #{!transmission_id}')
print('playlist_type: #{!playlist_type}')
# print('user_id: #{!user_id}')
# print('playlist_id: #{!playlist_id}')
# print('transmission_id: #{!transmission_id}')
# print('playlist_type: #{!playlist_type}')
if !playlist_type=='5' then
print('livesession')
@ -20,8 +20,9 @@ def notify(m)
end
if !playlist_type=='0' or !playlist_type=='1' or !playlist_type=='2' or !playlist_type=='3' or !playlist_type=='4' then
print('playlist')
system("./notify.sh --playing --playlist-type=#{!playlist_type} --media-id=#{m['media_id']} --export-source=#{m['export_source']}")
print('include_notify.liq: notify on playlist')
#system("./notify.sh --playing --playlist-type=#{!playlist_type} --media-id=#{m['media_id']} --export-source=#{m['export_source']}")
system("./notify.sh --data='#{!pypo_data}' --media-id=#{m['media_id']}")
end
end

View File

@ -2,9 +2,6 @@
# liquidsoap config file #
###########################################
# author Jonas Ohrstrom <jonas@digris.ch>
# this file is specific to the obp
# installation. eg it assumes that there are
# two instances of LS running

View File

@ -1,4 +1,4 @@
# author Jonas Ohrstrom <jonas@digris.ch>
# Register the cut protocol
def cue_protocol(arg,delay)
# The extraction program

View File

@ -1,8 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author Jonas Ohrstrom <jonas@digris.ch>
import sys
import shutil
import random

View File

@ -1,6 +1,5 @@
<?php
require_once('../conf.php');
require_once('DB.php');
require_once('../backend/StoredFile.php');
$api_key = $_GET['api_key'];
@ -13,13 +12,6 @@ if(!in_array($api_key, $CC_CONFIG["apiKey"]))
PEAR::setErrorHandling(PEAR_ERROR_RETURN);
$CC_DBC = DB::connect($CC_CONFIG['dsn'], TRUE);
if (PEAR::isError($CC_DBC)) {
echo "ERROR: ".$CC_DBC->getMessage()." ".$CC_DBC->getUserInfo()."\n";
exit(1);
}
$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC);
$filename = $_GET["file"];
$file_id = substr($filename, 0, strpos($filename, "."));
if (ctype_alnum($file_id) && strlen($file_id) == 32) {

View File

@ -0,0 +1,37 @@
<?php
require_once('../conf.php');
require_once('../backend/Schedule.php');
$api_key = $_GET['api_key'];
if(!in_array($api_key, $CC_CONFIG["apiKey"]))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
PEAR::setErrorHandling(PEAR_ERROR_RETURN);
$schedule_group_id = $_GET["schedule_id"];
$media_id = $_GET["media_id"];
$f = StoredFile::RecallByGunid($media_id);
if (is_numeric($schedule_group_id)) {
$sg = new ScheduleGroup($schedule_group_id);
if ($sg->exists()) {
$result = $sg->notifyMediaItemStartPlay($f->getId());
if (!PEAR::isError($result)) {
echo json_encode(array("status"=>1, "message"=>""));
exit;
} else {
echo json_encode(array("status"=>0, "message"=>"DB Error:".$result->getMessage()));
exit;
}
} else {
echo json_encode(array("status"=>0, "message"=>"Schedule group does not exist: ".$schedule_group_id));
exit;
}
} else {
echo json_encode(array("status"=>0, "message" => "Incorrect or non-numeric arguments given."));
}
?>

View File

@ -0,0 +1,35 @@
<?php
require_once('../conf.php');
require_once('../backend/Schedule.php');
$api_key = $_GET['api_key'];
if (!in_array($api_key, $CC_CONFIG["apiKey"]))
{
header('HTTP/1.0 401 Unauthorized');
print 'You are not allowed to access this resource.';
exit;
}
PEAR::setErrorHandling(PEAR_ERROR_RETURN);
$schedule_group_id = $_GET["schedule_id"];
if (is_numeric($schedule_group_id)) {
$sg = new ScheduleGroup($schedule_group_id);
if ($sg->exists()) {
$result = $sg->notifyGroupStartPlay();
if (!PEAR::isError($result)) {
echo json_encode(array("status"=>1, "message"=>""));
exit;
} else {
echo json_encode(array("status"=>0, "message"=>"DB Error:".$result->getMessage()));
exit;
}
} else {
echo json_encode(array("status"=>0, "message"=>"Schedule group does not exist: ".$schedule_group_id));
exit;
}
} else {
echo json_encode(array("status"=>0, "message"=>"Incorrect or non-numeric arguments given."));
exit;
}
?>

View File

@ -1,6 +1,5 @@
<?php
require_once('../conf.php');
require_once('DB.php');
require_once('../backend/Schedule.php');
$api_key = $_GET['api_key'];
@ -13,12 +12,6 @@ if(!in_array($api_key, $CC_CONFIG["apiKey"]))
PEAR::setErrorHandling(PEAR_ERROR_RETURN);
$CC_DBC = DB::connect($CC_CONFIG['dsn'], TRUE);
if (PEAR::isError($CC_DBC)) {
echo "ERROR: ".$CC_DBC->getMessage()." ".$CC_DBC->getUserInfo()."\n";
exit(1);
}
$CC_DBC->setFetchMode(DB_FETCHMODE_ASSOC);
$from = $_GET["from"];
$to = $_GET["to"];
echo Schedule::ExportRangeAsJson($from, $to);

View File

@ -10,6 +10,21 @@ class ScheduleGroup {
$this->groupId = $p_groupId;
}
/**
* Return true if the schedule group exists in the DB.
* @return boolean
*/
public function exists() {
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT COUNT(*) FROM ".$CC_CONFIG['scheduleTable']
." WHERE group_id=".$this->groupId;
$result = $CC_DBC->GetOne($sql);
if (PEAR::isError($result)) {
return $result;
}
return $result != "0";
}
/**
* Convert a date to an ID by stripping out all characters
* and padding with zeros.
@ -204,6 +219,22 @@ class ScheduleGroup {
// $sql = "UPDATE ".$CC_CONFIG["scheduleTable"]. " SET id=, starts=,ends="
}
public function notifyGroupStartPlay() {
global $CC_CONFIG, $CC_DBC;
$sql = "UPDATE ".$CC_CONFIG['scheduleTable']
." SET schedule_group_played=TRUE"
." WHERE group_id=".$this->groupId;
return $CC_DBC->query($sql);
}
public function notifyMediaItemStartPlay($p_fileId) {
global $CC_CONFIG, $CC_DBC;
$sql = "UPDATE ".$CC_CONFIG['scheduleTable']
." SET media_item_played=TRUE"
." WHERE group_id=".$this->groupId
." AND file_id=".pg_escape_string($p_fileId);
return $CC_DBC->query($sql);
}
}
class Schedule {
@ -464,7 +495,7 @@ class Schedule {
$playlists[$pkey]['user_id'] = 0;
$playlists[$pkey]['id'] = $dx["playlist_id"];
$playlists[$pkey]['start'] = Schedule::CcTimeToPypoTime($dx["start"]);
$playlists[$pkey]['end'] = Schedule::CcTimeToPypoTime($dx["end"]);
$playlists[$pkey]['end'] = Schedule::CcTimeToPypoTime($dx["end"]);
}
}

View File

@ -244,6 +244,8 @@ CREATE TABLE "cc_schedule"
"fade_out" TIME default '00:00:00',
"cue_in" TIME default '00:00:00',
"cue_out" TIME default '00:00:00',
"schedule_group_played" BOOLEAN default 'f',
"media_item_played" BOOLEAN default 'f',
PRIMARY KEY ("id")
);

View File

@ -182,6 +182,8 @@
<column name="fade_out" phpName="FadeOut" type="TIME" required="false" defaultValue="00:00:00"/>
<column name="cue_in" phpName="CueIn" type="TIME" required="false" defaultValue="00:00:00"/>
<column name="cue_out" phpName="CueOut" type="TIME" required="false" defaultValue="00:00:00"/>
<column name="schedule_group_played" phpName="ScheduleGroupPlayed" type="BOOLEAN" required="false" defaultValue="0"/>
<column name="media_item_played" phpName="MediaItemPlayed" type="BOOLEAN" required="false" defaultValue="0"/>
</table>
<table name="cc_sess" phpName="CcSess">
<column name="sessid" phpName="Sessid" type="CHAR" size="32" primaryKey="true" required="true"/>

View File

@ -31,11 +31,26 @@ $pl = new Playlist();
$pl->create($playlistName);
// Add a media clip
$mediaFile = StoredFile::findByOriginalName("test10001.mp3");
$mediaFile = StoredFile::findByOriginalName("Manolo Camp - Morning Coffee.mp3");
if (is_null($mediaFile)) {
echo "Adding test audio clip to the database.\n";
$v = array("filepath" => __DIR__."/test10001.mp3");
$v = array("filepath" => __DIR__."/../../audio_samples/OpSound/Manolo Camp - Morning Coffee.mp3");
$mediaFile = StoredFile::Insert($v);
if (PEAR::isError($mediaFile)) {
var_dump($mediaFile);
exit();
}
}
$pl->addAudioClip($mediaFile->getId());
$mediaFile = StoredFile::findByOriginalName("Peter Rudenko - Opening.mp3");
if (is_null($mediaFile)) {
echo "Adding test audio clip to the database.\n";
$v = array("filepath" => __DIR__."/../../audio_samples/OpSound/Peter Rudenko - Opening.mp3");
$mediaFile = StoredFile::Insert($v);
if (PEAR::isError($mediaFile)) {
var_dump($mediaFile);
exit();
}
}
$pl->addAudioClip($mediaFile->getId());
echo "done.\n";