From: Guillaume Pellerin Date: Tue, 10 Mar 2009 13:07:05 +0000 (+0000) Subject: rename d-fuzz to d-fuzz, restore real subprocess streaming, fix hidden file playlisting X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=dc8e2e2ad4e9fae1df8b37f4d4392c76b38a90e9;p=deefuzzer.git rename d-fuzz to d-fuzz, restore real subprocess streaming, fix hidden file playlisting --- diff --git a/INSTALL b/INSTALL index 39ac742..e62f82b 100644 --- a/INSTALL +++ b/INSTALL @@ -9,14 +9,13 @@ # # Author: Guillaume Pellerin - depends : python, python-xml, shout-python, libshout3, icecast2 + depends : python, python-xml, python-shout | shout-python, libshout3, icecast2 recommends : python-mutagen provides : shout-python - -This last library is included in D-Fuzz but needs to be compiled. +python-shout is included in DeFuzz but needs to be compiled. 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 + $ cd shout-python + $ sudo python setup.py install + \ No newline at end of file diff --git a/README b/README index 2ed6b01..4af55d5 100644 --- a/README +++ b/README @@ -1,13 +1,13 @@ # README # ====== -d-fuzz : a lightweight icecast streaming client written in python +defuzz : a lightweight icecast streaming client written in python # 1. Introduction # =============== -D-Fuzz is a light python program that streams media data from disks to icecast2 +DeFuzz 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,16 +25,16 @@ 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/d-fuzz/DFuzzLicense + are also available at http://svn.parisson.org/defuzz/DeFuzzLicense # 4. Usage # ========= -Usage : d-fuzz CONFIGFILE +Usage : defuzz CONFIGFILE where CONFIGFILE is the path for a XML config file - ex: d-fuzz ./myfuzz.xml + ex: defuzz ./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 @@ -42,7 +42,7 @@ application. 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 d-fuzz` . +You have then to kill each stream manually or make a full `pkill defuzz` . # 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/d-fuzz/ or http://parisson.com for more details... +see http://svn.parisson.org/defuzz/ or http://parisson.com for more details... diff --git a/d-fuzz.py b/d-fuzz.py index 3040bf0..e57cc37 100755 --- a/d-fuzz.py +++ b/d-fuzz.py @@ -1,12 +1,12 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # -# Copyright (c) 2007-2007 Guillaume Pellerin +# 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/d-fuzz/wiki/DfuzzLicense. +# are also available at http://svn.parisson.org/defuzz/wiki/DefuzzLicense. # # Author: Guillaume Pellerin @@ -23,11 +23,11 @@ from tools import * from threading import Thread from mutagen.oggvorbis import OggVorbis -version = '0.2.1' +version = '0.2.2' year = datetime.datetime.now().strftime("%Y") def prog_info(): - desc = '\n d-fuzz : easy and light streaming tool\n' + 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. @@ -36,22 +36,37 @@ def prog_info(): you should have received as part of this distribution. The terms are also available at http://svn.parisson.org/d-fuzz/DFuzzLicense - depends : python, python-xml, shout-python, libshout3, icecast2 + depends : python, python-xml, python-shout, libshout3, icecast2 recommends : python-mutagen - provides : shout-python + provides : python-shout - Usage : d-fuzz $1 + Usage : defuzz $1 where $1 is the path for a XML config file - ex: d-fuzz /etc/d-fuzz/myfuzz.xml + ex: defuzz example/myfuzz.xml - see http://parisson.com/d-fuzz/ for more details + see http://parisson.com/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 DFuzz: - """A D-Fuzz station""" +class DeFuzz: + """A DeFuzz station""" def __init__(self, conf_file): self.conf_file = conf_file @@ -70,10 +85,10 @@ class DFuzz: def run(self): # Fix wrong type data from xmltodict when one station (*) - if isinstance(self.conf['d-fuzz']['station'], dict): + if isinstance(self.conf['defuzz']['station'], dict): nb_stations = 1 else: - nb_stations = len(self.conf['d-fuzz']['station']) + nb_stations = len(self.conf['defuzz']['station']) print 'Number of stations : ' + str(nb_stations) for i in range(0,nb_stations): @@ -81,10 +96,10 @@ class DFuzz: #q = Queue.Queue(1) # (*) idem - if isinstance(self.conf['d-fuzz']['station'], dict): - station = self.conf['d-fuzz']['station'] + if isinstance(self.conf['defuzz']['station'], dict): + station = self.conf['defuzz']['station'] else: - station = self.conf['d-fuzz']['station'][i] + station = self.conf['defuzz']['station'][i] #print station name = station['infos']['name'] nb_channels = int(station['infos']['channels']) @@ -100,7 +115,7 @@ class DFuzz: class Station(Thread): - """A D-Fuzz Station thread""" + """A DeFuzz Station thread""" def __init__(self, station, nb_channels): Thread.__init__(self) @@ -131,6 +146,7 @@ class Channel(Thread): 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'] @@ -169,7 +185,7 @@ class Channel(Thread): 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): @@ -189,16 +205,39 @@ class Channel(Thread): def core_process(self, media, buffer_size): """Read media and stream data through a generator. - Taken from Telemeta...""" + Taken from Telemeta (see http://telemeta.org)""" + + command = self.command + '"' + media + '"' __chunk = 0 - file = open(media, 'r') + 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 = file.read(buffer_size) + __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 - file.close() + + #__chunk = 0 + #file = open(media, 'r') + ## Core processing + #while True: + #__chunk = file.read(buffer_size) + #if len(__chunk) == 0: + #break + #yield __chunk + #file.close() def run(self): #print "Using libshout version %s" % shout.version() @@ -209,9 +248,9 @@ class Channel(Thread): # Playlist playlist = self.get_playlist() - lp = len(playlist) + lp = len(playlist)-1 self.rand_list = range(0,lp) - + while True: if lp == 0: break @@ -221,14 +260,14 @@ class Channel(Thread): else: playlist, media = self.get_next_media_lin(playlist) self.counter += 1 - if os.path.exists(media): + if os.path.exists(media) and not '/.' in media: file_name = string.replace(media, self.media_dir + os.sep, '') #print 'Playlist (%s ch%s) : %s' % (self.short_name, self.channel_id, file_name) #print playlist #print media self.channel.set_metadata({'song': file_name}) stream = self.core_process(media, self.buffer_size) - print 'D-fuzz this file on %s (channel: %s, track: %s): %s' % (self.short_name, self.channel_id, self.id, file_name) + print 'Defuzzing this file on %s (channel: %s, track: %s): %s' % (self.short_name, self.channel_id, self.id, file_name) for __chunk in stream: # Get the queue @@ -239,27 +278,11 @@ class Channel(Thread): self.channel.close() -class DFuzzError: - """The D-Fuzz 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) - -def main(): +def main(): if len(sys.argv) == 2: - print "D-fuzz v"+version - dfuzz_main = DFuzz(sys.argv[1]) - dfuzz_main.run() + print "Defuzz v"+version + defuzz_main = DeFuzz(sys.argv[1]) + defuzz_main.run() else: text = prog_info() sys.exit(text) diff --git a/defuzz.py b/defuzz.py new file mode 100755 index 0000000..b57af89 --- /dev/null +++ b/defuzz.py @@ -0,0 +1,294 @@ +#!/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 subprocess +from shout import Shout +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/DFuzzLicense + + 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://parisson.com/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 station""" + + 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 run(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) + + for i in range(0,nb_stations): + # Create a Queue + #q = Queue.Queue(1) + + # (*) idem + if isinstance(self.conf['defuzz']['station'], dict): + station = self.conf['defuzz']['station'] + else: + station = self.conf['defuzz']['station'][i] + #print station + name = station['infos']['name'] + nb_channels = int(station['infos']['channels']) + print 'Station %s: %s has %s channels' % (str(i+1), name, str(nb_channels)) + #s = Station(station, nb_channels, q) + #s.start() + #time.sleep(0.1) + for channel_id in range(0, nb_channels): + #print channel_id + c = Channel(station, channel_id + 1) + c.start() + #time.sleep(0.5) + + +class Station(Thread): + """A DeFuzz Station thread""" + + def __init__(self, station, nb_channels): + Thread.__init__(self) + #self.station_q = station_q + self.station = station + self.nb_channels = nb_channels + + def run(self): + #station_q = self.station_q + i=0 + while 1 : + #print currentThread(),"Produced One Item:",i + self.station_q.put(i,1) + i+=1 + #time.sleep(1) + + +class Channel(Thread): + """A channel shouting thread""" + + def __init__(self, station, channel_id): + Thread.__init__(self) + #self.channel_q = channel_q + self.station = station + self.buffer_size = 16384 + self.channel_id = channel_id + self.channel = 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']) + # Infos + self.short_name = self.station['infos']['short_name'] + '_' + str(self.channel_id) + self.channel.name = self.station['infos']['name'] + '_' + str(self.channel_id) + self.channel.genre = self.station['infos']['genre'] + self.channel.description = self.station['infos']['description'] + self.channel.url = self.station['infos']['url'] + # 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 + #print self.channel.mount + self.channel.public = int(self.station['server']['public']) + # s.audio_info = { 'key': 'val', ... } + # (keys are shout.SHOUT_AI_BITRATE, shout.SHOUT_AI_SAMPLERATE, + # shout.SHOUT_AI_CHANNELS, shout.SHOUT_AI_QUALITY) + + 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): + #print 'Get random list...' + 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) + #print self.rand_list + self.id = 0 + else: + self.id = self.id + 1 + index = self.rand_list[self.id] + #print str(self.id) +':'+ str(index) + 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 + + #__chunk = 0 + #file = open(media, 'r') + ## Core processing + #while True: + #__chunk = file.read(buffer_size) + #if len(__chunk) == 0: + #break + #yield __chunk + #file.close() + + def run(self): + #print "Using libshout version %s" % shout.version() + #__chunk = 0 + self.channel.open() + print 'Opening ' + self.channel.name + '...' + time.sleep(0.1) + + # Playlist + playlist = self.get_playlist() + lp = len(playlist) + print playlist + self.rand_list = range(0,lp-1) + + while True: + if lp == 0: + break + if self.mode_shuffle == 1: + #print 'Shuffle mode' + playlist, media = self.get_next_media_rand(playlist) + else: + playlist, media = self.get_next_media_lin(playlist) + self.counter += 1 + if os.path.exists(media) and not '/.' in media: + file_name = string.replace(media, self.media_dir + os.sep, '') + #print 'Playlist (%s ch%s) : %s' % (self.short_name, self.channel_id, file_name) + #print playlist + #print media + self.channel.set_metadata({'song': file_name}) + stream = self.core_process(media, self.buffer_size) + print 'Defuzzing this file on %s (channel: %s, track: %s): %s' % (self.short_name, self.channel_id, self.id, file_name) + + for __chunk in stream: + # Get the queue + #self.channel_q.get(1) + self.channel.send(__chunk) + self.channel.sync() + + self.channel.close() + + +def main(): + if len(sys.argv) == 2: + print "Defuzz v"+version + defuzz_main = DeFuzz(sys.argv[1]) + defuzz_main.run() + else: + text = prog_info() + sys.exit(text) + +if __name__ == '__main__': + main() +