From 5bd6d6f272417d21c02b001a21990a3d9111c7d8 Mon Sep 17 00:00:00 2001 From: yomguy Date: Wed, 3 Feb 2010 10:30:37 +0000 Subject: [PATCH] fix jack implementation, fix url parsing, add first ajax bridge --- etc/pre-barreau_conferences.xml | 2 +- etc/telecaster_mp3.cfg | 4 +- etc/telecaster_mp3.xml | 17 +- js/rssajax.js | 275 ++++++++++++++++++++ station.py | 106 ++++++-- tools/PyRSS2Gen.py | 443 ++++++++++++++++++++++++++++++++ tools/__init__.py | 2 + webview.py | 47 +++- 8 files changed, 857 insertions(+), 39 deletions(-) create mode 100644 js/rssajax.js create mode 100644 tools/PyRSS2Gen.py diff --git a/etc/pre-barreau_conferences.xml b/etc/pre-barreau_conferences.xml index 82dd852..d995041 100644 --- a/etc/pre-barreau_conferences.xml +++ b/etc/pre-barreau_conferences.xml @@ -1,5 +1,5 @@ - http://localhost + augustins.pre-barreau.com Pre-Barreau 8000 mp3 diff --git a/etc/telecaster_mp3.cfg b/etc/telecaster_mp3.cfg index ad02c6a..eee50e2 100644 --- a/etc/telecaster_mp3.cfg +++ b/etc/telecaster_mp3.cfg @@ -7,7 +7,7 @@ AutomaticReconnectSecs=10 Encode=MP3 Lame BitrateNominal=96 OggQuality=3 -NumberChannels=1 +NumberChannels=2 Samplerate=44100 ServerType=Icecast2 ExternalFile=/tmp/test @@ -18,7 +18,7 @@ ServerDescription=Pre-Barreau_-_ICP_-_AE_-_Adm_Correction_-_2_-_GIUSTINIANI_G._- ServerGenre=Teaching #Advanced Settings LogLevel=1 -LogFile=oddcastv3.log +LogFile=/tmp/oddcastv3.log SaveDirectoryFlag=1 SaveDirectory=/home/prebarreau/backup SaveAsWAV=0 diff --git a/etc/telecaster_mp3.xml b/etc/telecaster_mp3.xml index 37e74d9..087bf0d 100644 --- a/etc/telecaster_mp3.xml +++ b/etc/telecaster_mp3.xml @@ -3,9 +3,8 @@ Pre-Barreau Pre-Barreau La preparation au Barreau de Paris - http://localhost + http://augustins.pre-barreau.com Other - 1 localhost @@ -16,6 +15,9 @@ etc/telecaster_mp3.cfg lock/telecaster.lock localhost/tmp/ + + /var/www/rss/ + true @@ -23,9 +25,16 @@ /home/prebarreau/backup mp3 96 + 2 3 44100 - 1 - 1 + + + jack_rack:out_1 + + + jack_rack:out_2 + + diff --git a/js/rssajax.js b/js/rssajax.js new file mode 100644 index 0000000..84b5cf4 --- /dev/null +++ b/js/rssajax.js @@ -0,0 +1,275 @@ + +//OBJECTS + +//objects inside the RSS2Item object +function RSS2Enclosure(encElement) +{ + if (encElement == null) + { + this.url = null; + this.length = null; + this.type = null; + } + else + { + this.url = encElement.getAttribute("url"); + this.length = encElement.getAttribute("length"); + this.type = encElement.getAttribute("type"); + } +} + +function RSS2Guid(guidElement) +{ + if (guidElement == null) + { + this.isPermaLink = null; + this.value = null; + } + else + { + this.isPermaLink = guidElement.getAttribute("isPermaLink"); + this.value = guidElement.childNodes[0].nodeValue; + } +} + +function RSS2Source(souElement) +{ + if (souElement == null) + { + this.url = null; + this.value = null; + } + else + { + this.url = souElement.getAttribute("url"); + this.value = souElement.childNodes[0].nodeValue; + } +} + +//object containing the RSS 2.0 item +function RSS2Item(itemxml) +{ + //required + this.title; + this.link; + this.description; + + //optional vars + this.author; + this.comments; + this.pubDate; + + //optional objects + this.category; + this.enclosure; + this.guid; + this.source; + + var properties = new Array("title", "link", "description", "author", "comments", "pubDate"); + var tmpElement = null; + for (var i=0; i"; + } + + //populate the items + document.getElementById("chan_items").innerHTML = ""; + for (var i=0; i" + RSS.items[i].link + "" + endTag; + item_html += endTag; + document.getElementById("chan_items").innerHTML += item_html; + } + + //we're done + //document.getElementById("chan").style.visibility = "visible"; + return true; +} + +var xhr; + + +function callExternalScript(url){ + var n = document.createElement("script"); + n.setAttribute("type", "text/javascript"); + n.setAttribute("src", url); + document.getElementsByTagName("head")[0].appendChild(n); +} diff --git a/station.py b/station.py index 63b4a7d..e77c359 100644 --- a/station.py +++ b/station.py @@ -1,4 +1,5 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- # *-* coding: utf-8 *-* """ telecaster @@ -27,6 +28,7 @@ import time import codecs import string import signal +import jack import unicodedata from tools import * from mutagen.oggvorbis import OggVorbis @@ -34,8 +36,9 @@ from mutagen.id3 import ID3, TIT2, TP1, TAL, TDA, TCO, COM 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'] @@ -50,25 +53,29 @@ class 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)) @@ -77,12 +84,12 @@ class Station(Conference): 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' @@ -95,6 +102,11 @@ class Station(Conference): 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') @@ -105,7 +117,7 @@ class Station(Conference): if 'ServerDescription' in line.split('='): newlines.append('ServerDescription=' + \ self.ServerDescription.replace(' ','_') + '\n') - + elif 'ServerName' in line.split('='): newlines.append('ServerName=' + self.ServerName + '\n') @@ -114,10 +126,15 @@ class Station(Conference): 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) @@ -128,8 +145,14 @@ class Station(Conference): 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) @@ -147,7 +170,7 @@ class Station(Conference): 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) @@ -155,22 +178,25 @@ class Station(Conference): 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): @@ -184,7 +210,7 @@ class Station(Conference): 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): @@ -210,6 +236,7 @@ class Station(Conference): self.set_oddcast_conf() self.start_oddcast() self.start_rip() + self.update_rss() def stop(self): self.stop_rip() @@ -222,7 +249,7 @@ class Station(Conference): #self.mp3_convert() #self.rsync_out() - def start_mp3cast(self): + def start_mp3cast(self): item_id = item_id source = source metadata = metadata @@ -234,9 +261,9 @@ class Station(Conference): 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: @@ -264,3 +291,38 @@ class Station(Conference): 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 = '' + media_description_item = '' + for key in self.dict.keys(): + if self.dict[key] != '': + media_description += media_description_item % (key.capitalize(), self.dict[key]) + media_description += '
%s: %s
' + + 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() diff --git a/tools/PyRSS2Gen.py b/tools/PyRSS2Gen.py new file mode 100644 index 0000000..fc1f1cf --- /dev/null +++ b/tools/PyRSS2Gen.py @@ -0,0 +1,443 @@ +"""PyRSS2Gen - A Python library for generating RSS 2.0 feeds.""" + +__name__ = "PyRSS2Gen" +__version__ = (1, 0, 0) +__author__ = "Andrew Dalke " + +_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 diff --git a/tools/__init__.py b/tools/__init__.py index 5ef8cec..9691d32 100644 --- a/tools/__init__.py +++ b/tools/__init__.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- from xmltodict import * from tools import * from acpi import * +from PyRSS2Gen import * diff --git a/webview.py b/webview.py index 393ea42..192263e 100644 --- a/webview.py +++ b/webview.py @@ -37,7 +37,7 @@ cgitb.enable() class WebView(FieldStorage): """Gives the web CGI frontend""" - + def __init__(self, school_file, url, version): FieldStorage.__init__(self) self.version = version @@ -53,7 +53,12 @@ class WebView(FieldStorage): 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'] @@ -79,7 +84,9 @@ class WebView(FieldStorage): print "" print "TeleCaster - "+self.title+"" print "" + print "" + print '' + + + print "" + print "" if self.refresh: print "" print "\n" - - print "" + #print "" + print "" print "
" print "
" print "

 TeleCaster - L'enregistrement et la diffusion audio en direct par internet

" @@ -119,7 +134,7 @@ class WebView(FieldStorage): print "
" print "TeleCaster "+self.version+" © "+date+" Parisson SARL. Tous droits réservés." print "
" - + def footer(self): print "
" print "" @@ -135,7 +150,7 @@ class WebView(FieldStorage): power_info = "secteur" else: power_info = "" - + #if self.power_state == 0: #batt_info = "en décharge" #elif self.power_state == 1: @@ -163,7 +178,7 @@ class WebView(FieldStorage): jackd_info = 'éteint' else: jackd_info = 'démarré' - + print "
" print "
Informations matérielles
" print "" @@ -189,7 +204,7 @@ class WebView(FieldStorage): print "" % jackd_info print "
%s
" print "
" - + def start_form(self, message=''): self.refresh = False @@ -236,8 +251,8 @@ class WebView(FieldStorage): print "
" print "
" #print "" - print "" print "" + print "" print "\"\"Archives" print "\"\"Trash" #print "" @@ -277,9 +292,22 @@ class WebView(FieldStorage): print "" #print "
Cliquez ici pour écouter cette formation en direct
" print "
" + + print """
+

+
+ +
+ +
+
+ +
""" + print "
" print "
" print "
" + print "" if writing: print "" else: @@ -288,7 +316,6 @@ class WebView(FieldStorage): print "" else: print "" - print "" print "\"\"Play" print "" print "
" -- 2.39.5