1
# sourceclientgui.py: new for version 0.7 this provides the graphical
2
# user interface for the new improved streaming module
3
# Copyright (C) 2007 Stephen Fairchild (s-fairchild@users.sourceforge.net)
5
# This program is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation, either version 2 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program in the file entitled COPYING.
17
# If not, see <http://www.gnu.org/licenses/>.
19
__all__ = ['SourceClientGui']
31
import xml.dom.minidom as mdom
32
import xml.etree.ElementTree
33
from collections import namedtuple
34
from threading import Thread
40
from idjc import FGlobs, PGlobs
41
from .freefunctions import string_multireplace
42
from .gtkstuff import DefaultEntry, threadslock, HistoryEntry, WindowSizeTracker
43
from .dialogs import *
44
from .irc import IRCPane
45
from .tooltips import main_tips
46
from .prelims import ProfileManager
49
t = gettext.translation(FGlobs.package_name, FGlobs.localedir, fallback=True)
54
set_tip = main_tips.set_tip
57
ENCODER_START=1; ENCODER_STOP=0 # start_stop_encoder constants
59
LISTFORMAT = (("check_stats", bool), ("server_type", int), ("host", str), ("port", int), ("mount", str), ("listeners", int), ("login", str), ("password", str))
60
ListLine = namedtuple("ListLine", " ".join([x[0] for x in LISTFORMAT]))
61
BLANK_LISTLINE = ListLine(1, 0, "", 8000, "", -1, "", "")
64
class SmallLabel(gtk.Label):
65
def __init__(self, text=None):
66
gtk.Label.__init__(self, text)
67
attrlist = pango.AttrList()
68
attrlist.insert(pango.AttrSize(8000, 0, 1000000))
69
self.set_attributes(attrlist)
72
class HistoryEntryWithMenu(HistoryEntry):
74
HistoryEntry.__init__(self, initial_text=("", "%s", "%r - %t"))
75
self.child.connect("populate-popup", self._on_populate_popup)
78
def _on_populate_popup(self, entry, menu):
79
# TC: gtk.Entry popup menu item. Attribute may be one of Artist, Title, Album, and so on.
80
attr_menu_item = gtk.MenuItem(_('Insert Attribute'))
82
attr_menu_item.set_submenu(submenu)
83
for label, subst in zip((_('Artist'), _('Title'), _('Album'), _('Song name')),
84
(u"%r", u"%t", u"%l", u"%s")):
85
mi = gtk.MenuItem(label)
86
mi.connect("activate", self._on_menu_activate, entry, subst)
89
menu.append(attr_menu_item)
90
attr_menu_item.show_all()
93
def _on_menu_activate(self, mi, entry, subst):
94
p = entry.get_position()
95
entry.insert_text(subst, p)
96
entry.set_position(p + len(subst))
99
class ModuleFrame(gtk.Frame):
100
def __init__(self, frametext = None):
101
gtk.Frame.__init__(self, frametext)
102
gtk.Frame.set_shadow_type(self, gtk.SHADOW_ETCHED_OUT)
103
self.vbox = gtk.VBox()
107
class CategoryFrame(gtk.Frame):
108
def __init__(self, frametext = None):
109
gtk.Frame.__init__(self, frametext)
110
gtk.Frame.set_shadow_type(self, gtk.SHADOW_IN)
112
class SubcategoryFrame(gtk.Frame):
113
def __init__(self, frametext = None):
114
gtk.Frame.__init__(self, frametext)
115
gtk.Frame.set_shadow_type(self, gtk.SHADOW_ETCHED_IN)
117
class ConnectionDialog(gtk.Dialog):
118
"""Create new data for or edit an item in the connection table.
120
When an item is selected in the TreeView, will edit, else add.
122
server_types = (_('Icecast 2 Master'), _('Shoutcast Master'),
123
_('Icecast 2 Stats/Relay'), _('Shoutcast Stats/Relay'))
125
def __init__(self, parent_window, tree_selection):
126
gtk.Dialog.__init__(self, _('Enter new server connection details'),
127
parent_window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
128
(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
129
model, iter = tree_selection.get_selected()
131
# Configuration from existing server data.
135
data = BLANK_LISTLINE
137
first = ListLine._make(model[0])
139
pass # Defaults are fine. Server table currently empty.
143
self.set_title(_('Edit existing server connection details') + pm.title_extra)
144
index = model.get_path(iter)[0]
145
data = ListLine._make(model[index])
146
preselect = data.server_type
147
if index and first.server_type < 2:
148
# Editing non first line where a master server is configured.
151
# In adding additional server mode.
152
if first.server_type < 2:
154
preselect = first.server_type + 2
158
liststore = gtk.ListStore(int, str, int)
159
for i, (l, t) in enumerate(zip(self.server_types, (cap_master, cap_master, True, True))):
160
liststore.append((i, l, t))
161
self.servertype = gtk.ComboBox(liststore)
162
icon_renderer = CellRendererXCast()
163
text_renderer = gtk.CellRendererText()
164
self.servertype.pack_start(icon_renderer, False)
165
self.servertype.pack_start(text_renderer, True)
166
self.servertype.set_attributes(icon_renderer, servertype=0, sensitive=2)
167
self.servertype.set_attributes(text_renderer, text=1, sensitive=2)
168
self.servertype.set_model(liststore)
170
self.hostname = DefaultEntry("localhost")
171
adj = gtk.Adjustment(8000.0, 0.0, 65535.0, 1.0, 10.0)
172
self.portnumber = gtk.SpinButton(adj, 1.0, 0)
173
self.mountpoint = DefaultEntry("/listen")
174
self.loginname = DefaultEntry("source")
175
self.password = DefaultEntry("changeme")
176
self.password.set_visibility(False)
177
self.stats = gtk.CheckButton(_('This server is to be scanned for audience figures'))
181
self.set_border_width(5)
182
hbox = gtk.HBox(spacing = 20)
183
hbox.set_border_width(15)
184
icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_DIALOG)
185
hbox.pack_start(icon)
186
col = gtk.VBox(homogeneous = True, spacing = 4)
188
sg = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
189
for text, widget in zip(
190
(_('Server type'), _('Hostname'), _('Port number'),
191
_('Mount point'), _('Login name'), _('Password')),
192
(self.servertype, self.hostname, self.portnumber,
193
self.mountpoint, self.loginname, self.password)):
196
label = gtk.Label(text)
197
label.set_alignment(1.0, 0.5)
198
row.pack_start(label, False)
199
row.pack_start(widget)
202
col.pack_start(self.stats, False)
203
self.get_content_area().pack_start(hbox)
204
self.hostname.set_width_chars(30)
209
self.connect("response", self._on_response, tree_selection, model, iter)
210
self.servertype.connect("changed", self._on_servertype_changed)
214
self.servertype.set_active(preselect)
215
self.hostname.set_text(data.host)
216
self.portnumber.set_value(data.port)
217
self.mountpoint.set_text(data.mount)
218
self.loginname.set_text(data.login)
219
self.password.set_text(data.password)
220
self.stats.set_active(data.check_stats)
223
def _on_response(self, response_id, tree_selection, model, iter):
224
if response_id == gtk.RESPONSE_ACCEPT:
225
for entry in (self.hostname, self.mountpoint, self.loginname, self.password):
226
entry.set_text(entry.get_text().strip())
227
self.hostname.set_text(self.hostname.get_text().split("://")[-1].strip())
228
self.mountpoint.set_text("/" + self.mountpoint.get_text().lstrip("/"))
230
data = ListLine(check_stats=self.stats.get_active(),
231
server_type=self.servertype.get_active(),
232
host=self.hostname.get_text(),
233
port=int(self.portnumber.get_value()),
234
mount=self.mountpoint.get_text(),
236
login=self.loginname.get_text(),
237
password=self.password.get_text())
239
if self.servertype.get_active() < 2:
242
new_iter = model.insert(0, data)
245
new_iter = model.insert_after(iter, data)
248
new_iter = model.append(data)
249
tree_selection.select_path(model.get_path(new_iter))
250
tree_selection.get_tree_view().scroll_to_cell(model.get_path(new_iter))
251
tree_selection.get_tree_view().get_model().row_changed(model.get_path(new_iter), new_iter)
254
def _on_servertype_changed(self, servertype):
255
sens = not (servertype.get_active() & 1)
256
self.mountpoint.set_sensitive(sens)
257
self.loginname.set_sensitive(sens)
259
class StatsThread(Thread):
260
def __init__(self, d):
261
Thread.__init__(self)
262
self.is_shoutcast = d["server_type"] % 2
263
self.host = d["host"]
264
self.port = d["port"]
265
self.mount = d["mount"]
266
if self.is_shoutcast:
269
self.login = d["login"]
270
self.passwd = d["password"]
271
self.listeners = -2 # preset error code for failed/timeout
272
self.url = "http://%s:%d%s" % (self.host, self.port, self.mount)
274
class BadXML(ValueError):
277
hostport = "%s:%d" % (self.host, self.port)
278
if self.is_shoutcast:
279
stats_url = "http://%s/admin.cgi?mode=viewxml" % hostport
280
realm = "Shoutcast Server"
282
stats_url = "http://%s/admin/listclients?mount=%s" % (hostport, self.mount)
283
realm = "Icecast2 Server"
284
auth_handler = urllib2.HTTPBasicAuthHandler()
285
auth_handler.add_password(realm, hostport, self.login, self.passwd)
286
opener = urllib2.build_opener(auth_handler)
287
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
290
f = opener.open(stats_url)
293
print "failed to get server stats for", self.url
297
dom = mdom.parseString(xmlfeed)
299
print "failed to parse server stats for", self.url
303
if self.is_shoutcast:
304
if dom.documentElement.tagName == u'SHOUTCASTSERVER':
305
shoutcastserver = dom.documentElement
308
currentlisteners = shoutcastserver.getElementsByTagName('CURRENTLISTENERS')
310
self.listeners = int(currentlisteners[0].firstChild.wholeText.strip())
314
if dom.documentElement.tagName == u'icestats':
315
icestats = dom.documentElement
318
sources = icestats.getElementsByTagName('source')
319
for source in sources:
320
mount = source.getAttribute('mount')
321
if stats_url.endswith(mount):
322
listeners = source.getElementsByTagName('Listeners')
324
self.listeners = int(listeners[0].firstChild.wholeText.strip())
331
print "Unexpected data in server stats XML file"
333
print "server", self.url, "has", self.listeners, "listeners"
335
class ActionTimer(object):
340
if self.n == self.ticks:
343
def __init__(self, ticks, first, last):
350
class CellRendererXCast(gtk.CellRendererText):
351
icons = ("<span foreground='#0077FF'>■</span>",
352
"<span foreground='orange'>■</span>",
353
"<span foreground='#0077FF'>▴</span>",
354
"<span foreground='orange'>▴</span>")
356
ins_icons = ("<span foreground='#CCCCCC'>■</span>",
357
"<span foreground='#CCCCCC'>■</span>",
358
"<span foreground='#CCCCCC'>▴</span>",
359
"<span foreground='#CCCCCC'>▴</span>")
362
'servertype' : (gobject.TYPE_INT,
364
'indication by number of the server in use',
365
0, 3, 0, gobject.PARAM_READWRITE),
366
'sensitive' : (gobject.TYPE_BOOLEAN,
368
'indication of selectability',
369
1, gobject.PARAM_READWRITE)
373
gtk.CellRendererText.__init__(self)
376
self.props.xalign = 0.5
377
self.props.family = "monospace"
379
def do_get_property(self, property):
380
if property.name == 'servertype':
381
return self._servertype
382
elif property.name == 'sensitive':
383
return self._sensitive
387
def do_set_property(self, property, value):
388
if property.name == 'servertype':
389
self._servertype = value
390
elif property.name == 'sensitive':
391
self._sensitive = value
396
self.props.markup = self.icons[self._servertype]
398
self.props.markup = self.ins_icons[self._servertype]
401
class ConnectionPane(gtk.VBox):
402
def get_master_server_type(self):
404
s_type = ListLine(*self.liststore[0]).server_type
407
return 0 if s_type >= 2 else s_type + 1
409
def set_button(self, tab):
410
if self.get_master_server_type():
411
config = ListLine(*self.liststore[0])
412
text = "{0.host}:{0.port}{0.mount}".format(config)
413
tab.server_connect_label.set_text(text)
415
# TC: The connect button text when no connection details have been entered.
416
tab.server_connect_label.set_text(_('No Master Server Configured'))
418
def individual_listeners_toggle_cb(self, cell, path):
419
self.liststore[path][0] = not self.liststore[path][0]
421
def listeners_renderer_cb(self, column, cell, model, iter):
422
listeners = model.get_value(iter, 5)
424
cell.set_property("text", "")
425
cell.set_property("xalign", 0.5)
426
elif listeners == -2:
427
cell.set_property("text", u"\u2049")
428
cell.set_property("xalign", 0.5)
430
cell.set_property("text", listeners)
431
cell.set_property("xalign", 1.0)
433
def master_is_set(self):
434
return bool(self.get_master_server_type())
436
def streaming_set(self, val):
437
self._streaming_set = val
439
def streaming_is_set(self):
440
return self._streaming_set
442
def row_to_dict(self, rownum):
443
""" obtain a dictionary of server data for a specified row """
445
return ListLine._make(self.liststore[rownum])._asdict()
447
def dict_to_row(self, _dict):
448
""" append a row of server data from a dictionary """
450
_dict["listeners"] = -1
451
row = ListLine(**_dict)
453
if t < 2: # check if first line contains master server info
454
self.liststore.insert(0, row)
456
self.liststore.append(row)
461
template = ("<%s dtype=\"int\">%d</%s>", "<%s dtype=\"str\">%s</%s>")
462
for i in range(len(self.liststore)):
463
s = self.row_to_dict(i)
465
s["password"] = base64.encodestring(s["password"])
467
for key, value in s.iteritems():
468
if type(value) == str:
470
value = urllib.quote(value)
473
d.append(t % (key, value, key))
474
server.append("".join(("<server>", "".join(d), "</server>")))
475
return "<connections>%s</connections>" % "".join(server)
477
def loader(self, xmldata):
478
def get_child_text(nodelist):
480
for node in nodelist:
481
if node.nodeType == node.TEXT_NODE:
488
dom = mdom.parseString(xmldata)
490
print "ConnectionPane.loader: failed to parse xml data...\n", xmldata
492
assert(dom.documentElement.tagName == "connections")
493
for server in dom.getElementsByTagName("server"):
495
for node in server.childNodes:
496
key = str(node.tagName)
497
dtype = node.getAttribute("dtype")
498
raw = get_child_text(node.childNodes)
500
value = urllib.unquote(raw)
504
raise ValueError("ConnectionPane.loader: dtype (%s) is unhandled" % dtype)
507
d["password"] = base64.decodestring(d["password"])
513
self.treeview.get_selection().select_path(0)
515
def stats_commence(self):
517
getstats = self.stats_always.get_active() or (self.stats_ifconnected.get_active() and self.streaming_is_set())
518
for i, row in enumerate(self.liststore):
519
if row[0] and getstats:
520
d = self.row_to_dict(i)
521
if d["server_type"] == 1:
522
ap = self.tab.admin_password_entry.get_text().strip()
525
stats_thread = StatsThread(d)
527
ref = gtk.TreeRowReference(self.liststore, i)
528
self.stats_rows.append((ref, stats_thread))
530
row[5] = -1 # sets listeners text to 'unknown'
532
def stats_collate(self):
534
for ref, thread in self.stats_rows:
535
if ref.valid() == False:
536
print "stats_collate:", thread.url, "invalidated by its removal from the stats list"
538
row = ref.get_model()[ref.get_path()[0]]
539
row[5] = thread.listeners
540
if thread.listeners > 0:
541
count += thread.listeners
542
self.listeners_display.set_text(str(count))
543
self.listeners = count
545
def on_dialog_destroy(self, dialog, tree_selection, old_iter):
546
model, iter = tree_selection.get_selected()
547
if iter is None and old_iter is not None:
548
tree_selection.select_iter(old_iter)
550
def on_new_clicked(self, button, tree_selection):
551
old_iter = tree_selection.get_selected()[1]
552
tree_selection.unselect_all()
553
self.connection_dialog = ConnectionDialog(self.tab.scg.window, tree_selection)
554
self.connection_dialog.connect("destroy", self.on_dialog_destroy, tree_selection, old_iter)
555
self.connection_dialog.show()
557
def on_edit_clicked(self, button, tree_selection):
558
model, iter = tree_selection.get_selected()
560
self.connection_dialog = ConnectionDialog(self.tab.scg.window, tree_selection)
561
self.connection_dialog.show()
563
print "nothing selected for edit"
565
def on_remove_clicked(self, button, tree_selection):
566
model, iter = tree_selection.get_selected()
568
if model.remove(iter):
569
tree_selection.select_iter(iter)
571
print "nothing selected for removal"
573
def on_keypress(self, widget, event):
574
if gtk.gdk.keyval_name(event.keyval) == "Delete":
575
self.remove.clicked()
577
def on_selection_changed(self, tree_selection):
578
sens = tree_selection.get_selected()[1] is not None
579
for button in self.require_selection:
580
button.set_sensitive(sens)
582
def __init__(self, set_tip, tab):
584
gtk.VBox.__init__(self)
585
self.streaming_set(False)
587
vbox.set_border_width(6)
591
scrolled = gtk.ScrolledWindow()
592
scrolled.set_shadow_type(gtk.SHADOW_ETCHED_IN)
593
scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
594
vbox.pack_start(scrolled, True)
596
self.liststore = gtk.ListStore(*[x[1] for x in LISTFORMAT])
597
self.liststore.connect("row-deleted", lambda x, y: self.set_button(tab))
598
self.liststore.connect("row-changed", lambda x, y, z: self.set_button(tab))
600
self.treeview = gtk.TreeView(self.liststore)
601
set_tip(self.treeview, _('A table of servers with which to connect. Only one master server can be added for the purpose of streaming. All other servers will appear below the master server in the list for the purpose of stats collection which can be toggled on a per server basis.'))
602
self.treeview.set_enable_search(False)
603
self.treeview.connect("key-press-event", self.on_keypress)
605
rend_type = CellRendererXCast()
606
rend_type.set_property("xalign", 0.5)
607
col_type = gtk.TreeViewColumn("", rend_type, servertype = 1)
608
col_type.set_sizing = gtk.TREE_VIEW_COLUMN_AUTOSIZE
609
col_type.set_alignment(0.5)
610
self.treeview.append_column(col_type)
611
text_cell_rend = gtk.CellRendererText()
612
text_cell_rend.set_property("ellipsize", pango.ELLIPSIZE_END)
613
col_host = gtk.TreeViewColumn(_('Hostname/IP address'), text_cell_rend, text=2)
614
col_host.set_sizing = gtk.TREE_VIEW_COLUMN_FIXED
615
col_host.set_expand(True)
616
self.treeview.append_column(col_host)
617
rend_port = gtk.CellRendererText()
618
rend_port.set_property("xalign", 1.0)
619
# TC: TCP port number.
620
col_port = gtk.TreeViewColumn(_('Port'), rend_port, text = 3)
621
col_port.set_sizing = gtk.TREE_VIEW_COLUMN_AUTOSIZE
622
col_port.set_alignment(0.5)
623
self.treeview.append_column(col_port)
624
# TC: Mount point is a technical term in relation to icecast servers.
625
col_mount = gtk.TreeViewColumn(_('Mount point '), text_cell_rend, text=4)
626
col_mount.set_sizing = gtk.TREE_VIEW_COLUMN_AUTOSIZE
627
self.treeview.append_column(col_mount)
629
rend_enabled = gtk.CellRendererToggle()
630
rend_enabled.connect("toggled", self.individual_listeners_toggle_cb)
631
rend_listeners = gtk.CellRendererText()
632
# TC: This is the listener count heading.
633
col_listeners = gtk.TreeViewColumn(_('Listeners'))
634
col_listeners.set_sizing = gtk.TREE_VIEW_COLUMN_AUTOSIZE
635
col_listeners.pack_start(rend_enabled, False)
636
col_listeners.pack_start(rend_listeners)
637
col_listeners.add_attribute(rend_enabled, "active", 0)
638
col_listeners.set_cell_data_func(rend_listeners, self.listeners_renderer_cb)
639
self.treeview.append_column(col_listeners)
640
scrolled.add(self.treeview)
645
self.listener_count_button = gtk.Button()
647
set_tip(ihbox, _('The sum total of listeners in this server tab.'))
648
pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(FGlobs.pkgdatadir / "listenerphones.png", 20, 16)
649
image = gtk.image_new_from_pixbuf(pixbuf)
650
ihbox.pack_start(image, False, False, 0)
653
frame.set_border_width(0)
654
ihbox.pack_start(frame, True, True, 0)
657
self.listeners_display = gtk.Label("0")
658
self.listeners_display.set_alignment(1.0, 0.5)
659
self.listeners_display.set_width_chars(6)
660
self.listeners_display.set_padding(3, 0)
661
frame.add(self.listeners_display)
662
self.listeners_display.show()
663
self.listener_count_button.add(ihbox)
664
hbox.pack_start(self.listener_count_button, False)
667
self.listener_count_button.connect("button-press-event",
668
lambda w, e: lcmenu.popup(None, None, None, e.button, e.time))
669
lc_stats = gtk.MenuItem("Update")
670
lcmenu.append(lc_stats)
671
lcsubmenu = gtk.Menu()
672
lc_stats.set_submenu(lcsubmenu)
673
self.stats_never = gtk.RadioMenuItem(None, _('Never'))
674
self.stats_never.connect("toggled", lambda w: ihbox.set_sensitive(not w.get_active()))
675
self.stats_always = gtk.RadioMenuItem(self.stats_never, _('Always'))
676
self.stats_ifconnected = gtk.RadioMenuItem(self.stats_never, _('If connected'))
677
self.stats_ifconnected.set_active(True)
678
lcsubmenu.append(self.stats_never)
679
lcsubmenu.append(self.stats_always)
680
lcsubmenu.append(self.stats_ifconnected)
683
bbox = gtk.HButtonBox()
685
bbox.set_layout(gtk.BUTTONBOX_END)
686
new = gtk.Button("New")
687
self.remove = gtk.Button("Remove")
688
edit = gtk.Button("Edit")
690
bbox.add(self.remove)
692
self.require_selection = (edit, self.remove)
693
selection = self.treeview.get_selection()
694
selection.connect("changed", self.on_selection_changed)
695
selection.emit("changed")
696
new.connect("clicked", self.on_new_clicked, selection)
697
edit.connect("clicked", self.on_edit_clicked, selection)
698
self.remove.connect("clicked", self.on_remove_clicked, selection)
699
self.require_selection = (self.remove, edit)
700
hbox.pack_start(bbox)
701
vbox.pack_start(hbox, False)
703
self.timer = ActionTimer(40, self.stats_commence, self.stats_collate)
705
class TimeEntry(gtk.HBox): # A 24-hour-time entry widget with a checkbutton
706
def time_valid(self):
707
return self.seconds_past_midnight >= 0
708
def get_seconds_past_midnight(self):
709
return self.seconds_past_midnight
710
def set_active(self, boolean):
711
self.check.set_active(boolean and True or False)
712
def get_active(self):
713
return self.check.get_active() and self.time_valid
714
def __entry_activate(self, widget):
715
boolean = widget.get_active()
716
self.entry.set_sensitive(boolean)
718
self.entry.grab_focus()
719
def __key_validator(self, widget, event):
720
if event.keyval < 128:
721
if event.string == ":":
723
if event.string < "0" or event.string > "9":
725
def __time_updater(self, widget):
726
text = widget.get_text()
727
if len(text) == 5 and text[2] == ":":
729
hours = int(text[:2])
730
minutes = int(text[3:])
732
self.seconds_past_midnight = -1
734
if hours >= 0 and hours <=23 and minutes >= 0 and minutes <= 59:
735
self.seconds_past_midnight = hours * 3600 + minutes * 60
737
self.seconds_past_midnight = -1
739
self.seconds_past_midnight = -1
740
def __init__(self, labeltext):
741
gtk.HBox.__init__(self)
743
self.check = gtk.CheckButton(labeltext)
744
self.check.connect("toggled", self.__entry_activate)
745
self.pack_start(self.check, False)
747
self.entry = gtk.Entry(5)
748
self.entry.set_sensitive(False)
749
self.entry.set_width_chars(5)
750
self.entry.set_text("00:00")
751
self.entry.connect("key-press-event", self.__key_validator)
752
self.entry.connect("changed", self.__time_updater)
753
self.pack_start(self.entry, False)
755
self.seconds_past_midnight = -1
757
class AutoAction(gtk.HBox): # widget consiting of a check button and several radio buttons
758
def activate(self): # radio buttons linked to actions to be performed on the activate method
759
if self.get_active(): # all radio buttons exist in the same radio group so only one action is
760
for radio, action in self.action_lookup: # performed
761
if radio.get_active():
763
def get_active(self):
764
return self.check_button.get_active()
765
def set_active(self, boolean):
766
self.check_button.set_active(boolean)
767
def get_radio_index(self):
768
return self.radio_active
769
def set_radio_index(self, value):
771
self.action_lookup[value][0].clicked()
774
self.action_lookup[0][0].clicked()
777
def __set_sensitive(self, widget):
778
boolean = widget.get_active()
779
for radio, action in self.action_lookup:
780
radio.set_sensitive(boolean)
781
def __handle_radioclick(self, widget, which):
782
if widget.get_active():
783
self.radio_active = which
784
def __init__(self, labeltext, names_actions):
785
gtk.HBox.__init__(self)
786
self.radio_active = 0
787
self.check_button = gtk.CheckButton(labeltext)
789
self.pack_start(self.check_button, False, False, 0)
790
self.check_button.show()
792
self.action_lookup = []
793
for index, (name, action) in enumerate(names_actions):
794
radio = gtk.RadioButton(lastradio, name)
795
radio.connect("clicked", self.__handle_radioclick, index)
797
radio.set_sensitive(False)
798
self.check_button.connect("toggled", self.__set_sensitive)
799
self.pack_start(radio, False, False, 0)
801
self.action_lookup.append((radio, action))
803
class FramedSpin(gtk.Frame):
804
"""A framed spin button that can be disabled"""
806
if self.check.get_active():
807
return self.spin.get_value()
810
def get_cooked_value(self):
811
if self.check.get_active():
812
return self.spin.get_value() * self.adj_basis.get_value() / 100
815
def set_value(self, new_value):
816
self.spin.set_value(new_value)
817
def cb_toggled(self, widget):
818
self.spin.set_sensitive(widget.get_active())
819
def __init__(self, text, adj, adj_basis):
820
self.adj_basis = adj_basis
821
gtk.Frame.__init__(self)
822
self.check = gtk.CheckButton(text)
824
hbox.pack_start(self.check, False, False, 2)
826
self.set_label_widget(hbox)
829
vbox.set_border_width(2)
830
self.spin = gtk.SpinButton(adj)
833
self.spin.set_sensitive(False)
836
self.check.connect("toggled", self.cb_toggled)
838
class SimpleFramedSpin(gtk.Frame):
839
"""A framed spin button"""
841
if self.check.get_active():
842
return self.spin.get_value()
845
def set_value(self, new_value):
846
self.spin.set_value(new_value)
847
def __init__(self, text, adj):
848
gtk.Frame.__init__(self)
849
label = gtk.Label(text)
851
hbox.pack_start(label, False, False, 3)
853
self.set_label_widget(hbox)
856
vbox.set_border_width(2)
857
self.spin = gtk.SpinButton(adj)
864
def show_indicator(self, colour):
865
thematch = self.indicator_lookup[colour]
867
for colour, indicator in self.indicator_lookup.iteritems():
868
if indicator is not thematch:
870
def send(self, stringtosend):
871
self.source_client_gui.send("tab_id=%d\n%s" % (self.numeric_id, stringtosend))
873
return self.source_client_gui.receive()
874
def __init__(self, scg, numeric_id, indicator_lookup):
875
self.indicator_lookup = indicator_lookup
876
self.numeric_id = numeric_id
877
self.source_client_gui = scg
878
gtk.VBox.__init__(self)
879
gtk.VBox.set_border_width(self, 8)
884
class Troubleshooting(gtk.VBox):
886
gtk.VBox.__init__(self)
887
self.set_border_width(6)
892
# TC: user agents are strings that internet clients use to identify themselves to a server.
893
# TC: typically application name, version, maybe a capabilities list.
894
self.custom_user_agent = gtk.CheckButton(_("Custom user agent string"))
895
self.custom_user_agent.connect("toggled", self._on_custom_user_agent)
896
hbox.pack_start(self.custom_user_agent, False)
897
self.user_agent_entry = HistoryEntry()
898
self.user_agent_entry.set_sensitive(False)
899
hbox.pack_start(self.user_agent_entry)
900
self.pack_start(hbox, False)
901
set_tip(hbox, _("Set this on the occasion that the server or its firewall specifically refuses to allow libshout based clients."))
904
self.automatic_reconnection = gtk.CheckButton(_("If the connection breaks reconnect to the server"))
905
self.automatic_reconnection.set_active(True)
906
frame.set_label_widget(self.automatic_reconnection)
907
self.pack_start(frame, False)
909
reconbox = gtk.HBox()
910
reconbox.set_border_width(6)
911
reconbox.set_spacing(4)
913
# TC: Label for a comma separated list of delay times.
914
reconlabel = gtk.Label(_("Delay times"))
915
reconbox.pack_start(reconlabel, False)
916
self.reconnection_times = HistoryEntry(initial_text=("10,10,60", "5"), store_blank=False)
917
set_tip(self.reconnection_times, _("A comma separated list of delays in seconds between reconnection attempts. Note that bad values or values less than 5 will be interpreted as 5."))
918
reconbox.pack_start(self.reconnection_times, True)
919
# TC: A user specifed sequence is to be allowed to repeat itself indefinitely.
920
self.reconnection_repeat = gtk.CheckButton(_("Repeat"))
921
set_tip(self.reconnection_repeat, _("Repeat the sequence of delays indefinitely."))
922
reconbox.pack_start(self.reconnection_repeat, False)
923
# TC: User specifies no dialog box to be shown.
924
self.reconnection_quiet = gtk.CheckButton(_("Quiet"))
925
set_tip(self.reconnection_quiet, _("Keep the reconnection dialogue box hidden at all times."))
926
reconbox.pack_start(self.reconnection_quiet, False)
927
self.automatic_reconnection.connect("toggled", self._on_automatic_reconnection, reconbox)
929
frame = gtk.Frame(" %s " % _("The contingency plan upon the stream buffer becoming full is..."))
931
sbfbox.set_border_width(6)
932
sbfbox.set_spacing(1)
934
self.pack_start(frame, False)
936
self.sbf_discard_audio = gtk.RadioButton(None, _("Discard audio data for as long as needed."))
937
self.sbf_reconnect = gtk.RadioButton(self.sbf_discard_audio, _("Assume the connection is beyond saving and reconnect."))
938
for each in (self.sbf_discard_audio, self.sbf_reconnect):
939
sbfbox.pack_start(each, True, False)
943
self.objects = {"custom_user_agent": (self.custom_user_agent, "active"),
944
"user_agent_entry": (self.user_agent_entry, "history"),
945
"automatic_reconnection": (self.automatic_reconnection, "active"),
946
"reconnection_times": (self.reconnection_times, "history"),
947
"reconnection_repeat": (self.reconnection_repeat, "active"),
948
"reconnection_quiet": (self.reconnection_quiet, "active"),
949
"sbf_reconnect": (self.sbf_reconnect, "active"),
953
def _on_custom_user_agent(self, widget):
954
self.user_agent_entry.set_sensitive(widget.get_active())
957
def _on_automatic_reconnection(self, widget, reconbox):
958
reconbox.set_sensitive(widget.get_active())
962
class StreamTab(Tab):
963
class ResampleFrame(SubcategoryFrame):
964
def cb_eval(self, widget, data = None):
966
if widget.get_active():
967
self.extraction_method = data
970
if self.extraction_method == "no_resample":
971
self.resample_rate = self.jack_sample_rate
972
elif self.extraction_method == "standard":
973
self.resample_rate = int(self.resample_rate_combo_box.get_active_text())
975
self.resample_rate = int(self.resample_rate_spin_adj.get_value())
976
self.resample_quality = ("highest", "high", "fast", "fastest")[self.resample_quality_combo_box.get_active()]
977
self.mp3_compatible = self.resample_rate in self.mp3_samplerates
978
self.parentobject.mp3_dummy_object.clicked() # update mp3 pane
979
self.parentobject.vorbis_dummy_object.clicked()
980
def __init__(self, parent, sizegroup):
981
self.parentobject = parent
982
self.jack_sample_rate = parent.source_client_gui.jack_sample_rate
983
self.resample_rate = self.jack_sample_rate
984
self.extraction_method = "no_resample"
985
self.mp3_compatible = True
986
SubcategoryFrame.__init__(self, " %s " % _('Sample rate'))
987
self.resample_no_resample, self.resample_standard, self.resample_custom = self.parentobject.make_radio(3)
988
self.resample_no_resample.connect("clicked", self.cb_eval, "no_resample")
989
self.resample_standard.connect("clicked", self.cb_eval, "standard")
990
self.resample_custom.connect("clicked", self.cb_eval, "custom")
991
no_resample_label = gtk.Label(_('Use JACK sample rate'))
992
self.mp3_samplerates = (48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000)
993
self.resample_rate_combo_box = self.parentobject.make_combo_box(map(str, self.mp3_samplerates))
994
self.resample_rate_combo_box.set_active(1)
995
self.resample_rate_combo_box.connect("changed", self.cb_eval)
996
self.resample_rate_spin_adj = gtk.Adjustment(44100, 4000, 190000, 10, 100, 0)
997
self.resample_rate_spin_control = gtk.SpinButton(self.resample_rate_spin_adj, 0, 0)
998
self.resample_rate_spin_control.connect("value-changed", self.cb_eval)
999
resample_quality_label = gtk.Label(_('Quality'))
1000
self.resample_quality_combo_box = self.parentobject.make_combo_box((_('Highest'),
1001
_('Good'), _('Fast'), _('Fastest')))
1002
self.resample_quality_combo_box.set_active(2)
1003
self.resample_quality_combo_box.connect("changed", self.cb_eval)
1004
self.resample_dummy_object = gtk.Button()
1005
self.resample_dummy_object.connect("clicked", self.cb_eval)
1006
sample_rate_pane = self.parentobject.item_item_layout(((self.resample_no_resample, no_resample_label),
1007
(self.resample_standard, self.resample_rate_combo_box),
1008
(self.resample_custom, self.resample_rate_spin_control),
1009
(resample_quality_label, self.resample_quality_combo_box)), sizegroup)
1010
sample_rate_pane.set_border_width(10)
1011
self.add(sample_rate_pane)
1012
sample_rate_pane.show()
1013
set_tip(self.resample_no_resample.get_parent(), _('No additional resampling will occur. The stream sample rate will be that of the JACK sound server.'))
1014
set_tip(self.resample_standard.get_parent(), _('Use one of the standard mp3 sample rates for the stream.'))
1015
set_tip(self.resample_custom.get_parent(), _('Complete sample rate freedom. Note that only sample rates that appear in the drop down box can be used with an mp3 stream.'))
1016
set_tip(self.resample_quality_combo_box.get_parent(), _('This selects the audio resampling method to be used, efficiency versus quality. Highest mode offers the best sound quality but also uses the most CPU (not recommended for systems built before 2006). Fastest mode while it uses by far the least amount of CPU should be avoided if at all possible.'))
1017
def make_combo_box(self, items):
1018
combobox = gtk.combo_box_new_text()
1020
combobox.append_text(each)
1022
def make_radio(self, qty):
1023
listofradiobuttons = []
1024
for iteration in range(qty):
1025
listofradiobuttons.append(gtk.RadioButton())
1027
listofradiobuttons[iteration].set_group(listofradiobuttons[0])
1028
return listofradiobuttons
1029
def make_radio_with_text(self, labels):
1030
listofradiobuttons = []
1031
for count, label in enumerate(labels):
1032
listofradiobuttons.append(gtk.RadioButton(None, label))
1034
listofradiobuttons[count].set_group(listofradiobuttons[0])
1035
return listofradiobuttons
1036
def make_notebook_tab(self, notebook, labeltext, tooltip = None):
1037
label = gtk.Label(labeltext)
1038
if tooltip is not None:
1039
set_tip(label, tooltip)
1041
notebook.append_page(vbox, label)
1045
def item_item_layout(self, item_item_pairs, sizegroup):
1048
for left, right in item_item_pairs:
1050
sizegroup.add_widget(hbox)
1052
if left is not None:
1053
hbox.pack_start(left, False, False, 0)
1055
if right is not None:
1056
hbox.pack_start(right, True, True, 0)
1058
vbox.pack_start(hbox, False, False, 0)
1061
def item_item_layout2(self, item_item_pairs, sizegroup):
1062
rhs_size = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
1065
for left, right in item_item_pairs:
1067
rhs_size.add_widget(left)
1068
sizegroup.add_widget(hbox)
1070
hbox.pack_start(left, False, False, 0)
1072
if right is not None:
1073
rhs_size.add_widget(right)
1074
hbox.pack_end(right, False, False, 0)
1076
vbox.pack_start(hbox, False, False, 0)
1079
def item_item_layout3(self, leftitems, rightitems):
1082
outer.pack_start(wedge, False, False, 2)
1084
outer.pack_end(wedge, False, False, 2)
1087
outer.pack_start(lh, True, False, 0)
1088
outer.pack_start(rh, True, False, 0)
1091
lh.pack_start(lv, False, False, 0)
1092
rh.pack_start(rv, False, False, 0)
1093
lframe = gtk.Frame()
1094
lframe.set_shadow_type(gtk.SHADOW_OUT)
1095
rframe = gtk.Frame()
1096
rframe.set_shadow_type(gtk.SHADOW_OUT)
1097
lv.pack_start(lframe, True, False, 0)
1098
rv.pack_start(rframe, True, False, 0)
1100
lvi.set_border_width(5)
1103
rvi.set_border_width(5)
1107
for item in leftitems:
1108
lvi.pack_start(item, True, False, 0)
1109
for item in rightitems:
1110
rvi.pack_start(item, True, False, 0)
1112
def label_item_layout(self, label_item_pairs, sizegroup): # align vertical colums of label : item
1113
hbox = gtk.HBox() # label is right justified and narrow as possible
1114
vbox_left = gtk.VBox() # the item widget is free to expand
1115
vbox_left.set_spacing(1)
1116
vbox_right = gtk.VBox()
1117
vbox_right.set_spacing(1)
1118
hbox.pack_start(vbox_left, False, False, 0)
1119
hbox.pack_start(vbox_right, True, True, 0)
1121
for text, item in label_item_pairs:
1122
if text is not None:
1123
labelbox = gtk.HBox()
1124
if type(text) == str:
1125
label = gtk.Label(text)
1128
sizegroup.add_widget(label)
1129
labelbox.pack_end(label, False, False)
1131
vbox_left.pack_start(labelbox, False, False, 0)
1133
itembox = gtk.HBox()
1134
sizegroup.add_widget(itembox)
1137
vbox_right.pack_start(itembox, False, False, 0)
1142
def send(self, string_to_send):
1143
Tab.send(self, "dev_type=streamer\n" + string_to_send)
1145
return Tab.receive(self)
1146
def cb_servertype(self, widget):
1147
sens = bool(widget.get_active())
1148
for each in (self.mount_entry, self.login_entry):
1149
each.set_sensitive(sens)
1150
self.update_sensitives()
1151
def update_sensitives(self, *params):
1152
if self.encoder == "off":
1153
self.update_button.set_sensitive(False)
1154
mode = self.connection_pane.get_master_server_type() # 0 = none, 1 = icecast2, 2 = shoutcast
1155
self.recorder_valid_override = False
1157
if self.encoder == "ogg":
1158
self.server_connect.set_sensitive(mode == 1 or self.server_connect.get_active())
1159
if self.format_page == 0:
1160
self.update_button.set_sensitive(False)
1161
elif self.format_page == 1:
1162
self.update_button.set_sensitive(self.vorbis_settings_valid)
1163
elif self.format_page == 2:
1165
self.update_button.set_sensitive(self.file_dialog.get_filename().lower().endswith(".ogg"))
1166
except AttributeError:
1167
self.update_button.set_sensitive(False)
1169
print "update_sensitives: unhandled format page"
1170
elif self.encoder == "mp3":
1171
self.server_connect.set_sensitive(mode != 0 or self.server_connect.get_active())
1172
if self.format_page == 0:
1173
self.update_button.set_sensitive(self.mp3_compatibility != "s-rate!")
1174
elif self.format_page == 1:
1175
self.update_button.set_sensitive(False)
1176
elif self.format_page == 2:
1178
self.update_button.set_sensitive(self.file_dialog.get_filename().lower().endswith(".mp3"))
1179
except AttributeError:
1180
self.update_button.set_sensitive(False)
1182
print "update_sensitives: unhandled format page"
1183
elif self.encoder == "off":
1184
self.test_monitor.set_sensitive(True)
1185
if self.format_page == 0:
1186
self.recorder_valid_override = sens = bool(self.mp3_compatibility != "s-rate!" and lameenabled)
1187
sens = sens and mode
1188
self.server_connect.set_sensitive(sens)
1189
self.test_monitor.set_sensitive(sens)
1190
elif self.format_page == 1:
1191
if self.subformat_page == 0:
1192
self.recorder_valid_override = sens = self.vorbis_settings_valid
1193
elif self.subformat_page == 1: # OggFLAC
1194
sr = self.stream_resample_frame.resample_rate
1195
self.recorder_valid_override = sens = sr <= 65535 or sr % 10 == 0
1196
elif self.subformat_page == 2: # Speex
1197
self.recorder_valid_override = sens = True # True always for now
1198
self.server_connect.set_sensitive(sens and mode == 1)
1199
self.test_monitor.set_sensitive(sens)
1201
record_tabs = self.source_client_gui.recordtabframe.tabs
1203
pass # this will be called inevitably before recordtabframe has been created yet
1205
for rectab in record_tabs:
1206
rectab.source_dest.source_combo.emit("changed") # update sensitivity on record buttons
1207
if self.encoder != "off":
1208
if self.format_page == 0:
1209
if self.encoder == "ogg" or self.mp3_compatibility == "s-rate!":
1210
self.update_button.set_sensitive(False)
1211
if self.format_page == 1 and self.encoder == "mp3":
1212
self.update_button.set_sensitive(False)
1214
def cb_file_dialog_response(self, widget, response_id):
1215
self.update_sensitives()
1216
def cb_format_notebook(self, widget, page, page_num):
1217
if self.format_page != page_num:
1218
self.format_page = page_num
1219
self.update_sensitives()
1220
def cb_subformat_notebook(self, widget, page, page_num):
1221
if self.subformat_page != page_num:
1222
self.subformat_page = page_num
1223
self.update_sensitives()
1224
def cb_mp3tab(self, widget, data = None):
1225
if data == "standard" or data == "custom":
1226
if widget.get_active():
1227
self.mp3_bitrate_widget = data
1230
self.mp3_stereo_type = ("stereo", "mono", "jstereo")[self.mp3_stereo_combo_box.get_active()]
1231
self.mp3_encode_quality = self.mp3_encoding_quality_combo_box.get_active_text()
1232
if self.mp3_bitrate_widget == "standard":
1233
self.mp3_bitrate = int(self.mp3_bitrate_combo_box.get_active_text())
1234
elif self.mp3_bitrate_widget == "custom":
1235
self.mp3_bitrate = int(self.mp3_bitrate_spin_adj.get_value())
1236
self.mp3_standard_bitrate = self.mp3_bitrate in self.mp3_standard_bitrates
1237
self.mp3_samplerate = self.stream_resample_frame.resample_rate
1238
self.mp3_resample_compatible = self.stream_resample_frame.mp3_compatible
1239
self.mp3_compatibility = "freeformat"
1240
if not self.mp3_resample_compatible:
1241
self.mp3_compatibility = "s-rate!"
1243
if self.mpeg_std_search(self.mp3_bitrate, self.mp3_samplerate, self.mp3_mpeg2_5_bitrates_samplerates):
1244
self.mp3_compatibility = "mpeg 2.5"
1245
if self.mpeg_std_search(self.mp3_bitrate, self.mp3_samplerate, self.mp3_mpeg2_bitrates_samplerates):
1246
self.mp3_compatibility = "mpeg 2"
1247
if self.mpeg_std_search(self.mp3_bitrate, self.mp3_samplerate, self.mp3_mpeg1_bitrates_samplerates):
1248
self.mp3_compatibility = "mpeg 1"
1249
self.mp3_compatibility_status.push(1, self.mp3_compatibility)
1250
self.mp3_freeformat = ("0", "1")[self.mp3_compatibility == "freeformat"]
1251
self.update_sensitives()
1253
def cb_oggtab(self, widget, data = None):
1254
ogg_bitrate = self.ogg_encoding_nominal_spin_adj.get_value()
1255
minactive = self.ogg_min_checkbutton.get_active()
1256
maxactive = self.ogg_max_checkbutton.get_active()
1257
self.ogg_encoding_relmin_spin_control.set_sensitive(minactive)
1258
self.ogg_encoding_relmax_spin_control.set_sensitive(maxactive)
1260
ogg_min = self.ogg_encoding_relmin_spin_adj.get_value() + ogg_bitrate
1266
ogg_max = self.ogg_encoding_relmax_spin_adj.get_value() + ogg_bitrate
1269
self.send("sample_rate=%d\nbit_rate=%d\nbit_rate_min=%d\nbit_rate_max=%d\nstereo=%s\ncommand=test_ogg_values\n" % (self.stream_resample_frame.resample_rate,
1270
ogg_bitrate, ogg_min, ogg_max,
1271
("mono","stereo")[self.ogg_encoding_stereo_checkbutton.get_active()]))
1272
self.vorbis_settings_valid = self.receive() == "succeeded"
1273
self.update_sensitives()
1275
def cb_vorbistab(self, widget, data = None):
1276
vorbis_bitrate = self.vorbis_encoding_nominal_spin_adj.get_value()
1277
vorbis_min = self.vorbis_encoding_lower_spin_control.get_cooked_value()
1278
vorbis_max = self.vorbis_encoding_upper_spin_control.get_cooked_value()
1279
self.send("sample_rate=%d\nbit_rate=%d\nbit_rate_min=%d\nbit_rate_max=%d\nstereo=%s\ncommand=test_ogg_values\n" % (self.stream_resample_frame.resample_rate,
1280
vorbis_bitrate, vorbis_min, vorbis_max,
1281
("mono","stereo")[self.vorbis_stereo_rb.get_active()]))
1282
self.vorbis_settings_valid = self.receive() == "succeeded"
1283
self.update_sensitives()
1285
def mpeg_std_search(self, bitrate, samplerate, brsr):
1286
return bitrate in brsr[0] and samplerate in brsr[1]
1287
def server_reconnect(self):
1288
if self.connection_string:
1289
self.send("command=server_disconnect\n")
1292
self.send(self.connection_string)
1294
def cb_server_connect(self, widget):
1295
if widget.get_active():
1296
self.start_stop_encoder(ENCODER_START)
1297
d = self.connection_pane.row_to_dict(0)
1299
self.connection_string = "\n".join((
1300
"stream_source=" + str(self.numeric_id),
1301
"server_type=" + ("Icecast 2", "Shoutcast")[d["server_type"]],
1302
"host=" + d["host"],
1303
"port=%d" % d["port"],
1304
"mount=" + d["mount"],
1305
"login=" + d["login"],
1306
"password=" + d["password"],
1307
"useragent=" + (self.troubleshooting.user_agent_entry.get_text().strip() if self.troubleshooting.custom_user_agent.get_active() else ""),
1308
"dj_name=" + self.dj_name_entry.get_text().strip(),
1309
"listen_url=" + self.listen_url_entry.get_text().strip(),
1310
"description=" + self.description_entry.get_text().strip(),
1311
"genre=" + self.genre_entry.get_text().strip(),
1312
"irc=" + self.irc_entry.get_text().strip(),
1313
"aim=" + self.aim_entry.get_text().strip(),
1314
"icq=" + self.icq_entry.get_text().strip(),
1315
"make_public=" + str(bool(self.make_public.get_active())),
1316
"command=server_connect\n"))
1317
self.send(self.connection_string)
1318
self.is_shoutcast = d["server_type"] == 1
1319
if self.receive() == "failed":
1320
self.server_connect.set_active(False)
1321
self.connection_string = None
1323
self.connection_pane.streaming_set(True)
1325
self.send("command=server_disconnect\n")
1327
self.start_stop_encoder(ENCODER_STOP)
1328
self.connection_string = None
1329
self.connection_pane.streaming_set(False)
1330
def cb_test_monitor(self, widget):
1331
if widget.get_active():
1332
self.start_stop_encoder(ENCODER_START)
1333
self.send("command=monitor_start\n")
1335
self.send("command=monitor_stop\n")
1336
self.start_stop_encoder(ENCODER_STOP)
1337
def cb_update_button(self, widget):
1338
self.start_encoder("encoder_update")
1339
if self.server_connect.get_active() and self.is_shoutcast:
1340
self.server_reconnect()
1341
def start_stop_encoder(self, command): # provides for nested starts, stops of the encoder
1342
if command == ENCODER_START:
1343
self.encoder_on_count += 1
1344
if self.encoder_on_count == 1:
1345
self.start_encoder()
1347
self.encoder_on_count -= 1
1348
if self.encoder_on_count == 0:
1350
self.update_sensitives()
1351
def start_encoder(self, command = "encoder_start"):
1352
self.metadata_update.clicked()
1353
if self.format_page == 0:
1354
self.encoder = "mp3"
1355
self.send("format=mp3\nencode_source=jack\nsample_rate=%d\nresample_quality=%s\nbit_rate=%d\nstereo=%s\nencode_quality=%s\nfreeformat_mp3=%s\ncommand=%s\n" % (self.stream_resample_frame.resample_rate,
1356
self.stream_resample_frame.resample_quality,
1358
self.mp3_stereo_type,
1359
self.mp3_encode_quality,
1360
self.mp3_freeformat,
1362
if self.receive() == "succeeded":
1363
self.format_info_bar.push(1, "mp3 %dHz %dkbps %s" % (self.stream_resample_frame.resample_rate, self.mp3_bitrate, self.mp3_stereo_type))
1365
self.format_info_bar.push(1, "")
1366
elif self.format_page == 1:
1367
self.encoder = "ogg"
1368
if self.subformat_page == 0: # vorbis
1369
vorbis_bitrate = self.vorbis_encoding_nominal_spin_adj.get_value()
1370
vorbis_min = self.vorbis_encoding_lower_spin_control.get_cooked_value()
1371
vorbis_max = self.vorbis_encoding_upper_spin_control.get_cooked_value()
1372
vorbis_channels = ("mono", "stereo")[self.vorbis_stereo_rb.get_active()]
1373
self.send("format=ogg\nsubformat=vorbis\nencode_source=jack\nsample_rate=%d\nresample_quality=%s\nbit_rate=%d\nbit_rate_min=%d\nbit_rate_max=%d\nstereo=%s\ncommand=%s\n" % (self.stream_resample_frame.resample_rate,
1374
self.stream_resample_frame.resample_quality,
1375
vorbis_bitrate, vorbis_min, vorbis_max,
1376
vorbis_channels, command))
1377
if self.receive() == "succeeded":
1378
if vorbis_min == vorbis_max == -1:
1381
managed = " managed"
1382
self.format_info_bar.push(1, "Ogg Vorbis %dHz %dkbps %s%s" % (self.stream_resample_frame.resample_rate, vorbis_bitrate, vorbis_channels, managed))
1384
self.format_info_bar.push(1, "")
1385
elif self.subformat_page == 1: # OggFLAC
1386
flac_channels = ("mono", "stereo")[self.flacstereo.get_active()]
1387
flac_bitwidth = ("16", "20", "24")[(self.flac20bit.get_active() and 1) + (self.flac24bit.get_active() and 2)]
1388
self.send("format=ogg\nsubformat=flac\nencode_source=jack\nsample_rate=%d\nresample_quality=%s\nbit_width=%s\nstereo=%s\nuse_metadata=%d\ncommand=%s\n" % (self.stream_resample_frame.resample_rate, self.stream_resample_frame.resample_quality, flac_bitwidth, flac_channels, self.flacmetadata.get_active(), command))
1389
if self.receive() == "succeeded":
1390
self.format_info_bar.push(1, "Ogg FLAC %s bit %s" % (flac_bitwidth, flac_channels))
1392
self.format_info_bar.push(1, "")
1393
elif self.subformat_page == 2: # speex
1394
speex_srate = (32000, 16000, 8000)[self.speex_mode.get_active()]
1395
speex_channels = ("mono", "stereo")[self.speex_stereo.get_active()]
1396
self.send("format=ogg\nsubformat=speex\nencode_source=jack\nsample_rate=%d\nresample_quality=%s\nspeex_mode=%d\nstereo=%s\nuse_metadata=%d\nspeex_quality=%s\nspeex_complexity=%s\ncommand=%s\n" % (speex_srate, self.stream_resample_frame.resample_quality, self.speex_mode.get_active(), speex_channels, self.speex_metadata.get_active(), self.speex_quality.get_active_text(), self.speex_complexity.get_active_text(), command))
1397
metatext = ("-Meta", "+Meta")[self.speex_metadata.get_active()]
1398
if self.receive() == "succeeded":
1399
self.format_info_bar.push(1, "Speex %s %s Q%s C%s %s" % (self.speex_mode.get_active_text(), speex_channels.capitalize(), self.speex_quality.get_active_text(), self.speex_complexity.get_active_text(), metatext))
1401
self.format_info_bar.push(1, "")
1403
if self.file_dialog.get_filename().endswith(".mp3"):
1404
self.encoder = "mp3"
1406
self.encoder = "ogg"
1407
self.send("format=%s\nencode_source=file\nfilename=%s\noffset=%d\ncommand=%s\n" % (self.encoder, self.file_dialog.get_filename(), self.file_offset_adj.get_value(), command))
1408
if self.receive() == "succeeded":
1409
self.format_info_bar.push(1, self.file_dialog.get_filename())
1411
self.format_info_bar.push(1, "")
1412
def stop_encoder(self):
1413
self.encoder = "off"
1414
self.send("command=encoder_stop\n")
1415
if self.receive() == "failed":
1416
print "stop_encoder: encoder was already stopped"
1417
self.format_info_bar.push(1, "")
1419
def server_type_cell_data_func(self, celllayout, cell, model, iter):
1420
text = model.get_value(iter, 0)
1421
if text == _('Shoutcast') and lameenabled == 0:
1422
cell.set_property("sensitive", False)
1424
cell.set_property("sensitive", True)
1426
def cb_metadata(self, widget):
1427
fallback = self.metadata_fallback.get_text()
1428
songname = self.scg.parent.songname.encode("utf-8") or fallback
1429
table = [("%%", "%")] + zip(("%r", "%t", "%l"), ((getattr(self.scg.parent, x) or fallback) for x in ("artist", "title", "album")))
1430
table.append(("%s", songname))
1431
raw_cm = self.metadata.get_text().encode("utf-8", "replace").strip()
1432
cm = string_multireplace(raw_cm, table)
1434
if self.scg.parent.prefs_window.mp3_utf8.get_active():
1437
cm_lat1 = cm.decode("utf-8").encode("iso8859-1", "replace").strip()
1442
tab = ("mp3", "ogg")[self.format_page] if self.encoder == "off" else self.encoder
1446
disp = "[{0[%r]}], [{0[%t]}], [{0[%l]}]".format(dict(table))
1448
disp = "no metadata string defined for this stream format"
1451
self.metadata_display.push(0, disp)
1452
self.metadata_update.set_relief(gtk.RELIEF_HALF)
1453
self.scg.send("tab_id=%d\ndev_type=encoder\ncustom_meta=%s\ncustom_meta_lat1=%s\ncommand=new_custom_metadata\n" % (self.numeric_id, cm, cm_lat1))
1455
def cb_new_metadata_format(self, widget):
1456
self.metadata_update.set_relief(gtk.RELIEF_NORMAL)
1459
def deferred_connect(self):
1460
"""Intended to be called from a thread."""
1462
self.server_connect.set_active(True)
1464
def cb_kick_incumbent(self, widget, post_action=lambda : None):
1465
"""Try to remove whoever is using the server so that we can connect."""
1467
mode = self.connection_pane.get_master_server_type()
1471
srv = ListLine(*self.connection_pane.liststore[0])
1472
auth_handler = urllib2.HTTPBasicAuthHandler()
1475
url = "http://" + urllib.quote(srv.host) + ":" + str(srv.port) + "/admin/killsource?mount=" + urllib.quote(srv.mount)
1476
auth_handler.add_password("Icecast2 Server", srv.host + ":" + str(srv.port), srv.login, srv.password)
1477
def check_reply(reply):
1479
elem = xml.etree.ElementTree.fromstring(reply)
1480
except xml.etree.ElementTree.ParseError:
1483
rslt = "succeeded" if elem.findtext("return") == "1" else "failed"
1484
print "kick %s: %s" % (rslt, elem.findtext("message"))
1485
return rslt == "succeeded"
1488
password = self.admin_password_entry.get_text().strip() or srv.password
1489
url = "http://" + urllib.quote(srv.host) + ":" + str(srv.port) + "/admin.cgi?mode=kicksrc"
1490
auth_handler.add_password("Shoutcast Server", srv.host + ":" + str(srv.port), "admin", password)
1491
def check_reply(reply):
1492
# Could go to lengths to check the XML stats here.
1493
# Thats one whole extra HTTP request.
1494
print "kick succeeded"
1497
opener = urllib2.build_opener(auth_handler)
1498
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
1502
reply = opener.open(url).read()
1503
except urllib2.URLError, e:
1504
print "kick failed:", e
1509
Thread(target=threaded).start()
1511
def __init__(self, scg, numeric_id, indicator_lookup):
1512
Tab.__init__(self, scg, numeric_id, indicator_lookup)
1514
self.show_indicator("clear")
1515
self.tab_type = "streamer"
1516
self.encoder = "off" # can also be set to "mp3" or "ogg" depending on what is encoded
1517
self.encoder_on_count = 0 # when this counter hits zero the encoder is turned off
1518
self.format_page = 0 # the current format page
1519
self.subformat_page = 0 # the Ogg sub-format
1520
self.set_spacing(10)
1522
self.ic_expander = gtk.Expander(_('Individual Controls'))
1523
self.pack_start(self.ic_expander, False)
1524
self.ic_expander.show()
1526
self.ic_frame = gtk.Frame()
1527
ic_vbox = gtk.VBox() # box containing connect button and timers
1528
ic_vbox.set_border_width(10)
1529
ic_vbox.set_spacing(10)
1530
self.ic_frame.add(ic_vbox)
1535
self.server_connect = gtk.ToggleButton()
1536
set_tip(self.server_connect, _('Connect to or disconnect from the radio server. If the button does not stay in, the connection failed for some reason.\n \nIf the button is greyed out it means you are using unsupported settings. Shoutcast only supports mp3 and mp3 requires that you use one of the sample rates in the drop down box. Ogg only supports certain sample rate, bit rate, and stereo combinations. Also, the connection list must contain details for a master server.'))
1537
self.server_connect.connect("toggled", self.cb_server_connect)
1538
hbox.pack_start(self.server_connect, True, True, 0)
1539
self.server_connect_label = gtk.Label()
1540
self.server_connect_label.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
1541
self.server_connect.add(self.server_connect_label)
1542
self.server_connect_label.show()
1543
self.server_connect.show()
1545
# TC: Kick whoever is on the server.
1546
self.kick_incumbent = gtk.Button(_('Kick Incumbent'))
1547
self.kick_incumbent.connect("clicked", self.cb_kick_incumbent)
1548
set_tip(self.kick_incumbent, _('This will disconnect whoever is currently using the server, freeing it up for personal use.'))
1549
hbox.pack_start(self.kick_incumbent, False)
1550
self.kick_incumbent.show()
1552
ic_vbox.pack_start(hbox, False)
1557
label = gtk.Label(_('Connection timer:'))
1558
hbox.pack_start(label, False)
1561
self.start_timer = TimeEntry(_('Begin'))
1562
set_tip(self.start_timer, _('Automatically connect to the server at a specific time in 24 hour format, midnight being 00:00'))
1563
hbox.pack_start(self.start_timer, False)
1564
self.start_timer.show()
1566
self.kick_before_start = gtk.CheckButton(_('With kick'))
1567
self.kick_before_start.set_sensitive(False)
1568
set_tip(self.kick_before_start, _('Disconnect whoever is using the server just before start time.'))
1569
hbox.pack_start(self.kick_before_start, False)
1570
self.kick_before_start.show()
1572
self.start_timer.check.connect("toggled", lambda w: self.kick_before_start.set_sensitive(w.props.active))
1575
self.stop_timer = TimeEntry(_('End'))
1576
set_tip(self.stop_timer, _('Automatically disconnect from the server at a specific time in 24 hour format.'))
1577
hbox.pack_end(self.stop_timer, False)
1578
self.stop_timer.show()
1581
ic_vbox.pack_start(hbox, False, False, 0)
1584
hbox = gtk.HBox() # box containing auto action widgets
1585
hbox.set_spacing(10)
1586
label = gtk.Label(_('At connect:'))
1587
hbox.pack_start(label, False, False, 0)
1589
# TC: [x] Start player (*) 1 ( ) 2
1590
self.start_player_action = AutoAction(_('Start player'), (
1591
("1", self.source_client_gui.parent.player_left.play.clicked),
1592
("2", self.source_client_gui.parent.player_right.play.clicked)))
1593
hbox.pack_start(self.start_player_action, False, False, 0)
1594
self.start_player_action.show()
1595
set_tip(self.start_player_action, _('Have one of the players start automatically when a radio server connection is successfully made.'))
1596
if PGlobs.num_recorders:
1597
vseparator = gtk.VSeparator()
1598
hbox.pack_start(vseparator, True, False, 0)
1601
# TC: [x] Start recorder (*) 1 ( ) 2
1602
self.start_recorder_action = AutoAction(_('Start recorder'), [ (chr(ord("1") + i), t.record_buttons.record_button.activate) for i, t in enumerate(self.source_client_gui.recordtabframe.tabs) ])
1604
hbox.pack_end(self.start_recorder_action, False, False, 0)
1605
if PGlobs.num_recorders:
1606
self.start_recorder_action.show()
1607
set_tip(self.start_recorder_action, _('Have a recorder start automatically when a radio server connection is successfully made.'))
1608
ic_vbox.pack_start(hbox, False, False, 0)
1611
frame = gtk.Frame(" %s " % _('Metadata'))
1612
table = gtk.Table(3, 3)
1613
table.set_border_width(6)
1614
table.set_row_spacings(1)
1615
table.set_col_spacings(4)
1618
ic_vbox.pack_start(frame, False)
1621
format_label = SmallLabel(_('Format String'))
1622
# TC: Label for the metadata fallback value.
1623
fallback_label = SmallLabel(_('Fallback'))
1624
self.metadata = HistoryEntryWithMenu()
1625
self.metadata.child.connect("changed", self.cb_new_metadata_format)
1626
self.metadata_fallback = gtk.Entry()
1627
self.metadata_fallback.set_width_chars(10)
1628
self.metadata_fallback.set_text("<Unknown>")
1629
self.metadata_update = gtk.Button()
1630
image = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
1631
self.metadata_update.set_image(image)
1633
self.metadata_update.connect("clicked", self.cb_metadata)
1634
self.metadata_display = gtk.Statusbar()
1635
self.metadata_display.set_has_resize_grip(False)
1637
set_tip(self.metadata, _('You can enter text to accompany the stream here and can specify placemarkers %r %t %l %s for the artist, title, album, and songname respectively, or leave this text field blank to use the default metadata.\n\nSongname (%s) is derived from the filename in the absence of sufficient metadata, while the other placemarkers will use the fallback text to the right.\n\nWhen blank, Ogg streams will use the standard Vorbis tags and mp3 will use %s.'))
1638
set_tip(self.metadata_fallback, _('The fallback text to use when %r %t %l metadata is unavailable. See the format string to the left.'))
1639
set_tip(self.metadata_update, _('Metadata normally updates only on song title changes but you can force an immediate update here.'))
1644
arrangement = (((format_label, x|f), (fallback_label, s|f)),
1645
((self.metadata, x|f), (self.metadata_fallback, s), (self.metadata_update, s)))
1647
for r, row in enumerate(arrangement):
1648
for c, (child, xopt) in enumerate(row):
1649
table.attach(child, c, c + 1, r, r + 1, xopt, s|f)
1651
table.attach(self.metadata_display, 0, 3, 2, 3, x|f, s|f)
1652
self.metadata_display.show()
1654
self.pack_start(self.ic_frame, False)
1656
self.details = gtk.Expander(_('Configuration'))
1657
set_tip(self.details, _('The controls for configuring a stream.'))
1658
self.pack_start(self.details, False)
1661
self.details_nb = gtk.Notebook()
1662
self.pack_start(self.details_nb, False)
1664
self.connection_pane = ConnectionPane(set_tip, self)
1665
self.connection_pane.liststore.connect("row-deleted", self.update_sensitives)
1666
self.connection_pane.liststore.connect("row-changed", self.update_sensitives)
1667
label = gtk.Label(_('Connection'))
1668
self.details_nb.append_page(self.connection_pane, label)
1670
self.connection_pane.show()
1672
vbox = gtk.VBox() # format box
1673
vbox.set_border_width(10)
1674
vbox.set_spacing(14)
1675
label = gtk.Label(_('Format'))
1676
self.details_nb.append_page(vbox, label)
1679
hbox = gtk.HBox(True)
1680
hbox.set_spacing(16)
1681
vbox.pack_start(hbox, False)
1683
sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_VERTICAL)
1684
self.stream_resample_frame = self.ResampleFrame(self, sizegroup) # stream resample frame
1685
hbox.add(self.stream_resample_frame)
1686
self.stream_resample_frame.show()
1687
self.format_notebook = gtk.Notebook() # [mp3 / ogg / file] chooser
1688
hbox.add(self.format_notebook)
1689
self.format_notebook.show()
1692
self.mp3tab = self.make_notebook_tab(self.format_notebook, "MP3", _('Clicking this tab selects the mp3 file format for streaming and contains settings for configuring the mp3 encoder.'))
1693
self.standard_mp3_bitrate, self.custom_mp3_bitrate = self.make_radio(2)
1694
set_tip(self.standard_mp3_bitrate, _('Use one of the standard mp3 bit rates.'))
1695
set_tip(self.custom_mp3_bitrate, _("Freedom to choose a non standard bitrate. Note however that the use of a non-standard bit rate will result in a 'free-format' stream that cannot be handled by a great many media players."))
1696
self.standard_mp3_bitrate.connect("clicked", self.cb_mp3tab, "standard")
1697
self.custom_mp3_bitrate.connect("clicked", self.cb_mp3tab, "custom")
1698
self.mp3_standard_bitrates = (320, 256, 224, 192, 160, 144, 128, 112, 96, 80, 64, 56, 48, 40, 32, 24, 16, 8)
1699
self.mp3_mpeg1_bitrates_samplerates = ((320, 256, 224, 192, 160, 128, 112, 96, 80, 64, 56, 48, 40, 32), (48000, 44100, 32000))
1700
self.mp3_mpeg2_bitrates_samplerates = ((160, 144, 128, 112, 96, 80, 64, 56, 48, 40, 32, 24, 16, 8), (24000, 22050, 16000))
1701
self.mp3_mpeg2_5_bitrates_samplerates = ((160, 144, 128, 112, 96, 80, 64, 56, 48, 40, 32, 24, 16, 8), (12000, 11025, 8000))
1702
self.mp3_bitrate_combo_box = self.make_combo_box(map(str, self.mp3_standard_bitrates))
1703
set_tip(self.mp3_bitrate_combo_box, _('The bit-rate in kilobits per second.'))
1704
self.mp3_bitrate_combo_box.set_active(6)
1705
self.mp3_bitrate_combo_box.connect("changed", self.cb_mp3tab)
1706
self.mp3_bitrate_spin_adj = gtk.Adjustment(128, 8, 640, 10, 100, 0)
1707
self.mp3_bitrate_spin_control = gtk.SpinButton(self.mp3_bitrate_spin_adj)
1708
set_tip(self.mp3_bitrate_spin_control, _('The bit-rate in kilobits per second.'))
1709
self.mp3_bitrate_spin_control.connect("value-changed", self.cb_mp3tab)
1710
encoding_quality_label = gtk.Label(_('Quality (0=best)'))
1711
self.mp3_encoding_quality_combo_box = self.make_combo_box(("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"))
1712
set_tip(self.mp3_encoding_quality_combo_box, _('This trades off sound quality against CPU efficiency. The more streams you want to run concurrently the more you might want to consider using a lower quality setting.'))
1713
self.mp3_encoding_quality_combo_box.set_active(2)
1714
self.mp3_encoding_quality_combo_box.connect("changed", self.cb_mp3tab)
1715
self.mp3_stereo_combo_box = self.make_combo_box(("Stereo", "Mono", "Joint Stereo"))
1716
set_tip(self.mp3_stereo_combo_box, _('Mono is self explanatory. Joint Stereo is recommended below 160kb/s where regular Stereo might result in metallic sounding distortion. At higher bitrates regular stereo sounds better due to superior channel separation.'))
1717
self.mp3_stereo_combo_box.set_active(2)
1718
self.mp3_stereo_combo_box.connect("changed", self.cb_mp3tab)
1719
self.mp3_compatibility_status = gtk.Statusbar()
1720
set_tip(self.mp3_compatibility_status, _("The type of mpeg header used in the mp3 stream or either s-rate or freeformat. Freeformat indicates that the bitrate is not specified in the header since it is non-standard, rather the listener client has to figure out what the bitrate is by itself and not all of them are capable of doing that. In short you'll be streaming something many listeners may not be able to listen to. S-rate indicates the sample rate you have selected is not compatible with mp3 and you'll need to change it if you want to stream."))
1721
self.mp3_compatibility_status.set_has_resize_grip(False)
1722
self.mp3_dummy_object = gtk.Button()
1723
self.mp3_dummy_object.connect("clicked", self.cb_mp3tab)
1724
self.mp3_bitrate = 128
1725
self.mp3_bitrate_widget = "standard"
1728
mp3_pane = self.item_item_layout(((self.standard_mp3_bitrate, self.mp3_bitrate_combo_box),
1729
(self.custom_mp3_bitrate, self.mp3_bitrate_spin_control),
1730
(encoding_quality_label, self.mp3_encoding_quality_combo_box),
1731
(self.mp3_stereo_combo_box, self.mp3_compatibility_status)), sizegroup)
1732
mp3_pane.set_border_width(10)
1734
mp3_pane = gtk.VBox(True)
1735
for line in _("To enable MP3 streaming\ninstall the package named\n'libmp3lame'\n and restart IDJC.").splitlines():
1736
label = gtk.Label(line)
1739
set_tip(mp3_pane, _('Installing libmp3lame will allow you to stream the MP3 format to Shoutcast servers. Currently only Ogg streaming to Icecast servers is possible.'))
1741
self.mp3tab.add(mp3_pane)
1745
self.oggtab = self.make_notebook_tab(self.format_notebook, "Ogg", _('Clicking this tab selects the Ogg family of file formats.'))
1746
self.subformat_notebook = gtk.Notebook()
1747
self.oggtab.add(self.subformat_notebook)
1748
self.subformat_notebook.show()
1749
self.oggvorbistab = self.make_notebook_tab(self.subformat_notebook, "Vorbis", _('This chooses the Ogg/vorbis format for streaming and recording.'))
1750
self.oggflactab = self.make_notebook_tab(self.subformat_notebook, "FLAC", _('This chooses the OggFLAC format for streaming and recording.'))
1751
self.oggspeextab = self.make_notebook_tab(self.subformat_notebook, "Speex", _('This chooses the Speex speech format for streaming and recording.'))
1753
# Vorbis subtab contents
1754
self.vorbis_encoding_nominal_spin_adj = gtk.Adjustment(128, 8, 500, 1, 10, 0)
1755
self.vorbis_encoding_nominal_spin_control = SimpleFramedSpin(_('Bitrate'), self.vorbis_encoding_nominal_spin_adj)
1756
self.vorbis_encoding_nominal_spin_control.spin.connect("value-changed", self.cb_vorbistab)
1758
self.vorbis_stereo_rb, self.vorbis_mono_rb = self.make_radio(2)
1759
self.vorbis_stereo_rb.connect("toggled", self.cb_vorbistab)
1760
radiovbox = gtk.VBox()
1761
radiovbox.set_border_width(5)
1762
stereohbox = gtk.HBox()
1763
monohbox = gtk.HBox()
1764
radiovbox.add(stereohbox)
1765
radiovbox.add(monohbox)
1766
stereohbox.pack_start(self.vorbis_stereo_rb, False, False, 0)
1767
monohbox.pack_start(self.vorbis_mono_rb, False, False, 0)
1768
label = gtk.Label(_('Stereo'))
1769
stereohbox.pack_start(label)
1770
label = gtk.Label(_('Mono'))
1771
monohbox.pack_start(label)
1772
radiovbox.show_all()
1774
upper_spin_adj = gtk.Adjustment(150, 100, 400, 1, 10, 0)
1775
lower_spin_adj = gtk.Adjustment(50, 0, 100, 1, 10, 0)
1776
# TC: The upper bitrate limit as a percentage.
1777
self.vorbis_encoding_upper_spin_control = FramedSpin(_('Upper %'), upper_spin_adj, self.vorbis_encoding_nominal_spin_adj)
1778
# TC: The lower bitrate limit as a percentage.
1779
self.vorbis_encoding_lower_spin_control = FramedSpin(_('Lower %'), lower_spin_adj, self.vorbis_encoding_nominal_spin_adj)
1781
sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_VERTICAL)
1783
vorbis_pane = self.item_item_layout2(((self.vorbis_encoding_nominal_spin_control, self.vorbis_encoding_upper_spin_control), (radiovbox, self.vorbis_encoding_lower_spin_control)), sizegroup)
1784
vorbis_pane.set_border_width(3)
1785
self.oggvorbistab.add(vorbis_pane)
1788
set_tip(self.vorbis_encoding_nominal_spin_control, _('The nominal Ogg/Vorbis bitrate in kilobits per second.'))
1789
set_tip(self.vorbis_encoding_upper_spin_control, _('The upper bitrate limit relative to the nominal bitrate. This is an advisory limit and it may be exceeded. Normally it is safe to leave the upper limit uncapped since the bitrate will be averaged and the listeners have buffers that extend for many seconds. The checkbox enables/disables this feature.'))
1790
set_tip(self.vorbis_encoding_lower_spin_control, _('The minimum bitrate in relative percentage terms. For streaming it is recommended that you set a minimum bitrate to ensure correct listener client behaviour however setting any upper or lower limit will result in a significantly higher CPU usage by a factor of at least three, and slightly degraded sound quality. The checkbox enables/disables this feature.'))
1792
self.vorbis_settings_valid = False
1793
self.vorbis_dummy_object = gtk.Button()
1794
self.vorbis_dummy_object.connect("clicked", self.cb_vorbistab)
1796
# FLAC subtab contents
1797
self.flacstereo = gtk.CheckButton(_('Stereo'))
1798
self.flacmetadata = gtk.CheckButton(_('Metadata'))
1799
self.flacstereo.set_active(True)
1800
self.flacmetadata.set_active(True)
1801
set_tip(self.flacmetadata, _('You can prevent the sending of metadata by turning this feature off. This will prevent certain players from dropping the stream or inserting an audible gap every time the song title changes.'))
1803
flac_bitrates = (_('%d Bit') % x for x in (16, 20, 24))
1804
self.flac16bit, self.flac20bit, self.flac24bit = self.make_radio_with_text(flac_bitrates)
1805
set_tip(self.flac16bit, _('Useful for streaming but for recording choose a higher bitrate option.'))
1806
set_tip(self.flac20bit, _('Ideal for very high quality streaming or recording although not as compatible as 16 bit.'))
1807
set_tip(self.flac24bit, _('The highest quality audio format available within IDJC. Recommended for pre-recording.'))
1808
if FGlobs.oggflacenabled:
1809
flac_pane = self.item_item_layout3((self.flacstereo, self.flacmetadata),(self.flac16bit, self.flac20bit, self.flac24bit))
1811
flac_pane = gtk.Label(_('Feature Disabled'))
1812
self.oggflactab.add(flac_pane)
1813
flac_pane.show_all()
1815
# Speex subtab contents
1816
self.speex_mode = gtk.combo_box_new_text()
1817
# The Speex audio codec has specific modes that are user selectable (dropdown list text).
1819
# TC: One of the modes supported by the Speex codec.
1820
_('Ultra Wide Band'),
1821
# TC: One of the modes supported by the Speex codec.
1823
# TC: One of the modes supported by the Speex codec.
1825
for each in speex_modes:
1826
self.speex_mode.append_text(each)
1827
self.speex_mode.set_active(0)
1828
self.speex_stereo = gtk.CheckButton(_('Stereo'))
1829
set_tip(self.speex_stereo, _('Apply intensity stereo to the audio stream. This is a very efficient implementation of stereo but is only suited to voice.'))
1830
self.speex_metadata = gtk.CheckButton(_('Metadata'))
1831
set_tip(self.speex_metadata, _('Sending metadata may cause listener clients to misbehave when the metadata changes. By keeping this feature turned off you can avoid that.'))
1832
self.speex_quality = gtk.combo_box_new_text()
1834
self.speex_quality.append_text("%d" % i)
1835
self.speex_quality.set_active(8)
1836
self.speex_complexity = gtk.combo_box_new_text()
1837
for i in range(1, 11):
1838
self.speex_complexity.append_text("%d" % i)
1839
self.speex_complexity.set_active(2)
1841
if FGlobs.speexenabled:
1843
svbox.set_border_width(5)
1845
# TC: The mode uesd by the Speex codec.
1846
label = gtk.Label(_('Mode'))
1848
shbox0.set_spacing(5)
1849
shbox0.pack_start(label, False, False, 0)
1850
shbox0.pack_start(self.speex_mode, True, True, 0)
1851
set_tip(shbox0, _('This is the audio bandwidth selector. Ultra Wide Band has a bandwidth of 16kHz; Wide Band, 8kHz; Narrow Band, 4kHz. The samplerate is twice the value of the selected bandwidth consequently all settings in the samplerate pane to the left will be disregarded apart from the resample quality setting.'))
1852
svbox.pack_start(shbox0, True, False, 0)
1854
shbox1.pack_start(self.speex_stereo, True, False, 0)
1855
shbox1.pack_end(self.speex_metadata, True, False, 0)
1856
svbox.pack_start(shbox1, True, False, 0)
1859
shbox3.set_spacing(5)
1861
shbox4.set_spacing(5)
1862
shbox2.pack_start(shbox3, False, False, 0)
1863
shbox2.pack_end(shbox4, False, False, 0)
1865
label = gtk.Label(_('Quality'))
1866
shbox3.pack_start(label, False, False, 0)
1867
shbox3.pack_start(self.speex_quality, False, False, 0)
1868
set_tip(shbox3, _('This picks an appropriate bitrate for the selected bandwidth on a quality metric. Q8 is a good choice for artifact-free speech and Q10 would be the ideal choice for music.'))
1870
label = gtk.Label(_('CPU'))
1871
shbox4.pack_start(label, False, False, 0)
1872
shbox4.pack_start(self.speex_complexity, False, False, 0)
1873
set_tip(shbox4, _('This sets the level of complexity in the encoder. Higher values use more CPU but result in better sounding audio though not as great an improvement as you would get by increasing the quality setting to the left.'))
1875
svbox.pack_start(shbox2, True, False, 0)
1876
self.oggspeextab.add(svbox)
1879
label = gtk.Label(_('Feature Disabled'))
1880
self.oggspeextab.add(label)
1883
format_control_bar = gtk.HBox() # Button box in Format frame
1884
format_control_sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
1885
format_control_bar.set_spacing(10)
1886
vbox.pack_start(format_control_bar, False)
1887
format_control_bar.show()
1888
self.test_monitor = gtk.ToggleButton(_(' Test / Monitor '))
1889
self.test_monitor.connect("toggled", self.cb_test_monitor)
1890
format_control_sizegroup.add_widget(self.test_monitor)
1891
format_control_bar.pack_start(self.test_monitor, False, False, 0)
1892
#self.test_monitor.show()
1893
self.format_info_bar = gtk.Statusbar()
1894
self.format_info_bar.set_has_resize_grip(False)
1895
format_control_bar.pack_start(self.format_info_bar, True, True, 0)
1896
self.format_info_bar.show()
1897
set_tip(self.format_info_bar, _('Information about how the encoder is currently configured is displayed here.'))
1898
# TC: Button when pressed performs an update.
1899
self.update_button = gtk.Button(_('Update'))
1900
set_tip(self.update_button, _('Use this to change the encoder settings while streaming or recording.\n \nIf this button is greyed out it means that the encoder is not running, or the bitrate/samplerate combination is not supported by the encoder, or you are trying to switch between Ogg and mp3, which is not permitted.'))
1901
self.update_button.connect("clicked", self.cb_update_button)
1902
format_control_sizegroup.add_widget(self.update_button)
1903
self.update_button.set_sensitive(False)
1904
format_control_bar.pack_start(self.update_button, False, False, 0)
1905
self.update_button.show()
1906
self.format_notebook.connect("switch-page", self.cb_format_notebook)
1907
self.subformat_notebook.connect("switch-page", self.cb_subformat_notebook)
1908
self.format_notebook.set_current_page(0)
1911
# TC: Tab heading. User can enter information about the stream here.
1912
label = gtk.Label(_('Stream Info'))
1913
self.details_nb.append_page(vbox, label)
1916
self.dj_name_entry = DefaultEntry("eyedeejaycee")
1917
set_tip(self.dj_name_entry, _('Enter your DJ name or station name here. Typically this information will be displayed by listener clients.'))
1918
self.listen_url_entry = DefaultEntry("http://www.example.com")
1919
set_tip(self.listen_url_entry, _('The URL of your radio station. This and the rest of the information below is intended for display on a radio station listings website.'))
1920
self.description_entry = gtk.Entry()
1921
set_tip(self.description_entry, _('A description of your radio station.'))
1922
genre_entry_box = gtk.HBox()
1923
genre_entry_box.set_spacing(12)
1924
self.genre_entry = DefaultEntry("Misc")
1925
set_tip(self.genre_entry, _('The musical genres you are likely to play.'))
1926
genre_entry_box.pack_start(self.genre_entry, True, True, 0)
1927
self.genre_entry.show()
1928
self.make_public = gtk.CheckButton(_('Make Public'))
1929
set_tip(self.make_public, _('Publish your radio station on a listings website. The website in question will depend on how the server to which you connect is configured.'))
1930
genre_entry_box.pack_start(self.make_public, False, False, 0)
1931
self.make_public.show()
1932
info_sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_VERTICAL)
1933
stream_details_pane = self.label_item_layout((
1934
# TC: The DJ or Stream name.
1935
(_('DJ name'), self.dj_name_entry),
1936
(_('Listen URL'), self.listen_url_entry),
1937
# TC: Station description. Typically the user enters a small paragraph of text.
1938
(_('Description'), self.description_entry),
1939
(_('Genre(s)'), genre_entry_box)
1941
stream_details_pane.set_border_width(10)
1942
vbox.add(stream_details_pane)
1943
stream_details_pane.show()
1948
alhbox.set_border_width(10)
1949
alhbox.set_spacing(3)
1950
label = gtk.Label(_('Master server admin password'))
1951
alhbox.pack_start(label, False)
1953
self.admin_password_entry = gtk.Entry()
1954
self.admin_password_entry.set_visibility(False)
1955
set_tip(self.admin_password_entry, _("This is for kick and stats on Shoutcast master servers that have an administrator password. For those that don't leave this blank (the source password is sufficient for those)."))
1956
alhbox.pack_start(self.admin_password_entry)
1957
self.admin_password_entry.show()
1958
vbox.pack_start(alhbox, False)
1961
frame = CategoryFrame(" %s " % _('Contact Details'))
1962
frame.set_shadow_type(gtk.SHADOW_NONE)
1963
frame.set_border_width(0)
1964
self.irc_entry = gtk.Entry()
1965
set_tip(self.irc_entry, _('Internet Relay Chat connection info goes here.'))
1966
self.aim_entry = gtk.Entry()
1967
set_tip(self.aim_entry, _('Connection info for AOL instant messenger goes here.'))
1968
self.icq_entry = gtk.Entry()
1969
set_tip(self.icq_entry, _('ICQ instant messenger connection info goes here.'))
1970
contact_sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_VERTICAL)
1971
contact_details_pane = self.label_item_layout((
1972
(_('IRC'), self.irc_entry),
1973
(_('AIM'), self.aim_entry),
1974
(_('ICQ'), self.icq_entry)
1975
), contact_sizegroup)
1976
contact_details_pane.set_border_width(10)
1977
frame.add(contact_details_pane)
1978
contact_details_pane.show()
1980
vbox.pack_start(frame, False)
1981
if FGlobs.enh_libshout:
1983
label = gtk.Label(_('Extra Shoutcast'))
1984
self.details_nb.append_page(vbox, label)
1988
label = gtk.Label(_("Troubleshooting"))
1989
self.troubleshooting = Troubleshooting()
1990
self.details_nb.append_page(self.troubleshooting, label)
1993
label = gtk.Label("IRC")
1994
self.ircpane = IRCPane()
1995
self.details_nb.append_page(self.ircpane, label)
1998
self.details_nb.set_current_page(0)
2000
self.stream_resample_frame.resample_no_resample.emit("clicked") # bogus signal to update mp3 pane
2001
self.objects = { "metadata" : (self.metadata, "history"),
2002
"metadata_fb" : (self.metadata_fallback, "text"),
2003
"prekick" : (self.kick_before_start, "active"),
2004
"connections" : (self.connection_pane, ("loader", "saver")),
2005
"stats_never" : (self.connection_pane.stats_never, "active"),
2006
"stats_always": (self.connection_pane.stats_always, "active"),
2007
"rs_use_jack" : (self.stream_resample_frame.resample_no_resample, "active"),
2008
"rs_use_std" : (self.stream_resample_frame.resample_standard, "active"),
2009
"rs_use_custom_rate" : (self.stream_resample_frame.resample_custom, "active"),
2010
"rs_std_rate" : (self.stream_resample_frame.resample_rate_combo_box, "active"),
2011
"rs_custom_rate" : (self.stream_resample_frame.resample_rate_spin_adj, "value"),
2012
"rs_quality" : (self.stream_resample_frame.resample_quality_combo_box, "active"),
2013
"source_type" : (self.format_notebook, "notebookpage"),
2014
"ogg_type": (self.subformat_notebook, "notebookpage"),
2015
"std_mp3bitrate" : (self.standard_mp3_bitrate, "active"),
2016
"custom_mp3_bitrate" : (self.custom_mp3_bitrate, "active"),
2017
"mp3_bitrate_combo" : (self.mp3_bitrate_combo_box, "active"),
2018
"mp3_bitrate_spin" : (self.mp3_bitrate_spin_adj, "value"),
2019
"mp3_quality" : (self.mp3_encoding_quality_combo_box, "active"),
2020
"mp3_stereo" : (self.mp3_stereo_combo_box, "active"),
2021
"vorbis_bitrate" : (self.vorbis_encoding_nominal_spin_adj, "value"),
2022
"vorbis_upper_pc": (self.vorbis_encoding_upper_spin_control.spin, "value"),
2023
"vorbis_lower_pc": (self.vorbis_encoding_lower_spin_control.spin, "value"),
2024
"vorbis_upper_enable": (self.vorbis_encoding_upper_spin_control.check, "active"),
2025
"vorbis_lower_enable": (self.vorbis_encoding_lower_spin_control.check, "active"),
2026
"vorbis_mono": (self.vorbis_mono_rb, "active"),
2027
"flac_stereo": (self.flacstereo, "active"),
2028
"flac_metadata": (self.flacmetadata, "active"),
2029
"flac_20_bit": (self.flac20bit, "active"),
2030
"flac_24_bit": (self.flac24bit, "active"),
2031
"speex_mode": (self.speex_mode, "active"),
2032
"speex_stereo": (self.speex_stereo, "active"),
2033
"speex_metadata": (self.speex_metadata, "active"),
2034
"speex_quality": (self.speex_quality, "active"),
2035
"speex_complexity": (self.speex_complexity, "active"),
2036
"dj_name" : (self.dj_name_entry, "text"),
2037
"listen_url" : (self.listen_url_entry, "text"),
2038
"description" : (self.description_entry, "text"),
2039
"genre" : (self.genre_entry, "text"),
2040
"make_public" : (self.make_public, "active"),
2041
"contact_aim" : (self.aim_entry, "text"),
2042
"contact_irc" : (self.irc_entry, "text"),
2043
"contact_icq" : (self.icq_entry, "text"),
2044
"timer_start_active" : (self.start_timer.check, "active"),
2045
"timer_start_time" : (self.start_timer.entry, "text"),
2046
"timer_stop_active" : (self.stop_timer.check, "active"),
2047
"timer_stop_time" : (self.stop_timer.entry, "text"),
2048
"sc_admin_pass" : (self.admin_password_entry, "text"),
2049
"ic_expander" : (self.ic_expander, "expanded"),
2050
"conf_expander" : (self.details, "expanded"),
2051
"action_play_active" : (self.start_player_action, "active"),
2052
"action_play_which" : (self.start_player_action, "radioindex"),
2053
"action_record_active" : (self.start_recorder_action, "active"),
2054
"action_record_which" : (self.start_recorder_action, "radioindex"),
2055
"irc_data" : (self.ircpane, "marshall"),
2056
"details_nb" : (self.details_nb, "current_page"),
2059
self.objects.update(self.troubleshooting.objects)
2061
self.reconnection_dialog = ReconnectionDialog(self)
2063
class RecordTab(Tab):
2064
class RecordButtons(CategoryFrame):
2065
def cb_recbuttons(self, widget, userdata):
2066
changed_state = False
2067
if userdata == "rec":
2068
if widget.get_active():
2069
if not self.recording:
2070
sd = self.parentobject.source_dest
2071
if sd.streamtab is not None:
2072
sd.streamtab.start_stop_encoder(ENCODER_START)
2073
num_id = sd.streamtab.numeric_id
2076
self.parentobject.send("record_source=%d\nrecord_folder=%s\ncommand=recorder_start\n" % (
2078
sd.file_dialog.get_current_folder()))
2079
sd.file_dialog.response(gtk.RESPONSE_CLOSE)
2080
sd.set_sensitive(False)
2081
self.parentobject.time_indicator.set_sensitive(True)
2082
self.recording = True
2083
if self.parentobject.receive() == "failed":
2084
self.stop_button.clicked()
2086
if self.stop_pressed:
2087
self.stop_pressed = False
2088
if self.recording == True:
2089
self.recording = False
2090
self.parentobject.send("command=recorder_stop\n")
2091
self.parentobject.receive()
2092
if self.parentobject.source_dest.streamtab is not None:
2093
self.parentobject.source_dest.streamtab.start_stop_encoder(ENCODER_STOP)
2094
self.parentobject.source_dest.set_sensitive(True)
2095
self.parentobject.time_indicator.set_sensitive(False)
2096
if self.pause_button.get_active():
2097
self.pause_button.set_active(False)
2099
widget.set_active(True)
2100
elif userdata == "stop":
2102
self.stop_pressed = True
2103
self.record_button.set_active(False)
2105
self.pause_button.set_active(False)
2106
elif userdata == "pause":
2107
if self.pause_button.get_active():
2108
self.parentobject.send("command=recorder_pause\n")
2110
self.parentobject.send("command=recorder_unpause\n")
2111
self.parentobject.receive()
2112
def path2image(self, pathname):
2113
pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(pathname, 14, 14)
2115
image.set_from_pixbuf(pixbuf)
2118
def __init__(self, parent):
2119
CategoryFrame.__init__(self)
2120
self.parentobject = parent
2121
self.stop_pressed = False
2122
self.recording = False
2124
hbox.set_border_width(3)
2126
self.stop_button = gtk.Button()
2127
self.record_button = gtk.ToggleButton()
2128
self.pause_button = gtk.ToggleButton()
2129
for button, gname, signal, tip_text in (
2130
(self.stop_button, "stop", "clicked", _('Stop recording.')),
2131
(self.record_button, "rec", "toggled", _('Start recording.\n\nIf this button is greyed out it could mean the encoder settings are not valid. This can be fixed by using one of the approved sample rates for mp3 or by choosing a sensible samplerate and bitrate combination for Ogg.\n\nAlso check that you have write permission on the folder you have selected to record to.')),
2132
(self.pause_button, "pause", "toggled", _('Pause recording.'))):
2133
button.set_size_request(30, -1)
2134
button.add(self.path2image(FGlobs.pkgdatadir / (gname + ".png")))
2135
button.connect(signal, self.cb_recbuttons, gname)
2136
hbox.pack_start(button, False, False, 0)
2138
set_tip(button, tip_text)
2141
class TimeIndicator(gtk.Entry):
2142
def set_value(self, seconds):
2143
if self.oldvalue != seconds:
2144
self.oldvalue = seconds
2145
minutes, seconds = divmod(seconds, 60)
2146
hours, minutes = divmod(minutes, 60)
2147
days, hours = divmod(hours, 24)
2148
if days > 10: # shut off the recorder after 10 days continuous recording
2149
self.parentobject.record_buttons.stop_button.clicked()
2151
self.set_text("%dd:%02d:%02d" % (days, hours, minutes))
2153
self.set_text("%02d:%02d:%02d" % (hours, minutes, seconds))
2154
def button_press_cancel(self, widget, event):
2156
def __init__(self, parent):
2157
self.parentobject = parent
2158
gtk.Entry.__init__(self)
2159
self.set_width_chars(7)
2160
self.set_sensitive(False)
2161
self.set_editable(False)
2164
self.connect("button-press-event", self.button_press_cancel)
2165
set_tip(self, _('Recording time elapsed.'))
2166
class SourceDest(CategoryFrame):
2168
def set_sensitive(self, boolean):
2169
self.source_combo.set_sensitive(boolean)
2170
self.file_chooser_button.set_sensitive(boolean)
2171
def cb_source_combo(self, widget):
2172
if widget.get_active() > 0:
2173
self.streamtab = self.streamtabs[widget.get_active() - 1]
2175
self.streamtab = None
2176
self.parentobject.record_buttons.record_button.set_sensitive(self.streamtab is None or (self.cansave and ((self.streamtab.server_connect.flags() & gtk.SENSITIVE) or self.streamtab.recorder_valid_override)))
2177
def populate_stream_selector(self, text, tabs):
2178
self.streamtabs = tabs
2179
for index in range(len(tabs)):
2180
self.source_combo.append_text(" ".join((text, str(index + 1))))
2181
self.source_combo.connect("changed", self.cb_source_combo)
2182
self.source_combo.set_active(0)
2183
def cb_new_folder(self, filechooser):
2184
self.cansave = os.access(filechooser.get_current_folder(), os.W_OK)
2185
self.source_combo.emit("changed")
2186
def __init__(self, parent):
2187
self.parentobject = parent
2188
CategoryFrame.__init__(self)
2191
self.source_combo = gtk.combo_box_new_text()
2192
self.source_combo.append_text(" FLAC+CUE")
2193
hbox.pack_start(self.source_combo, False, False, 0)
2194
self.source_combo.show()
2195
arrow = gtk.Arrow(gtk.ARROW_RIGHT, gtk.SHADOW_IN)
2196
hbox.pack_start(arrow, False, False, 0)
2198
self.file_dialog = gtk.FileChooserDialog("", None, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
2199
self.file_dialog.set_do_overwrite_confirmation(True)
2200
self.file_chooser_button = gtk.FileChooserButton(self.file_dialog)
2201
# TC: Dialog title bar text.
2202
self.file_dialog.set_title(_('Select the folder to record to') + pm.title_extra)
2203
self.file_dialog.connect("current-folder-changed", self.cb_new_folder)
2204
self.file_dialog.set_current_folder(os.environ["HOME"])
2205
hbox.pack_start(self.file_chooser_button, True, True, 0)
2206
self.file_chooser_button.show()
2209
set_tip(self.source_combo, _("Choose which stream to record or the 24 bit FLAC option. If the stream isn't already running the encoder will be started automatically using whatever settings are currently configured."))
2210
set_tip(self.file_chooser_button, _('Choose which directory you want to save to. All file names will be in a timestamp format and have either an oga, mp3, or flac file extension. Important: you need to select a directory to which you have adequate write permission.'))
2211
def send(self, string_to_send):
2212
Tab.send(self, "dev_type=recorder\n" + string_to_send)
2214
return Tab.receive(self)
2215
def show_indicator(self, colour):
2216
Tab.show_indicator(self, colour)
2217
self.scg.parent.recording_panel.indicator[self.numeric_id].set_indicator(colour)
2220
def __init__(self, scg, numeric_id, indicator_lookup):
2221
Tab.__init__(self, scg, numeric_id, indicator_lookup)
2223
self.show_indicator("clear")
2224
self.tab_type = "recorder"
2226
hbox.set_spacing(10)
2227
self.pack_start(hbox, False, False, 0)
2229
self.source_dest = self.SourceDest(self)
2230
hbox.pack_start(self.source_dest, True, True, 0)
2231
self.source_dest.show()
2232
self.time_indicator = self.TimeIndicator(self)
2233
hbox.pack_start(self.time_indicator, False, False, 0)
2234
self.time_indicator.show()
2235
self.record_buttons = self.RecordButtons(self)
2236
hbox.pack_start(self.record_buttons, False, False, 0)
2237
self.record_buttons.show()
2238
self.objects = { "recording_source": (self.source_dest.source_combo, "active"),
2239
"recording_directory": (self.source_dest.file_dialog, "directory") }
2241
class TabFrame(ModuleFrame):
2242
def __init__(self, scg, frametext, q_tabs, tabtype, indicatorlist, tab_tip_text):
2243
ModuleFrame.__init__(self, " %s " % frametext)
2244
self.notebook = gtk.Notebook()
2245
self.notebook.set_border_width(8)
2246
self.vbox.add(self.notebook)
2247
self.notebook.show()
2249
self.indicator_image_qty = len(indicatorlist)
2250
for index in range(q_tabs):
2251
labelbox = gtk.HBox()
2252
labelbox.set_spacing(3)
2253
numlabel = gtk.Label(str(index + 1))
2254
labelbox.add(numlabel)
2256
indicator_lookup = {}
2257
for colour, indicator in indicatorlist:
2259
pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(
2260
FGlobs.pkgdatadir / (indicator + ".png"), 16, 16)
2261
image.set_from_pixbuf(pixbuf)
2263
indicator_lookup[colour] = image
2264
self.tabs.append(tabtype(scg, index, indicator_lookup))
2265
self.notebook.append_page(self.tabs[-1], labelbox)
2267
set_tip(labelbox, tab_tip_text)
2269
class StreamTabFrame(TabFrame):
2270
def forall(self, widget, f, *args):
2271
for cb, tab in zip(self.togglelist, self.tabs):
2275
def cb_metadata_group_set(self, tab):
2276
tab.metadata.set_text(self.metadata_group.get_text())
2278
def cb_metadata_group_update(self, tab):
2279
self.cb_metadata_group_set(tab)
2280
tab.metadata_update.clicked()
2282
def cb_connect_toggle(self, tab, val):
2283
if tab.server_connect.flags() & gtk.SENSITIVE:
2284
tab.server_connect.set_active(val)
2286
def cb_kick_group(self, tab):
2287
tab.kick_incumbent.clicked()
2289
def cb_group_safety(self, widget):
2290
sens = widget.get_active()
2291
for each in (self.disconnect_group, self.kick_group):
2292
each.set_sensitive(sens)
2294
def __init__(self, scg, frametext, q_tabs, tabtype, indicatorlist, tab_tip_text):
2295
TabFrame.__init__(self, scg, frametext, q_tabs, tabtype, indicatorlist, tab_tip_text)
2297
outerframe = gtk.Frame()
2298
set_tip(outerframe, _('Perform operations on multiple servers in unison.'))
2299
outerframe.set_border_width(8)
2300
outerframe.set_shadow_type(gtk.SHADOW_OUT)
2302
gvbox.set_border_width(8)
2303
gvbox.set_spacing(8)
2304
outerframe.add(gvbox)
2308
gvbox.pack_start(hbox, False)
2310
self.connect_group = gtk.Button(_("Connect"))
2311
self.connect_group.connect("clicked", self.forall, self.cb_connect_toggle, True)
2312
hbox.add(self.connect_group)
2313
self.connect_group.show()
2318
ihbox.set_border_width(3)
2319
ihbox.set_spacing(6)
2322
self.group_safety = gtk.CheckButton()
2323
self.group_safety.connect("toggled", self.cb_group_safety)
2324
ihbox.pack_start(self.group_safety, False)
2325
self.group_safety.show()
2326
self.disconnect_group = gtk.Button(_("Disconnect"))
2327
self.disconnect_group.connect("clicked", self.forall, self.cb_connect_toggle, False)
2328
self.disconnect_group.connect("clicked", lambda x: self.group_safety.set_active(False))
2329
self.disconnect_group.set_sensitive(False)
2330
ihbox.add(self.disconnect_group)
2331
self.disconnect_group.show()
2332
self.kick_group = gtk.Button(_("Kick Incumbents"))
2333
self.kick_group.connect("clicked", self.forall, self.cb_kick_group)
2334
self.kick_group.connect("clicked", lambda x: self.group_safety.set_active(False))
2335
self.kick_group.set_sensitive(False)
2336
ihbox.add(self.kick_group)
2337
self.kick_group.show()
2340
label = gtk.Label("%s " % _('Metadata:'))
2341
hbox.pack_start(label, False)
2343
self.metadata_group = HistoryEntryWithMenu()
2344
hbox.pack_start(self.metadata_group)
2345
self.metadata_group.show()
2346
self.metadata_group_set = gtk.Button()
2347
image = gtk.image_new_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU)
2348
self.metadata_group_set.set_image(image)
2350
self.metadata_group_set.connect("clicked", self.forall, self.cb_metadata_group_set)
2351
hbox.pack_start(self.metadata_group_set, False)
2352
self.metadata_group_set.show()
2353
self.metadata_group_update = gtk.Button()
2354
image = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
2355
self.metadata_group_update.set_image(image)
2357
self.metadata_group_update.connect("clicked", self.forall, self.cb_metadata_group_update)
2358
hbox.pack_start(self.metadata_group_update, False)
2359
self.metadata_group_update.show()
2360
gvbox.pack_start(hbox, False)
2362
self.vbox.pack_start(outerframe, False)
2364
self.vbox.reorder_child(outerframe, 0)
2365
self.objects = { "group_metadata": (self.metadata_group, "history") }
2366
self.togglelist = [gtk.CheckButton(str(x + 1)) for x in range(q_tabs)]
2368
label = gtk.Label(" %s " % _('Group Controls'))
2369
hbox.pack_start(label, False)
2371
for i, cb in enumerate(self.togglelist):
2372
hbox.pack_start(cb, False)
2374
self.objects["group_toggle_" + str(i + 1)] = (cb, "active")
2376
hbox.pack_end(spc, False, False, 2)
2378
outerframe.set_label_widget(hbox)
2383
class SourceClientGui:
2384
server_errmsg = "idjc: idjcsourceclient appears to have crashed -- possible segfault"
2385
unexpected_reply = "unexpected reply from idjcsourceclient"
2389
self.led_alternate = not self.led_alternate
2390
streaming = recording = False
2391
# update the recorder LED indicators
2392
for rectab in self.recordtabframe.tabs:
2393
self.send("dev_type=recorder\ntab_id=%d\ncommand=get_report\n" % rectab.numeric_id)
2395
reply = self.receive()
2396
if reply == "succeeded" or reply == "failed":
2398
if reply.startswith("recorder%dreport=" % rectab.numeric_id):
2399
recorder_state, recorded_seconds = reply.split("=")[1].split(":")
2400
rectab.show_indicator(("clear", "red", "amber", "clear")[int(recorder_state)])
2401
rectab.time_indicator.set_value(int(recorded_seconds))
2402
if recorder_state != "0":
2404
update_listeners = False
2406
for streamtab in self.streamtabframe.tabs:
2407
cp = streamtab.connection_pane
2408
cp.timer.run() # obtain connection stats
2410
update_listeners = True
2411
l_count += cp.listeners
2413
self.send("dev_type=streamer\ntab_id=%d\ncommand=get_report\n" % streamtab.numeric_id)
2414
reply = self.receive()
2415
if reply != "failed":
2417
if reply.startswith("streamer%dreport=" % streamtab.numeric_id):
2418
streamer_state, stream_sendbuffer_pc, brand_new = reply.split("=")[1].split(":")
2419
state = int(streamer_state)
2420
streamtab.show_indicator(("clear", "amber", "green", "clear")[state])
2421
streamtab.ircpane.connections_controller.set_stream_active(state > 1)
2422
mi = self.parent.stream_indicator[streamtab.numeric_id]
2423
if (streamer_state == "2"):
2425
mi.set_value(int(stream_sendbuffer_pc))
2426
if int(stream_sendbuffer_pc) >= 100 and self.led_alternate:
2427
if streamtab.troubleshooting.sbf_discard_audio.get_active():
2428
streamtab.show_indicator("amber")
2431
streamtab.server_connect.set_active(False)
2432
streamtab.server_connect.set_active(True)
2433
print "remade the connection because stream buffer was full"
2437
mi.set_active(False)
2439
if brand_new == "1":
2440
# connection has just been made, do user requested actions at this time
2441
streamtab.start_recorder_action.activate()
2442
streamtab.start_player_action.activate()
2443
streamtab.reconnection_dialog.deactivate()
2444
if streamer_state != "0":
2446
elif streamtab.server_connect.get_active():
2447
streamtab.server_connect.set_active(False)
2448
streamtab.reconnection_dialog.activate()
2450
print "sourceclientgui.monitor: bad reply for streamer data:", reply
2452
print "sourceclientgui.monitor: failed to get a report from the streamer"
2453
# the connection start/stop timers are processed here
2454
if streamtab.start_timer.get_active():
2455
diff = time.localtime(time.time() - streamtab.start_timer.get_seconds_past_midnight())
2456
# check hours, minutes, seconds for midnightness
2457
if not (diff[3] or diff[4] or diff[5]):
2458
streamtab.start_timer.check.set_active(False)
2459
if streamtab.kick_before_start.get_active():
2460
streamtab.cb_kick_incumbent(None, streamtab.deferred_connect)
2462
streamtab.server_connect.set_active(True)
2463
if streamtab.stop_timer.get_active():
2464
diff = time.localtime(int(time.time()) - streamtab.stop_timer.get_seconds_past_midnight())
2465
if not (diff[3] or diff[4] or diff[5]):
2466
streamtab.server_connect.set_active(False)
2467
streamtab.stop_timer.check.set_active(False)
2468
self.autoshutdown_dialog.present()
2469
self.is_streaming = streaming
2470
self.is_recording = recording
2471
streamtab.reconnection_dialog.run()
2472
if update_listeners:
2473
self.parent.listener_indicator.set_text(str(l_count))
2475
def stop_streaming_all(self):
2476
for streamtab in self.streamtabframe.tabs:
2477
streamtab.server_connect.set_active(False)
2478
def stop_irc_all(self):
2479
for streamtab in self.streamtabframe.tabs:
2480
streamtab.ircpane.connections_controller.cleanup()
2481
def stop_recording_all(self):
2482
for rectab in self.recordtabframe.tabs:
2483
rectab.record_buttons.stop_button.clicked()
2484
def stop_test_monitor_all(self):
2485
for streamtab in self.streamtabframe.tabs:
2486
streamtab.test_monitor.set_active(False)
2488
self.stop_recording_all()
2489
self.stop_streaming_all()
2491
self.stop_test_monitor_all()
2492
gobject.source_remove(self.monitor_source_id)
2494
if self.parent.session_loaded:
2495
self.parent.destroy()
2497
self.parent.destroy_hard()
2500
if not self.comms_reply_pending:
2501
print "sourceclientgui.receive: nothing to receive"
2505
reply = self.comms_rply.readline()
2508
if reply.startswith("idjcsc: "):
2510
if reply == "succeeded" or reply == "failed":
2511
self.comms_reply_pending = False
2514
print self.unexpected_reply, reply
2515
if reply == "" or reply == "Segmentation Fault\n":
2516
self.comms_reply_pending = False
2518
def send(self, string_to_send):
2519
while self.comms_reply_pending: # dump unused replies from previous send
2521
if not "tab_id=" in string_to_send:
2522
string_to_send = "tab_id=-1\n" + string_to_send
2524
self.comms_cmd.write(string_to_send + "end\n")
2525
self.comms_cmd.flush()
2526
self.comms_reply_pending = True
2527
except (ValueError, IOError):
2528
print "sourceclientgui.send: send failed - idjcsourceclient crashed"
2529
self.source_client_crash_count += 1
2530
self.source_client_close()
2531
print self.server_errmsg
2533
if self.source_client_crash_count == 3:
2534
print "idjcsourceclient is crashing repeatedly - exiting\n"
2536
self.source_client_open()
2537
self.comms_reply_pending = False
2539
if self.source_client_crash_count:
2540
if time.time() > self.uptime + 15.0:
2541
self.source_client_crash_count -= 1
2542
self.uptime = time.time()
2544
def new_metadata(self, artist, title, album, songname):
2546
artist_title = artist + " - " + title
2548
artist_title = title
2549
if not self.parent.prefs_window.mp3_utf8.get_active():
2550
artist_title_lat1 = artist_title.decode("utf-8", "replace").encode("iso8859-1", "replace")
2552
artist_title_lat1 = artist_title
2554
self.send("artist=%s\ntitle=%s\nalbum=%s\nartist_title_lat1=%s\ncommand=new_song_metadata\n" % (artist.strip(), title.strip(), album.strip(), artist_title_lat1.strip()))
2555
if self.receive() == "succeeded":
2556
print "updated song metadata successfully"
2558
common = {"artist": artist, "title": title, "album": album, "songname": songname}
2559
for tab in self.streamtabframe.tabs: # Update the custom metadata on all stream tabs.
2560
tab.metadata_update.clicked()
2561
ircmetadata = {"djname": tab.dj_name_entry.get_text(),
2562
"description": tab.description_entry.get_text(),
2563
"url": tab.listen_url_entry.get_text()
2565
ircmetadata.update(common)
2567
tab.ircpane.connections_controller.new_metadata(ircmetadata)
2569
def source_client_open(self):
2571
sp_sc = subprocess.Popen([FGlobs.libexecdir / "idjcsourceclient"],
2572
bufsize = 4096, stdin = subprocess.PIPE, stdout = subprocess.PIPE, close_fds = True)
2573
except Exception, inst:
2575
print "unable to open a pipe to the sourceclient module"
2577
(self.comms_cmd, self.comms_rply) = (sp_sc.stdin, sp_sc.stdout)
2578
self.comms_reply_pending = True
2579
reply = self.receive()
2580
if reply != "succeeded":
2581
print self.server_errmsg
2583
self.send("encoders=%d\nstreamers=%d\nrecorders=%d\ncommand=threads_init\n" % (
2584
PGlobs.num_encoders, PGlobs.num_streamers, PGlobs.num_recorders))
2585
if self.receive() != "succeeded":
2586
print self.unexpected_reply
2587
print "failed to initialise threads\n"
2589
self.send("command=jack_samplerate_request\n")
2590
reply = self.receive()
2591
if reply != "failed" and self.receive() == "succeeded":
2592
sample_rate_string = reply
2594
print self.unexpected_reply
2595
print "failed to obtain the sample rate"
2597
if not sample_rate_string.startswith("sample_rate="):
2598
print self.unexpected_reply
2599
print "sample rate reply contains the following:", sample_rate_string
2601
self.send("command=encoder_lame_availability\n")
2602
reply = self.receive()
2603
if reply != "failed" and self.receive() == "succeeded" and reply.startswith("lame_available="):
2605
if reply[15] == "1":
2610
print self.unexpected_reply
2612
print "threads initialised"
2613
self.jack_sample_rate = int(sample_rate_string[12:])
2614
print "jack sample rate is", self.jack_sample_rate
2616
for streamtab in self.streamtabframe.tabs:
2617
streamtab.stream_resample_frame.jack_sample_rate = self.jack_sample_rate
2618
streamtab.stream_resample_frame.resample_dummy_object.clicked()
2619
# update the stream tabs with the current jack sample rate
2620
except (NameError, AttributeError):
2621
# If this is the initial call the stream tabs will not exist yet.
2623
self.uptime = time.time()
2625
def source_client_close(self):
2631
self.comms_cmd.close()
2633
def cb_delete_event(self, widget, event, data = None):
2637
def save_session_settings(self):
2638
try: # check the following are initilised before proceeding
2639
tabframes = (self, self.streamtabframe, self.recordtabframe)
2640
except AttributeError:
2641
return # cancelled save
2644
with open(pm.basedir / "s_data", "w") as f:
2645
for tabframe in tabframes:
2646
for tab in tabframe.tabs:
2647
f.write("".join(("[", tab.tab_type, " ", str(tab.numeric_id), "]\n")))
2648
for lvalue, (widget, method) in tab.objects.iteritems():
2649
if type(method) == tuple:
2650
rvalue = widget.__getattribute__(method[1])()
2651
elif method == "active":
2652
rvalue = str(int(widget.get_active()))
2653
elif method == "text":
2654
rvalue = widget.get_text()
2655
elif method == "value":
2656
rvalue = str(widget.get_value())
2657
elif method == "expanded":
2658
rvalue = str(int(widget.get_expanded()))
2659
elif method == "notebookpage":
2660
rvalue = str(widget.get_current_page())
2661
elif method == "password":
2662
rvalue = widget.get_text()
2663
elif method == "history":
2664
rvalue = widget.get_history()
2665
elif method == "radioindex":
2666
rvalue = str(widget.get_radio_index())
2667
elif method == "current_page":
2668
rvalue = str(widget.get_current_page())
2669
elif method == "directory":
2670
rvalue = widget.get_filename() or ""
2671
elif method == "filename":
2672
rvalue = widget.get_filename() or ""
2673
elif method == "marshall":
2674
rvalue = widget.marshall()
2676
print "unsupported", lvalue, widget, method
2678
if method != "password" or self.parent.prefs_window.keeppass.get_active():
2679
f.write("".join((lvalue, "=", rvalue, "\n")))
2682
print "error attempting to write file: serverdata"
2684
def load_previous_session(self):
2686
with open(pm.basedir / "s_data") as f:
2693
line = line[:-1] # strip off the newline character
2696
if line.startswith("[") and line.endswith("]"):
2698
name, numeric_id = line[1:-1].split(" ")
2700
print "malformed line:", line, "in serverdata file"
2703
if name == "server_window":
2705
elif name == "streamer":
2706
tabframe = self.streamtabframe
2707
elif name == "recorder":
2708
tabframe = self.recordtabframe
2710
print "unsupported element:", line, "in serverdata file"
2712
if tabframe is not None:
2714
tab = tabframe.tabs[int(numeric_id)]
2716
print "unsupported tab number:", line, "in serverdata file"
2719
if tabframe is not None:
2721
lvalue, rvalue = line.split("=", 1)
2723
print "not a valid key, value pair:", line, "in serverdata file"
2726
print "key value is missing:", line, "in serverdata file"
2729
(widget, method) = tab.objects[lvalue]
2731
print "key value not recognised:", line, "in serverdata file"
2734
int_rvalue = int(rvalue)
2738
float_rvalue = float(rvalue)
2741
if type(method) == tuple:
2742
widget.__getattribute__(method[0])(rvalue)
2743
elif method == "active":
2744
if int_rvalue is not None:
2745
widget.set_active(int_rvalue)
2746
elif method == "expanded":
2747
if int_rvalue is not None:
2748
widget.set_expanded(int_rvalue)
2749
elif method == "value":
2750
if float_rvalue is not None:
2751
widget.set_value(float_rvalue)
2752
elif method == "notebookpage":
2753
if int_rvalue is not None:
2754
widget.set_current_page(int_rvalue)
2755
elif method == "radioindex":
2756
if int_rvalue is not None:
2757
widget.set_radio_index(int_rvalue)
2758
elif method == "current_page":
2759
widget.set_current_page(int_rvalue)
2760
elif method == "text":
2761
widget.set_text(rvalue)
2762
elif method == "password":
2763
widget.set_text(rvalue)
2764
elif method == "history":
2765
widget.set_history(rvalue)
2766
elif method == "directory":
2768
widget.set_current_folder(rvalue)
2769
elif method == "filename":
2771
rvalue = widget.set_filename(rvalue)
2772
elif method == "marshall":
2773
widget.unmarshall(rvalue)
2775
print "method", method, "is unsupported at this time hence widget pertaining to", lvalue, "will not be set"
2776
except Exception as e:
2777
if isinstance(e, IOError):
2780
traceback.print_exc()
2783
def cb_after_realize(self, widget):
2785
#widget.resize(int(self.win_x), 1)
2786
self.streamtabframe.connect_group.grab_focus()
2788
def cb_stream_details_expand(self, expander, param_spec, next_expander, sw):
2789
if expander.get_expanded():
2794
if expander.get_expanded() == next_expander.get_expanded():
2795
if not expander.get_expanded():
2796
self.window.resize((self.wst.get_x()), 1)
2800
next_expander.set_expanded(expander.get_expanded())
2802
def cb_stream_controls_expand(self, expander, param_spec, next_expander, frame, details_shown):
2803
if expander.get_expanded():
2808
if expander.get_expanded() == next_expander.get_expanded():
2809
self.window.resize((self.wst.get_x()), 1)
2811
next_expander.set_expanded(expander.get_expanded())
2813
def update_metadata(self, text=None, filter=None):
2814
for tab in self.streamtabframe.tabs:
2815
if filter is None or str(tab.numeric_id) in filter:
2816
if text is not None:
2817
tab.metadata.set_text(text)
2818
tab.metadata_update.clicked()
2820
def __init__(self, parent):
2821
self.parent = parent
2822
parent.server_window = self
2823
self.source_client_crash_count = 0
2824
self.source_client_open()
2825
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
2826
self.parent.window_group.add_window(self.window)
2827
# TC: Window title bar text.
2828
self.window.set_title(_('IDJC Output') + pm.title_extra)
2829
self.window.set_destroy_with_parent(True)
2830
self.window.set_border_width(11)
2831
self.window.set_resizable(True)
2832
self.window.connect_after("realize", self.cb_after_realize)
2833
self.window.connect("delete_event", self.cb_delete_event)
2834
self.wst = WindowSizeTracker(self.window)
2836
vbox.set_spacing(10)
2837
self.window.add(vbox)
2839
self.recordtabframe = TabFrame(self, _('Record'), PGlobs.num_recorders, RecordTab, (
2840
("clear", "led_unlit_clear_border_64x64"),
2841
("amber", "led_lit_amber_black_border_64x64"),
2842
("red", "led_lit_red_black_border_64x64")),
2843
_('Each one of these tabs represents a separate stream recorder. The LED indicator colours represent the following: Clear=Stopped Yellow=Paused Red=Recording.'))
2844
self.streamtabframe = StreamTabFrame(self, _('Stream'), PGlobs.num_streamers, StreamTab, (
2845
("clear", "led_unlit_clear_border_64x64"),
2846
("amber", "led_lit_amber_black_border_64x64"),
2847
("green", "led_lit_green_black_border_64x64")),
2848
_('Each one of these tabs represents a separate radio streamer. The LED indicator colours represent the following: Clear=No connection Yellow=Awaiting authentication. Green=Connected. Flashing=Packet loss due to a bad connection.'))
2850
tab = self.streamtabframe.tabs[-1]
2851
for next_tab in self.streamtabframe.tabs:
2852
tab.details.connect("notify::expanded", self.cb_stream_details_expand, next_tab.details, tab.details_nb)
2853
tab.ic_expander.connect("notify::expanded", self.cb_stream_controls_expand, next_tab.ic_expander, tab.ic_frame, self.streamtabframe.tabs[0].details.get_expanded)
2856
self.streamtabframe.set_sensitive(True)
2857
vbox.pack_start(self.streamtabframe, True, True, 0)
2858
self.streamtabframe.show()
2859
for rectab in self.recordtabframe.tabs:
2860
rectab.source_dest.populate_stream_selector(_(' Stream '), self.streamtabframe.tabs)
2861
vbox.pack_start(self.recordtabframe, False, False, 0)
2862
if PGlobs.num_recorders:
2863
self.recordtabframe.show()
2865
self.tabs = (self, ) #
2866
self.numeric_id = 0 # pretend to be a tabframe and its tab for save/load purposes
2867
self.tab_type = "server_window" #
2868
self.objects = { "wst" : (self.wst, "text"),
2869
"streamer_page": (self.streamtabframe.notebook, "notebookpage"),
2870
"recorder_page": (self.recordtabframe.notebook, "notebookpage"),
2871
"controls_shown": (self.streamtabframe.tabs[0].ic_expander, "expanded") }
2872
self.objects.update(self.streamtabframe.objects)
2873
self.load_previous_session()
2874
self.is_streaming = False
2875
self.is_recording = False
2876
self.led_alternate = False
2877
self.last_message_time = 0
2878
self.connection_string = None
2879
self.is_shoutcast = False
2881
self.dialog_group = dialog_group()
2882
self.disconnected_dialog = disconnection_notification_dialog(self.dialog_group, self.parent.window_group, "", _('<span weight="bold" size="12000">A connection to a radio server has failed.</span>\n\nReconnection will not be attempted.'))
2884
self.autoshutdown_dialog = disconnection_notification_dialog(self.dialog_group, self.parent.window_group, "", _('<span weight="bold" size="12000">A scheduled stream disconnection has occurred.</span>'))
2886
self.monitor_source_id = gobject.timeout_add(250, self.monitor)
2887
self.window.realize() # prevent rendering bug and problems with sizegroups on certain widgets