]> git.parisson.com Git - deefuzzer.git/commitdiff
rename d-fuzz to d-fuzz, restore real subprocess streaming, fix hidden file playlisting
authorGuillaume Pellerin <yomguy@parisson.com>
Tue, 10 Mar 2009 13:07:05 +0000 (13:07 +0000)
committerGuillaume Pellerin <yomguy@parisson.com>
Tue, 10 Mar 2009 13:07:05 +0000 (13:07 +0000)
INSTALL
README
d-fuzz.py
defuzz.py [new file with mode: 0755]

diff --git a/INSTALL b/INSTALL
index 39ac742bfbd517c8f7a72c43a8d4ec33c557c79d..e62f82bd3eb12b9dbe47cf922cb7bf0218d8f964 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -9,14 +9,13 @@
 #
 # Author: Guillaume Pellerin <pellerin@parisson.com>
 
- 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 2ed6b019620a965f65de0b835a2cbca7e8781292..4af55d51cd89c01386fdd3dcdd96ab4016e82be8 100644 (file)
--- 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...
 
index 3040bf000202566e530fc41294255a6668d953c7..e57cc37360e8372fded8ff7356180224e6489f67 100755 (executable)
--- a/d-fuzz.py
+++ b/d-fuzz.py
@@ -1,12 +1,12 @@
 #!/usr/bin/python
 # -*- coding: utf-8 -*-
 #
-# Copyright (c) 2007-2007 Guillaume Pellerin <yomguy@parisson.com>
+# Copyright (c) 2007-2009 Guillaume Pellerin <yomguy@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/d-fuzz/wiki/DfuzzLicense.
+# are also available at http://svn.parisson.org/defuzz/wiki/DefuzzLicense.
 #
 # Author: Guillaume Pellerin <yomguy@parisson.com>
 
@@ -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 <yomguy@parisson.com>
  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 (executable)
index 0000000..b57af89
--- /dev/null
+++ b/defuzz.py
@@ -0,0 +1,294 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2007-2009 Guillaume Pellerin <yomguy@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/defuzz/wiki/DefuzzLicense.
+#
+# Author: Guillaume Pellerin <yomguy@parisson.com>
+
+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 <yomguy@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/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()
+