How to Get KAFM On Air Program
This How-to is intended for:
Any audience.
This is how to process the KAFM Google Calendar to get the current (On Air) program, and the next two programs.
I used a two-step process to get this information.
- Grab the KAFM Google Calendar in agenda mode and rip out the sections for today and tomorrow. Save the resulting file to the file system. This should be done through a cron job after midnight each night. We need two days worth of programs because by late tonight, the next two shows might be tomorrow morning.
Here is the first script:
###############################################################
# Call this file with /usr/bin/python getKAFMCal.py from
# cron to download the KAFM Schedule from Google Calendar
#
# After some crude parsing to grab just today and tomorrow,
# the file is saved to the file system as kafmschedule.html
#
import urllib
# URL for the calendar
cal = 'http://www.google.com/calendar/htmlembed?src=ryan%40kafmradio.org&mode=AG
ENDA'
# Location and name of file where output is saved
lf = '/home/zope/GoogleCalendar/kafmschedule.html'
sock = urllib.urlopen(cal)
htmlSource = sock.read()
sock.close()
#print htmlSource
# Now that we have the source of the KAFM Google Calendar we
# will parse it some, then save it for complete parsing for the
# On Air and two upcoming shows.
p1 = htmlSource.find('<div class="date-section date-section-even date-section-0
date-section-today">')
p2 = htmlSource.find('<div class="date-section date-section-even date-section-2"
>')
shows = htmlSource[p1:p2]
page = "<html><body>\n" + shows + "\n</body></html>"
f = open(lf, 'w+')
f.write(page)
f.close()
I have commented the script to explain its use. The lf line should be changed to use the location where you want the file saved. Even its name can be changed, just be sure to make the similar change in the next script which parses the file this one creates. There are some zope timing products that might work in place of cron.
Here is the parsing script. It needs to reside in the Extensions folder of the Zope instance that is running the KAFM web site.
#############################################################
# This file, getOnAir.py is called as an external method
# from Plone to get the current and next two shows for use
# in the KAFM web site.
# Written by Steve Rauch (steve@sundagger.info) for use by
# desighKiln on the kafm radio web site.
import urllib, os, datetime, time
from elementtree import ElementTree, HTMLTreeBuilder
from string import strip, split
def getOnAir():
"""
A cron job pulls down today and tomorrow from the KAFM
Google Calendar and saves it to the filesystem. This
script parses the content for use in the header of the
KAFM web site that shows the program that is currently
ON AIR, and the next two shows.
"""
# Set some variables we use, like the file we parse, and
# a counter that we need to turn on and off to check program
# start times against the time right now.
lf = '/home/zope/GoogleCalendar/kafmschedule.html'
counter = 0
showlist = [] # list used to hold possible current programs
showtime = [] # list of times parsed from Google Calendar
showname = [] # list of show names parsed from Google Calendar
# The calendar gives us the time of the show in HHam format.
# We need to compare that to the time right now, but need
# now to be in the same format for comparisons. Also, the
# server is not in Mountain time, but the radio station is.
# The timezone is set to match our needs.
os.environ['TZ'] = 'US/Mountain'
time.tzset()
now = datetime.datetime.now().time()
# You might want to expose this print statement to see if the server
# gives us Mountain time.
# print now
# Using ElementTree we parse the table for the tag and
# class pairs that contain the data we want, and ignore the
# rest of the markup. Since ElementTree is needed by Plone,
# I know it is installed on the server.
tree = ElementTree.parse(lf, parser=HTMLTreeBuilder.TreeBuilder())
root = tree.getroot()
# Event start time is in a td with class event-time
for row in tree.getiterator('td'):
cl = row.get('class')
if cl == 'event-time':
showtime.append(strip(row.text))
# Show name is in a span with class event-summary
for row in tree.getiterator('span'):
cl = row.get('class')
if cl == 'event-summary':
showname.append(row.text)
# I could not compare times in the 5pm format against each other, so I had
# to convert those to datetime objects. This only works for specific formats
# hence the test for the existence of a colon. Also, it appears that Google
# Calendar can return the time in 24 hour format, so a new test as well.
for i in range (len(showtime)):
t = showtime[i]
if ':' in t:
if t[-1] == 'm':
t2 = time.strptime(t, '%I:%M%p')
showinfo = showtime[i] +'|'+ showname[i] +'|'+ str(t2[3]) +'|'+ str(t2[4])
showlist.append(showinfo)
else:
t2 = time.strptime(t, '%H:%M')
showinfo = showtime[i] +'|'+ showname[i] +'|'+ str(t2[3]) +'|'+ str(t2[4])
showlist.append(showinfo)
else:
t2 = time.strptime(t, '%I%p')
showinfo = showtime[i] +'|'+ showname[i] +'|'+ str(t2[3]) +'|'+ str(t2[4])
showlist.append(showinfo)
# We had to save the hour and minute pieces above as text as you cannot
# concatenate string and datetime objects, and you can only append a single
# object. Now we have to turn them to integers in order to compare the
# program times against the time now.
# We do not have end times, just start times, so I had to use a counter
# to keep track of where we are. We just need to know the first show,
# so once we have that, we set the index and break from the loop.
for index, item in enumerate(showlist):
showstart, summary, h, m = split(item, '|')
progtime = datetime.time(int(h), int(m))
if progtime == now:
item1 = index
break
elif progtime <= now and counter == 0:
counter = counter + 1
elif progtime <= now and counter > 0:
counter = 0
else:
item1 = index -1
break
# This is a hack. But I had trouble returning the content in a way that
# that worked nicely as an external method (do not know why).
# To incorporate html I had to resort to using write statements, and had
# to define it. Then I set the index and following two items and parse
# those items from the list. The whole table ends up being spit out
# by the external method.
output = []
def write(s):
output.append(s)
write('''<table><tr><td class="rightAlign">''')
write('''<span class="onAir">On Air</span></td>''')
i, j, k = item1, item1 + 1, item1 + 2
showstart, summary, h, m = split(showlist[i], '|')
write('''<td class="leftAlign">%s</td></tr>''' %summary)
showstart, summary, h, m = split(showlist[j], '|')
write('''<tr><td class="rightAlign"><span class="time">%s</span></td>''' %sh
owstart)
write('''<td class="leftAlign">%s</td></tr>''' %summary)
showstart, summary, h, m = split(showlist[k], '|')
write('''<tr><td class="rightAlign"><span class="time">%s</span></td>''' %sh
owstart)
write('''<td class="leftAlign">%s</td></tr></table>''' %summary)
return '\n'.join(output)
Once the file is in place, go to the ZMI for the KAFM site and create an External Method. The id, module name, and function name are all getOnAir.
This script returns the full table that holds the On AIr info. I replaced the time of the first show to be On AIr. I used the html content that I found in the home-page-view.
The one thing to double check is that the script shows the time now in mountain time, not central time where the server is located.
Also, if the GoogleCalendar feed gives 24 hour times, the resulting time in the output is 24 hour format for now. Once we feel comfortable knowing what Google will give us, we will adjust that.
-- Steve