From 173e43f1c82d52a93e0c463f82814e16ea0d925e Mon Sep 17 00:00:00 2001 From: yomguy Date: Wed, 11 Jan 2012 15:00:41 +0100 Subject: [PATCH] add type for server: icecast (default) or stream-m add an HTTP streamer client (pycurl needed) adapt main loop (ONLY tested stream-m in relay mode) --- deefuzzer/core.py | 2 +- deefuzzer/station.py | 224 +++++++++++++++++++++--------------- deefuzzer/tools/__init__.py | 1 + deefuzzer/tools/player.py | 16 +++ deefuzzer/tools/relay.py | 1 - deefuzzer/tools/streamer.py | 80 +++++++++++++ example/deefuzzer.xml | 1 + example/deefuzzer_doc.xml | 2 + 8 files changed, 231 insertions(+), 96 deletions(-) create mode 100644 deefuzzer/tools/streamer.py diff --git a/deefuzzer/core.py b/deefuzzer/core.py index 5fc7569..f9ff7e8 100755 --- a/deefuzzer/core.py +++ b/deefuzzer/core.py @@ -88,7 +88,7 @@ class DeeFuzzer(Thread): m3u.write('#EXTM3U\n') for s in self.stations: info = '#EXTINF:%s,%s - %s\n' % ('-1',s.short_name, s.channel.name) - url = s.channel.protocol + '://' + s.channel.host + ':' + str(s.channel.port) + s.channel.mount + '\n' + url = 'http://' + s.channel.host + ':' + str(s.channel.port) + s.channel.mount + '\n' m3u.write(info) m3u.write(url) m3u.close() diff --git a/deefuzzer/station.py b/deefuzzer/station.py index 2b95cbf..1dedd19 100644 --- a/deefuzzer/station.py +++ b/deefuzzer/station.py @@ -46,40 +46,69 @@ import shout import urllib import mimetypes from threading import Thread -from __init__ import * +from tools import * class Station(Thread): """a DeeFuzzer shouting station thread""" + id = 999999 + counter = 0 + command = 'cat ' + delay = 0 + start_time = time.time() + 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() + self.m3u = m3u + self.server_ping = False # Media self.media_dir = self.station['media']['dir'] - self.channel.format = self.station['media']['format'] + self.media_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'] + # Server self.short_name = self.station['infos']['short_name'] + self.mount = '/' + self.short_name + + if 'type' in self.station['server']: + self.type = self.station['server']['type'] # 'icecast' | 'stream-m' + else: + self.type = 'icecast' + + if 'stream-m' in self.type: + self.channel = HTTPStreamer() + self.channel.mount = self.mount + else: + self.channel = shout.Shout() + self.channel.mount = self.mount + '.' + self.media_format + + self.channel.url = self.station['infos']['url'] 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 + self.channel.format = self.media_format + 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.public = int(self.station['server']['public']) + self.channel.genre = self.station['infos']['genre'] + self.channel.description = self.station['infos']['description'] + 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 # RSS self.rss_dir = self.station['rss']['dir'] @@ -93,23 +122,7 @@ class Station(Thread): 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) @@ -162,7 +175,7 @@ class Station(Thread): self.twitter_messages = list(self.twitter_messages) except: pass - + if self.twitter_mode == 1: self.twitter_callback('/twitter', [1]) @@ -213,10 +226,12 @@ class Station(Thread): value = value[0] if value == 1: self.relay_mode = 1 - self.player.start_relay(self.relay_url) + if self.type == 'icecast': + self.player.start_relay(self.relay_url) elif value == 0: self.relay_mode = 0 - self.player.stop_relay() + if self.type == 'icecast': + 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) @@ -314,7 +329,7 @@ class Station(Thread): 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: @@ -332,16 +347,16 @@ class Station(Thread): 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)') @@ -456,7 +471,10 @@ class Station(Thread): self.title = self.title.replace('_', ' ') self.artist = self.artist.replace('_', ' ') self.song = self.artist + ' : ' + self.title - self.stream = self.player.relay_read() + if self.type == 'stream-m': + self.channel.set_callback(RelayReader(self.relay_url).read_callback) + else: + self.stream = self.player.relay_read() def set_read_mode(self): self.prefix = '#nowplaying' @@ -494,7 +512,7 @@ class Station(Thread): def channel_open(self): self.channel.open() self.channel_delay = self.channel.delay() - + def ping_server(self): log = True while not self.server_ping: @@ -511,84 +529,102 @@ class Station(Thread): 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.ping_server() self.q.task_done() - - while self.run_mode: + + if self.type == 'stream-m': 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.channel_open() + self.channel.start() self.q.task_done() + if self.type == 'icecast': 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.channel_open() + self.logger.write_info('Station ' + self.channel_url + ' : channel connected') self.q.task_done() - for self.chunk in self.stream: - if self.next_media or not self.run_mode: - break - if self.record_mode: + 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.q.get(1) - self.recorder.write(self.chunk) - self.q.task_done() + self.update_twitter_current() 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() + self.channel.set_metadata({'song': self.song, 'charset': 'utf-8',}) 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 + 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.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.channel.send(self.chunk) + self.channel.sync() self.q.task_done() except: - self.logger.write_error('Station ' + self.channel_url + ' : could not restart the channel') + 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 - continue - if self.record_mode: - self.recorder.close() + if self.record_mode: + self.recorder.close() + + self.channel.close() + + + + + - self.channel.close() - diff --git a/deefuzzer/tools/__init__.py b/deefuzzer/tools/__init__.py index 3f3aa05..9327c41 100644 --- a/deefuzzer/tools/__init__.py +++ b/deefuzzer/tools/__init__.py @@ -10,3 +10,4 @@ from osc import * from twitt import * from relay import * from tools import * +from streamer import * diff --git a/deefuzzer/tools/player.py b/deefuzzer/tools/player.py index 69b6e15..99de24c 100644 --- a/deefuzzer/tools/player.py +++ b/deefuzzer/tools/player.py @@ -100,3 +100,19 @@ class Player: yield self.sub_chunk self.queue.task_done() self.sub_chunk = 0 + + +class FileReader: + def __init__(self, fp): + self.fp = fp + + def read_callback(self, size): + return self.fp.read(size) + + +class RelayReader: + def __init__(self, relay): + self.relay = urllib.urlopen(relay) + + def read_callback(self, size): + return self.relay.read(size) diff --git a/deefuzzer/tools/relay.py b/deefuzzer/tools/relay.py index 7c77b71..7973db0 100644 --- a/deefuzzer/tools/relay.py +++ b/deefuzzer/tools/relay.py @@ -73,4 +73,3 @@ class Relay(Thread): break - diff --git a/deefuzzer/tools/streamer.py b/deefuzzer/tools/streamer.py new file mode 100644 index 0000000..8454dbb --- /dev/null +++ b/deefuzzer/tools/streamer.py @@ -0,0 +1,80 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2006-2011 Guillaume Pellerin + +# + +# 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 + +from threading import Thread +import pycurl + +class HTTPStreamer(Thread): + + protocol = 'http' + host = str + port = str + mount = str + user = str + password = str + public = str + audio_info = dict + name = str + genre = str + decription = str + format = str + url = str + delay = 0 + + def __init__(self): + Thread.__init__(self) + self.curl = pycurl.Curl() + + def set_callback(self, read_callback): + self.read_callback = read_callback + + def delay(self): + return self.delay + + def open(self): + self.uri = self.protocol + '://' + self.host + ':' + str(self.port) + self.mount + '?' + 'password=' + self.password + + self.curl.setopt(pycurl.URL, self.uri) + self.curl.setopt(pycurl.UPLOAD, 1) + self.curl.setopt(pycurl.READFUNCTION, self.read_callback) + + def run(self): + self.curl.perform() + + def close(self): + self.curl.close() diff --git a/example/deefuzzer.xml b/example/deefuzzer.xml index 02dab96..4b3d889 100644 --- a/example/deefuzzer.xml +++ b/example/deefuzzer.xml @@ -11,6 +11,7 @@ Various Funk Groove + icecast mydomain.com 8000 icecast_source_password diff --git a/example/deefuzzer_doc.xml b/example/deefuzzer_doc.xml index 02d21eb..e6b6455 100644 --- a/example/deefuzzer_doc.xml +++ b/example/deefuzzer_doc.xml @@ -21,6 +21,8 @@ Various Funk Groove + + icecast mydomain.com -- 2.39.5