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": "[HTTP]", # magenta HTTP REQUEST "DATA": "[DATA]", # cyan DATA "INFO": "[INFO]", # gray INFO "STAT": "[STATE]", # 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 + 0.95999999999999996 5 station search center-on-parent dialog False center - 0.95999999999999996 True @@ -27,11 +28,10 @@ cancel False True True True - False False False @@ -41,11 +41,10 @@ False True True - False False False 1 @@ -57,11 +56,10 @@ False True True True Instead of searching in the station list, just look up the above search term on google. - False half 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 - + - + gtk-save-as @@ -2687,13 +2946,13 @@ False True False True True - + - + gtk-edit @@ -2700,14 +2959,14 @@ False True False True True - - + - + + False @@ -2741,12 +3000,12 @@ False True False True True - + @@ -2780,13 +3039,13 @@ False True False True True - + - + gtk-find @@ -2793,12 +3052,12 @@ False True False True True - + True @@ -2908,12 +3167,12 @@ False True False True True - + @@ -2944,12 +3203,12 @@ False True False Reload True - + False @@ -3058,11 +3317,10 @@ False True False - False play gtk-media-play @@ -3073,11 +3331,10 @@ False True False - False record gtk-media-record @@ -3088,11 +3345,10 @@ False True False - False station gtk-home @@ -3113,11 +3369,10 @@ False True False - False reload gtk-refresh @@ -3128,11 +3383,10 @@ False True False - False stop gtk-cancel @@ -3356,8 +3610,13 @@ end 2 + + + + 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__("→", 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