Skip to content
This repository was archived by the owner on Dec 26, 2019. It is now read-only.

Commit 56ca276

Browse files
author
azlux
committed
Here it is
1 parent 371138f commit 56ca276

4 files changed

Lines changed: 218 additions & 6 deletions

File tree

README.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
# MumbleRadioPlayer
22
A Mumble bot that plays radio stream by URL
33

4-
Time before commit : not long :) (2 days I think)
5-
6-
![Alt progression] (http://progressed.io/bar/70)
4+
![Alt progression] (http://progressed.io/bar/90)
75

86
####What the bot can do
97
commands :
108
- [x] !play
119
- [x] from a list of url
12-
- [ ] from a url
10+
- [x] from a url
1311
- [x] !stop
1412
- [x] !joinme (join the user who speak to me)
15-
- [ ] !kill
16-
- [ ] !oust (stop + go into the default channel)
13+
- [x] !kill
14+
- [x] !oust (stop + go into the default channel)
15+
- [x] !v <number> (change volume with a float number between 0 and 1)
16+
17+
#### info
18+
The bot don't speak, it's not implemented into pymumble yet.
19+
20+
#### TODO
21+
- [ ] Make the bot speak into the channel

configuration.ini

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[server]
2+
host = mumble.crystalyx.net
3+
user = StreamPlayer
4+
channel = Bots
5+
port = 64738
6+
# password = "password_here"
7+
comment = coucou je suis la pour les stream : <br/>!play <stream> avec au choix : luna, radiobrony ou ponyville ou avec une url valide<br/>!joinme - je te rejoins<br/>!stop - j'arrete de chanter
8+
9+
[stream]
10+
ponyville = http://192.99.131.205:8000/stream.mp3
11+
luna = http://radio.ponyvillelive.com:8002/stream
12+
radiobrony = http://62.210.138.34:8000/live
13+
14+
[bot]
15+
allow_new_url = True
16+
volume = 0.1

mumble_stream.py

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
#!/usr/bin/python
2+
import time
3+
import sys
4+
import signal
5+
import ConfigParser
6+
import urllib2
7+
import json
8+
import re
9+
import audioop
10+
11+
import pymumble
12+
import subprocess as sp
13+
14+
15+
class MumblePlayUrlBot:
16+
def __init__(self):
17+
signal.signal(signal.SIGINT, self.ctrl_caught)
18+
19+
self.config = ConfigParser.SafeConfigParser({'password': ''}, allow_no_value=True)
20+
self.config.read("configuration.ini")
21+
22+
host = self.config.get('server', 'host')
23+
user = self.config.get('server', 'user')
24+
user = " StreamPlayer"
25+
port = self.config.getint('server', 'port')
26+
password = self.config.get('server', 'password')
27+
debug = False
28+
29+
self.channel = self.config.get('server', 'channel')
30+
self.volume = self.config.getfloat('bot', 'volume')
31+
32+
self.playing = False
33+
self.exit = False
34+
self.nbexit = 0
35+
self.mumble = pymumble.Mumble(host, user=user, port=port, password=password, debug=debug)
36+
self.mumble.callbacks.set_callback("text_received", self.message_received)
37+
38+
self.mumble.start() # start the mumble thread
39+
self.mumble.is_ready() # wait for the connection
40+
self.set_comment()
41+
self.mumble.users.myself.unmute() # by sure the user is not muted
42+
self.mumble.channels.find_by_name(self.channel).move_in()
43+
self.mumble.set_bandwidth(200000)
44+
self.thread = None
45+
self.loop()
46+
47+
def ctrl_caught(self, signal, frame):
48+
print("\ndeconnection asked")
49+
self.exit = True
50+
if self.nbexit > 2:
51+
print("Forced Quit")
52+
sys.exit(0)
53+
self.nbexit += 1
54+
55+
def message_received(self, texte):
56+
message = texte.message
57+
if message[0] == '!':
58+
message = message[1:].split(' ', 1)
59+
60+
if len(message) > 0:
61+
command = message[0]
62+
parameter = ''
63+
if len(message) > 1:
64+
parameter = message[1]
65+
else:
66+
return
67+
68+
print(command + ' - ' + parameter)
69+
if command == 'play' and parameter:
70+
self.play(parameter)
71+
72+
elif command == 'stop':
73+
self.stop()
74+
75+
elif command == 'kill':
76+
self.exit = True
77+
78+
elif command == 'oust':
79+
self.stop()
80+
self.mumble.channels.find_by_name(self.channel).move_in()
81+
82+
elif command == 'joinme':
83+
self.mumble.users.myself.move_in(self.mumble.users[texte.actor]['channel_id'])
84+
elif command == 'v':
85+
if parameter and parameter.replace('.', '', 1).isdigit() and parameter >= 0 and parameter <=1 :
86+
print("changement de volume")
87+
self.volume = float(parameter)
88+
89+
def play(self, msg):
90+
if self.config.has_option('stream', msg):
91+
url = self.config.get('stream', msg)
92+
self.launch_play(url)
93+
elif get_url(msg):
94+
self.launch_play(get_url(msg))
95+
else:
96+
print("Bad URL")
97+
98+
def launch_play(self, url):
99+
self.stop()
100+
101+
time.sleep(2)
102+
103+
command = ["ffmpeg", '-v', 'warning', '-nostdin', '-i', url, '-ac', '1', '-f', 's16le', '-ar', '48000', '-']
104+
print(command)
105+
self.thread = sp.Popen(command, stdout=sp.PIPE, bufsize=480)
106+
self.set_comment("Stream from %s" % get_server_description(url))
107+
time.sleep(3)
108+
self.playing = True
109+
110+
def loop(self):
111+
while not self.exit:
112+
if self.playing:
113+
while self.mumble.sound_output.get_buffer_size() > 0.5:
114+
time.sleep(0.01)
115+
116+
self.mumble.sound_output.add_sound(audioop.mul(self.thread.stdout.read(480), 2, self.volume))
117+
else:
118+
time.sleep(1)
119+
120+
while self.mumble.sound_output.get_buffer_size() > 0:
121+
time.sleep(0.01)
122+
time.sleep(0.5)
123+
124+
def stop(self):
125+
if self.thread:
126+
self.thread.kill()
127+
self.playing = False
128+
print("Stop")
129+
130+
def set_comment(self, txt=None):
131+
if txt is None:
132+
txt = ""
133+
self.config.get('server', 'comment')
134+
self.mumble.users.myself.comment(txt + "<p /> " + self.config.get('server', 'comment'))
135+
136+
137+
def get_url(url):
138+
if url.startswith('http'):
139+
return url
140+
p = re.compile('href="(.+)"', re.IGNORECASE)
141+
res = re.search(p, url)
142+
if res:
143+
return res.group(1)
144+
else:
145+
return False
146+
147+
148+
def get_server_description(url):
149+
p = re.compile('(https?\:\/\/[^\/]*)', re.IGNORECASE)
150+
res = re.search(p, url)
151+
base_url = res.group(1)
152+
url_icecast = base_url + '/status-json.xsl'
153+
url_shoutcast = base_url + '/stats?json=1'
154+
request = urllib2.Request(url_icecast)
155+
try:
156+
response = urllib2.urlopen(request)
157+
data = json.loads(response.read())
158+
title_server = data['icestats']['source'][0]['server_name'] + ' - ' + data['icestats']['source'][0][
159+
'server_description']
160+
if not title_server:
161+
request = urllib2.Request(url_shoutcast)
162+
response = urllib2.urlopen(request)
163+
data = json.loads(response.read())
164+
title_server = data['servertitle']
165+
if not title_server:
166+
title_server = url
167+
except:
168+
title_server = url
169+
return title_server
170+
171+
172+
def get_title(url):
173+
request = urllib2.Request(url)
174+
try:
175+
request.add_header('Icy-MetaData', 1)
176+
response = urllib2.urlopen(request)
177+
icy_metaint_header = response.headers.get('icy-metaint')
178+
if icy_metaint_header is not None:
179+
metaint = int(icy_metaint_header)
180+
read_buffer = metaint + 255
181+
content = response.read(read_buffer)
182+
title = content[metaint:].split("'")
183+
print(title)
184+
except:
185+
print('Error')
186+
187+
188+
if __name__ == '__main__':
189+
playbot = MumblePlayUrlBot()

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
opuslib==1.1.0
2+
protobuf==2.6.1

0 commit comments

Comments
 (0)