</department>
</teleoddcast>
-:q!
\ No newline at end of file
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Parisson SARL
+# Copyright (c) 2007 Olivier Guilyardi <olivier@samalyse.com>
+# Copyright (c) 2007 Guillaume Pellerin <pellerin@parisson.com>
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://svn.parisson.org/telemeta/TelemetaLicense.
+#
+# Author: Olivier Guilyardi <olivier@samalyse.com>
+# Guillaume Pellerin <pellerin@parisson.com>
+
+#from telemeta.core import Interface, TelemetaError
+
+
+class IExporter(Interface):
+ """Export driver interface"""
+
+ # Remark: the method prototypes do not include any self or cls argument
+ # because an interface is meant to show what methods a class must expose
+ # from the caller's point of view. However, when implementing the class
+ # you'll obviously want to include this extra argument.
+
+ def get_format():
+ """Return the export/encoding format as a short string
+ Example: "MP3", "OGG", "AVI", ...
+ """
+
+ def get_description():
+ """Return a string describing what this export format provides, is good
+ for, etc... The description is meant to help the end user decide what
+ format is good for him/her
+ """
+
+ def get_file_extension():
+ """Return the filename extension corresponding to this export format"""
+
+ def get_mime_type():
+ """Return the mime type corresponding to this export format"""
+
+ def set_cache_dir(path):
+ """Set the directory where cached files should be stored. Does nothing
+ if the exporter doesn't support caching.
+
+ The driver shouldn't assume that this method will always get called. A
+ temporary directory should be used if that's not the case.
+ """
+
+ def process(item_id, source, metadata, options=None):
+ """Perform the exporting process and return the absolute path
+ to the resulting file.
+
+ item_id is the media item id that uniquely identifies this audio/video
+ resource
+
+ source is the audio/video source file absolute path. For audio that
+ should be a WAV file
+
+ metadata is a dictionary
+
+ The returned file path is not meant to be permanent in any way, it
+ should be considered temporary/volatile by the caller.
+
+ It is highly recommended that export drivers implement some sort of
+ cache instead of re-encoding each time process() is called.
+
+ It should be possible to make subsequent calls to process() with
+ different items, using the same driver instance.
+ """
+
+class ExportProcessError:
+
+ def __init__(self, message, command, subprocess):
+ self.message = message
+ self.command = str(command)
+ self.subprocess = subprocess
+
+ def __str__(self):
+ if self.subprocess.stderr != None:
+ error = self.subprocess.stderr.read()
+ else:
+ error = ''
+ return "%s ; command: %s; error: %s" % (self.message,
+ self.command,
+ error)
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Parisson SARL
+# Copyright (c) 2006-2007 Guillaume Pellerin <pellerin@parisson.com>
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# yo"u should have received as part of this distribution. The terms
+# are also available at http://svn.parisson.org/telemeta/TelemetaLicense.
+#
+# Author: Guillaume Pellerin <pellerin@parisson.com>
+
+import os
+import re
+import string
+import subprocess
+import mutagen
+import export
+import xml.dom.minidom
+import xml.dom.ext
+
+#from telemeta.core import *
+from export import *
+from tools import *
+
+class ExporterCore:
+ """Defines the main parts of the exporting tools :
+ paths, formats, metadata..."""
+
+ def __init__(self):
+ self.source = ''
+ self.collection = ''
+ self.verbose = ''
+ self.dest = ''
+ self.metadata = []
+ self.cache_dir = 'cache'
+ self.buffer_size = 0xFFFF
+
+ def set_cache_dir(self,path):
+ self.cache_dir = path
+
+ def normalize(self):
+ """ Normalize the source and return its path """
+ args = ''
+ if self.verbose == '0':
+ args = '-q'
+ try:
+ os.system('normalize-audio '+args+' "'+self.source+'"')
+ return self.source
+ except IOError:
+ return 'Exporter error: Cannot normalize, path does not exist.'
+
+ def check_md5_key(self):
+ """ Check if the md5 key is OK and return a boolean """
+ try:
+ md5_log = os.popen4('md5sum -c "'+self.dest+ \
+ '" "'+self.dest+'.md5"')
+ return 'OK' in md5_log.split(':')
+ except IOError:
+ return 'Exporter error: Cannot check the md5 key...'
+
+ def get_file_info(self):
+ """ Return the list of informations of the dest """
+ return self.export.get_file_info()
+
+ def get_wav_length_sec(self) :
+ """ Return the length of the audio source file in seconds """
+ try:
+ file1, file2 = os.popen4('wavinfo "'+self.source+ \
+ '" | grep wavDataSize')
+ for line in file2.readlines():
+ line_split = line.split(':')
+ value = int(int(line_split[1])/(4*44100))
+ return value
+ except IOError:
+ return 'Exporter error: Cannot get the wav length...'
+
+ def compare_md5_key(self):
+ """ Compare 2 files wih md5 method """
+ in1, in2 = os.popen4('md5sum -b "'+self.source+'"')
+ out1, out2 = os.popen4('md5sum -b "'+self.dest+'"')
+ for line in in2.readlines():
+ line1 = line.split('*')[0]
+ for line in out2.readlines():
+ line2 = line.split('*')[0]
+ return line1 == line2
+
+ def write_metadata_xml(self,path):
+ doc = xml.dom.minidom.Document()
+ root = doc.createElement('telemeta')
+ doc.appendChild(root)
+ for tag in self.metadata.keys() :
+ value = self.metadata[tag]
+ node = doc.createElement(tag)
+ node.setAttribute('value', str(value))
+ #node.setAttribute('type', get_type(value))
+ root.appendChild(node)
+ xml_file = open(path, "w")
+ xml.dom.ext.PrettyPrint(doc, xml_file)
+ xml_file.close()
+
+ def pre_process(self, item_id, source, metadata, ext,
+ cache_dir, options=None):
+ """ Pre processing : prepare the export path and return it"""
+ self.item_id = str(item_id)
+ self.source = source
+ file_name = get_file_name(self.source)
+ file_name_wo_ext, file_ext = split_file_name(file_name)
+ self.cache_dir = cache_dir
+ self.metadata = metadata
+ #self.collection = self.metadata['Collection']
+ #self.artist = self.metadata['Artist']
+ #self.title = self.metadata['Title']
+
+ # Normalize if demanded
+ if not options is None:
+ self.options = options
+ if 'normalize' in self.options and \
+ self.options['normalize'] == True:
+ self.normalize()
+
+ # Define the export directory
+ self.ext = self.get_file_extension()
+ export_dir = os.path.join(self.cache_dir,self.ext)
+
+ if not os.path.exists(export_dir):
+ export_dir_split = export_dir.split(os.sep)
+ path = os.sep + export_dir_split[0]
+ for _dir in export_dir_split[1:]:
+ path = os.path.join(path,_dir)
+ if not os.path.exists(path):
+ os.mkdir(path)
+ else:
+ path = export_dir
+
+ # Set the target file
+ target_file = self.item_id+'.'+self.ext
+ dest = os.path.join(path,target_file)
+ return dest
+
+ def core_process(self, command, buffer_size, dest):
+ """Encode and stream audio data through a generator"""
+
+ __chunk = 0
+ file_out = open(dest,'w')
+
+ try:
+ proc = subprocess.Popen(command,
+ shell = True,
+ bufsize = buffer_size,
+ stdin = subprocess.PIPE,
+ stdout = subprocess.PIPE,
+ close_fds = True)
+ except:
+ raise ExportProcessError('Command failure:', command, proc)
+
+
+ # Core processing
+ while True:
+ __chunk = proc.stdout.read(buffer_size)
+ status = proc.poll()
+ if status != None and status != 0:
+ raise ExportProcessError('Command failure:', command, proc)
+ if len(__chunk) == 0:
+ break
+ yield __chunk
+ file_out.write(__chunk)
+
+ file_out.close()
+
+ def post_process(self, item_id, source, metadata, ext,
+ cache_dir, options=None):
+ """ Post processing : write tags, print infos, etc..."""
+ self.write_tags()
+ if not options is None:
+ if 'verbose' in self.options and self.options['verbose'] != '0':
+ print self.dest
+ print self.get_file_info()
+
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Parisson SARL
+# Copyright (c) 2006-2007 Guillaume Pellerin <pellerin@parisson.com>
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://svn.parisson.org/telemeta/TelemetaLicense.
+#
+# Author: Guillaume Pellerin <pellerin@parisson.com>
+
+import os
+import string
+import subprocess
+
+from telemeta.export.core import *
+from telemeta.export.api import IExporter
+#from mutagen.id3 import *
+
+
+class Mp3Exporter(ExporterCore):
+ """Defines methods to export to MP3"""
+
+ implements(IExporter)
+
+ def __init__(self):
+ self.item_id = ''
+ self.metadata = {}
+ self.description = ''
+ self.info = []
+ self.source = ''
+ self.dest = ''
+ self.options = {}
+ self.bitrate_default = '192'
+ self.buffer_size = 0xFFFF
+ self.dub2id3_dict = {'title': 'TIT2', #title2
+ 'creator': 'TCOM', #composer
+ 'creator': 'TPE1', #lead
+ 'identifier': 'UFID', #Unique ID...
+ 'identifier': 'TALB', #album
+ 'type': 'TCON', #genre
+ 'publisher': 'TPUB', #comment
+ #'date': 'TYER', #year
+ }
+ self.dub2args_dict = {'title': 'tt', #title2
+ 'creator': 'ta', #composer
+ 'relation': 'tl', #album
+ #'type': 'tg', #genre
+ 'publisher': 'tc', #comment
+ 'date': 'ty', #year
+ }
+ def get_format(self):
+ return 'MP3'
+
+ def get_file_extension(self):
+ return 'mp3'
+
+ def get_mime_type(self):
+ return 'audio/mpeg'
+
+ def get_description(self):
+ return "FIXME"
+
+ def set_cache_dir(self,path):
+ self.cache_dir = path
+
+ def get_file_info(self):
+ try:
+ file_out1, file_out2 = os.popen4('mp3info "'+self.dest+'"')
+ info = []
+ for line in file_out2.readlines():
+ info.append(clean_word(line[:-1]))
+ self.info = info
+ return self.info
+ except IOError:
+ return 'Exporter error [1]: file does not exist.'
+
+ def decode(self):
+ try:
+ os.system('sox "'+self.source+'" -w -r 44100 -t wav "' \
+ +self.cache_dir+os.sep+self.item_id+'"')
+ return self.cache_dir+os.sep+self.item_id+'.wav'
+ except IOError:
+ return 'ExporterError [2]: decoder not compatible.'
+
+ def write_tags(self):
+ """Write all ID3v2.4 tags by mapping dub2id3_dict dictionnary with the
+ respect of mutagen classes and methods"""
+ from mutagen import id3
+ id3 = id3.ID3(self.dest)
+ for tag in self.metadata.keys():
+ if tag in self.dub2id3_dict.keys():
+ frame_text = self.dub2id3_dict[tag]
+ value = self.metadata[tag]
+ frame = mutagen.id3.Frames[frame_text](3,value)
+ id3.add(frame)
+ id3.save()
+
+ def get_args(self, options=None):
+ """Get process options and return arguments for the encoder"""
+ args = []
+ if not options is None:
+ self.options = options
+ if not ( 'verbose' in self.options and self.options['verbose'] != '0' ):
+ args.append('-S')
+ if 'mp3_bitrate' in self.options:
+ args.append('-b ' + self.options['mp3_bitrate'])
+ else:
+ args.append('-b '+self.bitrate_default)
+ #Copyrights, etc..
+ args.append('-c -o')
+ else:
+ args.append('-S -c -o')
+
+ for tag in self.metadata.keys():
+ if tag in self.dub2args_dict.keys():
+ arg = self.dub2args_dict[tag]
+ value = clean_word(self.metadata[tag])
+ args.append('--' + arg)
+ args.append('"' + value + '"')
+
+ return args
+
+ def process(self, item_id, source, metadata, options=None):
+ self.item_id = item_id
+ self.source = source
+ self.metadata = metadata
+ self.args = self.get_args(options)
+ self.ext = self.get_file_extension()
+ self.args = ' '.join(self.args)
+ self.command = 'sox "%s" -q -w -r 44100 -t wav -c2 - | lame %s -' \
+ % (self.source,self.args)
+
+ # Pre-proccessing
+ self.dest = self.pre_process(self.item_id,
+ self.source,
+ self.metadata,
+ self.ext,
+ self.cache_dir,
+ self.options)
+
+ # Processing (streaming + cache writing)
+ stream = self.core_process(self.command,self.buffer_size,self.dest)
+ for chunk in stream:
+ yield chunk
+
+ # Post-proccessing
+ self.post_process(self.item_id,
+ self.source,
+ self.metadata,
+ self.ext,
+ self.cache_dir,
+ self.options)
+
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Parisson SARL
+# Copyright (c) 2006-2007 Guillaume Pellerin <pellerin@parisson.com>
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://svn.parisson.org/telemeta/TelemetaLicense.
+#
+# Author: Guillaume Pellerin <pellerin@parisson.com>
+
+import os
+import string
+import subprocess
+
+from telemeta.export.core import *
+from telemeta.export.api import IExporter
+from mutagen.oggvorbis import OggVorbis
+
+class OggExporter(ExporterCore):
+ """Defines methods to export to OGG Vorbis"""
+
+ implements(IExporter)
+
+ def __init__(self):
+ self.item_id = ''
+ self.metadata = {}
+ self.description = ''
+ self.info = []
+ self.source = ''
+ self.dest = ''
+ self.options = {}
+ self.bitrate_default = '192'
+ self.buffer_size = 0xFFFF
+ self.dub2args_dict = {'creator': 'artist',
+ 'relation': 'album'
+ }
+
+ def get_format(self):
+ return 'OGG'
+
+ def get_file_extension(self):
+ return 'ogg'
+
+ def get_mime_type(self):
+ return 'application/ogg'
+
+ def get_description(self):
+ return 'FIXME'
+
+ def get_file_info(self):
+ try:
+ file_out1, file_out2 = os.popen4('ogginfo "'+self.dest+'"')
+ info = []
+ for line in file_out2.readlines():
+ info.append(clean_word(line[:-1]))
+ self.info = info
+ return self.info
+ except IOError:
+ return 'Exporter error [1]: file does not exist.'
+
+ def set_cache_dir(self,path):
+ self.cache_dir = path
+
+ def decode(self):
+ try:
+ os.system('oggdec -o "'+self.cache_dir+os.sep+self.item_id+
+ '.wav" "'+self.source+'"')
+ return self.cache_dir+os.sep+self.item_id+'.wav'
+ except IOError:
+ return 'ExporterError [2]: decoder not compatible.'
+
+ def write_tags(self):
+ media = OggVorbis(self.dest)
+ for tag in self.metadata.keys():
+ media[tag] = str(self.metadata[tag])
+ media.save()
+
+ def get_args(self,options=None):
+ """Get process options and return arguments for the encoder"""
+ args = []
+ if not options is None:
+ self.options = options
+ if not ('verbose' in self.options and self.options['verbose'] != '0'):
+ args.append('-Q ')
+ if 'ogg_bitrate' in self.options:
+ args.append('-b '+self.options['ogg_bitrate'])
+ elif 'ogg_quality' in self.options:
+ args.append('-q '+self.options['ogg_quality'])
+ else:
+ args.append('-b '+self.bitrate_default)
+ else:
+ args.append('-Q -b '+self.bitrate_default)
+
+ for tag in self.metadata.keys():
+ value = clean_word(self.metadata[tag])
+ args.append('-c %s="%s"' % (tag, value))
+ if tag in self.dub2args_dict.keys():
+ arg = self.dub2args_dict[tag]
+ args.append('-c %s="%s"' % (arg, value))
+
+ return args
+
+ def process(self, item_id, source, metadata, options=None):
+ self.item_id = item_id
+ self.source = source
+ self.metadata = metadata
+ self.args = self.get_args(options)
+ self.ext = self.get_file_extension()
+ self.args = ' '.join(self.args)
+ self.command = 'sox "%s" -q -w -r 44100 -t wav -c2 - | oggenc %s -' \
+ % (self.source,self.args)
+
+ # Pre-proccessing
+ self.dest = self.pre_process(self.item_id,
+ self.source,
+ self.metadata,
+ self.ext,
+ self.cache_dir,
+ self.options)
+
+ # Processing (streaming + cache writing)
+ stream = self.core_process(self.command,self.buffer_size,self.dest)
+ for chunk in stream:
+ yield chunk
+
+ # Post-proccessing
+ self.post_process(self.item_id,
+ self.source,
+ self.metadata,
+ self.ext,
+ self.cache_dir,
+ self.options)
+
--- /dev/null
+
+# External functions
+
+def get_type(value):
+ """ Return a String with the type of value """
+ types = {bool : 'bool', int : 'int', str : 'str'}
+ # 'bool' type must be placed *before* 'int' type, otherwise booleans are
+ # detected as integers
+ for type in types.keys():
+ if isinstance(value, type) :
+ return types[type]
+ raise TypeError, str(value) + ' has an unsupported type'
+
+def get_cast(value, type) :
+ """ Return value, casted into type """
+ if type == 'bool' :
+ if value == 'True' :
+ return True
+ return False
+ elif type == 'int' :
+ return int(value)
+ elif type == 'str' :
+ return str(value)
+ raise TypeError, type + ' is an unsupported type'
+
+def get_file_mime_type(path):
+ """ Return the mime type of a file """
+ try:
+ file_out1, file_out2 = os.popen4('file -i "'+path+'"')
+ for line in file_out2.readlines():
+ line_split = line.split(': ')
+ mime = line_split[len(line_split)-1]
+ return mime[:len(mime)-1]
+ except IOError:
+ return 'Exporter error [1]: path does not exist.'
+
+def get_file_type_desc(path):
+ """ Return the type of a file given by the 'file' command """
+ try:
+ file_out1, file_out2 = os.popen4('file "'+path+'"')
+ for line in file_out2.readlines():
+ description = line.split(': ')
+ description = description[1].split(', ')
+ return description
+ except IOError:
+ return 'Exporter error [1]: path does not exist.'
+
+def iswav(path):
+ """ Tell if path is a WAV """
+ try:
+ mime = get_file_mime_type(path)
+ return mime == 'audio/x-wav'
+ except IOError:
+ return 'Exporter error [1]: path does not exist.'
+
+def iswav16(path):
+ """ Tell if path is a 16 bit WAV """
+ try:
+ file_type_desc = get_file_type_desc(path)
+ return iswav(path) and '16 bit' in file_type_desc
+ except IOError:
+ return 'Exporter error [1]: path does not exist.'
+
+def get_file_name(path):
+ """ Return the file name targeted in the path """
+ return os.path.split(path)[1]
+
+def split_file_name(file):
+ """ Return main file name and its extension """
+ try:
+ return os.path.splitext(file)
+ except IOError:
+ return 'Exporter error [1]: path does not exist.'
+
+def clean_word(word) :
+ """ Return the word without excessive blank spaces, underscores and
+ characters causing problem to exporters"""
+ word = re.sub("^[^\w]+","",word) #trim the beginning
+ word = re.sub("[^\w]+$","",word) #trim the end
+ word = re.sub("_+","_",word) #squeeze continuous _ to one _
+ word = re.sub("^[^\w]+","",word) #trim the beginning _
+ #word = string.replace(word,' ','_')
+ #word = string.capitalize(word)
+ dict = '&[];"*:,'
+ for letter in dict:
+ word = string.replace(word,letter,'_')
+ return word
+
+def recover_par_key(path):
+ """ Recover a file with par2 key """
+ os.system('par2 r "'+path+'"')
+
+def verify_par_key(path):
+ """ Verify a par2 key """
+ os.system('par2 v "'+path+'.par2"')
+
--- /dev/null
+#!/usr/bin/python
+# Capture 3 seconds of stereo audio from alsa_pcm:capture_1/2; then play it back.
+#
+# Copyright 2003, Andrew W. Schmeder
+# This source code is released under the terms of the GNU Public License.
+# See LICENSE for the full text of these terms.
+
+import Numeric
+import jack
+import time
+
+class JackInput:
+ "A JACK connexion input in TeleOddCast"
+
+ def __init__(self, dict):
+ self.host = dict['host']
+ self.name = dict['name']
+ self.jack = jack()
+ self.buffer_size = self.jack.get_buffer_size()
+ self.sample_rate = float(self.jack.get_sample_rate())
+ print "Buffer Size:", N, "Sample Rate:", Sr
+ self.power = True
+ self.capture = Numeric.zeros((2, self.buffer_size), 'f')
+
+
+ def attach(self):
+ jack.attach(self.name)
+
+ def get_ports(self):
+ return self.jack.get_ports()
+
+ def register_ports(self):
+ self.jack.register_port("in_1", self.jack.IsInput)
+ self.jack.register_port("in_2", self.jack.IsInput)
+
+ def activate(self):
+ self.jack.activate()
+
+ def stop(self):
+ self.power = False
+
+ def connect(self):
+ self.jack.connect("alsa_pcm:capture_1", self.name+":in_1")
+ self.jack.connect("alsa_pcm:capture_2", self.name+":in_2")
+ #jack.connect(self.name+":out_1", "alsa_pcm:playback_1")
+ #jack.connect(self.name+":out_2", "alsa_pcm:playback_2")
+
+ def process(self):
+ while True:
+ try:
+ if not self.power:
+ break
+ self.jack.process(__chunk, capture[:,self.buffer_size])
+ yield __chunk
+ except self.jack.InputSyncError:
+ print "Input Sync"
+ pass
+ except self.jack.OutputSyncError:
+ print "Output Sync"
+ pass
+
+ def desactivate(self):
+ self.jack.deactivate()
+
+ def detach(self):
+ self.jack.detach()
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""
-version = '0.3'
+version = '0.3.1'
import os
import time
import codecs
import string
+import jack
from tools import *
from mutagen.oggvorbis import OggVorbis
self.set_lock()
time.sleep(1)
+ def star_mp3cast(self):
+
+ item_id = item_id
+ source = source
+ metadata = metadata
+ args = get_args(options)
+ ext = get_file_extension()
+ args = ' '.join(args)
+ command = 'sox "%s" -q -w -r 44100 -t wav -c2 - | lame %s -' \
+ % (source, args)
+
+ # Processing (streaming + cache writing)
+ e = ExporterCore()
+ stream = e.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"""
+
+ __chunk = 0
+ file_out = open(dest,'w')
+
+ try:
+ proc = subprocess.Popen(command,
+ shell = True,
+ bufsize = buffer_size,
+ stdin = subprocess.PIPE,
+ stdout = subprocess.PIPE,
+ close_fds = True)
+ except:
+ raise ExportProcessError('Command failure:', command, proc)
+
+
+ # Core processing
+ while True:
+ __chunk = proc.stdout.read(buffer_size)
+ status = proc.poll()
+ if status != None and status != 0:
+ raise ExportProcessError('Command failure:', command, proc)
+ if len(__chunk) == 0:
+ break
+ yield __chunk
+ file_out.write(__chunk)
+
+ file_out.close()
+
def set_lock(self):
lock = open(self.lock_file,'w')
lock_text = clean_string('_*_'.join(self.description))
os.system(command)
def stop_oddcast(self):
- if self.odd_pid[0]:
+ if len(self.odd_pid) != 0:
os.system('kill -9 ' + self.odd_pid[0])
def stop_rip(self):
- if self.rip_pid[0]:
+ if len(self.rip_pid) != 0:
os.system('kill -9 ' + self.rip_pid[0])
time.sleep(1)
date = datetime.datetime.now().strftime("%Y")
def start_form(self):
self.header()
print "<div id=\"main\">"
- print "<h5><a href=\""+self.url+":"+self.port+"/crfpa.pre-barreau.com_live.ogg.m3u\">Cliquez ici pour écouter le flux continu 24/24 en direct</a></h5>"
+ print "<h5><a href=\""+self.url+":"+self.port+"/augustins.pre-barreau.com_live.ogg.m3u\">Cliquez ici pour écouter le flux continu 24/24 en direct</a></h5>"
print "\t<TABLE BORDER = 0>"
print "\t\t<form method=post action=\"teleoddcast.py\" name=\"formulaire\">"
print "\t\t<TR><TH align=\"left\">Titre :</TH><TD>"+self.title+"</TD></TR>"