2010-11-05 15:54:15 +01:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author Jonas Ohrstrom <jonas@digris.ch>
"""
Python part of radio playout ( pypo )
2010-11-08 22:54:54 +01:00
The main functions are " fetch " ( . / pypo_cli . py - f ) and " push " ( . / pypo_cli . py - p )
2010-11-05 15:54:15 +01:00
2010-11-12 23:07:01 +01:00
There are two layers : scheduler & daypart ( fallback )
The daypart is a fallback - layer generated by the playlists daypart - settings
( eg a playlist creator can say that the list is good for Monday and Tues ,
between 14 : 00 and 16 : 00 ) . So if there is nothing in the schedule , pypo will
still play something ( instead of silence . . ) This layer is optional .
It is there so that you dont have a fallback player which plays the same 100
tracks over and over again .
2010-11-05 15:54:15 +01:00
Attention & ToDos
- liquidsoap does not like mono files ! So we have to make sure that only files with
2 channels are fed to LS
( solved : current = audio_to_stereo ( current ) - maybe not with ultimate performance )
made for python version 2.5 ! !
should work with 2.6 as well with a bit of adaption . for
sure the json parsing has to be changed
2010-11-08 22:54:54 +01:00
( 2.6 has an parser , pypo brings it ' s own -> util/json.py)
2010-11-05 15:54:15 +01:00
"""
# python defaults (debian default)
import time
import os
import traceback
from optparse import *
import sys
import time
import datetime
import logging
import logging . config
import shutil
import urllib
import urllib2
import pickle
import telnetlib
import random
import string
import operator
2010-11-08 22:54:54 +01:00
import inspect
2010-11-05 15:54:15 +01:00
# additional modules (should be checked)
from configobj import ConfigObj
# custom imports
from util import *
2010-11-08 22:54:54 +01:00
from api_clients import *
2010-11-05 15:54:15 +01:00
2010-11-08 22:54:54 +01:00
PYPO_VERSION = ' 0.2 '
2010-11-19 00:00:13 +01:00
PYPO_MEDIA_SKIP = 1
PYPO_MEDIA_LIVE_SESSION = 2
PYPO_MEDIA_STREAM = 3
PYPO_MEDIA_FILE_URL = 4
PYPO_MEDIA_FILE_LOCAL = 5
2010-11-05 15:54:15 +01:00
2010-11-08 22:54:54 +01:00
# Set up command-line options
2010-11-05 15:54:15 +01:00
parser = OptionParser ( )
# help screeen / info
usage = " % prog [options] " + " - python playout system "
parser = OptionParser ( usage = usage )
2010-11-08 22:54:54 +01:00
# Options
parser . add_option ( " -v " , " --compat " , help = " Check compatibility with server API version " , default = False , action = " store_true " , dest = " check_compat " )
2010-11-19 00:00:13 +01:00
parser . add_option ( " -t " , " --test " , help = " Do a test to make sure everything is working properly. " , default = False , action = " store_true " , dest = " test " )
2010-11-05 15:54:15 +01:00
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-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 " )
2010-11-08 22:54:54 +01:00
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 " )
2010-11-05 15:54:15 +01:00
parser . add_option ( " -c " , " --check " , help = " Check the cached schedule and exit " , default = False , action = " store_true " , dest = " check " )
# parse options
( options , args ) = parser . parse_args ( )
# configure logging
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 ' ]
2010-11-08 22:54:54 +01:00
API_BASE = config [ ' api_base ' ]
API_KEY = config [ ' api_key ' ]
2010-11-05 15:54:15 +01:00
POLL_INTERVAL = float ( config [ ' poll_interval ' ] )
PUSH_INTERVAL = float ( config [ ' push_interval ' ] )
LS_HOST = config [ ' ls_host ' ]
LS_PORT = config [ ' ls_port ' ]
2010-11-08 22:54:54 +01:00
#PREPARE_AHEAD = config['prepare_ahead']
2010-11-05 15:54:15 +01:00
CACHE_FOR = config [ ' cache_for ' ]
CUE_STYLE = config [ ' cue_style ' ]
2010-11-08 22:54:54 +01:00
#print config
2010-11-05 15:54:15 +01:00
except Exception , e :
2010-11-08 22:54:54 +01:00
print ' Error loading config file: ' , e
2010-11-05 15:54:15 +01:00
sys . exit ( )
#TIME = time.localtime(time.time())
TIME = ( 2010 , 6 , 26 , 15 , 33 , 23 , 2 , 322 , 0 )
class Global :
def __init__ ( self ) :
print
2010-11-08 22:54:54 +01:00
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 ( )
2010-11-05 15:54:15 +01:00
"""
"""
class Playout :
def __init__ ( self ) :
#print '# init fallback'
self . file_dir = FILE_DIR
self . tmp_dir = TMP_DIR
2010-11-08 22:54:54 +01:00
self . api_auth = urllib . urlencode ( { ' api_key ' : API_KEY } )
self . api_client = api_client . api_client_factory ( config )
2010-11-05 15:54:15 +01:00
self . cue_file = CueFile ( )
# set initial state
self . range_updated = False
2010-11-19 00:00:13 +01:00
def test_api ( self ) :
self . api_client . test ( )
2010-11-05 15:54:15 +01:00
"""
Fetching part of pypo
- Reads the scheduled entries of a given range ( actual time + / - " prepare_ahead " / " cache_for " )
- Saves a serialized file of the schedule
2010-11-08 22:54:54 +01:00
- playlists are prepared . ( brought to liquidsoap format ) and , if not mounted via nsf , files are copied
2010-11-05 15:54:15 +01:00
to the cache dir ( Folder - structure : cache / YYYY - MM - DD - hh - mm - ss )
- runs the cleanup routine , to get rid of unused cashed files
"""
def fetch ( self , export_source ) :
"""
2010-11-08 22:54:54 +01:00
wrapper script for fetching the whole schedule ( in json )
2010-11-05 15:54:15 +01:00
"""
logger = logging . getLogger ( " fetch " )
self . export_source = export_source
self . cache_dir = CACHE_DIR + self . export_source + ' / '
self . schedule_file = self . cache_dir + ' schedule '
try : os . mkdir ( self . cache_dir )
except Exception , e : pass
"""
Trigger daypart range - generation . ( Only if daypart - instance )
"""
if self . export_source == ' daypart ' :
print ' ****************************** '
print ' *** TRIGGER DAYPART UPDATE *** '
print ' ****************************** '
try :
self . generate_range_dp ( )
except Exception , e :
2010-11-19 00:00:13 +01:00
logger . error ( " %s " , e )
2010-11-05 15:54:15 +01:00
2010-11-08 22:54:54 +01:00
# get schedule
2010-11-05 15:54:15 +01:00
try :
2010-11-08 22:54:54 +01:00
while self . get_schedule ( ) != 1 :
2010-11-05 15:54:15 +01:00
logger . warning ( " failed to read from export url " )
time . sleep ( 1 )
2010-11-19 00:00:13 +01:00
except Exception , e : logger . error ( " %s " , e )
2010-11-05 15:54:15 +01:00
# prepare the playlists
if CUE_STYLE == ' pre ' :
try : self . prepare_playlists_cue ( self . export_source )
2010-11-19 00:00:13 +01:00
except Exception , e : logger . error ( " %s " , e )
2010-11-05 15:54:15 +01:00
elif CUE_STYLE == ' otf ' :
try : self . prepare_playlists ( self . export_source )
2010-11-19 00:00:13 +01:00
except Exception , e : logger . error ( " %s " , e )
2010-11-05 15:54:15 +01:00
# cleanup
try : self . cleanup ( self . export_source )
2010-11-19 00:00:13 +01:00
except Exception , e : logger . error ( " %s " , e )
2010-11-05 15:54:15 +01:00
logger . info ( " fetch loop completed " )
"""
This is actually a bit ugly ( again feel free to improve ! ! )
The generate_range_dp function should be called once a day ,
we do this at 18 h . The hour before the state is set back to ' False '
"""
def generate_range_dp ( self ) :
logger = logging . getLogger ( " generate_range_dp " )
logger . debug ( " trying to trigger daypart update " )
tnow = time . localtime ( time . time ( ) )
if ( tnow [ 3 ] == 16 ) :
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 " )
self . range_updated = True
except Exception , e :
print e
2010-11-08 22:54:54 +01:00
def get_schedule ( self ) :
logger = logging . getLogger ( " Playout.get_schedule " )
status , response = self . api_client . get_schedule ( ) ;
2010-11-05 15:54:15 +01:00
if status == 1 :
2010-11-08 22:54:54 +01:00
logger . info ( " dump serialized schedule to %s " , self . schedule_file )
schedule = response [ ' playlists ' ]
2010-11-05 15:54:15 +01:00
try :
schedule_file = open ( self . schedule_file , " w " )
pickle . dump ( schedule , schedule_file )
schedule_file . close ( )
except Exception , e :
2010-11-19 00:00:13 +01:00
logger . critical ( " Exception %s " , e )
2010-11-05 15:54:15 +01:00
status = 0
return status
"""
Alternative version of playout preparation . Every playlist entry is
pre - cued if neccessary ( cue_in / cue_out != 0 ) and stored in the
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 ) :
logger = logging . getLogger ( " fetch.prepare_playlists " )
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
2010-11-19 00:00:13 +01:00
# Dont do anything if schedule is empty
if ( not schedule ) :
return
2010-11-05 15:54:15 +01:00
try :
for pkey in sorted ( schedule . iterkeys ( ) ) :
logger . info ( " found playlist at %s " , pkey )
#print pkey
playlist = schedule [ pkey ]
# create playlist directory
try : os . mkdir ( self . cache_dir + str ( pkey ) )
except Exception , e : pass
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 ' ***************************************** '
# TODO: maybe a bit more modular..
silence_file = self . file_dir + ' basic/silence.mp3 '
2010-11-19 00:00:13 +01:00
mediaType = api_client . get_media_type ( playlist )
if ( mediaType == PYPO_MEDIA_SKIP ) :
#if int(playlist['played']) == 1:
2010-11-05 15:54:15 +01:00
logger . info ( " playlist %s already played / sent to liquidsoap, so will ignore it " , pkey )
elif int ( playlist [ ' subtype ' ] ) == 5 :
"""
This is a live session , so silence is scheduled
Maybe not the most elegant solution : )
It adds 20 time 30 min silence to the playlist
Silence file has to be in < file_dir > / basic / silence . mp3
"""
logger . debug ( " found %s seconds of live/studio session at %s " , pkey , playlist [ ' duration ' ] )
if os . path . isfile ( silence_file ) :
logger . debug ( ' file stored at: %s ' + silence_file )
for i in range ( 0 , 19 ) :
ls_playlist + = silence_file + " \n "
else :
print ' Could not find silence file! '
2010-11-12 23:07:01 +01:00
print ' File is expected to be at: ' + silence_file
logger . critical ( ' File is expected to be at: %s ' , silence_file )
2010-11-05 15:54:15 +01:00
sys . exit ( )
elif int ( playlist [ ' subtype ' ] ) == 6 :
"""
This is a live - cast session
2010-11-12 23:07:01 +01:00
Create a silence list . ( could eg also be a fallback list . . )
2010-11-05 15:54:15 +01:00
"""
logger . debug ( " found %s seconds of live-cast session at %s " , pkey , playlist [ ' duration ' ] )
if os . path . isfile ( silence_file ) :
logger . debug ( ' file stored at: %s ' + silence_file )
for i in range ( 0 , 19 ) :
ls_playlist + = silence_file + " \n "
else :
print ' Could not find silence file! '
2010-11-12 23:07:01 +01:00
print ' File is expected to be at: ' + silence_file
logger . critical ( ' File is expected to be at: %s ' , silence_file )
2010-11-05 15:54:15 +01:00
sys . exit ( )
elif int ( playlist [ ' subtype ' ] ) > 0 and int ( playlist [ ' subtype ' ] ) < 5 :
for media in playlist [ ' medias ' ] :
logger . debug ( " found track at %s " , media [ ' uri ' ] )
try :
src = media [ ' uri ' ]
if str ( media [ ' cue_in ' ] ) == ' 0 ' and str ( media [ ' cue_out ' ] ) == ' 0 ' :
dst = " %s %s / %s .mp3 " % ( self . cache_dir , str ( pkey ) , str ( media [ ' id ' ] ) )
do_cue = False
else :
dst = " %s %s / %s _cue_ %s - %s .mp3 " % \
( self . cache_dir , str ( pkey ) , str ( media [ ' id ' ] ) , str ( float ( media [ ' cue_in ' ] ) / 1000 ) , str ( float ( media [ ' cue_out ' ] ) / 1000 ) )
do_cue = True
#print "dst_cue: " + dst
# check if it is a remote file, if yes download
if src [ 0 : 4 ] == ' http ' and do_cue == False :
if os . path . isfile ( dst ) :
logger . debug ( " file already in cache: %s " , dst )
else :
logger . debug ( " try to download %s " , src )
2010-11-12 23:07:01 +01:00
api_client . get_media ( src , dst )
#try:
# print '** urllib auth with: ',
# print self.api_auth
# urllib.urlretrieve (src, dst, False, self.api_auth)
# logger.info("downloaded %s to %s", src, dst)
#except Exception, e:
# logger.error("%s", e)
2010-11-05 15:54:15 +01:00
elif src [ 0 : 4 ] == ' http ' and do_cue == True :
if os . path . isfile ( dst ) :
logger . debug ( " file already in cache: %s " , dst )
print ' cached '
else :
logger . debug ( " try to download and cue %s " , src )
print ' *** '
dst_tmp = self . tmp_dir + " " . join ( [ random . choice ( string . letters ) for i in xrange ( 10 ) ] ) + ' .mp3 '
print dst_tmp
print ' *** '
2010-11-12 23:07:01 +01:00
api_client . get_media ( src , dst_tmp )
#try:
# print '** urllib auth with: ',
# print self.api_auth
# urllib.urlretrieve (src, dst_tmp, False, self.api_auth)
# logger.info("downloaded %s to %s", src, dst_tmp)
#except Exception, e:
# logger.error("%s", e)
#
2010-11-05 15:54:15 +01:00
# cue
print " STARTIONG CUE "
print self . cue_file . cue ( dst_tmp , dst , float ( media [ ' cue_in ' ] ) / 1000 , float ( media [ ' cue_out ' ] ) / 1000 )
print " END CUE "
if True == os . access ( dst , os . R_OK ) :
try : fsize = os . path . getsize ( dst )
except Exception , e :
logger . error ( " %s " , e )
fsize = 0
if fsize > 0 :
logger . debug ( ' try to remove temporary file: %s ' + dst_tmp )
try : os . remove ( dst_tmp )
except Exception , e :
logger . error ( " %s " , e )
else :
logger . warning ( ' something went wrong cueing: %s - using uncued file ' + dst )
try : os . rename ( dst_tmp , dst )
except Exception , e :
logger . error ( " %s " , e )
else :
"""
Handle files on nas . Pre - cueing not implemented at the moment .
( not needed by openbroadcast , feel free to add this )
"""
# assume the file is local
# logger.info("local file assumed for : %s", src)
# dst = src
"""
Here an implementation for localy stored files .
Works the same as with remote files , just replaced API - download with
file copy .
"""
if do_cue == False :
if os . path . isfile ( dst ) :
logger . debug ( " file already in cache: %s " , dst )
else :
logger . debug ( " try to copy file to cache %s " , src )
try :
shutil . copy ( src , dst )
logger . info ( " copied %s to %s " , src , dst )
except Exception , e :
logger . error ( " %s " , e )
if do_cue == True :
if os . path . isfile ( dst ) :
logger . debug ( " file already in cache: %s " , dst )
else :
logger . debug ( " try to copy and cue %s " , src )
print ' *** '
dst_tmp = self . tmp_dir + " " . join ( [ random . choice ( string . letters ) for i in xrange ( 10 ) ] )
print dst_tmp
print ' *** '
try :
shutil . copy ( src , dst_tmp )
logger . info ( " copied %s to %s " , src , dst_tmp )
except Exception , e :
logger . error ( " %s " , e )
# cue
print " STARTIONG CUE "
print self . cue_file . cue ( dst_tmp , dst , float ( media [ ' cue_in ' ] ) / 1000 , float ( media [ ' cue_out ' ] ) / 1000 )
print " END CUE "
if True == os . access ( dst , os . R_OK ) :
try : fsize = os . path . getsize ( dst )
except Exception , e :
logger . error ( " %s " , e )
fsize = 0
if fsize > 0 :
logger . debug ( ' try to remove temporary file: %s ' + dst_tmp )
try : os . remove ( dst_tmp )
except Exception , e :
logger . error ( " %s " , e )
else :
logger . warning ( ' something went wrong cueing: %s - using uncued file ' + dst )
try : os . rename ( dst_tmp , dst )
except Exception , e :
logger . error ( " %s " , e )
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 )
fsize = 0
if fsize > 0 :
pl_entry = ' annotate:export_source= " %s " ,media_id= " %s " ,liq_start_next= " %s " ,liq_fade_in= " %s " ,liq_fade_out= " %s " : %s ' % \
( str ( media [ ' export_source ' ] ) , media [ ' id ' ] , 0 , str ( float ( media [ ' fade_in ' ] ) / 1000 ) , str ( float ( media [ ' fade_out ' ] ) / 1000 ) , dst )
print pl_entry
"""
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 . . . )
"""
ls_playlist + = pl_entry + " \n "
logger . debug ( " everything ok, adding %s to playlist " , pl_entry )
else :
print ' zero-file: ' + dst + ' from ' + src
logger . warning ( " zero-size file - skiping %s . will not add it to playlist " , dst )
else :
logger . warning ( " something went wrong. file %s not available. will not add it to playlist " , dst )
except Exception , e : logger . info ( " %s " , e )
"""
This is kind of hackish . We add a bunch of " silence " tracks to the end of each playlist .
So we can make sure the list does not get repeated just before a new one is called .
( or in case nothing is in the scheduler afterwards )
20 x silence = 10 hours
"""
for i in range ( 0 , 1 ) :
ls_playlist + = silence_file + " \n "
print ' ' ,
# write playlist file
plfile = open ( self . cache_dir + str ( pkey ) + ' /list.lsp ' , " w " )
plfile . write ( ls_playlist )
plfile . close ( )
logger . info ( ' ls playlist file written to %s ' , self . cache_dir + str ( pkey ) + ' /list.lsp ' )
except Exception , e :
logger . info ( " %s " , e )
def cleanup ( self , export_source ) :
logger = logging . getLogger ( " cleanup " )
self . export_source = export_source
self . cache_dir = CACHE_DIR + self . export_source + ' / '
self . schedule_file = self . cache_dir + ' schedule '
"""
Cleans up folders in cache_dir . Look for modification date older than " now - CACHE_FOR "
and deletes them .
"""
offset = 3600 * int ( CACHE_FOR )
now = time . time ( )
for r , d , f in os . walk ( CACHE_DIR ) :
for dir in d :
timestamp = os . path . getmtime ( os . path . join ( r , dir ) )
2010-11-19 00:00:13 +01:00
logger . debug ( ' Folder " Age " : %s - %s ' , round ( ( ( ( now - offset ) - timestamp ) / 60 ) , 2 ) , os . path . join ( r , dir ) )
2010-11-05 15:54:15 +01:00
if now - offset > timestamp :
try :
logger . debug ( ' trying to remove %s - timestamp: %s ' , os . path . join ( r , dir ) , timestamp )
shutil . rmtree ( os . path . join ( r , dir ) )
except Exception , e :
logger . error ( " %s " , e )
pass
else :
logger . info ( ' sucessfully removed %s ' , os . path . join ( r , dir ) )
"""
The counterpart - the push loop periodically ( minimal 1 / 2 of the playlist - grid )
2010-11-08 22:54:54 +01:00
checks if there is a playlist that should be scheduled at the current time .
2010-11-05 15:54:15 +01:00
If yes , the temporary liquidsoap playlist gets replaced with the corresponding one ,
2010-11-08 22:54:54 +01:00
then liquidsoap is asked ( via telnet ) to reload and immediately play it .
2010-11-05 15:54:15 +01:00
"""
def push ( self , export_source ) :
logger = logging . getLogger ( " push " )
self . export_source = export_source
self . cache_dir = CACHE_DIR + self . export_source + ' / '
self . schedule_file = self . cache_dir + ' schedule '
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 . 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_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 '
else :
for pkey in self . schedule :
logger . debug ( ' found playlist schedulet at: %s ' , pkey )
#if pkey[0:16] == str_tnow:
if pkey [ 0 : 16 ] == str_tcomming :
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
"""
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 ) :
self . schedule [ pkey ] [ ' played ' ] = 1
"""
Call api to update schedule states and
write changes back to cache file
"""
2010-11-08 22:54:54 +01:00
self . api_client . update_scheduled_item ( int ( playlist [ ' schedule_id ' ] ) , 1 )
2010-11-05 15:54:15 +01:00
schedule_file = open ( self . schedule_file , " w " )
pickle . dump ( self . schedule , schedule_file )
schedule_file . close ( )
#else:
# print 'Nothing to do...'
def push_init ( self , export_source ) :
logger = logging . getLogger ( " push_init " )
self . export_source = export_source
self . cache_dir = CACHE_DIR + self . export_source + ' / '
self . schedule_file = self . cache_dir + ' schedule '
# load the shedule from cache
logger . debug ( ' load shedule 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
return schedule
def push_liquidsoap ( self , pkey , ptype , user_id , playlist_id , transmission_id , push_ahead ) :
logger = logging . getLogger ( " push_liquidsoap " )
#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 '
src = self . cache_dir + str ( pkey ) + ' /list.lsp '
print src
try :
if True == os . access ( src , os . R_OK ) :
print ' OK - Can read '
pl_file = open ( src , " r " )
"""
i know this could be wrapped , maybe later . .
"""
tn = telnetlib . Telnet ( LS_HOST , 1234 )
if ( int ( ptype ) == 6 ) :
tn . write ( " live_in.start " )
tn . write ( " \n " )
if ( int ( ptype ) < 5 ) :
for line in pl_file . readlines ( ) :
print line . strip ( )
tn . write ( self . export_source + ' .push %s ' % ( line . strip ( ) ) )
tn . write ( " \n " )
#time.sleep(0.1)
tn . write ( " exit \n " )
print tn . read_all ( )
print ' sleeping for %s s ' % ( self . push_ahead )
time . sleep ( self . push_ahead )
print ' sending " flip " '
tn = telnetlib . Telnet ( LS_HOST , 1234 )
"""
Pass some extra information to liquidsoap
"""
print ' user_id: %s ' % user_id
print ' playlist_id: %s ' % playlist_id
print ' transmission_id: %s ' % transmission_id
print ' 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 )
# if(int(ptype) < 5):
# tn.write(self.export_source + '.flip')
# tn.write("\n")
tn . write ( self . export_source + ' .flip ' )
tn . write ( " \n " )
if ( int ( ptype ) == 6 ) :
tn . write ( " live.active 1 " )
tn . write ( " \n " )
else :
tn . write ( " live.active 0 " )
tn . write ( " \n " )
tn . write ( " live_in.stop " )
tn . write ( " \n " )
tn . write ( " exit \n " )
print tn . read_all ( )
status = 1
except Exception , e :
logger . error ( ' %s ' , e )
status = 0
return status
def push_liquidsoap_legacy ( self , pkey , ptype , p_id , user_id ) :
logger = logging . getLogger ( " push_liquidsoap " )
logger . debug ( ' trying to push %s to liquidsoap ' , pkey )
self . export_source = export_source
self . cache_dir = CACHE_DIR + self . export_source + ' / '
self . schedule_file = self . cache_dir + ' schedule '
src = self . cache_dir + str ( pkey ) + ' /list.lsp '
dst = self . cache_dir + ' current.lsp '
print src
print dst
print ' ************* '
print ptype
print ' ************* '
if True == os . access ( src , os . R_OK ) :
try :
shutil . copy2 ( src , dst )
logger . debug ( ' copy %s to %s ' , src , dst )
"""
i know this could be wrapped , maybe later . .
"""
tn = telnetlib . Telnet ( LS_HOST , 1234 )
tn . write ( " \n " )
tn . write ( " live_in.stop \n " )
tn . write ( " stream_disable \n " )
time . sleep ( 0.2 )
tn . write ( " \n " )
#tn.write("reload_current\n")
tn . write ( " current.reload \n " )
time . sleep ( 0.2 )
tn . write ( " skip_current \n " )
if ( int ( ptype ) == 6 ) :
"""
Couchcaster comming . Stop / Start live input to have ls re - read it ' s playlist
"""
print ' Couchcaster - switching to stream '
tn . write ( " live_in.start \n " )
time . sleep ( 0.2 )
tn . write ( " stream_enable \n " )
if ( int ( ptype ) == 7 ) :
"""
Recast comming . Start the live input
"""
print ' Recast - switching to stream '
tn . write ( " live_in.start \n " )
time . sleep ( 0.2 )
tn . write ( " stream_enable \n " )
"""
Pass some extra information to liquidsoap
"""
tn . write ( " pl.pl_id ' %s ' \n " % p_id )
tn . write ( " pl.user_id ' %s ' \n " % user_id )
tn . write ( " exit \n " )
print tn . read_all ( )
status = 1
except Exception , e :
logger . error ( ' %s ' , e )
status = 0
else :
status = 0
return status
"""
2010-11-08 22:54:54 +01:00
Updates the jingles . Give comma separated list of jingle tracks .
2010-11-05 15:54:15 +01:00
"""
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
2010-11-08 22:54:54 +01:00
src = API_BASE + ' api/pypo/get_media/ ' + str ( media_id )
2010-11-05 15:54:15 +01:00
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 check_schedule ( self , export_source ) :
logger = logging . getLogger ( " check_schedule " )
self . export_source = export_source
self . cache_dir = CACHE_DIR + self . export_source + ' / '
self . schedule_file = self . cache_dir + ' schedule '
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
#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 )
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 ' ----------------------------------------- '
for media in playlist [ ' medias ' ] :
print media
print
if __name__ == ' __main__ ' :
print
2010-11-08 22:54:54 +01:00
print ' ########################################### '
print ' # *** pypo *** # '
print ' # Liquidsoap + External Scheduler # '
print ' # Playout System # '
print ' ########################################### '
2010-11-05 15:54:15 +01:00
print
# initialize
g = Global ( )
g . selfcheck ( )
po = Playout ( )
run = True
while run == True :
logger = logging . getLogger ( " pypo " )
loops = 0
2010-11-19 00:00:13 +01:00
if options . test :
po . test_api ( )
sys . exit ( )
2010-11-05 15:54:15 +01:00
while options . fetch_scheduler :
try : po . fetch ( ' scheduler ' )
except Exception , e :
print e
sys . exit ( )
print ' ZZzZzZzzzzZZZz.... sleeping for ' + str ( POLL_INTERVAL ) + ' seconds '
logger . info ( ' fetch loop %s - ZZzZzZzzzzZZZz.... sleeping for %s seconds ' , loops , POLL_INTERVAL )
loops + = 1
time . sleep ( POLL_INTERVAL )
while options . fetch_daypart :
try : po . fetch ( ' daypart ' )
except Exception , e :
print e
sys . exit ( )
print ' ZZzZzZzzzzZZZz.... sleeping for ' + str ( POLL_INTERVAL ) + ' seconds '
logger . info ( ' fetch loop %s - ZZzZzZzzzzZZZz.... sleeping for %s seconds ' , loops , POLL_INTERVAL )
loops + = 1
time . sleep ( POLL_INTERVAL )
while options . push_scheduler :
po . push ( ' scheduler ' )
try : po . push ( ' scheduler ' )
except Exception , e :
print ' PUSH ERROR!! WILL EXIT NOW:( '
print e
sys . exit ( )
logger . info ( ' push loop %s - ZZzZzZzzzzZZZz.... sleeping for %s seconds ' , loops , PUSH_INTERVAL )
loops + = 1
time . sleep ( PUSH_INTERVAL )
while options . push_daypart :
po . push ( ' daypart ' )
try : po . push ( ' daypart ' )
except Exception , e :
print ' PUSH ERROR!! WILL EXIT NOW:( '
print e
sys . exit ( )
logger . info ( ' push loop %s - ZZzZzZzzzzZZZz.... sleeping for %s seconds ' , loops , PUSH_INTERVAL )
loops + = 1
time . sleep ( PUSH_INTERVAL )
while options . jingles :
try : po . update_jingles ( options . jingles )
except Exception , e :
print e
sys . exit ( )
while options . check :
try : po . check_schedule ( )
except Exception , e :
print e
sys . exit ( )
while options . cleanup :
try : po . cleanup ( )
except Exception , e :
print e
sys . exit ( )
2010-11-08 22:54:54 +01:00
2010-11-05 15:54:15 +01:00
sys . exit ( )