]> git.parisson.com Git - telecaster-cgi.git/commitdiff
fix jack implementation, fix url parsing, add first ajax bridge
authoryomguy <yomguy@parisson.com>
Wed, 3 Feb 2010 10:30:37 +0000 (10:30 +0000)
committeryomguy <yomguy@parisson.com>
Wed, 3 Feb 2010 10:30:37 +0000 (10:30 +0000)
etc/pre-barreau_conferences.xml
etc/telecaster_mp3.cfg
etc/telecaster_mp3.xml
js/rssajax.js [new file with mode: 0644]
station.py
tools/PyRSS2Gen.py [new file with mode: 0644]
tools/__init__.py
webview.py

index 82dd852fb4660ed13ec644b23cd17f61b5da0bdf..d9950416cbdbf9cddbf237c0cb81fb4f3350306b 100644 (file)
@@ -1,5 +1,5 @@
 <telecaster>
-    <url>http://localhost</url>
+    <host>augustins.pre-barreau.com</host>
     <title>Pre-Barreau</title>
     <port>8000</port>
     <format>mp3</format>
index ad02c6ab9846e90a79035236a96e81845c15f004..eee50e282009e64c3bf5fe76c44316d9f23808b4 100644 (file)
@@ -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
index 37e74d9fb0d06e097fd369721c3712a696c830a3..087bf0d2e2abd9cf35d630ca482036aefd7aa66b 100644 (file)
@@ -3,9 +3,8 @@
         <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>
@@ -16,6 +15,9 @@
         <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>
diff --git a/js/rssajax.js b/js/rssajax.js
new file mode 100644 (file)
index 0000000..84b5cf4
--- /dev/null
@@ -0,0 +1,275 @@
+\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
index 63b4a7df606c85ab698ea3cab8125b40c53dfd95..e77c3598cbaf768e9c5eb26c0450aca22a82a79a 100644 (file)
@@ -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 = '<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()
diff --git a/tools/PyRSS2Gen.py b/tools/PyRSS2Gen.py
new file mode 100644 (file)
index 0000000..fc1f1cf
--- /dev/null
@@ -0,0 +1,443 @@
+"""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
index 5ef8cecb171b0db6c742be3560d48cedc2566a95..9691d325a871ce2e9ae314c393aeb15ff99b1ba5 100644 (file)
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
 from xmltodict import *
 from tools import *
 from acpi import *
+from PyRSS2Gen import *
index 393ea42077d13bd4102db0449b88afce849fb339..192263e10846dd1081cf2fd4393db35010246e93 100644 (file)
@@ -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 "<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;'
@@ -104,11 +111,19 @@ class WebView(FieldStorage):
         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>&nbsp;TeleCaster - L'enregistrement et la diffusion audio en direct par internet</H3>"
@@ -119,7 +134,7 @@ class WebView(FieldStorage):
         print "<div class=\"colophon\">"
         print "TeleCaster "+self.version+" &copy; <span>"+date+"</span>&nbsp;<a href=\"http://parisson.com\">Parisson SARL</a>. Tous droits r&eacute;serv&eacute;s."
         print "</div>"
-            
+
     def footer(self):
         print "</div>"
         print "</BODY>"
@@ -135,7 +150,7 @@ class WebView(FieldStorage):
             power_info = "<span style=\"color: green\">secteur</span>"
         else:
             power_info = ""
-            
+
         #if self.power_state == 0:
             #batt_info = "en d&eacute;charge"
         #elif self.power_state == 1:
@@ -163,7 +178,7 @@ class WebView(FieldStorage):
             jackd_info = '<span style=\"color: red\">&eacute;teint</span>'
         else:
             jackd_info = '<span style=\"color: green\">d&eacute;marr&eacute;</span>'
-        
+
         print "<div class=\"hardware\">"
         print "<div class=\"title\">Informations mat&eacute;rielles</div>"
         print "<table class=\"hardware\">"
@@ -189,7 +204,7 @@ class WebView(FieldStorage):
         print "<td>%s</td></tr>" % jackd_info
         print "</table>"
         print "</div>"
-        
+
 
     def start_form(self, message=''):
         self.refresh = False
@@ -236,8 +251,8 @@ class WebView(FieldStorage):
         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\">"
@@ -277,9 +292,22 @@ class WebView(FieldStorage):
         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 &eacute;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:
@@ -288,7 +316,6 @@ class WebView(FieldStorage):
             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>"