]> git.parisson.com Git - deefuzzer.git/commitdiff
mv station
authoryomguy <yomguy@parisson.com>
Fri, 30 Dec 2011 21:55:32 +0000 (22:55 +0100)
committeryomguy <yomguy@parisson.com>
Fri, 30 Dec 2011 21:55:32 +0000 (22:55 +0100)
deefuzzer/__init__.py
deefuzzer/core.py
deefuzzer/station.py [new file with mode: 0644]
deefuzzer/tools/station.py [deleted file]

index 7044ccca55104300530e7901c6f48f1f7aa58697..f42a4201bbbd12c0c80f53adadb56389063abc2c 100644 (file)
@@ -1,4 +1,5 @@
 from core import *
+from station import *
 from tools import *
 
 __version__ = '0.6.1'
index a40bc368103e33ed57d6ea88c1c9e02a26dd9676..5fc75694408ca100a2c853588590be2f0cae2101 100755 (executable)
@@ -41,6 +41,7 @@ import shout
 import Queue
 import datetime
 from threading import Thread
+from deefuzzer.station import *
 from deefuzzer.tools import *
 
 class DeeFuzzer(Thread):
diff --git a/deefuzzer/station.py b/deefuzzer/station.py
new file mode 100644 (file)
index 0000000..2b95cbf
--- /dev/null
@@ -0,0 +1,594 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-2011 Guillaume Pellerin
+
+# <yomguy@parisson.com>
+
+# This software is a computer program whose purpose is to stream audio
+# and video data through icecast2 servers.
+
+# This software is governed by the CeCILL license under French law and
+# abiding by the rules of distribution of free software. You can use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty and the software's author, the holder of the
+# economic rights, and the successive licensors have only limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading, using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean that it is complicated to manipulate, and that also
+# therefore means that it is reserved for developers and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and, more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+
+# Author: Guillaume Pellerin <yomguy@parisson.com>
+
+import os
+import sys
+import time
+import datetime
+import string
+import random
+import shout
+import urllib
+import mimetypes
+from threading import Thread
+from __init__ import *
+
+
+class Station(Thread):
+    """a DeeFuzzer shouting station thread"""
+
+    def __init__(self, station, q, logger, m3u):
+        Thread.__init__(self)
+        self.station = station
+        self.q = q
+        self.logger = logger
+        self.channel = shout.Shout()
+        self.id = 999999
+        self.counter = 0
+        self.command = 'cat '
+        self.delay = 0
+        self.start_time = time.time()
+
+        # Media
+        self.media_dir = self.station['media']['dir']
+        self.channel.format = self.station['media']['format']
+        self.shuffle_mode = 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.channel.url = self.station['infos']['url']
+        self.short_name = self.station['infos']['short_name']
+        self.channel.name = self.station['infos']['name'] + ' : ' + self.channel.url
+        self.channel.genre = self.station['infos']['genre']
+        self.channel.description = self.station['infos']['description']
+        self.m3u = m3u
+
+        # RSS
+        self.rss_dir = self.station['rss']['dir']
+        self.rss_enclosure = self.station['rss']['enclosure']
+        if 'media_url' in self.station['rss']:
+            self.rss_media_url = self.station['rss']['media_url']
+            if self.rss_media_url[-1] != '/':
+                self.rss_media_url = self.rss_media_url + '/'
+        else:
+            self.rss_media_url = self.channel.url + '/media/'
+        self.base_name = self.rss_dir + os.sep + self.short_name + '_' + self.channel.format
+        self.rss_current_file = self.base_name + '_current.xml'
+        self.rss_playlist_file = self.base_name + '_playlist.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 = { 'bitrate': self.bitrate,
+                                    'samplerate': self.samplerate,
+                                    'quality': self.ogg_quality,
+                                    'channels': self.voices,}
+        self.server_url = 'http://' + self.channel.host + ':' + str(self.channel.port)
+        self.channel_url = self.server_url + self.channel.mount 
+        self.server_ping = False
+        
+        # Playlist
+        self.playlist = self.get_playlist()
+        self.lp = len(self.playlist)
+
+        # Logging
+        self.logger.write_info('Opening ' + self.short_name + ' - ' + self.channel.name + \
+                ' (' + str(self.lp) + ' tracks)...')
+
+        self.metadata_relative_dir = 'metadata'
+        self.metadata_url = self.channel.url + '/rss/' + self.metadata_relative_dir
+        self.metadata_dir = self.rss_dir + os.sep + self.metadata_relative_dir
+        if not os.path.exists(self.metadata_dir):
+            os.makedirs(self.metadata_dir)
+
+        # The station's player
+        self.player = Player()
+        self.player_mode = 0
+
+        # Jingling between each media.
+        # mode = 0 means Off, mode = 1 means On
+        self.jingles_mode = 0
+        if 'jingles' in self.station:
+            self.jingles_mode =  int(self.station['jingles']['mode'])
+            self.jingles_shuffle = self.station['jingles']['shuffle']
+            self.jingles_dir = self.station['jingles']['dir']
+            if self.jingles_mode == 1:
+                self.jingles_callback('/jingles', [1])
+
+        # Relaying
+        # mode = 0 means Off, mode = 1 means On
+        self.relay_mode = 0
+        if 'relay' in self.station:
+            self.relay_mode = int(self.station['relay']['mode'])
+            self.relay_url = self.station['relay']['url']
+            self.relay_author = self.station['relay']['author']
+            if self.relay_mode == 1:
+                self.relay_callback('/media/relay', [1])
+
+        # Twitting
+        # mode = 0 means Off, mode = 1 means On
+        self.twitter_mode = 0
+        if 'twitter' in self.station:
+            self.twitter_mode = int(self.station['twitter']['mode'])
+            self.twitter_key = self.station['twitter']['key']
+            self.twitter_secret = self.station['twitter']['secret']
+            self.twitter_tags = self.station['twitter']['tags'].split(' ')
+            try:
+                self.twitter_messages = self.station['twitter']['message']
+                if isinstance(self.twitter_messages,  dict):
+                    self.twitter_messages = list(self.twitter_messages)
+            except:
+                pass
+                    
+            if self.twitter_mode == 1:
+                self.twitter_callback('/twitter', [1])
+
+        # Recording
+        # mode = 0 means Off, mode = 1 means On
+        self.record_mode = 0
+        if 'record' in self.station:
+            self.record_mode = int(self.station['record']['mode'])
+            self.record_dir = self.station['record']['dir']
+            if self.record_mode == 1:
+                self.record_callback('/record', [1])
+
+        # Running
+        # mode = 0 means Off, mode = 1 means On
+        self.run_mode = 1
+
+        # OSCing
+        self.osc_control_mode = 0
+        # mode = 0 means Off, mode = 1 means On
+        if 'control' in self.station:
+            self.osc_control_mode = int(self.station['control']['mode'])
+            self.osc_port = self.station['control']['port']
+            if self.osc_control_mode == 1:
+                self.osc_controller = OSCController(self.osc_port)
+                self.osc_controller.start()
+                # OSC paths and callbacks
+                self.osc_controller.add_method('/media/next', 'i', self.media_next_callback)
+                self.osc_controller.add_method('/media/relay', 'i', self.relay_callback)
+                self.osc_controller.add_method('/twitter', 'i', self.twitter_callback)
+                self.osc_controller.add_method('/jingles', 'i', self.jingles_callback)
+                self.osc_controller.add_method('/record', 'i', self.record_callback)
+                self.osc_controller.add_method('/player', 'i', self.player_callback)
+                self.osc_controller.add_method('/run', 'i', self.run_callback)
+
+    def run_callback(self, path, value):
+        value = value[0]
+        self.run_mode = value
+        message = "Station " + self.channel_url + " : received OSC message '%s' with arguments '%d'" % (path, value)
+        self.logger.write_info(message)
+
+    def media_next_callback(self, path, value):
+        value = value[0]
+        self.next_media = value
+        message = "Station " + self.channel_url + " : received OSC message '%s' with arguments '%d'" % (path, value)
+        self.logger.write_info(message)
+
+    def relay_callback(self, path, value):
+        value = value[0]
+        if value == 1:
+            self.relay_mode = 1
+            self.player.start_relay(self.relay_url)
+        elif value == 0:
+            self.relay_mode = 0
+            self.player.stop_relay()
+        self.id = 0
+        self.next_media = 1
+        message = "Station " + self.channel_url + " : received OSC message '%s' with arguments '%d'" % (path, value)
+        self.logger.write_info(message)
+        message = "Station " + self.channel_url + " : relaying : %s" % self.relay_url
+        self.logger.write_info(message)
+
+    def twitter_callback(self, path, value):
+        value = value[0]
+        import tinyurl
+        self.twitter = Twitter(self.twitter_key, self.twitter_secret)
+        self.twitter_mode = value
+        message = "Station " + self.channel_url + " : received OSC message '%s' with arguments '%d'" % (path, value)
+        self.m3u_tinyurl = tinyurl.create_one(self.channel.url + '/m3u/' + self.m3u.split(os.sep)[-1])
+        self.rss_tinyurl = tinyurl.create_one(self.channel.url + '/rss/' + self.rss_playlist_file.split(os.sep)[-1])
+        self.logger.write_info(message)
+
+    def jingles_callback(self, path, value):
+        value = value[0]
+        if value == 1:
+            self.jingles_list = self.get_jingles()
+            self.jingles_length = len(self.jingles_list)
+            self.jingle_id = 0
+        self.jingles_mode = value
+        message = "Station " + self.channel_url + " : received OSC message '%s' with arguments '%d'" % (path, value)
+        self.logger.write_info(message)
+
+    def record_callback(self, path, value):
+        value = value[0]
+        if value == 1:
+            if not os.path.exists(self.record_dir):
+                os.makedirs(self.record_dir)
+            self.rec_file = self.short_name + '-' + \
+              datetime.datetime.now().strftime("%x-%X").replace('/', '_') + '.' + self.channel.format
+            self.recorder = Recorder(self.record_dir)
+            self.recorder.open(self.rec_file)
+        elif value == 0:
+            self.recorder.close()
+            date = datetime.datetime.now().strftime("%Y")
+            if self.channel.format == 'mp3':
+                media = Mp3(self.record_dir + os.sep + self.rec_file)
+            if self.channel.format == 'ogg':
+                media = Ogg(self.record_dir + os.sep + self.rec_file)
+            media.metadata = {'artist': self.artist.encode('utf-8'),
+                                'title': self.title.encode('utf-8'),
+                                'album': self.short_name.encode('utf-8'),
+                                'genre': self.channel.genre.encode('utf-8'),
+                                'date' : date.encode('utf-8'),}
+            media.write_tags()
+        self.record_mode = value
+        message = "Station " + self.channel_url + " : received OSC message '%s' with arguments '%d'" % (path, value)
+        self.logger.write_info(message)
+
+    def player_callback(self, path, value):
+        value = value[0]
+        self.player_mode = value
+        message = "Station " + self.channel_url + " : received OSC message '%s' with arguments '%d'" % (path, value)
+        self.logger.write_info(message)
+
+    def get_playlist(self):
+        file_list = []
+        for root, dirs, files in os.walk(self.media_dir):
+            for file in files:
+                s = file.split('.')
+                ext = s[len(s)-1]
+                if ext.lower() == self.channel.format and not os.sep+'.' in file:
+                    file_list.append(root + os.sep + file)
+        file_list.sort()
+        return file_list
+
+    def get_jingles(self):
+        file_list = []
+        for root, dirs, files in os.walk(self.jingles_dir):
+            for file in files:
+                s = file.split('.')
+                ext = s[len(s)-1]
+                if ext.lower() == self.channel.format and not os.sep+'.' in file:
+                    file_list.append(root + os.sep + file)
+        file_list.sort()
+        return file_list
+
+    def get_next_media(self):
+        # Init playlist
+        if self.lp != 0:
+            playlist = self.playlist
+            new_playlist = self.get_playlist()
+            lp_new = len(new_playlist)
+
+            if lp_new != self.lp or self.counter == 0:
+                self.id = 0
+                self.lp = lp_new
+
+                # Twitting new tracks
+                new_playlist_set = set(new_playlist)
+                playlist_set = set(playlist)
+                new_tracks = new_playlist_set - playlist_set
+                self.new_tracks = list(new_tracks.copy())
+                
+                if len(new_tracks) != 0:
+                    new_tracks_objs = self.media_to_objs(self.new_tracks)
+                    for media_obj in new_tracks_objs:
+                        title = media_obj.metadata['title']
+                        artist = media_obj.metadata['artist']
+                        if not (title or artist):
+                            song = str(media_obj.file_name)
+                        else:
+                            song = artist + ' : ' + title
+                        song = song.encode('utf-8')
+                        artist = artist.encode('utf-8')
+                        if self.twitter_mode == 1:
+                            artist_names = artist.split(' ')
+                            artist_tags = ' #'.join(list(set(artist_names)-set(['&', '-'])))
+                            message = '#NEWTRACK ! %s #%s on #%s RSS: ' % (song.replace('_', ' '), artist_tags, self.short_name)
+                            message = message[:113] + self.rss_tinyurl
+                            self.update_twitter(message)
+                
+                # Shake it, Fuzz it !
+                if self.shuffle_mode == 1:
+                    random.shuffle(playlist)
+                    
+                # Play new tracks first
+                for track in self.new_tracks:
+                    playlist.insert(0, track)
+                self.playlist = playlist
+                
+                self.logger.write_info('Station ' + self.channel_url + \
+                                 ' : generating new playlist (' + str(self.lp) + ' tracks)')
+                self.update_rss(self.media_to_objs(self.playlist), self.rss_playlist_file, '(playlist)')
+
+            if self.jingles_mode == 1 and (self.counter % 2) == 0 and not self.jingles_length == 0:
+                media = self.jingles_list[self.jingle_id]
+                self.jingle_id = (self.jingle_id + 1) % self.jingles_length
+            else:
+                media = self.playlist[self.id]
+                self.id = (self.id + 1) % self.lp
+            return media
+        else:
+            mess = 'No media in media_dir !'
+            self.logger.write_error(mess)
+            sys.exit(mess)
+
+    def media_to_objs(self, media_list):
+        media_objs = []
+        for media in media_list:
+            file_name, file_title, file_ext = get_file_info(media)
+            if file_ext.lower() == 'mp3' and mimetypes.guess_type(media)[0] == 'audio/mpeg':
+                try:
+                    media_objs.append(Mp3(media))
+                except:
+                    continue
+            elif file_ext.lower() == 'ogg' and mimetypes.guess_type(media)[0] == 'audio/ogg':
+                try:
+                    media_objs.append(Ogg(media))
+                except:
+                    continue
+        return media_objs
+
+    def update_rss(self, media_list, rss_file, sub_title):
+        rss_item_list = []
+        if not os.path.exists(self.rss_dir):
+            os.makedirs(self.rss_dir)
+        channel_subtitle = self.channel.name + ' ' + sub_title
+        _date_now = datetime.datetime.now()
+        date_now = str(_date_now)
+        media_absolute_playtime = _date_now
+
+        for media in media_list:
+            media_stats = os.stat(media.media)
+            media_date = time.localtime(media_stats[8])
+            media_date = time.strftime("%a, %d %b %Y %H:%M:%S +0200", media_date)
+            media.metadata['Duration'] = str(media.length).split('.')[0]
+            media.metadata['Bitrate'] = str(media.bitrate) + ' kbps'
+            media.metadata['Next play'] = str(media_absolute_playtime).split('.')[0]
+
+            media_description = '<table>'
+            media_description_item = '<tr><td>%s:   </td><td><b>%s</b></td></tr>'
+            for key in media.metadata.keys():
+                if media.metadata[key] != '':
+                    media_description += media_description_item % (key.capitalize(), media.metadata[key])
+            media_description += '</table>'
+
+            title = media.metadata['title']
+            artist = media.metadata['artist']
+            if not (title or artist):
+                song = str(media.file_title)
+            else:
+                song = artist + ' : ' + title
+
+            media_absolute_playtime += media.length
+
+            if self.rss_enclosure == '1':
+                media_link = self.rss_media_url + media.file_name
+                media_link = media_link.decode('utf-8')
+                rss_item_list.append(RSSItem(
+                    title = song,
+                    link = media_link,
+                    description = media_description,
+                    enclosure = Enclosure(media_link, str(media.size), 'audio/mpeg'),
+                    guid = Guid(media_link),
+                    pubDate = media_date,)
+                    )
+            else:
+                media_link = self.metadata_url + '/' + media.file_name + '.xml'
+                try:
+                    media_link = media_link.decode('utf-8')
+                except:
+                    continue
+                rss_item_list.append(RSSItem(
+                    title = song,
+                    link = media_link,
+                    description = media_description,
+                    guid = Guid(media_link),
+                    pubDate = media_date,)
+                    )
+
+        rss = RSS2(title = channel_subtitle,
+                            link = self.channel.url,
+                            description = self.channel.description.decode('utf-8'),
+                            lastBuildDate = date_now,
+                            items = rss_item_list,)
+        f = open(rss_file, 'w')
+        rss.write_xml(f, 'utf-8')
+        f.close()
+
+    def update_twitter(self, message):
+        try:
+            self.twitter.post(message.decode('utf8'))
+            self.logger.write_info('Twitting : "' + message + '"')
+        except:
+            self.logger.write_error('Twitting : "' + message + '"')
+            pass
+
+    def set_relay_mode(self):
+        self.prefix = '#nowplaying (relaying #LIVE)'
+        self.title = self.channel.description.encode('utf-8')
+        self.artist = self.relay_author.encode('utf-8')
+        self.title = self.title.replace('_', ' ')
+        self.artist = self.artist.replace('_', ' ')
+        self.song = self.artist + ' : ' + self.title
+        self.stream = self.player.relay_read()
+
+    def set_read_mode(self):
+        self.prefix = '#nowplaying'
+        self.current_media_obj = self.media_to_objs([self.media])
+        self.title = self.current_media_obj[0].metadata['title']
+        self.artist = self.current_media_obj[0].metadata['artist']
+        self.title = self.title.replace('_', ' ')
+        self.artist = self.artist.replace('_', ' ')
+        if not (self.title or self.artist):
+            song = str(self.current_media_obj[0].file_name)
+        else:
+            song = self.artist + ' : ' + self.title
+        self.song = song.encode('utf-8')
+        self.artist = self.artist.encode('utf-8')
+        self.metadata_file = self.metadata_dir + os.sep + self.current_media_obj[0].file_name + '.xml'
+        #self.update_rss(self.current_media_obj, self.metadata_file, '')
+        self.update_rss(self.current_media_obj, self.rss_current_file, '(currently playing)')
+        self.logger.write_info('DeeFuzzing on %s :  id = %s, name = %s' \
+            % (self.short_name, self.id, self.current_media_obj[0].file_name))
+        self.player.set_media(self.media)
+        if self.player_mode == 0:
+            self.stream = self.player.file_read_slow()
+        elif self.player_mode == 1:
+            self.stream = self.player.file_read_fast()
+
+    def update_twitter_current(self):
+        artist_names = self.artist.split(' ')
+        artist_tags = ' #'.join(list(set(artist_names)-set(['&', '-'])))
+        message = '%s %s on #%s' % (self.prefix, self.song, self.short_name)
+        tags = '#' + ' #'.join(self.twitter_tags)
+        message = message + ' ' + tags
+        message = message[:108] + ' M3U: ' + self.m3u_tinyurl
+        self.update_twitter(message)
+
+    def channel_open(self):
+        self.channel.open()
+        self.channel_delay = self.channel.delay()
+    
+    def ping_server(self):
+        log = True
+        while not self.server_ping:
+            try:
+                self.q.get(1)
+                server = urllib.urlopen(self.server_url)
+                self.server_ping = True
+                self.logger.write_info('Station ' + self.channel_url + ' : channel available')
+                self.q.task_done()
+            except:
+                time.sleep(0.5)
+                if log:
+                    self.logger.write_error('Station ' + self.channel_url + ' : could not connect the channel' )
+                    log = False
+                self.q.task_done()
+                pass
+        
+    def run(self):
+        self.ping_server()
+        self.q.get(1)
+        self.channel_open()
+        self.logger.write_info('Station ' + self.channel_url + ' : channel connected')
+        self.q.task_done()
+        
+        while self.run_mode:
+            self.q.get(1)
+            self.next_media = 0
+            self.media = self.get_next_media()
+            self.counter += 1
+            if self.relay_mode:
+                self.set_relay_mode()
+            elif os.path.exists(self.media) and not os.sep+'.' in self.media:
+                if self.lp == 0:
+                    self.logger.write_error('Station ' + self.channel_url + ' : has no media to stream !')
+                    break
+                self.set_read_mode()
+            self.q.task_done()
+
+            self.q.get(1)
+            if (not (self.jingles_mode and (self.counter % 2)) or self.relay_mode) and self.twitter_mode:
+                try:
+                    self.update_twitter_current()
+                except:
+                    continue
+            try:
+                self.channel.set_metadata({'song': self.song, 'charset': 'utf-8',})
+            except:
+                continue
+            self.q.task_done()
+
+            for self.chunk in self.stream:
+                if self.next_media or not self.run_mode:
+                    break
+                if self.record_mode:
+                    try:
+                        self.q.get(1)
+                        self.recorder.write(self.chunk)
+                        self.q.task_done()
+                    except:
+                        self.logger.write_error('Station ' + self.channel_url + ' : could not write the buffer to the file')
+                        self.q.task_done()
+                        continue
+                try:
+                    self.q.get(1)
+                    self.channel.send(self.chunk)
+                    self.channel.sync()
+                    self.q.task_done()
+                except:
+                    self.logger.write_error('Station ' + self.channel_url + ' : could not send the buffer')
+                    self.q.task_done()
+                    try:
+                        self.q.get(1)
+                        self.channel.close()
+                        self.logger.write_info('Station ' + self.channel_url + ' : channel closed')
+                        self.q.task_done()
+                    except:
+                        self.logger.write_error('Station ' + self.channel_url + ' : could not close the channel')
+                        self.q.task_done()
+                        continue
+                    try:
+                        self.ping_server()
+                        self.q.get(1)
+                        self.channel_open()
+                        self.channel.set_metadata({'song': self.song, 'charset': 'utf8',})
+                        self.logger.write_info('Station ' + self.channel_url + ' : channel restarted')
+                        self.q.task_done()
+                    except:
+                        self.logger.write_error('Station ' + self.channel_url + ' : could not restart the channel')
+                        self.q.task_done()
+                        continue
+                    continue
+
+        if self.record_mode:
+            self.recorder.close()
+
+        self.channel.close()
+        
diff --git a/deefuzzer/tools/station.py b/deefuzzer/tools/station.py
deleted file mode 100644 (file)
index 2b95cbf..0000000
+++ /dev/null
@@ -1,594 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2006-2011 Guillaume Pellerin
-
-# <yomguy@parisson.com>
-
-# This software is a computer program whose purpose is to stream audio
-# and video data through icecast2 servers.
-
-# This software is governed by the CeCILL license under French law and
-# abiding by the rules of distribution of free software. You can use,
-# modify and/ or redistribute the software under the terms of the CeCILL
-# license as circulated by CEA, CNRS and INRIA at the following URL
-# "http://www.cecill.info".
-
-# As a counterpart to the access to the source code and  rights to copy,
-# modify and redistribute granted by the license, users are provided only
-# with a limited warranty and the software's author, the holder of the
-# economic rights, and the successive licensors have only limited
-# liability.
-
-# In this respect, the user's attention is drawn to the risks associated
-# with loading, using,  modifying and/or developing or reproducing the
-# software by the user in light of its specific status of free software,
-# that may mean that it is complicated to manipulate, and that also
-# therefore means that it is reserved for developers and  experienced
-# professionals having in-depth computer knowledge. Users are therefore
-# encouraged to load and test the software's suitability as regards their
-# requirements in conditions enabling the security of their systems and/or
-# data to be ensured and, more generally, to use and operate it in the
-# same conditions as regards security.
-
-# The fact that you are presently reading this means that you have had
-# knowledge of the CeCILL license and that you accept its terms.
-
-# Author: Guillaume Pellerin <yomguy@parisson.com>
-
-import os
-import sys
-import time
-import datetime
-import string
-import random
-import shout
-import urllib
-import mimetypes
-from threading import Thread
-from __init__ import *
-
-
-class Station(Thread):
-    """a DeeFuzzer shouting station thread"""
-
-    def __init__(self, station, q, logger, m3u):
-        Thread.__init__(self)
-        self.station = station
-        self.q = q
-        self.logger = logger
-        self.channel = shout.Shout()
-        self.id = 999999
-        self.counter = 0
-        self.command = 'cat '
-        self.delay = 0
-        self.start_time = time.time()
-
-        # Media
-        self.media_dir = self.station['media']['dir']
-        self.channel.format = self.station['media']['format']
-        self.shuffle_mode = 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.channel.url = self.station['infos']['url']
-        self.short_name = self.station['infos']['short_name']
-        self.channel.name = self.station['infos']['name'] + ' : ' + self.channel.url
-        self.channel.genre = self.station['infos']['genre']
-        self.channel.description = self.station['infos']['description']
-        self.m3u = m3u
-
-        # RSS
-        self.rss_dir = self.station['rss']['dir']
-        self.rss_enclosure = self.station['rss']['enclosure']
-        if 'media_url' in self.station['rss']:
-            self.rss_media_url = self.station['rss']['media_url']
-            if self.rss_media_url[-1] != '/':
-                self.rss_media_url = self.rss_media_url + '/'
-        else:
-            self.rss_media_url = self.channel.url + '/media/'
-        self.base_name = self.rss_dir + os.sep + self.short_name + '_' + self.channel.format
-        self.rss_current_file = self.base_name + '_current.xml'
-        self.rss_playlist_file = self.base_name + '_playlist.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 = { 'bitrate': self.bitrate,
-                                    'samplerate': self.samplerate,
-                                    'quality': self.ogg_quality,
-                                    'channels': self.voices,}
-        self.server_url = 'http://' + self.channel.host + ':' + str(self.channel.port)
-        self.channel_url = self.server_url + self.channel.mount 
-        self.server_ping = False
-        
-        # Playlist
-        self.playlist = self.get_playlist()
-        self.lp = len(self.playlist)
-
-        # Logging
-        self.logger.write_info('Opening ' + self.short_name + ' - ' + self.channel.name + \
-                ' (' + str(self.lp) + ' tracks)...')
-
-        self.metadata_relative_dir = 'metadata'
-        self.metadata_url = self.channel.url + '/rss/' + self.metadata_relative_dir
-        self.metadata_dir = self.rss_dir + os.sep + self.metadata_relative_dir
-        if not os.path.exists(self.metadata_dir):
-            os.makedirs(self.metadata_dir)
-
-        # The station's player
-        self.player = Player()
-        self.player_mode = 0
-
-        # Jingling between each media.
-        # mode = 0 means Off, mode = 1 means On
-        self.jingles_mode = 0
-        if 'jingles' in self.station:
-            self.jingles_mode =  int(self.station['jingles']['mode'])
-            self.jingles_shuffle = self.station['jingles']['shuffle']
-            self.jingles_dir = self.station['jingles']['dir']
-            if self.jingles_mode == 1:
-                self.jingles_callback('/jingles', [1])
-
-        # Relaying
-        # mode = 0 means Off, mode = 1 means On
-        self.relay_mode = 0
-        if 'relay' in self.station:
-            self.relay_mode = int(self.station['relay']['mode'])
-            self.relay_url = self.station['relay']['url']
-            self.relay_author = self.station['relay']['author']
-            if self.relay_mode == 1:
-                self.relay_callback('/media/relay', [1])
-
-        # Twitting
-        # mode = 0 means Off, mode = 1 means On
-        self.twitter_mode = 0
-        if 'twitter' in self.station:
-            self.twitter_mode = int(self.station['twitter']['mode'])
-            self.twitter_key = self.station['twitter']['key']
-            self.twitter_secret = self.station['twitter']['secret']
-            self.twitter_tags = self.station['twitter']['tags'].split(' ')
-            try:
-                self.twitter_messages = self.station['twitter']['message']
-                if isinstance(self.twitter_messages,  dict):
-                    self.twitter_messages = list(self.twitter_messages)
-            except:
-                pass
-                    
-            if self.twitter_mode == 1:
-                self.twitter_callback('/twitter', [1])
-
-        # Recording
-        # mode = 0 means Off, mode = 1 means On
-        self.record_mode = 0
-        if 'record' in self.station:
-            self.record_mode = int(self.station['record']['mode'])
-            self.record_dir = self.station['record']['dir']
-            if self.record_mode == 1:
-                self.record_callback('/record', [1])
-
-        # Running
-        # mode = 0 means Off, mode = 1 means On
-        self.run_mode = 1
-
-        # OSCing
-        self.osc_control_mode = 0
-        # mode = 0 means Off, mode = 1 means On
-        if 'control' in self.station:
-            self.osc_control_mode = int(self.station['control']['mode'])
-            self.osc_port = self.station['control']['port']
-            if self.osc_control_mode == 1:
-                self.osc_controller = OSCController(self.osc_port)
-                self.osc_controller.start()
-                # OSC paths and callbacks
-                self.osc_controller.add_method('/media/next', 'i', self.media_next_callback)
-                self.osc_controller.add_method('/media/relay', 'i', self.relay_callback)
-                self.osc_controller.add_method('/twitter', 'i', self.twitter_callback)
-                self.osc_controller.add_method('/jingles', 'i', self.jingles_callback)
-                self.osc_controller.add_method('/record', 'i', self.record_callback)
-                self.osc_controller.add_method('/player', 'i', self.player_callback)
-                self.osc_controller.add_method('/run', 'i', self.run_callback)
-
-    def run_callback(self, path, value):
-        value = value[0]
-        self.run_mode = value
-        message = "Station " + self.channel_url + " : received OSC message '%s' with arguments '%d'" % (path, value)
-        self.logger.write_info(message)
-
-    def media_next_callback(self, path, value):
-        value = value[0]
-        self.next_media = value
-        message = "Station " + self.channel_url + " : received OSC message '%s' with arguments '%d'" % (path, value)
-        self.logger.write_info(message)
-
-    def relay_callback(self, path, value):
-        value = value[0]
-        if value == 1:
-            self.relay_mode = 1
-            self.player.start_relay(self.relay_url)
-        elif value == 0:
-            self.relay_mode = 0
-            self.player.stop_relay()
-        self.id = 0
-        self.next_media = 1
-        message = "Station " + self.channel_url + " : received OSC message '%s' with arguments '%d'" % (path, value)
-        self.logger.write_info(message)
-        message = "Station " + self.channel_url + " : relaying : %s" % self.relay_url
-        self.logger.write_info(message)
-
-    def twitter_callback(self, path, value):
-        value = value[0]
-        import tinyurl
-        self.twitter = Twitter(self.twitter_key, self.twitter_secret)
-        self.twitter_mode = value
-        message = "Station " + self.channel_url + " : received OSC message '%s' with arguments '%d'" % (path, value)
-        self.m3u_tinyurl = tinyurl.create_one(self.channel.url + '/m3u/' + self.m3u.split(os.sep)[-1])
-        self.rss_tinyurl = tinyurl.create_one(self.channel.url + '/rss/' + self.rss_playlist_file.split(os.sep)[-1])
-        self.logger.write_info(message)
-
-    def jingles_callback(self, path, value):
-        value = value[0]
-        if value == 1:
-            self.jingles_list = self.get_jingles()
-            self.jingles_length = len(self.jingles_list)
-            self.jingle_id = 0
-        self.jingles_mode = value
-        message = "Station " + self.channel_url + " : received OSC message '%s' with arguments '%d'" % (path, value)
-        self.logger.write_info(message)
-
-    def record_callback(self, path, value):
-        value = value[0]
-        if value == 1:
-            if not os.path.exists(self.record_dir):
-                os.makedirs(self.record_dir)
-            self.rec_file = self.short_name + '-' + \
-              datetime.datetime.now().strftime("%x-%X").replace('/', '_') + '.' + self.channel.format
-            self.recorder = Recorder(self.record_dir)
-            self.recorder.open(self.rec_file)
-        elif value == 0:
-            self.recorder.close()
-            date = datetime.datetime.now().strftime("%Y")
-            if self.channel.format == 'mp3':
-                media = Mp3(self.record_dir + os.sep + self.rec_file)
-            if self.channel.format == 'ogg':
-                media = Ogg(self.record_dir + os.sep + self.rec_file)
-            media.metadata = {'artist': self.artist.encode('utf-8'),
-                                'title': self.title.encode('utf-8'),
-                                'album': self.short_name.encode('utf-8'),
-                                'genre': self.channel.genre.encode('utf-8'),
-                                'date' : date.encode('utf-8'),}
-            media.write_tags()
-        self.record_mode = value
-        message = "Station " + self.channel_url + " : received OSC message '%s' with arguments '%d'" % (path, value)
-        self.logger.write_info(message)
-
-    def player_callback(self, path, value):
-        value = value[0]
-        self.player_mode = value
-        message = "Station " + self.channel_url + " : received OSC message '%s' with arguments '%d'" % (path, value)
-        self.logger.write_info(message)
-
-    def get_playlist(self):
-        file_list = []
-        for root, dirs, files in os.walk(self.media_dir):
-            for file in files:
-                s = file.split('.')
-                ext = s[len(s)-1]
-                if ext.lower() == self.channel.format and not os.sep+'.' in file:
-                    file_list.append(root + os.sep + file)
-        file_list.sort()
-        return file_list
-
-    def get_jingles(self):
-        file_list = []
-        for root, dirs, files in os.walk(self.jingles_dir):
-            for file in files:
-                s = file.split('.')
-                ext = s[len(s)-1]
-                if ext.lower() == self.channel.format and not os.sep+'.' in file:
-                    file_list.append(root + os.sep + file)
-        file_list.sort()
-        return file_list
-
-    def get_next_media(self):
-        # Init playlist
-        if self.lp != 0:
-            playlist = self.playlist
-            new_playlist = self.get_playlist()
-            lp_new = len(new_playlist)
-
-            if lp_new != self.lp or self.counter == 0:
-                self.id = 0
-                self.lp = lp_new
-
-                # Twitting new tracks
-                new_playlist_set = set(new_playlist)
-                playlist_set = set(playlist)
-                new_tracks = new_playlist_set - playlist_set
-                self.new_tracks = list(new_tracks.copy())
-                
-                if len(new_tracks) != 0:
-                    new_tracks_objs = self.media_to_objs(self.new_tracks)
-                    for media_obj in new_tracks_objs:
-                        title = media_obj.metadata['title']
-                        artist = media_obj.metadata['artist']
-                        if not (title or artist):
-                            song = str(media_obj.file_name)
-                        else:
-                            song = artist + ' : ' + title
-                        song = song.encode('utf-8')
-                        artist = artist.encode('utf-8')
-                        if self.twitter_mode == 1:
-                            artist_names = artist.split(' ')
-                            artist_tags = ' #'.join(list(set(artist_names)-set(['&', '-'])))
-                            message = '#NEWTRACK ! %s #%s on #%s RSS: ' % (song.replace('_', ' '), artist_tags, self.short_name)
-                            message = message[:113] + self.rss_tinyurl
-                            self.update_twitter(message)
-                
-                # Shake it, Fuzz it !
-                if self.shuffle_mode == 1:
-                    random.shuffle(playlist)
-                    
-                # Play new tracks first
-                for track in self.new_tracks:
-                    playlist.insert(0, track)
-                self.playlist = playlist
-                
-                self.logger.write_info('Station ' + self.channel_url + \
-                                 ' : generating new playlist (' + str(self.lp) + ' tracks)')
-                self.update_rss(self.media_to_objs(self.playlist), self.rss_playlist_file, '(playlist)')
-
-            if self.jingles_mode == 1 and (self.counter % 2) == 0 and not self.jingles_length == 0:
-                media = self.jingles_list[self.jingle_id]
-                self.jingle_id = (self.jingle_id + 1) % self.jingles_length
-            else:
-                media = self.playlist[self.id]
-                self.id = (self.id + 1) % self.lp
-            return media
-        else:
-            mess = 'No media in media_dir !'
-            self.logger.write_error(mess)
-            sys.exit(mess)
-
-    def media_to_objs(self, media_list):
-        media_objs = []
-        for media in media_list:
-            file_name, file_title, file_ext = get_file_info(media)
-            if file_ext.lower() == 'mp3' and mimetypes.guess_type(media)[0] == 'audio/mpeg':
-                try:
-                    media_objs.append(Mp3(media))
-                except:
-                    continue
-            elif file_ext.lower() == 'ogg' and mimetypes.guess_type(media)[0] == 'audio/ogg':
-                try:
-                    media_objs.append(Ogg(media))
-                except:
-                    continue
-        return media_objs
-
-    def update_rss(self, media_list, rss_file, sub_title):
-        rss_item_list = []
-        if not os.path.exists(self.rss_dir):
-            os.makedirs(self.rss_dir)
-        channel_subtitle = self.channel.name + ' ' + sub_title
-        _date_now = datetime.datetime.now()
-        date_now = str(_date_now)
-        media_absolute_playtime = _date_now
-
-        for media in media_list:
-            media_stats = os.stat(media.media)
-            media_date = time.localtime(media_stats[8])
-            media_date = time.strftime("%a, %d %b %Y %H:%M:%S +0200", media_date)
-            media.metadata['Duration'] = str(media.length).split('.')[0]
-            media.metadata['Bitrate'] = str(media.bitrate) + ' kbps'
-            media.metadata['Next play'] = str(media_absolute_playtime).split('.')[0]
-
-            media_description = '<table>'
-            media_description_item = '<tr><td>%s:   </td><td><b>%s</b></td></tr>'
-            for key in media.metadata.keys():
-                if media.metadata[key] != '':
-                    media_description += media_description_item % (key.capitalize(), media.metadata[key])
-            media_description += '</table>'
-
-            title = media.metadata['title']
-            artist = media.metadata['artist']
-            if not (title or artist):
-                song = str(media.file_title)
-            else:
-                song = artist + ' : ' + title
-
-            media_absolute_playtime += media.length
-
-            if self.rss_enclosure == '1':
-                media_link = self.rss_media_url + media.file_name
-                media_link = media_link.decode('utf-8')
-                rss_item_list.append(RSSItem(
-                    title = song,
-                    link = media_link,
-                    description = media_description,
-                    enclosure = Enclosure(media_link, str(media.size), 'audio/mpeg'),
-                    guid = Guid(media_link),
-                    pubDate = media_date,)
-                    )
-            else:
-                media_link = self.metadata_url + '/' + media.file_name + '.xml'
-                try:
-                    media_link = media_link.decode('utf-8')
-                except:
-                    continue
-                rss_item_list.append(RSSItem(
-                    title = song,
-                    link = media_link,
-                    description = media_description,
-                    guid = Guid(media_link),
-                    pubDate = media_date,)
-                    )
-
-        rss = RSS2(title = channel_subtitle,
-                            link = self.channel.url,
-                            description = self.channel.description.decode('utf-8'),
-                            lastBuildDate = date_now,
-                            items = rss_item_list,)
-        f = open(rss_file, 'w')
-        rss.write_xml(f, 'utf-8')
-        f.close()
-
-    def update_twitter(self, message):
-        try:
-            self.twitter.post(message.decode('utf8'))
-            self.logger.write_info('Twitting : "' + message + '"')
-        except:
-            self.logger.write_error('Twitting : "' + message + '"')
-            pass
-
-    def set_relay_mode(self):
-        self.prefix = '#nowplaying (relaying #LIVE)'
-        self.title = self.channel.description.encode('utf-8')
-        self.artist = self.relay_author.encode('utf-8')
-        self.title = self.title.replace('_', ' ')
-        self.artist = self.artist.replace('_', ' ')
-        self.song = self.artist + ' : ' + self.title
-        self.stream = self.player.relay_read()
-
-    def set_read_mode(self):
-        self.prefix = '#nowplaying'
-        self.current_media_obj = self.media_to_objs([self.media])
-        self.title = self.current_media_obj[0].metadata['title']
-        self.artist = self.current_media_obj[0].metadata['artist']
-        self.title = self.title.replace('_', ' ')
-        self.artist = self.artist.replace('_', ' ')
-        if not (self.title or self.artist):
-            song = str(self.current_media_obj[0].file_name)
-        else:
-            song = self.artist + ' : ' + self.title
-        self.song = song.encode('utf-8')
-        self.artist = self.artist.encode('utf-8')
-        self.metadata_file = self.metadata_dir + os.sep + self.current_media_obj[0].file_name + '.xml'
-        #self.update_rss(self.current_media_obj, self.metadata_file, '')
-        self.update_rss(self.current_media_obj, self.rss_current_file, '(currently playing)')
-        self.logger.write_info('DeeFuzzing on %s :  id = %s, name = %s' \
-            % (self.short_name, self.id, self.current_media_obj[0].file_name))
-        self.player.set_media(self.media)
-        if self.player_mode == 0:
-            self.stream = self.player.file_read_slow()
-        elif self.player_mode == 1:
-            self.stream = self.player.file_read_fast()
-
-    def update_twitter_current(self):
-        artist_names = self.artist.split(' ')
-        artist_tags = ' #'.join(list(set(artist_names)-set(['&', '-'])))
-        message = '%s %s on #%s' % (self.prefix, self.song, self.short_name)
-        tags = '#' + ' #'.join(self.twitter_tags)
-        message = message + ' ' + tags
-        message = message[:108] + ' M3U: ' + self.m3u_tinyurl
-        self.update_twitter(message)
-
-    def channel_open(self):
-        self.channel.open()
-        self.channel_delay = self.channel.delay()
-    
-    def ping_server(self):
-        log = True
-        while not self.server_ping:
-            try:
-                self.q.get(1)
-                server = urllib.urlopen(self.server_url)
-                self.server_ping = True
-                self.logger.write_info('Station ' + self.channel_url + ' : channel available')
-                self.q.task_done()
-            except:
-                time.sleep(0.5)
-                if log:
-                    self.logger.write_error('Station ' + self.channel_url + ' : could not connect the channel' )
-                    log = False
-                self.q.task_done()
-                pass
-        
-    def run(self):
-        self.ping_server()
-        self.q.get(1)
-        self.channel_open()
-        self.logger.write_info('Station ' + self.channel_url + ' : channel connected')
-        self.q.task_done()
-        
-        while self.run_mode:
-            self.q.get(1)
-            self.next_media = 0
-            self.media = self.get_next_media()
-            self.counter += 1
-            if self.relay_mode:
-                self.set_relay_mode()
-            elif os.path.exists(self.media) and not os.sep+'.' in self.media:
-                if self.lp == 0:
-                    self.logger.write_error('Station ' + self.channel_url + ' : has no media to stream !')
-                    break
-                self.set_read_mode()
-            self.q.task_done()
-
-            self.q.get(1)
-            if (not (self.jingles_mode and (self.counter % 2)) or self.relay_mode) and self.twitter_mode:
-                try:
-                    self.update_twitter_current()
-                except:
-                    continue
-            try:
-                self.channel.set_metadata({'song': self.song, 'charset': 'utf-8',})
-            except:
-                continue
-            self.q.task_done()
-
-            for self.chunk in self.stream:
-                if self.next_media or not self.run_mode:
-                    break
-                if self.record_mode:
-                    try:
-                        self.q.get(1)
-                        self.recorder.write(self.chunk)
-                        self.q.task_done()
-                    except:
-                        self.logger.write_error('Station ' + self.channel_url + ' : could not write the buffer to the file')
-                        self.q.task_done()
-                        continue
-                try:
-                    self.q.get(1)
-                    self.channel.send(self.chunk)
-                    self.channel.sync()
-                    self.q.task_done()
-                except:
-                    self.logger.write_error('Station ' + self.channel_url + ' : could not send the buffer')
-                    self.q.task_done()
-                    try:
-                        self.q.get(1)
-                        self.channel.close()
-                        self.logger.write_info('Station ' + self.channel_url + ' : channel closed')
-                        self.q.task_done()
-                    except:
-                        self.logger.write_error('Station ' + self.channel_url + ' : could not close the channel')
-                        self.q.task_done()
-                        continue
-                    try:
-                        self.ping_server()
-                        self.q.get(1)
-                        self.channel_open()
-                        self.channel.set_metadata({'song': self.song, 'charset': 'utf8',})
-                        self.logger.write_info('Station ' + self.channel_url + ' : channel restarted')
-                        self.q.task_done()
-                    except:
-                        self.logger.write_error('Station ' + self.channel_url + ' : could not restart the channel')
-                        self.q.task_done()
-                        continue
-                    continue
-
-        if self.record_mode:
-            self.recorder.close()
-
-        self.channel.close()
-