1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#!/usr/bin/env python
# encoding: UTF-8
# api: python
# type: application
# title: streamtuner2
# description: directory browser for internet radio / audio streams
# depends: gtk, pygtk, xml.dom.minidom, threading, lxml, pyquery, kronos
# version: 2.0.9.5
# author: mario salzer
# license: public domain
# url: http://freshmeat.net/projects/streamtuner2
# config: <env name="http_proxy" value="" description="proxy for HTTP access" /> <env name="XDG_CONFIG_HOME" description="relocates user .config subdirectory" />
# category: multimedia
#
#
|
|
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#!/usr/bin/env python
# encoding: UTF-8
# api: python
# type: application
# title: streamtuner2
# description: directory browser for internet radio / audio streams
# depends: gtk, pygtk, xml.dom.minidom, threading, lxml, pyquery, kronos
# version: 2.0.9.7
# author: mario salzer
# license: public domain
# url: http://freshmeat.net/projects/streamtuner2
# config: <env name="http_proxy" value="" description="proxy for HTTP access" /> <env name="XDG_CONFIG_HOME" description="relocates user .config subdirectory" />
# category: multimedia
#
#
|
︙ | | | ︙ | |
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
# standard modules
import sys
import os, os.path
import re
import copy
import urllib
# threading or processing module
try:
from processing import Process as Thread
except:
from threading import Thread
Thread.stop = lambda self: None
# add library path
sys.path.insert(0, "/usr/share/streamtuner2") # pre-defined directory for modules
sys.path.insert(0, ".") # pre-defined directory for modules
# gtk modules
from mygtk import pygtk, gtk, gobject, ui_file, mygtk
# custom modules
from config import conf # initializes itself, so all conf.vars are available right away
from config import __print__, dbg
import http
import action # needs workaround... (action.main=main)
from channels import *
import favicon
#from pq import pq
# this represents the main window
# and also contains most application behaviour
main = None
class StreamTunerTwo(gtk.Builder):
|
<
<
|
|
|
<
|
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
# standard modules
import sys
import os, os.path
import re
# threading or processing module
try:
from processing import Process as Thread
except:
from threading import Thread
Thread.stop = lambda self: None
# add library path
sys.path.insert(0, "/usr/share/streamtuner2") # pre-defined directory for modules
sys.path.insert(0, ".") # development module path
# gtk modules
from mygtk import pygtk, gtk, gobject, ui_file, mygtk, ver as GTK_VER
# custom modules
from config import conf # initializes itself, so all conf.vars are available right away
from config import __print__, dbg
import ahttp
import action # needs workaround... (action.main=main)
from channels import *
import favicon
# this represents the main window
# and also contains most application behaviour
main = None
class StreamTunerTwo(gtk.Builder):
|
︙ | | | ︙ | |
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
self.current_channel = self.current_channel_gtk()
try: self.channel().first_show()
except: __print__(dbg.INIT, "main.__init__: current_channel.first_show() initialization error")
# bind gtk/glade event names to functions
gui_startup(19/20.0)
self.connect_signals(dict( {
"gtk_main_quit" : self.gtk_main_quit, # close window
# treeviews / notebook
"on_stream_row_activated" : self.on_play_clicked, # double click in a streams list
"on_category_clicked": self.on_category_clicked, # new selection in category list
"on_notebook_channels_switch_page": self.channel_switch, # channel notebook tab changed
"station_context_menu": lambda tv,ev: station_context_menu(tv,ev),
# toolbar
|
|
|
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
self.current_channel = self.current_channel_gtk()
try: self.channel().first_show()
except: __print__(dbg.INIT, "main.__init__: current_channel.first_show() initialization error")
# bind gtk/glade event names to functions
gui_startup(19/20.0)
self.connect_signals(dict( list({
"gtk_main_quit" : self.gtk_main_quit, # close window
# treeviews / notebook
"on_stream_row_activated" : self.on_play_clicked, # double click in a streams list
"on_category_clicked": self.on_category_clicked, # new selection in category list
"on_notebook_channels_switch_page": self.channel_switch, # channel notebook tab changed
"station_context_menu": lambda tv,ev: station_context_menu(tv,ev),
# toolbar
|
︙ | | | ︙ | |
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
|
"search_google": search.google,
"search_cancel": search.cancel,
"true": lambda w,*args: True,
"streamedit_open": streamedit.open,
"streamedit_save": streamedit.save,
"streamedit_new": streamedit.new,
"streamedit_cancel": streamedit.cancel,
}.items() + self.add_signals.items() ))
# actually display main window
gui_startup(99/100.0)
self.win_streamtuner2.show()
# WHY DON'T YOU WANT TO WORK?!
#self.shoutcast.gtk_list.set_enable_search(True)
#self.shoutcast.gtk_list.set_search_column(4)
#-- Shortcut for glade.get_widget()
# Allows access to widgets as direct attributes instead of using .get_widget()
# Also looks in self.channels[] for the named channel plugins
def __getattr__(self, name):
if (self.channels.has_key(name)):
return self.channels[name] # like self.shoutcast
else:
return self.get_object(name) # or gives an error if neither exists
# custom-named widgets are available from .widgets{} not via .get_widget()
def get_widget(self, name):
if self.widgets.has_key(name):
return self.widgets[name]
else:
return gtk.Builder.get_object(self, name)
|
|
|
|
|
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
|
"search_google": search.google,
"search_cancel": search.cancel,
"true": lambda w,*args: True,
"streamedit_open": streamedit.open,
"streamedit_save": streamedit.save,
"streamedit_new": streamedit.new,
"streamedit_cancel": streamedit.cancel,
}.items() ) + list( self.add_signals.items() ) ))
# actually display main window
gui_startup(99/100.0)
self.win_streamtuner2.show()
# WHY DON'T YOU WANT TO WORK?!
#self.shoutcast.gtk_list.set_enable_search(True)
#self.shoutcast.gtk_list.set_search_column(4)
#-- Shortcut for glade.get_widget()
# Allows access to widgets as direct attributes instead of using .get_widget()
# Also looks in self.channels[] for the named channel plugins
def __getattr__(self, name):
if (name in self.channels):
return self.channels[name] # like self.shoutcast
else:
return self.get_object(name) # or gives an error if neither exists
# custom-named widgets are available from .widgets{} not via .get_widget()
def get_widget(self, name):
if name in self.widgets:
return self.widgets[name]
else:
return gtk.Builder.get_object(self, name)
|
︙ | | | ︙ | |
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
|
gtk.rc_parse(f)
pass
# end application and gtk+ main loop
def gtk_main_quit(self, widget, *x):
if conf.auto_save_appstate:
self.app_state(widget)
gtk.main_quit()
|
>
|
>
>
|
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
|
gtk.rc_parse(f)
pass
# end application and gtk+ main loop
def gtk_main_quit(self, widget, *x):
if conf.auto_save_appstate:
try: # doesn't work with gtk3 yet (probably just hooking at the wrong time)
self.app_state(widget)
except:
None
gtk.main_quit()
|
︙ | | | ︙ | |
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
|
# right click in streams/stations TreeView
def station_context_menu(treeview, event):
# right-click ?
if event.button >= 3:
path = treeview.get_path_at_pos(int(event.x), int(event.y))[0]
treeview.grab_focus()
treeview.set_cursor(path, None, False)
main.streamactions.popup(None, None, None, event.button, event.time)
return None
# we need to pass on to normal left-button signal handler
else:
return False
# this works better as callback function than as class - because of False/Object result for event trigger
# encapsulates references to gtk objects AND properties in main window
class auxiliary_window(object):
def __getattr__(self, name):
if main.__dict__.has_key(name):
return main.__dict__[name]
elif StreamTunerTwo.__dict__.has_key(name):
return StreamTunerTwo.__dict__[name]
else:
return main.get_widget(name)
""" allows to use self. and main. almost interchangably """
|
|
>
>
>
>
|
|
|
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
|
# right click in streams/stations TreeView
def station_context_menu(treeview, event):
# right-click ?
if event.button >= 3:
path = treeview.get_path_at_pos(int(event.x), int(event.y))[0]
treeview.grab_focus()
treeview.set_cursor(path, None, False)
main.streamactions.popup(
parent_menu_shell=None, parent_menu_item=None, func=None,
button=event.button, activate_time=event.time,
data=None
)
return None
# we need to pass on to normal left-button signal handler
else:
return False
# this works better as callback function than as class - because of False/Object result for event trigger
# encapsulates references to gtk objects AND properties in main window
class auxiliary_window(object):
def __getattr__(self, name):
if name in main.__dict__:
return main.__dict__[name]
elif name in StreamTunerTwo.__dict__:
return StreamTunerTwo.__dict__[name]
else:
return main.get_widget(name)
""" allows to use self. and main. almost interchangably """
|
︙ | | | ︙ | |
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
|
def hide(self, *args):
self.win_config.hide()
return True
# set/load values between gtk window and conf. dict
def apply(self, config, prefix="config_", save=0):
for key,val in config.iteritems():
# map non-alphanumeric chars from config{} to underscores in according gtk widget names
id = re.sub("[^\w]", "_", key)
w = main.get_widget(prefix + id)
__print__(dbg.CONF, "config", ("save" if save else "load"), prefix+id, w, val)
# recurse into dictionaries, transform: conf.play.audio/mp3 => conf.play_audio_mp3
if (type(val) == dict):
self.apply(val, prefix + id + "_", save)
|
|
|
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
|
def hide(self, *args):
self.win_config.hide()
return True
# set/load values between gtk window and conf. dict
def apply(self, config, prefix="config_", save=0):
for key,val in config.items():
# map non-alphanumeric chars from config{} to underscores in according gtk widget names
id = re.sub("[^\w]", "_", key)
w = main.get_widget(prefix + id)
__print__(dbg.CONF, "config", ("save" if save else "load"), prefix+id, w, val)
# recurse into dictionaries, transform: conf.play.audio/mp3 => conf.play_audio_mp3
if (type(val) == dict):
self.apply(val, prefix + id + "_", save)
|
︙ | | | ︙ | |
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
|
# add configuration setting definitions from plugins
once = 0
def add_plugins(self):
if self.once:
return
for name,enabled in conf.plugins.iteritems():
# add plugin load entry
if name:
label = ("enable ⎗ %s channel" if self.channels.get(name) else "use ⎗ %s plugin")
cb = gtk.ToggleButton(label=label % name)
self.add_( "config_plugins_"+name, cb )#, label=None, color="#ddd" )
|
|
|
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
|
# add configuration setting definitions from plugins
once = 0
def add_plugins(self):
if self.once:
return
for name,enabled in conf.plugins.items():
# add plugin load entry
if name:
label = ("enable ⎗ %s channel" if self.channels.get(name) else "use ⎗ %s plugin")
cb = gtk.ToggleButton(label=label % name)
self.add_( "config_plugins_"+name, cb )#, label=None, color="#ddd" )
|
︙ | | | ︙ | |
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
|
# First we'll generate a list of current bookmark stream urls, and then
# remove all but those from the currently UPDATED_channel + category.
# This step is most likely redundant, but prevents accidently re-rewriting
# stations that are in two channels (=duplicates with different PLS urls).
check = {"http//": "[row]"}
check = dict((row["url"],row) for row in fav)
# walk through all channels/streams
for chname,channel in main.channels.iteritems():
for cat,streams in channel.streams.iteritems():
# keep the potentially changed rows
if (chname == updated_channel) and (cat == updated_category):
freshened_streams = streams
# remove unchanged urls/rows
else:
|
|
|
|
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
|
# First we'll generate a list of current bookmark stream urls, and then
# remove all but those from the currently UPDATED_channel + category.
# This step is most likely redundant, but prevents accidently re-rewriting
# stations that are in two channels (=duplicates with different PLS urls).
check = {"http//": "[row]"}
check = dict((row["url"],row) for row in fav)
# walk through all channels/streams
for chname,channel in main.channels.items():
for cat,streams in channel.streams.items():
# keep the potentially changed rows
if (chname == updated_channel) and (cat == updated_category):
freshened_streams = streams
# remove unchanged urls/rows
else:
|
︙ | | | ︙ | |
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
|
#-- run main ---------------------------------------------
if __name__ == "__main__":
#-- global configuration settings
"conf = Config()" # already happened with "from config import conf"
# graphical
if len(sys.argv) < 2:
# prepare for threading in Gtk+ callbacks
gobject.threads_init()
gui_startup(1/100.0)
# prepare main window
main = StreamTunerTwo()
# module coupling
action.main = main # action (play/record) module needs a reference to main window for gtk interaction and some URL/URI callbacks
action = action.action # shorter name
http.feedback = main.status # http module gives status feedbacks too
# first invocation
if (conf.get("firstrun")):
config_dialog.open(None)
del conf.firstrun
|
|
|
|
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
|
#-- run main ---------------------------------------------
if __name__ == "__main__":
#-- global configuration settings
"conf = Config()" # already happened with "from config import conf"
# graphical
if len(sys.argv) < 2 or "--gtk3" in sys.argv:
# prepare for threading in Gtk+ callbacks
gobject.threads_init()
gui_startup(1/100.0)
# prepare main window
main = StreamTunerTwo()
# module coupling
action.main = main # action (play/record) module needs a reference to main window for gtk interaction and some URL/URI callbacks
action = action.action # shorter name
ahttp.feedback = main.status # http module gives status feedbacks too
# first invocation
if (conf.get("firstrun")):
config_dialog.open(None)
del conf.firstrun
|
︙ | | | ︙ | |