<telecaster>
- <url>http://localhost</url>
+ <host>augustins.pre-barreau.com</host>
<title>Pre-Barreau</title>
<port>8000</port>
<format>mp3</format>
Encode=MP3 Lame
BitrateNominal=96
OggQuality=3
-NumberChannels=1
+NumberChannels=2
Samplerate=44100
ServerType=Icecast2
ExternalFile=/tmp/test
ServerGenre=Teaching
#Advanced Settings
LogLevel=1
-LogFile=oddcastv3.log
+LogFile=/tmp/oddcastv3.log
SaveDirectoryFlag=1
SaveDirectory=/home/prebarreau/backup
SaveAsWAV=0
<short_name>Pre-Barreau</short_name>
<name>Pre-Barreau</name>
<description>La preparation au Barreau de Paris</description>
- <url>http://localhost</url>
+ <url>http://augustins.pre-barreau.com</url>
<genre>Other</genre>
- <channels>1</channels>
</infos>
<server>
<host>localhost</host>
<odd_conf_file>etc/telecaster_mp3.cfg</odd_conf_file>
<lock_file>lock/telecaster.lock</lock_file>
<rsync_host>localhost/tmp/</rsync_host>
+ <rss>
+ <dir>/var/www/rss/</dir>
+ </rss>
</server>
<media>
<record>true</record>
<raw_dir>/home/prebarreau/backup</raw_dir>
<format>mp3</format>
<bitrate>96</bitrate>
+ <channels>2</channels>
<ogg_quality>3</ogg_quality>
<samplerate>44100</samplerate>
- <voices>1</voices>
- <shuffle>1</shuffle>
</media>
+ <jack>
+ <input>
+ <name>jack_rack:out_1</name>
+ </input>
+ <input>
+ <name>jack_rack:out_2</name>
+ </input>
+ </jack>
</telecaster>
--- /dev/null
+\r
+//OBJECTS\r
+\r
+//objects inside the RSS2Item object\r
+function RSS2Enclosure(encElement)\r
+{\r
+ if (encElement == null)\r
+ {\r
+ this.url = null;\r
+ this.length = null;\r
+ this.type = null;\r
+ }\r
+ else\r
+ {\r
+ this.url = encElement.getAttribute("url");\r
+ this.length = encElement.getAttribute("length");\r
+ this.type = encElement.getAttribute("type");\r
+ }\r
+}\r
+\r
+function RSS2Guid(guidElement)\r
+{\r
+ if (guidElement == null)\r
+ {\r
+ this.isPermaLink = null;\r
+ this.value = null;\r
+ }\r
+ else\r
+ {\r
+ this.isPermaLink = guidElement.getAttribute("isPermaLink");\r
+ this.value = guidElement.childNodes[0].nodeValue;\r
+ }\r
+}\r
+\r
+function RSS2Source(souElement)\r
+{\r
+ if (souElement == null)\r
+ {\r
+ this.url = null;\r
+ this.value = null;\r
+ }\r
+ else\r
+ {\r
+ this.url = souElement.getAttribute("url");\r
+ this.value = souElement.childNodes[0].nodeValue;\r
+ }\r
+}\r
+\r
+//object containing the RSS 2.0 item\r
+function RSS2Item(itemxml)\r
+{\r
+ //required\r
+ this.title;\r
+ this.link;\r
+ this.description;\r
+\r
+ //optional vars\r
+ this.author;\r
+ this.comments;\r
+ this.pubDate;\r
+\r
+ //optional objects\r
+ this.category;\r
+ this.enclosure;\r
+ this.guid;\r
+ this.source;\r
+\r
+ var properties = new Array("title", "link", "description", "author", "comments", "pubDate");\r
+ var tmpElement = null;\r
+ for (var i=0; i<properties.length; i++)\r
+ {\r
+ tmpElement = itemxml.getElementsByTagName(properties[i])[0];\r
+ if (tmpElement != null)\r
+ eval("this."+properties[i]+"=tmpElement.childNodes[0].nodeValue");\r
+ }\r
+\r
+ this.category = new RSS2Category(itemxml.getElementsByTagName("category")[0]);\r
+ this.enclosure = new RSS2Enclosure(itemxml.getElementsByTagName("enclosure")[0]);\r
+ this.guid = new RSS2Guid(itemxml.getElementsByTagName("guid")[0]);\r
+ this.source = new RSS2Source(itemxml.getElementsByTagName("source")[0]);\r
+}\r
+\r
+//objects inside the RSS2Channel object\r
+function RSS2Category(catElement)\r
+{\r
+ if (catElement == null)\r
+ {\r
+ this.domain = null;\r
+ this.value = null;\r
+ }\r
+ else\r
+ {\r
+ this.domain = catElement.getAttribute("domain");\r
+ this.value = catElement.childNodes[0].nodeValue;\r
+ }\r
+}\r
+\r
+//object containing RSS image tag info\r
+function RSS2Image(imgElement)\r
+{\r
+ if (imgElement == null)\r
+ {\r
+ this.url = null;\r
+ this.link = null;\r
+ this.width = null;\r
+ this.height = null;\r
+ this.description = null;\r
+ }\r
+ else\r
+ {\r
+ imgAttribs = new Array("url","title","link","width","height","description");\r
+ for (var i=0; i<imgAttribs.length; i++)\r
+ if (imgElement.getAttribute(imgAttribs[i]) != null)\r
+ eval("this."+imgAttribs[i]+"=imgElement.getAttribute("+imgAttribs[i]+")");\r
+ }\r
+}\r
+\r
+//object containing the parsed RSS 2.0 channel\r
+function RSS2Channel(rssxml)\r
+{\r
+ //required\r
+ this.title;\r
+ this.link;\r
+ this.description;\r
+\r
+ //array of RSS2Item objects\r
+ this.items = new Array();\r
+\r
+ //optional vars\r
+ this.language;\r
+ this.copyright;\r
+ this.managingEditor;\r
+ this.webMaster;\r
+ this.pubDate;\r
+ this.lastBuildDate;\r
+ this.generator;\r
+ this.docs;\r
+ this.ttl;\r
+ this.rating;\r
+\r
+ //optional objects\r
+ this.category;\r
+ this.image;\r
+\r
+ var chanElement = rssxml.getElementsByTagName("channel")[0];\r
+ var itemElements = rssxml.getElementsByTagName("item");\r
+\r
+ for (var i=0; i<itemElements.length; i++)\r
+ {\r
+ Item = new RSS2Item(itemElements[i]);\r
+ this.items.push(Item);\r
+ //chanElement.removeChild(itemElements[i]);\r
+ }\r
+\r
+ var properties = new Array("title", "link", "description", "language", "copyright", "managingEditor", "webMaster", "pubDate", "lastBuildDate", "generator", "docs", "ttl", "rating");\r
+ var tmpElement = null;\r
+ for (var i=0; i<properties.length; i++)\r
+ {\r
+ tmpElement = chanElement.getElementsByTagName(properties[i])[0];\r
+ if (tmpElement!= null)\r
+ eval("this."+properties[i]+"=tmpElement.childNodes[0].nodeValue");\r
+ }\r
+\r
+ this.category = new RSS2Category(chanElement.getElementsByTagName("category")[0]);\r
+ this.image = new RSS2Image(chanElement.getElementsByTagName("image")[0]);\r
+}\r
+\r
+//PROCESSES\r
+\r
+//uses xmlhttpreq to get the raw rss xml\r
+function getRSS(url)\r
+{\r
+ //call the right constructor for the browser being used\r
+ if (window.ActiveXObject)\r
+ xhr = new ActiveXObject("Microsoft.XMLHTTP");\r
+ else if (window.XMLHttpRequest)\r
+ xhr = new XMLHttpRequest();\r
+ else\r
+ alert("not supported");\r
+\r
+ //prepare the xmlhttprequest object\r
+ xhr.open("GET",url,true);\r
+ xhr.setRequestHeader("Cache-Control", "no-cache");\r
+ xhr.setRequestHeader("Pragma", "no-cache");\r
+ xhr.onreadystatechange = function() {\r
+ if (xhr.readyState == 4)\r
+ {\r
+ if (xhr.status == 200)\r
+ {\r
+ if (xhr.responseText != null)\r
+ processRSS(xhr.responseXML);\r
+ else\r
+ {\r
+ alert("Failed to receive RSS file from the server - file not found.");\r
+ return false;\r
+ }\r
+ }\r
+ else\r
+ alert("Error code " + xhr.status + " received: " + xhr.statusText);\r
+ }\r
+ }\r
+\r
+ //send the request\r
+ xhr.send(null);\r
+}\r
+\r
+//processes the received rss xml\r
+function processRSS(rssxml)\r
+{\r
+ RSS = new RSS2Channel(rssxml);\r
+ showRSS(RSS);\r
+}\r
+\r
+//shows the RSS content in the browser\r
+function showRSS(RSS)\r
+{\r
+ //default values for html tags used\r
+ var imageTag = "<img id='chan_image'";\r
+ var startItemTag = "<div id='item'>";\r
+ var startTitle = "<div id='item_title'>";\r
+ var startLink = "<div id='item_link'>";\r
+ var startDescription = "<div id='item_description'>";\r
+ var endTag = "</div>";\r
+\r
+ //populate channel data\r
+ var properties = new Array("title","link","description","pubDate","copyright");\r
+ for (var i=0; i<properties.length; i++)\r
+ {\r
+ property = properties[i]\r
+ eval("document.getElementById('chan_"+property+"').innerHTML = ''");\r
+ curProp = eval("RSS."+property);\r
+ if (curProp != null){\r
+ eval("document.getElementById('chan_"+property+"').innerHTML = curProp");\r
+ }\r
+ }\r
+\r
+ //show the image\r
+ document.getElementById("chan_image_link").innerHTML = "";\r
+ if (RSS.image.src != null)\r
+ {\r
+ document.getElementById("chan_image_link").href = RSS.image.link;\r
+ document.getElementById("chan_image_link").innerHTML = imageTag\r
+ +" alt='"+RSS.image.description\r
+ +"' width='"+RSS.image.width\r
+ +"' height='"+RSS.image.height\r
+ +"' src='"+RSS.image.url\r
+ +"' "+"/>";\r
+ }\r
+\r
+ //populate the items\r
+ document.getElementById("chan_items").innerHTML = "";\r
+ for (var i=0; i<RSS.items.length; i++)\r
+ {\r
+ item_html = startItemTag;\r
+ item_html += (RSS.items[i].title == null) ? "" : startTitle + RSS.items[i].title + endTag;\r
+ item_html += (RSS.items[i].description == null) ? "" : startDescription + RSS.items[i].description + endTag;\r
+ item_html += (RSS.items[i].link == null) ? "" : startLink + "<a href='" + RSS.items[i].link + "'>" + RSS.items[i].link + "</a>" + endTag;\r
+ item_html += endTag;\r
+ document.getElementById("chan_items").innerHTML += item_html;\r
+ }\r
+\r
+ //we're done\r
+ //document.getElementById("chan").style.visibility = "visible";\r
+ return true;\r
+}\r
+\r
+var xhr;\r
+\r
+\r
+function callExternalScript(url){\r
+ var n = document.createElement("script");\r
+ n.setAttribute("type", "text/javascript");\r
+ n.setAttribute("src", url);\r
+ document.getElementsByTagName("head")[0].appendChild(n);\r
+}\r
#!/usr/bin/python
+# -*- coding: utf-8 -*-
# *-* coding: utf-8 *-*
"""
telecaster
import codecs
import string
import signal
+import jack
import unicodedata
from tools import *
from mutagen.oggvorbis import OggVorbis
class Conference:
"""A conference object including metadata"""
-
+
def __init__(self, dict):
+ self.dict = dict
self.title = dict['title']
self.department = dict['department']
self.conference = dict['conference']
class Station(Conference):
"""Control the Oddcastv3-jack thread which send audio data to the icecast server
and the Streamripper thread which write audio on the hard disk"""
-
+
def __init__(self, conf_file, conference_dict, lock_file):
Conference.__init__(self, conference_dict)
self.date = datetime.datetime.now().strftime("%Y")
self.time = datetime.datetime.now().strftime("%x-%X")
- self.time1 = self.time.replace('/','_')
- self.time2 = self.time1.replace(':','_')
- self.time = self.time2.replace(' ','_')
+ self.time_txt = self.time.replace('/','_').replace(':','_').replace(' ','_')
self.conf = xml2dict(conf_file)
self.conf = self.conf['telecaster']
self.root_dir = self.conf['server']['root_dir']
+ self.url_ext = self.conf['infos']['url']
self.media_dir = self.conf['media']['dir']
self.host = self.conf['server']['host']
self.port = self.conf['server']['port']
+ self.rss_dir = self.conf['server']['rss']['dir']
+ self.rss_file = 'telecaster.xml'
self.password = self.conf['server']['sourcepassword']
- self.url = 'http://'+self.host+':'+self.port
+ self.url_int = 'http://'+self.host+':'+self.port
self.odd_conf_file = self.conf['server']['odd_conf_file']
self.bitrate = self.conf['media']['bitrate']
+ self.dict['Bitrate'] = str(self.bitrate) + ' kbps'
+ self.ogg_quality = self.conf['media']['ogg_quality']
self.format = self.conf['media']['format']
+ self.channels = int(self.conf['media']['channels'])
self.description = [self.title, self.department, self.conference, self.session, self.professor, self.comment]
self.server_name = [self.title, self.department, self.conference]
self.ServerDescription = clean_string('_-_'.join(self.description))
clean_string(self.department) + '_-_' + \
clean_string(self.conference)+'.'+self.format
self.lock_file = self.root_dir + os.sep + self.conf['server']['lock_file']
- self.filename = clean_string('_-_'.join(self.description[1:])) + '_-_' + self.time + '.' + self.format
+ self.filename = clean_string('_-_'.join(self.description[1:])) + '_-_' + self.time_txt + '.' + self.format
self.output_dir = self.media_dir + os.sep + self.department + os.sep + self.date
self.file_dir = self.output_dir + os.sep + self.ServerName
self.uid = os.getuid()
self.odd_pid = get_pid('^oddcastv3\ -n', self.uid)
- self.rip_pid = get_pid('streamripper ' + self.url + self.mount_point, self.uid)
+ self.rip_pid = get_pid('streamripper ' + self.url_int + self.mount_point, self.uid)
self.new_title = clean_string('_-_'.join(self.server_name)+'_-_'+self.session+'_-_'+self.professor+'_-_'+self.comment)
self.short_title = clean_string('_-_'.join(self.conference)+'_-_'+self.session+'_-_'+self.professor+'_-_'+self.comment)
self.genre = 'Vocal'
if not os.path.exists(self.raw_dir):
os.makedirs(self.raw_dir)
+ self.jack_inputs = []
+ if 'jack' in self.conf:
+ for jack_input in self.conf['jack']['input']:
+ self.jack_inputs.append(jack_input['name'])
+
def set_oddcast_conf(self):
#oddconf_temp = NamedTemporaryFile(suffix='.cfg')
oddconf = open(self.odd_conf_file,'r')
if 'ServerDescription' in line.split('='):
newlines.append('ServerDescription=' + \
self.ServerDescription.replace(' ','_') + '\n')
-
+
elif 'ServerName' in line.split('='):
newlines.append('ServerName=' + self.ServerName + '\n')
elif 'ServerPassword' in line.split('='):
newlines.append('ServerPassword=' + self.password + '\n')
-
+
elif 'SaveDirectory' in line.split('='):
newlines.append('SaveDirectory=' + self.raw_dir + '\n')
-
+ elif 'NumberChannels' in line.split('='):
+ newlines.append('NumberChannels=' + str(len(self.jack_inputs)) + '\n')
+ elif 'BitrateNominal' in line.split('='):
+ newlines.append('BitrateNominal=' + str(self.bitrate) + '\n')
+ elif 'OggQuality' in line.split('='):
+ newlines.append('OggQuality=' + str(self.ogg_quality) + '\n')
else:
newlines.append(line)
self.odd_conf = odd_conf_file
def start_oddcast(self):
+ if not self.jack_inputs:
+ jack.attach('telecaster')
+ for jack_input in jack.get_ports():
+ if 'system' in jack_input and 'capture' in jack_input.split(':')[1] :
+ self.jack_inputs.append(jack_input)
+ jack_ports = ' '.join(self.jack_inputs)
command = 'oddcastv3 -n "'+clean_string(self.conference)[0:16]+'" -c "'+self.odd_conf+ \
- '" alsa_pcm:capture_1 > /dev/null &'
+ '" '+ jack_ports + ' > /dev/null &'
os.system(command)
self.set_lock()
time.sleep(1)
def start_rip(self):
if not os.path.exists(self.output_dir):
os.makedirs(self.output_dir)
- command = 'streamripper ' + self.url + self.mount_point + \
+ command = 'streamripper ' + self.url_int + self.mount_point + \
' -d '+self.output_dir+' -D \"%S\" -s -t --quiet > /dev/null &'
os.system(command)
time.sleep(1)
def stop_oddcast(self):
if len(self.odd_pid) != 0:
os.system('kill -9 '+self.odd_pid[0])
-
+
def stop_rip(self):
if len(self.rip_pid) != 0:
os.system('kill -9 ' + self.rip_pid[0])
time.sleep(1)
date = datetime.datetime.now().strftime("%Y")
if os.path.exists(self.file_dir) and os.path.exists(self.file_dir + os.sep + 'incomplete'):
- shutil.move(self.file_dir+os.sep+'incomplete'+os.sep+' - .'+self.format, self.file_dir+os.sep)
- os.rename(self.file_dir+os.sep+' - .'+self.format, self.file_dir+os.sep+self.filename)
- shutil.rmtree(self.file_dir+os.sep+'incomplete'+os.sep)
+ try:
+ shutil.move(self.file_dir+os.sep+'incomplete'+os.sep+' - .'+self.format, self.file_dir+os.sep)
+ os.rename(self.file_dir+os.sep+' - .'+self.format, self.file_dir+os.sep+self.filename)
+ shutil.rmtree(self.file_dir+os.sep+'incomplete'+os.sep)
+ except:
+ pass
def mp3_convert(self):
os.system('oggdec -o - '+ self.file_dir+os.sep+self.filename+' | lame -S -m m -h -b '+ self.bitrate + \
' --add-id3v2 --tt "'+ self.new_title + '" --ta "'+self.professor+'" --tl "'+self.title+'" --ty "'+self.date+ \
'" --tg "'+self.genre+'" - ' + self.file_dir+os.sep+self.ServerDescription + '.mp3 &')
-
+
def write_tags_ogg(self):
file = self.file_dir + os.sep + self.filename
if os.path.exists(file):
audio['ENCODER'] = self.encoder
audio['COMMENT'] = self.comment
audio.save()
-
+
def write_tags_mp3(self):
file = self.file_dir + os.sep + self.filename
if os.path.exists(file):
self.set_oddcast_conf()
self.start_oddcast()
self.start_rip()
+ self.update_rss()
def stop(self):
self.stop_rip()
#self.mp3_convert()
#self.rsync_out()
- def start_mp3cast(self):
+ def start_mp3cast(self):
item_id = item_id
source = source
metadata = metadata
stream = self.core_process(self.command,self.buffer_size,self.dest)
for chunk in stream:
yield chunk
-
+
def core_process(self, command, buffer_size, dest):
- """Encode and stream audio data through a generator"""
+ """Encode and stream audio data through a generator"""
__chunk = 0
file_out = open(dest,'w')
try:
local_uname = os.uname()
hostname = local_uname[1]
os.system('rsync -a '+self.media_dir+os.sep+' '+self.rsync_host+':'+os.sep+hostname+os.sep)
+
+ def update_rss(self):
+ rss_item_list = []
+ if not os.path.exists(self.rss_dir):
+ os.makedirs(self.rss_dir)
+
+ time_now = datetime.datetime.now().strftime("%x-%X")
+
+ media_description = '<table>'
+ media_description_item = '<tr><td>%s: </td><td><b>%s</b></td></tr>'
+ for key in self.dict.keys():
+ if self.dict[key] != '':
+ media_description += media_description_item % (key.capitalize(), self.dict[key])
+ media_description += '</table>'
+
+ media_link = self.url_ext + '/rss/' + self.rss_file
+ media_link = media_link.decode('utf-8')
+
+ rss_item_list.append(RSSItem(
+ title = self.ServerName,
+ link = media_link,
+ description = media_description,
+ guid = Guid(media_link),
+ pubDate = self.time_txt,)
+ )
+
+ rss = RSS2(title = self.title + ' - ' + self.department,
+ link = self.url_ext,
+ description = self.ServerDescription.decode('utf-8'),
+ lastBuildDate = str(time_now),
+ items = rss_item_list,)
+
+ f = open(self.rss_dir + os.sep + self.rss_file, 'w')
+ rss.write_xml(f, 'utf-8')
+ f.close()
--- /dev/null
+"""PyRSS2Gen - A Python library for generating RSS 2.0 feeds."""
+
+__name__ = "PyRSS2Gen"
+__version__ = (1, 0, 0)
+__author__ = "Andrew Dalke <dalke@dalkescientific.com>"
+
+_generator_name = __name__ + "-" + ".".join(map(str, __version__))
+
+import datetime
+
+# Could make this the base class; will need to add 'publish'
+class WriteXmlMixin:
+ def write_xml(self, outfile, encoding = "iso-8859-1"):
+ from xml.sax import saxutils
+ handler = saxutils.XMLGenerator(outfile, encoding)
+ handler.startDocument()
+ self.publish(handler)
+ handler.endDocument()
+
+ def to_xml(self, encoding = "iso-8859-1"):
+ try:
+ import cStringIO as StringIO
+ except ImportError:
+ import StringIO
+ f = StringIO.StringIO()
+ self.write_xml(f, encoding)
+ return f.getvalue()
+
+
+def _element(handler, name, obj, d = {}):
+ if isinstance(obj, basestring) or obj is None:
+ # special-case handling to make the API easier
+ # to use for the common case.
+ handler.startElement(name, d)
+ if obj is not None:
+ handler.characters(obj)
+ handler.endElement(name)
+ else:
+ # It better know how to emit the correct XML.
+ obj.publish(handler)
+
+def _opt_element(handler, name, obj):
+ if obj is None:
+ return
+ _element(handler, name, obj)
+
+
+def _format_date(dt):
+ """convert a datetime into an RFC 822 formatted date
+
+ Input date must be in GMT.
+ """
+ # Looks like:
+ # Sat, 07 Sep 2002 00:00:01 GMT
+ # Can't use strftime because that's locale dependent
+ #
+ # Isn't there a standard way to do this for Python? The
+ # rfc822 and email.Utils modules assume a timestamp. The
+ # following is based on the rfc822 module.
+ return "%s, %02d %s %04d %02d:%02d:%02d GMT" % (
+ ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][dt.weekday()],
+ dt.day,
+ ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][dt.month-1],
+ dt.year, dt.hour, dt.minute, dt.second)
+
+
+##
+# A couple simple wrapper objects for the fields which
+# take a simple value other than a string.
+class IntElement:
+ """implements the 'publish' API for integers
+
+ Takes the tag name and the integer value to publish.
+
+ (Could be used for anything which uses str() to be published
+ to text for XML.)
+ """
+ element_attrs = {}
+ def __init__(self, name, val):
+ self.name = name
+ self.val = val
+ def publish(self, handler):
+ handler.startElement(self.name, self.element_attrs)
+ handler.characters(str(self.val))
+ handler.endElement(self.name)
+
+class DateElement:
+ """implements the 'publish' API for a datetime.datetime
+
+ Takes the tag name and the datetime to publish.
+
+ Converts the datetime to RFC 2822 timestamp (4-digit year).
+ """
+ def __init__(self, name, dt):
+ self.name = name
+ self.dt = dt
+ def publish(self, handler):
+ _element(handler, self.name, _format_date(self.dt))
+####
+
+class Category:
+ """Publish a category element"""
+ def __init__(self, category, domain = None):
+ self.category = category
+ self.domain = domain
+ def publish(self, handler):
+ d = {}
+ if self.domain is not None:
+ d["domain"] = self.domain
+ _element(handler, "category", self.category, d)
+
+class Cloud:
+ """Publish a cloud"""
+ def __init__(self, domain, port, path,
+ registerProcedure, protocol):
+ self.domain = domain
+ self.port = port
+ self.path = path
+ self.registerProcedure = registerProcedure
+ self.protocol = protocol
+ def publish(self, handler):
+ _element(handler, "cloud", None, {
+ "domain": self.domain,
+ "port": str(self.port),
+ "path": self.path,
+ "registerProcedure": self.registerProcedure,
+ "protocol": self.protocol})
+
+class Image:
+ """Publish a channel Image"""
+ element_attrs = {}
+ def __init__(self, url, title, link,
+ width = None, height = None, description = None):
+ self.url = url
+ self.title = title
+ self.link = link
+ self.width = width
+ self.height = height
+ self.description = description
+
+ def publish(self, handler):
+ handler.startElement("image", self.element_attrs)
+
+ _element(handler, "url", self.url)
+ _element(handler, "title", self.title)
+ _element(handler, "link", self.link)
+
+ width = self.width
+ if isinstance(width, int):
+ width = IntElement("width", width)
+ _opt_element(handler, "width", width)
+
+ height = self.height
+ if isinstance(height, int):
+ height = IntElement("height", height)
+ _opt_element(handler, "height", height)
+
+ _opt_element(handler, "description", self.description)
+
+ handler.endElement("image")
+
+class Guid:
+ """Publish a guid
+
+ Defaults to being a permalink, which is the assumption if it's
+ omitted. Hence strings are always permalinks.
+ """
+ def __init__(self, guid, isPermaLink = 1):
+ self.guid = guid
+ self.isPermaLink = isPermaLink
+ def publish(self, handler):
+ d = {}
+ if self.isPermaLink:
+ d["isPermaLink"] = "true"
+ else:
+ d["isPermaLink"] = "false"
+ _element(handler, "guid", self.guid, d)
+
+class TextInput:
+ """Publish a textInput
+
+ Apparently this is rarely used.
+ """
+ element_attrs = {}
+ def __init__(self, title, description, name, link):
+ self.title = title
+ self.description = description
+ self.name = name
+ self.link = link
+
+ def publish(self, handler):
+ handler.startElement("textInput", self.element_attrs)
+ _element(handler, "title", self.title)
+ _element(handler, "description", self.description)
+ _element(handler, "name", self.name)
+ _element(handler, "link", self.link)
+ handler.endElement("textInput")
+
+
+class Enclosure:
+ """Publish an enclosure"""
+ def __init__(self, url, length, type):
+ self.url = url
+ self.length = length
+ self.type = type
+ def publish(self, handler):
+ _element(handler, "enclosure", None,
+ {"url": self.url,
+ "length": str(self.length),
+ "type": self.type,
+ })
+
+class Source:
+ """Publish the item's original source, used by aggregators"""
+ def __init__(self, name, url):
+ self.name = name
+ self.url = url
+ def publish(self, handler):
+ _element(handler, "source", self.name, {"url": self.url})
+
+class SkipHours:
+ """Publish the skipHours
+
+ This takes a list of hours, as integers.
+ """
+ element_attrs = {}
+ def __init__(self, hours):
+ self.hours = hours
+ def publish(self, handler):
+ if self.hours:
+ handler.startElement("skipHours", self.element_attrs)
+ for hour in self.hours:
+ _element(handler, "hour", str(hour))
+ handler.endElement("skipHours")
+
+class SkipDays:
+ """Publish the skipDays
+
+ This takes a list of days as strings.
+ """
+ element_attrs = {}
+ def __init__(self, days):
+ self.days = days
+ def publish(self, handler):
+ if self.days:
+ handler.startElement("skipDays", self.element_attrs)
+ for day in self.days:
+ _element(handler, "day", day)
+ handler.endElement("skipDays")
+
+class RSS2(WriteXmlMixin):
+ """The main RSS class.
+
+ Stores the channel attributes, with the "category" elements under
+ ".categories" and the RSS items under ".items".
+ """
+
+ rss_attrs = {"version": "2.0"}
+ element_attrs = {}
+ def __init__(self,
+ title,
+ link,
+ description,
+
+ language = None,
+ copyright = None,
+ managingEditor = None,
+ webMaster = None,
+ pubDate = None, # a datetime, *in* *GMT*
+ lastBuildDate = None, # a datetime
+
+ categories = None, # list of strings or Category
+ generator = _generator_name,
+ docs = "http://blogs.law.harvard.edu/tech/rss",
+ cloud = None, # a Cloud
+ ttl = None, # integer number of minutes
+
+ image = None, # an Image
+ rating = None, # a string; I don't know how it's used
+ textInput = None, # a TextInput
+ skipHours = None, # a SkipHours with a list of integers
+ skipDays = None, # a SkipDays with a list of strings
+
+ items = None, # list of RSSItems
+ ):
+ self.title = title
+ self.link = link
+ self.description = description
+ self.language = language
+ self.copyright = copyright
+ self.managingEditor = managingEditor
+
+ self.webMaster = webMaster
+ self.pubDate = pubDate
+ self.lastBuildDate = lastBuildDate
+
+ if categories is None:
+ categories = []
+ self.categories = categories
+ self.generator = generator
+ self.docs = docs
+ self.cloud = cloud
+ self.ttl = ttl
+ self.image = image
+ self.rating = rating
+ self.textInput = textInput
+ self.skipHours = skipHours
+ self.skipDays = skipDays
+
+ if items is None:
+ items = []
+ self.items = items
+
+ def publish(self, handler):
+ handler.startElement("rss", self.rss_attrs)
+ handler.startElement("channel", self.element_attrs)
+ _element(handler, "title", self.title)
+ _element(handler, "link", self.link)
+ _element(handler, "description", self.description)
+
+ self.publish_extensions(handler)
+
+ _opt_element(handler, "language", self.language)
+ _opt_element(handler, "copyright", self.copyright)
+ _opt_element(handler, "managingEditor", self.managingEditor)
+ _opt_element(handler, "webMaster", self.webMaster)
+
+ pubDate = self.pubDate
+ if isinstance(pubDate, datetime.datetime):
+ pubDate = DateElement("pubDate", pubDate)
+ _opt_element(handler, "pubDate", pubDate)
+
+ lastBuildDate = self.lastBuildDate
+ if isinstance(lastBuildDate, datetime.datetime):
+ lastBuildDate = DateElement("lastBuildDate", lastBuildDate)
+ _opt_element(handler, "lastBuildDate", lastBuildDate)
+
+ for category in self.categories:
+ if isinstance(category, basestring):
+ category = Category(category)
+ category.publish(handler)
+
+ _opt_element(handler, "generator", self.generator)
+ _opt_element(handler, "docs", self.docs)
+
+ if self.cloud is not None:
+ self.cloud.publish(handler)
+
+ ttl = self.ttl
+ if isinstance(self.ttl, int):
+ ttl = IntElement("ttl", ttl)
+ _opt_element(handler, "tt", ttl)
+
+ if self.image is not None:
+ self.image.publish(handler)
+
+ _opt_element(handler, "rating", self.rating)
+ if self.textInput is not None:
+ self.textInput.publish(handler)
+ if self.skipHours is not None:
+ self.skipHours.publish(handler)
+ if self.skipDays is not None:
+ self.skipDays.publish(handler)
+
+ for item in self.items:
+ item.publish(handler)
+
+ handler.endElement("channel")
+ handler.endElement("rss")
+
+ def publish_extensions(self, handler):
+ # Derived classes can hook into this to insert
+ # output after the three required fields.
+ pass
+
+
+
+class RSSItem(WriteXmlMixin):
+ """Publish an RSS Item"""
+ element_attrs = {}
+ def __init__(self,
+ title = None, # string
+ link = None, # url as string
+ description = None, # string
+ author = None, # email address as string
+ categories = None, # list of string or Category
+ comments = None, # url as string
+ enclosure = None, # an Enclosure
+ guid = None, # a unique string
+ pubDate = None, # a datetime
+ source = None, # a Source
+ ):
+
+ if title is None and description is None:
+ raise TypeError(
+ "must define at least one of 'title' or 'description'")
+ self.title = title
+ self.link = link
+ self.description = description
+ self.author = author
+ if categories is None:
+ categories = []
+ self.categories = categories
+ self.comments = comments
+ self.enclosure = enclosure
+ self.guid = guid
+ self.pubDate = pubDate
+ self.source = source
+ # It sure does get tedious typing these names three times...
+
+ def publish(self, handler):
+ handler.startElement("item", self.element_attrs)
+ _opt_element(handler, "title", self.title)
+ _opt_element(handler, "link", self.link)
+ self.publish_extensions(handler)
+ _opt_element(handler, "description", self.description)
+ _opt_element(handler, "author", self.author)
+
+ for category in self.categories:
+ if isinstance(category, basestring):
+ category = Category(category)
+ category.publish(handler)
+
+ _opt_element(handler, "comments", self.comments)
+ if self.enclosure is not None:
+ self.enclosure.publish(handler)
+ _opt_element(handler, "guid", self.guid)
+
+ pubDate = self.pubDate
+ if isinstance(pubDate, datetime.datetime):
+ pubDate = DateElement("pubDate", pubDate)
+ _opt_element(handler, "pubDate", pubDate)
+
+ if self.source is not None:
+ self.source.publish(handler)
+
+ handler.endElement("item")
+
+ def publish_extensions(self, handler):
+ # Derived classes can hook into this to insert
+ # output after the title and link elements
+ pass
+# -*- coding: utf-8 -*-
from xmltodict import *
from tools import *
from acpi import *
+from PyRSS2Gen import *
class WebView(FieldStorage):
"""Gives the web CGI frontend"""
-
+
def __init__(self, school_file, url, version):
FieldStorage.__init__(self)
self.version = version
break
except:
self.ip = 'localhost'
- self.url = 'http://' + self.ip
+ if 'host' in self.conf:
+ self.host = self.conf['host']
+ else:
+ self.host = self.ip
+ self.url = 'http://' + self.host
+ self.rss_url = self.url+'/rss/telecaster.xml'
self.port = self.conf['port']
self.acpi = acpi.Acpi()
self.format = self.conf['format']
print "<HEAD>"
print "<TITLE>TeleCaster - "+self.title+"</TITLE>"
print "<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\">"
+
print "<link href=\""+self.url+"/telecaster/css/telecaster.css\" rel=\"stylesheet\" type=\"text/css\">"
+
print '<script language="Javascript" type="text/javascript" >'
print 'function choix(formulaire)'
print '{var j; var i = formulaire.department.selectedIndex;'
print '}'
print ' formulaire.conference.selectedIndex=0;}'
print '</script>'
+
+
+ print "<script type=\"text/javascript\" src=\"js/rssajax.js\"></script>"
+ print "<script type=\"text/javascript\">"
+ print " function rss_reload(url) {"
+ print " getRSS(url)"
+ print " setTimeout(\"rss_reload(\'\" + url + \"\')\", 10000);}"
+ print "</script>"
if self.refresh:
print "<meta http-equiv=\"refresh\" content=\"10; URL=telecaster.py\">"
print "</HEAD>\n"
-
- print "<BODY BGCOLOR =\"#FFFFFF\">"
+ #print "<BODY bgcolor =\"#ffffff\" onload=\"rss_reload(\'" + self.rss_url + "\');\">"
+ print "<BODY bgcolor =\"#ffffff\">"
print "<div class=\"bg\">"
print "<div class=\"header\">"
print "<H3> TeleCaster - L'enregistrement et la diffusion audio en direct par internet</H3>"
print "<div class=\"colophon\">"
print "TeleCaster "+self.version+" © <span>"+date+"</span> <a href=\"http://parisson.com\">Parisson SARL</a>. Tous droits réservés."
print "</div>"
-
+
def footer(self):
print "</div>"
print "</BODY>"
power_info = "<span style=\"color: green\">secteur</span>"
else:
power_info = ""
-
+
#if self.power_state == 0:
#batt_info = "en décharge"
#elif self.power_state == 1:
jackd_info = '<span style=\"color: red\">éteint</span>'
else:
jackd_info = '<span style=\"color: green\">démarré</span>'
-
+
print "<div class=\"hardware\">"
print "<div class=\"title\">Informations matérielles</div>"
print "<table class=\"hardware\">"
print "<td>%s</td></tr>" % jackd_info
print "</table>"
print "</div>"
-
+
def start_form(self, message=''):
self.refresh = False
print "<div class=\"tools\">"
print "<div class=\"buttons\">"
#print "<INPUT TYPE = hidden NAME = \"action\" VALUE = \"start\">"
- print "<button type=\"submit\" name=\"action\" value=\"start\" class=\"negative\"><img src=\"img/stop.png\" alt=\"\">Record</button>"
print "<button type=\"submit\" class=\"positive\"><img src=\"img/arrow_refresh.png\" alt=\"\">Refresh</button>"
+ print "<button type=\"submit\" name=\"action\" value=\"start\" class=\"negative\"><img src=\"img/stop.png\" alt=\"\">Record</button>"
print "<a href=\""+self.url+"/media/\"><img src=\"img/folder_go.png\" alt=\"\">Archives</a>"
print "<a href=\""+self.url+"/backup/\"><img src=\"img/bin.png\" alt=\"\">Trash</a>"
#print "<INPUT TYPE = submit VALUE = \"Enregistrer\">"
print "</table>"
#print "<h5><a href=\""+self.url+":"+self.port+"/"+clean_string(self.title)+"_-_"+clean_string(department)+"_-_"+clean_string(conference)+"."+self.format+".m3u\">Cliquez ici pour écouter cette formation en direct</a></h5>"
print "</div>"
+
+ print """<div class="rss" id="chan">
+ <b><div id="chan_description"></div></b><br>
+ <div id="chan_title"></div>
+ <div id="chan_link"></div>
+ <div id="chan_description"></div>
+ <a id="chan_image_link" href=""></a>
+ <div id="chan_items"></div>
+ <div id="chan_pubDate"></div>
+ <div id="chan_copyright"></div>
+ </div>"""
+
print "<div class=\"tools\">"
print "<form method=\"post\" action=\""+self.url+"/telecaster/telecaster.py\">"
print "<div class=\"buttons\">"
+ print "<button type=\"submit\"><img src=\"img/arrow_refresh.png\" alt=\"\">Refresh</button>"
if writing:
print "<button type=\"submit\" class=\"positive\"><img src=\"img/drive_add.png\" alt=\"\">Recording...</button>"
else:
print "<button type=\"submit\" class=\"positive\"><img src=\"img/transmit_add.png\" alt=\"\">Diffusing...</button>"
else:
print "<button type=\"submit\" class=\"negative\"><img src=\"img/transmit_error.png\" alt=\"\">NOT Diffusing !</button>"
- print "<button type=\"submit\"><img src=\"img/arrow_refresh.png\" alt=\"\">Refresh</button>"
print "<a href=\""+self.url+":"+self.port+"/"+clean_string(self.title)+"_-_"+clean_string(department)+"_-_"+clean_string(conference)+"."+self.format+".m3u\"><img src=\"img/control_play_blue.png\" alt=\"\">Play</a>"
print "<button type=\"submit\" name=\"action\" value=\"stop\" class=\"negative\"><img src=\"img/cancel.png\" alt=\"\">Stop</button>"
print "</div>"