From: Guillaume Pellerin Date: Fri, 20 Mar 2009 10:44:06 +0000 (+0000) Subject: renamed (again and last) to deefuzz X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=f231537b37eda27fe044d65640a3d2356353f613;p=deefuzzer.git renamed (again and last) to deefuzz --- diff --git a/INSTALL b/INSTALL index e62f82b..929c18c 100644 --- a/INSTALL +++ b/INSTALL @@ -5,17 +5,22 @@ # # 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/d-fuzz/DFuzzLicense. +# are also available at http://svn.parisson.org/deefuzz/DeeFuzzLicense. # # Author: Guillaume Pellerin - depends : python, python-xml, python-shout | shout-python, libshout3, icecast2 + depends : python, python-dev, python-xml, python-shout | shout-python, libshout3, libshout3-dev, icecast2 recommends : python-mutagen provides : shout-python -python-shout is included in DeFuzz but needs to be compiled. +python-shout is included in DeeFuzz but needs to be compiled and installed. As explained in shout-python/INSTALL, you just have to run this command : $ cd shout-python $ sudo python setup.py install - \ No newline at end of file + +To install deefuzz (from the main deefuzz directory) : + + $ sudo python install.py + +For more informations, see http://svn.parisson.org/deefuzz/ \ No newline at end of file diff --git a/README b/README index 22ba2a3..c9bacb7 100644 --- a/README +++ b/README @@ -1,13 +1,13 @@ # README # ====== -defuzz : a lightweight icecast streaming client written in python +deefuzz : a lightweight icecast streaming client written in python # 1. Introduction # =============== -DeFuzz is a light python program that streams media data from disks to icecast2 +DeeFuzz is a light python program that streams media data from disks to icecast2 with some metadata. It supports MP3, OGG, SPEEX and THEORA media. It is neccessary to provide a config file which sets all needed parameters @@ -25,24 +25,24 @@ see INSTALL 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/defuzz/DeFuzzLicense + are also available at http://svn.parisson.org/deefuzz/DeeFuzzLicense # 4. Usage # ========= -Usage : defuzz CONFIGFILE +Usage : deefuzz CONFIGFILE where CONFIGFILE is the path for a XML config file - ex: defuzz example/myfuzz.xml + ex: deefuzz example/myfuzz.xml Note that you must edit the config file with right parameters before executing... You can find an example for the XML file in the directory "example/" of this -application (maybe in /usr/share/defuzz if installed with the help of install.py +application (maybe in /usr/share/deefuzz if installed with the help of install.py Be carefull : at the moment the Thread implementation raises exceptions when shutting down with CTRL + C... -You have then to kill each stream manually or make a full `pkill defuzz` . +You have then to kill each stream manually or make a full `pkill deefuzz` . # 5. Author # ========= @@ -64,5 +64,5 @@ Some parts of this work are also taken from another Parisson's project : Telemet # 7. Contact / Infos # ================== -see http://svn.parisson.org/defuzz/ or http://parisson.com for more details... +see http://svn.parisson.org/deefuzz/ or http://parisson.com for more details... diff --git a/deefuzz.py b/deefuzz.py new file mode 100755 index 0000000..532ec79 --- /dev/null +++ b/deefuzz.py @@ -0,0 +1,305 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2007-2009 Guillaume Pellerin +# 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/deefuzz/wiki/DefuzzLicense. +# +# Author: Guillaume Pellerin + +import os +import sys +import time +import datetime +import string +import random +import Queue +import shout +import subprocess +from tools import * +from threading import Thread +from mutagen.oggvorbis import OggVorbis + +version = '0.2.2' +year = datetime.datetime.now().strftime("%Y") + + +def prog_info(): + desc = '\n deefuzz : easy and light streaming tool\n' + ver = ' version : %s \n\n' % (version) + info = """ Copyright (c) 2007-%s Guillaume Pellerin + 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/d-fuzz/DeeFuzzLicense + + depends : python, python-xml, python-shout, libshout3, icecast2 + recommends : python-mutagen + provides : python-shout + + Usage : deefuzz $1 + where $1 is the path for a XML config file + ex: deefuzz example/myfuzz.xml + + see http://svn.parisson.org/deefuzz/ for more details + """ % (year) + text = desc + ver + info + return text + + +class DeeFuzzError: + """The DeeFuzz main error class""" + 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) + +class DeeFuzz: + """A DeeFuzz diffuser""" + + def __init__(self, conf_file): + self.conf_file = conf_file + self.conf = self.get_conf_dict() + #print self.conf + + def get_conf_dict(self): + confile = open(self.conf_file,'r') + conf_xml = confile.read() + confile.close() + dict = xmltodict(conf_xml,'utf-8') + return dict + + def get_station_names(self): + return self.conf['station']['name'] + + def start(self): + # Fix wrong type data from xmltodict when one station (*) + if isinstance(self.conf['deefuzz']['station'], dict): + nb_stations = 1 + else: + nb_stations = len(self.conf['deefuzz']['station']) + print 'Number of stations : ' + str(nb_stations) + + # Create a Queue + q = Queue.Queue(nb_stations) + + # Create a Producer + p = Producer(q) + p.start() + + s = [] + for i in range(0,nb_stations): + if isinstance(self.conf['deefuzz']['station'], dict): + station = self.conf['deefuzz']['station'] + else: + station = self.conf['deefuzz']['station'][i] + name = station['infos']['name'] + # Create a Station + s.append(Station(station, q)) + + for i in range(0,nb_stations): + # Start the Stations + s[i].start() + time.sleep(0.1) + pass + + +class Producer(Thread): + """a DeeFuzz Producer master thread""" + + def __init__(self, q): + Thread.__init__(self) + self.q = q + + def run(self): + q = self.q + i=0 + while 1 : + #print "Producer produced one queue step: "+str(i) + self.q.put(i,1) + i+=1 + + +class Station(Thread): + """a DeeFuzz Station shouting slave thread""" + + def __init__(self, station, q): + Thread.__init__(self) + self.q = q + self.station = station + self.buffer_size = 16384 + self.channel = shout.Shout() + self.id = 999999 + self.counter = 0 + self.rand_list = [] + self.command = "cat " + # Media + self.media_dir = self.station['media']['dir'] + self.channel.format = self.station['media']['format'] + self.mode_shuffle = int(self.station['media']['shuffle']) + self.bitrate = self.station['media']['bitrate'] + self.ogg_quality = self.station['media']['ogg_quality'] + self.samplerate = self.station['media']['samplerate'] + self.voices = self.station['media']['voices'] + # Infos + self.short_name = self.station['infos']['short_name'] + self.channel.name = self.station['infos']['name'] + self.channel.genre = self.station['infos']['genre'] + self.channel.description = self.station['infos']['description'] + self.channel.url = self.station['infos']['url'] + self.rss_file = '/tmp/' + self.short_name + '.xml' + # Server + self.channel.protocol = 'http' # | 'xaudiocast' | 'icy' + self.channel.host = self.station['server']['host'] + self.channel.port = int(self.station['server']['port']) + self.channel.user = 'source' + self.channel.password = self.station['server']['sourcepassword'] + self.channel.mount = '/' + self.short_name + '.' + self.channel.format + self.channel.public = int(self.station['server']['public']) + self.channel.audio_info = { 'SHOUT_AI_BITRATE': self.bitrate, + 'SHOUT_AI_SAMPLERATE': self.samplerate, + 'SHOUT_AI_QUALITY': self.ogg_quality, + 'SHOUT_AI_CHANNELS': self.voices, + } + self.channel.open() + self.playlist = self.get_playlist() + self.lp = len(self.playlist) + self.rand_list = range(0,self.lp-1) + print 'Opening ' + self.short_name + ' - ' + self.channel.name + \ + ' (' + str(self.lp) + ' tracks)...' + #print "Using libshout version %s" % shout.version() + time.sleep(0.1) + + def update_rss(self, file_name): + self.media_url_dir = '/media/' + rss = PyRSS2Gen.RSS2( + title = self.channel.name, + link = self.channel.url, + description = self.channel.description, + lastBuildDate = datetime.datetime.now(), + + items = [ + PyRSS2Gen.RSSItem( + title = file_name, + link = self.channel.url + self.media_url_dir + file_name, + description = file_name, + guid = PyRSS2Gen.Guid(self.channel.url + self.media_url_dir + file_name), + pubDate = datetime.datetime(2003, 9, 6, 21, 31)), + ]) + + rss.write_xml(open(self.rss_file, "w")) + + def get_playlist(self): + file_list = [] + for root, dirs, files in os.walk(self.media_dir): + for file in files: + if not '/.' in file: + file_list.append(root + os.sep + file) + return file_list + + def get_next_media_lin(self, playlist): + lp = len(playlist) + if self.id >= (lp - 1): + playlist = self.get_playlist() + self.id = 0 + else: + self.id = self.id + 1 + return playlist, playlist[self.id] + + def get_next_media_rand(self, playlist): + lp = len(playlist) + if self.id >= (lp - 1): + playlist = self.get_playlist() + lp_new = len(playlist) + if lp_new != lp or self.counter == 0: + self.rand_list = range(0,lp_new) + random.shuffle(self.rand_list) + self.id = 0 + else: + self.id = self.id + 1 + index = self.rand_list[self.id] + return playlist, playlist[index] + + + def core_process(self, media, buffer_size): + """Read media and stream data through a generator. + Taken from Telemeta (see http://telemeta.org)""" + + command = self.command + '"' + media + '"' + __chunk = 0 + + try: + proc = subprocess.Popen(command, + shell = True, + bufsize = buffer_size, + stdin = subprocess.PIPE, + stdout = subprocess.PIPE, + close_fds = True) + except: + raise DeeFuzzError('Command failure:', command, proc) + + # Core processing + while True: + __chunk = proc.stdout.read(buffer_size) + status = proc.poll() + if status != None and status != 0: + raise DeeFuzzError('Command failure:', command, proc) + if len(__chunk) == 0: + break + yield __chunk + + def run(self): + q = self.q + __chunk = 0 + + while True: + if self.lp == 0: + break + if self.mode_shuffle == 1: + self.playlist, media = self.get_next_media_rand(self.playlist) + else: + self.playlist, media = self.get_next_media_lin(self.playlist) + + self.counter += 1 + if os.path.exists(media) and not '/.' in media: + file_name = string.replace(media, self.media_dir + os.sep, '') + self.channel.set_metadata({'song': file_name}) + stream = self.core_process(media, self.buffer_size) + print 'Defuzzing this file on %s : id = %s, name = %s' % (self.short_name, self.id, file_name) + self.update_rss(file_name) + + for __chunk in stream: + self.channel.send(__chunk) + self.channel.sync() + # Get the queue + it = q.get(1) + #print "Station eated one queue step: "+str(it) + + #self.channel.close() + + +def main(): + if len(sys.argv) == 2: + print "Defuzz v"+version + deefuzz_main = DeeFuzz(sys.argv[1]) + deefuzz_main.start() + else: + text = prog_info() + sys.exit(text) + +if __name__ == '__main__': + main() + diff --git a/defuzz.py b/defuzz.py deleted file mode 100755 index 0ffbd6b..0000000 --- a/defuzz.py +++ /dev/null @@ -1,307 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Copyright (c) 2007-2009 Guillaume Pellerin -# 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/defuzz/wiki/DefuzzLicense. -# -# Author: Guillaume Pellerin - -import os -import sys -import time -import datetime -import string -import random -import Queue -import shout -import subprocess -from tools import * -from threading import Thread -from mutagen.oggvorbis import OggVorbis - -version = '0.2.2' -year = datetime.datetime.now().strftime("%Y") - - -def prog_info(): - desc = '\n defuzz : easy and light streaming tool\n' - ver = ' version : %s \n\n' % (version) - info = """ Copyright (c) 2007-%s Guillaume Pellerin - 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/d-fuzz/DeFuzzLicense - - depends : python, python-xml, python-shout, libshout3, icecast2 - recommends : python-mutagen - provides : python-shout - - Usage : defuzz $1 - where $1 is the path for a XML config file - ex: defuzz example/myfuzz.xml - - see http://svn.parisson.org/defuzz/ for more details - """ % (year) - text = desc + ver + info - return text - - -class DeFuzzError: - """The DeFuzz main error class""" - 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) - -class DeFuzz: - """A DeFuzz diffuser""" - - def __init__(self, conf_file): - self.conf_file = conf_file - self.conf = self.get_conf_dict() - #print self.conf - - def get_conf_dict(self): - confile = open(self.conf_file,'r') - conf_xml = confile.read() - confile.close() - dict = xmltodict(conf_xml,'utf-8') - return dict - - def get_station_names(self): - return self.conf['station']['name'] - - def start(self): - # Fix wrong type data from xmltodict when one station (*) - if isinstance(self.conf['defuzz']['station'], dict): - nb_stations = 1 - else: - nb_stations = len(self.conf['defuzz']['station']) - print 'Number of stations : ' + str(nb_stations) - - # Create a Queue - q = Queue.Queue(1) - - # Create a Producer - p = Producer(q) - p.start() - - s = [] - for i in range(0,nb_stations): - if isinstance(self.conf['defuzz']['station'], dict): - station = self.conf['defuzz']['station'] - else: - station = self.conf['defuzz']['station'][i] - name = station['infos']['name'] - # Create a Station - s.append(Station(station, q)) - - for i in range(0,nb_stations): - # Start the Stations - s[i].start() - time.sleep(0.1) - pass - - -class Producer(Thread): - """a DeFuzz Producer master thread""" - - def __init__(self, q): - Thread.__init__(self) - self.q = q - - def run(self): - q = self.q - i=0 - while 1 : - #print "Producer produced one queue step: "+str(i) - self.q.put(i,1) - i+=1 - - -class Station(Thread): - """a DeFuzz Station shouting slave thread""" - - def __init__(self, station, q): - Thread.__init__(self) - self.q = q - self.station = station - self.buffer_size = 1024 - self.channel = shout.Shout() - self.id = 999999 - self.counter = 0 - self.rand_list = [] - self.command = 'cat ' - # Media - self.media_dir = self.station['media']['dir'] - self.channel.format = self.station['media']['format'] - self.mode_shuffle = int(self.station['media']['shuffle']) - self.bitrate = self.station['media']['bitrate'] - self.ogg_quality = self.station['media']['ogg_quality'] - self.samplerate = self.station['media']['samplerate'] - self.voices = self.station['media']['voices'] - # Infos - self.short_name = self.station['infos']['short_name'] - self.channel.name = self.station['infos']['name'] - self.channel.genre = self.station['infos']['genre'] - self.channel.description = self.station['infos']['description'] - self.channel.url = self.station['infos']['url'] - self.rss_file = '/var/www/files/rss/' + self.short_name + '.xml' - # Server - self.channel.protocol = 'http' # | 'xaudiocast' | 'icy' - self.channel.host = self.station['server']['host'] - self.channel.port = int(self.station['server']['port']) - self.channel.user = 'source' - self.channel.password = self.station['server']['sourcepassword'] - self.channel.mount = '/' + self.short_name + '.' + self.channel.format - self.channel.public = int(self.station['server']['public']) - self.channel.audio_info = { 'SHOUT_AI_BITRATE': self.bitrate, - 'SHOUT_AI_SAMPLERATE': self.samplerate, - 'SHOUT_AI_QUALITY': self.ogg_quality, - 'SHOUT_AI_CHANNELS': self.voices, - } - - #time.sleep(0.1) - - def update_rss(self, file_name): - self.media_url_dir = '/media/' - rss = PyRSS2Gen.RSS2( - title = self.channel.name, - link = self.channel.url, - description = self.channel.description, - lastBuildDate = datetime.datetime.now(), - - items = [ - PyRSS2Gen.RSSItem( - title = file_name, - link = self.channel.url + self.media_url_dir + file_name, - description = file_name, - guid = PyRSS2Gen.Guid(self.channel.url + self.media_url_dir + file_name), - pubDate = datetime.datetime(2003, 9, 6, 21, 31)), - ]) - - rss.write_xml(open(self.rss_file, "w")) - - def get_playlist(self): - file_list = [] - for root, dirs, files in os.walk(self.media_dir): - for file in files: - if not '/.' in file: - file_list.append(root + os.sep + file) - return file_list - - def get_next_media_lin(self, playlist): - lp = len(playlist) - if self.id >= (lp - 1): - playlist = self.get_playlist() - self.id = 0 - else: - self.id = self.id + 1 - return playlist, playlist[self.id] - - def get_next_media_rand(self, playlist): - lp = len(playlist) - if self.id >= (lp - 1): - playlist = self.get_playlist() - lp_new = len(playlist) - if lp_new != lp or self.counter == 0: - self.rand_list = range(0,lp_new) - random.shuffle(self.rand_list) - self.id = 0 - else: - self.id = self.id + 1 - index = self.rand_list[self.id] - return playlist, playlist[index] - - - def core_process(self, media, buffer_size): - """Read media and stream data through a generator. - Taken from Telemeta (see http://telemeta.org)""" - - command = self.command + '"' + media + '"' - __chunk = 0 - try: - proc = subprocess.Popen(command, - shell = True, - bufsize = buffer_size, - stdin = subprocess.PIPE, - stdout = subprocess.PIPE, - close_fds = True) - except: - raise DeFuzzError('Command failure:', command, proc) - - # Core processing - while True: - __chunk = proc.stdout.read(buffer_size) - status = proc.poll() - if status != None and status != 0: - raise DeFuzzError('Command failure:', command, proc) - if len(__chunk) == 0: - break - yield __chunk - - def run(self): - #print "Using libshout version %s" % shout.version() - q = self.q - __chunk = 0 - self.channel.open() - self.playlist = self.get_playlist() - self.lp = len(self.playlist) - self.rand_list = range(0,self.lp-1) - print 'Opening ' + self.short_name + ' - ' + self.channel.name + \ - ' (' + str(self.lp) + ' tracks)...' - - while True: - if self.lp == 0: - break - if self.mode_shuffle == 1: - self.playlist, media = self.get_next_media_rand(self.playlist) - else: - self.playlist, media = self.get_next_media_lin(self.playlist) - - self.counter += 1 - if os.path.exists(media) and not '/.' in media: - file_name = string.replace(media, self.media_dir + os.sep, '') - self.channel.set_metadata({'song': file_name}) - self.update_rss(file_name) - _stream = self.core_process(media, self.buffer_size) - print 'Defuzzing this file on %s : id = %s, name = %s' % (self.short_name, self.id, file_name) - - for __chunk in _stream: - if len(__chunk) == 0: - break - self.channel.send(__chunk) - it = q.get(1) - self.channel.sync() - #time.sleep(0.001) - #print "Station " + self.short_name + " eated 1 queue step: "+str(it) - - self.channel.close() - - -def main(): - if len(sys.argv) == 2: - print "Defuzz v"+version - defuzz_main = DeFuzz(sys.argv[1]) - defuzz_main.start() - else: - text = prog_info() - sys.exit(text) - -if __name__ == '__main__': - main() - diff --git a/example/myfuzz.xml b/example/myfuzz.xml index d9087e9..8cd3e4f 100644 --- a/example/myfuzz.xml +++ b/example/myfuzz.xml @@ -1,4 +1,4 @@ - + My_Station_1 @@ -49,5 +49,5 @@ 1 - + diff --git a/install.py b/install.py index 80d403e..4e8846e 100644 --- a/install.py +++ b/install.py @@ -17,12 +17,12 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# ONLY FOR LINUX +# ONLY FOR LINUX / UNIX import os, sys if len(sys.argv) == 1: - install_dir = '/usr/share/defuzz/' + install_dir = '/usr/share/deefuzz/' elif len(sys.argv) > 2: sys.exit('Give just one directory to install Telemeta, or none.') else: @@ -34,13 +34,13 @@ if not os.path.exists(install_dir): os.system('cp -ra ./* '+install_dir+os.sep) os.system('rm -rf '+install_dir+os.sep+'debian') -if os.path.exists('/usr/bin/defuzz'): - os.system('rm -r /usr/bin/defuzz') +if os.path.exists('/usr/bin/deefuzz'): + os.system('rm -r /usr/bin/deefuzz') -os.system('ln -s '+install_dir+os.sep+'defuzz.py '+'/usr/bin/defuzz') +os.system('ln -s '+install_dir+os.sep+'deefuzz.py '+'/usr/bin/deefuzz') print """ Installation successfull ! - Type 'defuzz' now... + Type 'deefuzz' now... """