Index: action.py
==================================================================
--- action.py
+++ action.py
@@ -45,22 +45,22 @@
# web
@staticmethod
def browser(url):
- __print__( conf.browser )
+ __print__( dbg.CONF, conf.browser )
action.run(conf.browser + " " + action.quote(url))
# os shell cmd escaping
@staticmethod
def quote(s):
if conf.windows:
- return s # should actually be "\\\"%s\\\"" % s
+ return str(s) # should actually be "\\\"%s\\\"" % s
else:
- return "%r" % s
+ return "%r" % str(s)
# calls player for stream url and format
@staticmethod
def play(url, audioformat="audio/mp3", listformat="text/x-href"):
@@ -68,11 +68,11 @@
url = action.url(url, listformat)
if (audioformat):
if audioformat == "audio/mpeg":
audioformat = "audio/mp3" # internally we use the more user-friendly moniker
cmd = conf.play.get(audioformat, conf.play.get("*/*", "vlc %u"))
- __print__( "play", url, cmd )
+ __print__( dbg.PROC,"play", url, cmd )
try:
action.run( action.interpol(cmd, url) )
except:
pass
@@ -87,11 +87,11 @@
# streamripper
@staticmethod
def record(url, audioformat="audio/mp3", listformat="text/x-href", append="", row={}):
- __print__( "record", url )
+ __print__( dbg.PROC, "record", url )
cmd = conf.record.get(audioformat, conf.record.get("*/*", None))
try: action.run( action.interpol(cmd, url, row) + append )
except: pass
@@ -189,11 +189,11 @@
# download a .pls resource and extract urls
@staticmethod
def pls(url):
text = http.get(url)
- __print__( "pls_text=", text )
+ __print__( dbg.DATA, "pls_text=", text )
return re.findall("\s*File\d*\s*=\s*(\w+://[^\s]+)", text, re.I)
# currently misses out on the titles
# get a single direct ICY stream url (extract either from PLS or M3U)
@staticmethod
@@ -224,11 +224,11 @@
stream_id = stream_id and stream_id.group(1) or "XXXXXX"
try:
channelname = main.current_channel
except:
channelname = "unknown"
- return (conf.tmp + os.sep + "streamtuner2."+channelname+"."+stream_id+".m3u", len(stream_id) > 3 and stream_id != "XXXXXX")
+ return (str(conf.tmp) + os.sep + "streamtuner2."+channelname+"."+stream_id+".m3u", len(stream_id) > 3 and stream_id != "XXXXXX")
# check if there are any urls in a given file
@staticmethod
def has_urls(tmp_fn):
if os.path.exists(tmp_fn):
@@ -244,13 +244,13 @@
# does it already exist?
if tmp_fn and unique and conf.reuse_m3u and action.has_urls(tmp_fn):
return tmp_fn
# download PLS
- __print__( "pls=",pls )
+ __print__( dbg.DATA, "pls=",pls )
url_list = action.extract_urls(pls)
- __print__( "urls=", url_list )
+ __print__( dbg.DATA, "urls=", url_list )
# output URL list to temporary .m3u file
if (len(url_list)):
#tmp_fn =
f = open(tmp_fn, "w")
@@ -258,11 +258,11 @@
f.write("\n".join(url_list) + "\n")
f.close()
# return path/name of temporary file
return tmp_fn
else:
- __print__( "error, there were no URLs in ", pls )
+ __print__( dbg.ERR, "error, there were no URLs in ", pls )
raise "Empty PLS"
# open help browser
@staticmethod
def help(*args):
Index: ahttp.py
==================================================================
--- ahttp.py
+++ ahttp.py
@@ -10,11 +10,11 @@
# And a function to add trailings slashes on http URLs.
#
#
-from compat2and3 import urllib2, urlencode, urlparse, cookielib, StringIO, xrange
+from compat2and3 import urllib2, urlencode, urlparse, cookielib, StringIO, xrange, PY3
from gzip import GzipFile
from config import conf, __print__, dbg
#-- url download ---------------------------------------------
@@ -39,11 +39,11 @@
#-- GET
def get(url, maxsize=1<<19, feedback="old"):
- __print__("GET", url)
+ __print__( dbg.HTTP, "GET", url)
# statusbar info
progress_feedback(url, 0.0)
# read
@@ -67,11 +67,11 @@
# clean statusbar
progress_feedback()
# fin
- __print__(len(content))
+ __print__( dbg.INFO, "Content-Length", len(content) )
return content
@@ -124,16 +124,16 @@
if type(post) == dict:
post = urlencode(post)
request = urllib2.Request(url, post, headers)
# open url
- __print__( vars(request) )
+ __print__( dbg.INFO, vars(request) )
progress_feedback(url, 0.2)
r = urllib2.urlopen(request)
# get data
- __print__( r.info() )
+ __print__( dbg.HTTP, r.info() )
progress_feedback(0.5)
data = r.read()
progress_feedback()
return data
Index: channels/_generic.py
==================================================================
--- channels/_generic.py
+++ channels/_generic.py
@@ -70,12 +70,12 @@
# mapping of stream{} data into gtk treeview/treestore representation
datamap = [
# coltitle width [ datasrc key, type, renderer, attrs ] [cellrenderer2], ...
["", 20, ["state", str, "pixbuf", {}], ],
["Genre", 65, ['genre', str, "t", {}], ],
- ["Station Title",275,["title", str, "text", {"strikethrough":11, "cell-background":12, "cell-background-set":13}], ["favicon",gtk.gdk.Pixbuf,"pixbuf",{"width":20}], ],
- ["Now Playing",185, ["playing", str, "text", {"strikethrough":11}], ],
+ ["Station Title",275,["title", str, "text", {"strikethrough":11, "cell-background":12, "cell-background-set":13}], ["favicon", gtk.gdk.Pixbuf, "pixbuf", {}], ],
+ ["Now Playing",185, ["playing", str, "text", {"strikethrough":11}], ], #{"width":20}
["Listeners", 45, ["listeners", int, "t", {"strikethrough":11}], ],
#["Max", 45, ["max", int, "t", {}], ],
["Bitrate", 35, ["bitrate", int, "t", {}], ],
["Homepage", 160, ["homepage", str, "t", {"underline":10}], ],
[False, 25, ["url", str, "t", {"strikethrough":11}], ],
Index: config.py
==================================================================
--- config.py
+++ config.py
@@ -19,18 +19,20 @@
import sys
import pson
import gzip
import platform
+
+
#-- create a single instance of config object
conf = object()
-
#-- global configuration data ---------------------------------------------
class ConfigDict(dict):
+
# start
def __init__(self):
# object==dict means conf.var is conf["var"]
@@ -50,10 +52,11 @@
self.update(last)
# store defaults in file
else:
self.save("settings")
self.firstrun = 1
+
# some defaults
def defaults(self):
self.browser = "sensible-browser"
self.play = {
@@ -87,10 +90,11 @@
self.debug = False
self.channel_order = "shoutcast, xiph, internet_radio_org_uk, jamendo, myoggradio, .."
self.reuse_m3u = 1
self.google_homepage = 1
self.windows = platform.system()=="Windows"
+ self.debug = 1
# each plugin has a .config dict list, we add defaults here
def add_plugin_defaults(self, config, module=""):
@@ -179,15 +183,10 @@
def find_in_dirs(self, dirs, file):
for d in dirs:
if os.path.exists(d+"/"+file):
return d+"/"+file
-
-
-#-- actually fill global conf instance
-conf = ConfigDict()
-
# wrapper for all print statements
def __print__(*args):
@@ -205,7 +204,16 @@
"HTTP": "[35m[HTTP][0m", # magenta HTTP REQUEST
"DATA": "[36m[DATA][0m", # cyan DATA
"INFO": "[37m[INFO][0m", # gray INFO
"STAT": "[37m[STATE][0m", # gray CONFIG STATE
})
+
+
+
+#-- actually fill global conf instance
+conf = ConfigDict()
+if conf:
+ __print__(dbg.PROC, "ConfigDict() initialized")
+
+
Index: gtk3.xml
==================================================================
--- gtk3.xml
+++ gtk3.xml
@@ -1,17 +1,18 @@
+
-
+
False
@@ -75,11 +73,10 @@
False
True
False
True
Instead of doing a cache search, go through the search functions on the directory service homepages. (UNIMPLEMENTED)
- False
half
False
@@ -95,14 +92,13 @@
False
True
True
True
Start searching for above search term in the currently loaded station lists. Doesn't find *new* information, just looks through the known data.
- False
True
-
+
False
False
4
@@ -277,18 +273,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
all channels
False
True
True
False
- False
0.5
True
True
@@ -363,11 +493,10 @@
False
True
True
True
- False
none
False
False
@@ -379,11 +508,10 @@
in title
False
True
True
False
- False
0.5
True
True
@@ -397,11 +525,10 @@
in description
False
True
True
False
- False
0.5
True
True
@@ -415,11 +542,10 @@
any fields
False
True
True
False
- False
0.5
True
True
@@ -432,11 +558,10 @@
False
True
True
True
- False
none
False
False
@@ -462,11 +587,10 @@
False
True
True
True
- False
none
False
False
@@ -478,11 +602,10 @@
homepage url
False
True
True
False
- False
0.5
True
True
@@ -496,11 +619,10 @@
extra info
False
True
True
False
- False
0.5
True
True
@@ -513,11 +635,10 @@
and genre
False
True
True
False
- False
0.5
True
True
@@ -530,11 +651,10 @@
False
True
True
True
- False
none
False
False
@@ -690,11 +810,10 @@
cancel
False
True
True
True
- False
False
False
@@ -706,11 +825,10 @@
ok
False
True
True
True
- False
False
False
@@ -783,10 +901,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
True
True
●
@@ -799,10 +971,19 @@
2
1
2
+
+
+
+
+
+
+
+
+
@@ -1010,11 +1191,10 @@
200
20
True
True
●
- True
False
False
1
@@ -1028,11 +1208,10 @@
200
20
True
True
●
- True
False
False
1
@@ -1046,11 +1225,10 @@
200
20
True
True
●
- True
False
False
1
@@ -1064,11 +1242,10 @@
200
20
True
True
●
- True
False
False
1
@@ -1112,11 +1289,10 @@
200
20
True
True
●
- True
False
False
1
@@ -1168,11 +1344,10 @@
200
20
True
True
●
- True
False
False
1
@@ -1186,11 +1361,10 @@
200
20
True
True
●
- True
False
False
1
@@ -1215,11 +1389,10 @@
200
20
True
True
●
- True
False
False
1
@@ -1367,18 +1540,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
show bookmark star for favourites in stream lists
False
True
True
False
- False
0
True
1
@@ -1398,11 +1606,10 @@
5
●
4
120
out
- True
False
False
True
@@ -1434,11 +1641,10 @@
retain deleted stations in list
False
True
True
False
- False
0
True
1
@@ -1452,11 +1658,10 @@
display favicons for individual music stations
False
True
True
False
- False
0
True
1
@@ -1470,11 +1675,10 @@
load favicon for played stations
False
True
True
False
- False
0
True
1
@@ -1488,11 +1692,10 @@
update favorites from freshened stream urls
False
True
True
False
- False
0
True
1
@@ -1506,11 +1709,10 @@
google for homepage URL if missing
False
True
True
False
- False
0
True
1
@@ -1539,11 +1741,10 @@
True
True
●
- True
False
False
True
@@ -1564,11 +1765,10 @@
automatically save window state
False
True
True
False
- False
0
True
1
@@ -1803,10 +2003,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
True
False
Directories
@@ -1833,11 +2105,10 @@
200
20
True
True
●
- True
False
False
1
@@ -1908,11 +2179,10 @@
20
True
True
False
●
- True
False
False
1
@@ -1926,11 +2196,10 @@
reuse temporary .m3u files
False
True
True
False
- False
0.5
True
1
@@ -2244,11 +2513,10 @@
100
35
True
True
True
- False
False
True
@@ -2262,11 +2530,10 @@
100
35
True
True
True
- False
True
True
@@ -2293,16 +2560,16 @@
False
+ 0.94999999999999996
5
inspect/edit stream data
center-on-parent
True
False
- 0.94999999999999996
True
False
@@ -2492,11 +2759,10 @@
100
25
True
True
True
- False
100
10
@@ -2510,11 +2776,10 @@
25
True
True
True
Save changes.
- False
210
10
@@ -2527,11 +2792,10 @@
50
25
True
True
True
- False
5
10
@@ -2631,15 +2895,10 @@
streamtuner2
980
775
/usr/share/pixmaps/streamtuner2.png
applications-multimedia
-
-
- streamtuner2
-
-
True
False
@@ -2674,13 +2933,13 @@
False
True
False
bookmark
True
-
+
-
+
@@ -2780,13 +3039,13 @@
False
True
False
True
True
-
+
-
+
@@ -2944,12 +3203,12 @@
False
True
False
Reload
True
-
+
+
+
+
+ streamtuner2
+
Index: mygtk.py
==================================================================
--- mygtk.py
+++ mygtk.py
@@ -133,11 +133,11 @@
for attr,val in list(cell[3].items()):
col.add_attribute(rend, attr, val)
# next
datapos += 1
- __print__(dbg.INFO, cell)
+ __print__(dbg.INFO, cell, len(cell))
# add column to treeview
widget.append_column(col)
# finalize widget
widget.set_search_column(5) #??
widget.set_search_column(4) #??
@@ -156,12 +156,12 @@
for var in xrange(2, len(desc)):
vartypes.append(desc[var][1]) # content types
rowmap.append(desc[var][0]) # dict{} column keys in entries[] list
# create gtk array storage
ls = gtk.ListStore(*vartypes) # could be a TreeStore, too
- __print__(dbg.UI, vartypes)
- __print__(dbg.DATA, rowmap)
+ __print__(dbg.UI, vartypes, len(vartypes))
+ __print__(dbg.DATA, rowmap, len(rowmap))
# prepare for missing values, and special variable types
defaults = {
str: "",
unicode: "",
@@ -173,18 +173,21 @@
pix_entry = vartypes.index(gtk.gdk.Pixbuf)
# sort data into gtk liststore array
for row in entries:
- # defaults
- row["deleted"] = 0
- row["search_col"] = "#ffffff"
- row["search_set"] = 0
+ # preset some values if absent
+ row.setdefault("deleted", False)
+ row.setdefault("search_col", "#ffffff")
+ row.setdefault("search_set", False)
# generate ordered list from dictionary, using rowmap association
row = [ row.get( skey , defaults[vartypes[i]] ) for i,skey in enumerate(rowmap) ]
+ # map Python2 unicode to str
+ row = [ str(value) if type(value) is unicode else value for value in row ]
+
# autotransform string -> gtk image object
if (pix_entry and type(row[pix_entry]) == str):
row[pix_entry] = ( gtk.gdk.pixbuf_new_from_file(row[pix_entry]) if os.path.exists(row[pix_entry]) else defaults[gtk.gdk.Pixbuf] )
try:
@@ -192,11 +195,11 @@
ls.append(row) # had to be adapted for real TreeStore (would require additional input for grouping/level/parents)
except:
# brute-force typecast
ls.append( [va if ty==gtk.gdk.Pixbuf else ty(va) for va,ty in zip(row,vartypes)] )
- __print__(row)
+ __print__("[37m→[0m", row, len(row))
# apply array to widget
widget.set_model(ls)
return ls
@@ -213,18 +216,19 @@
@staticmethod
def tree(widget, entries, title="category", icon=gtk.STOCK_DIRECTORY):
# list types
ls = gtk.TreeStore(str, str)
+ print(entries)
# add entries
for entry in entries:
- if (type(entry) == str):
- main = ls.append(None, [entry, icon])
+ if isinstance(entry, (str,unicode)):
+ main = ls.append(None, [str(entry), icon])
else:
for sub_title in entry:
- ls.append(main, [sub_title, icon])
+ ls.append(main, [str(sub_title), icon])
# just one column
tvcolumn = gtk.TreeViewColumn(title);
widget.append_column(tvcolumn)
@@ -308,11 +312,11 @@
for wn in r.keys(): # widgetnames
w = wTree.get_widget(wn)
if (not w):
continue
t = type(w)
- for method,args in r[wn].iteritems():
+ for method,args in r[wn].items():
# gtk.Window
if method == "size":
w.resize(args[0], args[1])
# gtk.TreeView
if method == "columns:width":
Index: pson.py
==================================================================
--- pson.py
+++ pson.py
@@ -14,10 +14,15 @@
# eval() and Python notation. (The representations are close.)
#
# Additionally it filters out any left-over objects. Sometimes
# pygtk-objects crawled into the streams[] lists, because rows
# might have been queried from the widgets.
+# (Need to find out if that still happens..)
+#
+# filter_data should become redundant, as mygtk.columns now
+# converts unicode to str in Py2. And since we depend on Py2.7
+# anway the JSON-like Python serialization should be dropped.
#
#-- reading and writing json (for the config module) ----------------------------------
@@ -59,11 +64,11 @@
# load from filepointer, decode string into dicts/list
def load(fp):
try:
#print("try json")
r = json_load(fp)
- r = filter_data(r) # turn unicode() strings back into str() - pygtk does not accept u"strings"
+# r = filter_data(r) # turn unicode() strings back into str() - pygtk does not accept u"strings"
except:
#print("fall back on pson")
fp.seek(0)
r = eval(fp.read(1<<27)) # max 128 MB
# print("fake json module: in python variable dump notation")
Index: st2.py
==================================================================
--- st2.py
+++ st2.py
@@ -3,11 +3,11 @@
# 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.6
+# version: 2.0.9.7
# author: mario salzer
# license: public domain
# url: http://freshmeat.net/projects/streamtuner2
# config:
# category: multimedia
@@ -78,12 +78,10 @@
# 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:
@@ -90,11 +88,11 @@
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
+sys.path.insert(0, ".") # development module path
# gtk modules
from mygtk import pygtk, gtk, gobject, ui_file, mygtk, ver as GTK_VER
# custom modules
@@ -102,11 +100,10 @@
from config import __print__, dbg
import ahttp
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
@@ -561,14 +558,15 @@
# 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)
- if GTK_VER == 2:
- main.streamactions.popup(None, None, None, event.button, event.time)
- else:
- main.streamactions.popup(None, None, None, None, event.button, event.time)
+ 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
@@ -776,11 +774,11 @@
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():
+ 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
@@ -832,11 +830,11 @@
once = 0
def add_plugins(self):
if self.once:
return
- for name,enabled in conf.plugins.iteritems():
+ 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)
@@ -1051,12 +1049,12 @@
# 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():
+ 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